From 8de895c1cf5e1ffb365bde56853d03e0d6ef1433 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 9 Mar 2021 13:06:40 +0100 Subject: [PATCH 01/57] doc: warn about SuppressDestruct() Not sure how this ever made its way into the API, but people should *never* use this. --- napi.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/napi.h b/napi.h index 3db1b6da6..0227ce561 100644 --- a/napi.h +++ b/napi.h @@ -1178,6 +1178,8 @@ namespace Napi { // Call this on a reference that is declared as static data, to prevent its destructor // from running at program shutdown time, which would attempt to reset the reference when // the environment is no longer valid. + // Avoid using this if at all possible. If you do need to use static data, + // MAKE SURE to warn your users that your addon is NOT threadsafe. void SuppressDestruct(); protected: From 97a0eb374cd6fc8297d02fa40acdcad1dc340419 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 9 Mar 2021 13:09:42 +0100 Subject: [PATCH 02/57] fixup --- napi.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/napi.h b/napi.h index 0227ce561..f95da7402 100644 --- a/napi.h +++ b/napi.h @@ -1175,11 +1175,11 @@ namespace Napi { void Reset(); void Reset(const T& value, uint32_t refcount = 0); - // Call this on a reference that is declared as static data, to prevent its destructor - // from running at program shutdown time, which would attempt to reset the reference when - // the environment is no longer valid. - // Avoid using this if at all possible. If you do need to use static data, - // MAKE SURE to warn your users that your addon is NOT threadsafe. + // Call this on a reference that is declared as static data, to prevent its + // destructor from running at program shutdown time, which would attempt to + // reset the reference when the environment is no longer valid. Avoid using + // this if at all possible. If you do need to use static data, MAKE SURE to + // warn your users that your addon is NOT threadsafe. void SuppressDestruct(); protected: From 1647b21eff3f77d3fbd9ae77f4483edee595772b Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 9 Mar 2021 13:10:45 +0100 Subject: [PATCH 03/57] fixup --- doc/reference.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/reference.md b/doc/reference.md index 108c009bb..b1033775f 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -4,7 +4,7 @@ Holds a counted reference to a [`Napi::Value`](value.md) object; initially a wea The referenced `Napi::Value` is not immediately destroyed when the reference count is zero; it is merely then eligible for garbage-collection if there are no other references to the `Napi::Value`. -`Napi::Reference` objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. +`Napi::Reference` objects allocated in static space, such as a global static instance, must call the `SuppressDestruct` method to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. Avoid using this if at all possible. The following classes inherit, either directly or indirectly, from `Napi::Reference`: @@ -109,3 +109,5 @@ void Napi::Reference::SuppressDestruct(); ``` Call this method on a `Napi::Reference` that is declared as static data to prevent its destructor, running at program shutdown time, from attempting to reset the reference when the environment is no longer valid. + + Avoid using this if at all possible. If you do need to use static data, **MAKE SURE** to warn your users that your addon is **NOT** threadsafe. From 09f39def6a7d11747602a34e03333cdc69d8cc9e Mon Sep 17 00:00:00 2001 From: JoseExposito Date: Wed, 10 Mar 2021 07:37:07 +0100 Subject: [PATCH 04/57] test: dd tests for Function constructors PR-URL: https://github.com/nodejs/node-addon-api/pull/937 Reviewed-By: Michael Dawson --- test/function.cc | 126 ++++++++++++++++++++++++++++++++++++++++++++++- test/function.js | 51 ++++++++++++++++--- 2 files changed, 168 insertions(+), 9 deletions(-) diff --git a/test/function.cc b/test/function.cc index b0ae92c7d..b45e91dd1 100644 --- a/test/function.cc +++ b/test/function.cc @@ -6,6 +6,13 @@ namespace { int testData = 1; +Boolean EmptyConstructor(const CallbackInfo& info) { + auto env = info.Env(); + bool isEmpty = info[0].As(); + Function function = isEmpty ? Function() : Function(env, Object::New(env)); + return Boolean::New(env, function.IsEmpty()); +} + void VoidCallback(const CallbackInfo& info) { auto env = info.Env(); Object obj = info[0].As(); @@ -45,8 +52,9 @@ Value ValueCallbackWithData(const CallbackInfo& info) { } Value CallWithArgs(const CallbackInfo& info) { - Function func = info[0].As(); - return func({ info[1], info[2], info[3] }); + Function func = info[0].As(); + return func.Call( + std::initializer_list{info[1], info[2], info[3]}); } Value CallWithVector(const CallbackInfo& info) { @@ -59,6 +67,27 @@ Value CallWithVector(const CallbackInfo& info) { return func.Call(args); } +Value CallWithCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return func.Call(args.size(), args.data()); +} + +Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + Value receiver = info[1]; + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + return func.Call(receiver, args.size(), args.data()); +} + Value CallWithReceiverAndArgs(const CallbackInfo& info) { Function func = info[0].As(); Value receiver = info[1]; @@ -96,17 +125,81 @@ Value CallConstructorWithVector(const CallbackInfo& info) { return func.New(args); } +Value CallConstructorWithCStyleArray(const CallbackInfo& info) { + Function func = info[0].As(); + std::vector args; + args.reserve(3); + args.push_back(info[1]); + args.push_back(info[2]); + args.push_back(info[3]); + return func.New(args.size(), args.data()); +} + void IsConstructCall(const CallbackInfo& info) { Function callback = info[0].As(); bool isConstructCall = info.IsConstructCall(); callback({Napi::Boolean::New(info.Env(), isConstructCall)}); } +void MakeCallbackWithArgs(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + callback.MakeCallback( + resource, + std::initializer_list{info[2], info[3], info[4]}, + context); +} + +void MakeCallbackWithVector(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + callback.MakeCallback(resource, args, context); +} + +void MakeCallbackWithCStyleArray(const CallbackInfo& info) { + Env env = info.Env(); + Function callback = info[0].As(); + Object resource = info[1].As(); + + AsyncContext context(env, "function_test_context", resource); + + std::vector args; + args.reserve(3); + args.push_back(info[2]); + args.push_back(info[3]); + args.push_back(info[4]); + callback.MakeCallback(resource, args.size(), args.data(), context); +} + +void MakeCallbackWithInvalidReceiver(const CallbackInfo& info) { + Function callback = info[0].As(); + callback.MakeCallback(Value(), std::initializer_list{}); +} + +Value CallWithFunctionOperator(const CallbackInfo& info) { + Function func = info[0].As(); + return func({info[1], info[2], info[3]}); +} + } // end anonymous namespace Object InitFunction(Env env) { Object result = Object::New(env); Object exports = Object::New(env); + exports["emptyConstructor"] = Function::New(env, EmptyConstructor); exports["voidCallback"] = Function::New(env, VoidCallback, "voidCallback"); exports["valueCallback"] = Function::New(env, ValueCallback, std::string("valueCallback")); exports["voidCallbackWithData"] = @@ -115,15 +208,30 @@ Object InitFunction(Env env) { Function::New(env, ValueCallbackWithData, nullptr, &testData); exports["callWithArgs"] = Function::New(env, CallWithArgs); exports["callWithVector"] = Function::New(env, CallWithVector); + exports["callWithCStyleArray"] = Function::New(env, CallWithCStyleArray); + exports["callWithReceiverAndCStyleArray"] = + Function::New(env, CallWithReceiverAndCStyleArray); exports["callWithReceiverAndArgs"] = Function::New(env, CallWithReceiverAndArgs); exports["callWithReceiverAndVector"] = Function::New(env, CallWithReceiverAndVector); exports["callWithInvalidReceiver"] = Function::New(env, CallWithInvalidReceiver); exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs); exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector); + exports["callConstructorWithCStyleArray"] = + Function::New(env, CallConstructorWithCStyleArray); exports["isConstructCall"] = Function::New(env, IsConstructCall); + exports["makeCallbackWithArgs"] = Function::New(env, MakeCallbackWithArgs); + exports["makeCallbackWithVector"] = + Function::New(env, MakeCallbackWithVector); + exports["makeCallbackWithCStyleArray"] = + Function::New(env, MakeCallbackWithCStyleArray); + exports["makeCallbackWithInvalidReceiver"] = + Function::New(env, MakeCallbackWithInvalidReceiver); + exports["callWithFunctionOperator"] = + Function::New(env, CallWithFunctionOperator); result["plain"] = exports; exports = Object::New(env); + exports["emptyConstructor"] = Function::New(env, EmptyConstructor); exports["voidCallback"] = Function::New(env, "voidCallback"); exports["valueCallback"] = Function::New(env, std::string("valueCallback")); @@ -133,6 +241,9 @@ Object InitFunction(Env env) { Function::New(env, nullptr, &testData); exports["callWithArgs"] = Function::New(env); exports["callWithVector"] = Function::New(env); + exports["callWithCStyleArray"] = Function::New(env); + exports["callWithReceiverAndCStyleArray"] = + Function::New(env); exports["callWithReceiverAndArgs"] = Function::New(env); exports["callWithReceiverAndVector"] = @@ -143,7 +254,18 @@ Object InitFunction(Env env) { Function::New(env); exports["callConstructorWithVector"] = Function::New(env); + exports["callConstructorWithCStyleArray"] = + Function::New(env); exports["isConstructCall"] = Function::New(env); + exports["makeCallbackWithArgs"] = Function::New(env); + exports["makeCallbackWithVector"] = + Function::New(env); + exports["makeCallbackWithCStyleArray"] = + Function::New(env); + exports["makeCallbackWithInvalidReceiver"] = + Function::New(env); + exports["callWithFunctionOperator"] = + Function::New(env); result["templated"] = exports; return result; } diff --git a/test/function.js b/test/function.js index 8ab742c27..d98b1944d 100644 --- a/test/function.js +++ b/test/function.js @@ -8,6 +8,9 @@ test(require(`./build/${buildType}/binding.node`).function.templated); test(require(`./build/${buildType}/binding_noexcept.node`).function.templated); function test(binding) { + assert.strictEqual(binding.emptyConstructor(true), true); + assert.strictEqual(binding.emptyConstructor(false), false); + let obj = {}; assert.deepStrictEqual(binding.voidCallback(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar" }); @@ -26,26 +29,50 @@ function test(binding) { args = [].slice.call(arguments); } + function makeCallbackTestFunction(receiver, expectedOne, expectedTwo, expectedThree) { + return function callback(one, two, three) { + assert.strictEqual(this, receiver); + assert.strictEqual(one, expectedOne); + assert.strictEqual(two, expectedTwo); + assert.strictEqual(three, expectedThree); + } + } + ret = 4; - assert.equal(binding.callWithArgs(testFunction, 1, 2, 3), 4); + assert.strictEqual(binding.callWithArgs(testFunction, 1, 2, 3), 4); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 1, 2, 3 ]); ret = 5; - assert.equal(binding.callWithVector(testFunction, 2, 3, 4), 5); + assert.strictEqual(binding.callWithVector(testFunction, 2, 3, 4), 5); assert.strictEqual(receiver, undefined); assert.deepStrictEqual(args, [ 2, 3, 4 ]); ret = 6; - assert.equal(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.strictEqual(binding.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 3, 4, 5 ]); ret = 7; - assert.equal(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); + assert.strictEqual(binding.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); assert.deepStrictEqual(receiver, obj); assert.deepStrictEqual(args, [ 4, 5, 6 ]); + ret = 8; + assert.strictEqual(binding.callWithCStyleArray(testFunction, 5, 6, 7), ret); + assert.deepStrictEqual(receiver, undefined); + assert.deepStrictEqual(args, [ 5, 6, 7 ]); + + ret = 9; + assert.strictEqual(binding.callWithReceiverAndCStyleArray(testFunction, obj, 6, 7, 8), ret); + assert.deepStrictEqual(receiver, obj); + assert.deepStrictEqual(args, [ 6, 7, 8 ]); + + ret = 10; + assert.strictEqual(binding.callWithFunctionOperator(testFunction, 7, 8, 9), ret); + assert.strictEqual(receiver, undefined); + assert.deepStrictEqual(args, [ 7, 8, 9 ]); + assert.throws(() => { binding.callWithInvalidReceiver(); }, /Invalid (pointer passed as )?argument/); @@ -58,14 +85,18 @@ function test(binding) { assert(obj instanceof testConstructor); assert.deepStrictEqual(args, [ 6, 7, 8 ]); + obj = binding.callConstructorWithCStyleArray(testConstructor, 7, 8, 9); + assert(obj instanceof testConstructor); + assert.deepStrictEqual(args, [ 7, 8, 9 ]); + obj = {}; assert.deepStrictEqual(binding.voidCallbackWithData(obj), undefined); assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); assert.deepStrictEqual(binding.valueCallbackWithData(), { "foo": "bar", "data": 1 }); - assert.equal(binding.voidCallback.name, 'voidCallback'); - assert.equal(binding.valueCallback.name, 'valueCallback'); + assert.strictEqual(binding.voidCallback.name, 'voidCallback'); + assert.strictEqual(binding.valueCallback.name, 'valueCallback'); let testConstructCall = undefined; binding.isConstructCall((result) => { testConstructCall = result; }); @@ -73,5 +104,11 @@ function test(binding) { new binding.isConstructCall((result) => { testConstructCall = result; }); assert.ok(testConstructCall); - // TODO: Function::MakeCallback tests + obj = {}; + binding.makeCallbackWithArgs(makeCallbackTestFunction(obj, "1", "2", "3"), obj, "1", "2", "3"); + binding.makeCallbackWithVector(makeCallbackTestFunction(obj, 4, 5, 6), obj, 4, 5, 6); + binding.makeCallbackWithCStyleArray(makeCallbackTestFunction(obj, 7, 8, 9), obj, 7, 8, 9); + assert.throws(() => { + binding.makeCallbackWithInvalidReceiver(() => {}); + }); } From cf1c43b49e7f4df6a8cd27e2e5bcbd202dfc6f7d Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 23 Mar 2021 04:37:25 +0800 Subject: [PATCH 05/57] chore: fixup linter commands (#940) --- package.json | 4 ++-- tools/clang-format.js | 30 +++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 7538c84f9..13138fec2 100644 --- a/package.json +++ b/package.json @@ -344,8 +344,8 @@ "predev:incremental": "node-gyp configure build -C test --debug", "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile", - "lint": "node tools/clang-format.js", - "lint:fix": "git-clang-format '*.h', '*.cc'" + "lint": "node tools/clang-format", + "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", "version": "3.1.0", diff --git a/tools/clang-format.js b/tools/clang-format.js index 3bf6f48e8..e97098336 100644 --- a/tools/clang-format.js +++ b/tools/clang-format.js @@ -7,17 +7,31 @@ const filesToCheck = ['*.h', '*.cc']; const CLANG_FORMAT_START = process.env.CLANG_FORMAT_START || 'main'; function main(args) { + let fix = false; + while (args.length > 0) { + switch (args[0]) { + case '-f': + case '--fix': + fix = true; + default: + } + args.shift(); + } + let clangFormatPath = path.dirname(require.resolve('clang-format')); const options = ['--binary=node_modules/.bin/clang-format', '--style=file']; + if (fix) { + options.push(CLANG_FORMAT_START); + } else { + options.push('--diff', CLANG_FORMAT_START); + } const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); const result = spawn('python', [ gitClangFormatPath, ...options, - '--diff', - CLANG_FORMAT_START, - 'HEAD', + '--', ...filesToCheck ], { encoding: 'utf-8' }); @@ -27,13 +41,19 @@ function main(args) { } const clangFormatOutput = result.stdout.trim(); + // Bail fast if in fix mode. + if (fix) { + console.log(clangFormatOutput); + return 0; + } + // Detect if there is any complains from clang-format if (clangFormatOutput !== '' && clangFormatOutput !== ('no modified files to format') && clangFormatOutput !== ('clang-format did not modify any files')) { console.error(clangFormatOutput); - const fixCmd = '"npm run lint:fix"'; + const fixCmd = 'npm run lint:fix'; console.error(` - ERROR: please run ${fixCmd} to format changes in your commit + ERROR: please run "${fixCmd}" to format changes in your commit Note that when running the command locally, please keep your local main branch and working branch up to date with nodejs/node-addon-api to exclude un-related complains. From e320666301563a31b7db7fbc891d27e843ccfa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 22 Mar 2021 21:38:54 +0100 Subject: [PATCH 06/57] Fix tab indent (#938) * doc: fix tab indent --- doc/object.md | 4 ++-- doc/property_descriptor.md | 4 ++-- napi-inl.h | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/object.md b/doc/object.md index 3ce7f75eb..28d54a227 100644 --- a/doc/object.md +++ b/doc/object.md @@ -218,8 +218,8 @@ Define a property on the object. void Napi::Object::DefineProperties (____ properties) ``` - `[in] properties`: A list of [`Napi::PropertyDescriptor`](property_descriptor.md). Can be one of the following types: - - const std::initializer_list& - - const std::vector& + - const std::initializer_list& + - const std::vector& Defines properties on the object. diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 793e53626..1a7e4f940 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -220,7 +220,7 @@ The name of the property can be any of the following types: static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function (___ name, Callable cb, napi_property_attributes attributes = napi_default, - void *data = nullptr); + void *data = nullptr); ``` * `[in] name`: The name of the Callable function. @@ -244,7 +244,7 @@ static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( ___ name, Callable cb, napi_property_attributes attributes = napi_default, - void *data = nullptr); + void *data = nullptr); ``` * `[in] env`: The environment in which to create this accessor. diff --git a/napi-inl.h b/napi-inl.h index a5bdff854..178807815 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4148,10 +4148,9 @@ inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) } inline AsyncContext::AsyncContext(napi_env env, - const char* resource_name, + const char* resource_name, const Object& resource) - : _env(env), - _context(nullptr) { + : _env(env), _context(nullptr) { napi_value resource_id; napi_status status = napi_create_string_utf8( _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); From 666e9ffaa99efa4a27cfa2e1b95ef3312dd7a42c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Mon, 22 Mar 2021 21:39:48 +0100 Subject: [PATCH 07/57] doc: sync Object::Set value arg with Value::From (#933) --- doc/object.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/doc/object.md b/doc/object.md index 28d54a227..ce5430cc4 100644 --- a/doc/object.md +++ b/doc/object.md @@ -86,13 +86,7 @@ The key can be any of the following types: - `const std::string&` - `uint32_t` -While the value must be any of the following types: -- `napi_value` -- [`Napi::Value`](value.md) -- `const char*` -- `std::string&` -- `bool` -- `double` +The `value` can be of any type that is accepted by [`Napi::Value::From`][]. ### Delete() @@ -271,3 +265,4 @@ Napi::Value Napi::Object::operator[] (uint32_t index) const; Returns an indexed property or array element as a [`Napi::Value`](value.md). [`Napi::Value`]: ./value.md +[`Napi::Value::From`]: ./value.md#from From 81d41ee96d394facfab1ec3881d368eee0739da8 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Mon, 22 Mar 2021 21:40:23 +0100 Subject: [PATCH 08/57] doc: added some warnings for buffer and array buffer factory method. (#929) * Fixes issue 258. --- doc/array_buffer.md | 6 ++++++ doc/buffer.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/doc/array_buffer.md b/doc/array_buffer.md index 346fe6ace..b089b0dfe 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -29,6 +29,12 @@ The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it to be valid for the lifetime of the instance. Since the `Napi::ArrayBuffer` is subject to garbage collection this overload is only suitable for data which is static and never needs to be freed. +This factory method will not provide the caller with an opportunity to free the +data when the `Napi::ArrayBuffer` gets garbage-collected. If you need to free +the data retained by the `Napi::ArrayBuffer` object please use other +variants of the `Napi::ArrayBuffer::New` factory method that accept +`Napi::Finalizer`, which is a function that will be invoked when the +`Napi::ArrayBuffer` object has been destroyed. ```cpp static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength); diff --git a/doc/buffer.md b/doc/buffer.md index 97ed48a5a..55e26e90b 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -28,6 +28,12 @@ The `Napi::Buffer` object does not assume ownership for the data and expects it valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage collection this overload is only suitable for data which is static and never needs to be freed. +This factory method will not provide the caller with an opportunity to free the +data when the `Napi::Buffer` gets garbage-collected. If you need to free the +data retained by the `Napi::Buffer` object please use other variants of the +`Napi::Buffer::New` factory method that accept `Napi::Finalizer`, which is a +function that will be invoked when the `Napi::Buffer` object has been +destroyed. ```cpp static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length); From d198de0e32023c23349a474951fba0e43fdf42e4 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 24 Mar 2021 21:49:37 +0100 Subject: [PATCH 09/57] build: add CI configuration for Windows Added windows-2016 as virtual environment. PR-URL: https://github.com/nodejs/node-addon-api/pull/948 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- .github/workflows/ci-win.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci-win.yml diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml new file mode 100644 index 000000000..29e89fe47 --- /dev/null +++ b/.github/workflows/ci-win.yml @@ -0,0 +1,31 @@ +name: Node.js CI Windows Platform + +on: [push, pull_request] + +jobs: + test: + timeout-minutes: 30 + strategy: + matrix: + node-version: [10.x, 12.x, 14.x, 15.x] + os: + - windows-latest + - windows-2016 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2.1.5 + with: + node-version: ${{ matrix.node-version }} + - name: Check Node.js installation + run: | + node --version + npm --version + - name: Install dependencies + run: | + npm install + - name: npm test + run: | + npm run pretest -- --verbose + node test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 193f09384..2ad80e98b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Node.js CI +name: Node.js CI Unix Platform on: [push, pull_request] From 5110815318a39642d9b62a7edfa429e2a7152c7f Mon Sep 17 00:00:00 2001 From: NickNaso Date: Thu, 25 Mar 2021 20:39:06 +0100 Subject: [PATCH 10/57] src: rename N-API with Node-API on comments Also fixed lint errors. Fixes: https://github.com/nodejs/node-addon-api/issues/952 PR-URL: https://github.com/nodejs/node-addon-api/pull/953 Reviewed-By: Chengzhong Wu Reviewed-By: Gabriel Schulhof --- napi-inl.h | 5 +- napi.h | 596 +++++++++++++++++++++++++++----------------------- package.json | 3 +- test/error.cc | 2 +- test/index.js | 2 +- 5 files changed, 333 insertions(+), 275 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 178807815..0177c7d6b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2,9 +2,10 @@ #define SRC_NAPI_INL_H_ //////////////////////////////////////////////////////////////////////////////// -// N-API C++ Wrapper Classes +// Node-API C++ Wrapper Classes // -// Inline header-only implementations for "N-API" ABI-stable C APIs for Node.js. +// Inline header-only implementations for "Node-API" ABI-stable C APIs for +// Node.js. //////////////////////////////////////////////////////////////////////////////// // Note: Do not include this file directly! Include "napi.h" instead. diff --git a/napi.h b/napi.h index f95da7402..8eeb1443b 100644 --- a/napi.h +++ b/napi.h @@ -106,9 +106,9 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 } while (0) //////////////////////////////////////////////////////////////////////////////// -/// N-API C++ Wrapper Classes +/// Node-API C++ Wrapper Classes /// -/// These classes wrap the "N-API" ABI-stable C APIs for Node.js, providing a +/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a /// C++ object model and C++ exception-handling semantics with low overhead. /// The wrappers are all header-only so that they do not affect the ABI. //////////////////////////////////////////////////////////////////////////////// @@ -159,22 +159,25 @@ namespace Napi { TypedArrayOf; ///< Typed array of unsigned 64-bit integers #endif // NAPI_VERSION > 5 - /// Defines the signature of a N-API C++ module's registration callback (init) function. + /// Defines the signature of a Node-API C++ module's registration callback + /// (init) function. using ModuleRegisterCallback = Object (*)(Env env, Object exports); class MemoryManagement; - /// Environment for N-API values and operations. + /// Environment for Node-API values and operations. /// - /// All N-API values and operations must be associated with an environment. An environment - /// instance is always provided to callback functions; that environment must then be used for any - /// creation of N-API values or other N-API operations within the callback. (Many methods infer - /// the environment from the `this` instance that the method is called on.) + /// All Node-API values and operations must be associated with an environment. + /// An environment instance is always provided to callback functions; that + /// environment must then be used for any creation of Node-API values or other + /// Node-API operations within the callback. (Many methods infer the + /// environment from the `this` instance that the method is called on.) /// - /// In the future, multiple environments per process may be supported, although current - /// implementations only support one environment per process. + /// In the future, multiple environments per process may be supported, + /// although current implementations only support one environment per process. /// - /// In the V8 JavaScript engine, a N-API environment approximately corresponds to an Isolate. + /// In the V8 JavaScript engine, a Node-API environment approximately + /// corresponds to an Isolate. class Env { #if NAPI_VERSION > 5 private: @@ -232,7 +235,8 @@ namespace Napi { class Value { public: Value(); ///< Creates a new _empty_ Value instance. - Value(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + Value(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. /// Creates a JS value from a C++ primitive. /// @@ -249,7 +253,7 @@ namespace Napi { template static Value From(napi_env env, const T& value); - /// Converts to a N-API value primitive. + /// Converts to a Node-API value primitive. /// /// If the instance is _empty_, this returns `nullptr`. operator napi_value() const; @@ -322,80 +326,92 @@ namespace Napi { /// A JavaScript boolean value. class Boolean : public Value { public: - static Boolean New( - napi_env env, ///< N-API environment - bool value ///< Boolean value - ); + static Boolean New(napi_env env, ///< Node-API environment + bool value ///< Boolean value + ); - Boolean(); ///< Creates a new _empty_ Boolean instance. - Boolean(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + Boolean(); ///< Creates a new _empty_ Boolean instance. + Boolean(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. - operator bool() const; ///< Converts a Boolean value to a boolean primitive. - bool Value() const; ///< Converts a Boolean value to a boolean primitive. + operator bool() const; ///< Converts a Boolean value to a boolean primitive. + bool Value() const; ///< Converts a Boolean value to a boolean primitive. }; /// A JavaScript number value. class Number : public Value { public: - static Number New( - napi_env env, ///< N-API environment - double value ///< Number value - ); - - Number(); ///< Creates a new _empty_ Number instance. - Number(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - operator int32_t() const; ///< Converts a Number value to a 32-bit signed integer value. - operator uint32_t() const; ///< Converts a Number value to a 32-bit unsigned integer value. - operator int64_t() const; ///< Converts a Number value to a 64-bit signed integer value. - operator float() const; ///< Converts a Number value to a 32-bit floating-point value. - operator double() const; ///< Converts a Number value to a 64-bit floating-point value. - - int32_t Int32Value() const; ///< Converts a Number value to a 32-bit signed integer value. - uint32_t Uint32Value() const; ///< Converts a Number value to a 32-bit unsigned integer value. - int64_t Int64Value() const; ///< Converts a Number value to a 64-bit signed integer value. - float FloatValue() const; ///< Converts a Number value to a 32-bit floating-point value. - double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. + static Number New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + Number(); ///< Creates a new _empty_ Number instance. + Number(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator int32_t() + const; ///< Converts a Number value to a 32-bit signed integer value. + operator uint32_t() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + operator int64_t() + const; ///< Converts a Number value to a 64-bit signed integer value. + operator float() + const; ///< Converts a Number value to a 32-bit floating-point value. + operator double() + const; ///< Converts a Number value to a 64-bit floating-point value. + + int32_t Int32Value() + const; ///< Converts a Number value to a 32-bit signed integer value. + uint32_t Uint32Value() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + int64_t Int64Value() + const; ///< Converts a Number value to a 64-bit signed integer value. + float FloatValue() + const; ///< Converts a Number value to a 32-bit floating-point value. + double DoubleValue() + const; ///< Converts a Number value to a 64-bit floating-point value. }; #if NAPI_VERSION > 5 /// A JavaScript bigint value. class BigInt : public Value { public: - static BigInt New( - napi_env env, ///< N-API environment - int64_t value ///< Number value - ); - static BigInt New( - napi_env env, ///< N-API environment - uint64_t value ///< Number value - ); - - /// Creates a new BigInt object using a specified sign bit and a - /// specified list of digits/words. - /// The resulting number is calculated as: - /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) - static BigInt New( - napi_env env, ///< N-API environment - int sign_bit, ///< Sign bit. 1 if negative. - size_t word_count, ///< Number of words in array - const uint64_t* words ///< Array of words - ); - - BigInt(); ///< Creates a new _empty_ BigInt instance. - BigInt(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - int64_t Int64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit signed integer value. - uint64_t Uint64Value(bool* lossless) const; ///< Converts a BigInt value to a 64-bit unsigned integer value. - - size_t WordCount() const; ///< The number of 64-bit words needed to store the result of ToWords(). - - /// Writes the contents of this BigInt to a specified memory location. - /// `sign_bit` must be provided and will be set to 1 if this BigInt is negative. - /// `*word_count` has to be initialized to the length of the `words` array. - /// Upon return, it will be set to the actual number of words that would - /// be needed to store this BigInt (i.e. the return value of `WordCount()`). - void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); + static BigInt New(napi_env env, ///< Node-API environment + int64_t value ///< Number value + ); + static BigInt New(napi_env env, ///< Node-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New(napi_env env, ///< Node-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + int64_t Int64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store + ///< the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is + /// negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); }; #endif // NAPI_VERSION > 5 @@ -404,16 +420,15 @@ namespace Napi { class Date : public Value { public: /// Creates a new Date value from a double primitive. - static Date New( - napi_env env, ///< N-API environment - double value ///< Number value - ); + static Date New(napi_env env, ///< Node-API environment + double value ///< Number value + ); - Date(); ///< Creates a new _empty_ Date instance. - Date(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - operator double() const; ///< Converts a Date value to double primitive + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + operator double() const; ///< Converts a Date value to double primitive - double ValueOf() const; ///< Converts a Date value to a double primitive. + double ValueOf() const; ///< Converts a Date value to a double primitive. }; #endif @@ -421,102 +436,111 @@ namespace Napi { class Name : public Value { public: Name(); ///< Creates a new _empty_ Name instance. - Name(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + Name(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. }; /// A JavaScript string value. class String : public Name { public: /// Creates a new String value from a UTF-8 encoded C++ string. - static String New( - napi_env env, ///< N-API environment - const std::string& value ///< UTF-8 encoded C++ string - ); - - /// Creates a new String value from a UTF-16 encoded C++ string. - static String New( - napi_env env, ///< N-API environment - const std::u16string& value ///< UTF-16 encoded C++ string - ); - - /// Creates a new String value from a UTF-8 encoded C string. - static String New( - napi_env env, ///< N-API environment - const char* value ///< UTF-8 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-16 encoded C string. - static String New( - napi_env env, ///< N-API environment - const char16_t* value ///< UTF-16 encoded null-terminated C string - ); - - /// Creates a new String value from a UTF-8 encoded C string with specified length. - static String New( - napi_env env, ///< N-API environment - const char* value, ///< UTF-8 encoded C string (not necessarily null-terminated) - size_t length ///< length of the string in bytes - ); - - /// Creates a new String value from a UTF-16 encoded C string with specified length. - static String New( - napi_env env, ///< N-API environment - const char16_t* value, ///< UTF-16 encoded C string (not necessarily null-terminated) - size_t length ///< Length of the string in 2-byte code units - ); - - /// Creates a new String based on the original object's type. - /// - /// `value` may be any of: - /// - const char* (encoded using UTF-8, null-terminated) - /// - const char16_t* (encoded using UTF-16-LE, null-terminated) - /// - std::string (encoded using UTF-8) - /// - std::u16string - template - static String From(napi_env env, const T& value); - - String(); ///< Creates a new _empty_ String instance. - String(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - operator std::string() const; ///< Converts a String value to a UTF-8 encoded C++ string. - operator std::u16string() const; ///< Converts a String value to a UTF-16 encoded C++ string. - std::string Utf8Value() const; ///< Converts a String value to a UTF-8 encoded C++ string. - std::u16string Utf16Value() const; ///< Converts a String value to a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::string& value ///< UTF-8 encoded C++ string + ); + + /// Creates a new String value from a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::u16string& value ///< UTF-16 encoded C++ string + ); + + /// Creates a new String value from a UTF-8 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char* value ///< UTF-8 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-16 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value ///< UTF-16 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-8 encoded C string with specified + /// length. + static String New(napi_env env, ///< Node-API environment + const char* value, ///< UTF-8 encoded C string (not + ///< necessarily null-terminated) + size_t length ///< length of the string in bytes + ); + + /// Creates a new String value from a UTF-16 encoded C string with specified + /// length. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value, ///< UTF-16 encoded C string (not necessarily + ///< null-terminated) + size_t length ///< Length of the string in 2-byte code units + ); + + /// Creates a new String based on the original object's type. + /// + /// `value` may be any of: + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + template + static String From(napi_env env, const T& value); + + String(); ///< Creates a new _empty_ String instance. + String(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator std::string() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + operator std::u16string() + const; ///< Converts a String value to a UTF-16 encoded C++ string. + std::string Utf8Value() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + std::u16string Utf16Value() + const; ///< Converts a String value to a UTF-16 encoded C++ string. }; /// A JavaScript symbol value. class Symbol : public Name { public: /// Creates a new Symbol value with an optional description. - static Symbol New( - napi_env env, ///< N-API environment - const char* description = nullptr ///< Optional UTF-8 encoded null-terminated C string - /// describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - const std::string& description ///< UTF-8 encoded C++ string describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - String description ///< String value describing the symbol - ); - - /// Creates a new Symbol value with a description. - static Symbol New( - napi_env env, ///< N-API environment - napi_value description ///< String value describing the symbol - ); - - /// Get a public Symbol (e.g. Symbol.iterator). - static Symbol WellKnown(napi_env, const std::string& name); - - Symbol(); ///< Creates a new _empty_ Symbol instance. - Symbol(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + static Symbol New( + napi_env env, ///< Node-API environment + const char* description = + nullptr ///< Optional UTF-8 encoded null-terminated C string + /// describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + const std::string& + description ///< UTF-8 encoded C++ string describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New(napi_env env, ///< Node-API environment + String description ///< String value describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + napi_value description ///< String value describing the symbol + ); + + /// Get a public Symbol (e.g. Symbol.iterator). + static Symbol WellKnown(napi_env, const std::string& name); + + Symbol(); ///< Creates a new _empty_ Symbol instance. + Symbol(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. }; /// A JavaScript object value. @@ -552,12 +576,12 @@ namespace Napi { }; /// Creates a new Object value. - static Object New( - napi_env env ///< N-API environment + static Object New(napi_env env ///< Node-API environment ); Object(); ///< Creates a new _empty_ Object instance. - Object(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + Object(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. /// Gets or sets a named property. PropertyLValue operator []( @@ -792,46 +816,61 @@ namespace Napi { class ArrayBuffer : public Object { public: /// Creates a new ArrayBuffer instance over a new automatically-allocated buffer. - static ArrayBuffer New( - napi_env env, ///< N-API environment - size_t byteLength ///< Length of the buffer to be allocated, in bytes - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength ///< Length of the external buffer to be used by the array, in bytes - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength, ///< Length of the external buffer to be used by the array, - /// in bytes - Finalizer finalizeCallback ///< Function to be called when the array buffer is destroyed; - /// must implement `void operator()(Env env, void* externalData)` - ); - - /// Creates a new ArrayBuffer instance, using an external buffer with specified byte length. - template - static ArrayBuffer New( - napi_env env, ///< N-API environment - void* externalData, ///< Pointer to the external buffer to be used by the array - size_t byteLength, ///< Length of the external buffer to be used by the array, - /// in bytes - Finalizer finalizeCallback, ///< Function to be called when the array buffer is destroyed; - /// must implement `void operator()(Env env, void* externalData, Hint* hint)` - Hint* finalizeHint ///< Hint (second parameter) to be passed to the finalize callback - ); - - ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. - ArrayBuffer(napi_env env, napi_value value); ///< Wraps a N-API value primitive. - - void* Data(); ///< Gets a pointer to the data buffer. - size_t ByteLength(); ///< Gets the length of the array buffer in bytes. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + size_t byteLength ///< Length of the buffer to be allocated, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength ///< Length of the external buffer to be used by the + ///< array, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env env, + /// void* externalData)` + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback, ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData, Hint* hint)` + Hint* finalizeHint ///< Hint (second parameter) to be passed to the + ///< finalize callback + ); + + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. + ArrayBuffer(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + void* Data(); ///< Gets a pointer to the data buffer. + size_t ByteLength(); ///< Gets the length of the array buffer in bytes. #if NAPI_VERSION >= 7 bool IsDetached() const; @@ -851,7 +890,8 @@ namespace Napi { class TypedArray : public Object { public: TypedArray(); ///< Creates a new _empty_ TypedArray instance. - TypedArray(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + TypedArray(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. napi_typedarray_type TypedArrayType() const; ///< Gets the type of this typed-array. Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. @@ -906,16 +946,19 @@ namespace Napi { /// parameter T), except when creating a "clamped" array: /// /// Uint8Array::New(env, length, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< N-API environment - size_t elementLength, ///< Length of the created array, as a number of elements + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements #if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() #else - napi_typedarray_type type + napi_typedarray_type type #endif - ///< Type of array, if different from the default array type for the template parameter T. - ); + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); /// Creates a new TypedArray instance over a provided array buffer. /// @@ -923,21 +966,26 @@ namespace Napi { /// parameter T), except when creating a "clamped" array: /// /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) - static TypedArrayOf New( - napi_env env, ///< N-API environment - size_t elementLength, ///< Length of the created array, as a number of elements - Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use - size_t bufferOffset, ///< Offset into the array buffer where the typed-array starts + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts #if defined(NAPI_HAS_CONSTEXPR) - napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() #else - napi_typedarray_type type + napi_typedarray_type type #endif - ///< Type of array, if different from the default array type for the template parameter T. - ); + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. - TypedArrayOf(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + TypedArrayOf(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. T& operator [](size_t index); ///< Gets or sets an element in the array. const T& operator [](size_t index) const; ///< Gets an element in the array. @@ -979,7 +1027,8 @@ namespace Napi { size_t byteLength); DataView(); ///< Creates a new _empty_ DataView instance. - DataView(napi_env env, napi_value value); ///< Wraps a N-API value primitive. + DataView(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. @@ -1276,43 +1325,48 @@ namespace Napi { ObjectReference Persistent(Object value); FunctionReference Persistent(Function value); - /// A persistent reference to a JavaScript error object. Use of this class depends somewhat - /// on whether C++ exceptions are enabled at compile time. + /// A persistent reference to a JavaScript error object. Use of this class + /// depends somewhat on whether C++ exceptions are enabled at compile time. /// /// ### Handling Errors With C++ Exceptions /// - /// If C++ exceptions are enabled, then the `Error` class extends `std::exception` and enables - /// integrated error-handling for C++ exceptions and JavaScript exceptions. + /// If C++ exceptions are enabled, then the `Error` class extends + /// `std::exception` and enables integrated error-handling for C++ exceptions + /// and JavaScript exceptions. /// - /// If a N-API call fails without executing any JavaScript code (for example due to an invalid - /// argument), then the N-API wrapper automatically converts and throws the error as a C++ - /// exception of type `Napi::Error`. Or if a JavaScript function called by C++ code via N-API - /// throws a JavaScript exception, then the N-API wrapper automatically converts and throws it as - /// a C++ exception of type `Napi::Error`. + /// If a Node-API call fails without executing any JavaScript code (for + /// example due to an invalid argument), then the Node-API wrapper + /// automatically converts and throws the error as a C++ exception of type + /// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API + /// throws a JavaScript exception, then the Node-API wrapper automatically + /// converts and throws it as a C++ exception of type `Napi::Error`. /// - /// If a C++ exception of type `Napi::Error` escapes from a N-API C++ callback, then the N-API - /// wrapper automatically converts and throws it as a JavaScript exception. Therefore, catching - /// a C++ exception of type `Napi::Error` prevents a JavaScript exception from being thrown. + /// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ + /// callback, then the Node-API wrapper automatically converts and throws it + /// as a JavaScript exception. Therefore, catching a C++ exception of type + /// `Napi::Error` prevents a JavaScript exception from being thrown. /// /// #### Example 1A - Throwing a C++ exception: /// /// Napi::Env env = ... /// throw Napi::Error::New(env, "Example exception"); /// - /// Following C++ statements will not be executed. The exception will bubble up as a C++ - /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically propataged as a JavaScript exception when the callback returns to JavaScript. + /// Following C++ statements will not be executed. The exception will bubble + /// up as a C++ exception of type `Napi::Error`, until it is either caught + /// while still in C++, or else automatically propataged as a JavaScript + /// exception when the callback returns to JavaScript. /// - /// #### Example 2A - Propagating a N-API C++ exception: + /// #### Example 2A - Propagating a Node-API C++ exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); /// - /// Following C++ statements will not be executed. The exception will bubble up as a C++ - /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically propagated as a JavaScript exception when the callback returns to JavaScript. + /// Following C++ statements will not be executed. The exception will bubble + /// up as a C++ exception of type `Napi::Error`, until it is either caught + /// while still in C++, or else automatically propagated as a JavaScript + /// exception when the callback returns to JavaScript. /// - /// #### Example 3A - Handling a N-API C++ exception: + /// #### Example 3A - Handling a Node-API C++ exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); /// Napi::Value result; @@ -1322,38 +1376,42 @@ namespace Napi { /// cerr << "Caught JavaScript exception: " + e.what(); /// } /// - /// Since the exception was caught here, it will not be propagated as a JavaScript exception. + /// Since the exception was caught here, it will not be propagated as a + /// JavaScript exception. /// /// ### Handling Errors Without C++ Exceptions /// - /// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) then this class - /// does not extend `std::exception`, and APIs in the `Napi` namespace do not throw C++ - /// exceptions when they fail. Instead, they raise _pending_ JavaScript exceptions and - /// return _empty_ `Value`s. Calling code should check `Value::IsEmpty()` before attempting - /// to use a returned value, and may use methods on the `Env` class to check for, get, and - /// clear a pending JavaScript exception. If the pending exception is not cleared, it will - /// be thrown when the native callback returns to JavaScript. + /// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) + /// then this class does not extend `std::exception`, and APIs in the `Napi` + /// namespace do not throw C++ exceptions when they fail. Instead, they raise + /// _pending_ JavaScript exceptions and return _empty_ `Value`s. Calling code + /// should check `Value::IsEmpty()` before attempting to use a returned value, + /// and may use methods on the `Env` class to check for, get, and clear a + /// pending JavaScript exception. If the pending exception is not cleared, it + /// will be thrown when the native callback returns to JavaScript. /// /// #### Example 1B - Throwing a JS exception /// /// Napi::Env env = ... - /// Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); - /// return; + /// Napi::Error::New(env, "Example + /// exception").ThrowAsJavaScriptException(); return; /// - /// After throwing a JS exception, the code should generally return immediately from the native - /// callback, after performing any necessary cleanup. + /// After throwing a JS exception, the code should generally return + /// immediately from the native callback, after performing any necessary + /// cleanup. /// - /// #### Example 2B - Propagating a N-API JS exception: + /// #### Example 2B - Propagating a Node-API JS exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); /// if (result.IsEmpty()) return; /// - /// An empty value result from a N-API call indicates an error occurred, and a JavaScript - /// exception is pending. To let the exception propagate, the code should generally return - /// immediately from the native callback, after performing any necessary cleanup. + /// An empty value result from a Node-API call indicates an error occurred, + /// and a JavaScript exception is pending. To let the exception propagate, the + /// code should generally return immediately from the native callback, after + /// performing any necessary cleanup. /// - /// #### Example 3B - Handling a N-API JS exception: + /// #### Example 3B - Handling a Node-API JS exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); @@ -1362,8 +1420,8 @@ namespace Napi { /// cerr << "Caught JavaScript exception: " + e.Message(); /// } /// - /// Since the exception was cleared here, it will not be propagated as a JavaScript exception - /// after the native callback returns. + /// Since the exception was cleared here, it will not be propagated as a + /// JavaScript exception after the native callback returns. class Error : public ObjectReference #ifdef NAPI_CPP_EXCEPTIONS , public std::exception @@ -2289,10 +2347,10 @@ namespace Napi { class TypedThreadSafeFunction { public: // This API may only be called from the main thread. - // Helper function that returns nullptr if running N-API 5+, otherwise a + // Helper function that returns nullptr if running Node-API 5+, otherwise a // non-empty, no-op Function. This provides the ability to specify at // compile-time a callback parameter to `New` that safely does no action - // when targeting _any_ N-API version. + // when targeting _any_ Node-API version. #if NAPI_VERSION > 4 static std::nullptr_t EmptyFunctionFactory(Napi::Env env); #else diff --git a/package.json b/package.json index 13138fec2..d062cb0c9 100644 --- a/package.json +++ b/package.json @@ -296,8 +296,7 @@ "url": "https://github.com/ZzqiZQute" } ], - "dependencies": {}, - "description": "Node.js API (N-API)", + "description": "Node.js API (Node-API)", "devDependencies": { "benchmark": "^2.1.4", "bindings": "^1.5.0", diff --git a/test/error.cc b/test/error.cc index 832cad525..44f4f79eb 100644 --- a/test/error.cc +++ b/test/error.cc @@ -165,7 +165,7 @@ void ThrowDefaultError(const CallbackInfo& info) { NAPI_FATAL_IF_FAILED(status, "ThrowDefaultError", "napi_get_undefined"); if (info[0].As().Value()) { - // Provoke N-API into setting an error, then use the `Napi::Error::New` + // Provoke Node-API into setting an error, then use the `Napi::Error::New` // factory with only the `env` parameter to throw an exception generated // from the last error. uint32_t dummy_uint32; diff --git a/test/index.js b/test/index.js index 35db3187b..8799bb302 100644 --- a/test/index.js +++ b/test/index.js @@ -113,7 +113,7 @@ if (majorNodeVersion < 12) { } (async function() { -console.log(`Testing with N-API Version '${napiVersion}'.`); +console.log(`Testing with Node-API Version '${napiVersion}'.`); console.log('Starting test suite\n'); From 22c67dab0e457b79dc82b6a00e7fee8719318328 Mon Sep 17 00:00:00 2001 From: Darshan Sen Date: Thu, 25 Mar 2021 19:45:31 +0530 Subject: [PATCH 11/57] doc: rename N-API with Node-API Done by running: sed -i "s/N-API/Node-API/g" doc/* sed -i "s/N-API/Node-API/g" README.md CONTRIBUTING.md Fixes: https://github.com/nodejs/node-addon-api/issues/950 PR-URL: https://github.com/nodejs/node-addon-api/pull/951 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- CONTRIBUTING.md | 16 +++---- README.md | 72 ++++++++++++++++---------------- doc/async_context.md | 4 +- doc/async_worker.md | 4 +- doc/callback_scope.md | 6 +-- doc/checker-tool.md | 2 +- doc/class_property_descriptor.md | 4 +- doc/cmake-js.md | 14 +++---- doc/error_handling.md | 16 +++---- doc/escapable_handle_scope.md | 18 ++++---- doc/function_reference.md | 2 +- doc/generator.md | 4 +- doc/handle_scope.md | 12 +++--- doc/object_reference.md | 2 +- doc/prebuild_tools.md | 2 +- doc/promises.md | 4 +- doc/reference.md | 2 +- doc/setup.md | 14 +++---- doc/string.md | 2 +- doc/symbol.md | 2 +- doc/threadsafe.md | 12 +++--- doc/threadsafe_function.md | 2 +- doc/typed_threadsafe_function.md | 2 +- doc/value.md | 12 +++--- doc/version_management.md | 6 +-- 25 files changed, 116 insertions(+), 120 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d9fdf926..7abe84d33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,12 +5,12 @@ contribute to **node-addon-api**: - Source code fixes - Additional tests - Documentation improvements -- Joining the N-API working group and participating in meetings +- Joining the Node-API working group and participating in meetings ## Source changes -**node-addon-api** is meant to be a thin convenience wrapper around N-API. With this -in mind, contributions of any new APIs that wrap around a core N-API API will +**node-addon-api** is meant to be a thin convenience wrapper around Node-API. With this +in mind, contributions of any new APIs that wrap around a core Node-API API will be considered for merge. However, changes that wrap existing **node-addon-api** APIs are encouraged to instead be provided as an ecosystem module. The **node-addon-api** team is happy to link to a curated set of modules that build on @@ -19,7 +19,7 @@ a recommended idiom or pattern. ### Rationale -The N-API team considered a couple different approaches with regards to changes +The Node-API team considered a couple different approaches with regards to changes extending **node-addon-api** - Larger core module - Incorporate these helpers and patterns into **node-addon-api** - Extras package - Create a new package (strawman name '**node-addon-api**-extras') @@ -35,7 +35,7 @@ encourage folks to make PRs for utility helpers against the same repository. The downside of the approach is the following: - Less coherency for our API set -- More maintenance burden on the N-API WG core team. +- More maintenance burden on the Node-API WG core team. #### Extras Package This involves us spinning up a new package which contains the utility classes @@ -48,10 +48,10 @@ The downside of this approach is the following: community understand where a particular contribution should be directed to (what belongs in **node-addon-api** vs **node-addon-api-extras**) - Need to define the level of support/API guarantees -- Unclear if the maintenance burden on the N-API WG is reduced or not +- Unclear if the maintenance burden on the Node-API WG is reduced or not #### Ecosystem -This doesn't require a ton of up-front work from the N-API WG. Instead of +This doesn't require a ton of up-front work from the Node-API WG. Instead of accepting utility PRs into **node-addon-api** or creating and maintaining a new module, the WG will encourage the creation of an ecosystem of modules that build on top of **node-addon-api**, and provide some level of advertising for these @@ -61,6 +61,6 @@ etc). The downside of this approach is the following: - Potential for lack of visibility - evangelism and education is hard, and module authors might not find right patterns and instead implement things themselves -- There might be greater friction for the N-API WG in evolving APIs since the +- There might be greater friction for the Node-API WG in evolving APIs since the ecosystem would have taken dependencies on the API shape of **node-addon-api** diff --git a/README.md b/README.md index f739b6b92..c104a45dc 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,19 @@ git branch -u origin/main main # **node-addon-api module** This module contains **header-only C++ wrapper classes** which simplify -the use of the C based [N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) provided by Node.js when using C++. It provides a C++ object model and exception handling semantics with low overhead. -There are three options for implementing addons: N-API, nan, or direct +There are three options for implementing addons: Node-API, nan, or direct use of internal V8, libuv and Node.js libraries. Unless there is a need for -direct access to functionality which is not exposed by N-API as outlined +direct access to functionality which is not exposed by Node-API as outlined in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html) -in Node.js core, use N-API. Refer to -[C/C++ addons with N-API](https://nodejs.org/dist/latest/docs/api/n-api.html) -for more information on N-API. +in Node.js core, use Node-API. Refer to +[C/C++ addons with Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +for more information on Node-API. -N-API is an ABI stable C interface provided by Node.js for building native +Node-API is an ABI stable C interface provided by Node.js for building native addons. It is independent from the underlying JavaScript runtime (e.g. V8 or ChakraCore) and is maintained as part of Node.js itself. It is intended to insulate native addons from changes in the underlying JavaScript engine and allow @@ -31,24 +31,24 @@ modules compiled for one version to run on later versions of Node.js without recompilation. The `node-addon-api` module, which is not part of Node.js, preserves the benefits -of the N-API as it consists only of inline code that depends only on the stable API -provided by N-API. As such, modules built against one version of Node.js +of the Node-API as it consists only of inline code that depends only on the stable API +provided by Node-API. As such, modules built against one version of Node.js using node-addon-api should run without having to be rebuilt with newer versions of Node.js. It is important to remember that *other* Node.js interfaces such as `libuv` (included in a project via `#include `) are not ABI-stable across -Node.js major versions. Thus, an addon must use N-API and/or `node-addon-api` +Node.js major versions. Thus, an addon must use Node-API and/or `node-addon-api` exclusively and build against a version of Node.js that includes an -implementation of N-API (meaning an active LTS version of Node.js) in +implementation of Node-API (meaning an active LTS version of Node.js) in order to benefit from ABI stability across Node.js major versions. Node.js provides an [ABI stability guide][] containing a detailed explanation of ABI -stability in general, and the N-API ABI stability guarantee in particular. +stability in general, and the Node-API ABI stability guarantee in particular. -As new APIs are added to N-API, node-addon-api must be updated to provide +As new APIs are added to Node-API, node-addon-api must be updated to provide wrappers for those new APIs. For this reason node-addon-api provides -methods that allow callers to obtain the underlying N-API handles so -direct calls to N-API and the use of the objects/methods provided by +methods that allow callers to obtain the underlying Node-API handles so +direct calls to Node-API and the use of the objects/methods provided by node-addon-api can be used together. For example, in order to be able to use an API for which the node-addon-api does not yet provide a wrapper. @@ -56,8 +56,8 @@ APIs exposed by node-addon-api are generally used to create and manipulate JavaScript values. Concepts and operations generally map to ideas specified in the **ECMA262 Language Specification**. -The [N-API Resource](https://nodejs.github.io/node-addon-examples/) offers an -excellent orientation and tips for developers just getting started with N-API +The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers an +excellent orientation and tips for developers just getting started with Node-API and node-addon-api. - **[Setup](#setup)** @@ -78,8 +78,8 @@ and node-addon-api. -node-addon-api is based on [N-API](https://nodejs.org/api/n-api.html) and supports using different N-API versions. -This allows addons built with it to run with Node.js versions which support the targeted N-API version. +node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. +This allows addons built with it to run with Node.js versions which support the targeted Node-API version. **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. @@ -178,14 +178,14 @@ npm install npm test --disable-deprecated ``` -To run the tests targeting a specific version of N-API run +To run the tests targeting a specific version of Node-API run ``` npm install export NAPI_VERSION=X npm test --NAPI_VERSION=X ``` -where X is the version of N-API you want to target. +where X is the version of Node-API you want to target. ### **Debug** @@ -217,11 +217,11 @@ See [benchmark/README.md](benchmark/README.md) for more details about running an ### **More resource and info about native Addons** - **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)** -- **[N-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** -- **[N-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)** -- **[How We Migrated Realm JavaScript From NAN to N-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api)** +- **[Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** +- **[Node-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)** +- **[How We Migrated Realm JavaScript From NAN to Node-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api)** -As node-addon-api's core mission is to expose the plain C N-API as C++ +As node-addon-api's core mission is to expose the plain C Node-API as C++ wrappers, tools that facilitate n-api/node-addon-api providing more convenient patterns on developing a Node.js add-ons with n-api/node-addon-api can be published to NPM as standalone packages. It is also recommended to tag @@ -233,19 +233,19 @@ Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/sea ### **Badges** -The use of badges is recommended to indicate the minimum version of N-API +The use of badges is recommended to indicate the minimum version of Node-API required for the module. This helps to determine which Node.js major versions are -supported. Addon maintainers can consult the [N-API support matrix][] to determine -which Node.js versions provide a given N-API version. The following badges are +supported. Addon maintainers can consult the [Node-API support matrix][] to determine +which Node.js versions provide a given Node-API version. The following badges are available: -![N-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v1%20Badge.svg) -![N-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v2%20Badge.svg) -![N-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v3%20Badge.svg) -![N-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v4%20Badge.svg) -![N-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v5%20Badge.svg) -![N-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20v6%20Badge.svg) -![N-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/N-API%20Experimental%20Version%20Badge.svg) +![Node-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v1%20Badge.svg) +![Node-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v2%20Badge.svg) +![Node-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v3%20Badge.svg) +![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) +![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) +![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) +![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) ## **Contributing** @@ -282,4 +282,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around Licensed under [MIT](./LICENSE.md) [ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ -[N-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix +[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix diff --git a/doc/async_context.md b/doc/async_context.md index b217d336a..06a606cd9 100644 --- a/doc/async_context.md +++ b/doc/async_context.md @@ -61,8 +61,8 @@ Returns the `Napi::Env` environment in which the async context has been created. Napi::AsyncContext::operator napi_async_context() const; ``` -Returns the N-API `napi_async_context` wrapped by the `Napi::AsyncContext` -object. This can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_async_context` wrapped by the `Napi::AsyncContext` +object. This can be used to mix usage of the C Node-API and node-addon-api. ## Example diff --git a/doc/async_worker.md b/doc/async_worker.md index b6bb0cd67..5d495a97c 100644 --- a/doc/async_worker.md +++ b/doc/async_worker.md @@ -343,8 +343,8 @@ virtual Napi::AsyncWorker::~AsyncWorker(); Napi::AsyncWorker::operator napi_async_work() const; ``` -Returns the N-API napi_async_work wrapped by the `Napi::AsyncWorker` object. This -can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_async_work` wrapped by the `Napi::AsyncWorker` object. This +can be used to mix usage of the C Node-API and node-addon-api. ## Example diff --git a/doc/callback_scope.md b/doc/callback_scope.md index 35f0f8d9b..39e4b58fe 100644 --- a/doc/callback_scope.md +++ b/doc/callback_scope.md @@ -2,7 +2,7 @@ There are cases (for example, resolving promises) where it is necessary to have the equivalent of the scope associated with a callback in place when making -certain N-API calls. +certain Node-API calls. ## Methods @@ -50,5 +50,5 @@ Returns the `Napi::Env` associated with the `Napi::CallbackScope`. Napi::CallbackScope::operator napi_callback_scope() const; ``` -Returns the N-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` -object. This can be used to mix usage of the C N-API and node-addon-api. +Returns the Node-API `napi_callback_scope` wrapped by the `Napi::CallbackScope` +object. This can be used to mix usage of the C Node-API and node-addon-api. diff --git a/doc/checker-tool.md b/doc/checker-tool.md index 135f13fd7..9d755bd3a 100644 --- a/doc/checker-tool.md +++ b/doc/checker-tool.md @@ -2,7 +2,7 @@ **node-addon-api** provides a [checker tool][] that will inspect a given directory tree, identifying all Node.js native addons therein, and further -indicating for each addon whether it is an N-API addon. +indicating for each addon whether it is an Node-API addon. ## To use the checker tool: diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index 92336e7ea..a0ac0d38f 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -108,10 +108,10 @@ inside the `Napi::ObjectWrap` class. operator napi_property_descriptor&() { return _desc; } ``` -Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` +Returns the original Node-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` ```cpp operator const napi_property_descriptor&() const { return _desc; } ``` -Returns the original N-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` +Returns the original Node-API `napi_property_descriptor` wrapped inside the `Napi::ClassPropertyDescriptor` diff --git a/doc/cmake-js.md b/doc/cmake-js.md index b26ad50ff..04ed4b94a 100644 --- a/doc/cmake-js.md +++ b/doc/cmake-js.md @@ -27,19 +27,19 @@ Your project will require a `CMakeLists.txt` file. The [CMake.js README file](ht ### NAPI_VERSION -When building N-API addons, it's crucial to specify the N-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: +When building Node-API addons, it's crucial to specify the Node-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: ``` add_definitions(-DNAPI_VERSION=3) ``` -Since N-API is ABI-stable, your N-API addon will work, without recompilation, with the N-API version you specify in `NAPI_VERSION` and all subsequent N-API versions. +Since Node-API is ABI-stable, your Node-API addon will work, without recompilation, with the Node-API version you specify in `NAPI_VERSION` and all subsequent Node-API versions. -In the absence of a need for features available only in a specific N-API version, version 3 is a good choice as it is the version of N-API that was active when N-API left experimental status. +In the absence of a need for features available only in a specific Node-API version, version 3 is a good choice as it is the version of Node-API that was active when Node-API left experimental status. ### NAPI_EXPERIMENTAL -The following line in the `CMakeLists.txt` file will enable N-API experimental features if your code requires them: +The following line in the `CMakeLists.txt` file will enable Node-API experimental features if your code requires them: ``` add_definitions(-DNAPI_EXPERIMENTAL) @@ -47,17 +47,17 @@ add_definitions(-DNAPI_EXPERIMENTAL) ### node-addon-api -If your N-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api). +If your Node-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api). ## Example -A working example of an N-API native addon built using CMake.js can be found on the [node-addon-examples repository](https://github.com/nodejs/node-addon-examples/tree/HEAD/build_with_cmake#building-n-api-addons-using-cmakejs). +A working example of an Node-API native addon built using CMake.js can be found on the [node-addon-examples repository](https://github.com/nodejs/node-addon-examples/tree/HEAD/build_with_cmake#building-n-api-addons-using-cmakejs). ## **CMake** Reference - [Installation](https://github.com/cmake-js/cmake-js#installation) - [How to use](https://github.com/cmake-js/cmake-js#usage) - - [Using N-API and node-addon-api](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api) + - [Using Node-API and node-addon-api](https://github.com/cmake-js/cmake-js#n-api-and-node-addon-api) - [Tutorials](https://github.com/cmake-js/cmake-js#tutorials) - [Use case in the works - ArrayFire.js](https://github.com/cmake-js/cmake-js#use-case-in-the-works---arrayfirejs) diff --git a/doc/error_handling.md b/doc/error_handling.md index 9a0ef349e..50c04a0fe 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -41,8 +41,8 @@ exception, then node-addon-api automatically converts and throws it as a C++ exception of type `Napi:Error` on return from the JavaScript code to the native method. -If a C++ exception of type `Napi::Error` escapes from a N-API C++ callback, then -the N-API wrapper automatically converts and throws it as a JavaScript exception. +If a C++ exception of type `Napi::Error` escapes from a Node-API C++ callback, then +the Node-API wrapper automatically converts and throws it as a JavaScript exception. On return from a native method, node-addon-api will automatically convert a pending C++ exception to a JavaScript exception. @@ -67,7 +67,7 @@ will bubble up as a C++ exception of type `Napi::Error`, until it is either caug while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. -### Propagating a N-API C++ exception +### Propagating a Node-API C++ exception ```cpp Napi::Function jsFunctionThatThrows = someObj.As(); @@ -81,7 +81,7 @@ executed. The exception will bubble up as a C++ exception of type `Napi::Error`, until it is either caught while still in C++, or else automatically propagated as a JavaScript exception when returning to JavaScript. -### Handling a N-API C++ exception +### Handling a Node-API C++ exception ```cpp Napi::Function jsFunctionThatThrows = someObj.As(); @@ -123,7 +123,7 @@ return; After throwing a JavaScript exception, the code should generally return immediately from the native callback, after performing any necessary cleanup. -### Propagating a N-API JS exception +### Propagating a Node-API JS exception ```cpp Napi::Env env = ... @@ -139,7 +139,7 @@ If env.IsExceptionPending() returns true a JavaScript exception is pending. To let the exception propagate, the code should generally return immediately from the native callback, after performing any necessary cleanup. -### Handling a N-API JS exception +### Handling a Node-API JS exception ```cpp Napi::Env env = ... @@ -154,10 +154,10 @@ if (env.IsExceptionPending()) { Since the exception was cleared here, it will not be propagated as a JavaScript exception after the native callback returns. -## Calling N-API directly from a **node-addon-api** addon +## Calling Node-API directly from a **node-addon-api** addon **node-addon-api** provides macros for throwing errors in response to non-OK -`napi_status` results when calling [N-API](https://nodejs.org/docs/latest/api/n-api.html) +`napi_status` results when calling [Node-API](https://nodejs.org/docs/latest/api/n-api.html) functions from within a native addon. These macros are defined differently depending on whether C++ exceptions are enabled or not, but are available for use in either case. diff --git a/doc/escapable_handle_scope.md b/doc/escapable_handle_scope.md index 4f3e2d062..a58942a79 100644 --- a/doc/escapable_handle_scope.md +++ b/doc/escapable_handle_scope.md @@ -35,22 +35,20 @@ Creates a new escapable handle scope. Napi::EscapableHandleScope Napi::EscapableHandleScope::New(napi_env env, napi_handle_scope scope); ``` -- `[in] env`: napi_env in which the scope passed in was created. -- `[in] scope`: pre-existing napi_handle_scope. +- `[in] env`: `napi_env` in which the scope passed in was created. +- `[in] scope`: pre-existing `napi_handle_scope`. Returns a new `Napi::EscapableHandleScope` instance which wraps the -napi_escapable_handle_scope handle passed in. This can be used -to mix usage of the C N-API and node-addon-api. - -operator EscapableHandleScope::napi_escapable_handle_scope +`napi_escapable_handle_scope` handle passed in. This can be used +to mix usage of the C Node-API and node-addon-api. ```cpp operator Napi::EscapableHandleScope::napi_escapable_handle_scope() const ``` -Returns the N-API napi_escapable_handle_scope wrapped by the `Napi::EscapableHandleScope` object. -This can be used to mix usage of the C N-API and node-addon-api by allowing -the class to be used be converted to a napi_escapable_handle_scope. +Returns the Node-API `napi_escapable_handle_scope` wrapped by the `Napi::EscapableHandleScope` object. +This can be used to mix usage of the C Node-API and node-addon-api by allowing +the class to be used be converted to a `napi_escapable_handle_scope`. ### Destructor ```cpp @@ -67,7 +65,7 @@ guarantee as to when the garbage collector will do this. napi::Value Napi::EscapableHandleScope::Escape(napi_value escapee); ``` -- `[in] escapee`: Napi::Value or napi_env to promote to the outer scope +- `[in] escapee`: `Napi::Value` or `napi_env` to promote to the outer scope Returns `Napi::Value` which can be used in the outer scope. This method can be called at most once on a given `Napi::EscapableHandleScope`. If it is called diff --git a/doc/function_reference.md b/doc/function_reference.md index fa21830b8..07afc643b 100644 --- a/doc/function_reference.md +++ b/doc/function_reference.md @@ -60,7 +60,7 @@ Napi::FunctionReference::FunctionReference(napi_env env, napi_ref ref); ``` - `[in] env`: The environment in which to construct the `Napi::FunctionReference` object. -- `[in] ref`: The N-API reference to be held by the `Napi::FunctionReference`. +- `[in] ref`: The Node-API reference to be held by the `Napi::FunctionReference`. Returns a newly created `Napi::FunctionReference` object. diff --git a/doc/generator.md b/doc/generator.md index 9167480cf..2ab1f5c2d 100644 --- a/doc/generator.md +++ b/doc/generator.md @@ -3,8 +3,8 @@ ## What is generator **[generator-napi-module](https://www.npmjs.com/package/generator-napi-module)** is a module to quickly generate a skeleton module using -**N-API**, the new API for Native addons. This module automatically sets up your -**gyp file** to use **node-addon-api**, the C++ wrappers for N-API and generates +**Node-API**, the new API for Native addons. This module automatically sets up your +**gyp file** to use **node-addon-api**, the C++ wrappers for Node-API and generates a wrapper JS module. Optionally, it can even configure the generated project to use **TypeScript** instead. diff --git a/doc/handle_scope.md b/doc/handle_scope.md index 1bebb8176..aabe0eada 100644 --- a/doc/handle_scope.md +++ b/doc/handle_scope.md @@ -33,19 +33,17 @@ Napi::HandleScope::HandleScope(Napi::Env env, Napi::HandleScope scope); - `[in] env`: `Napi::Env` in which the scope passed in was created. - `[in] scope`: pre-existing `Napi::HandleScope`. -Returns a new `Napi::HandleScope` instance which wraps the napi_handle_scope -handle passed in. This can be used to mix usage of the C N-API +Returns a new `Napi::HandleScope` instance which wraps the `napi_handle_scope` +handle passed in. This can be used to mix usage of the C Node-API and node-addon-api. -operator HandleScope::napi_handle_scope - ```cpp operator Napi::HandleScope::napi_handle_scope() const ``` -Returns the N-API napi_handle_scope wrapped by the `Napi::EscapableHandleScope` object. -This can be used to mix usage of the C N-API and node-addon-api by allowing -the class to be used be converted to a napi_handle_scope. +Returns the Node-API `napi_handle_scope` wrapped by the `Napi::EscapableHandleScope` object. +This can be used to mix usage of the C Node-API and node-addon-api by allowing +the class to be used be converted to a `napi_handle_scope`. ### Destructor ```cpp diff --git a/doc/object_reference.md b/doc/object_reference.md index f2d8905a8..8522b5341 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -75,7 +75,7 @@ Napi::ObjectReference::ObjectReference(napi_env env, napi_value value); * `[in] env`: The `napi_env` environment in which to construct the `Napi::ObjectReference` object. -* `[in] value`: The N-API primitive value to be held by the `Napi::ObjectReference`. +* `[in] value`: The Node-API primitive value to be held by the `Napi::ObjectReference`. Returns the newly created reference. diff --git a/doc/prebuild_tools.md b/doc/prebuild_tools.md index ac1273812..4f1041aab 100644 --- a/doc/prebuild_tools.md +++ b/doc/prebuild_tools.md @@ -9,7 +9,7 @@ possible to distribute the native add-on in pre-built form for different platfor and architectures. The prebuild tools help to create and distribute the pre-built form of a native add-on. -The following list report known tools that are compatible with **N-API**: +The following list report known tools that are compatible with **Node-API**: - **[node-pre-gyp](https://www.npmjs.com/package/node-pre-gyp)** - **[prebuild](https://www.npmjs.com/package/prebuild)** diff --git a/doc/promises.md b/doc/promises.md index fd32c17d0..21594c6b8 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -63,7 +63,7 @@ void Napi::Promise::Deferred::Resolve(napi_value value) const; Resolves the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to resolve the `Napi::Promise`. +* `[in] value`: The Node-API primitive value with which to resolve the `Napi::Promise`. ### Reject @@ -73,7 +73,7 @@ void Napi::Promise::Deferred::Reject(napi_value value) const; Rejects the Promise object held by the `Napi::Promise::Deferred` object. -* `[in] value`: The N-API primitive value with which to reject the `Napi::Promise`. +* `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`. [`Napi::Object`]: ./object.md diff --git a/doc/reference.md b/doc/reference.md index b1033775f..42ddc0609 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -40,7 +40,7 @@ Napi::Reference::Reference(napi_env env, napi_value value); * `[in] env`: The `napi_env` environment in which to construct the `Napi::Reference` object. -* `[in] value`: The N-API primitive value to be held by the `Napi::Reference`. +* `[in] value`: The Node-API primitive value to be held by the `Napi::Reference`. ### Env diff --git a/doc/setup.md b/doc/setup.md index 7a7590b4b..bbb16a138 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -2,7 +2,7 @@ ## Prerequisites -Before starting to use **N-API** you need to assure you have the following +Before starting to use **Node-API** you need to assure you have the following prerequisites: * **Node.JS** see: [Installing Node.js](https://nodejs.org/) @@ -13,7 +13,7 @@ prerequisites: ## Installation and usage -To use **N-API** in a native module: +To use **Node-API** in a native module: 1. Add a dependency on this package to `package.json`: @@ -29,9 +29,9 @@ To use **N-API** in a native module: 'include_dirs': ["Value(), {Number::New(env, *data)}); diff --git a/doc/value.md b/doc/value.md index ca9e3d2c9..505b7945f 100644 --- a/doc/value.md +++ b/doc/value.md @@ -3,9 +3,9 @@ `Napi::Value` is the C++ manifestation of a JavaScript value. It is the base class upon which other JavaScript values such as `Napi::Number`, `Napi::Boolean`, `Napi::String`, and `Napi::Object` are based. It represents a -JavaScript value of an unknown type. It is a thin wrapper around the N-API +JavaScript value of an unknown type. It is a thin wrapper around the Node-API datatype `napi_value`. Methods on this class can be used to check the JavaScript -type of the underlying N-API `napi_value` and also to convert to C++ types. +type of the underlying Node-API `napi_value` and also to convert to C++ types. ## Constructors @@ -45,7 +45,7 @@ value` may be any of: Napi::Value::operator napi_value() const; ``` -Returns the underlying N-API `napi_value`. If the instance is _empty_, this +Returns the underlying Node-API `napi_value`. If the instance is _empty_, this returns `nullptr`. ### operator == @@ -98,10 +98,10 @@ static Napi::Value Napi::Value::From(napi_env env, const T& value); - `[in] env`: The `napi_env` environment in which to create the `Napi::Value` object. -- `[in] value`: The N-API primitive value from which to create the `Napi::Value` +- `[in] value`: The Node-API primitive value from which to create the `Napi::Value` object. -Returns a `Napi::Value` object from an N-API primitive value. +Returns a `Napi::Value` object from an Node-API primitive value. This method is used to convert from a C++ type to a JavaScript value. Here, `value` may be any of: @@ -192,7 +192,7 @@ Thus, when C++ exceptions are not being used, callers should check the result of bool Napi::Value::IsExternal() const; ``` -Returns `true` if the underlying value is a N-API external object or `false` +Returns `true` if the underlying value is a Node-API external object or `false` otherwise. ### IsFunction diff --git a/doc/version_management.md b/doc/version_management.md index 8b1e7040b..6fe51196a 100644 --- a/doc/version_management.md +++ b/doc/version_management.md @@ -1,14 +1,14 @@ # VersionManagement The `Napi::VersionManagement` class contains methods that allow information -to be retrieved about the version of N-API and Node.js. In some cases it is +to be retrieved about the version of Node-API and Node.js. In some cases it is important to make decisions based on different versions of the system. ## Methods ### GetNapiVersion -Retrieves the highest N-API version supported by Node.js runtime. +Retrieves the highest Node-API version supported by Node.js runtime. ```cpp static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); @@ -16,7 +16,7 @@ static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); - `[in] env`: The environment in which the API is invoked under. -Returns the highest N-API version supported by Node.js runtime. +Returns the highest Node-API version supported by Node.js runtime. ### GetNodeVersion From 7df5bb4f804f494268af5d0a08b0f55f25a2365b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 20 Mar 2021 17:46:57 +0100 Subject: [PATCH 12/57] doc: unambiguously mark deprecated signatures Currently, deprecation notices are always right between two function signatures and it's virtually impossible to be certain whether they refer to the previous signature or the next signature. PR-URL: https://github.com/nodejs/node-addon-api/pull/942 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- doc/property_descriptor.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/property_descriptor.md b/doc/property_descriptor.md index 1a7e4f940..571cff4fd 100644 --- a/doc/property_descriptor.md +++ b/doc/property_descriptor.md @@ -138,7 +138,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( @@ -186,7 +186,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Accessor ( @@ -236,7 +236,7 @@ The name of the property can be any of the following types: - `napi_value value` - `Napi::Name` -**This signature is deprecated. It will result in a memory leak if used.** +**The above signature is deprecated. It will result in a memory leak if used.** ```cpp static Napi::PropertyDescriptor Napi::PropertyDescriptor::Function ( From 0b7e87280d159aa024d6b7f1f1f4aa3910df11d4 Mon Sep 17 00:00:00 2001 From: JckXia Date: Mon, 25 Jan 2021 19:13:53 -0500 Subject: [PATCH 13/57] Finished tests relating to fetch property from Global Object PR-URL: https://github.com/nodejs/node-addon-api/pull/939/ Reviewed-By: Nicola Del Gobbo Reviewed-By: Michael Dawson --- test/binding.cc | 2 + test/binding.gyp | 5 ++ test/globalObject/global_object.cc | 61 ++++++++++++++++++ .../global_object_delete_property.cc | 27 ++++++++ .../global_object_delete_property.js | 64 +++++++++++++++++++ .../global_object_get_property.cc | 39 +++++++++++ .../global_object_get_property.js | 60 +++++++++++++++++ .../global_object_has_own_property.cc | 22 +++++++ .../global_object_has_own_property.js | 51 +++++++++++++++ .../global_object_set_property.cc | 31 +++++++++ .../global_object_set_property.js | 61 ++++++++++++++++++ test/object/delete_property.cc | 6 ++ test/object/delete_property.js | 6 ++ test/object/get_property.cc | 6 ++ test/object/get_property.js | 4 ++ test/object/has_property.cc | 6 ++ test/object/has_property.js | 3 + test/object/object.cc | 7 ++ 18 files changed, 461 insertions(+) create mode 100644 test/globalObject/global_object.cc create mode 100644 test/globalObject/global_object_delete_property.cc create mode 100644 test/globalObject/global_object_delete_property.js create mode 100644 test/globalObject/global_object_get_property.cc create mode 100644 test/globalObject/global_object_get_property.js create mode 100644 test/globalObject/global_object_has_own_property.cc create mode 100644 test/globalObject/global_object_has_own_property.js create mode 100644 test/globalObject/global_object_set_property.cc create mode 100644 test/globalObject/global_object_set_property.js diff --git a/test/binding.cc b/test/binding.cc index fa37a8d53..1ed7cb053 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -58,6 +58,7 @@ Object InitTypedThreadSafeFunctionUnref(Env env); Object InitTypedThreadSafeFunction(Env env); #endif Object InitTypedArray(Env env); +Object InitGlobalObject(Env env); Object InitObjectWrap(Env env); Object InitObjectWrapConstructorException(Env env); Object InitObjectWrapRemoveWrap(Env env); @@ -78,6 +79,7 @@ Object Init(Env env, Object exports) { exports.Set("asyncprogressqueueworker", InitAsyncProgressQueueWorker(env)); exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); #endif + exports.Set("globalObject", InitGlobalObject(env)); exports.Set("asyncworker", InitAsyncWorker(env)); exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env)); exports.Set("basic_types_array", InitBasicTypesArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index c9f82ee20..8f67fee55 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -28,6 +28,11 @@ 'movable_callbacks.cc', 'memory_management.cc', 'name.cc', + 'globalObject/global_object_delete_property.cc', + 'globalObject/global_object_has_own_property.cc', + 'globalObject/global_object_set_property.cc', + 'globalObject/global_object_get_property.cc', + 'globalObject/global_object.cc', 'object/delete_property.cc', 'object/finalizer.cc', 'object/get_property.cc', diff --git a/test/globalObject/global_object.cc b/test/globalObject/global_object.cc new file mode 100644 index 000000000..00ef2ba8f --- /dev/null +++ b/test/globalObject/global_object.cc @@ -0,0 +1,61 @@ +#include "napi.h" + +using namespace Napi; + +// Wrappers for testing Object::Get() for global Objects +Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value GetPropertyWithInt32AsKey(const CallbackInfo& info); +Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info); +void CreateMockTestObject(const CallbackInfo& info); + +// Wrapper for testing Object::Set() for global Objects +void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info); +void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +void SetPropertyWithInt32AsKey(const CallbackInfo& info); +void SetPropertyWithNapiValueAsKey(const CallbackInfo& info); + +Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info); + +Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info); +Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info); +Value DeletePropertyWithInt32AsKey(const CallbackInfo& info); +Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info); + +Object InitGlobalObject(Env env) { + Object exports = Object::New(env); + exports["getPropertyWithInt32"] = + Function::New(env, GetPropertyWithInt32AsKey); + exports["getPropertyWithNapiValue"] = + Function::New(env, GetPropertyWithNapiValueAsKey); + exports["getPropertyWithCppString"] = + Function::New(env, GetPropertyWithCppStyleStringAsKey); + exports["getPropertyWithCString"] = + Function::New(env, GetPropertyWithCStyleStringAsKey); + exports["createMockTestObject"] = Function::New(env, CreateMockTestObject); + exports["setPropertyWithCStyleString"] = + Function::New(env, SetPropertyWithCStyleStringAsKey); + exports["setPropertyWithCppStyleString"] = + Function::New(env, SetPropertyWithCppStyleStringAsKey); + exports["setPropertyWithNapiValue"] = + Function::New(env, SetPropertyWithNapiValueAsKey); + exports["setPropertyWithInt32"] = + Function::New(env, SetPropertyWithInt32AsKey); + exports["hasPropertyWithCStyleString"] = + Function::New(env, HasPropertyWithCStyleStringAsKey); + exports["hasPropertyWithCppStyleString"] = + Function::New(env, HasPropertyWithCppStyleStringAsKey); + exports["hasPropertyWithNapiValue"] = + Function::New(env, HasPropertyWithNapiValueAsKey); + exports["deletePropertyWithCStyleString"] = + Function::New(env, DeletePropertyWithCStyleStringAsKey); + exports["deletePropertyWithCppStyleString"] = + Function::New(env, DeletePropertyWithCppStyleStringAsKey); + exports["deletePropertyWithInt32"] = + Function::New(env, DeletePropertyWithInt32AsKey); + exports["deletePropertyWithNapiValue"] = + Function::New(env, DeletePropertyWithNapiValueAsKey); + return exports; +} diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc new file mode 100644 index 000000000..8064e5864 --- /dev/null +++ b/test/globalObject/global_object_delete_property.cc @@ -0,0 +1,27 @@ +#include "napi.h" + +using namespace Napi; + +Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value().c_str())); +} + +Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value())); +} + +Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].As(); + return Boolean::New(info.Env(), globalObject.Delete(key.Uint32Value())); +} + +Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].As(); + return Boolean::New(info.Env(), globalObject.Delete(key)); +} \ No newline at end of file diff --git a/test/globalObject/global_object_delete_property.js b/test/globalObject/global_object_delete_property.js new file mode 100644 index 000000000..b6334f1ce --- /dev/null +++ b/test/globalObject/global_object_delete_property.js @@ -0,0 +1,64 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + + +function test(binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function assertNotGlobalObjectHasNoProperty(key, keyType) + { + switch(keyType) + { + case KEY_TYPE.NAPI: + assert.notStrictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); + break; + + case KEY_TYPE.C_STR: + assert.notStrictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); + break; + + case KEY_TYPE.CPP_STR: + assert.notStrictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); + break; + + case KEY_TYPE.INT_32: + assert.notStrictEqual(binding.globalObject.hasPropertyWithInt32(key), true); + break; + } + } + + function assertErrMessageIsThrown(propertyCheckExistanceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistanceFunction(undefined); + }, errMsg); + } + + binding.globalObject.createMockTestObject(); + + binding.globalObject.deletePropertyWithCStyleString('c_str_key'); + binding.globalObject.deletePropertyWithCppStyleString('cpp_string_key'); + binding.globalObject.deletePropertyWithCppStyleString('circular'); + binding.globalObject.deletePropertyWithInt32(15); + binding.globalObject.deletePropertyWithNapiValue('2'); + + + assertNotGlobalObjectHasNoProperty('c_str_key',KEY_TYPE.C_STR); + assertNotGlobalObjectHasNoProperty('cpp_string_key',KEY_TYPE.CPP_STR); + assertNotGlobalObjectHasNoProperty('circular',KEY_TYPE.CPP_STR); + assertNotGlobalObjectHasNoProperty(15,true); + assertNotGlobalObjectHasNoProperty('2', KEY_TYPE.NAPI); + + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc new file mode 100644 index 000000000..bd402abf2 --- /dev/null +++ b/test/globalObject/global_object_get_property.cc @@ -0,0 +1,39 @@ +#include "napi.h" + +using namespace Napi; + +Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].As(); + return globalObject.Get(key); +} + +Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].As(); + return globalObject.Get(key.Uint32Value()); +} + +Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String cStrkey = info[0].As(); + return globalObject.Get(cStrkey.Utf8Value().c_str()); +} + +Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String cppStrKey = info[0].As(); + return globalObject.Get(cppStrKey.Utf8Value()); +} + +void CreateMockTestObject(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number napi_key = Number::New(info.Env(), 2); + const char* CStringKey = "c_str_key"; + + globalObject.Set(napi_key, "napi_attribute"); + globalObject[CStringKey] = "c_string_attribute"; + globalObject[std::string("cpp_string_key")] = "cpp_string_attribute"; + globalObject[std::string("circular")] = globalObject; + globalObject[(uint32_t)15] = 15; +} diff --git a/test/globalObject/global_object_get_property.js b/test/globalObject/global_object_get_property.js new file mode 100644 index 000000000..3630f4398 --- /dev/null +++ b/test/globalObject/global_object_get_property.js @@ -0,0 +1,60 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + + +function test(binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + binding.globalObject.createMockTestObject(); + function assertGlobalObjectPropertyIs(key, attribute, keyType) { + let napiObjectAttr; + switch(keyType) + { + case KEY_TYPE.NAPI: + napiObjectAttr = binding.globalObject.getPropertyWithNapiValue(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.C_STR: + napiObjectAttr = binding.globalObject.getPropertyWithCString(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.CPP_STR: + napiObjectAttr = binding.globalObject.getPropertyWithCppString(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + + case KEY_TYPE.INT_32: + napiObjectAttr = binding.globalObject.getPropertyWithInt32(key); + assert.deepStrictEqual(attribute, napiObjectAttr); + break; + } + } + + function assertErrMessageIsThrown(propertyFetchFunction, errMsg) { + assert.throws(() => { + propertyFetchFunction(undefined); + }, errMsg); + } + + assertGlobalObjectPropertyIs('2',global['2'], KEY_TYPE.NAPI); + assertGlobalObjectPropertyIs('c_str_key',global['c_str_key'],KEY_TYPE.C_STR); + assertGlobalObjectPropertyIs('cpp_string_key',global['cpp_string_key'],KEY_TYPE.CPP_STR); + assertGlobalObjectPropertyIs('circular',global['circular'],KEY_TYPE.CPP_STR); + assertGlobalObjectPropertyIs(15, global['15'], KEY_TYPE.INT_32); + + assertErrMessageIsThrown(binding.globalObject.getPropertyWithCString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.getPropertyWithCppString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.getPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_has_own_property.cc b/test/globalObject/global_object_has_own_property.cc new file mode 100644 index 000000000..1dfb4e281 --- /dev/null +++ b/test/globalObject/global_object_has_own_property.cc @@ -0,0 +1,22 @@ +#include "napi.h" + +using namespace Napi; + +Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + return Boolean::New(info.Env(), + globalObject.HasOwnProperty(key.Utf8Value().c_str())); +} + +Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + return Boolean::New(info.Env(), globalObject.HasOwnProperty(key.Utf8Value())); +} + +Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].As(); + return Boolean::New(info.Env(), globalObject.HasOwnProperty(key)); +} \ No newline at end of file diff --git a/test/globalObject/global_object_has_own_property.js b/test/globalObject/global_object_has_own_property.js new file mode 100644 index 000000000..65c8c6a71 --- /dev/null +++ b/test/globalObject/global_object_has_own_property.js @@ -0,0 +1,51 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + + +function test(binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function assertGlobalObjectHasProperty(key, keyType) + { + switch(keyType) + { + case KEY_TYPE.NAPI: + assert.strictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); + break; + + case KEY_TYPE.C_STR: + assert.strictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); + break; + + case KEY_TYPE.CPP_STR: + assert.strictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); + break; + } + } + + function assertErrMessageIsThrown(propertyCheckExistanceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistanceFunction(undefined); + }, errMsg); + } + + binding.globalObject.createMockTestObject(); + assertGlobalObjectHasProperty('c_str_key',KEY_TYPE.C_STR); + assertGlobalObjectHasProperty('cpp_string_key',KEY_TYPE.CPP_STR); + assertGlobalObjectHasProperty('circular',KEY_TYPE.CPP_STR); + assertGlobalObjectHasProperty('2', KEY_TYPE.NAPI); + + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/globalObject/global_object_set_property.cc b/test/globalObject/global_object_set_property.cc new file mode 100644 index 000000000..7065bee56 --- /dev/null +++ b/test/globalObject/global_object_set_property.cc @@ -0,0 +1,31 @@ +#include "napi.h" + +using namespace Napi; + +void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + Value value = info[1]; + globalObject.Set(key.Utf8Value().c_str(), value); +} + +void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String key = info[0].As(); + Value value = info[1]; + globalObject.Set(key.Utf8Value(), value); +} + +void SetPropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].As(); + Value value = info[1]; + globalObject.Set(key.Uint32Value(), value); +} + +void SetPropertyWithNapiValueAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Name key = info[0].As(); + Value value = info[1]; + globalObject.Set(key, value); +} \ No newline at end of file diff --git a/test/globalObject/global_object_set_property.js b/test/globalObject/global_object_set_property.js new file mode 100644 index 000000000..06f01a611 --- /dev/null +++ b/test/globalObject/global_object_set_property.js @@ -0,0 +1,61 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + + +function test(binding) { + const KEY_TYPE = { + C_STR: 'KEY_AS_C_STRING', + CPP_STR: 'KEY_AS_CPP_STRING', + NAPI: 'KEY_AS_NAPI_VALUES', + INT_32: 'KEY_AS_INT_32_NUM' + }; + + function setGlobalObjectKeyValue(key, value, keyType) { + switch(keyType) + { + case KEY_TYPE.CPP_STR: + binding.globalObject.setPropertyWithCppStyleString(key,value); + break; + + case KEY_TYPE.C_STR: + binding.globalObject.setPropertyWithCStyleString(key,value); + break; + + case KEY_TYPE.INT_32: + binding.globalObject.setPropertyWithInt32(key,value); + break; + + case KEY_TYPE.NAPI: + binding.globalObject.setPropertyWithNapiValue(key,value); + break; + } + } + + function assertErrMessageIsThrown(nativeObjectSetFunction, errMsg) { + assert.throws(() => { + nativeObjectSetFunction(undefined, 1); + }, errMsg); + } + + + setGlobalObjectKeyValue("cKey","cValue",KEY_TYPE.CPP_STR); + setGlobalObjectKeyValue(1,10,KEY_TYPE.INT_32); + setGlobalObjectKeyValue("napi_key","napi_value",KEY_TYPE.NAPI); + setGlobalObjectKeyValue("cppKey","cppValue",KEY_TYPE.CPP_STR); + setGlobalObjectKeyValue("circular",global,KEY_TYPE.NAPI); + + assert.deepStrictEqual(global["circular"], global); + assert.deepStrictEqual(global["cppKey"],"cppValue"); + assert.deepStrictEqual(global["napi_key"],"napi_value"); + assert.deepStrictEqual(global[1],10); + assert.deepStrictEqual(global["cKey"],"cValue"); + + assertErrMessageIsThrown(binding.globalObject.setPropertyWithCppStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.setPropertyWithCStyleString, 'Error: A string was expected'); + assertErrMessageIsThrown(binding.globalObject.setPropertyWithInt32, 'Error: A number was expected'); +} diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index bd2488435..80caa7e31 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -2,6 +2,12 @@ using namespace Napi; +Value DeletePropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].As(); + Number key = info[1].As(); + return Boolean::New(info.Env(), obj.Delete(key.Uint32Value())); +} + Value DeletePropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); diff --git a/test/object/delete_property.js b/test/object/delete_property.js index 8c313d03e..b65e063b0 100644 --- a/test/object/delete_property.js +++ b/test/object/delete_property.js @@ -25,6 +25,12 @@ function test(binding) { }, /Cannot convert undefined or null to object/); } + const testObj = { 15 : 42 , three: 3}; + + binding.object.deletePropertyWithUint32(testObj,15); + + assert.strictEqual(testObj.hasOwnProperty(15),false); + testDeleteProperty(binding.object.deletePropertyWithNapiValue); testDeleteProperty(binding.object.deletePropertyWithNapiWrapperValue); testDeleteProperty(binding.object.deletePropertyWithCStyleString); diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 0cdaa50d7..8069da465 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -14,6 +14,12 @@ Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { return obj.Get(key); } +Value GetPropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].As(); + Number key = info[1].As(); + return obj.Get(key.Uint32Value()); +} + Value GetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); diff --git a/test/object/get_property.js b/test/object/get_property.js index 8028165c3..b2b68d914 100644 --- a/test/object/get_property.js +++ b/test/object/get_property.js @@ -23,6 +23,10 @@ function test(binding) { }, /Cannot convert undefined or null to object/); } + const testObject = { 42: 100 }; + const property = binding.object.getPropertyWithUint32(testObject, 42); + assert.strictEqual(property,100) + const nativeFunctions = [ binding.object.getPropertyWithNapiValue, binding.object.getPropertyWithNapiWrapperValue, diff --git a/test/object/has_property.cc b/test/object/has_property.cc index 0a1a45942..669935cc1 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -20,6 +20,12 @@ Value HasPropertyWithCStyleString(const CallbackInfo& info) { return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value().c_str())); } +Value HasPropertyWithUint32(const CallbackInfo& info) { + Object obj = info[0].As(); + Number jsKey = info[1].As(); + return Boolean::New(info.Env(), obj.Has(jsKey.Uint32Value())); +} + Value HasPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); diff --git a/test/object/has_property.js b/test/object/has_property.js index a1b942dfb..f283fc77e 100644 --- a/test/object/has_property.js +++ b/test/object/has_property.js @@ -24,6 +24,9 @@ function test(binding) { }, /Cannot convert undefined or null to object/); } + const objectWithInt32Key = { 12: 101 }; + assert.strictEqual(binding.object.hasPropertyWithUint32(objectWithInt32Key,12),true); + testHasProperty(binding.object.hasPropertyWithNapiValue); testHasProperty(binding.object.hasPropertyWithNapiWrapperValue); testHasProperty(binding.object.hasPropertyWithCStyleString); diff --git a/test/object/object.cc b/test/object/object.cc index b2f6b5f95..3ee454e77 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -3,6 +3,7 @@ using namespace Napi; // Native wrappers for testing Object::Get() +Value GetPropertyWithUint32(const CallbackInfo& info); Value GetPropertyWithNapiValue(const CallbackInfo& info); Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info); Value GetPropertyWithCStyleString(const CallbackInfo& info); @@ -15,6 +16,7 @@ void SetPropertyWithCStyleString(const CallbackInfo& info); void SetPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Delete() +Value DeletePropertyWithUint32(const CallbackInfo& info); Value DeletePropertyWithNapiValue(const CallbackInfo& info); Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info); Value DeletePropertyWithCStyleString(const CallbackInfo& info); @@ -27,6 +29,7 @@ Value HasOwnPropertyWithCStyleString(const CallbackInfo& info); Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Has() +Value HasPropertyWithUint32(const CallbackInfo& info); Value HasPropertyWithNapiValue(const CallbackInfo& info); Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info); Value HasPropertyWithCStyleString(const CallbackInfo& info); @@ -265,6 +268,7 @@ Object InitObject(Env env) { exports["defineProperties"] = Function::New(env, DefineProperties); exports["defineValueProperty"] = Function::New(env, DefineValueProperty); + exports["getPropertyWithUint32"] = Function::New(env, GetPropertyWithUint32); exports["getPropertyWithNapiValue"] = Function::New(env, GetPropertyWithNapiValue); exports["getPropertyWithNapiWrapperValue"] = Function::New(env, GetPropertyWithNapiWrapperValue); exports["getPropertyWithCStyleString"] = Function::New(env, GetPropertyWithCStyleString); @@ -275,6 +279,8 @@ Object InitObject(Env env) { exports["setPropertyWithCStyleString"] = Function::New(env, SetPropertyWithCStyleString); exports["setPropertyWithCppStyleString"] = Function::New(env, SetPropertyWithCppStyleString); + exports["deletePropertyWithUint32"] = + Function::New(env, DeletePropertyWithUint32); exports["deletePropertyWithNapiValue"] = Function::New(env, DeletePropertyWithNapiValue); exports["deletePropertyWithNapiWrapperValue"] = Function::New(env, DeletePropertyWithNapiWrapperValue); exports["deletePropertyWithCStyleString"] = Function::New(env, DeletePropertyWithCStyleString); @@ -285,6 +291,7 @@ Object InitObject(Env env) { exports["hasOwnPropertyWithCStyleString"] = Function::New(env, HasOwnPropertyWithCStyleString); exports["hasOwnPropertyWithCppStyleString"] = Function::New(env, HasOwnPropertyWithCppStyleString); + exports["hasPropertyWithUint32"] = Function::New(env, HasPropertyWithUint32); exports["hasPropertyWithNapiValue"] = Function::New(env, HasPropertyWithNapiValue); exports["hasPropertyWithNapiWrapperValue"] = Function::New(env, HasPropertyWithNapiWrapperValue); exports["hasPropertyWithCStyleString"] = Function::New(env, HasPropertyWithCStyleString); From bdf0eef4c22b3b828f327d96c27c8b8f178870a8 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Sat, 27 Mar 2021 21:00:29 +0100 Subject: [PATCH 14/57] src: added Freeze and Seal method to Object class. PR-URL: https://github.com/nodejs/node-addon-api/pull/955 Reviewed-By: Gabriel Schulhof Reviewed-By: Michael Dawson --- doc/object.md | 24 +++++++++++++++++++ napi-inl.h | 12 ++++++++++ napi.h | 4 ++++ test/binding.cc | 6 +++++ test/binding.gyp | 1 + test/index.js | 4 ++++ test/object/object_freeze_seal.cc | 24 +++++++++++++++++++ test/object/object_freeze_seal.js | 38 +++++++++++++++++++++++++++++++ 8 files changed, 113 insertions(+) create mode 100644 test/object/object_freeze_seal.cc create mode 100644 test/object/object_freeze_seal.js diff --git a/doc/object.md b/doc/object.md index ce5430cc4..596e6d632 100644 --- a/doc/object.md +++ b/doc/object.md @@ -217,6 +217,30 @@ void Napi::Object::DefineProperties (____ properties) Defines properties on the object. +### Freeze() + +```cpp +void Napi::Object::Freeze() +``` + +The `Napi::Object::Freeze()` method freezes an object. A frozen object can no +longer changed. Freezing an object prevents new properties from being added to +it, existing properties from being removed, prevents changing the +enumerability, configurability, or writability of existing properties and +prevents the valuee of existing properties from being changed. In addition, +freezing an object also prevents its prototype from being changed. + +### Seal() + +```cpp +void Napi::Object::Seal() +``` + +The `Napi::Object::Seal()` method seals an object, preventing new properties +from being added to it and marking all existing properties as non-configurable. +Values of present properties can still be changed as long as thery are +writable. + ### operator\[\]() ```cpp diff --git a/napi-inl.h b/napi-inl.h index 0177c7d6b..9d2f366e1 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1379,6 +1379,18 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } +#if NAPI_VERSION >= 8 +inline void Object::Freeze() { + napi_status status = napi_object_freeze(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Object::Seal() { + napi_status status = napi_object_seal(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION >= 8 + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 8eeb1443b..d27ddb28b 100644 --- a/napi.h +++ b/napi.h @@ -776,6 +776,10 @@ namespace Napi { inline void AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint); +#if NAPI_VERSION >= 8 + void Freeze(); + void Seal(); +#endif // NAPI_VERSION >= 8 }; template diff --git a/test/binding.cc b/test/binding.cc index 1ed7cb053..2dc089cbe 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -67,6 +67,9 @@ Object InitObjectReference(Env env); Object InitReference(Env env); Object InitVersionManagement(Env env); Object InitThunkingManual(Env env); +#if (NAPI_VERSION > 7) +Object InitObjectFreezeSeal(Env env); +#endif Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -141,6 +144,9 @@ Object Init(Env env, Object exports) { exports.Set("reference", InitReference(env)); exports.Set("version_management", InitVersionManagement(env)); exports.Set("thunking_manual", InitThunkingManual(env)); +#if (NAPI_VERSION > 7) + exports.Set("object_freeze_seal", InitObjectFreezeSeal(env)); +#endif return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 8f67fee55..8ecf91b83 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -39,6 +39,7 @@ 'object/has_own_property.cc', 'object/has_property.cc', 'object/object.cc', + 'object/object_freeze_seal.cc', 'object/set_property.cc', 'object/subscript_operator.cc', 'promise.cc', diff --git a/test/index.js b/test/index.js index 8799bb302..46bb749f2 100644 --- a/test/index.js +++ b/test/index.js @@ -112,6 +112,10 @@ if (majorNodeVersion < 12) { testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); } +if (napiVersion < 8) { + testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1); +} + (async function() { console.log(`Testing with Node-API Version '${napiVersion}'.`); diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc new file mode 100644 index 000000000..2d6f4b616 --- /dev/null +++ b/test/object/object_freeze_seal.cc @@ -0,0 +1,24 @@ +#include "napi.h" + +#if (NAPI_VERSION > 7) + +using namespace Napi; + +void Freeze(const CallbackInfo& info) { + Object obj = info[0].As(); + obj.Freeze(); +} + +void Seal(const CallbackInfo& info) { + Object obj = info[0].As(); + obj.Seal(); +} + +Object InitObjectFreezeSeal(Env env) { + Object exports = Object::New(env); + exports["freeze"] = Function::New(env, Freeze); + exports["seal"] = Function::New(env, Seal); + return exports; +} + +#endif diff --git a/test/object/object_freeze_seal.js b/test/object/object_freeze_seal.js new file mode 100644 index 000000000..f85c36d67 --- /dev/null +++ b/test/object/object_freeze_seal.js @@ -0,0 +1,38 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + { + const obj = { x: 'a', y: 'b', z: 'c' }; + binding.object_freeze_seal.freeze(obj); + assert.strictEqual(Object.isFrozen(obj), true); + assert.throws(() => { + obj.x = 10; + }, /Cannot assign to read only property 'x' of object '#/); + assert.throws(() => { + obj.w = 15; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + } + + { + const obj = { x: 'a', y: 'b', z: 'c' }; + binding.object_freeze_seal.seal(obj); + assert.strictEqual(Object.isSealed(obj), true); + assert.throws(() => { + obj.w = 'd'; + }, /Cannot add property w, object is not extensible/); + assert.throws(() => { + delete obj.x; + }, /Cannot delete property 'x' of #/); + // Sealed objects allow updating existing properties, + // so this should not throw. + obj.x = 'd'; + } +} From 79e078634e8363c1a5717a341fc72933a095340b Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Fri, 9 Apr 2021 16:07:24 -0400 Subject: [PATCH 15/57] Update CONTRIBUTING.md (#966) --- CONTRIBUTING.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7abe84d33..2296aef5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,30 @@ + +# Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + # **node-addon-api** Contribution Philosophy The **node-addon-api** team loves contributions. There are many ways in which you can From 837a8200b1ce7c3e580e8c88bf1335d1a000dc99 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Fri, 9 Apr 2021 16:07:40 -0400 Subject: [PATCH 16/57] src: add pull request template (#967) --- .github/PULL_REQUEST_TEMPLATE.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..f04d2c139 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ + From ac71abb304c82bda5a5d9dc0cc44d7b61e67be22 Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Mon, 12 Apr 2021 17:51:45 +0200 Subject: [PATCH 17/57] Added badges for Node-API v7 and v8. (#954) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c104a45dc..78ffe36ea 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,8 @@ available: ![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) ![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) ![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) +![Node-API v7 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v7%20Badge.svg) +![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg) ![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) ## **Contributing** From a5280a8164daab46cfd4a3b5af694c1b95d8c2f5 Mon Sep 17 00:00:00 2001 From: Darshan Sen Date: Fri, 16 Apr 2021 23:50:48 +0530 Subject: [PATCH 18/57] doc: correct struct definition (#969) --- doc/version_management.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/version_management.md b/doc/version_management.md index 6fe51196a..1cdc48321 100644 --- a/doc/version_management.md +++ b/doc/version_management.md @@ -25,8 +25,7 @@ information is stored in the `napi_node_version` structure that is defined as shown below: ```cpp -using napi_node_version = -struct { +typedef struct { uint32_t major; uint32_t minor; uint32_t patch; From 34502c3c8b7aafa393f65b6b7674389b6717569d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20VOYER?= Date: Fri, 16 Apr 2021 20:21:57 +0200 Subject: [PATCH 19/57] fix: key for wapping drawing's system condition (#970) --- doc/setup.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/setup.md b/doc/setup.md index bbb16a138..513f4a3c8 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -58,12 +58,14 @@ To use **Node-API** in a native module: following settings in the `binding.gyp` file: ```gyp - ['OS=="mac"', { - 'cflags+': ['-fvisibility=hidden'], - 'xcode_settings': { - 'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden - } - }] + 'conditions': [ + ['OS=="mac"', { + 'cflags+': ['-fvisibility=hidden'], + 'xcode_settings': { + 'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden + } + }] + ] ``` 5. Include `napi.h` in the native module code. From b74908f46cec857fff27404e38a6f849433abb0e Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Wed, 21 Apr 2021 11:55:56 +0200 Subject: [PATCH 20/57] test: fix intermittent TSFN crashes PR-URL: https://github.com/nodejs/node-addon-api/pull/974 Reviewed-By: Michael Dawson --- test/binding.cc | 2 +- .../threadsafe_function.cc | 24 ++++++++--- .../typed_threadsafe_function.cc | 42 ++++++++++++------- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/test/binding.cc b/test/binding.cc index 2dc089cbe..92f6c77df 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -121,7 +121,7 @@ Object Init(Env env, Object exports) { exports.Set("threadsafe_function_ptr", InitThreadSafeFunctionPtr(env)); exports.Set("threadsafe_function_sum", InitThreadSafeFunctionSum(env)); exports.Set("threadsafe_function_unref", InitThreadSafeFunctionUnref(env)); - exports.Set("threadsafe_function", InitTypedThreadSafeFunction(env)); + exports.Set("threadsafe_function", InitThreadSafeFunction(env)); exports.Set("typed_threadsafe_function_ctx", InitTypedThreadSafeFunctionCtx(env)); exports.Set("typed_threadsafe_function_existing_tsfn", diff --git a/test/threadsafe_function/threadsafe_function.cc b/test/threadsafe_function/threadsafe_function.cc index e9b16083b..6886eef47 100644 --- a/test/threadsafe_function/threadsafe_function.cc +++ b/test/threadsafe_function/threadsafe_function.cc @@ -1,4 +1,6 @@ #include +#include +#include #include #include "napi.h" @@ -22,6 +24,9 @@ struct ThreadSafeFunctionInfo { bool startSecondary; FunctionReference jsFinalizeCallback; uint32_t maxQueueSize; + bool closeCalledFromJs; + std::mutex protect; + std::condition_variable signal; } tsfnInfo; // Thread data to transmit to JS @@ -65,12 +70,13 @@ static void DataSourceThread() { break; } - if (info->maxQueueSize == 0) { - // Let's make this thread really busy for 200 ms to give the main thread a - // chance to abort. - auto start = std::chrono::high_resolution_clock::now(); - constexpr auto MS_200 = std::chrono::milliseconds(200); - for (; std::chrono::high_resolution_clock::now() - start < MS_200;); + if (info->abort && info->type != ThreadSafeFunctionInfo::NON_BLOCKING) { + // Let's make this thread really busy to give the main thread a chance to + // abort / close. + std::unique_lock lk(info->protect); + while (!info->closeCalledFromJs) { + info->signal.wait(lk); + } } switch (status) { @@ -112,6 +118,11 @@ static Value StopThread(const CallbackInfo& info) { } else { tsfn.Release(); } + { + std::lock_guard _(tsfnInfo.protect); + tsfnInfo.closeCalledFromJs = true; + tsfnInfo.signal.notify_one(); + } return Value(); } @@ -134,6 +145,7 @@ static Value StartThreadInternal(const CallbackInfo& info, tsfnInfo.abort = info[1].As(); tsfnInfo.startSecondary = info[2].As(); tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); + tsfnInfo.closeCalledFromJs = false; tsfn = ThreadSafeFunction::New(info.Env(), info[0].As(), "Test", tsfnInfo.maxQueueSize, 2, &tsfnInfo, JoinTheThreads, threads); diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.cc b/test/typed_threadsafe_function/typed_threadsafe_function.cc index f9896db86..c25268aaf 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function.cc @@ -1,4 +1,6 @@ #include +#include +#include #include #include "napi.h" @@ -17,6 +19,9 @@ static struct ThreadSafeFunctionInfo { bool startSecondary; FunctionReference jsFinalizeCallback; uint32_t maxQueueSize; + bool closeCalledFromJs; + std::mutex protect; + std::condition_variable signal; } tsfnInfo; static void TSFNCallJS(Env env, @@ -42,7 +47,7 @@ static int ints[ARRAY_LENGTH]; static void SecondaryThread() { if (tsfn.Release() != napi_ok) { - Error::Fatal("SecondaryThread", "ThreadSafeFunction.Release() failed"); + Error::Fatal("TypedSecondaryThread", "ThreadSafeFunction.Release() failed"); } } @@ -52,7 +57,8 @@ static void DataSourceThread() { if (info->startSecondary) { if (tsfn.Acquire() != napi_ok) { - Error::Fatal("DataSourceThread", "ThreadSafeFunction.Acquire() failed"); + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.Acquire() failed"); } threads[1] = std::thread(SecondaryThread); @@ -75,13 +81,13 @@ static void DataSourceThread() { break; } - if (info->maxQueueSize == 0) { - // Let's make this thread really busy for 200 ms to give the main thread a - // chance to abort. - auto start = std::chrono::high_resolution_clock::now(); - constexpr auto MS_200 = std::chrono::milliseconds(200); - for (; std::chrono::high_resolution_clock::now() - start < MS_200;) - ; + if (info->abort && info->type != ThreadSafeFunctionInfo::NON_BLOCKING) { + // Let's make this thread really busy to give the main thread a chance to + // abort / close. + std::unique_lock lk(info->protect); + while (!info->closeCalledFromJs) { + info->signal.wait(lk); + } } switch (status) { @@ -98,20 +104,22 @@ static void DataSourceThread() { break; default: - Error::Fatal("DataSourceThread", "ThreadSafeFunction.*Call() failed"); + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.*Call() failed"); } } if (info->type == ThreadSafeFunctionInfo::NON_BLOCKING && !queueWasFull) { - Error::Fatal("DataSourceThread", "Queue was never full"); + Error::Fatal("TypedDataSourceThread", "Queue was never full"); } if (info->abort && !queueWasClosing) { - Error::Fatal("DataSourceThread", "Queue was never closing"); + Error::Fatal("TypedDataSourceThread", "Queue was never closing"); } if (!queueWasClosing && tsfn.Release() != napi_ok) { - Error::Fatal("DataSourceThread", "ThreadSafeFunction.Release() failed"); + Error::Fatal("TypedDataSourceThread", + "ThreadSafeFunction.Release() failed"); } } @@ -123,6 +131,11 @@ static Value StopThread(const CallbackInfo& info) { } else { tsfn.Release(); } + { + std::lock_guard _(tsfnInfo.protect); + tsfnInfo.closeCalledFromJs = true; + tsfnInfo.signal.notify_one(); + } return Value(); } @@ -145,6 +158,7 @@ static Value StartThreadInternal(const CallbackInfo& info, tsfnInfo.abort = info[1].As(); tsfnInfo.startSecondary = info[2].As(); tsfnInfo.maxQueueSize = info[3].As().Uint32Value(); + tsfnInfo.closeCalledFromJs = false; tsfn = TSFN::New(info.Env(), info[0].As(), @@ -163,7 +177,7 @@ static Value StartThreadInternal(const CallbackInfo& info, static Value Release(const CallbackInfo& /* info */) { if (tsfn.Release() != napi_ok) { - Error::Fatal("Release", "ThreadSafeFunction.Release() failed"); + Error::Fatal("Release", "TypedThreadSafeFunction.Release() failed"); } return Value(); } From 3aad6daaccf78b91ece034a60fd043faa7f2336c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Tue, 20 Apr 2021 20:25:57 +0200 Subject: [PATCH 21/57] test: rename misspelled parameters PR-URL: https://github.com/nodejs/node-addon-api/pull/973 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- test/globalObject/global_object_delete_property.js | 4 ++-- test/globalObject/global_object_has_own_property.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/globalObject/global_object_delete_property.js b/test/globalObject/global_object_delete_property.js index b6334f1ce..885200745 100644 --- a/test/globalObject/global_object_delete_property.js +++ b/test/globalObject/global_object_delete_property.js @@ -37,9 +37,9 @@ function test(binding) { } } - function assertErrMessageIsThrown(propertyCheckExistanceFunction, errMsg) { + function assertErrMessageIsThrown(propertyCheckExistenceFunction, errMsg) { assert.throws(() => { - propertyCheckExistanceFunction(undefined); + propertyCheckExistenceFunction(undefined); }, errMsg); } diff --git a/test/globalObject/global_object_has_own_property.js b/test/globalObject/global_object_has_own_property.js index 65c8c6a71..d3955cdfb 100644 --- a/test/globalObject/global_object_has_own_property.js +++ b/test/globalObject/global_object_has_own_property.js @@ -33,9 +33,9 @@ function test(binding) { } } - function assertErrMessageIsThrown(propertyCheckExistanceFunction, errMsg) { + function assertErrMessageIsThrown(propertyCheckExistenceFunction, errMsg) { assert.throws(() => { - propertyCheckExistanceFunction(undefined); + propertyCheckExistenceFunction(undefined); }, errMsg); } From ad4cc6ff5730cac7e4948c3aeb3127e3477e6ee4 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 23 Apr 2021 20:51:19 +0800 Subject: [PATCH 22/57] test: run test suites with helpers PR-URL: https://github.com/nodejs/node-addon-api/pull/976 Reviewed-By: Michael Dawson --- test/addon.js | 5 +- test/addon_data.js | 9 +--- test/arraybuffer.js | 5 +- test/asynccontext.js | 49 ++++++++++++------- test/asyncprogressqueueworker.js | 6 +-- test/asyncprogressworker.js | 5 +- test/asyncworker-nocallback.js | 5 +- test/asyncworker-persistent.js | 13 ++--- test/asyncworker.js | 23 ++++++--- test/basic_types/array.js | 4 +- test/basic_types/boolean.js | 5 +- test/basic_types/number.js | 4 +- test/basic_types/value.js | 4 +- test/bigint.js | 4 +- test/buffer.js | 5 +- test/callbackscope.js | 15 +++--- test/common/index.js | 27 ++++++++++ test/dataview/dataview.js | 5 +- test/dataview/dataview_read_write.js | 4 +- test/date.js | 4 +- test/error.js | 5 +- test/external.js | 6 +-- test/function.js | 10 ++-- .../global_object_delete_property.js | 5 +- .../global_object_get_property.js | 5 +- .../global_object_has_own_property.js | 5 +- .../global_object_set_property.js | 5 +- test/handlescope.js | 5 +- test/index.js | 16 +++--- test/memory_management.js | 7 ++- test/movable_callbacks.js | 10 +--- test/name.js | 5 +- test/object/delete_property.js | 4 +- test/object/finalizer.js | 4 +- test/object/get_property.js | 4 +- test/object/has_own_property.js | 4 +- test/object/has_property.js | 4 +- test/object/object.js | 5 +- test/object/object_deprecated.js | 5 +- test/object/object_freeze_seal.js | 5 +- test/object/set_property.js | 4 +- test/object/subscript_operator.js | 4 +- test/objectreference.js | 5 +- test/objectwrap-removewrap.js | 6 +-- test/objectwrap.js | 7 ++- test/objectwrap_constructor_exception.js | 5 +- test/objectwrap_multiple_inheritance.js | 4 +- test/objectwrap_worker_thread.js | 3 +- test/promise.js | 5 +- test/reference.js | 5 +- test/run_script.js | 5 +- .../threadsafe_function.js | 6 +-- .../threadsafe_function_ctx.js | 4 +- .../threadsafe_function_existing_tsfn.js | 7 +-- .../threadsafe_function_ptr.js | 5 +- .../threadsafe_function_sum.js | 4 +- .../threadsafe_function_unref.js | 4 +- test/thunking_manual.js | 5 +- .../typed_threadsafe_function.js | 6 +-- .../typed_threadsafe_function_ctx.js | 4 +- ...typed_threadsafe_function_existing_tsfn.js | 5 +- .../typed_threadsafe_function_ptr.js | 5 +- .../typed_threadsafe_function_sum.js | 4 +- .../typed_threadsafe_function_unref.js | 4 +- test/typedarray-bigint.js | 5 +- test/typedarray.js | 5 +- test/version_management.js | 5 +- 67 files changed, 191 insertions(+), 260 deletions(-) diff --git a/test/addon.js b/test/addon.js index 54a5f666a..b9fca2f15 100644 --- a/test/addon.js +++ b/test/addon.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { assert.strictEqual(binding.addon.increment(), 43); diff --git a/test/addon_data.js b/test/addon_data.js index 571b23e72..1762d9663 100644 --- a/test/addon_data.js +++ b/test/addon_data.js @@ -1,15 +1,10 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const { spawn } = require('child_process'); const readline = require('readline'); -const path = require('path'); -module.exports = - test(path.resolve(__dirname, `./build/${buildType}/binding.node`)) - .then(() => - test(path.resolve(__dirname, - `./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTestWithBindingPath(test); // Make sure the instance data finalizer is called at process exit. If the hint // is non-zero, it will be printed out by the child process. diff --git a/test/arraybuffer.js b/test/arraybuffer.js index 363de17d9..942686f2f 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); function test(binding) { return testUtil.runGCTests([ diff --git a/test/asynccontext.js b/test/asynccontext.js index e9b4aabc3..d6a3fc5aa 100644 --- a/test/asynccontext.js +++ b/test/asynccontext.js @@ -1,5 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const common = require('./common'); @@ -17,14 +17,27 @@ function checkAsyncHooks() { return false; } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = common.runTest(test); function installAsyncHooksForTest() { return new Promise((resolve, reject) => { let id; const events = []; - const hook = async_hooks.createHook({ + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let hook; + let destroyed = false; + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + + hook = async_hooks.createHook({ init(asyncId, type, triggerAsyncId, resource) { if (id === undefined && type === 'async_context_test') { id = asyncId; @@ -44,8 +57,7 @@ function installAsyncHooksForTest() { destroy(asyncId) { if (asyncId === id) { events.push({ eventName: 'destroy' }); - hook.disable(); - resolve(events); + destroyed = true; } } }).enable(); @@ -53,21 +65,22 @@ function installAsyncHooksForTest() { } function test(binding) { - binding.asynccontext.makeCallback(common.mustCall(), { foo: 'foo' }); - if (!checkAsyncHooks()) + if (!checkAsyncHooks()) { return; + } const hooks = installAsyncHooksForTest(); const triggerAsyncId = async_hooks.executionAsyncId(); - hooks.then(actual => { - assert.deepStrictEqual(actual, [ - { eventName: 'init', - type: 'async_context_test', - triggerAsyncId: triggerAsyncId, - resource: { foo: 'foo' } }, - { eventName: 'before' }, - { eventName: 'after' }, - { eventName: 'destroy' } - ]); + binding.asynccontext.makeCallback(common.mustCall(), { foo: 'foo' }); + return hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { eventName: 'init', + type: 'async_context_test', + triggerAsyncId: triggerAsyncId, + resource: { foo: 'foo' } }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); }).catch(common.mustNotCall()); } diff --git a/test/asyncprogressqueueworker.js b/test/asyncprogressqueueworker.js index 4bb525e9a..e7150012d 100644 --- a/test/asyncprogressqueueworker.js +++ b/test/asyncprogressqueueworker.js @@ -1,11 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const common = require('./common') const assert = require('assert'); -const os = require('os'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); async function test({ asyncprogressqueueworker }) { await success(asyncprogressqueueworker); diff --git a/test/asyncprogressworker.js b/test/asyncprogressworker.js index 285fd2a91..2a81b204d 100644 --- a/test/asyncprogressworker.js +++ b/test/asyncprogressworker.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const common = require('./common') const assert = require('assert'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); async function test({ asyncprogressworker }) { await success(asyncprogressworker); diff --git a/test/asyncworker-nocallback.js b/test/asyncworker-nocallback.js index fa9c172c0..d4e28ce6a 100644 --- a/test/asyncworker-nocallback.js +++ b/test/asyncworker-nocallback.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const common = require('./common'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); async function test(binding) { await binding.asyncworker.doWorkNoCallback(true, {}) diff --git a/test/asyncworker-persistent.js b/test/asyncworker-persistent.js index d584086e7..f4febac45 100644 --- a/test/asyncworker-persistent.js +++ b/test/asyncworker-persistent.js @@ -1,9 +1,6 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -const common = require('./common'); -const binding = require(`./build/${buildType}/binding.node`); -const noexceptBinding = require(`./build/${buildType}/binding_noexcept.node`); function test(binding, succeed) { return new Promise((resolve) => @@ -21,7 +18,7 @@ function test(binding, succeed) { })); } -module.exports = test(binding.persistentasyncworker, false) - .then(() => test(binding.persistentasyncworker, true)) - .then(() => test(noexceptBinding.persistentasyncworker, false)) - .then(() => test(noexceptBinding.persistentasyncworker, true)); +module.exports = require('./common').runTest(async binding => { + await test(binding.persistentasyncworker, false); + await test(binding.persistentasyncworker, true); +}); diff --git a/test/asyncworker.js b/test/asyncworker.js index 0f008bb33..46ab14340 100644 --- a/test/asyncworker.js +++ b/test/asyncworker.js @@ -1,5 +1,4 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('./common'); @@ -17,14 +16,27 @@ function checkAsyncHooks() { return false; } -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); function installAsyncHooksForTest() { return new Promise((resolve, reject) => { let id; const events = []; - const hook = async_hooks.createHook({ + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let hook; + let destroyed = false; + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + + hook = async_hooks.createHook({ init(asyncId, type, triggerAsyncId, resource) { if (id === undefined && type === 'TestResource') { id = asyncId; @@ -44,8 +56,7 @@ function installAsyncHooksForTest() { destroy(asyncId) { if (asyncId === id) { events.push({ eventName: 'destroy' }); - hook.disable(); - resolve(events); + destroyed = true; } } }).enable(); diff --git a/test/basic_types/array.js b/test/basic_types/array.js index 38ccba448..a3e569453 100644 --- a/test/basic_types/array.js +++ b/test/basic_types/array.js @@ -1,9 +1,7 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { diff --git a/test/basic_types/boolean.js b/test/basic_types/boolean.js index 13817ee52..04f38e56e 100644 --- a/test/basic_types/boolean.js +++ b/test/basic_types/boolean.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { const bool1 = binding.basic_types_boolean.createBoolean(true); diff --git a/test/basic_types/number.js b/test/basic_types/number.js index a7c66bbc3..592b6ada0 100644 --- a/test/basic_types/number.js +++ b/test/basic_types/number.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { const MIN_INT32 = -2147483648; diff --git a/test/basic_types/value.js b/test/basic_types/value.js index bef7e44e8..81a95cb02 100644 --- a/test/basic_types/value.js +++ b/test/basic_types/value.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { const externalValue = binding.basic_types_value.createExternal(); diff --git a/test/bigint.js b/test/bigint.js index 0af867b43..bc27d9501 100644 --- a/test/bigint.js +++ b/test/bigint.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { const { diff --git a/test/buffer.js b/test/buffer.js index ff17d30e7..9ff5ec1be 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -1,11 +1,10 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); const safeBuffer = require('safe-buffer'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); function test(binding) { return testUtil.runGCTests([ diff --git a/test/callbackscope.js b/test/callbackscope.js index 523bca462..739648202 100644 --- a/test/callbackscope.js +++ b/test/callbackscope.js @@ -1,7 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -const common = require('./common'); // we only check async hooks on 8.x an higher were // they are closer to working properly @@ -17,8 +15,7 @@ function checkAsyncHooks() { return false; } -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { if (!checkAsyncHooks()) @@ -26,7 +23,7 @@ function test(binding) { let id; let insideHook = false; - async_hooks.createHook({ + const hook = async_hooks.createHook({ init(asyncId, type, triggerAsyncId, resource) { if (id === undefined && type === 'callback_scope_test') { id = asyncId; @@ -42,7 +39,11 @@ function test(binding) { } }).enable(); - binding.callbackscope.runInCallbackScope(function() { - assert(insideHook); + return new Promise(resolve => { + binding.callbackscope.runInCallbackScope(function() { + assert(insideHook); + hook.disable(); + resolve(); + }); }); } diff --git a/test/common/index.js b/test/common/index.js index 54139bb2d..65bdc10b7 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -74,3 +74,30 @@ exports.mustNotCall = function(msg) { assert.fail(msg || 'function should not have been called'); }; }; + +exports.runTest = async function(test, buildType) { + buildType = buildType || process.config.target_defaults.default_configuration; + + const bindings = [ + `../build/${buildType}/binding.node`, + `../build/${buildType}/binding_noexcept.node`, + ].map(it => require.resolve(it)); + + for (const item of bindings) { + await Promise.resolve(test(require(item))) + .finally(exports.mustCall()); + } +} + +exports.runTestWithBindingPath = async function(test, buildType) { + buildType = buildType || process.config.target_defaults.default_configuration; + + const bindings = [ + `../build/${buildType}/binding.node`, + `../build/${buildType}/binding_noexcept.node`, + ].map(it => require.resolve(it)); + + for (const item of bindings) { + await test(item); + } +} diff --git a/test/dataview/dataview.js b/test/dataview/dataview.js index 4e3936457..1d476c44d 100644 --- a/test/dataview/dataview.js +++ b/test/dataview/dataview.js @@ -1,10 +1,7 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); - -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testDataViewCreation(factory, arrayBuffer, offset, length) { diff --git a/test/dataview/dataview_read_write.js b/test/dataview/dataview_read_write.js index 83d58cc11..fb8502a71 100644 --- a/test/dataview/dataview_read_write.js +++ b/test/dataview/dataview_read_write.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function expected(type, value) { diff --git a/test/date.js b/test/date.js index 16e618e5b..bd24291b8 100644 --- a/test/date.js +++ b/test/date.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { const { diff --git a/test/error.js b/test/error.js index 031db081a..763115c64 100644 --- a/test/error.js +++ b/test/error.js @@ -1,5 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); if (process.argv[2] === 'fatal') { @@ -8,8 +8,7 @@ if (process.argv[2] === 'fatal') { return; } -test(`./build/${buildType}/binding.node`); -test(`./build/${buildType}/binding_noexcept.node`); +module.exports = require('./common').runTestWithBindingPath(test); function test(bindingPath) { const binding = require(bindingPath); diff --git a/test/external.js b/test/external.js index 0443e3f55..8a8cebcde 100644 --- a/test/external.js +++ b/test/external.js @@ -1,5 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const { spawnSync } = require('child_process'); const testUtil = require('./testUtil'); @@ -40,9 +40,7 @@ if (process.argv.length === 3) { return; } -module.exports = test(require.resolve(`./build/${buildType}/binding.node`)) - .then(() => - test(require.resolve(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTestWithBindingPath(test); function test(bindingPath) { const binding = require(bindingPath); diff --git a/test/function.js b/test/function.js index d98b1944d..9c30e046d 100644 --- a/test/function.js +++ b/test/function.js @@ -1,11 +1,11 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`).function.plain); -test(require(`./build/${buildType}/binding_noexcept.node`).function.plain); -test(require(`./build/${buildType}/binding.node`).function.templated); -test(require(`./build/${buildType}/binding_noexcept.node`).function.templated); +module.exports = require('./common').runTest(binding => { + test(binding.function.plain); + test(binding.function.templated); +}); function test(binding) { assert.strictEqual(binding.emptyConstructor(true), true); diff --git a/test/globalObject/global_object_delete_property.js b/test/globalObject/global_object_delete_property.js index 885200745..c3f3dad95 100644 --- a/test/globalObject/global_object_delete_property.js +++ b/test/globalObject/global_object_delete_property.js @@ -1,11 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - +module.exports = require('../common').runTest(test); function test(binding) { const KEY_TYPE = { diff --git a/test/globalObject/global_object_get_property.js b/test/globalObject/global_object_get_property.js index 3630f4398..83b337368 100644 --- a/test/globalObject/global_object_get_property.js +++ b/test/globalObject/global_object_get_property.js @@ -1,11 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - +module.exports = require('../common').runTest(test); function test(binding) { const KEY_TYPE = { diff --git a/test/globalObject/global_object_has_own_property.js b/test/globalObject/global_object_has_own_property.js index d3955cdfb..30deeecdd 100644 --- a/test/globalObject/global_object_has_own_property.js +++ b/test/globalObject/global_object_has_own_property.js @@ -1,11 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - +module.exports = require('../common').runTest(test); function test(binding) { const KEY_TYPE = { diff --git a/test/globalObject/global_object_set_property.js b/test/globalObject/global_object_set_property.js index 06f01a611..d934efae9 100644 --- a/test/globalObject/global_object_set_property.js +++ b/test/globalObject/global_object_set_property.js @@ -1,11 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); - +module.exports = require('../common').runTest(test); function test(binding) { const KEY_TYPE = { diff --git a/test/handlescope.js b/test/handlescope.js index 71cb89783..f8887f0a0 100644 --- a/test/handlescope.js +++ b/test/handlescope.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { assert.strictEqual(binding.handlescope.createScope(), 'scope'); diff --git a/test/index.js b/test/index.js index 46bb749f2..1b03f76cf 100644 --- a/test/index.js +++ b/test/index.js @@ -117,17 +117,17 @@ if (napiVersion < 8) { } (async function() { -console.log(`Testing with Node-API Version '${napiVersion}'.`); + console.log(`Testing with Node-API Version '${napiVersion}'.`); -console.log('Starting test suite\n'); + console.log('Starting test suite\n'); -// Requiring each module runs tests in the module. -for (const name of testModules) { - console.log(`Running test '${name}'`); - await require('./' + name); -}; + // Requiring each module runs tests in the module. + for (const name of testModules) { + console.log(`Running test '${name}'`); + await require('./' + name); + }; -console.log('\nAll tests passed!'); + console.log('\nAll tests passed!'); })().catch((error) => { console.log(error); process.exit(1); diff --git a/test/memory_management.js b/test/memory_management.js index f4911a2a6..707899e79 100644 --- a/test/memory_management.js +++ b/test/memory_management.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -function test(binding) { +function test(binding) { assert.strictEqual(binding.memory_management.externalAllocatedMemory(), true) } diff --git a/test/movable_callbacks.js b/test/movable_callbacks.js index 49810d510..3588295da 100644 --- a/test/movable_callbacks.js +++ b/test/movable_callbacks.js @@ -1,15 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const common = require('./common'); const testUtil = require('./testUtil'); -Promise.all([ - test(require(`./build/${buildType}/binding.node`).movable_callbacks), - test(require(`./build/${buildType}/binding_noexcept.node`).movable_callbacks), -]).catch(e => { - console.error(e); - process.exitCode = 1; -}); +module.exports = require('./common').runTest(binding => test(binding.movable_callbacks)); async function test(binding) { await testUtil.runGCTests([ diff --git a/test/name.js b/test/name.js index 4e9659312..457774690 100644 --- a/test/name.js +++ b/test/name.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { const expected = '123456789'; diff --git a/test/object/delete_property.js b/test/object/delete_property.js index b65e063b0..459298d8a 100644 --- a/test/object/delete_property.js +++ b/test/object/delete_property.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testDeleteProperty(nativeDeleteProperty) { diff --git a/test/object/finalizer.js b/test/object/finalizer.js index 312b2de6d..bf2fb189e 100644 --- a/test/object/finalizer.js +++ b/test/object/finalizer.js @@ -1,11 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('../testUtil'); -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); function createWeakRef(binding, bindingToTest) { return binding.object[bindingToTest]({}); diff --git a/test/object/get_property.js b/test/object/get_property.js index b2b68d914..d661bab4d 100644 --- a/test/object/get_property.js +++ b/test/object/get_property.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testGetProperty(nativeGetProperty) { diff --git a/test/object/has_own_property.js b/test/object/has_own_property.js index 570b0ee46..fda74c3cb 100644 --- a/test/object/has_own_property.js +++ b/test/object/has_own_property.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testHasOwnProperty(nativeHasOwnProperty) { diff --git a/test/object/has_property.js b/test/object/has_property.js index f283fc77e..722f00652 100644 --- a/test/object/has_property.js +++ b/test/object/has_property.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testHasProperty(nativeHasProperty) { diff --git a/test/object/object.js b/test/object/object.js index 4b6f72fc7..ef06ad2fc 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function assertPropertyIs(obj, key, attribute) { diff --git a/test/object/object_deprecated.js b/test/object/object_deprecated.js index 153fb11e1..00ee7b4e2 100644 --- a/test/object/object_deprecated.js +++ b/test/object/object_deprecated.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { if (!('object_deprecated' in binding)) { diff --git a/test/object/object_freeze_seal.js b/test/object/object_freeze_seal.js index f85c36d67..b6434b6e3 100644 --- a/test/object/object_freeze_seal.js +++ b/test/object/object_freeze_seal.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { { diff --git a/test/object/set_property.js b/test/object/set_property.js index 9b64cc5dd..772538b26 100644 --- a/test/object/set_property.js +++ b/test/object/set_property.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testSetProperty(nativeSetProperty) { diff --git a/test/object/subscript_operator.js b/test/object/subscript_operator.js index 21a6ee891..0caefe45b 100644 --- a/test/object/subscript_operator.js +++ b/test/object/subscript_operator.js @@ -1,10 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { function testProperty(obj, key, value, nativeGetProperty, nativeSetProperty) { diff --git a/test/objectreference.js b/test/objectreference.js index 55b95dba6..daecb5958 100644 --- a/test/objectreference.js +++ b/test/objectreference.js @@ -10,12 +10,11 @@ */ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); function test(binding) { function testCastedEqual(testToCompare) { diff --git a/test/objectwrap-removewrap.js b/test/objectwrap-removewrap.js index 560f61d46..c7c99aaf4 100644 --- a/test/objectwrap-removewrap.js +++ b/test/objectwrap-removewrap.js @@ -5,11 +5,12 @@ if (process.argv[2] === 'child') { return new (require(process.argv[3]).objectwrap.Test)(); } -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const { spawnSync } = require('child_process'); const testUtil = require('./testUtil'); +module.exports = require('./common').runTestWithBindingPath(test); + function test(bindingName) { return testUtil.runGCTests([ 'objectwrap removewrap test', @@ -37,6 +38,3 @@ function test(bindingName) { assert.strictEqual(child.signal, null); assert.strictEqual(child.status, 0); } - -module.exports = test(`./build/${buildType}/binding.node`) - .then(() => test(`./build/${buildType}/binding_noexcept.node`)); diff --git a/test/objectwrap.js b/test/objectwrap.js index 3a4168359..2dcd12fd1 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -1,8 +1,10 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); +module.exports = require('./common').runTest(test); + async function test(binding) { const Test = binding.objectwrap.Test; @@ -280,6 +282,3 @@ async function test(binding) { // Make sure the C++ object can be garbage collected without issues. await testUtil.runGCTests(['one last gc', () => {}, () => {}]); } - -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); diff --git a/test/objectwrap_constructor_exception.js b/test/objectwrap_constructor_exception.js index 02dff2c48..6a5e469f6 100644 --- a/test/objectwrap_constructor_exception.js +++ b/test/objectwrap_constructor_exception.js @@ -1,5 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); @@ -15,5 +15,4 @@ function test(binding) { ]); } -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); diff --git a/test/objectwrap_multiple_inheritance.js b/test/objectwrap_multiple_inheritance.js index 87c669eb3..d4d2282b4 100644 --- a/test/objectwrap_multiple_inheritance.js +++ b/test/objectwrap_multiple_inheritance.js @@ -1,6 +1,5 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const test = bindingName => { @@ -11,5 +10,4 @@ const test = bindingName => { assert.strictEqual(testmi.test, 0); } -test(`./build/${buildType}/binding.node`); -test(`./build/${buildType}/binding_noexcept.node`); +module.exports = require('./common').runTestWithBindingPath(test); diff --git a/test/objectwrap_worker_thread.js b/test/objectwrap_worker_thread.js index 5e5e50b7e..eedc9eeda 100644 --- a/test/objectwrap_worker_thread.js +++ b/test/objectwrap_worker_thread.js @@ -10,6 +10,5 @@ if (isMainThread) { }; const buildType = workerData; - test(require(`./build/${buildType}/binding.node`)); - test(require(`./build/${buildType}/binding_noexcept.node`)); + require('./common').runTest(test, buildType); } diff --git a/test/promise.js b/test/promise.js index 65544c648..f681a2e0e 100644 --- a/test/promise.js +++ b/test/promise.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const common = require('./common'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = common.runTest(test); async function test(binding) { assert.strictEqual(binding.promise.isPromise({}), false); diff --git a/test/reference.js b/test/reference.js index 22ee8c842..8c56ceabf 100644 --- a/test/reference.js +++ b/test/reference.js @@ -1,12 +1,9 @@ 'use strict'; - -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); function test(binding) { return testUtil.runGCTests([ diff --git a/test/run_script.js b/test/run_script.js index ec36dcf51..711ab1035 100644 --- a/test/run_script.js +++ b/test/run_script.js @@ -1,10 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); const testUtil = require('./testUtil'); -module.exports = test(require(`./build/${buildType}/binding.node`)) - .then(() => test(require(`./build/${buildType}/binding_noexcept.node`))); +module.exports = require('./common').runTest(test); function test(binding) { return testUtil.runGCTests([ diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 419c214f8..7da0e3df6 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -1,13 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = (async function() { - await test(require(`../build/${buildType}/binding.node`)); - await test(require(`../build/${buildType}/binding_noexcept.node`)); -})(); +module.exports = common.runTest(test); async function test(binding) { const expectedArray = (function(arrayLength) { diff --git a/test/threadsafe_function/threadsafe_function_ctx.js b/test/threadsafe_function/threadsafe_function_ctx.js index 2651586a0..94ea01fb4 100644 --- a/test/threadsafe_function/threadsafe_function_ctx.js +++ b/test/threadsafe_function/threadsafe_function_ctx.js @@ -1,10 +1,8 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); async function test(binding) { const ctx = { }; diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.js b/test/threadsafe_function/threadsafe_function_existing_tsfn.js index d5ab1854a..f45978762 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.js +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.js @@ -2,14 +2,11 @@ const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); async function test(binding) { const testCall = binding.threadsafe_function_existing_tsfn.testCall; - + assert.strictEqual(typeof await testCall({ blocking: true, data: true }), "number"); assert.strictEqual(typeof await testCall({ blocking: true, data: false }), "undefined"); assert.strictEqual(typeof await testCall({ blocking: false, data: true }), "number"); diff --git a/test/threadsafe_function/threadsafe_function_ptr.js b/test/threadsafe_function/threadsafe_function_ptr.js index 535b5d642..189081615 100644 --- a/test/threadsafe_function/threadsafe_function_ptr.js +++ b/test/threadsafe_function/threadsafe_function_ptr.js @@ -1,9 +1,6 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; - -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { binding.threadsafe_function_ptr.test({}, () => {}); diff --git a/test/threadsafe_function/threadsafe_function_sum.js b/test/threadsafe_function/threadsafe_function_sum.js index 738e31db2..64f2b4513 100644 --- a/test/threadsafe_function/threadsafe_function_sum.js +++ b/test/threadsafe_function/threadsafe_function_sum.js @@ -1,6 +1,5 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; /** * @@ -29,8 +28,7 @@ const buildType = process.config.target_defaults.default_configuration; const THREAD_COUNT = 5; const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); /** @param {number[]} N */ const sum = (N) => N.reduce((sum, n) => sum + n, 0); diff --git a/test/threadsafe_function/threadsafe_function_unref.js b/test/threadsafe_function/threadsafe_function_unref.js index e8f0ee391..85aa85dc3 100644 --- a/test/threadsafe_function/threadsafe_function_unref.js +++ b/test/threadsafe_function/threadsafe_function_unref.js @@ -1,7 +1,6 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; const isMainProcess = process.argv[1] != __filename; @@ -15,8 +14,7 @@ const isMainProcess = process.argv[1] != __filename; */ if (isMainProcess) { - module.exports = test(`../build/${buildType}/binding.node`) - .then(() => test(`../build/${buildType}/binding_noexcept.node`)); + module.exports = require('../common').runTestWithBindingPath(test); } else { test(process.argv[2]); } diff --git a/test/thunking_manual.js b/test/thunking_manual.js index 22fb8877d..f1a93e296 100644 --- a/test/thunking_manual.js +++ b/test/thunking_manual.js @@ -1,10 +1,9 @@ // Flags: --expose-gc 'use strict'; -const buildType = 'Debug'; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { console.log("Thunking: Performing initial GC"); diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.js b/test/typed_threadsafe_function/typed_threadsafe_function.js index 7aa8cc2ad..5d4a52a79 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function.js @@ -1,13 +1,9 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); const common = require('../common'); -module.exports = (async function () { - await test(require(`../build/${buildType}/binding.node`)); - await test(require(`../build/${buildType}/binding_noexcept.node`)); -})(); +module.exports = common.runTest(test); async function test(binding) { const expectedArray = (function (arrayLength) { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js index 2651586a0..94ea01fb4 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js @@ -1,10 +1,8 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); async function test(binding) { const ctx = { }; diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js index b6df669d4..bd37824ad 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js @@ -2,10 +2,7 @@ const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; - -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); async function test(binding) { const testCall = binding.typed_threadsafe_function_existing_tsfn.testCall; diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js index 47b187761..36b146c23 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ptr.js @@ -1,9 +1,6 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; - -test(require(`../build/${buildType}/binding.node`)); -test(require(`../build/${buildType}/binding_noexcept.node`)); +module.exports = require('../common').runTest(test); function test(binding) { binding.typed_threadsafe_function_ptr.test({}, () => {}); diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_sum.js b/test/typed_threadsafe_function/typed_threadsafe_function_sum.js index 8f10476f6..59e667000 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_sum.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_sum.js @@ -1,6 +1,5 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; /** * @@ -29,8 +28,7 @@ const buildType = process.config.target_defaults.default_configuration; const THREAD_COUNT = 5; const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; -module.exports = test(require(`../build/${buildType}/binding.node`)) - .then(() => test(require(`../build/${buildType}/binding_noexcept.node`))); +module.exports = require('../common').runTest(test); /** @param {number[]} N */ const sum = (N) => N.reduce((sum, n) => sum + n, 0); diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_unref.js b/test/typed_threadsafe_function/typed_threadsafe_function_unref.js index 55b42a553..aafc5e0e0 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_unref.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_unref.js @@ -1,7 +1,6 @@ 'use strict'; const assert = require('assert'); -const buildType = process.config.target_defaults.default_configuration; const isMainProcess = process.argv[1] != __filename; @@ -15,8 +14,7 @@ const isMainProcess = process.argv[1] != __filename; */ if (isMainProcess) { - module.exports = test(`../build/${buildType}/binding.node`) - .then(() => test(`../build/${buildType}/binding_noexcept.node`)); + module.exports = require('../common').runTestWithBindingPath(test); } else { test(process.argv[2]); } diff --git a/test/typedarray-bigint.js b/test/typedarray-bigint.js index ce66d3898..e96a51f63 100644 --- a/test/typedarray-bigint.js +++ b/test/typedarray-bigint.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { [ diff --git a/test/typedarray.js b/test/typedarray.js index 9aa880c16..5d0fb4a27 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function test(binding) { const testData = [ diff --git a/test/version_management.js b/test/version_management.js index f52db2f73..6bb1b189b 100644 --- a/test/version_management.js +++ b/test/version_management.js @@ -1,9 +1,8 @@ 'use strict'; -const buildType = process.config.target_defaults.default_configuration; + const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); function parseVersion() { const expected = {}; From c4f1eb7674a960fc6a59ccc05512a1f2be8ef6be Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 28 Apr 2021 22:22:38 +0800 Subject: [PATCH 23/57] build: add Node.js v16.x to CI (#983) --- .github/workflows/ci-win.yml | 2 +- .github/workflows/ci.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 29e89fe47..4d8315c9b 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -7,7 +7,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - node-version: [10.x, 12.x, 14.x, 15.x] + node-version: [10.x, 12.x, 14.x, 15.x, 16.x] os: - windows-latest - windows-2016 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ad80e98b..83ab4e47e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ jobs: - node/12 - node/14 - node/15 + - node/16 compiler: - gcc - clang From 61236ef2ffe213e23fe3bbce6757f4677cbd9328 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 29 Apr 2021 10:49:29 +0800 Subject: [PATCH 24/57] src: return bool on object set and define property (#977) If c++ exception is disabled, there is no ergonomic way to detect failtures on void returning. --- doc/object.md | 6 +- doc/object_reference.md | 2 +- napi-inl.h | 109 ++++++++++++++++++++---------------- napi.h | 74 ++++++++++++------------ test/object/object.cc | 8 +-- test/object/set_property.cc | 16 +++--- test/object/set_property.js | 2 +- 7 files changed, 113 insertions(+), 104 deletions(-) diff --git a/doc/object.md b/doc/object.md index 596e6d632..06b19f9f1 100644 --- a/doc/object.md +++ b/doc/object.md @@ -72,7 +72,7 @@ Creates a new `Napi::Object` value. ### Set() ```cpp -void Napi::Object::Set (____ key, ____ value); +bool Napi::Object::Set (____ key, ____ value); ``` - `[in] key`: The name for the property being assigned. - `[in] value`: The value being assigned to the property. @@ -200,7 +200,7 @@ The key can be any of the following types: ### DefineProperty() ```cpp -void Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property); +bool Napi::Object::DefineProperty (const Napi::PropertyDescriptor& property); ``` - `[in] property`: A [`Napi::PropertyDescriptor`](property_descriptor.md). @@ -209,7 +209,7 @@ Define a property on the object. ### DefineProperties() ```cpp -void Napi::Object::DefineProperties (____ properties) +bool Napi::Object::DefineProperties (____ properties) ``` - `[in] properties`: A list of [`Napi::PropertyDescriptor`](property_descriptor.md). Can be one of the following types: - const std::initializer_list& diff --git a/doc/object_reference.md b/doc/object_reference.md index 8522b5341..38d6acc0e 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -81,7 +81,7 @@ Returns the newly created reference. ### Set ```cpp -void Napi::ObjectReference::Set(___ key, ___ value); +bool Napi::ObjectReference::Set(___ key, ___ value); ``` * `[in] key`: The name for the property being assigned. diff --git a/napi-inl.h b/napi-inl.h index 9d2f366e1..be20dc085 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1236,29 +1236,32 @@ inline Value Object::Get(const std::string& utf8name) const { } template -inline void Object::Set(napi_value key, const ValueType& value) { +inline bool Object::Set(napi_value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } template -inline void Object::Set(Value key, const ValueType& value) { +inline bool Object::Set(Value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } template -inline void Object::Set(const char* utf8name, const ValueType& value) { +inline bool Object::Set(const char* utf8name, const ValueType& value) { napi_status status = napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } template -inline void Object::Set(const std::string& utf8name, const ValueType& value) { - Set(utf8name.c_str(), value); +inline bool Object::Set(const std::string& utf8name, const ValueType& value) { + return Set(utf8name.c_str(), value); } inline bool Object::Delete(napi_value key) { @@ -1298,10 +1301,11 @@ inline Value Object::Get(uint32_t index) const { } template -inline void Object::Set(uint32_t index, const ValueType& value) { +inline bool Object::Set(uint32_t index, const ValueType& value) { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } inline bool Object::Delete(uint32_t index) { @@ -1318,22 +1322,27 @@ inline Array Object::GetPropertyNames() const { return Array(_env, result); } -inline void Object::DefineProperty(const PropertyDescriptor& property) { +inline bool Object::DefineProperty(const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } -inline void Object::DefineProperties(const std::initializer_list& properties) { +inline bool Object::DefineProperties( + const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } -inline void Object::DefineProperties(const std::vector& properties) { +inline bool Object::DefineProperties( + const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } inline bool Object::InstanceOf(const Function& constructor) const { @@ -2678,54 +2687,58 @@ inline Napi::Value ObjectReference::Get(const std::string& utf8name) const { return scope.Escape(Value().Get(utf8name)); } -inline void ObjectReference::Set(const char* utf8name, napi_value value) { +inline bool ObjectReference::Set(const char* utf8name, napi_value value) { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const char* utf8name, Napi::Value value) { +inline bool ObjectReference::Set(const char* utf8name, Napi::Value value) { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const char* utf8name, const char* utf8value) { +inline bool ObjectReference::Set(const char* utf8name, const char* utf8value) { HandleScope scope(_env); - Value().Set(utf8name, utf8value); + return Value().Set(utf8name, utf8value); } -inline void ObjectReference::Set(const char* utf8name, bool boolValue) { +inline bool ObjectReference::Set(const char* utf8name, bool boolValue) { HandleScope scope(_env); - Value().Set(utf8name, boolValue); + return Value().Set(utf8name, boolValue); } -inline void ObjectReference::Set(const char* utf8name, double numberValue) { +inline bool ObjectReference::Set(const char* utf8name, double numberValue) { HandleScope scope(_env); - Value().Set(utf8name, numberValue); + return Value().Set(utf8name, numberValue); } -inline void ObjectReference::Set(const std::string& utf8name, napi_value value) { +inline bool ObjectReference::Set(const std::string& utf8name, + napi_value value) { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const std::string& utf8name, Napi::Value value) { +inline bool ObjectReference::Set(const std::string& utf8name, + Napi::Value value) { HandleScope scope(_env); - Value().Set(utf8name, value); + return Value().Set(utf8name, value); } -inline void ObjectReference::Set(const std::string& utf8name, std::string& utf8value) { +inline bool ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) { HandleScope scope(_env); - Value().Set(utf8name, utf8value); + return Value().Set(utf8name, utf8value); } -inline void ObjectReference::Set(const std::string& utf8name, bool boolValue) { +inline bool ObjectReference::Set(const std::string& utf8name, bool boolValue) { HandleScope scope(_env); - Value().Set(utf8name, boolValue); + return Value().Set(utf8name, boolValue); } -inline void ObjectReference::Set(const std::string& utf8name, double numberValue) { +inline bool ObjectReference::Set(const std::string& utf8name, + double numberValue) { HandleScope scope(_env); - Value().Set(utf8name, numberValue); + return Value().Set(utf8name, numberValue); } inline Napi::Value ObjectReference::Get(uint32_t index) const { @@ -2733,34 +2746,34 @@ inline Napi::Value ObjectReference::Get(uint32_t index) const { return scope.Escape(Value().Get(index)); } -inline void ObjectReference::Set(uint32_t index, napi_value value) { +inline bool ObjectReference::Set(uint32_t index, napi_value value) { HandleScope scope(_env); - Value().Set(index, value); + return Value().Set(index, value); } -inline void ObjectReference::Set(uint32_t index, Napi::Value value) { +inline bool ObjectReference::Set(uint32_t index, Napi::Value value) { HandleScope scope(_env); - Value().Set(index, value); + return Value().Set(index, value); } -inline void ObjectReference::Set(uint32_t index, const char* utf8value) { +inline bool ObjectReference::Set(uint32_t index, const char* utf8value) { HandleScope scope(_env); - Value().Set(index, utf8value); + return Value().Set(index, utf8value); } -inline void ObjectReference::Set(uint32_t index, const std::string& utf8value) { +inline bool ObjectReference::Set(uint32_t index, const std::string& utf8value) { HandleScope scope(_env); - Value().Set(index, utf8value); + return Value().Set(index, utf8value); } -inline void ObjectReference::Set(uint32_t index, bool boolValue) { +inline bool ObjectReference::Set(uint32_t index, bool boolValue) { HandleScope scope(_env); - Value().Set(index, boolValue); + return Value().Set(index, boolValue); } -inline void ObjectReference::Set(uint32_t index, double numberValue) { +inline bool ObjectReference::Set(uint32_t index, double numberValue) { HandleScope scope(_env); - Value().Set(index, numberValue); + return Value().Set(index, numberValue); } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index d27ddb28b..8be2ca3f7 100644 --- a/napi.h +++ b/napi.h @@ -675,30 +675,26 @@ namespace Napi { /// Sets a property. template - void Set( - napi_value key, ///< Property key primitive - const ValueType& value ///< Property value primitive + bool Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive ); /// Sets a property. template - void Set( - Value key, ///< Property key - const ValueType& value ///< Property value + bool Set(Value key, ///< Property key + const ValueType& value ///< Property value ); /// Sets a named property. template - void Set( - const char* utf8name, ///< UTF-8 encoded null-terminated property name - const ValueType& value - ); + bool Set( + const char* utf8name, ///< UTF-8 encoded null-terminated property name + const ValueType& value); /// Sets a named property. template - void Set( - const std::string& utf8name, ///< UTF-8 encoded property name - const ValueType& value ///< Property value primitive + bool Set(const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive ); /// Delete property. @@ -733,9 +729,8 @@ namespace Napi { /// Sets an indexed property or array element. template - void Set( - uint32_t index, ///< Property / element index - const ValueType& value ///< Property value primitive + bool Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive ); /// Deletes an indexed property or array element. @@ -746,19 +741,20 @@ namespace Napi { Array GetPropertyNames() const; ///< Get all property names /// Defines a property on the object. - void DefineProperty( - const PropertyDescriptor& property ///< Descriptor for the property to be defined + bool DefineProperty( + const PropertyDescriptor& + property ///< Descriptor for the property to be defined ); /// Defines properties on the object. - void DefineProperties( - const std::initializer_list& properties + bool DefineProperties( + const std::initializer_list& properties ///< List of descriptors for the properties to be defined ); /// Defines properties on the object. - void DefineProperties( - const std::vector& properties + bool DefineProperties( + const std::vector& properties ///< Vector of descriptors for the properties to be defined ); @@ -1261,26 +1257,26 @@ namespace Napi { Napi::Value Get(const char* utf8name) const; Napi::Value Get(const std::string& utf8name) const; - void Set(const char* utf8name, napi_value value); - void Set(const char* utf8name, Napi::Value value); - void Set(const char* utf8name, const char* utf8value); - void Set(const char* utf8name, bool boolValue); - void Set(const char* utf8name, double numberValue); - void Set(const std::string& utf8name, napi_value value); - void Set(const std::string& utf8name, Napi::Value value); - void Set(const std::string& utf8name, std::string& utf8value); - void Set(const std::string& utf8name, bool boolValue); - void Set(const std::string& utf8name, double numberValue); + bool Set(const char* utf8name, napi_value value); + bool Set(const char* utf8name, Napi::Value value); + bool Set(const char* utf8name, const char* utf8value); + bool Set(const char* utf8name, bool boolValue); + bool Set(const char* utf8name, double numberValue); + bool Set(const std::string& utf8name, napi_value value); + bool Set(const std::string& utf8name, Napi::Value value); + bool Set(const std::string& utf8name, std::string& utf8value); + bool Set(const std::string& utf8name, bool boolValue); + bool Set(const std::string& utf8name, double numberValue); Napi::Value Get(uint32_t index) const; - void Set(uint32_t index, const napi_value value); - void Set(uint32_t index, const Napi::Value value); - void Set(uint32_t index, const char* utf8value); - void Set(uint32_t index, const std::string& utf8value); - void Set(uint32_t index, bool boolValue); - void Set(uint32_t index, double numberValue); + bool Set(uint32_t index, const napi_value value); + bool Set(uint32_t index, const Napi::Value value); + bool Set(uint32_t index, const char* utf8value); + bool Set(uint32_t index, const std::string& utf8value); + bool Set(uint32_t index, bool boolValue); + bool Set(uint32_t index, double numberValue); - protected: + protected: ObjectReference(const ObjectReference&); }; diff --git a/test/object/object.cc b/test/object/object.cc index 3ee454e77..58c0392d8 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -10,10 +10,10 @@ Value GetPropertyWithCStyleString(const CallbackInfo& info); Value GetPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Set() -void SetPropertyWithNapiValue(const CallbackInfo& info); -void SetPropertyWithNapiWrapperValue(const CallbackInfo& info); -void SetPropertyWithCStyleString(const CallbackInfo& info); -void SetPropertyWithCppStyleString(const CallbackInfo& info); +Value SetPropertyWithNapiValue(const CallbackInfo& info); +Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info); +Value SetPropertyWithCStyleString(const CallbackInfo& info); +Value SetPropertyWithCppStyleString(const CallbackInfo& info); // Native wrappers for testing Object::Delete() Value DeletePropertyWithUint32(const CallbackInfo& info); diff --git a/test/object/set_property.cc b/test/object/set_property.cc index 19ab245b4..c17366544 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -2,30 +2,30 @@ using namespace Napi; -void SetPropertyWithNapiValue(const CallbackInfo& info) { +Value SetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - obj.Set(static_cast(key), value); + return Boolean::New(info.Env(), obj.Set(static_cast(key), value)); } -void SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { +Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - obj.Set(key, value); + return Boolean::New(info.Env(), obj.Set(key, value)); } -void SetPropertyWithCStyleString(const CallbackInfo& info) { +Value SetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - obj.Set(jsKey.Utf8Value().c_str(), value); + return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value().c_str(), value)); } -void SetPropertyWithCppStyleString(const CallbackInfo& info) { +Value SetPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - obj.Set(jsKey.Utf8Value(), value); + return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value(), value)); } diff --git a/test/object/set_property.js b/test/object/set_property.js index 772538b26..86fab3b75 100644 --- a/test/object/set_property.js +++ b/test/object/set_property.js @@ -7,7 +7,7 @@ module.exports = require('../common').runTest(test); function test(binding) { function testSetProperty(nativeSetProperty) { const obj = {}; - nativeSetProperty(obj, 'test', 1); + assert.strictEqual(nativeSetProperty(obj, 'test', 1), true); assert.strictEqual(obj.test, 1); } From 5920f5798e0685a746794b2528ef5f8bc058f032 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 7 May 2021 11:35:43 +0800 Subject: [PATCH 25/57] src: return bool on object freeze and seal (#991) These operations can call into JavaScript by using Proxy handlers, hence JavaScript exceptions might pending at the end of the operations. --- napi-inl.h | 10 ++++++---- napi.h | 4 ++-- test/common/index.js | 4 ++-- test/object/object_freeze_seal.cc | 8 ++++---- test/object/object_freeze_seal.js | 28 ++++++++++++++++++++++++++-- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index be20dc085..7e9dd726a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1389,14 +1389,16 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } #if NAPI_VERSION >= 8 -inline void Object::Freeze() { +inline bool Object::Freeze() { napi_status status = napi_object_freeze(_env, _value); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } -inline void Object::Seal() { +inline bool Object::Seal() { napi_status status = napi_object_seal(_env, _value); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_THROW_IF_FAILED(_env, status, false); + return true; } #endif // NAPI_VERSION >= 8 diff --git a/napi.h b/napi.h index 8be2ca3f7..7dc9b17df 100644 --- a/napi.h +++ b/napi.h @@ -773,8 +773,8 @@ namespace Napi { T* data, Hint* finalizeHint); #if NAPI_VERSION >= 8 - void Freeze(); - void Seal(); + bool Freeze(); + bool Seal(); #endif // NAPI_VERSION >= 8 }; diff --git a/test/common/index.js b/test/common/index.js index 65bdc10b7..ab6d12bc8 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -76,7 +76,7 @@ exports.mustNotCall = function(msg) { }; exports.runTest = async function(test, buildType) { - buildType = buildType || process.config.target_defaults.default_configuration; + buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ `../build/${buildType}/binding.node`, @@ -90,7 +90,7 @@ exports.runTest = async function(test, buildType) { } exports.runTestWithBindingPath = async function(test, buildType) { - buildType = buildType || process.config.target_defaults.default_configuration; + buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ `../build/${buildType}/binding.node`, diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc index 2d6f4b616..51071a1c6 100644 --- a/test/object/object_freeze_seal.cc +++ b/test/object/object_freeze_seal.cc @@ -4,14 +4,14 @@ using namespace Napi; -void Freeze(const CallbackInfo& info) { +Value Freeze(const CallbackInfo& info) { Object obj = info[0].As(); - obj.Freeze(); + return Boolean::New(info.Env(), obj.Freeze()); } -void Seal(const CallbackInfo& info) { +Value Seal(const CallbackInfo& info) { Object obj = info[0].As(); - obj.Seal(); + return Boolean::New(info.Env(), obj.Seal()); } Object InitObjectFreezeSeal(Env env) { diff --git a/test/object/object_freeze_seal.js b/test/object/object_freeze_seal.js index b6434b6e3..62c1c48f3 100644 --- a/test/object/object_freeze_seal.js +++ b/test/object/object_freeze_seal.js @@ -7,7 +7,7 @@ module.exports = require('../common').runTest(test); function test(binding) { { const obj = { x: 'a', y: 'b', z: 'c' }; - binding.object_freeze_seal.freeze(obj); + assert.strictEqual(binding.object_freeze_seal.freeze(obj), true); assert.strictEqual(Object.isFrozen(obj), true); assert.throws(() => { obj.x = 10; @@ -20,9 +20,21 @@ function test(binding) { }, /Cannot delete property 'x' of #/); } + { + const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { + preventExtensions() { + throw new Error('foo'); + }, + }); + + assert.throws(() => { + binding.object_freeze_seal.freeze(obj); + }, /foo/); + } + { const obj = { x: 'a', y: 'b', z: 'c' }; - binding.object_freeze_seal.seal(obj); + assert.strictEqual(binding.object_freeze_seal.seal(obj), true); assert.strictEqual(Object.isSealed(obj), true); assert.throws(() => { obj.w = 'd'; @@ -34,4 +46,16 @@ function test(binding) { // so this should not throw. obj.x = 'd'; } + + { + const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { + preventExtensions() { + throw new Error('foo'); + }, + }); + + assert.throws(() => { + binding.object_freeze_seal.seal(obj); + }, /foo/); + } } From a1c102abfb86664f2e2f571cf5a07e4ec2b8be56 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 11 May 2021 00:21:38 +0800 Subject: [PATCH 26/57] test: fix undoc assumptions about the timing of tsfn calls PR-URL: https://github.com/nodejs/node-addon-api/pull/995 Reviewed-By: Michael Dawson --- test/threadsafe_function/threadsafe_function.js | 8 +++----- .../typed_threadsafe_function.js | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/test/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 7da0e3df6..5236f28ea 100644 --- a/test/threadsafe_function/threadsafe_function.js +++ b/test/threadsafe_function/threadsafe_function.js @@ -25,11 +25,9 @@ async function test(binding) { binding.threadsafe_function[threadStarter](function testCallback(value) { array.push(value); if (array.length === quitAfter) { - setImmediate(() => { - binding.threadsafe_function.stopThread(common.mustCall(() => { - resolve(array); - }), !!abort); - }); + binding.threadsafe_function.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); } }, !!abort, !!launchSecondary, maxQueueSize); if (threadStarter === 'startThreadNonblocking') { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.js b/test/typed_threadsafe_function/typed_threadsafe_function.js index 5d4a52a79..51bbf2f73 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function.js @@ -25,11 +25,9 @@ async function test(binding) { binding.typed_threadsafe_function[threadStarter](function testCallback(value) { array.push(value); if (array.length === quitAfter) { - setImmediate(() => { - binding.typed_threadsafe_function.stopThread(common.mustCall(() => { - resolve(array); - }), !!abort); - }); + binding.typed_threadsafe_function.stopThread(common.mustCall(() => { + resolve(array); + }), !!abort); } }, !!abort, !!launchSecondary, maxQueueSize); if (threadStarter === 'startThreadNonblocking') { From 55af990471055d01934040711a0c209f1ac894c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Fri, 14 May 2021 07:08:49 +0200 Subject: [PATCH 27/57] doc: fix typo in code example (#997) --- doc/async_worker_variants.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 54007762c..4f1762177 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -375,7 +375,7 @@ const exampleCallback = (errorResponse, okResponse, progressData) => { // ... }; -// Call our native addon with the paramters of a string and a function +// Call our native addon with the parameters of a string and a function nativeAddon.echo("example", exampleCallback); ``` @@ -550,7 +550,7 @@ const onProgressCallback = (num) => { // ... }; -// Call our native addon with the paramters of a string and three callback functions +// Call our native addon with the parameters of a string and three callback functions nativeAddon.echo("example", onErrorCallback, onOkCallback, onProgressCallback); ``` From 9f0605d191e19fd2abe885e8014961399f537980 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 14 May 2021 23:21:19 +0800 Subject: [PATCH 28/57] docs: add napi-rs iin Other Bindings section (#999) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 78ffe36ea..0bb664fc5 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,12 @@ such packages with `node-addon-api` to provide more visibility to the community. Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). + + +### **Other bindings** + +- **[napi-rs](https://napi.rs)** - (`Rust`) + ### **Badges** From 8558e96b05108cdad4bd5078ce9d6c5d877ba227 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Mon, 17 May 2021 16:40:40 +0200 Subject: [PATCH 29/57] Prepare release 3.2.0. --- CHANGELOG.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +-- package.json | 18 ++++++++++- 3 files changed, 103 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e77f4109e..58c679541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,89 @@ # node-addon-api Changelog +## 2021-05-17 Version 3.2.0, @NickNaso + +### Notable changes: + +#### API + +- Remove unnecessary symbol exposure. +- Fixed leak in `Napi::ObjectWrap` instance for getter and setter method. +- Added `Napi::Object::Freeze` and `Napi::object::Seal` methods. +- `Napi::Reference` is now copyable. + +#### Documentation + +- Added docuemtnation for `Napi::Object::PropertyLValue`. +- Changed all N-API references to Node-API. +- Some minor corrections all over the documentation. + +#### TEST + +- Added tests relating to fetch property from Global Object. +- Added addtiona tests for `Napi::Object`. +- Added test for `Napi::Function` contructors. +- Fixed intermittent failure for `Napi::ThreadSafeFunction` test. +- Some minor corrections all over the test suite. + +### TOOL + +- Added Node.js v16.x to CI. +- Added CI configuration for Windows. +- Some fixex on linter command. + +### Commits + +* [[`52721312f6`](https://github.com/nodejs/node-addon-api/commit/52721312f6)] - **docs**: add napi-rs iin Other Bindings section (#999) (LongYinan) +* [[`78a6570a42`](https://github.com/nodejs/node-addon-api/commit/78a6570a42)] - **doc**: fix typo in code example (#997) (Tobias Nießen) +* [[`da3bd5778f`](https://github.com/nodejs/node-addon-api/commit/da3bd5778f)] - **test**: fix undoc assumptions about the timing of tsfn calls (legendecas) [#995](https://github.com/nodejs/node-addon-api/pull/995) +* [[`410cf6a81e`](https://github.com/nodejs/node-addon-api/commit/410cf6a81e)] - **src**: return bool on object freeze and seal (#991) (legendecas) +* [[`93f1898312`](https://github.com/nodejs/node-addon-api/commit/93f1898312)] - **src**: return bool on object set and define property (#977) (legendecas) +* [[`331c2ee274`](https://github.com/nodejs/node-addon-api/commit/331c2ee274)] - **build**: add Node.js v16.x to CI (#983) (legendecas) +* [[`b6f5eb15e6`](https://github.com/nodejs/node-addon-api/commit/b6f5eb15e6)] - **test**: run test suites with helpers (legendecas) [#976](https://github.com/nodejs/node-addon-api/pull/976) +* [[`fbcdf00ea0`](https://github.com/nodejs/node-addon-api/commit/fbcdf00ea0)] - **test**: rename misspelled parameters (Tobias Nießen) [#973](https://github.com/nodejs/node-addon-api/pull/973) +* [[`63a6c32e80`](https://github.com/nodejs/node-addon-api/commit/63a6c32e80)] - **test**: fix intermittent TSFN crashes (Kevin Eady) [#974](https://github.com/nodejs/node-addon-api/pull/974) +* [[`8f120b033f`](https://github.com/nodejs/node-addon-api/commit/8f120b033f)] - **fix**: key for wapping drawing's system condition (#970) (Kévin VOYER) +* [[`1c9d528d66`](https://github.com/nodejs/node-addon-api/commit/1c9d528d66)] - **doc**: correct struct definition (#969) (Darshan Sen) +* [[`5e64d1fa61`](https://github.com/nodejs/node-addon-api/commit/5e64d1fa61)] - Added badges for Node-API v7 and v8. (#954) (Nicola Del Gobbo) +* [[`6ce629b3fa`](https://github.com/nodejs/node-addon-api/commit/6ce629b3fa)] - **src**: add pull request template (#967) (Michael Dawson) +* [[`98126661af`](https://github.com/nodejs/node-addon-api/commit/98126661af)] - Update CONTRIBUTING.md (#966) (Michael Dawson) +* [[`77350eee98`](https://github.com/nodejs/node-addon-api/commit/77350eee98)] - **src**: added Freeze and Seal method to Object class. (NickNaso) [#955](https://github.com/nodejs/node-addon-api/pull/955) +* [[`bc5147cc4a`](https://github.com/nodejs/node-addon-api/commit/bc5147cc4a)] - Finished tests relating to fetch property from Global Object (JckXia) +* [[`0127813111`](https://github.com/nodejs/node-addon-api/commit/0127813111)] - **doc**: unambiguously mark deprecated signatures (Tobias Nießen) [#942](https://github.com/nodejs/node-addon-api/pull/942) +* [[`787e216105`](https://github.com/nodejs/node-addon-api/commit/787e216105)] - **doc**: rename N-API with Node-API (Darshan Sen) [#951](https://github.com/nodejs/node-addon-api/pull/951) +* [[`628023689a`](https://github.com/nodejs/node-addon-api/commit/628023689a)] - **src**: rename N-API with Node-API on comments (NickNaso) [#953](https://github.com/nodejs/node-addon-api/pull/953) +* [[`5c6391578f`](https://github.com/nodejs/node-addon-api/commit/5c6391578f)] - **build**: add CI configuration for Windows (NickNaso) [#948](https://github.com/nodejs/node-addon-api/pull/948) +* [[`8ef07251ec`](https://github.com/nodejs/node-addon-api/commit/8ef07251ec)] - **doc**: added some warnings for buffer and array buffer factory method. (#929) (Nicola Del Gobbo) +* [[`6490b1f730`](https://github.com/nodejs/node-addon-api/commit/6490b1f730)] - **doc**: sync Object::Set value arg with Value::From (#933) (Tobias Nießen) +* [[`7319a0d7a2`](https://github.com/nodejs/node-addon-api/commit/7319a0d7a2)] - Fix tab indent (#938) (Tobias Nießen) +* [[`1916cb937e`](https://github.com/nodejs/node-addon-api/commit/1916cb937e)] - **chore**: fixup linter commands (#940) (legendecas) +* [[`fc4585fa23`](https://github.com/nodejs/node-addon-api/commit/fc4585fa23)] - **test**: dd tests for Function constructors (JoseExposito) [#937](https://github.com/nodejs/node-addon-api/pull/937) +* [[`87b7aae469`](https://github.com/nodejs/node-addon-api/commit/87b7aae469)] - **doc**: warn about SuppressDestruct() (#926) (Anna Henningsen) +* [[`71494a49a3`](https://github.com/nodejs/node-addon-api/commit/71494a49a3)] - **src,doc**: refactor to replace typedefs with usings (Darshan Sen) [#910](https://github.com/nodejs/node-addon-api/pull/910) +* [[`298ff8d9d2`](https://github.com/nodejs/node-addon-api/commit/298ff8d9d2)] - **test**: add additional tests for Object (JoseExposito) [#923](https://github.com/nodejs/node-addon-api/pull/923) +* [[`8a1147b430`](https://github.com/nodejs/node-addon-api/commit/8a1147b430)] - **revert**: src: add additional tests for Function (Michael Dawson) +* [[`bb56ffaa6f`](https://github.com/nodejs/node-addon-api/commit/bb56ffaa6f)] - **doc**: fix documentation for object api (Nicola Del Gobbo) [#931](https://github.com/nodejs/node-addon-api/pull/931) +* [[`3b8bddab49`](https://github.com/nodejs/node-addon-api/commit/3b8bddab49)] - **src**: add additional tests for Function (José Expósito) [#928](https://github.com/nodejs/node-addon-api/pull/928) +* [[`74ab50c775`](https://github.com/nodejs/node-addon-api/commit/74ab50c775)] - **src**: allow references to be copyable in APIs (legendecas) [#915](https://github.com/nodejs/node-addon-api/pull/915) +* [[`929709d0fe`](https://github.com/nodejs/node-addon-api/commit/929709d0fe)] - **doc**: add propertylvalue.md (#925) (Gabriel Schulhof) +* [[`69d0d98be4`](https://github.com/nodejs/node-addon-api/commit/69d0d98be4)] - fixup (Anna Henningsen) +* [[`46e41d961b`](https://github.com/nodejs/node-addon-api/commit/46e41d961b)] - fixup (Anna Henningsen) +* [[`1af1642fb7`](https://github.com/nodejs/node-addon-api/commit/1af1642fb7)] - **doc**: warn about SuppressDestruct() (Anna Henningsen) +* [[`12c548b2ff`](https://github.com/nodejs/node-addon-api/commit/12c548b2ff)] - **tools**: fix error detection (#914) (Darshan Sen) +* [[`458d895d5b`](https://github.com/nodejs/node-addon-api/commit/458d895d5b)] - **packaging**: list files to be published to npm (Lovell Fuller) [#889](https://github.com/nodejs/node-addon-api/pull/889) +* [[`f7ed2490d4`](https://github.com/nodejs/node-addon-api/commit/f7ed2490d4)] - **test**: remove outdated V8 flag (Darshan Sen) [#895](https://github.com/nodejs/node-addon-api/pull/895) +* [[`a575a6ec60`](https://github.com/nodejs/node-addon-api/commit/a575a6ec60)] - **src**: fix leak in ObjectWrap instance set/getters (Kevin Eady) [#899](https://github.com/nodejs/node-addon-api/pull/899) +* [[`b6e844e0b0`](https://github.com/nodejs/node-addon-api/commit/b6e844e0b0)] - **doc**: fix spelling of "targeted" and "targeting" (#904) (Tobias Nießen) +* [[`4d856f6e91`](https://github.com/nodejs/node-addon-api/commit/4d856f6e91)] - **src**: remove unnecessary symbol exposure (Gabriel Schulhof) [#896](https://github.com/nodejs/node-addon-api/pull/896) +* [[`f35bb7d0d7`](https://github.com/nodejs/node-addon-api/commit/f35bb7d0d7)] - **doc**: Update GitHub URL references from 'master' to 'HEAD' (#898) (Jim Schlight) +* [[`286ae215d1`](https://github.com/nodejs/node-addon-api/commit/286ae215d1)] - Add warning about branch rename (Michael Dawson) +* [[`a4a7b28288`](https://github.com/nodejs/node-addon-api/commit/a4a7b28288)] - Update branch references from master to main (#886) (Jim Schlight) +* [[`a2ad0a107a`](https://github.com/nodejs/node-addon-api/commit/a2ad0a107a)] - **docs**: add NAN to N-API resource link (#880) (kidneysolo) +* [[`1c040eeb63`](https://github.com/nodejs/node-addon-api/commit/1c040eeb63)] - **test**: load testModules automatically (raisinten) [#876](https://github.com/nodejs/node-addon-api/pull/876) +* [[`bf478e4496`](https://github.com/nodejs/node-addon-api/commit/bf478e4496)] - **src**: use NAPI\_NOEXCEPT macro instead of noexcept (NickNaso) [#864](https://github.com/nodejs/node-addon-api/pull/864) +* [[`744705f2eb`](https://github.com/nodejs/node-addon-api/commit/744705f2eb)] - **test**: refactor remove repeated execution index.js (raisinten) [#839](https://github.com/nodejs/node-addon-api/pull/839) +* [[`db62e3c811`](https://github.com/nodejs/node-addon-api/commit/db62e3c811)] - Update team members (Michael Dawson) + ## 2020-12-17 Version 3.1.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 0bb664fc5..5e179be30 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 3.1.0** +## **Current version: 3.2.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -83,7 +83,7 @@ This allows addons built with it to run with Node.js versions which support the **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 10.x. +The oldest Node.js version supported by the current version of node-addon-api is Node.js 12.x. ## Setup - [Installation and usage](doc/setup.md) diff --git a/package.json b/package.json index d062cb0c9..28537b9bd 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,10 @@ "name": "ikokostya", "url": "https://github.com/ikokostya" }, + { + "name": "Jack Xia", + "url": "https://github.com/JckXia" + }, { "name": "Jake Barnes", "url": "https://github.com/DuBistKomisch" @@ -139,6 +143,10 @@ "name": "Jinho Bang", "url": "https://github.com/romandev" }, + { + "name": "José Expósito", + "url": "https://github.com/JoseExposito" + }, { "name": "joshgarde", "url": "https://github.com/joshgarde" @@ -155,6 +163,10 @@ "name": "Kevin Eady", "url": "https://github.com/KevinEady" }, + { + "name": "Kévin VOYER", + "url": "https://github.com/kecsou" + }, { "name": "kidneysolo", "url": "https://github.com/kidneysolo" @@ -175,6 +187,10 @@ "name": "legendecas", "url": "https://github.com/legendecas" }, + { + "name": "LongYinan", + "url": "https://github.com/Brooooooklyn" + }, { "name": "Lovell Fuller", "url": "https://github.com/lovell" @@ -347,6 +363,6 @@ "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "3.1.0", + "version": "3.2.0", "support": true } From 370f55a2d8606fc9fd6e463527613906298cc3f6 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 28 May 2021 16:32:45 +0200 Subject: [PATCH 30/57] Fixed readme for new release. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e179be30..46f0ee5e6 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 3.2.0** +## **Current version: 3.2.1** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -83,7 +83,7 @@ This allows addons built with it to run with Node.js versions which support the **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 12.x. +The oldest Node.js version supported by the current version of node-addon-api is Node.js 10.x. ## Setup - [Installation and usage](doc/setup.md) From 02f9ea1a0e0b5dbffef668dc4360054ee92c22f0 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 28 May 2021 16:37:21 +0200 Subject: [PATCH 31/57] Prepare release 3.2.1. --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c679541..12b04f2e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # node-addon-api Changelog +## 2021-05-28 Version 3.2.1, @NickNaso + +### Notable changes: + +#### Documentation + +- Fixed documentation about the oldest Node.js version supported. + +### Commits + +* [[`6d41ee5a3a`](https://github.com/nodejs/node-addon-api/commit/6d41ee5a3a)] - Fixed readme for new release. (NickNaso) + ## 2021-05-17 Version 3.2.0, @NickNaso ### Notable changes: From c2ec24be49d0e01182e8492218e7aee38f8d8949 Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 28 May 2021 20:09:02 +0200 Subject: [PATCH 32/57] Update version on package.json. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 28537b9bd..6c8da4f51 100644 --- a/package.json +++ b/package.json @@ -363,6 +363,6 @@ "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "3.2.0", + "version": "3.2.1", "support": true } From 83e18ae9a19f11de7df543d30724acd6c02ef567 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 25 May 2021 00:01:22 +0800 Subject: [PATCH 33/57] test: function reference call & construct PR-URL: https://github.com/nodejs/node-addon-api/pull/1005 Reviewed-By: Michael Dawson --- test/binding.cc | 2 ++ test/binding.gyp | 1 + test/functionreference.cc | 30 ++++++++++++++++++++++++++++++ test/functionreference.js | 20 ++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 test/functionreference.cc create mode 100644 test/functionreference.js diff --git a/test/binding.cc b/test/binding.cc index 92f6c77df..f450c71d2 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -33,6 +33,7 @@ Object InitDataViewReadWrite(Env env); Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); +Object InitFunctionReference(Env env); Object InitHandleScope(Env env); Object InitMovableCallbacks(Env env); Object InitMemoryManagement(Env env); @@ -105,6 +106,7 @@ Object Init(Env env, Object exports) { exports.Set("error", InitError(env)); exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); + exports.Set("functionreference", InitFunctionReference(env)); exports.Set("name", InitName(env)); exports.Set("handlescope", InitHandleScope(env)); exports.Set("movable_callbacks", InitMovableCallbacks(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 8ecf91b83..7e6864535 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -24,6 +24,7 @@ 'error.cc', 'external.cc', 'function.cc', + 'functionreference.cc', 'handlescope.cc', 'movable_callbacks.cc', 'memory_management.cc', diff --git a/test/functionreference.cc b/test/functionreference.cc new file mode 100644 index 000000000..44dec3ce7 --- /dev/null +++ b/test/functionreference.cc @@ -0,0 +1,30 @@ +#include "napi.h" + +using namespace Napi; + +namespace { +Value Call(const CallbackInfo& info) { + HandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return ref.Call({}); +} + +Value Construct(const CallbackInfo& info) { + HandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return ref.New({}); +} +} // namespace + +Object InitFunctionReference(Env env) { + Object exports = Object::New(env); + + exports["call"] = Function::New(env, Call); + exports["construct"] = Function::New(env, Construct); + + return exports; +} diff --git a/test/functionreference.js b/test/functionreference.js new file mode 100644 index 000000000..3266f0031 --- /dev/null +++ b/test/functionreference.js @@ -0,0 +1,20 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(binding => { + test(binding.functionreference); +}); + +function test(binding) { + const e = new Error('foobar'); + const functionMayThrow = () => { throw e; }; + const classMayThrow = class { constructor() { throw e; } }; + + assert.throws(() => { + binding.call(functionMayThrow); + }, /foobar/); + assert.throws(() => { + binding.construct(classMayThrow); + }, /foobar/); +} From c1734a96a310de04fbda6e6356a9ce91d43001b7 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 11 Jun 2021 17:25:34 -0400 Subject: [PATCH 34/57] src: fix gcc-11 c++20 compilation Fixes: https://github.com/nodejs/node-addon-api/issues/1007 PR-URL: https://github.com/nodejs/node-addon-api/pull/1009 Reviewed-By: Michael Dawson --- napi.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/napi.h b/napi.h index 7dc9b17df..8e9f5214a 100644 --- a/napi.h +++ b/napi.h @@ -2474,9 +2474,8 @@ namespace Napi { Finalizer finalizeCallback, FinalizerDataType* data = nullptr); - TypedThreadSafeFunction(); - TypedThreadSafeFunction( - napi_threadsafe_function tsFunctionValue); + TypedThreadSafeFunction(); + TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); operator napi_threadsafe_function() const; From 4479eacebb9070209df6e274ebaa575c836614ec Mon Sep 17 00:00:00 2001 From: rudolftam <78262801+rudolftam@users.noreply.github.com> Date: Sat, 6 Feb 2021 11:42:26 +0200 Subject: [PATCH 35/57] src: fix Error::ThrowAsJavaScriptException crash When terminating an environment (e.g., by calling worker.terminate), napi_throw, which is called by Error::ThrowAsJavaScriptException, returns napi_pending_exception, which is then incorrectly treated as a fatal error resulting in a crash. PR-URL: https://github.com/nodejs/node-addon-api/pull/975 Reviewed-By: Nicola Del Gobbo Reviewed-By: Gabriel Schulhof Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- doc/setup.md | 7 ++ napi-inl.h | 28 +++++++- test/binding-swallowexcept.cc | 12 ++++ test/binding.gyp | 30 +++++++-- test/error.cc | 62 ++++++++++++++++++ test/error_terminating_environment.js | 94 +++++++++++++++++++++++++++ test/index.js | 1 + 7 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 test/binding-swallowexcept.cc create mode 100644 test/error_terminating_environment.js diff --git a/doc/setup.md b/doc/setup.md index 513f4a3c8..aec397e61 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -81,3 +81,10 @@ targeted node version *does not* have Node-API built-in. The preprocessor directive `NODE_ADDON_API_DISABLE_DEPRECATED` can be defined at compile time before including `napi.h` to skip the definition of deprecated APIs. + +By default, throwing an exception on a terminating environment (eg. worker +threads) will cause a fatal exception, terminating the Node process. This is to +provide feedback to the user of the runtime error, as it is impossible to pass +the error to JavaScript when the environment is terminating. In order to bypass +this behavior such that the Node process will not terminate, define the +preprocessor directive `NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS`. diff --git a/napi-inl.h b/napi-inl.h index 7e9dd726a..dd563a4dd 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2397,12 +2397,38 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { - +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + bool pendingException = false; + + // check if there is already a pending exception. If so don't try to throw a + // new one as that is not allowed/possible + napi_status status = napi_is_exception_pending(_env, &pendingException); + + if ((status != napi_ok) || + ((status == napi_ok) && (pendingException == false))) { + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + status = napi_throw(_env, Value()); + + if (status == napi_pending_exception) { + // The environment must be terminating as we checked earlier and there + // was no pending exception. In this case continuing will result + // in a fatal error and there is nothing the author has done incorrectly + // in their code that is worth flagging through a fatal error + return; + } + } else { + status = napi_pending_exception; + } +#else // We intentionally don't use `NAPI_THROW_*` macros here to ensure // that there is no possible recursion as `ThrowAsJavaScriptException` // is part of `NAPI_THROW_*` macro definition for noexcept. napi_status status = napi_throw(_env, Value()); +#endif #ifdef NAPI_CPP_EXCEPTIONS if (status != napi_ok) { diff --git a/test/binding-swallowexcept.cc b/test/binding-swallowexcept.cc new file mode 100644 index 000000000..febc7db66 --- /dev/null +++ b/test/binding-swallowexcept.cc @@ -0,0 +1,12 @@ +#include "napi.h" + +using namespace Napi; + +Object InitError(Env env); + +Object Init(Env env, Object exports) { + exports.Set("error", InitError(env)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/test/binding.gyp b/test/binding.gyp index 7e6864535..562288c04 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,7 +1,8 @@ { 'target_defaults': { 'includes': ['../common.gypi'], - 'sources': [ + 'variables': { + 'build_sources': [ 'addon.cc', 'addon_data.cc', 'arraybuffer.cc', @@ -67,20 +68,39 @@ 'version_management.cc', 'thunking_manual.cc', ], + 'build_sources_swallowexcept': [ + 'binding-swallowexcept.cc', + 'error.cc', + ], 'conditions': [ ['disable_deprecated!="true"', { - 'sources': ['object/object_deprecated.cc'] + 'build_sources': ['object/object_deprecated.cc'] }] - ], + ] + }, }, 'targets': [ { 'target_name': 'binding', - 'includes': ['../except.gypi'] + 'includes': ['../except.gypi'], + 'sources': ['>@(build_sources)'] }, { 'target_name': 'binding_noexcept', - 'includes': ['../noexcept.gypi'] + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'] + }, + { + 'target_name': 'binding_swallowexcept', + 'includes': ['../except.gypi'], + 'sources': [ '>@(build_sources_swallowexcept)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'] + }, + { + 'target_name': 'binding_swallowexcept_noexcept', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources_swallowexcept)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'] }, ], } diff --git a/test/error.cc b/test/error.cc index 44f4f79eb..15858182e 100644 --- a/test/error.cc +++ b/test/error.cc @@ -1,9 +1,54 @@ +#include #include "napi.h" using namespace Napi; namespace { +std::promise promise_for_child_process_; +std::promise promise_for_worker_thread_; + +void ResetPromises(const CallbackInfo&) { + promise_for_child_process_ = std::promise(); + promise_for_worker_thread_ = std::promise(); +} + +void WaitForWorkerThread(const CallbackInfo&) { + std::future future = promise_for_worker_thread_.get_future(); + + std::future_status status = future.wait_for(std::chrono::seconds(5)); + + if (status != std::future_status::ready) { + Error::Fatal("WaitForWorkerThread", "status != std::future_status::ready"); + } +} + +void ReleaseAndWaitForChildProcess(const CallbackInfo& info, + const uint32_t index) { + if (info.Length() < index + 1) { + return; + } + + if (!info[index].As().Value()) { + return; + } + + promise_for_worker_thread_.set_value(); + + std::future future = promise_for_child_process_.get_future(); + + std::future_status status = future.wait_for(std::chrono::seconds(5)); + + if (status != std::future_status::ready) { + Error::Fatal("ReleaseAndWaitForChildProcess", + "status != std::future_status::ready"); + } +} + +void ReleaseWorkerThread(const CallbackInfo&) { + promise_for_child_process_.set_value(); +} + void DoNotCatch(const CallbackInfo& info) { Function thrower = info[0].As(); thrower({}); @@ -18,16 +63,22 @@ void ThrowApiError(const CallbackInfo& info) { void ThrowJSError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw Error::New(info.Env(), message); } void ThrowTypeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw TypeError::New(info.Env(), message); } void ThrowRangeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); throw RangeError::New(info.Env(), message); } @@ -83,16 +134,22 @@ void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { void ThrowJSError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); Error::New(info.Env(), message).ThrowAsJavaScriptException(); } void ThrowTypeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); TypeError::New(info.Env(), message).ThrowAsJavaScriptException(); } void ThrowRangeError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); + + ReleaseAndWaitForChildProcess(info, 1); RangeError::New(info.Env(), message).ThrowAsJavaScriptException(); } @@ -187,6 +244,8 @@ void ThrowDefaultError(const CallbackInfo& info) { Error::Fatal("ThrowDefaultError", "napi_get_named_property"); } + ReleaseAndWaitForChildProcess(info, 1); + // The macro creates a `Napi::Error` using the factory that takes only the // env, however, it heeds the exception mechanism to be used. NAPI_THROW_IF_FAILED_VOID(env, status); @@ -209,5 +268,8 @@ Object InitError(Env env) { Function::New(env, CatchAndRethrowErrorThatEscapesScope); exports["throwFatalError"] = Function::New(env, ThrowFatalError); exports["throwDefaultError"] = Function::New(env, ThrowDefaultError); + exports["resetPromises"] = Function::New(env, ResetPromises); + exports["waitForWorkerThread"] = Function::New(env, WaitForWorkerThread); + exports["releaseWorkerThread"] = Function::New(env, ReleaseWorkerThread); return exports; } diff --git a/test/error_terminating_environment.js b/test/error_terminating_environment.js new file mode 100644 index 000000000..63b42259e --- /dev/null +++ b/test/error_terminating_environment.js @@ -0,0 +1,94 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +// These tests ensure that Error types can be used in a terminating +// environment without triggering any fatal errors. + +if (process.argv[2] === 'runInChildProcess') { + const binding_path = process.argv[3]; + const index_for_test_case = Number(process.argv[4]); + + const binding = require(binding_path); + + // Use C++ promises to ensure the worker thread is terminated right + // before running the testable code in the binding. + + binding.error.resetPromises() + + const { Worker } = require('worker_threads'); + + const worker = new Worker( + __filename, + { + argv: [ + 'runInWorkerThread', + binding_path, + index_for_test_case, + ] + } + ); + + binding.error.waitForWorkerThread() + + worker.terminate(); + + binding.error.releaseWorkerThread() + + return; +} + +if (process.argv[2] === 'runInWorkerThread') { + const binding_path = process.argv[3]; + const index_for_test_case = Number(process.argv[4]); + + const binding = require(binding_path); + + switch (index_for_test_case) { + case 0: + binding.error.throwJSError('test', true); + break; + case 1: + binding.error.throwTypeError('test', true); + break; + case 2: + binding.error.throwRangeError('test', true); + break; + case 3: + binding.error.throwDefaultError(false, true); + break; + case 4: + binding.error.throwDefaultError(true, true); + break; + default: assert.fail('Invalid index'); + } + + assert.fail('This should not be reachable'); +} + +test(`./build/${buildType}/binding.node`, true); +test(`./build/${buildType}/binding_noexcept.node`, true); +test(`./build/${buildType}/binding_swallowexcept.node`, false); +test(`./build/${buildType}/binding_swallowexcept_noexcept.node`, false); + +function test(bindingPath, process_should_abort) { + const number_of_test_cases = 5; + + for (let i = 0; i < number_of_test_cases; ++i) { + const child_process = require('./napi_child').spawnSync( + process.execPath, + [ + __filename, + 'runInChildProcess', + bindingPath, + i, + ] + ); + + if (process_should_abort) { + assert(child_process.status !== 0, `Test case ${bindingPath} ${i} failed: Process exited with status code 0.`); + } else { + assert(child_process.status === 0, `Test case ${bindingPath} ${i} failed: Process status ${child_process.status} is non-zero`); + } + } +} diff --git a/test/index.js b/test/index.js index 1b03f76cf..69d13bbc0 100644 --- a/test/index.js +++ b/test/index.js @@ -110,6 +110,7 @@ if (napiVersion < 6) { if (majorNodeVersion < 12) { testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); + testModules.splice(testModules.indexOf('error_terminating_environment'), 1); } if (napiVersion < 8) { From 5d4d3583350a4a49fc7d0b7c67c4c732fb474d3d Mon Sep 17 00:00:00 2001 From: NickNaso Date: Tue, 15 Jun 2021 13:15:35 +0200 Subject: [PATCH 36/57] Prepare release 4.0.0. --- CHANGELOG.md | 24 ++++++++++++++++++++++++ README.md | 4 ++-- package.json | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b04f2e0..3b1fde44f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # node-addon-api Changelog +## 2021-06-15 Version 4.0.0, @NickNaso + +### Notable changes: + +#### API + +- Fixed a crashing issue in `Napi::Error::ThrowAsJavaScriptException` +introducing the preprocessor directive `NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS`. +- Fixed compilation problem for GCC 11 and C++20. + +#### TEST + +- Added test for function reference call and contructor. + +### Documentation + +- Updated the oldest Node.js version supported from `10.x` to `12.x`. + +### Commits + +* [[`028107f686`](https://github.com/nodejs/node-addon-api/commit/028107f686)] - **src**: fix Error::ThrowAsJavaScriptException crash (rudolftam) [#975](https://github.com/nodejs/node-addon-api/pull/975) +* [[`fed13534c5`](https://github.com/nodejs/node-addon-api/commit/fed13534c5)] - **src**: fix gcc-11 c++20 compilation (Kevin Eady) [#1009](https://github.com/nodejs/node-addon-api/pull/1009) +* [[`b75afc4d29`](https://github.com/nodejs/node-addon-api/commit/b75afc4d29)] - **test**: function reference call & construct (legendecas) [#1005](https://github.com/nodejs/node-addon-api/pull/1005) + ## 2021-05-28 Version 3.2.1, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 46f0ee5e6..a1fabea39 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 3.2.1** +## **Current version: 4.0.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -83,7 +83,7 @@ This allows addons built with it to run with Node.js versions which support the **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 10.x. +The oldest Node.js version supported by the current version of node-addon-api is Node.js 12.x. ## Setup - [Installation and usage](doc/setup.md) diff --git a/package.json b/package.json index 6c8da4f51..ad61cc1dc 100644 --- a/package.json +++ b/package.json @@ -363,6 +363,6 @@ "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "3.2.1", + "version": "4.0.0", "support": true } From cc11d729c067066e46939700a879faf7fae2df0d Mon Sep 17 00:00:00 2001 From: legendecas Date: Mon, 24 May 2021 01:04:24 +0800 Subject: [PATCH 37/57] src: set default return value of Reference Ref/Unref to 0 PR-URL: https://github.com/nodejs/node-addon-api/pull/1004 Reviewed-By: Michael Dawson --- napi-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index dd563a4dd..1f6563fea 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2609,7 +2609,7 @@ template inline uint32_t Reference::Ref() { uint32_t result; napi_status status = napi_reference_ref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 1); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -2617,7 +2617,7 @@ template inline uint32_t Reference::Unref() { uint32_t result; napi_status status = napi_reference_unref(_env, _ref, &result); - NAPI_THROW_IF_FAILED(_env, status, 1); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } From 92d1af5ac77842107f5f300c6315f71168f8fc09 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Mon, 21 Jun 2021 23:47:57 +0200 Subject: [PATCH 38/57] doc: update examples for context sensitivity Fixes: https://github.com/nodejs/node-addon-api/issues/1011 PR-URL: https://github.com/nodejs/node-addon-api/pull/1013 Reviewed-By: Michael Dawson Reviewed-By: Chengzhong Wu --- doc/class_property_descriptor.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/class_property_descriptor.md b/doc/class_property_descriptor.md index a0ac0d38f..e5035c126 100644 --- a/doc/class_property_descriptor.md +++ b/doc/class_property_descriptor.md @@ -17,7 +17,6 @@ class Example : public Napi::ObjectWrap { Example(const Napi::CallbackInfo &info); private: - static Napi::FunctionReference constructor; double _value; Napi::Value GetValue(const Napi::CallbackInfo &info); void SetValue(const Napi::CallbackInfo &info, const Napi::Value &value); @@ -31,8 +30,9 @@ Napi::Object Example::Init(Napi::Env env, Napi::Object exports) { InstanceAccessor<&Example::GetValue>("readOnlyProp") }); - constructor = Napi::Persistent(func); - constructor.SuppressDestruct(); + Napi::FunctionReference *constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); exports.Set("Example", func); return exports; @@ -45,8 +45,6 @@ Example::Example(const Napi::CallbackInfo &info) : Napi::ObjectWrap(inf this->_value = value.DoubleValue(); } -Napi::FunctionReference Example::constructor; - Napi::Value Example::GetValue(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); return Napi::Number::New(env, this->_value); From 4f9b270c2a10726bc3a72d68fe5d6d20881b74ae Mon Sep 17 00:00:00 2001 From: JckXia Date: Sun, 27 Jun 2021 17:24:19 -0400 Subject: [PATCH 39/57] test: dd check for nullptr inside String init PR-URL: https://github.com/nodejs/node-addon-api/pull/1015 Reviewed-By: Michael Dawson Reviewed-By: Kevin Eady --- napi-inl.h | 5 +++++ test/name.cc | 7 +++++++ test/name.js | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/napi-inl.h b/napi-inl.h index 1f6563fea..93cef5958 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -900,6 +900,11 @@ inline String String::New(napi_env env, const std::u16string& val) { } inline String String::New(napi_env env, const char* val) { + if (val == nullptr) { + NAPI_THROW( + TypeError::New(env, "String::New received a nullpointer as a value"), + Napi::String()); + } napi_value value; napi_status status = napi_create_string_utf8(env, val, std::strlen(val), &value); NAPI_THROW_IF_FAILED(env, status, String()); diff --git a/test/name.cc b/test/name.cc index 3a296ec58..4e56141bc 100644 --- a/test/name.cc +++ b/test/name.cc @@ -82,11 +82,18 @@ Value CheckSymbol(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].Type() == napi_symbol); } +void AssertErrorThrownWhenPassedNullptr(const CallbackInfo& info) { + const char* nullStr = nullptr; + String::New(info.Env(), nullStr); +} + Object InitName(Env env) { Object exports = Object::New(env); exports["echoString"] = Function::New(env, EchoString); exports["createString"] = Function::New(env, CreateString); + exports["nullStringShouldThrow"] = + Function::New(env, AssertErrorThrownWhenPassedNullptr); exports["checkString"] = Function::New(env, CheckString); exports["createSymbol"] = Function::New(env, CreateSymbol); exports["checkSymbol"] = Function::New(env, CheckSymbol); diff --git a/test/name.js b/test/name.js index 457774690..4c74542f0 100644 --- a/test/name.js +++ b/test/name.js @@ -7,6 +7,11 @@ module.exports = require('./common').runTest(test); function test(binding) { const expected = '123456789'; + + assert.throws(binding.name.nullStringShouldThrow, { + name: 'TypeError', + message: 'String::New received a nullpointer as a value', + }); assert.ok(binding.name.checkString(expected, 'utf8')); assert.ok(binding.name.checkString(expected, 'utf16')); assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf8', 3)); From e937efb6a0efb018c0c55a7f892f1ab4f5b748d2 Mon Sep 17 00:00:00 2001 From: JckXia Date: Sat, 17 Apr 2021 22:06:39 -0400 Subject: [PATCH 40/57] test: add first set of symbol tests PR-URL: https://github.com/nodejs/node-addon-api/pull/972 Reviewed-By: Michael Dawson Reviewed-By: Nicola Del Gobbo --- doc/symbol.md | 17 +++++++++-- napi-inl.h | 21 +++++++++++++ napi.h | 12 ++++++++ test/binding.cc | 2 ++ test/binding.gyp | 1 + test/symbol.cc | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ test/symbol.js | 70 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 test/symbol.cc create mode 100644 test/symbol.js diff --git a/doc/symbol.md b/doc/symbol.md index 72ac4c948..e2332674d 100644 --- a/doc/symbol.md +++ b/doc/symbol.md @@ -34,7 +34,7 @@ If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not being used, callers should check the result of `Napi::Env::IsExceptionPending` before attempting to use the returned value. -### Utf8Value +### WellKnown ```cpp static Napi::Symbol Napi::Symbol::WellKnown(napi_env env, const std::string& name); ``` @@ -45,4 +45,17 @@ static Napi::Symbol Napi::Symbol::WellKnown(napi_env env, const std::string& nam Returns a `Napi::Symbol` representing a well-known `Symbol` from the `Symbol` registry. -[`Napi::Name`]: ./name.md +### For +```cpp +static Napi::Symbol Napi::Symbol::For(napi_env env, const std::string& description); +static Napi::Symbol Napi::Symbol::For(napi_env env, const char* description); +static Napi::Symbol Napi::Symbol::For(napi_env env, String description); +static Napi::Symbol Napi::Symbol::For(napi_env env, napi_value description); +``` + +- `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. +- `[in] description`: The C++ string representing the `Napi::Symbol` in the global registry to retrieve. + +Searches in the global registry for existing symbol with the given name. If the symbol already exist it will be returned, otherwise a new symbol will be created in the registry. It's equivalent to Symbol.for() called from JavaScript. + +[`Napi::Name`]: ./name.md \ No newline at end of file diff --git a/napi-inl.h b/napi-inl.h index 93cef5958..663f229be 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1003,6 +1003,27 @@ inline Symbol Symbol::WellKnown(napi_env env, const std::string& name) { return Napi::Env(env).Global().Get("Symbol").As().Get(name).As(); } +inline Symbol Symbol::For(napi_env env, const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline Symbol Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline Symbol Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +inline Symbol Symbol::For(napi_env env, napi_value description) { + Object symbObject = Napi::Env(env).Global().Get("Symbol").As(); + auto forSymb = + symbObject.Get("for").As().Call(symbObject, {description}); + return forSymb.As(); +} + inline Symbol::Symbol() : Name() { } diff --git a/napi.h b/napi.h index 8e9f5214a..c3e02cb88 100644 --- a/napi.h +++ b/napi.h @@ -538,6 +538,18 @@ namespace Napi { /// Get a public Symbol (e.g. Symbol.iterator). static Symbol WellKnown(napi_env, const std::string& name); + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static Symbol For(napi_env env, const std::string& description); + + // Create a symbol in the global registry, C style string (null terminated) + static Symbol For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static Symbol For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static Symbol For(napi_env env, napi_value description); + Symbol(); ///< Creates a new _empty_ Symbol instance. Symbol(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. diff --git a/test/binding.cc b/test/binding.cc index f450c71d2..09bd110bc 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -58,6 +58,7 @@ Object InitTypedThreadSafeFunctionSum(Env env); Object InitTypedThreadSafeFunctionUnref(Env env); Object InitTypedThreadSafeFunction(Env env); #endif +Object InitSymbol(Env env); Object InitTypedArray(Env env); Object InitGlobalObject(Env env); Object InitObjectWrap(Env env); @@ -117,6 +118,7 @@ Object Init(Env env, Object exports) { #endif // !NODE_ADDON_API_DISABLE_DEPRECATED exports.Set("promise", InitPromise(env)); exports.Set("run_script", InitRunScript(env)); + exports.Set("symbol", InitSymbol(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); exports.Set("threadsafe_function_existing_tsfn", InitThreadSafeFunctionExistingTsfn(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 562288c04..5cef552c8 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -46,6 +46,7 @@ 'object/subscript_operator.cc', 'promise.cc', 'run_script.cc', + "symbol.cc", 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', diff --git a/test/symbol.cc b/test/symbol.cc new file mode 100644 index 000000000..8fdebce6d --- /dev/null +++ b/test/symbol.cc @@ -0,0 +1,77 @@ +#include +using namespace Napi; + +Symbol CreateNewSymbolWithNoArgs(const Napi::CallbackInfo&) { + return Napi::Symbol(); +} + +Symbol CreateNewSymbolWithCppStrDesc(const Napi::CallbackInfo& info) { + String cppStrKey = info[0].As(); + return Napi::Symbol::New(info.Env(), cppStrKey.Utf8Value()); +} + +Symbol CreateNewSymbolWithCStrDesc(const Napi::CallbackInfo& info) { + String cStrKey = info[0].As(); + return Napi::Symbol::New(info.Env(), cStrKey.Utf8Value().c_str()); +} + +Symbol CreateNewSymbolWithNapiString(const Napi::CallbackInfo& info) { + String strKey = info[0].As(); + return Napi::Symbol::New(info.Env(), strKey); +} + +Symbol GetWellknownSymbol(const Napi::CallbackInfo& info) { + String registrySymbol = info[0].As(); + return Napi::Symbol::WellKnown(info.Env(), + registrySymbol.Utf8Value().c_str()); +} + +Symbol FetchSymbolFromGlobalRegistry(const Napi::CallbackInfo& info) { + String registrySymbol = info[0].As(); + return Napi::Symbol::For(info.Env(), registrySymbol); +} + +Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value()); +} + +Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str()); +} + +Symbol TestUndefinedSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return Napi::Symbol::For(env, env.Undefined()); +} + +Symbol TestNullSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return Napi::Symbol::For(env, env.Null()); +} + +Object InitSymbol(Env env) { + Object exports = Object::New(env); + + exports["createNewSymbolWithNoArgs"] = + Function::New(env, CreateNewSymbolWithNoArgs); + exports["createNewSymbolWithCppStr"] = + Function::New(env, CreateNewSymbolWithCppStrDesc); + exports["createNewSymbolWithCStr"] = + Function::New(env, CreateNewSymbolWithCStrDesc); + exports["createNewSymbolWithNapi"] = + Function::New(env, CreateNewSymbolWithNapiString); + exports["getWellKnownSymbol"] = Function::New(env, GetWellknownSymbol); + exports["getSymbolFromGlobalRegistry"] = + Function::New(env, FetchSymbolFromGlobalRegistry); + exports["getSymbolFromGlobalRegistryWithCKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithCKey); + exports["getSymbolFromGlobalRegistryWithCppKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithCppKey); + exports["testUndefinedSymbolCanBeCreated"] = + Function::New(env, TestUndefinedSymbolsCanBeCreated); + exports["testNullSymbolCanBeCreated"] = + Function::New(env, TestNullSymbolsCanBeCreated); + return exports; +} diff --git a/test/symbol.js b/test/symbol.js new file mode 100644 index 000000000..171f81eb2 --- /dev/null +++ b/test/symbol.js @@ -0,0 +1,70 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + + +async function test(binding) +{ + + const wellKnownSymbolFunctions = ['asyncIterator','hasInstance','isConcatSpreadable', 'iterator','match','matchAll','replace','search','split','species','toPrimitive','toStringTag','unscopables']; + + function assertCanCreateSymbol(symbol) + { + assert(binding.symbol.createNewSymbolWithCppStr(symbol) !== null); + assert(binding.symbol.createNewSymbolWithCStr(symbol) !== null); + assert(binding.symbol.createNewSymbolWithNapi(symbol) !== null); + } + + function assertSymbolAreUnique(symbol) + { + const symbolOne = binding.symbol.createNewSymbolWithCppStr(symbol); + const symbolTwo = binding.symbol.createNewSymbolWithCppStr(symbol); + + assert(symbolOne !== symbolTwo); + } + + function assertSymbolIsWellknown(symbol) + { + const symbOne = binding.symbol.getWellKnownSymbol(symbol); + const symbTwo = binding.symbol.getWellKnownSymbol(symbol); + assert(symbOne && symbTwo); + assert(symbOne === symbTwo); + } + + function assertSymbolIsNotWellknown(symbol) + { + const symbolTest = binding.symbol.getWellKnownSymbol(symbol); + assert(symbolTest === undefined); + } + + function assertCanCreateOrFetchGlobalSymbols(symbol, fetchFunction) + { + const symbOne = fetchFunction(symbol); + const symbTwo = fetchFunction(symbol); + assert(symbOne && symbTwo); + assert(symbOne === symbTwo); + } + + assertCanCreateSymbol("testing"); + assertSymbolAreUnique("symbol"); + assertSymbolIsNotWellknown("testing"); + + for(const wellknownProperty of wellKnownSymbolFunctions) + { + assertSymbolIsWellknown(wellknownProperty); + } + + assertCanCreateOrFetchGlobalSymbols("data", binding.symbol.getSymbolFromGlobalRegistry); + assertCanCreateOrFetchGlobalSymbols("CppKey", binding.symbol.getSymbolFromGlobalRegistryWithCppKey); + assertCanCreateOrFetchGlobalSymbols("CKey", binding.symbol.getSymbolFromGlobalRegistryWithCKey); + + assert(binding.symbol.createNewSymbolWithNoArgs() === undefined); + + assert(binding.symbol.testNullSymbolCanBeCreated() === binding.symbol.testNullSymbolCanBeCreated()); + assert(binding.symbol.testUndefinedSymbolCanBeCreated() === binding.symbol.testUndefinedSymbolCanBeCreated()); + assert(binding.symbol.testUndefinedSymbolCanBeCreated() !== binding.symbol.testNullSymbolCanBeCreated()); +} From fe950f62c43eae67ec5c896772f73e1481e4d9e8 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 9 Jul 2021 10:26:39 -0700 Subject: [PATCH 41/57] src,test: fix up null char * exception thrown Throw an exception when receiving a null pointer for the `char *` and `char16_t *` overloads of `String::New` that looks identical to an error that core would have thrown under the circumstances (`napi_invalid_arg`). Also, rename the test methods to conform with our naming convention. PR-URL: https://github.com/nodejs/node-addon-api/pull/1019 Reviewed-By: Nicola Del Gobbo Reviewed-By: Chengzhong Wu Reviewed-By: Michael Dawson --- napi-inl.h | 13 ++++++++++--- test/name.cc | 12 +++++++++--- test/name.js | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 663f229be..a0569eb4a 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -900,10 +900,11 @@ inline String String::New(napi_env env, const std::u16string& val) { } inline String String::New(napi_env env, const char* val) { + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. if (val == nullptr) { - NAPI_THROW( - TypeError::New(env, "String::New received a nullpointer as a value"), - Napi::String()); + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); } napi_value value; napi_status status = napi_create_string_utf8(env, val, std::strlen(val), &value); @@ -913,6 +914,12 @@ inline String String::New(napi_env env, const char* val) { inline String String::New(napi_env env, const char16_t* val) { napi_value value; + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } napi_status status = napi_create_string_utf16(env, val, std::u16string(val).size(), &value); NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); diff --git a/test/name.cc b/test/name.cc index 4e56141bc..d8556e154 100644 --- a/test/name.cc +++ b/test/name.cc @@ -82,18 +82,24 @@ Value CheckSymbol(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].Type() == napi_symbol); } -void AssertErrorThrownWhenPassedNullptr(const CallbackInfo& info) { +void NullStringShouldThrow(const CallbackInfo& info) { const char* nullStr = nullptr; String::New(info.Env(), nullStr); } +void NullString16ShouldThrow(const CallbackInfo& info) { + const char16_t* nullStr = nullptr; + String::New(info.Env(), nullStr); +} + Object InitName(Env env) { Object exports = Object::New(env); exports["echoString"] = Function::New(env, EchoString); exports["createString"] = Function::New(env, CreateString); - exports["nullStringShouldThrow"] = - Function::New(env, AssertErrorThrownWhenPassedNullptr); + exports["nullStringShouldThrow"] = Function::New(env, NullStringShouldThrow); + exports["nullString16ShouldThrow"] = + Function::New(env, NullString16ShouldThrow); exports["checkString"] = Function::New(env, CheckString); exports["createSymbol"] = Function::New(env, CreateSymbol); exports["checkSymbol"] = Function::New(env, CheckSymbol); diff --git a/test/name.js b/test/name.js index 4c74542f0..717f1862f 100644 --- a/test/name.js +++ b/test/name.js @@ -9,8 +9,8 @@ function test(binding) { assert.throws(binding.name.nullStringShouldThrow, { - name: 'TypeError', - message: 'String::New received a nullpointer as a value', + name: 'Error', + message: 'Error in native callback', }); assert.ok(binding.name.checkString(expected, 'utf8')); assert.ok(binding.name.checkString(expected, 'utf16')); From 6ff72ac26593a7fca47a387866625a5bc8faeda9 Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Tue, 20 Jul 2021 15:27:07 -0400 Subject: [PATCH 42/57] doc: update tests to avoid running in parallel fixes: https://github.com/nodejs/node-addon-api/issues/1022 The objectwrap_worker_thread and symbol tests were not waiting for the test to complete before the subsequent tests were started. This caused intermitted crashes in the CI. Updated both tests so that they complete before the next test runs. Signed-off-by: Michael Dawson PR-URL: https://github.com/nodejs/node-addon-api/pull/1024 Reviewed-By: Chengzhong Wu --- test/common/index.js | 7 +++++++ test/objectwrap_worker_thread.js | 23 ++++++++++++++--------- test/symbol.js | 11 +++++++---- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/test/common/index.js b/test/common/index.js index ab6d12bc8..e8c480cab 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -101,3 +101,10 @@ exports.runTestWithBindingPath = async function(test, buildType) { await test(item); } } + +exports.runTestWithBuildType = async function(test, buildType) { + buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; + + await Promise.resolve(test(buildType)) + .finally(exports.mustCall()); +} diff --git a/test/objectwrap_worker_thread.js b/test/objectwrap_worker_thread.js index eedc9eeda..e5cfdb81a 100644 --- a/test/objectwrap_worker_thread.js +++ b/test/objectwrap_worker_thread.js @@ -1,14 +1,19 @@ 'use strict'; +const path = require('path'); const { Worker, isMainThread, workerData } = require('worker_threads'); -if (isMainThread) { - const buildType = process.config.target_defaults.default_configuration; - new Worker(__filename, { workerData: buildType }); -} else { - const test = binding => { - new binding.objectwrap.Test(); - }; +module.exports = require('./common').runTestWithBuildType(test); - const buildType = workerData; - require('./common').runTest(test, buildType); +async function test(buildType) { + if (isMainThread) { + const buildType = process.config.target_defaults.default_configuration; + const worker = new Worker(__filename, { workerData: buildType }); + return new Promise((resolve, reject) => { + worker.on('exit', () => { + resolve(); + }); + }, () => {}); + } else { + await require(path.join(__dirname, 'objectwrap.js')); + } } diff --git a/test/symbol.js b/test/symbol.js index 171f81eb2..7601feb2f 100644 --- a/test/symbol.js +++ b/test/symbol.js @@ -3,14 +3,17 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +module.exports = require('./common').runTest(test); -async function test(binding) +function test(binding) { + const majorNodeVersion = process.versions.node.split('.')[0]; - const wellKnownSymbolFunctions = ['asyncIterator','hasInstance','isConcatSpreadable', 'iterator','match','matchAll','replace','search','split','species','toPrimitive','toStringTag','unscopables']; + let wellKnownSymbolFunctions = ['asyncIterator','hasInstance','isConcatSpreadable', 'iterator','match','replace','search','split','species','toPrimitive','toStringTag','unscopables']; + if (majorNodeVersion >= 12) { + wellKnownSymbolFunctions.push('matchAll'); + } function assertCanCreateSymbol(symbol) { From daf910e2a3ef19cfaf84c2079dd4dde3a1e018a3 Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 25 Jun 2021 00:37:50 +0200 Subject: [PATCH 43/57] src: add AddCleanupHook Add CleanupHook support to Env PR-URL: https://github.com/nodejs/node-addon-api/pull/1014 Reviewed-By: Michael Dawson --- doc/env.md | 64 +++++++++++++++++++++++++++++++++ napi-inl.h | 67 ++++++++++++++++++++++++++++++++++ napi.h | 35 +++++++++++++++++- test/binding.cc | 4 +++ test/binding.gyp | 1 + test/env_cleanup.cc | 88 +++++++++++++++++++++++++++++++++++++++++++++ test/env_cleanup.js | 56 +++++++++++++++++++++++++++++ test/index.js | 1 + 8 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 test/env_cleanup.cc create mode 100644 test/env_cleanup.js diff --git a/doc/env.md b/doc/env.md index 66575ea2c..c04b72529 100644 --- a/doc/env.md +++ b/doc/env.md @@ -130,3 +130,67 @@ Associates a data item stored at `T* data` with the current instance of the addon. The item will be passed to the function `fini` which gets called when an instance of the addon is unloaded. This overload accepts an additional hint to be passed to `fini`. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook); +``` + +- `[in] hook`: A function to call when the environment exists. Accepts a + function of the form `void ()`. + +Registers `hook` as a function to be run once the current Node.js environment +exits. Unlike the underlying C-based Node-API, providing the same `hook` +multiple times **is** allowed. The hooks will be called in reverse order, i.e. +the most recently added one will be called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook, Arg* arg); +``` + +- `[in] hook`: A function to call when the environment exists. Accepts a + function of the form `void (Arg* arg)`. +- `[in] arg`: A pointer to data that will be passed as the argument to `hook`. + +Registers `hook` as a function to be run with the `arg` parameter once the +current Node.js environment exits. Unlike the underlying C-based Node-API, +providing the same `hook` and `arg` pair multiple times **is** allowed. The +hooks will be called in reverse order, i.e. the most recently added one will be +called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +# Env::CleanupHook + +The `Env::CleanupHook` object allows removal of the hook added via +`Env::AddCleanupHook()` + +## Methods + +### IsEmpty + +```cpp +bool IsEmpty(); +``` + +Returns `true` if the cleanup hook was **not** successfully registered. + +### Remove + +```cpp +bool Remove(Env env); +``` + +Unregisters the hook from running once the current Node.js environment exits. + +Returns `true` if the hook was successfully removed from the Node.js +environment. diff --git a/napi-inl.h b/napi-inl.h index a0569eb4a..cc02af3ac 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -442,6 +442,26 @@ inline Value Env::RunScript(String script) { return Value(_env, result); } +#if NAPI_VERSION > 2 +template +void Env::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = + static_cast::CleanupData*>( + data); + cleanupData->hook(); + delete cleanupData; +} + +template +void Env::CleanupHook::WrapperWithArg(void* data) NAPI_NOEXCEPT { + auto* cleanupData = + static_cast::CleanupData*>( + data); + cleanupData->hook(static_cast(cleanupData->arg)); + delete cleanupData; +} +#endif // NAPI_VERSION > 2 + #if NAPI_VERSION > 5 template fini> inline void Env::SetInstanceData(T* data) { @@ -5725,6 +5745,53 @@ Addon::DefineProperties(Object object, } #endif // NAPI_VERSION > 5 +#if NAPI_VERSION > 2 +template +Env::CleanupHook Env::AddCleanupHook(Hook hook, Arg* arg) { + return CleanupHook(*this, hook, arg); +} + +template +Env::CleanupHook Env::AddCleanupHook(Hook hook) { + return CleanupHook(*this, hook); +} + +template +Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook) + : wrapper(Env::CleanupHook::Wrapper) { + data = new CleanupData{std::move(hook), nullptr}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook, Arg* arg) + : wrapper(Env::CleanupHook::WrapperWithArg) { + data = new CleanupData{std::move(hook), arg}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +bool Env::CleanupHook::Remove(Env env) { + napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); + delete data; + data = nullptr; + return status == napi_ok; +} + +template +bool Env::CleanupHook::IsEmpty() const { + return data == nullptr; +} +#endif // NAPI_VERSION > 2 + } // namespace Napi #endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index c3e02cb88..9f12a76bf 100644 --- a/napi.h +++ b/napi.h @@ -179,8 +179,12 @@ namespace Napi { /// In the V8 JavaScript engine, a Node-API environment approximately /// corresponds to an Isolate. class Env { + private: +#if NAPI_VERSION > 2 + template + class CleanupHook; +#endif // NAPI_VERSION > 2 #if NAPI_VERSION > 5 - private: template static void DefaultFini(Env, T* data); template static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); @@ -201,6 +205,14 @@ namespace Napi { Value RunScript(const std::string& utf8script); Value RunScript(String script); +#if NAPI_VERSION > 2 + template + CleanupHook AddCleanupHook(Hook hook); + + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 + #if NAPI_VERSION > 5 template T* GetInstanceData(); @@ -219,7 +231,28 @@ namespace Napi { private: napi_env _env; + +#if NAPI_VERSION > 2 + template + class CleanupHook { + public: + CleanupHook(Env env, Hook hook, Arg* arg); + CleanupHook(Env env, Hook hook); + bool Remove(Env env); + bool IsEmpty() const; + + private: + static inline void Wrapper(void* data) NAPI_NOEXCEPT; + static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; + + void (*wrapper)(void* arg); + struct CleanupData { + Hook hook; + Arg* arg; + } * data; + }; }; +#endif // NAPI_VERSION > 2 /// A JavaScript value of unknown type. /// diff --git a/test/binding.cc b/test/binding.cc index 09bd110bc..267749394 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -30,6 +30,7 @@ Object InitDate(Env env); #endif Object InitDataView(Env env); Object InitDataViewReadWrite(Env env); +Object InitEnvCleanup(Env env); Object InitError(Env env); Object InitExternal(Env env); Object InitFunction(Env env); @@ -104,6 +105,9 @@ Object Init(Env env, Object exports) { exports.Set("dataview", InitDataView(env)); exports.Set("dataview_read_write", InitDataView(env)); exports.Set("dataview_read_write", InitDataViewReadWrite(env)); +#if (NAPI_VERSION > 2) + exports.Set("env_cleanup", InitEnvCleanup(env)); +#endif exports.Set("error", InitError(env)); exports.Set("external", InitExternal(env)); exports.Set("function", InitFunction(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 5cef552c8..f30d081d7 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -22,6 +22,7 @@ 'callbackscope.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', + 'env_cleanup.cc', 'error.cc', 'external.cc', 'function.cc', diff --git a/test/env_cleanup.cc b/test/env_cleanup.cc new file mode 100644 index 000000000..44be0d5f7 --- /dev/null +++ b/test/env_cleanup.cc @@ -0,0 +1,88 @@ +#include +#include "napi.h" + +using namespace Napi; + +#if (NAPI_VERSION > 2) +namespace { + +static void cleanup(void* arg) { + printf("static cleanup(%d)\n", *(int*)(arg)); +} +static void cleanupInt(int* arg) { + printf("static cleanup(%d)\n", *(arg)); +} + +static void cleanupVoid() { + printf("static cleanup()\n"); +} + +static int secret1 = 42; +static int secret2 = 43; + +Value AddHooks(const CallbackInfo& info) { + auto env = info.Env(); + + bool shouldRemove = info[0].As().Value(); + + // hook: void (*)(void *arg), hint: int + auto hook1 = env.AddCleanupHook(cleanup, &secret1); + // test using same hook+arg pair + auto hook1b = env.AddCleanupHook(cleanup, &secret1); + + // hook: void (*)(int *arg), hint: int + auto hook2 = env.AddCleanupHook(cleanupInt, &secret2); + + // hook: void (*)(int *arg), hint: void (default) + auto hook3 = env.AddCleanupHook(cleanupVoid); + // test using the same hook + auto hook3b = env.AddCleanupHook(cleanupVoid); + + // hook: lambda []void (int *arg)->void, hint: int + auto hook4 = env.AddCleanupHook( + [&](int* arg) { printf("lambda cleanup(%d)\n", *arg); }, &secret1); + + // hook: lambda []void (void *)->void, hint: void + auto hook5 = + env.AddCleanupHook([&](void*) { printf("lambda cleanup(void)\n"); }, + static_cast(nullptr)); + + // hook: lambda []void ()->void, hint: void (default) + auto hook6 = env.AddCleanupHook([&]() { printf("lambda cleanup()\n"); }); + + if (shouldRemove) { + hook1.Remove(env); + hook1b.Remove(env); + hook2.Remove(env); + hook3.Remove(env); + hook3b.Remove(env); + hook4.Remove(env); + hook5.Remove(env); + hook6.Remove(env); + } + + int added = 0; + + added += !hook1.IsEmpty(); + added += !hook1b.IsEmpty(); + added += !hook2.IsEmpty(); + added += !hook3.IsEmpty(); + added += !hook3b.IsEmpty(); + added += !hook4.IsEmpty(); + added += !hook5.IsEmpty(); + added += !hook6.IsEmpty(); + + return Number::New(env, added); +} + +} // anonymous namespace + +Object InitEnvCleanup(Env env) { + Object exports = Object::New(env); + + exports["addHooks"] = Function::New(env, AddHooks); + + return exports; +} + +#endif diff --git a/test/env_cleanup.js b/test/env_cleanup.js new file mode 100644 index 000000000..48113caac --- /dev/null +++ b/test/env_cleanup.js @@ -0,0 +1,56 @@ +'use strict'; + +const assert = require('assert'); + +if (process.argv[2] === 'runInChildProcess') { + const binding_path = process.argv[3]; + const remove_hooks = process.argv[4] === 'true'; + + const binding = require(binding_path); + const actualAdded = binding.env_cleanup.addHooks(remove_hooks); + const expectedAdded = remove_hooks === true ? 0 : 8; + assert(actualAdded === expectedAdded, 'Incorrect number of hooks added'); +} +else { + module.exports = require('./common').runTestWithBindingPath(test); +} + +function test(bindingPath) { + for (const remove_hooks of [false, true]) { + const { status, output } = require('./napi_child').spawnSync( + process.execPath, + [ + __filename, + 'runInChildProcess', + bindingPath, + remove_hooks, + ], + { encoding: 'utf8' } + ); + + const stdout = output[1].trim(); + /** + * There is no need to sort the lines, as per Node-API documentation: + * > The hooks will be called in reverse order, i.e. the most recently + * > added one will be called first. + */ + const lines = stdout.split(/[\r\n]+/); + + assert(status === 0, `Process aborted with status ${status}`); + + if (remove_hooks) { + assert.deepStrictEqual(lines, [''], 'Child process had console output when none expected') + } else { + assert.deepStrictEqual(lines, [ + 'lambda cleanup()', + 'lambda cleanup(void)', + 'lambda cleanup(42)', + 'static cleanup()', + 'static cleanup()', + 'static cleanup(43)', + 'static cleanup(42)', + 'static cleanup(42)' + ], 'Child process console output mismisatch') + } + } +} diff --git a/test/index.js b/test/index.js index 69d13bbc0..2fe09ac2b 100644 --- a/test/index.js +++ b/test/index.js @@ -82,6 +82,7 @@ if (process.env.NAPI_VERSION) { console.log('napiVersion:' + napiVersion); if (napiVersion < 3) { + testModules.splice(testModules.indexOf('env_cleanup'), 1); testModules.splice(testModules.indexOf('callbackscope'), 1); testModules.splice(testModules.indexOf('version_management'), 1); } From 6f790438f57074a2e8bd93aa6a23b8050e219ef6 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 3 Aug 2021 23:03:56 +0800 Subject: [PATCH 44/57] src: return Maybe on pending exception when cpp exception disabled PR-URL: https://github.com/nodejs/node-addon-api/pull/927 Reviewed-By: Michael Dawson --- doc/error_handling.md | 73 ++- doc/maybe.md | 76 +++ doc/setup.md | 9 + napi-inl.h | 601 ++++++++++++------ napi.h | 462 +++++++++----- test/README.md | 91 +++ test/addon_data.cc | 4 +- test/basic_types/value.cc | 9 +- test/bigint.cc | 4 +- test/binding.cc | 8 + test/binding.gyp | 8 + test/common/index.js | 2 + test/common/test_helper.h | 61 ++ test/function.cc | 28 +- test/functionreference.cc | 5 +- .../global_object_delete_property.cc | 14 +- .../global_object_get_property.cc | 9 +- .../global_object_has_own_property.cc | 16 +- test/maybe/check.cc | 23 + test/maybe/index.js | 38 ++ test/object/delete_property.cc | 15 +- test/object/get_property.cc | 11 +- test/object/has_own_property.cc | 15 +- test/object/has_property.cc | 15 +- test/object/object.cc | 5 +- test/object/object_freeze_seal.cc | 5 +- test/object/set_property.cc | 14 +- test/objectreference.cc | 19 +- test/objectwrap.cc | 195 ++++-- test/run_script.cc | 19 +- test/symbol.cc | 16 +- .../threadsafe_function_existing_tsfn.cc | 13 +- .../threadsafe_function_unref.cc | 3 +- ...typed_threadsafe_function_existing_tsfn.cc | 11 +- .../typed_threadsafe_function_unref.cc | 3 +- 35 files changed, 1391 insertions(+), 509 deletions(-) create mode 100644 doc/maybe.md create mode 100644 test/README.md create mode 100644 test/common/test_helper.h create mode 100644 test/maybe/check.cc create mode 100644 test/maybe/index.js diff --git a/doc/error_handling.md b/doc/error_handling.md index 50c04a0fe..57de85c9e 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -17,6 +17,7 @@ error-handling for C++ exceptions and JavaScript exceptions. The following sections explain the approach for each case: - [Handling Errors With C++ Exceptions](#exceptions) +- [Handling Errors With Maybe Type and C++ Exceptions Disabled](#noexceptions-maybe) - [Handling Errors Without C++ Exceptions](#noexceptions) @@ -70,7 +71,7 @@ when returning to JavaScript. ### Propagating a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // other C++ statements // ... @@ -84,7 +85,7 @@ a JavaScript exception when returning to JavaScript. ### Handling a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result; try { result = jsFunctionThatThrows({ arg1, arg2 }); @@ -96,6 +97,70 @@ try { Since the exception was caught here, it will not be propagated as a JavaScript exception. + + +## Handling Errors With Maybe Type and C++ Exceptions Disabled + +If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the +`Napi::Error` class does not extend `std::exception`. This means that any calls to +node-addon-api functions do not throw a C++ exceptions. Instead, these node-api +functions that call into JavaScript are returning with `Maybe` boxed values. +In that case, the calling side should convert the `Maybe` boxed values with +checks to ensure that the call did succeed and therefore no exception is pending. +If the check fails, that is to say, the returning value is _empty_, the calling +side should determine what to do with `env.GetAndClearPendingException()` before +attempting to call another node-api (for more info see: [Env](env.md)). + +The conversion from the `Maybe` boxed value to the actual return value is +enforced by compilers so that the exceptions must be properly handled before +continuing. + +## Examples with Maybe Type and C++ exceptions disabled + +### Throwing a JS exception + +```cpp +Napi::Env env = ... +Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); +return; +``` + +After throwing a JavaScript exception, the code should generally return +immediately from the native callback, after performing any necessary cleanup. + +### Propagating a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Value result; +if (!maybeResult.To(&result)) { + // The Maybe is empty, calling into js failed, cleaning up... + // It is recommended to return an empty Maybe if the procedure failed. + return result; +} +``` + +If `maybeResult.To(&result)` returns false a JavaScript exception is pending. +To let the exception propagate, the code should generally return immediately +from the native callback, after performing any necessary cleanup. + +### Handling a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +if (maybeResult.IsNothing()) { + Napi::Error e = env.GetAndClearPendingException(); + cerr << "Caught JavaScript exception: " + e.Message(); +} +``` + +Since the exception was cleared here, it will not be propagated as a JavaScript +exception after the native callback returns. + ## Handling Errors Without C++ Exceptions @@ -127,7 +192,7 @@ immediately from the native callback, after performing any necessary cleanup. ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Error e = env.GetAndClearPendingException(); @@ -143,7 +208,7 @@ the native callback, after performing any necessary cleanup. ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Napi::Error e = env.GetAndClearPendingException(); diff --git a/doc/maybe.md b/doc/maybe.md new file mode 100644 index 000000000..dc71c0750 --- /dev/null +++ b/doc/maybe.md @@ -0,0 +1,76 @@ +# Maybe (template) + +Class `Napi::Maybe` represents a value that may be empty: every `Maybe` is +either `Just` and contains a value, or `Nothing`, and does not. `Maybe` types +are very common in node-addon-api code, as they represent that the function may +throw a JavaScript exception and cause the program to be unable to evaluate any +JavaScript code until the exception has been handled. + +Typically, the value wrapped in `Napi::Maybe` is [`Napi::Value`] and its +subclasses. + +## Methods + +### IsNothing + +```cpp +template +bool Napi::Maybe::IsNothing() const; +``` + +Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and +`false` otherwise. + +### IsJust + +```cpp +template +bool Napi::Maybe::IsJust() const; +``` + +Returns `true` if the `Maybe` is `Just` and contains a value, and `false` +otherwise. + +### Check + +```cpp +template +void Napi::Maybe::Check() const; +``` + +Short-hand for `Maybe::Unwrap()`, which doesn't return a value. Could be used +where the actual value of the Maybe is not needed like `Object::Set`. +If this Maybe is nothing (empty), node-addon-api will crash the +process. + +### Unwrap + +```cpp +template +T Napi::Maybe::Unwrap() const; +``` + +Return the value of type `T` contained in the Maybe. If this Maybe is +nothing (empty), node-addon-api will crash the process. + +### UnwrapOr + +```cpp +template +T Napi::Maybe::UnwrapOr(const T& default_value) const; +``` + +Return the value of type T contained in the Maybe, or use a default +value if this Maybe is nothing (empty). + +### UnwrapTo + +```cpp +template +bool Napi::Maybe::UnwrapTo(T* result) const; +``` + +Converts this Maybe to a value of type `T` in the `out`. If this Maybe is +nothing (empty), `false` is returned and `out` is left untouched. + +[`Napi::Value`]: ./value.md diff --git a/doc/setup.md b/doc/setup.md index aec397e61..5db3452ad 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -54,6 +54,15 @@ To use **Node-API** in a native module: ```gyp 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], ``` + + If you decide to use node-addon-api without C++ exceptions enabled, please + consider enabling node-addon-api safe API type guards to ensure the proper + exception handling pattern: + +```gyp + 'defines': [ 'NODE_ADDON_API_ENABLE_MAYBE' ], +``` + 4. If you would like your native addon to support OSX, please also add the following settings in the `binding.gyp` file: diff --git a/napi-inl.h b/napi-inl.h index cc02af3ac..a1d67ac17 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -377,6 +377,72 @@ inline napi_value RegisterModule(napi_env env, }); } +//////////////////////////////////////////////////////////////////////////////// +// Maybe class +//////////////////////////////////////////////////////////////////////////////// + +template +bool Maybe::IsNothing() const { + return !_has_value; +} + +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + //////////////////////////////////////////////////////////////////////////////// // Env class //////////////////////////////////////////////////////////////////////////////// @@ -426,20 +492,20 @@ inline Error Env::GetAndClearPendingException() { return Error(_env, value); } -inline Value Env::RunScript(const char* utf8script) { +inline MaybeOrValue Env::RunScript(const char* utf8script) { String script = String::New(_env, utf8script); return RunScript(script); } -inline Value Env::RunScript(const std::string& utf8script) { +inline MaybeOrValue Env::RunScript(const std::string& utf8script) { return RunScript(utf8script.c_str()); } -inline Value Env::RunScript(String script) { +inline MaybeOrValue Env::RunScript(String script) { napi_value result; napi_status status = napi_run_script(_env, script, &result); - NAPI_THROW_IF_FAILED(_env, status, Undefined()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } #if NAPI_VERSION > 2 @@ -678,32 +744,32 @@ inline T Value::As() const { return T(_env, _value); } -inline Boolean Value::ToBoolean() const { +inline MaybeOrValue Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Boolean()); - return Boolean(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); } -inline Number Value::ToNumber() const { +inline MaybeOrValue Value::ToNumber() const { napi_value result; napi_status status = napi_coerce_to_number(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Number()); - return Number(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); } -inline String Value::ToString() const { +inline MaybeOrValue Value::ToString() const { napi_value result; napi_status status = napi_coerce_to_string(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, String()); - return String(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); } -inline Object Value::ToObject() const { +inline MaybeOrValue Value::ToObject() const { napi_value result; napi_status status = napi_coerce_to_object(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -1026,29 +1092,56 @@ inline Symbol Symbol::New(napi_env env, napi_value description) { return Symbol(env, value); } -inline Symbol Symbol::WellKnown(napi_env env, const std::string& name) { +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else return Napi::Env(env).Global().Get("Symbol").As().Get(name).As(); +#endif } -inline Symbol Symbol::For(napi_env env, const std::string& description) { +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); } -inline Symbol Symbol::For(napi_env env, const char* description) { +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); } -inline Symbol Symbol::For(napi_env env, String description) { +inline MaybeOrValue Symbol::For(napi_env env, String description) { return Symbol::For(env, static_cast(description)); } -inline Symbol Symbol::For(napi_env env, napi_value description) { - Object symbObject = Napi::Env(env).Global().Get("Symbol").As(); - auto forSymb = - symbObject.Get("for").As().Call(symbObject, {description}); - return forSymb.As(); +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif } inline Symbol::Symbol() : Name() { @@ -1163,12 +1256,23 @@ String String::From(napi_env env, const T& value) { template inline Object::PropertyLValue::operator Value() const { - return Object(_env, _object).Get(_key); + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif } template template inline Object::PropertyLValue& Object::PropertyLValue::operator =(ValueType value) { - Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif return *this; } @@ -1201,208 +1305,192 @@ inline Object::PropertyLValue Object::operator [](uint32_t index) { return PropertyLValue(*this, index); } -inline Value Object::operator [](const char* utf8name) const { +inline MaybeOrValue Object::operator[](const char* utf8name) const { return Get(utf8name); } -inline Value Object::operator [](const std::string& utf8name) const { +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { return Get(utf8name); } -inline Value Object::operator [](uint32_t index) const { +inline MaybeOrValue Object::operator[](uint32_t index) const { return Get(index); } -inline bool Object::Has(napi_value key) const { +inline MaybeOrValue Object::Has(napi_value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(Value key) const { +inline MaybeOrValue Object::Has(Value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const char* utf8name) const { +inline MaybeOrValue Object::Has(const char* utf8name) const { bool result; napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const std::string& utf8name) const { +inline MaybeOrValue Object::Has(const std::string& utf8name) const { return Has(utf8name.c_str()); } -inline bool Object::HasOwnProperty(napi_value key) const { +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(Value key) const { +inline MaybeOrValue Object::HasOwnProperty(Value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(const char* utf8name) const { +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { napi_value key; napi_status status = napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); - NAPI_THROW_IF_FAILED(_env, status, false); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); return HasOwnProperty(key); } -inline bool Object::HasOwnProperty(const std::string& utf8name) const { +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { return HasOwnProperty(utf8name.c_str()); } -inline Value Object::Get(napi_value key) const { +inline MaybeOrValue Object::Get(napi_value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(Value key) const { +inline MaybeOrValue Object::Get(Value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const char* utf8name) const { +inline MaybeOrValue Object::Get(const char* utf8name) const { napi_value result; napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const std::string& utf8name) const { +inline MaybeOrValue Object::Get(const std::string& utf8name) const { return Get(utf8name.c_str()); } template -inline bool Object::Set(napi_value key, const ValueType& value) { +inline MaybeOrValue Object::Set(napi_value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(Value key, const ValueType& value) { +inline MaybeOrValue Object::Set(Value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(const char* utf8name, const ValueType& value) { +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) { napi_status status = napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(const std::string& utf8name, const ValueType& value) { +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) { return Set(utf8name.c_str(), value); } -inline bool Object::Delete(napi_value key) { +inline MaybeOrValue Object::Delete(napi_value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(Value key) { +inline MaybeOrValue Object::Delete(Value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(const char* utf8name) { +inline MaybeOrValue Object::Delete(const char* utf8name) { return Delete(String::New(_env, utf8name)); } -inline bool Object::Delete(const std::string& utf8name) { +inline MaybeOrValue Object::Delete(const std::string& utf8name) { return Delete(String::New(_env, utf8name)); } -inline bool Object::Has(uint32_t index) const { +inline MaybeOrValue Object::Has(uint32_t index) const { bool result; napi_status status = napi_has_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Value Object::Get(uint32_t index) const { +inline MaybeOrValue Object::Get(uint32_t index) const { napi_value value; napi_status status = napi_get_element(_env, _value, index, &value); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); } template -inline bool Object::Set(uint32_t index, const ValueType& value) { +inline MaybeOrValue Object::Set(uint32_t index, const ValueType& value) { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::Delete(uint32_t index) { +inline MaybeOrValue Object::Delete(uint32_t index) { bool result; napi_status status = napi_delete_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Array Object::GetPropertyNames() const { +inline MaybeOrValue Object::GetPropertyNames() const { napi_value result; napi_status status = napi_get_property_names(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Array()); - return Array(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); } -inline bool Object::DefineProperty(const PropertyDescriptor& property) { +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::DefineProperties( +inline MaybeOrValue Object::DefineProperties( const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::DefineProperties( +inline MaybeOrValue Object::DefineProperties( const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::InstanceOf(const Function& constructor) const { +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } template @@ -1442,16 +1530,14 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } #if NAPI_VERSION >= 8 -inline bool Object::Freeze() { +inline MaybeOrValue Object::Freeze() { napi_status status = napi_object_freeze(_env, _value); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::Seal() { +inline MaybeOrValue Object::Seal() { napi_status status = napi_object_seal(_env, _value); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } #endif // NAPI_VERSION >= 8 @@ -2098,53 +2184,61 @@ inline Function::Function() : Object() { inline Function::Function(napi_env env, napi_value value) : Object(env, value) { } -inline Value Function::operator ()(const std::initializer_list& args) const { +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::vector& args) const { +inline MaybeOrValue Function::Call( + const std::vector& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { return Call(Env().Undefined(), argc, args); } -inline Value Function::Call(napi_value recv, const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { return Call(recv, args.size(), args.begin()); } -inline Value Function::Call(napi_value recv, const std::vector& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { return Call(recv, args.size(), args.data()); } -inline Value Function::Call(napi_value recv, size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call(napi_value recv, + size_t argc, + const napi_value* args) const { napi_value result; napi_status status = napi_call_function( _env, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.begin(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.data(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, size_t argc, const napi_value* args, @@ -2152,24 +2246,27 @@ inline Value Function::MakeCallback( napi_value result; napi_status status = napi_make_callback( _env, context, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Object Function::New(const std::initializer_list& args) const { +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { return New(args.size(), args.begin()); } -inline Object Function::New(const std::vector& args) const { +inline MaybeOrValue Function::New( + const std::vector& args) const { return New(args.size(), args.data()); } -inline Object Function::New(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { napi_value result; napi_status status = napi_new_instance( _env, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -2441,7 +2538,14 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { // the std::string::operator=, because this method may not throw. } #else // NAPI_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else _message = Get("message").As(); +#endif #endif // NAPI_CPP_EXCEPTIONS } return _message; @@ -2758,101 +2862,147 @@ inline ObjectReference::ObjectReference(const ObjectReference& other) : Reference(other) { } -inline Napi::Value ObjectReference::Get(const char* utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value ObjectReference::Get(const std::string& utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline bool ObjectReference::Set(const char* utf8name, napi_value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) { HandleScope scope(_env); return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const char* utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) { HandleScope scope(_env); return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const char* utf8name, double numberValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) { HandleScope scope(_env); return Value().Set(utf8name, numberValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - napi_value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) { HandleScope scope(_env); return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const std::string& utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) { HandleScope scope(_env); return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - double numberValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) { HandleScope scope(_env); return Value().Set(utf8name, numberValue); } -inline Napi::Value ObjectReference::Get(uint32_t index) const { +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(index)); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline bool ObjectReference::Set(uint32_t index, napi_value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) { HandleScope scope(_env); return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) { HandleScope scope(_env); return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) { HandleScope scope(_env); return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, const std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const std::string& utf8value) { HandleScope scope(_env); return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, bool boolValue) { HandleScope scope(_env); return Value().Set(index, boolValue); } -inline bool ObjectReference::Set(uint32_t index, double numberValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) { HandleScope scope(_env); return Value().Set(index, numberValue); } @@ -2886,105 +3036,200 @@ inline FunctionReference& FunctionReference::operator =(FunctionReference&& othe return *this; } -inline Napi::Value FunctionReference::operator ()( +inline MaybeOrValue FunctionReference::operator()( const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value()(args)); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::vector& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, size_t argc, const napi_value* args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, argc, args); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, size_t argc, const napi_value* args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, argc, args, context); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Object FunctionReference::New(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } -inline Object FunctionReference::New(const std::vector& args) const { +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 9f12a76bf..8475bfc96 100644 --- a/napi.h +++ b/napi.h @@ -34,6 +34,13 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #endif #endif +// If C++ NAPI_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE should +// not be set +#if defined(NAPI_CPP_EXCEPTIONS) && defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NAPI_CPP_EXCEPTIONS is defined. +#endif + #ifdef _NOEXCEPT #define NAPI_NOEXCEPT _NOEXCEPT #else @@ -77,20 +84,36 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 return; \ } while (0) -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ } -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return; \ +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ } #endif // NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif + # define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; # define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; @@ -98,13 +121,16 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 NAPI_DISALLOW_ASSIGN(CLASS) \ NAPI_DISALLOW_COPY(CLASS) -#define NAPI_FATAL_IF_FAILED(status, location, message) \ - do { \ - if ((status) != napi_ok) { \ - Napi::Error::Fatal((location), (message)); \ - } \ +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ } while (0) +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + //////////////////////////////////////////////////////////////////////////////// /// Node-API C++ Wrapper Classes /// @@ -165,6 +191,67 @@ namespace Napi { class MemoryManagement; + /// A simple Maybe type, representing an object which may or may not have a + /// value. + /// + /// If an API method returns a Maybe<>, the API method can potentially fail + /// either because an exception is thrown, or because an exception is pending, + /// e.g. because a previous API call threw an exception that hasn't been + /// caught yet. In that case, a "Nothing" value is returned. + template + class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); + }; + + template + inline Maybe Nothing(); + + template + inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + template + using MaybeOrValue = Maybe; +#else + template + using MaybeOrValue = T; +#endif + /// Environment for Node-API values and operations. /// /// All Node-API values and operations must be associated with an environment. @@ -201,9 +288,9 @@ namespace Napi { bool IsExceptionPending() const; Error GetAndClearPendingException(); - Value RunScript(const char* utf8script); - Value RunScript(const std::string& utf8script); - Value RunScript(String script); + MaybeOrValue RunScript(const char* utf8script); + MaybeOrValue RunScript(const std::string& utf8script); + MaybeOrValue RunScript(String script); #if NAPI_VERSION > 2 template @@ -344,12 +431,16 @@ namespace Napi { /// value type will throw `Napi::Error`. template T As() const; - Boolean ToBoolean() const; ///< Coerces a value to a JavaScript boolean. - Number ToNumber() const; ///< Coerces a value to a JavaScript number. - String ToString() const; ///< Coerces a value to a JavaScript string. - Object ToObject() const; ///< Coerces a value to a JavaScript object. + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. - protected: + protected: /// !cond INTERNAL napi_env _env; napi_value _value; @@ -569,19 +660,20 @@ namespace Napi { ); /// Get a public Symbol (e.g. Symbol.iterator). - static Symbol WellKnown(napi_env, const std::string& name); + static MaybeOrValue WellKnown(napi_env, const std::string& name); // Create a symbol in the global registry, UTF-8 Encoded cpp string - static Symbol For(napi_env env, const std::string& description); + static MaybeOrValue For(napi_env env, + const std::string& description); // Create a symbol in the global registry, C style string (null terminated) - static Symbol For(napi_env env, const char* description); + static MaybeOrValue For(napi_env env, const char* description); // Create a symbol in the global registry, String value describing the symbol - static Symbol For(napi_env env, String description); + static MaybeOrValue For(napi_env env, String description); // Create a symbol in the global registry, napi_value describing the symbol - static Symbol For(napi_env env, napi_value description); + static MaybeOrValue For(napi_env env, napi_value description); Symbol(); ///< Creates a new _empty_ Symbol instance. Symbol(napi_env env, @@ -591,16 +683,22 @@ namespace Napi { /// A JavaScript object value. class Object : public Value { public: - /// Enables property and element assignments using indexing syntax. - /// - /// Example: - /// - /// Napi::Value propertyValue = object1['A']; - /// object2['A'] = propertyValue; - /// Napi::Value elementValue = array[0]; - /// array[1] = elementValue; - template - class PropertyLValue { + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { public: /// Converts an L-value to a value. operator Value() const; @@ -618,7 +716,7 @@ namespace Napi { Key _key; friend class Napi::Object; - }; + }; /// Creates a new Object value. static Object New(napi_env env ///< Node-API environment @@ -644,161 +742,169 @@ namespace Napi { ); /// Gets a named property. - Value operator []( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Gets a named property. - Value operator []( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Gets an indexed property or array element. - Value operator []( - uint32_t index ///< Property / element index + MaybeOrValue operator[](uint32_t index ///< Property / element index ) const; /// Checks whether a property is present. - bool Has( - napi_value key ///< Property key primitive + MaybeOrValue Has(napi_value key ///< Property key primitive ) const; /// Checks whether a property is present. - bool Has( - Value key ///< Property key + MaybeOrValue Has(Value key ///< Property key ) const; /// Checks whether a named property is present. - bool Has( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Checks whether a named property is present. - bool Has( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - napi_value key ///< Property key primitive + MaybeOrValue HasOwnProperty( + napi_value key ///< Property key primitive ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - Value key ///< Property key + MaybeOrValue HasOwnProperty(Value key ///< Property key ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Gets a property. - Value Get( - napi_value key ///< Property key primitive + MaybeOrValue Get(napi_value key ///< Property key primitive ) const; /// Gets a property. - Value Get( - Value key ///< Property key + MaybeOrValue Get(Value key ///< Property key ) const; /// Gets a named property. - Value Get( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Gets a named property. - Value Get( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Sets a property. template - bool Set(napi_value key, ///< Property key primitive - const ValueType& value ///< Property value primitive + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive ); /// Sets a property. template - bool Set(Value key, ///< Property key - const ValueType& value ///< Property value + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value ); /// Sets a named property. template - bool Set( + MaybeOrValue Set( const char* utf8name, ///< UTF-8 encoded null-terminated property name const ValueType& value); /// Sets a named property. template - bool Set(const std::string& utf8name, ///< UTF-8 encoded property name - const ValueType& value ///< Property value primitive + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive ); /// Delete property. - bool Delete( - napi_value key ///< Property key primitive + MaybeOrValue Delete(napi_value key ///< Property key primitive ); /// Delete property. - bool Delete( - Value key ///< Property key + MaybeOrValue Delete(Value key ///< Property key ); /// Delete property. - bool Delete( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name ); /// Delete property. - bool Delete( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name ); /// Checks whether an indexed property is present. - bool Has( - uint32_t index ///< Property / element index + MaybeOrValue Has(uint32_t index ///< Property / element index ) const; /// Gets an indexed property or array element. - Value Get( - uint32_t index ///< Property / element index + MaybeOrValue Get(uint32_t index ///< Property / element index ) const; /// Sets an indexed property or array element. template - bool Set(uint32_t index, ///< Property / element index - const ValueType& value ///< Property value primitive + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive ); /// Deletes an indexed property or array element. - bool Delete( - uint32_t index ///< Property / element index + MaybeOrValue Delete(uint32_t index ///< Property / element index ); - Array GetPropertyNames() const; ///< Get all property names + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names /// Defines a property on the object. - bool DefineProperty( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( const PropertyDescriptor& property ///< Descriptor for the property to be defined ); /// Defines properties on the object. - bool DefineProperties( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( const std::initializer_list& properties ///< List of descriptors for the properties to be defined ); /// Defines properties on the object. - bool DefineProperties( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( const std::vector& properties ///< Vector of descriptors for the properties to be defined ); @@ -806,8 +912,13 @@ namespace Napi { /// Checks if an object is an instance created by a constructor function. /// /// This is equivalent to the JavaScript `instanceof` operator. - bool InstanceOf( - const Function& constructor ///< Constructor function + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function ) const; template @@ -818,8 +929,16 @@ namespace Napi { T* data, Hint* finalizeHint); #if NAPI_VERSION >= 8 - bool Freeze(); - bool Seal(); + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze(); + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal(); #endif // NAPI_VERSION >= 8 }; @@ -1153,30 +1272,37 @@ namespace Napi { Function(); Function(napi_env env, napi_value value); - Value operator()(const std::initializer_list& args) const; - - Value Call(const std::initializer_list& args) const; - Value Call(const std::vector& args) const; - Value Call(size_t argc, const napi_value* args) const; - Value Call(napi_value recv, - const std::initializer_list& args) const; - Value Call(napi_value recv, const std::vector& args) const; - Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; - Object New(size_t argc, const napi_value* args) const; + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New( + const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; }; class Promise : public Object { @@ -1300,26 +1426,26 @@ namespace Napi { ObjectReference& operator =(ObjectReference&& other); NAPI_DISALLOW_ASSIGN(ObjectReference) - Napi::Value Get(const char* utf8name) const; - Napi::Value Get(const std::string& utf8name) const; - bool Set(const char* utf8name, napi_value value); - bool Set(const char* utf8name, Napi::Value value); - bool Set(const char* utf8name, const char* utf8value); - bool Set(const char* utf8name, bool boolValue); - bool Set(const char* utf8name, double numberValue); - bool Set(const std::string& utf8name, napi_value value); - bool Set(const std::string& utf8name, Napi::Value value); - bool Set(const std::string& utf8name, std::string& utf8value); - bool Set(const std::string& utf8name, bool boolValue); - bool Set(const std::string& utf8name, double numberValue); - - Napi::Value Get(uint32_t index) const; - bool Set(uint32_t index, const napi_value value); - bool Set(uint32_t index, const Napi::Value value); - bool Set(uint32_t index, const char* utf8value); - bool Set(uint32_t index, const std::string& utf8value); - bool Set(uint32_t index, bool boolValue); - bool Set(uint32_t index, double numberValue); + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value); + MaybeOrValue Set(const char* utf8name, Napi::Value value); + MaybeOrValue Set(const char* utf8name, const char* utf8value); + MaybeOrValue Set(const char* utf8name, bool boolValue); + MaybeOrValue Set(const char* utf8name, double numberValue); + MaybeOrValue Set(const std::string& utf8name, napi_value value); + MaybeOrValue Set(const std::string& utf8name, Napi::Value value); + MaybeOrValue Set(const std::string& utf8name, std::string& utf8value); + MaybeOrValue Set(const std::string& utf8name, bool boolValue); + MaybeOrValue Set(const std::string& utf8name, double numberValue); + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value); + MaybeOrValue Set(uint32_t index, const Napi::Value value); + MaybeOrValue Set(uint32_t index, const char* utf8value); + MaybeOrValue Set(uint32_t index, const std::string& utf8value); + MaybeOrValue Set(uint32_t index, bool boolValue); + MaybeOrValue Set(uint32_t index, double numberValue); protected: ObjectReference(const ObjectReference&); @@ -1337,27 +1463,37 @@ namespace Napi { FunctionReference& operator =(FunctionReference&& other); NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) - Napi::Value operator ()(const std::initializer_list& args) const; - - Napi::Value Call(const std::initializer_list& args) const; - Napi::Value Call(const std::vector& args) const; - Napi::Value Call(napi_value recv, const std::initializer_list& args) const; - Napi::Value Call(napi_value recv, const std::vector& args) const; - Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Napi::Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New( + const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; }; // Shortcuts to creating a new reference with inferred type and refcount = 0. diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..7ea20d765 --- /dev/null +++ b/test/README.md @@ -0,0 +1,91 @@ +# Writing Tests + +There are multiple flavors of node-addon-api test builds that cover different +build flags defined in `napi.h`: + +1. c++ exceptions enabled, +2. c++ exceptions disabled, +3. c++ exceptions disabled, and `NODE_ADDON_API_ENABLE_MAYBE` defined. + +Functions in node-addon-api that call into JavaScript can have different +declared return types to reflect build flavor settings. For example, +`Napi::Object::Set` returns `bool` when `NODE_ADDON_API_ENABLE_MAYBE` +is not defined, and `Napi::Maybe` when `NODE_ADDON_API_ENABLE_MAYBE` +is defined. In source code, return type variants are defined as +`Napi::MaybeOrValue<>` to prevent the duplication of most of the code base. + +To properly test these build flavors, all values returned by a function defined +to return `Napi::MaybeOrValue<>` should be tested by using one of the following +test helpers to handle possible JavaScript exceptions. + +There are three test helper functions to conveniently convert +`Napi::MaybeOrValue<>` values to raw values. + +## MaybeUnwrap + +```cpp +template +T MaybeUnwrap(MaybeOrValue maybe); +``` + +Converts `MaybeOrValue` to `T` by checking that `MaybeOrValue` is NOT an +empty `Maybe`. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Object obj = info[0].As(); +// we are sure the parameters should not throw +Value value = MaybeUnwrap(obj->Get("foobar")); +``` + +## MaybeUnwrapOr + +```cpp +template +T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value = T()); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +`Maybe` or return the `default_value` if the `Maybe` is empty. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Value CallWithArgs(const CallbackInfo& info) { + Function func = info[0].As(); + // We don't care if the operation is throwing or not, just return it back to node-addon-api + return MaybeUnwrapOr( + func.Call(std::initializer_list{info[1], info[2], info[3]})); +} +``` + +## MaybeUnwrapTo + +```cpp +template +bool MaybeUnwrapTo(MaybeOrValue maybe, T* out); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +e`Maybe` or return `false` if the Maybe is empty + +Copies the `value` to `out` when `NODE_ADDON_API_ENABLE_MAYBE` is not defined + +Example: + +```cpp +Object opts = info[0].As(); +bool hasProperty = false; +// The check may throw, but we are going to suppress that. +if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); +} else { + env.GetAndClearPendingException(); +} +``` diff --git a/test/addon_data.cc b/test/addon_data.cc index d160a5946..bcbfc4eee 100644 --- a/test/addon_data.cc +++ b/test/addon_data.cc @@ -1,6 +1,7 @@ #if (NAPI_VERSION > 5) #include #include "napi.h" +#include "test_helper.h" // An overly elaborate way to get/set a boolean stored in the instance data: // 0. A boolean named "verbose" is stored in the instance data. The constructor @@ -42,7 +43,8 @@ class Addon { }; static Napi::Value Getter(const Napi::CallbackInfo& info) { - return info.Env().GetInstanceData()->VerboseIndicator.New({}); + return MaybeUnwrap( + info.Env().GetInstanceData()->VerboseIndicator.New({})); } static void Setter(const Napi::CallbackInfo& info) { diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index d18f8f30f..59de71202 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -75,19 +76,19 @@ static Value IsExternal(const CallbackInfo& info) { } static Value ToBoolean(const CallbackInfo& info) { - return info[0].ToBoolean(); + return MaybeUnwrap(info[0].ToBoolean()); } static Value ToNumber(const CallbackInfo& info) { - return info[0].ToNumber(); + return MaybeUnwrap(info[0].ToNumber()); } static Value ToString(const CallbackInfo& info) { - return info[0].ToString(); + return MaybeUnwrap(info[0].ToString()); } static Value ToObject(const CallbackInfo& info) { - return info[0].ToObject(); + return MaybeUnwrap(info[0].ToObject()); } Object InitBasicTypesValue(Env env) { diff --git a/test/bigint.cc b/test/bigint.cc index 1f89db84a..ab62d09f9 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -3,6 +3,8 @@ #define NAPI_EXPERIMENTAL #include "napi.h" +#include "test_helper.h" + using namespace Napi; namespace { @@ -11,7 +13,7 @@ Value IsLossless(const CallbackInfo& info) { Env env = info.Env(); BigInt big = info[0].As(); - bool is_signed = info[1].ToBoolean().Value(); + bool is_signed = MaybeUnwrap(info[1].ToBoolean()).Value(); bool lossless; if (is_signed) { diff --git a/test/binding.cc b/test/binding.cc index 267749394..389f61b97 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -74,6 +74,10 @@ Object InitThunkingManual(Env env); Object InitObjectFreezeSeal(Env env); #endif +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +Object InitMaybeCheck(Env env); +#endif + Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) exports.Set("addon", InitAddon(env)); @@ -155,6 +159,10 @@ Object Init(Env env, Object exports) { #if (NAPI_VERSION > 7) exports.Set("object_freeze_seal", InitObjectFreezeSeal(env)); #endif + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + exports.Set("maybe_check", InitMaybeCheck(env)); +#endif return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index f30d081d7..e45711af0 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,6 +1,7 @@ { 'target_defaults': { 'includes': ['../common.gypi'], + 'include_dirs': ['./common'], 'variables': { 'build_sources': [ 'addon.cc', @@ -28,6 +29,7 @@ 'function.cc', 'functionreference.cc', 'handlescope.cc', + 'maybe/check.cc', 'movable_callbacks.cc', 'memory_management.cc', 'name.cc', @@ -92,6 +94,12 @@ 'includes': ['../noexcept.gypi'], 'sources': ['>@(build_sources)'] }, + { + 'target_name': 'binding_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, { 'target_name': 'binding_swallowexcept', 'includes': ['../except.gypi'], diff --git a/test/common/index.js b/test/common/index.js index e8c480cab..2863925d8 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -81,6 +81,7 @@ exports.runTest = async function(test, buildType) { const bindings = [ `../build/${buildType}/binding.node`, `../build/${buildType}/binding_noexcept.node`, + `../build/${buildType}/binding_noexcept_maybe.node`, ].map(it => require.resolve(it)); for (const item of bindings) { @@ -95,6 +96,7 @@ exports.runTestWithBindingPath = async function(test, buildType) { const bindings = [ `../build/${buildType}/binding.node`, `../build/${buildType}/binding_noexcept.node`, + `../build/${buildType}/binding_noexcept_maybe.node`, ].map(it => require.resolve(it)); for (const item of bindings) { diff --git a/test/common/test_helper.h b/test/common/test_helper.h new file mode 100644 index 000000000..1b321a19e --- /dev/null +++ b/test/common/test_helper.h @@ -0,0 +1,61 @@ +#pragma once +#include "napi.h" + +namespace Napi { + +// Use this when a variable or parameter is unused in order to explicitly +// silence a compiler warning about that. +template +inline void USE(T&&) {} + +/** + * A test helper that converts MaybeOrValue to T by checking that + * MaybeOrValue is NOT an empty Maybe when NODE_ADDON_API_ENABLE_MAYBE is + * defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrap(MaybeOrValue maybe) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.Unwrap(); +#else + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return the default_value if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapOr(default_value); +#else + USE(default_value); + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return false if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Copying the value to out when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline bool MaybeUnwrapTo(MaybeOrValue maybe, T* out) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapTo(out); +#else + *out = maybe; + return true; +#endif +} + +} // namespace Napi diff --git a/test/function.cc b/test/function.cc index b45e91dd1..0fab27290 100644 --- a/test/function.cc +++ b/test/function.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -53,8 +54,8 @@ Value ValueCallbackWithData(const CallbackInfo& info) { Value CallWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.Call( - std::initializer_list{info[1], info[2], info[3]}); + return MaybeUnwrap( + func.Call(std::initializer_list{info[1], info[2], info[3]})); } Value CallWithVector(const CallbackInfo& info) { @@ -64,7 +65,7 @@ Value CallWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.Call(args); + return MaybeUnwrap(func.Call(args)); } Value CallWithCStyleArray(const CallbackInfo& info) { @@ -74,7 +75,7 @@ Value CallWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.Call(args.size(), args.data()); + return MaybeUnwrap(func.Call(args.size(), args.data())); } Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { @@ -85,13 +86,14 @@ Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return func.Call(receiver, args.size(), args.data()); + return MaybeUnwrap(func.Call(receiver, args.size(), args.data())); } Value CallWithReceiverAndArgs(const CallbackInfo& info) { Function func = info[0].As(); Value receiver = info[1]; - return func.Call(receiver, std::initializer_list{ info[2], info[3], info[4] }); + return MaybeUnwrap(func.Call( + receiver, std::initializer_list{info[2], info[3], info[4]})); } Value CallWithReceiverAndVector(const CallbackInfo& info) { @@ -102,17 +104,19 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return func.Call(receiver, args); + return MaybeUnwrap(func.Call(receiver, args)); } Value CallWithInvalidReceiver(const CallbackInfo& info) { Function func = info[0].As(); - return func.Call(Value(), std::initializer_list{}); + return MaybeUnwrapOr(func.Call(Value(), std::initializer_list{}), + Value()); } Value CallConstructorWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.New(std::initializer_list{ info[1], info[2], info[3] }); + return MaybeUnwrap( + func.New(std::initializer_list{info[1], info[2], info[3]})); } Value CallConstructorWithVector(const CallbackInfo& info) { @@ -122,7 +126,7 @@ Value CallConstructorWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.New(args); + return MaybeUnwrap(func.New(args)); } Value CallConstructorWithCStyleArray(const CallbackInfo& info) { @@ -132,7 +136,7 @@ Value CallConstructorWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.New(args.size(), args.data()); + return MaybeUnwrap(func.New(args.size(), args.data())); } void IsConstructCall(const CallbackInfo& info) { @@ -191,7 +195,7 @@ void MakeCallbackWithInvalidReceiver(const CallbackInfo& info) { Value CallWithFunctionOperator(const CallbackInfo& info) { Function func = info[0].As(); - return func({info[1], info[2], info[3]}); + return MaybeUnwrap(func({info[1], info[2], info[3]})); } } // end anonymous namespace diff --git a/test/functionreference.cc b/test/functionreference.cc index 44dec3ce7..aaa899e11 100644 --- a/test/functionreference.cc +++ b/test/functionreference.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -8,7 +9,7 @@ Value Call(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return ref.Call({}); + return MaybeUnwrapOr(ref.Call({}), Value()); } Value Construct(const CallbackInfo& info) { @@ -16,7 +17,7 @@ Value Construct(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return ref.New({}); + return MaybeUnwrapOr(ref.New({}), Object()); } } // namespace diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc index 8064e5864..295ed3f36 100644 --- a/test/globalObject/global_object_delete_property.cc +++ b/test/globalObject/global_object_delete_property.cc @@ -1,27 +1,31 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value().c_str())); + return Boolean::New( + info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value().c_str()))); } Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Utf8Value()))); } Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Number key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Uint32Value())); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key)); -} \ No newline at end of file + return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key))); +} diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc index bd402abf2..dd112043c 100644 --- a/test/globalObject/global_object_get_property.cc +++ b/test/globalObject/global_object_get_property.cc @@ -1,29 +1,30 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return globalObject.Get(key); + return MaybeUnwrap(globalObject.Get(key)); } Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Number key = info[0].As(); - return globalObject.Get(key.Uint32Value()); + return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); } Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String cStrkey = info[0].As(); - return globalObject.Get(cStrkey.Utf8Value().c_str()); + return MaybeUnwrapOr(globalObject.Get(cStrkey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String cppStrKey = info[0].As(); - return globalObject.Get(cppStrKey.Utf8Value()); + return MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); } void CreateMockTestObject(const CallbackInfo& info) { diff --git a/test/globalObject/global_object_has_own_property.cc b/test/globalObject/global_object_has_own_property.cc index 1dfb4e281..89c299913 100644 --- a/test/globalObject/global_object_has_own_property.cc +++ b/test/globalObject/global_object_has_own_property.cc @@ -1,22 +1,28 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), - globalObject.HasOwnProperty(key.Utf8Value().c_str())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()), + false)); } Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.HasOwnProperty(key.Utf8Value())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()), false)); } Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return Boolean::New(info.Env(), globalObject.HasOwnProperty(key)); -} \ No newline at end of file + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.HasOwnProperty(key))); +} diff --git a/test/maybe/check.cc b/test/maybe/check.cc new file mode 100644 index 000000000..d1e2261ed --- /dev/null +++ b/test/maybe/check.cc @@ -0,0 +1,23 @@ +#include "napi.h" +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + +using namespace Napi; + +namespace { + +void VoidCallback(const CallbackInfo& info) { + Function fn = info[0].As(); + + Maybe it = fn.Call({}); + + it.Check(); +} + +} // end anonymous namespace + +Object InitMaybeCheck(Env env) { + Object exports = Object::New(env); + exports.Set("voidCallback", Function::New(env, VoidCallback)); + return exports; +} +#endif diff --git a/test/maybe/index.js b/test/maybe/index.js new file mode 100644 index 000000000..ad562c9c9 --- /dev/null +++ b/test/maybe/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const os = require('os'); + +const napiChild = require('../napi_child'); + +module.exports = test(require(`../build/${buildType}/binding_noexcept_maybe.node`).maybe_check); + +function test(binding) { + if (process.argv.includes('child')) { + child(binding); + return; + } + const cp = napiChild.spawn(process.execPath, [__filename, 'child'], { + stdio: ['ignore', 'inherit', 'pipe'], + }); + cp.stderr.setEncoding('utf8'); + let stderr = ''; + cp.stderr.on('data', chunk => { + stderr += chunk; + }); + cp.on('exit', (code, signal) => { + if (process.platform === 'win32') { + assert.strictEqual(code, 128 + 6 /* SIGABRT */); + } else { + assert.strictEqual(signal, 'SIGABRT'); + } + assert.ok(stderr.match(/FATAL ERROR: Napi::Maybe::Check Maybe value is Nothing./)); + }); +} + +function child(binding) { + binding.voidCallback(() => { + throw new Error('foobar'); + }) +} diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index 80caa7e31..ca69e5387 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -1,33 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value DeletePropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(key.Uint32Value())); + return Boolean::New(info.Env(), MaybeUnwrap(obj.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Delete(static_cast(key)), false)); } Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(key), false)); } Value DeletePropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value().c_str()), false)); } Value DeletePropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value()), false)); } diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 8069da465..523f99199 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -1,33 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value GetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return obj.Get(static_cast(key)); + return MaybeUnwrapOr(obj.Get(static_cast(key)), Value()); } Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return obj.Get(key); + return MaybeUnwrapOr(obj.Get(key), Value()); } Value GetPropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return obj.Get(key.Uint32Value()); + return MaybeUnwrap(obj.Get(key.Uint32Value())); } Value GetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value().c_str()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value()), Value()); } diff --git a/test/object/has_own_property.cc b/test/object/has_own_property.cc index 7351be2c0..d7fbde98b 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -1,27 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(static_cast(key)), false)); } Value HasOwnPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(key)); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(key), false)); } Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value().c_str()), false)); } Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value()), false)); } diff --git a/test/object/has_property.cc b/test/object/has_property.cc index 669935cc1..fa410833f 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -1,33 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(static_cast(key))); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Has(static_cast(key)), false)); } Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(key), false)); } Value HasPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value().c_str())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()), false)); } Value HasPropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Uint32Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Uint32Value()), false)); } Value HasPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value()), false)); } diff --git a/test/object/object.cc b/test/object/object.cc index 58c0392d8..a73b8e354 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -95,7 +96,7 @@ Value ConstructorFromObject(const CallbackInfo& info) { Array GetPropertyNames(const CallbackInfo& info) { Object obj = info[0].As(); - Array arr = obj.GetPropertyNames(); + Array arr = MaybeUnwrap(obj.GetPropertyNames()); return arr; } @@ -255,7 +256,7 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) { Value InstanceOf(const CallbackInfo& info) { Object obj = info[0].As(); Function constructor = info[1].As(); - return Boolean::New(info.Env(), obj.InstanceOf(constructor)); + return Boolean::New(info.Env(), MaybeUnwrap(obj.InstanceOf(constructor))); } Object InitObject(Env env) { diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc index 51071a1c6..40aaeb467 100644 --- a/test/object/object_freeze_seal.cc +++ b/test/object/object_freeze_seal.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 7) @@ -6,12 +7,12 @@ using namespace Napi; Value Freeze(const CallbackInfo& info) { Object obj = info[0].As(); - return Boolean::New(info.Env(), obj.Freeze()); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Freeze(), false)); } Value Seal(const CallbackInfo& info) { Object obj = info[0].As(); - return Boolean::New(info.Env(), obj.Seal()); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Seal(), false)); } Object InitObjectFreezeSeal(Env env) { diff --git a/test/object/set_property.cc b/test/object/set_property.cc index c17366544..5f10b4f09 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,26 +7,31 @@ Value SetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(static_cast(key), value)); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(static_cast(key), value), false)); } Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(key, value)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Set(key, value), false)); } Value SetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value().c_str(), value)); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value().c_str(), value), false)); } Value SetPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value(), value)); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); } diff --git a/test/objectreference.cc b/test/objectreference.cc index 3143216dc..34f952088 100644 --- a/test/objectreference.cc +++ b/test/objectreference.cc @@ -4,6 +4,7 @@ it. Subclasses of Objects can only be set using an ObjectReference by first casting it as an Object. */ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -97,22 +98,22 @@ Value GetFromGetter(const CallbackInfo& info) { return String::New(env, "No Referenced Value"); } else { if (info[1].IsString()) { - return weak.Get(info[1].As().Utf8Value()); + return MaybeUnwrap(weak.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return weak.Get(info[1].As().Uint32Value()); + return MaybeUnwrap(weak.Get(info[1].As().Uint32Value())); } } } else if (info[0].As() == String::New(env, "persistent")) { if (info[1].IsString()) { - return persistent.Get(info[1].As().Utf8Value()); + return MaybeUnwrap(persistent.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return persistent.Get(info[1].As().Uint32Value()); + return MaybeUnwrap(persistent.Get(info[1].As().Uint32Value())); } } else { if (info[0].IsString()) { - return reference.Get(info[0].As().Utf8Value()); + return MaybeUnwrap(reference.Get(info[0].As().Utf8Value())); } else if (info[0].IsNumber()) { - return reference.Get(info[0].As().Uint32Value()); + return MaybeUnwrap(reference.Get(info[0].As().Uint32Value())); } } @@ -147,12 +148,12 @@ Value GetCastedFromGetter(const CallbackInfo& info) { if (casted_weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { - return casted_weak.Get(info[1].As()); + return MaybeUnwrap(casted_weak.Get(info[1].As())); } } else if (info[0].As() == String::New(env, "persistent")) { - return casted_persistent.Get(info[1].As()); + return MaybeUnwrap(casted_persistent.Get(info[1].As())); } else { - return casted_reference.Get(info[1].As()); + return MaybeUnwrap(casted_reference.Get(info[1].As())); } } diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 2ffc85a25..65727a3df 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -1,9 +1,10 @@ #include +#include "test_helper.h" Napi::ObjectReference testStaticContextRef; Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { - return testStaticContextRef.Value().Get("value"); + return MaybeUnwrap(testStaticContextRef.Value().Get("value")); } void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { @@ -11,12 +12,12 @@ void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) } Napi::Value TestStaticMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static"); } Napi::Value TestStaticMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static internal"); } @@ -55,7 +56,7 @@ class Test : public Napi::ObjectWrap { } void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { - value_ = value.ToString(); + value_ = MaybeUnwrap(value.ToString()); } Napi::Value Getter(const Napi::CallbackInfo& info) { @@ -63,12 +64,12 @@ class Test : public Napi::ObjectWrap { } Napi::Value TestMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance"); } Napi::Value TestMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance internal"); } @@ -80,11 +81,15 @@ class Test : public Napi::ObjectWrap { Napi::Value Iterator(const Napi::CallbackInfo& info) { Napi::Array array = Napi::Array::New(info.Env()); array.Set(array.Length(), Napi::String::From(info.Env(), value_)); - return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); + return MaybeUnwrap( + MaybeUnwrap(array.Get(MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), "iterator")))) + .As() + .Call(array, {})); } void TestVoidMethodT(const Napi::CallbackInfo &info) { - value_ = info[0].ToString(); + value_ = MaybeUnwrap(info[0].ToString()); } Napi::Value TestMethodT(const Napi::CallbackInfo &info) { @@ -96,7 +101,7 @@ class Test : public Napi::ObjectWrap { } static void TestStaticVoidMethodT(const Napi::CallbackInfo& info) { - s_staticMethodText = info[0].ToString(); + s_staticMethodText = MaybeUnwrap(info[0].ToString()); } static void Initialize(Napi::Env env, Napi::Object exports) { @@ -115,64 +120,120 @@ class Test : public Napi::ObjectWrap { Napi::Symbol kTestMethodTInternal = Napi::Symbol::New(env, "kTestMethodTInternal"); Napi::Symbol kTestVoidMethodTInternal = Napi::Symbol::New(env, "kTestVoidMethodTInternal"); - exports.Set("Test", DefineClass(env, "Test", { - - // expose symbols for testing - StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), - StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), - StaticValue("kTestStaticAccessorTInternal", kTestStaticAccessorTInternal), - StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), - StaticValue("kTestStaticMethodTInternal", kTestStaticMethodTInternal), - StaticValue("kTestStaticVoidMethodTInternal", kTestStaticVoidMethodTInternal), - StaticValue("kTestValueInternal", kTestValueInternal), - StaticValue("kTestAccessorInternal", kTestAccessorInternal), - StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), - StaticValue("kTestMethodInternal", kTestMethodInternal), - StaticValue("kTestMethodTInternal", kTestMethodTInternal), - StaticValue("kTestVoidMethodTInternal", kTestVoidMethodTInternal), - - // test data - StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), - StaticValue(kTestStaticValueInternal, Napi::Number::New(env, 5), napi_default), - - StaticAccessor("testStaticGetter", &StaticGetter, nullptr, napi_enumerable), - StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), - StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor<&StaticGetter>("testStaticGetterT"), - StaticAccessor<&StaticGetter, &StaticSetter>("testStaticGetSetT"), - StaticAccessor<&StaticGetter, &StaticSetter>(kTestStaticAccessorTInternal), - - StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), - StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), - StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), - StaticMethod<&TestStaticMethodT>("testStaticMethodT"), - StaticMethod<&TestStaticVoidMethodT>(kTestStaticVoidMethodTInternal), - StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), - - InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), - InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), - - InstanceAccessor("testGetter", &Test::Getter, nullptr, napi_enumerable), - InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), - InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor<&Test::Getter>("testGetterT"), - InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), - InstanceAccessor<&Test::Getter, &Test::Setter>(kTestAccessorInternal), - - InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), - InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), - InstanceMethod<&Test::TestMethodT>("testMethodT"), - InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), - InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), - InstanceMethod<&Test::TestVoidMethodT>(kTestVoidMethodTInternal), - - // conventions - InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), - InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iterator, napi_default), - - })); + exports.Set( + "Test", + DefineClass( + env, + "Test", + { + + // expose symbols for testing + StaticValue("kTestStaticValueInternal", + kTestStaticValueInternal), + StaticValue("kTestStaticAccessorInternal", + kTestStaticAccessorInternal), + StaticValue("kTestStaticAccessorTInternal", + kTestStaticAccessorTInternal), + StaticValue("kTestStaticMethodInternal", + kTestStaticMethodInternal), + StaticValue("kTestStaticMethodTInternal", + kTestStaticMethodTInternal), + StaticValue("kTestStaticVoidMethodTInternal", + kTestStaticVoidMethodTInternal), + StaticValue("kTestValueInternal", kTestValueInternal), + StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), + StaticValue("kTestMethodInternal", kTestMethodInternal), + StaticValue("kTestMethodTInternal", kTestMethodTInternal), + StaticValue("kTestVoidMethodTInternal", + kTestVoidMethodTInternal), + + // test data + StaticValue("testStaticValue", + Napi::String::New(env, "value"), + napi_enumerable), + StaticValue(kTestStaticValueInternal, + Napi::Number::New(env, 5), + napi_default), + + StaticAccessor("testStaticGetter", + &StaticGetter, + nullptr, + napi_enumerable), + StaticAccessor( + "testStaticSetter", nullptr, &StaticSetter, napi_default), + StaticAccessor("testStaticGetSet", + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor(kTestStaticAccessorInternal, + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor<&StaticGetter>("testStaticGetterT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + "testStaticGetSetT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + kTestStaticAccessorTInternal), + + StaticMethod( + "testStaticMethod", &TestStaticMethod, napi_enumerable), + StaticMethod(kTestStaticMethodInternal, + &TestStaticMethodInternal, + napi_default), + StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), + StaticMethod<&TestStaticMethodT>("testStaticMethodT"), + StaticMethod<&TestStaticVoidMethodT>( + kTestStaticVoidMethodTInternal), + StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), + + InstanceValue("testValue", + Napi::Boolean::New(env, true), + napi_enumerable), + InstanceValue(kTestValueInternal, + Napi::Boolean::New(env, false), + napi_enumerable), + + InstanceAccessor( + "testGetter", &Test::Getter, nullptr, napi_enumerable), + InstanceAccessor( + "testSetter", nullptr, &Test::Setter, napi_default), + InstanceAccessor("testGetSet", + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor(kTestAccessorInternal, + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor<&Test::Getter>("testGetterT"), + InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), + InstanceAccessor<&Test::Getter, &Test::Setter>( + kTestAccessorInternal), + + InstanceMethod( + "testMethod", &Test::TestMethod, napi_enumerable), + InstanceMethod(kTestMethodInternal, + &Test::TestMethodInternal, + napi_default), + InstanceMethod<&Test::TestMethodT>("testMethodT"), + InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), + InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), + InstanceMethod<&Test::TestVoidMethodT>( + kTestVoidMethodTInternal), + + // conventions + InstanceAccessor( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "toStringTag")), + &Test::ToStringTag, + nullptr, + napi_enumerable), + InstanceMethod( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "iterator")), + &Test::Iterator, + napi_default), + + })); } void Finalize(Napi::Env env) { diff --git a/test/run_script.cc b/test/run_script.cc index af47ae1f7..507164cad 100644 --- a/test/run_script.cc +++ b/test/run_script.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,39 +7,39 @@ namespace { Value RunPlainString(const CallbackInfo& info) { Env env = info.Env(); - return env.RunScript("1 + 2 + 3"); + return MaybeUnwrap(env.RunScript("1 + 2 + 3")); } Value RunStdString(const CallbackInfo& info) { Env env = info.Env(); std::string str = "1 + 2 + 3"; - return env.RunScript(str); + return MaybeUnwrap(env.RunScript(str)); } Value RunJsString(const CallbackInfo& info) { Env env = info.Env(); - return env.RunScript(info[0].As()); + return MaybeUnwrapOr(env.RunScript(info[0].As()), Value()); } Value RunWithContext(const CallbackInfo& info) { Env env = info.Env(); - Array keys = info[1].As().GetPropertyNames(); + Array keys = MaybeUnwrap(info[1].As().GetPropertyNames()); std::string code = "("; for (unsigned int i = 0; i < keys.Length(); i++) { if (i != 0) code += ","; - code += keys.Get(i).As().Utf8Value(); + code += MaybeUnwrap(keys.Get(i)).As().Utf8Value(); } code += ") => " + info[0].As().Utf8Value(); - Value ret = env.RunScript(code); + Value ret = MaybeUnwrap(env.RunScript(code)); Function fn = ret.As(); std::vector args; for (unsigned int i = 0; i < keys.Length(); i++) { - Value key = keys.Get(i); - args.push_back(info[1].As().Get(key)); + Value key = MaybeUnwrap(keys.Get(i)); + args.push_back(MaybeUnwrap(info[1].As().Get(key))); } - return fn.Call(args); + return MaybeUnwrap(fn.Call(args)); } } // end anonymous namespace diff --git a/test/symbol.cc b/test/symbol.cc index 8fdebce6d..08ea80393 100644 --- a/test/symbol.cc +++ b/test/symbol.cc @@ -1,4 +1,5 @@ #include +#include "test_helper.h" using namespace Napi; Symbol CreateNewSymbolWithNoArgs(const Napi::CallbackInfo&) { @@ -22,33 +23,34 @@ Symbol CreateNewSymbolWithNapiString(const Napi::CallbackInfo& info) { Symbol GetWellknownSymbol(const Napi::CallbackInfo& info) { String registrySymbol = info[0].As(); - return Napi::Symbol::WellKnown(info.Env(), - registrySymbol.Utf8Value().c_str()); + return MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), registrySymbol.Utf8Value().c_str())); } Symbol FetchSymbolFromGlobalRegistry(const Napi::CallbackInfo& info) { String registrySymbol = info[0].As(); - return Napi::Symbol::For(info.Env(), registrySymbol); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), registrySymbol)); } Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); - return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value()); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value())); } Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); - return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str()); + return MaybeUnwrap( + Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str())); } Symbol TestUndefinedSymbolsCanBeCreated(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - return Napi::Symbol::For(env, env.Undefined()); + return MaybeUnwrap(Napi::Symbol::For(env, env.Undefined())); } Symbol TestNullSymbolsCanBeCreated(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - return Napi::Symbol::For(env, env.Null()); + return MaybeUnwrap(Napi::Symbol::For(env, env.Null())); } Object InitSymbol(Env env) { diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc index 19971b824..9307b22f1 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ -#include "napi.h" #include +#include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -59,11 +60,13 @@ static Value TestCall(const CallbackInfo &info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } diff --git a/test/threadsafe_function/threadsafe_function_unref.cc b/test/threadsafe_function/threadsafe_function_unref.cc index 6877e50f6..a54620c48 100644 --- a/test/threadsafe_function/threadsafe_function_unref.cc +++ b/test/threadsafe_function/threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -11,7 +12,7 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); ThreadSafeFunction* tsfn = new ThreadSafeFunction; *tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc index eccf87c93..daa273fcb 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ #include #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -64,11 +65,13 @@ static Value TestCall(const CallbackInfo& info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc index 35345568d..2d137328e 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -14,7 +15,7 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); TSFN* tsfn = new TSFN; *tsfn = TSFN::New( From ecd9eefbb6199f66e6c2685329ae7a9029eed7af Mon Sep 17 00:00:00 2001 From: NickNaso Date: Wed, 25 Aug 2021 13:37:39 +0200 Subject: [PATCH 45/57] Prepare release 4.1.0. --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ README.md | 2 +- package.json | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b1fde44f..06d8a1818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # node-addon-api Changelog +## 2021-08-25 Version 4.1.0, @NickNaso + +### Notable changes: + +#### API + +- `Napi::Reference` updated the default value to reflect the most possible +values when there are any errors occurred on `napi_reference_unref`. +- Added the check for nullpointer on `Napi::String` initialization. +- Added the wraps for `napi_add_env_cleanup_hook` and +`napi_remove_env_cleanup_hook`. +- Added `Napi::Maybe` class to handle pending exception when cpp exception +disabled. + +#### TEST + +- Added first set of tests for `Napi::Symbol`. +- Updated test suite to avoid parallel running. + +### Documentation + +- Updated example for context sensitivity. + +### Commits + +* [[`3615041423`](https://github.com/nodejs/node-addon-api/commit/3615041423)] - **src**: return Maybe on pending exception when cpp exception disabled (legendecas) [#927](https://github.com/nodejs/node-addon-api/pull/927) +* [[`10564a43c6`](https://github.com/nodejs/node-addon-api/commit/10564a43c6)] - **src**: add AddCleanupHook (Kevin Eady) [#1014](https://github.com/nodejs/node-addon-api/pull/1014) +* [[`a459f5cc8f`](https://github.com/nodejs/node-addon-api/commit/a459f5cc8f)] - **doc**: update tests to avoid running in parallel (Michael Dawson) [#1024](https://github.com/nodejs/node-addon-api/pull/1024) +* [[`6697c51d1d`](https://github.com/nodejs/node-addon-api/commit/6697c51d1d)] - **src,test**: fix up null char \* exception thrown (Gabriel Schulhof) [#1019](https://github.com/nodejs/node-addon-api/pull/1019) +* [[`e02e8a4ce3`](https://github.com/nodejs/node-addon-api/commit/e02e8a4ce3)] - **test**: add first set of symbol tests (JckXia) [#972](https://github.com/nodejs/node-addon-api/pull/972) +* [[`da50b51398`](https://github.com/nodejs/node-addon-api/commit/da50b51398)] - **test**: dd check for nullptr inside String init (JckXia) [#1015](https://github.com/nodejs/node-addon-api/pull/1015) +* [[`627dbf3c37`](https://github.com/nodejs/node-addon-api/commit/627dbf3c37)] - **doc**: update examples for context sensitivity (Kevin Eady) [#1013](https://github.com/nodejs/node-addon-api/pull/1013) +* [[`37a9b8e753`](https://github.com/nodejs/node-addon-api/commit/37a9b8e753)] - **src**: set default return value of Reference Ref/Unref to 0 (legendecas) [#1004](https://github.com/nodejs/node-addon-api/pull/1004) + ## 2021-06-15 Version 4.0.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index a1fabea39..2e0723d8b 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 4.0.0** +## **Current version: 4.1.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index ad61cc1dc..b93cc90f1 100644 --- a/package.json +++ b/package.json @@ -363,6 +363,6 @@ "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "4.0.0", + "version": "4.1.0", "support": true } From e9cd96a87f7826399150af3bdd8d0b391ed744be Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Fri, 3 Sep 2021 15:02:13 +0200 Subject: [PATCH 46/57] test: fixed the way to enable C++ exceptions. (#1061) PR-URL: https://github.com/nodejs/node-addon-api/pull/1061 Reviewed-By: Michael Dawson --- except.gypi | 31 ++++++++++++++++++++----------- noexcept.gypi | 32 +++++++++++++++++++++----------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/except.gypi b/except.gypi index 1f295d10a..e2fb2c5a4 100644 --- a/except.gypi +++ b/except.gypi @@ -2,15 +2,24 @@ 'defines': [ 'NAPI_CPP_EXCEPTIONS' ], 'cflags!': [ '-fno-exceptions' ], 'cflags_cc!': [ '-fno-exceptions' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'ExceptionHandling': 1, - 'EnablePREfast': 'true', - }, - }, - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', - }, + 'conditions': [ + ["OS=='win'", { + "defines": [ + "_HAS_EXCEPTIONS=1" + ], + "msvs_settings": { + "VCCLCompilerTool": { + "ExceptionHandling": 1, + 'EnablePREfast': 'true', + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + }, + }], + ], } diff --git a/noexcept.gypi b/noexcept.gypi index 179f21f79..404a05f30 100644 --- a/noexcept.gypi +++ b/noexcept.gypi @@ -2,15 +2,25 @@ 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], 'cflags': [ '-fno-exceptions' ], 'cflags_cc': [ '-fno-exceptions' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'ExceptionHandling': 0, - 'EnablePREfast': 'true', - }, - }, - 'xcode_settings': { - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', - }, + 'conditions': [ + ["OS=='win'", { + # _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi + #"defines": [ + # "_HAS_EXCEPTIONS=0" + #], + "msvs_settings": { + "VCCLCompilerTool": { + 'ExceptionHandling': 0, + 'EnablePREfast': 'true', + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', + }, + }], + ], } From 7b573b10030ef6cf086b1521d5713adb97c9580d Mon Sep 17 00:00:00 2001 From: Nicola Del Gobbo Date: Fri, 3 Sep 2021 15:03:48 +0200 Subject: [PATCH 47/57] doc: fix documentation about how to enable C++ exception (#1059) * doc: fixed doc about how to enable C++ exceptions. PR-URL: https://github.com/nodejs/node-addon-api/pull/1059 Reviewed-By: Michael Dawson --- doc/setup.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/doc/setup.md b/doc/setup.md index 5db3452ad..49e039c8f 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -39,14 +39,25 @@ To use **Node-API** in a native module: ```gyp 'cflags!': [ '-fno-exceptions' ], 'cflags_cc!': [ '-fno-exceptions' ], - 'xcode_settings': { - 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', - 'CLANG_CXX_LIBRARY': 'libc++', - 'MACOSX_DEPLOYMENT_TARGET': '10.7', - }, - 'msvs_settings': { - 'VCCLCompilerTool': { 'ExceptionHandling': 1 }, - }, + 'conditions': [ + ["OS=='win'", { + "defines": [ + "_HAS_EXCEPTIONS=1" + ], + "msvs_settings": { + "VCCLCompilerTool": { + "ExceptionHandling": 1 + }, + }, + }], + ["OS=='mac'", { + 'xcode_settings': { + 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', + 'CLANG_CXX_LIBRARY': 'libc++', + 'MACOSX_DEPLOYMENT_TARGET': '10.7', + }, + }], + ], ``` Alternatively, disable use of C++ exceptions in Node-API: From 309ff888f910bd5d63a8591fda88d340e25ba10c Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Fri, 3 Sep 2021 09:40:35 -0400 Subject: [PATCH 48/57] test: fix errors reported by newer compiler - fix error reported about possible use of un-initialized variable from newer compiler Signed-off-by: Michael Dawson --- test/bigint.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bigint.cc b/test/bigint.cc index ab62d09f9..300e5a498 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -46,7 +46,7 @@ Value TestWords(const CallbackInfo& info) { int sign_bit; size_t word_count = 10; - uint64_t words[10]; + uint64_t words[10] = {0}; big.ToWords(&sign_bit, &word_count, words); @@ -61,7 +61,7 @@ Value TestWords(const CallbackInfo& info) { Value TestTooBigBigInt(const CallbackInfo& info) { int sign_bit = 0; size_t word_count = SIZE_MAX; - uint64_t words[10]; + uint64_t words[10] = {0}; return BigInt::New(info.Env(), sign_bit, word_count, words); } From 88ef504a9563dbbafb6ae9d78fee3563e20154ff Mon Sep 17 00:00:00 2001 From: Deepak Rajamohan Date: Fri, 27 Aug 2021 07:28:09 -0700 Subject: [PATCH 49/57] test: standardize unit test file names - change all unit test file names to snake case this helps in parsing file names to tokens and infer metadata like export initializers - all other changes other than file names is due to linter errors PR-URL: https://github.com/nodejs/node-addon-api/pull/1056 Reviewed-By: Michael Dawson --- test/{arraybuffer.cc => array_buffer.cc} | 92 +++++++++++-------- test/{arraybuffer.js => array_buffer.js} | 0 test/{asynccontext.cc => async_context.cc} | 6 +- test/{asynccontext.js => async_context.js} | 0 ...rker.cc => async_progress_queue_worker.cc} | 20 ++-- ...rker.js => async_progress_queue_worker.js} | 0 ...ressworker.cc => async_progress_worker.cc} | 18 ++-- ...ressworker.js => async_progress_worker.js} | 0 test/{asyncworker.cc => async_worker.cc} | 52 ++++++----- test/{asyncworker.js => async_worker.js} | 0 ...callback.js => async_worker_nocallback.js} | 0 ...rsistent.cc => async_worker_persistent.cc} | 20 ++-- ...rsistent.js => async_worker_persistent.js} | 0 test/binding.gyp | 18 ++-- ...tionreference.cc => function_reference.cc} | 0 ...tionreference.js => function_reference.js} | 0 ...objectreference.cc => object_reference.cc} | 0 ...objectreference.js => object_reference.js} | 0 ...removewrap.cc => objectwrap_removewrap.cc} | 6 +- ...removewrap.js => objectwrap_removewrap.js} | 0 20 files changed, 124 insertions(+), 108 deletions(-) rename test/{arraybuffer.cc => array_buffer.cc} (62%) rename test/{arraybuffer.js => array_buffer.js} (100%) rename test/{asynccontext.cc => async_context.cc} (76%) rename test/{asynccontext.js => async_context.js} (100%) rename test/{asyncprogressqueueworker.cc => async_progress_queue_worker.cc} (79%) rename test/{asyncprogressqueueworker.js => async_progress_queue_worker.js} (100%) rename test/{asyncprogressworker.cc => async_progress_worker.cc} (90%) rename test/{asyncprogressworker.js => async_progress_worker.js} (100%) rename test/{asyncworker.cc => async_worker.cc} (66%) rename test/{asyncworker.js => async_worker.js} (100%) rename test/{asyncworker-nocallback.js => async_worker_nocallback.js} (100%) rename test/{asyncworker-persistent.cc => async_worker_persistent.cc} (80%) rename test/{asyncworker-persistent.js => async_worker_persistent.js} (100%) rename test/{functionreference.cc => function_reference.cc} (100%) rename test/{functionreference.js => function_reference.js} (100%) rename test/{objectreference.cc => object_reference.cc} (100%) rename test/{objectreference.js => object_reference.js} (100%) rename test/{objectwrap-removewrap.cc => objectwrap_removewrap.cc} (98%) rename test/{objectwrap-removewrap.js => objectwrap_removewrap.js} (100%) diff --git a/test/arraybuffer.cc b/test/array_buffer.cc similarity index 62% rename from test/arraybuffer.cc rename to test/array_buffer.cc index 4a1b2a34e..2b07bd250 100644 --- a/test/arraybuffer.cc +++ b/test/array_buffer.cc @@ -27,7 +27,8 @@ Value CreateBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } @@ -41,12 +42,14 @@ Value CreateExternalBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testData, testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != testData) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -60,21 +63,20 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { uint8_t* data = new uint8_t[testLength]; ArrayBuffer buffer = ArrayBuffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, void* finalizeData) { - delete[] static_cast(finalizeData); - finalizeCount++; - }); + info.Env(), data, testLength, [](Env /*env*/, void* finalizeData) { + delete[] static_cast(finalizeData); + finalizeCount++; + }); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -89,22 +91,24 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { char* hint = nullptr; ArrayBuffer buffer = ArrayBuffer::New( - info.Env(), - data, - testLength, - [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) { - delete[] static_cast(finalizeData); - finalizeCount++; - }, - hint); + info.Env(), + data, + testLength, + [](Env /*env*/, void* finalizeData, char* /*finalizeHint*/) { + delete[] static_cast(finalizeData); + finalizeCount++; + }, + hint); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } if (buffer.Data() != data) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return Value(); } @@ -114,31 +118,35 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { void CheckBuffer(const CallbackInfo& info) { if (!info[0].IsArrayBuffer()) { - Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A buffer was expected.") + .ThrowAsJavaScriptException(); return; } ArrayBuffer buffer = info[0].As(); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return; } if (!VerifyData(static_cast(buffer.Data()), testLength)) { - Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer data.") + .ThrowAsJavaScriptException(); return; } } Value GetFinalizeCount(const CallbackInfo& info) { - return Number::New(info.Env(), finalizeCount); + return Number::New(info.Env(), finalizeCount); } Value CreateBufferWithConstructor(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); if (buffer.ByteLength() != testLength) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return Value(); } InitData(static_cast(buffer.Data()), testLength); @@ -153,7 +161,8 @@ Value CheckEmptyBuffer(const CallbackInfo& info) { void CheckDetachUpdatesData(const CallbackInfo& info) { if (!info[0].IsArrayBuffer()) { - Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A buffer was expected.") + .ThrowAsJavaScriptException(); return; } @@ -165,7 +174,8 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { #if NAPI_VERSION >= 7 if (buffer.IsDetached()) { - Error::New(info.Env(), "Buffer should not be detached.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Buffer should not be detached.") + .ThrowAsJavaScriptException(); return; } #endif @@ -173,7 +183,8 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { if (info.Length() == 2) { // Detach externally (in JavaScript). if (!info[1].IsFunction()) { - Error::New(info.Env(), "A function was expected.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "A function was expected.") + .ThrowAsJavaScriptException(); return; } @@ -190,23 +201,26 @@ void CheckDetachUpdatesData(const CallbackInfo& info) { #if NAPI_VERSION >= 7 if (!buffer.IsDetached()) { - Error::New(info.Env(), "Buffer should be detached.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Buffer should be detached.") + .ThrowAsJavaScriptException(); return; } #endif if (buffer.Data() != nullptr) { - Error::New(info.Env(), "Incorrect data pointer.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect data pointer.") + .ThrowAsJavaScriptException(); return; } if (buffer.ByteLength() != 0) { - Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + Error::New(info.Env(), "Incorrect buffer length.") + .ThrowAsJavaScriptException(); return; } } -} // end anonymous namespace +} // end anonymous namespace Object InitArrayBuffer(Env env) { Object exports = Object::New(env); @@ -214,14 +228,16 @@ Object InitArrayBuffer(Env env) { exports["createBuffer"] = Function::New(env, CreateBuffer); exports["createExternalBuffer"] = Function::New(env, CreateExternalBuffer); exports["createExternalBufferWithFinalize"] = - Function::New(env, CreateExternalBufferWithFinalize); + Function::New(env, CreateExternalBufferWithFinalize); exports["createExternalBufferWithFinalizeHint"] = - Function::New(env, CreateExternalBufferWithFinalizeHint); + Function::New(env, CreateExternalBufferWithFinalizeHint); exports["checkBuffer"] = Function::New(env, CheckBuffer); exports["getFinalizeCount"] = Function::New(env, GetFinalizeCount); - exports["createBufferWithConstructor"] = Function::New(env, CreateBufferWithConstructor); + exports["createBufferWithConstructor"] = + Function::New(env, CreateBufferWithConstructor); exports["checkEmptyBuffer"] = Function::New(env, CheckEmptyBuffer); - exports["checkDetachUpdatesData"] = Function::New(env, CheckDetachUpdatesData); + exports["checkDetachUpdatesData"] = + Function::New(env, CheckDetachUpdatesData); return exports; } diff --git a/test/arraybuffer.js b/test/array_buffer.js similarity index 100% rename from test/arraybuffer.js rename to test/array_buffer.js diff --git a/test/asynccontext.cc b/test/async_context.cc similarity index 76% rename from test/asynccontext.cc rename to test/async_context.cc index bb1acbb89..ed98db938 100644 --- a/test/asynccontext.cc +++ b/test/async_context.cc @@ -8,11 +8,11 @@ static void MakeCallback(const CallbackInfo& info) { Function callback = info[0].As(); Object resource = info[1].As(); AsyncContext context(info.Env(), "async_context_test", resource); - callback.MakeCallback(Object::New(info.Env()), - std::initializer_list{}, context); + callback.MakeCallback( + Object::New(info.Env()), std::initializer_list{}, context); } -} // end anonymous namespace +} // end anonymous namespace Object InitAsyncContext(Env env) { Object exports = Object::New(env); diff --git a/test/asynccontext.js b/test/async_context.js similarity index 100% rename from test/asynccontext.js rename to test/async_context.js diff --git a/test/asyncprogressqueueworker.cc b/test/async_progress_queue_worker.cc similarity index 79% rename from test/asyncprogressqueueworker.cc rename to test/async_progress_queue_worker.cc index 23c6707ff..eec3f9510 100644 --- a/test/asyncprogressqueueworker.cc +++ b/test/async_progress_queue_worker.cc @@ -16,17 +16,14 @@ struct ProgressData { }; class TestWorker : public AsyncProgressQueueWorker { -public: + public: static Napi::Value CreateWork(const CallbackInfo& info) { int32_t times = info[0].As().Int32Value(); Function cb = info[1].As(); Function progress = info[2].As(); - TestWorker* worker = new TestWorker(cb, - progress, - "TestResource", - Object::New(info.Env()), - times); + TestWorker* worker = new TestWorker( + cb, progress, "TestResource", Object::New(info.Env()), times); return Napi::External::New(info.Env(), worker); } @@ -37,7 +34,7 @@ class TestWorker : public AsyncProgressQueueWorker { worker->Queue(); } -protected: + protected: void Execute(const ExecutionProgress& progress) override { using namespace std::chrono_literals; std::this_thread::sleep_for(1s); @@ -56,18 +53,17 @@ class TestWorker : public AsyncProgressQueueWorker { Napi::Env env = Env(); if (!_js_progress_cb.IsEmpty()) { Number progress = Number::New(env, data->progress); - _js_progress_cb.Call(Receiver().Value(), { progress }); + _js_progress_cb.Call(Receiver().Value(), {progress}); } } -private: + private: TestWorker(Function cb, Function progress, const char* resource_name, const Object& resource, int32_t times) - : AsyncProgressQueueWorker(cb, resource_name, resource), - _times(times) { + : AsyncProgressQueueWorker(cb, resource_name, resource), _times(times) { _js_progress_cb.Reset(progress, 1); } @@ -75,7 +71,7 @@ class TestWorker : public AsyncProgressQueueWorker { FunctionReference _js_progress_cb; }; -} // namespace +} // namespace Object InitAsyncProgressQueueWorker(Env env) { Object exports = Object::New(env); diff --git a/test/asyncprogressqueueworker.js b/test/async_progress_queue_worker.js similarity index 100% rename from test/asyncprogressqueueworker.js rename to test/async_progress_queue_worker.js diff --git a/test/asyncprogressworker.cc b/test/async_progress_worker.cc similarity index 90% rename from test/asyncprogressworker.cc rename to test/async_progress_worker.cc index 1705124ad..36087e7ca 100644 --- a/test/asyncprogressworker.cc +++ b/test/async_progress_worker.cc @@ -16,18 +16,19 @@ struct ProgressData { }; class TestWorker : public AsyncProgressWorker { -public: + public: static void DoWork(const CallbackInfo& info) { int32_t times = info[0].As().Int32Value(); Function cb = info[1].As(); Function progress = info[2].As(); - TestWorker* worker = new TestWorker(cb, progress, "TestResource", Object::New(info.Env())); + TestWorker* worker = + new TestWorker(cb, progress, "TestResource", Object::New(info.Env())); worker->_times = times; worker->Queue(); } -protected: + protected: void Execute(const ExecutionProgress& progress) override { if (_times < 0) { SetError("test error"); @@ -45,13 +46,16 @@ class TestWorker : public AsyncProgressWorker { Napi::Env env = Env(); if (!_progress.IsEmpty()) { Number progress = Number::New(env, data->progress); - _progress.MakeCallback(Receiver().Value(), { progress }); + _progress.MakeCallback(Receiver().Value(), {progress}); } _cv.notify_one(); } -private: - TestWorker(Function cb, Function progress, const char* resource_name, const Object& resource) + private: + TestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource) : AsyncProgressWorker(cb, resource_name, resource) { _progress.Reset(progress, 1); } @@ -118,7 +122,7 @@ class MalignWorker : public AsyncProgressWorker { std::mutex _cvm; FunctionReference _progress; }; -} +} // namespace Object InitAsyncProgressWorker(Env env) { Object exports = Object::New(env); diff --git a/test/asyncprogressworker.js b/test/async_progress_worker.js similarity index 100% rename from test/asyncprogressworker.js rename to test/async_progress_worker.js diff --git a/test/asyncworker.cc b/test/async_worker.cc similarity index 66% rename from test/asyncworker.cc rename to test/async_worker.cc index 324146533..8efb0d5b0 100644 --- a/test/asyncworker.cc +++ b/test/async_worker.cc @@ -3,7 +3,7 @@ using namespace Napi; class TestWorker : public AsyncWorker { -public: + public: static void DoWork(const CallbackInfo& info) { bool succeed = info[0].As(); Object resource = info[1].As(); @@ -16,34 +16,35 @@ class TestWorker : public AsyncWorker { worker->Queue(); } -protected: + protected: void Execute() override { if (!_succeed) { SetError("test error"); } } -private: + private: TestWorker(Function cb, const char* resource_name, const Object& resource) : AsyncWorker(cb, resource_name, resource) {} bool _succeed; }; class TestWorkerWithResult : public AsyncWorker { -public: + public: static void DoWork(const CallbackInfo& info) { bool succeed = info[0].As(); Object resource = info[1].As(); Function cb = info[2].As(); Value data = info[3]; - TestWorkerWithResult* worker = new TestWorkerWithResult(cb, "TestResource", resource); + TestWorkerWithResult* worker = + new TestWorkerWithResult(cb, "TestResource", resource); worker->Receiver().Set("data", data); worker->_succeed = succeed; worker->Queue(); } -protected: + protected: void Execute() override { if (!_succeed) { SetError("test error"); @@ -55,40 +56,41 @@ class TestWorkerWithResult : public AsyncWorker { String::New(env, _succeed ? "ok" : "error")}; } -private: - TestWorkerWithResult(Function cb, const char* resource_name, const Object& resource) + private: + TestWorkerWithResult(Function cb, + const char* resource_name, + const Object& resource) : AsyncWorker(cb, resource_name, resource) {} bool _succeed; }; class TestWorkerNoCallback : public AsyncWorker { -public: + public: static Value DoWork(const CallbackInfo& info) { napi_env env = info.Env(); bool succeed = info[0].As(); Object resource = info[1].As(); - TestWorkerNoCallback* worker = new TestWorkerNoCallback(env, "TestResource", resource); + TestWorkerNoCallback* worker = + new TestWorkerNoCallback(env, "TestResource", resource); worker->_succeed = succeed; worker->Queue(); return worker->_deferred.Promise(); } -protected: - void Execute() override { - } - virtual void OnOK() override { - _deferred.Resolve(Env().Undefined()); - - } + protected: + void Execute() override {} + virtual void OnOK() override { _deferred.Resolve(Env().Undefined()); } virtual void OnError(const Napi::Error& /* e */) override { - _deferred.Reject(Env().Undefined()); + _deferred.Reject(Env().Undefined()); } -private: - TestWorkerNoCallback(napi_env env, const char* resource_name, const Object& resource) - : AsyncWorker(env, resource_name, resource), _deferred(Napi::Promise::Deferred::New(env)) { - } + private: + TestWorkerNoCallback(napi_env env, + const char* resource_name, + const Object& resource) + : AsyncWorker(env, resource_name, resource), + _deferred(Napi::Promise::Deferred::New(env)) {} Promise::Deferred _deferred; bool _succeed; }; @@ -96,7 +98,9 @@ class TestWorkerNoCallback : public AsyncWorker { Object InitAsyncWorker(Env env) { Object exports = Object::New(env); exports["doWork"] = Function::New(env, TestWorker::DoWork); - exports["doWorkNoCallback"] = Function::New(env, TestWorkerNoCallback::DoWork); - exports["doWorkWithResult"] = Function::New(env, TestWorkerWithResult::DoWork); + exports["doWorkNoCallback"] = + Function::New(env, TestWorkerNoCallback::DoWork); + exports["doWorkWithResult"] = + Function::New(env, TestWorkerWithResult::DoWork); return exports; } diff --git a/test/asyncworker.js b/test/async_worker.js similarity index 100% rename from test/asyncworker.js rename to test/async_worker.js diff --git a/test/asyncworker-nocallback.js b/test/async_worker_nocallback.js similarity index 100% rename from test/asyncworker-nocallback.js rename to test/async_worker_nocallback.js diff --git a/test/asyncworker-persistent.cc b/test/async_worker_persistent.cc similarity index 80% rename from test/asyncworker-persistent.cc rename to test/async_worker_persistent.cc index 97aa0cab8..90349ae34 100644 --- a/test/asyncworker-persistent.cc +++ b/test/async_worker_persistent.cc @@ -9,7 +9,7 @@ using namespace Napi; namespace { class PersistentTestWorker : public AsyncWorker { -public: + public: static PersistentTestWorker* current_worker; static void DoWork(const CallbackInfo& info) { bool succeed = info[0].As(); @@ -28,24 +28,21 @@ class PersistentTestWorker : public AsyncWorker { } static void DeleteWorker(const CallbackInfo& info) { - (void) info; + (void)info; delete current_worker; } - ~PersistentTestWorker() { - current_worker = nullptr; - } + ~PersistentTestWorker() { current_worker = nullptr; } -protected: + protected: void Execute() override { if (!_succeed) { SetError("test error"); } } -private: - PersistentTestWorker(Function cb, - const char* resource_name) + private: + PersistentTestWorker(Function cb, const char* resource_name) : AsyncWorker(cb, resource_name) {} bool _succeed; @@ -58,9 +55,8 @@ PersistentTestWorker* PersistentTestWorker::current_worker = nullptr; Object InitPersistentAsyncWorker(Env env) { Object exports = Object::New(env); exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork); - exports.DefineProperty( - PropertyDescriptor::Accessor(env, exports, "workerGone", - PersistentTestWorker::GetWorkerGone)); + exports.DefineProperty(PropertyDescriptor::Accessor( + env, exports, "workerGone", PersistentTestWorker::GetWorkerGone)); exports["deleteWorker"] = Function::New(env, PersistentTestWorker::DeleteWorker); return exports; diff --git a/test/asyncworker-persistent.js b/test/async_worker_persistent.js similarity index 100% rename from test/asyncworker-persistent.js rename to test/async_worker_persistent.js diff --git a/test/binding.gyp b/test/binding.gyp index e45711af0..2969dc1f9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -6,12 +6,12 @@ 'build_sources': [ 'addon.cc', 'addon_data.cc', - 'arraybuffer.cc', - 'asynccontext.cc', - 'asyncprogressqueueworker.cc', - 'asyncprogressworker.cc', - 'asyncworker.cc', - 'asyncworker-persistent.cc', + 'array_buffer.cc', + 'async_context.cc', + 'async_progress_queue_worker.cc', + 'async_progress_worker.cc', + 'async_worker.cc', + 'async_worker_persistent.cc', 'basic_types/array.cc', 'basic_types/boolean.cc', 'basic_types/number.cc', @@ -27,7 +27,7 @@ 'error.cc', 'external.cc', 'function.cc', - 'functionreference.cc', + 'function_reference.cc', 'handlescope.cc', 'maybe/check.cc', 'movable_callbacks.cc', @@ -65,9 +65,9 @@ 'typedarray.cc', 'objectwrap.cc', 'objectwrap_constructor_exception.cc', - 'objectwrap-removewrap.cc', + 'objectwrap_removewrap.cc', 'objectwrap_multiple_inheritance.cc', - 'objectreference.cc', + 'object_reference.cc', 'reference.cc', 'version_management.cc', 'thunking_manual.cc', diff --git a/test/functionreference.cc b/test/function_reference.cc similarity index 100% rename from test/functionreference.cc rename to test/function_reference.cc diff --git a/test/functionreference.js b/test/function_reference.js similarity index 100% rename from test/functionreference.js rename to test/function_reference.js diff --git a/test/objectreference.cc b/test/object_reference.cc similarity index 100% rename from test/objectreference.cc rename to test/object_reference.cc diff --git a/test/objectreference.js b/test/object_reference.js similarity index 100% rename from test/objectreference.js rename to test/object_reference.js diff --git a/test/objectwrap-removewrap.cc b/test/objectwrap_removewrap.cc similarity index 98% rename from test/objectwrap-removewrap.cc rename to test/objectwrap_removewrap.cc index fdcec07c7..a714186f0 100644 --- a/test/objectwrap-removewrap.cc +++ b/test/objectwrap_removewrap.cc @@ -1,5 +1,5 @@ -#include #include +#include namespace { @@ -18,7 +18,7 @@ Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { } class Test : public Napi::ObjectWrap { -public: + public: Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { #ifdef NAPI_CPP_EXCEPTIONS throw Napi::Error::New(Env(), "Some error"); @@ -32,7 +32,7 @@ class Test : public Napi::ObjectWrap { exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled)); } -private: + private: DtorCounter dtor_counter_; }; diff --git a/test/objectwrap-removewrap.js b/test/objectwrap_removewrap.js similarity index 100% rename from test/objectwrap-removewrap.js rename to test/objectwrap_removewrap.js From 1708851e7e04e69bd1aa8d466c81329c417e7b1b Mon Sep 17 00:00:00 2001 From: Deepak Rajamohan Date: Thu, 26 Aug 2021 22:23:33 -0700 Subject: [PATCH 50/57] test: run tests with opts to prefix bld root path PR-URL: https://github.com/nodejs/node-addon-api/pull/1055 Reviewed-By: Michael Dawson --- package.json | 1 + test/common/index.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index b93cc90f1..4bd6c2008 100644 --- a/package.json +++ b/package.json @@ -318,6 +318,7 @@ "bindings": "^1.5.0", "clang-format": "^1.4.0", "fs-extra": "^9.0.1", + "path": "^0.12.7", "pre-commit": "^1.2.2", "safe-buffer": "^5.1.1" }, diff --git a/test/common/index.js b/test/common/index.js index 2863925d8..110979917 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -1,6 +1,7 @@ /* Test helpers ported from test/common/index.js in Node.js project. */ 'use strict'; const assert = require('assert'); +const path = require('path'); const noop = () => {}; @@ -75,13 +76,13 @@ exports.mustNotCall = function(msg) { }; }; -exports.runTest = async function(test, buildType) { +exports.runTest = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') { buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ - `../build/${buildType}/binding.node`, - `../build/${buildType}/binding_noexcept.node`, - `../build/${buildType}/binding_noexcept_maybe.node`, + path.join(buildPathRoot, `../build/${buildType}/binding.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), ].map(it => require.resolve(it)); for (const item of bindings) { @@ -90,13 +91,13 @@ exports.runTest = async function(test, buildType) { } } -exports.runTestWithBindingPath = async function(test, buildType) { +exports.runTestWithBindingPath = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') { buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ - `../build/${buildType}/binding.node`, - `../build/${buildType}/binding_noexcept.node`, - `../build/${buildType}/binding_noexcept_maybe.node`, + path.join(buildPathRoot, `../build/${buildType}/binding.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept.node`), + path.join(buildPathRoot, `../build/${buildType}/binding_noexcept_maybe.node`), ].map(it => require.resolve(it)); for (const item of bindings) { From 55edda693af3f559b579683fb041c58aedf51e2e Mon Sep 17 00:00:00 2001 From: strager Date: Fri, 10 Sep 2021 13:13:11 -0700 Subject: [PATCH 51/57] docs: fix typo and formatting (#1062) --- doc/object_wrap.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 7b8fc9c28..da07448d9 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -160,7 +160,7 @@ static T* Napi::ObjectWrap::Unwrap(Napi::Object wrapper); * `[in] wrapper`: The JavaScript object that wraps the native instance. Returns a native instance wrapped in a JavaScript object. Given the -Napi:Object, this allows a method to get a pointer to the wrapped +`Napi::Object`, this allows a method to get a pointer to the wrapped C++ object and then reference fields, call methods, etc. within that class. In many cases calling Unwrap is not required, as methods can use the `this` field for ObjectWrap when running in a method on a From 0aa256ed5c1dff13816ae5a9671e3c294a1aa34f Mon Sep 17 00:00:00 2001 From: todoroff <48935314+todoroff@users.noreply.github.com> Date: Tue, 14 Sep 2021 18:39:18 +0100 Subject: [PATCH 52/57] docs: fix typos (#1068) --- doc/async_worker_variants.md | 6 +++--- doc/error_handling.md | 6 +++--- doc/escapable_handle_scope.md | 2 +- doc/object.md | 4 ++-- doc/object_wrap.md | 12 ++++++------ doc/type_error.md | 4 ++-- doc/typed_threadsafe_function.md | 8 ++++---- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/async_worker_variants.md b/doc/async_worker_variants.md index 4f1762177..591cd8a57 100644 --- a/doc/async_worker_variants.md +++ b/doc/async_worker_variants.md @@ -417,9 +417,9 @@ void Napi::AsyncProgressQueueWorker::ExecutionProcess::Send(const T* data, size_ ## Example -The code below show an example of the `Napi::AsyncProgressQueueWorker` implementation, but -also demonsrates how to use multiple `Napi::Function`'s if you wish to provide multiple -callback functions for more object oriented code: +The code below shows an example of the `Napi::AsyncProgressQueueWorker` implementation, but +also demonstrates how to use multiple `Napi::Function`'s if you wish to provide multiple +callback functions for more object-oriented code: ```cpp #include diff --git a/doc/error_handling.md b/doc/error_handling.md index 57de85c9e..642c223e5 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -22,8 +22,8 @@ The following sections explain the approach for each case: -In most cases when an error occurs, the addon should do whatever clean is possible -and then return to JavaScript so that they error can be propagated. In less frequent +In most cases when an error occurs, the addon should do whatever cleanup is possible +and then return to JavaScript so that the error can be propagated. In less frequent cases the addon may be able to recover from the error, clear the error and then continue. @@ -39,7 +39,7 @@ the error as a C++ exception of type `Napi::Error`. If a JavaScript function called by C++ code via node-addon-api throws a JavaScript exception, then node-addon-api automatically converts and throws it as a C++ -exception of type `Napi:Error` on return from the JavaScript code to the native +exception of type `Napi::Error` on return from the JavaScript code to the native method. If a C++ exception of type `Napi::Error` escapes from a Node-API C++ callback, then diff --git a/doc/escapable_handle_scope.md b/doc/escapable_handle_scope.md index a58942a79..faf4b5b47 100644 --- a/doc/escapable_handle_scope.md +++ b/doc/escapable_handle_scope.md @@ -20,7 +20,7 @@ For more details refer to the section titled Creates a new escapable handle scope. ```cpp -Napi::EscapableHandleScope Napi::EscapableHandleScope::New(Napi:Env env); +Napi::EscapableHandleScope Napi::EscapableHandleScope::New(Napi::Env env); ``` - `[in] Env`: The environment in which to construct the `Napi::EscapableHandleScope` object. diff --git a/doc/object.md b/doc/object.md index 06b19f9f1..9830675a7 100644 --- a/doc/object.md +++ b/doc/object.md @@ -227,7 +227,7 @@ The `Napi::Object::Freeze()` method freezes an object. A frozen object can no longer changed. Freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties and -prevents the valuee of existing properties from being changed. In addition, +prevents the value of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. ### Seal() @@ -238,7 +238,7 @@ void Napi::Object::Seal() The `Napi::Object::Seal()` method seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. -Values of present properties can still be changed as long as thery are +Values of present properties can still be changed as long as they are writable. ### operator\[\]() diff --git a/doc/object_wrap.md b/doc/object_wrap.md index da07448d9..d90da42c4 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -284,7 +284,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, void* data = nullptr); ``` -- `[in] name`: Napi:Symbol that represents the name of a static +- `[in] name`: Napi::Symbol that represents the name of a static method for the class. - `[in] method`: The native function that represents a static method of a JavaScript class. @@ -308,7 +308,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, ``` method for the class. -- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] name`: Napi::Symbol that represents the name of a static. - `[in] method`: The native function that represents a static method of a JavaScript class. - `[in] attributes`: The attributes associated with a particular property. @@ -380,7 +380,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, - `[in] method`: The native function that represents a static method of a JavaScript class. -- `[in] name`: Napi:Symbol that represents the name of a static +- `[in] name`: Napi::Symbol that represents the name of a static method for the class. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. @@ -403,7 +403,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, - `[in] method`: The native function that represents a static method of a JavaScript class. -- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] name`: Napi::Symbol that represents the name of a static. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. - `[in] data`: User-provided data passed into method when it is invoked. @@ -452,7 +452,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, void* data = nullptr); ``` -- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] name`: Napi::Symbol that represents the name of a static accessor. - `[in] getter`: The native function to call when a get access to the property of a JavaScript class is performed. - `[in] setter`: The native function to call when a set access to the property @@ -508,7 +508,7 @@ static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, of a JavaScript class is performed. - `[in] setter`: The native function to call when a set access to the property of a JavaScript class is performed. -- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] name`: Napi::Symbol that represents the name of a static accessor. - `[in] attributes`: The attributes associated with a particular property. One or more of `napi_property_attributes`. - `[in] data`: User-provided data passed into getter or setter when diff --git a/doc/type_error.md b/doc/type_error.md index 24bbf8eda..439a306ee 100644 --- a/doc/type_error.md +++ b/doc/type_error.md @@ -16,7 +16,7 @@ For more details about error handling refer to the section titled [Error handlin Creates a new instance of the `Napi::TypeError` object. ```cpp -Napi::TypeError::New(Napi:Env env, const char* message); +Napi::TypeError::New(Napi::Env env, const char* message); ``` - `[in] Env`: The environment in which to construct the `Napi::TypeError` object. @@ -29,7 +29,7 @@ Returns an instance of a `Napi::TypeError` object. Creates a new instance of a `Napi::TypeError` object. ```cpp -Napi::TypeError::New(Napi:Env env, const std::string& message); +Napi::TypeError::New(Napi::Env env, const std::string& message); ``` - `[in] Env`: The environment in which to construct the `Napi::TypeError` object. diff --git a/doc/typed_threadsafe_function.md b/doc/typed_threadsafe_function.md index 33c490e87..496c952b5 100644 --- a/doc/typed_threadsafe_function.md +++ b/doc/typed_threadsafe_function.md @@ -73,7 +73,7 @@ New(napi_env env, - `initialThreadCount`: The initial number of threads, including the main thread, which will be making use of this function. - `[optional] context`: Data to attach to the resulting `ThreadSafeFunction`. It - can be retreived via `GetContext()`. + can be retrieved via `GetContext()`. - `[optional] finalizeCallback`: Function to call when the `TypedThreadSafeFunction` is being destroyed. This callback will be invoked on the main thread when the thread-safe function is about to be destroyed. It @@ -130,7 +130,7 @@ napi_status Napi::TypedThreadSafeFunction::Rele Returns one of: - `napi_ok`: The thread-safe function has been successfully released. - `napi_invalid_arg`: The thread-safe function's thread-count is zero. -- `napi_generic_failure`: A generic error occurred when attemping to release the +- `napi_generic_failure`: A generic error occurred when attempting to release the thread-safe function. ### Abort @@ -152,7 +152,7 @@ napi_status Napi::TypedThreadSafeFunction::Abor Returns one of: - `napi_ok`: The thread-safe function has been successfully aborted. - `napi_invalid_arg`: The thread-safe function's thread-count is zero. -- `napi_generic_failure`: A generic error occurred when attemping to abort the +- `napi_generic_failure`: A generic error occurred when attempting to abort the thread-safe function. ### BlockingCall / NonBlockingCall @@ -180,7 +180,7 @@ Returns one of: - `napi_closing`: The thread-safe function is aborted and no further calls can be made. - `napi_invalid_arg`: The thread-safe function is closed. -- `napi_generic_failure`: A generic error occurred when attemping to add to the +- `napi_generic_failure`: A generic error occurred when attempting to add to the queue. From c82782f6baf8d874d66b69b042a9e241f08c46be Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 15 Sep 2021 11:33:48 +0200 Subject: [PATCH 53/57] src: fix casts to not be undefined behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I guess if these were broken in practice, V8/Node.js itself would also experience difficulties with it, but there’s no real reason not to use the proper alternatives. PR-URL: https://github.com/nodejs/node-addon-api/pull/1070 Reviewed-By: Michael Dawson --- napi-inl.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index a1d67ac17..0af8e7ae5 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2044,8 +2044,10 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) : TypedArray(env, value), _data(nullptr) { napi_status status = napi_ok; if (value != nullptr) { + void* data = nullptr; status = napi_get_typedarray_info( - _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); + _env, _value, &_type, &_length, &data, nullptr, nullptr); + _data = static_cast(data); } else { _type = TypedArrayTypeForPrimitiveType(); _length = 0; @@ -3967,10 +3969,10 @@ inline ObjectWrap::~ObjectWrap() { template inline T* ObjectWrap::Unwrap(Object wrapper) { - T* unwrapped; - napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); + void* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); - return unwrapped; + return static_cast(unwrapped); } template From 0ae9de546b0509136f03f6f25f3350ba73fe86ff Mon Sep 17 00:00:00 2001 From: "Matthew \"strager\" Glazar" Date: Mon, 6 Sep 2021 19:53:11 -0700 Subject: [PATCH 54/57] src,test: allow creating Function with move-only functor Function::New fails to compile when given a move-only functor. For example, when constructing a callback function for Promise#then, a lambda might capture an ObjectReference. Creating a Function for such a lambda results in a compilation error. Tweak Function::New to work with move-only functors. For existing users of Function::New, this commit should not change behavior. --- napi-inl.h | 3 ++- test/function.cc | 20 ++++++++++++++++++++ test/function.js | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/napi-inl.h b/napi-inl.h index 0af8e7ae5..5e51cc32b 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace Napi { @@ -2156,7 +2157,7 @@ inline Function Function::New(napi_env env, void* data) { using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); using CbData = details::CallbackData; - auto callbackData = new CbData({ cb, data }); + auto callbackData = new CbData{std::move(cb), data}; napi_value value; napi_status status = CreateFunction(env, diff --git a/test/function.cc b/test/function.cc index 0fab27290..3c5033d8a 100644 --- a/test/function.cc +++ b/test/function.cc @@ -1,3 +1,4 @@ +#include #include "napi.h" #include "test_helper.h" @@ -271,5 +272,24 @@ Object InitFunction(Env env) { exports["callWithFunctionOperator"] = Function::New(env); result["templated"] = exports; + + exports = Object::New(env); + exports["lambdaWithNoCapture"] = + Function::New(env, [](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, true); + }); + exports["lambdaWithCapture"] = + Function::New(env, [data = 42](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, data == 42); + }); + exports["lambdaWithMoveOnlyCapture"] = Function::New( + env, [data = std::make_unique(42)](const CallbackInfo& info) { + auto env = info.Env(); + return Boolean::New(env, *data == 42); + }); + result["lambda"] = exports; + return result; } diff --git a/test/function.js b/test/function.js index 9c30e046d..7536f62e8 100644 --- a/test/function.js +++ b/test/function.js @@ -5,6 +5,7 @@ const assert = require('assert'); module.exports = require('./common').runTest(binding => { test(binding.function.plain); test(binding.function.templated); + testLambda(binding.function.lambda); }); function test(binding) { @@ -112,3 +113,9 @@ function test(binding) { binding.makeCallbackWithInvalidReceiver(() => {}); }); } + +function testLambda(binding) { + assert.ok(binding.lambdaWithNoCapture()); + assert.ok(binding.lambdaWithCapture()); + assert.ok(binding.lambdaWithMoveOnlyCapture()); +} From 3de1e3879511c0080b61928a344ef47343b5635d Mon Sep 17 00:00:00 2001 From: NickNaso Date: Fri, 17 Sep 2021 15:06:55 +0200 Subject: [PATCH 55/57] Prepare release v4.2.0. --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ README.md | 2 +- package.json | 14 +++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d8a1818..0fb67881a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # node-addon-api Changelog +## 2021-09-17 Version 4.2.0, @NickNaso + +### Notable changes: + +#### API + +- Allow creating Function with move-only functor. +- Fixed casts to not be undefined behavior. + +#### TEST + +- Fixed the way to enable C++ exceptions. +- Run tests with options to prefix build root path. + +### Documentation + +- Fixed documentation about how to enable C++ exception. +- Minor fixes all over documentation. + +### Commits + +* [[`2dc1f5b66c`](https://github.com/nodejs/node-addon-api/commit/2dc1f5b66c)] - Merge pull request #1065 from strager/move-only-functor (Nicola Del Gobbo) +* [[`2b57a4aa4c`](https://github.com/nodejs/node-addon-api/commit/2b57a4aa4c)] - **src**: fix casts to not be undefined behavior (Anna Henningsen) [#1070](https://github.com/nodejs/node-addon-api/pull/1070) +* [[`76de4d8222`](https://github.com/nodejs/node-addon-api/commit/76de4d8222)] - **docs**: fix typos (#1068) (todoroff) +* [[`22a2f3c926`](https://github.com/nodejs/node-addon-api/commit/22a2f3c926)] - **docs**: fix typo and formatting (#1062) (strager) +* [[`62b666c34c`](https://github.com/nodejs/node-addon-api/commit/62b666c34c)] - **test**: run tests with opts to prefix bld root path (Deepak Rajamohan) [#1055](https://github.com/nodejs/node-addon-api/pull/1055) +* [[`cbac3aac5d`](https://github.com/nodejs/node-addon-api/commit/cbac3aac5d)] - **test**: standardize unit test file names (Deepak Rajamohan) [#1056](https://github.com/nodejs/node-addon-api/pull/1056) +* [[`3e5897a78b`](https://github.com/nodejs/node-addon-api/commit/3e5897a78b)] - **src,test**: allow creating Function with move-only functor (Matthew "strager" Glazar) +* [[`da2e754a02`](https://github.com/nodejs/node-addon-api/commit/da2e754a02)] - **test**: fix errors reported by newer compiler (Michael Dawson) +* [[`9aaf3b1324`](https://github.com/nodejs/node-addon-api/commit/9aaf3b1324)] - **doc**: fix documentation about how to enable C++ exception (#1059) (Nicola Del Gobbo) [#1059](https://github.com/nodejs/node-addon-api/pull/1059) +* [[`b2f861987f`](https://github.com/nodejs/node-addon-api/commit/b2f861987f)] - **test**: fixed the way to enable C++ exceptions. (#1061) (Nicola Del Gobbo) [#1061](https://github.com/nodejs/node-addon-api/pull/1061) + ## 2021-08-25 Version 4.1.0, @NickNaso ### Notable changes: diff --git a/README.md b/README.md index 2e0723d8b..673139708 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 4.1.0** +## **Current version: 4.2.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) diff --git a/package.json b/package.json index 4bd6c2008..df80ff76b 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,10 @@ "name": "David Halls", "url": "https://github.com/davedoesdev" }, + { + "name": "Deepak Rajamohan", + "url": "https://github.com/deepakrkris" + }, { "name": "Dmitry Ashkadov", "url": "https://github.com/dmitryash" @@ -275,6 +279,10 @@ "name": "Sam Roberts", "url": "https://github.com/sam-github" }, + { + "name": "strager", + "url": "https://github.com/strager" + }, { "name": "Taylor Woll", "url": "https://github.com/boingoing" @@ -291,6 +299,10 @@ "name": "Tobias Nießen", "url": "https://github.com/tniessen" }, + { + "name": "todoroff", + "url": "https://github.com/todoroff" + }, { "name": "Tux3", "url": "https://github.com/tux3" @@ -364,6 +376,6 @@ "lint:fix": "node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "4.1.0", + "version": "4.2.0", "support": true } From 764dc0081341b0af9c820540e1e998971ce811f4 Mon Sep 17 00:00:00 2001 From: Doni Rubiagatra Date: Sat, 18 Sep 2021 01:54:00 +0700 Subject: [PATCH 56/57] lint: add eslint based on config-semistandard (#1067) * lint: add eslint based on config-semistandard --- .eslintrc.js | 3 +++ package.json | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..dc51e2748 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['semistandard'] +}; diff --git a/package.json b/package.json index df80ff76b..03519e9c9 100644 --- a/package.json +++ b/package.json @@ -329,6 +329,12 @@ "benchmark": "^2.1.4", "bindings": "^1.5.0", "clang-format": "^1.4.0", + "eslint": "^7.32.0", + "eslint-config-semistandard": "^16.0.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.24.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", "fs-extra": "^9.0.1", "path": "^0.12.7", "pre-commit": "^1.2.2", @@ -351,7 +357,6 @@ "license": "MIT", "main": "index.js", "name": "node-addon-api", - "optionalDependencies": {}, "readme": "README.md", "repository": { "type": "git", @@ -372,8 +377,8 @@ "predev:incremental": "node-gyp configure build -C test --debug", "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile", - "lint": "node tools/clang-format", - "lint:fix": "node tools/clang-format --fix" + "lint": "eslint $(git diff --name-only refs/remotes/origin/main '**/*.js' | xargs) && node tools/clang-format", + "lint:fix": "node tools/clang-format --fix && eslint --fix $(git diff --cached --name-only '**/*.js' | xargs && git diff --name-only '**/*.js' | xargs)" }, "pre-commit": "lint", "version": "4.2.0", From 23f56253c6fdfd44c7ff99ca9ecbf7f68ab06636 Mon Sep 17 00:00:00 2001 From: Deepak Rajamohan Date: Thu, 29 Jul 2021 23:40:46 -0700 Subject: [PATCH 57/57] enable unit tests --- package.json | 4 +- test/common/index.js | 4 +- test/index.js | 38 +++++++--- .../typed_threadsafe_function_ctx.js | 2 +- unit-test/.gitignore | 3 + unit-test/binding-file-template.js | 27 +++++++ unit-test/binding.gyp | 64 +++++++++++++++++ unit-test/common.gypi | 21 ++++++ unit-test/exceptions.js | 20 ++++++ unit-test/generate-binding-cc.js | 41 +++++++++++ unit-test/injectTestParams.js | 69 ++++++++++++++++++ unit-test/listOfTestModules.js | 70 +++++++++++++++++++ unit-test/matchModules.js | 58 +++++++++++++++ unit-test/spawnTask.js | 23 ++++++ unit-test/test.js | 22 ++++++ 15 files changed, 452 insertions(+), 14 deletions(-) create mode 100644 unit-test/.gitignore create mode 100644 unit-test/binding-file-template.js create mode 100644 unit-test/binding.gyp create mode 100644 unit-test/common.gypi create mode 100644 unit-test/exceptions.js create mode 100644 unit-test/generate-binding-cc.js create mode 100644 unit-test/injectTestParams.js create mode 100644 unit-test/listOfTestModules.js create mode 100644 unit-test/matchModules.js create mode 100644 unit-test/spawnTask.js create mode 100644 unit-test/test.js diff --git a/package.json b/package.json index 03519e9c9..c3cbb5d07 100644 --- a/package.json +++ b/package.json @@ -378,7 +378,9 @@ "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile", "lint": "eslint $(git diff --name-only refs/remotes/origin/main '**/*.js' | xargs) && node tools/clang-format", - "lint:fix": "node tools/clang-format --fix && eslint --fix $(git diff --cached --name-only '**/*.js' | xargs && git diff --name-only '**/*.js' | xargs)" + "lint:fix": "node tools/clang-format --fix && eslint --fix $(git diff --cached --name-only '**/*.js' | xargs && git diff --name-only '**/*.js' | xargs)", + "preunit": "filter=\"$npm_config_filter\" node-gyp rebuild -C unit-test", + "unit": "filter=\"$npm_config_filter\" node unit-test/test" }, "pre-commit": "lint", "version": "4.2.0", diff --git a/test/common/index.js b/test/common/index.js index 110979917..9f29c07a7 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -76,7 +76,7 @@ exports.mustNotCall = function(msg) { }; }; -exports.runTest = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') { +exports.runTest = async function(test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ @@ -91,7 +91,7 @@ exports.runTest = async function(test, buildType, buildPathRoot = process.env.RE } } -exports.runTestWithBindingPath = async function(test, buildType, buildPathRoot = process.env.REL_BUILD_PATH || '') { +exports.runTestWithBindingPath = async function(test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; const bindings = [ diff --git a/test/index.js b/test/index.js index 2fe09ac2b..c45807d40 100644 --- a/test/index.js +++ b/test/index.js @@ -29,9 +29,23 @@ if (typeof global.gc !== 'function') { const fs = require('fs'); const path = require('path'); +process.env.filter = require('../unit-test/matchModules').matchWildCards(process.env.filter); let testModules = []; +const filterCondition = process.env.filter || ''; +const filterConditionFiles = filterCondition.split(' ').length ? filterCondition.split(' ') : [filterCondition]; + + +function checkFilterCondition(fileName, parsedFilepath) { + let result = false; + + if (!filterConditionFiles.length) result = true; + if (filterConditionFiles.includes(parsedFilepath)) result = true; + if (filterConditionFiles.includes(fileName)) result = true; + return result; +} + // TODO(RaisinTen): Update this when the test filenames // are changed into test_*.js. function loadTestModules(currentDirectory = __dirname, pre = '') { @@ -50,15 +64,19 @@ function loadTestModules(currentDirectory = __dirname, pre = '') { return; } const absoluteFilepath = path.join(currentDirectory, file); + const parsedFilepath = path.parse(file); + const parsedPath = path.parse(currentDirectory); + if (fs.statSync(absoluteFilepath).isDirectory()) { if (fs.existsSync(absoluteFilepath + '/index.js')) { - testModules.push(pre + file); + if (checkFilterCondition(parsedFilepath.name, parsedPath.base)) { + testModules.push(pre + file); + } } else { loadTestModules(absoluteFilepath, pre + file + '/'); } } else { - const parsedFilepath = path.parse(file); - if (parsedFilepath.ext === '.js') { + if (parsedFilepath.ext === '.js' && checkFilterCondition(parsedFilepath.name, parsedPath.base)) { testModules.push(pre + parsedFilepath.name); } } @@ -69,7 +87,7 @@ loadTestModules(); process.config.target_defaults.default_configuration = fs - .readdirSync(path.join(__dirname, 'build')) + .readdirSync(path.join(__dirname, process.env.REL_BUILD_PATH, 'build')) .filter((item) => (item === 'Debug' || item === 'Release'))[0]; let napiVersion = Number(process.versions.napi); @@ -87,7 +105,7 @@ if (napiVersion < 3) { testModules.splice(testModules.indexOf('version_management'), 1); } -if (napiVersion < 4) { +if (napiVersion < 4 && !filterConditionFiles.length) { testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); @@ -98,30 +116,30 @@ if (napiVersion < 4) { testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1); } -if (napiVersion < 5) { +if (napiVersion < 5 && !filterConditionFiles.length) { testModules.splice(testModules.indexOf('date'), 1); } -if (napiVersion < 6) { +if (napiVersion < 6 && !filterConditionFiles.length) { testModules.splice(testModules.indexOf('addon'), 1); testModules.splice(testModules.indexOf('addon_data'), 1); testModules.splice(testModules.indexOf('bigint'), 1); testModules.splice(testModules.indexOf('typedarray-bigint'), 1); } -if (majorNodeVersion < 12) { +if (majorNodeVersion < 12 && !filterConditionFiles.length) { testModules.splice(testModules.indexOf('objectwrap_worker_thread'), 1); testModules.splice(testModules.indexOf('error_terminating_environment'), 1); } -if (napiVersion < 8) { +if (napiVersion < 8 && !filterConditionFiles.length) { testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1); } (async function() { console.log(`Testing with Node-API Version '${napiVersion}'.`); - console.log('Starting test suite\n'); + console.log('Starting test suite\n', testModules); // Requiring each module runs tests in the module. for (const name of testModules) { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js index 94ea01fb4..f43da1d38 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js @@ -6,7 +6,7 @@ module.exports = require('../common').runTest(test); async function test(binding) { const ctx = { }; - const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); + const tsfn = new binding.typed_threadsafe_function_ctx.TSFNWrap(ctx); assert(tsfn.getContext() === ctx); await tsfn.release(); } diff --git a/unit-test/.gitignore b/unit-test/.gitignore new file mode 100644 index 000000000..3f1f6888d --- /dev/null +++ b/unit-test/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/build +/generated \ No newline at end of file diff --git a/unit-test/binding-file-template.js b/unit-test/binding-file-template.js new file mode 100644 index 000000000..eebcfa642 --- /dev/null +++ b/unit-test/binding-file-template.js @@ -0,0 +1,27 @@ +module.exports.generateFileContent = function(configs) { + const content = []; + const inits = []; + const exports = []; + + for (let config of configs) { + inits.push(`Object Init${config.objectName}(Env env);`); + exports.push(`exports.Set(\"${config.propertyName}\", Init${config.objectName}(env));`); + } + + content.push("#include \"napi.h\""); + content.push("using namespace Napi;"); + + //content.push("Object InitName(Env env);"); + inits.forEach(init => content.push(init)); + + content.push("Object Init(Env env, Object exports) {"); + + //content.push("exports.Set(\"name\", InitName(env));"); + exports.forEach(exp => content.push(exp)); + + content.push("return exports;"); + content.push("}"); + content.push("NODE_API_MODULE(addon, Init);"); + + return Promise.resolve(content.join('\r\n')); +} diff --git a/unit-test/binding.gyp b/unit-test/binding.gyp new file mode 100644 index 000000000..5c33af53b --- /dev/null +++ b/unit-test/binding.gyp @@ -0,0 +1,64 @@ +{ + 'target_defaults': { + 'includes': ['common.gypi'], + 'include_dirs': ['../test/common'], + 'variables': { + 'build_sources': [ + "@(build_sources)'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_noexcept', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, + { + 'target_name': 'binding_swallowexcept', + 'includes': ['../except.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], + 'dependencies': [ 'generateBindingCC' ] + }, + { + 'target_name': 'binding_swallowexcept_noexcept', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], + 'dependencies': [ 'generateBindingCC' ] + }, + ], +} \ No newline at end of file diff --git a/unit-test/common.gypi b/unit-test/common.gypi new file mode 100644 index 000000000..7173deb81 --- /dev/null +++ b/unit-test/common.gypi @@ -0,0 +1,21 @@ +{ + 'variables': { + 'NAPI_VERSION%': " { + const configName = file.split('.cc')[0]; + + if (buildDirs[configName]) { + for (let file of buildDirs[configName]) { + if (exceptions.skipBinding.includes(file)) continue; + configs.push(buildFiles[file]); + } + } else if (buildFiles[configName]) { + configs.push(buildFiles[configName]); + } else { + console.log('not found', file, configName); + } + }); + + return Promise.resolve(configs); +} + +function writeToBindingFile(content) { + const generatedFilePath = path.join(__dirname, 'generated', 'binding.cc' ); + fs.writeFileSync(generatedFilePath , "" ); + fs.writeFileSync(generatedFilePath, content, { flag: "a" } ); + console.log('generated binding file ', generatedFilePath, new Date()); +} + +generateBindingConfigurations().then(generateFileContent).then(writeToBindingFile); diff --git a/unit-test/injectTestParams.js b/unit-test/injectTestParams.js new file mode 100644 index 000000000..3e1a36263 --- /dev/null +++ b/unit-test/injectTestParams.js @@ -0,0 +1,69 @@ +const fs = require('fs'); +const listOfTestModules = require('./listOfTestModules'); + +const buildDirs = listOfTestModules.dirs; +const buildFiles = listOfTestModules.files; + +module.exports.filesToCompile = function () { + const filterCondition = require('./matchModules').matchWildCards(process.env.filter || ''); + let files_to_compile = './generated/binding.cc test_helper.h'; + let conditions = filterCondition.split(' ').length ? filterCondition.split(' ') : [filterCondition]; + let files = []; + + for (let matchCondition of conditions) { + if (buildDirs[matchCondition.toLowerCase()]) { + for (let file of buildDirs[matchCondition.toLowerCase()] ) { + const config = buildFiles[file]; + const separator = config.dir.length ? '/' : '' + files.push(config.dir + separator + file); + } + } else if (buildFiles[matchCondition.toLowerCase()]) { + const config = buildFiles[matchCondition.toLowerCase()]; + const separator = config.dir.length ? '/' : '' + files.push(config.dir + separator + matchCondition.toLowerCase()); + } + } + + let addedFiles = ''; + files.forEach((file) => { + addedFiles = `${addedFiles} ../test/${file}.cc`; + }); + fs.writeFileSync(__dirname + '/generated/compilelist', `${files_to_compile} ${addedFiles}`); + return `${files_to_compile} ${addedFiles}`; +}; + +module.exports.filesForBinding = function () { + const filterCondition = require('./matchModules').matchWildCards(process.env.filter || ''); + fs.writeFileSync(__dirname + '/generated/bindingList', filterCondition); + return filterCondition; +}; + + +if (require.main === module) { + const assert = require('assert'); + + const setEnvAndCall = (fn, filterCondition) => { process.env.filter = filterCondition; return fn(); }; + + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'typed*ex*'), './generated/binding.cc test_helper.h ../test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc'); + + const expectedFilesToMatch = [ + './generated/binding.cc test_helper.h ', + '../test/threadsafe_function/threadsafe_function.cc', + '../test/threadsafe_function/threadsafe_function_ctx.cc', + '../test/threadsafe_function/threadsafe_function_existing_tsfn.cc', + '../test/threadsafe_function/threadsafe_function_ptr.cc', + '../test/threadsafe_function/threadsafe_function_sum.cc', + '../test/threadsafe_function/threadsafe_function_unref.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_ctx.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_sum.cc', + '../test/typed_threadsafe_function/typed_threadsafe_function_unref.cc' + ] + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'threadsafe_function typed_threadsafe_function'), expectedFilesToMatch.join(' ')); + + assert.strictEqual(setEnvAndCall(exports.filesToCompile, 'objectwrap'), './generated/binding.cc test_helper.h ../test/objectwrap.cc'); + + console.log('ALL tests passed') +} diff --git a/unit-test/listOfTestModules.js b/unit-test/listOfTestModules.js new file mode 100644 index 000000000..20b712224 --- /dev/null +++ b/unit-test/listOfTestModules.js @@ -0,0 +1,70 @@ +const fs = require('fs'); +const path = require('path'); +const exceptions = require('./exceptions'); + +const buildFiles = {}; +const buidDirs = {}; + +function getExportObjectName(fileName) { + fileName = fileName.split('_').map(token => exceptions.nouns[token] ? exceptions.nouns[token] : token).join('_'); + const str = fileName.replace(/(\_\w)/g, (k) => k[1].toUpperCase()); + const exportObjectName = str.charAt(0).toUpperCase() + str.substring(1); + if (exceptions.exportNames[exportObjectName]) { + return exceptions.exportNames[exportObjectName]; + } + return exportObjectName; +} + +function getExportPropertyName(fileName) { + if (exceptions.propertyNames[fileName.toLowerCase()]) { + return exceptions.propertyNames[fileName.toLowerCase()]; + } + return fileName; +} + +function listOfTestModules(currentDirectory = __dirname + '/../test', pre = '') { + fs.readdirSync(currentDirectory).forEach((file) => { + if (file === 'binding.cc' || + file === 'binding.gyp' || + file === 'build' || + file === 'common' || + file === 'thunking_manual.cc' || + file === 'addon_build' || + file[0] === '.') { + return; + } + const absoluteFilepath = path.join(currentDirectory, file); + const fileName = file.toLowerCase().replace('.cc', ''); + if (fs.statSync(absoluteFilepath).isDirectory()) { + buidDirs[fileName] = [] + listOfTestModules(absoluteFilepath, pre + file + '/'); + } else { + if (!file.toLowerCase().endsWith('.cc')) return; + if (currentDirectory.trim().split('/test/').length > 1) { + buidDirs[currentDirectory.split('/test/')[1].toLowerCase()].push(fileName) + } + const relativePath = (currentDirectory.split(`${fileName}.cc`)[0]).split('/test/')[1] || ''; + buildFiles[fileName] = { dir: relativePath, propertyName: getExportPropertyName(fileName), objectName: getExportObjectName(fileName) }; + } + }); +} +listOfTestModules(); + +module.exports = { + dirs: buidDirs, + files: buildFiles +}; + + +if (require.main === module) { + const assert = require('assert') + assert.strictEqual(getExportObjectName('objectwrap_constructor_exception'), 'ObjectWrapConstructorException') + assert.strictEqual(getExportObjectName('typed_threadsafe_function'), 'TypedThreadSafeFunction') + assert.strictEqual(getExportObjectName('objectwrap_removewrap'), 'ObjectWrapRemovewrap') + assert.strictEqual(getExportObjectName('function_reference'), 'FunctionReference') + assert.strictEqual(getExportObjectName('async_worker'), 'AsyncWorker') + assert.strictEqual(getExportObjectName('async_progress_worker'), 'AsyncProgressWorker') + assert.strictEqual(getExportObjectName('async_worker_persistent'), 'PersistentAsyncWorker') + + console.log('ALL tests passed') +} diff --git a/unit-test/matchModules.js b/unit-test/matchModules.js new file mode 100644 index 000000000..8ce531cba --- /dev/null +++ b/unit-test/matchModules.js @@ -0,0 +1,58 @@ +const listOfTestModules = require('./listOfTestModules'); +const buildDirs = listOfTestModules.dirs; +const buildFiles = listOfTestModules.files; + +function isWildcard(filter) { + if (filter.includes('*')) return true; + return false; +} + +function filterBy(wildcard, item) { + return new RegExp('^' + wildcard.replace(/\*/g, '.*') + '$').test(item) +} + +function matchWildCards(filterCondition) { + let conditions = filterCondition.split(' ').length ? filterCondition.split(' ') : [filterCondition]; + let matches = []; + + for (let filter of conditions) { + if (isWildcard(filter)) { + const matchedDirs = Object.keys(buildDirs).filter(e => filterBy(filter, e)); + if (matchedDirs.length) { + matches.push(matchedDirs.join(' ')); + } else { + const matchedModules = Object.keys(buildFiles).filter(e => filterBy(filter, e)); + if (matchedModules.length) + matches.push(matchedModules.join(' ')); + } + } else { + matches.push(filter); + } + } + + return matches.join(' '); +} + +module.exports.matchWildCards = matchWildCards; + +if (require.main === module) { + const assert = require('assert') + + assert.strictEqual(matchWildCards('typed*ex'), 'typed*ex') + assert.strictEqual(matchWildCards('typed*ex*'), 'typed_threadsafe_function_existing_tsfn') + assert.strictEqual(matchWildCards('async*'), 'async_context async_progress_queue_worker async_progress_worker async_worker async_worker_persistent') + assert.strictEqual(matchWildCards('typed*func'), 'typed*func') + assert.strictEqual(matchWildCards('typed*func*'), 'typed_threadsafe_function') + assert.strictEqual(matchWildCards('typed*function'), 'typed_threadsafe_function') + assert.strictEqual(matchWildCards('object*inh'), 'object*inh') + assert.strictEqual(matchWildCards('object*inh*'), 'objectwrap_multiple_inheritance') + assert.strictEqual(matchWildCards('*remove*'), 'objectwrap_removewrap') + assert.strictEqual(matchWildCards('*function'), 'threadsafe_function typed_threadsafe_function') + assert.strictEqual(matchWildCards('**function'), 'threadsafe_function typed_threadsafe_function') + assert.strictEqual(matchWildCards('a*w*p*'), 'async_worker_persistent') + assert.strictEqual(matchWildCards('fun*ref'), 'fun*ref') + assert.strictEqual(matchWildCards('fun*ref*'), 'function_reference') + assert.strictEqual(matchWildCards('*reference'), 'function_reference object_reference reference') + + console.log('ALL tests passed') +} diff --git a/unit-test/spawnTask.js b/unit-test/spawnTask.js new file mode 100644 index 000000000..b14776481 --- /dev/null +++ b/unit-test/spawnTask.js @@ -0,0 +1,23 @@ +const { spawn } = require("child_process"); + +module.exports.runChildProcess = async function (command, options) { + const childProcess = spawn("node", [command], options); + + childProcess.stdout.on('data', data => { + console.log(`${data}`); + }); + childProcess.stderr.on('data', data => { + console.log(`error: ${data}`); + }); + + return new Promise((resolve, reject) => { + childProcess.on('error', (error) => { + console.log(`error: ${error.message}`); + reject(error); + }); + childProcess.on('close', code => { + console.log(`child process exited with code ${code}`); + resolve(); + }); + }); +} diff --git a/unit-test/test.js b/unit-test/test.js new file mode 100644 index 000000000..5d5667f04 --- /dev/null +++ b/unit-test/test.js @@ -0,0 +1,22 @@ +'use strict'; +const path = require('path'); +const runChildProcess = require('./spawnTask').runChildProcess; + +const executeTests = async function() { + try { + const workingDir = path.join(__dirname, '../'); + const relativeBuildPath = path.join('../', 'unit-test'); + const buildPath = path.join(__dirname, './unit-test'); + const envVars = { ...process.env, REL_BUILD_PATH: relativeBuildPath, BUILD_PATH: buildPath}; + + console.log('Starting to run tests in ', buildPath, new Date()); + + await runChildProcess('test', {cwd: workingDir, env: envVars}); + + console.log('Completed running tests', new Date()); + } catch(e) { + console.log('Error occured running tests', new Date()); + } +} + +executeTests();