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/.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 @@ + diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml new file mode 100644 index 000000000..4d8315c9b --- /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, 16.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..83ab4e47e 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] @@ -12,6 +12,7 @@ jobs: - node/12 - node/14 - node/15 + - node/16 compiler: - gcc - clang diff --git a/CHANGELOG.md b/CHANGELOG.md index e77f4109e..0fb67881a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,191 @@ # 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: + +#### 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: + +#### 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: + +#### 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: + +#### 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/CONTRIBUTING.md b/CONTRIBUTING.md index 0d9fdf926..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 @@ -5,12 +32,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 +46,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 +62,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 +75,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 +88,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..673139708 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)** @@ -70,7 +70,7 @@ and node-addon-api. - **[Contributors](#contributors)** - **[License](#license)** -## **Current version: 3.1.0** +## **Current version: 4.2.0** (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) @@ -78,12 +78,12 @@ 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. -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) @@ -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 @@ -229,23 +229,31 @@ 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** -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 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** @@ -282,4 +290,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/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/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/async_worker_variants.md b/doc/async_worker_variants.md index 54007762c..591cd8a57 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); ``` @@ -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 @@ -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); ``` 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); 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..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); @@ -108,10 +106,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/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/doc/error_handling.md b/doc/error_handling.md index 9a0ef349e..642c223e5 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -17,12 +17,13 @@ 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) -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. @@ -38,11 +39,11 @@ 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 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,10 +68,10 @@ 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(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // other C++ statements // ... @@ -81,10 +82,10 @@ 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(); +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 @@ -123,11 +188,11 @@ 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 = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Error e = env.GetAndClearPendingException(); @@ -139,11 +204,11 @@ 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 = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Napi::Error e = env.GetAndClearPendingException(); @@ -154,10 +219,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..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. @@ -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/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/object.md b/doc/object.md index 3ce7f75eb..9830675a7 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. @@ -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() @@ -206,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). @@ -215,14 +209,38 @@ 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& - - const std::vector& + - const std::initializer_list& + - const std::vector& 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 value 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 they are +writable. + ### operator\[\]() ```cpp @@ -271,3 +289,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 diff --git a/doc/object_reference.md b/doc/object_reference.md index f2d8905a8..38d6acc0e 100644 --- a/doc/object_reference.md +++ b/doc/object_reference.md @@ -75,13 +75,13 @@ 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. ### 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/doc/object_wrap.md b/doc/object_wrap.md index 7b8fc9c28..d90da42c4 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 @@ -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/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/property_descriptor.md b/doc/property_descriptor.md index 793e53626..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 ( @@ -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. @@ -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 ( @@ -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/doc/reference.md b/doc/reference.md index 108c009bb..42ddc0609 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`: @@ -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 @@ -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. diff --git a/doc/setup.md b/doc/setup.md index 7a7590b4b..49e039c8f 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': ["::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. @@ -263,7 +263,7 @@ void CallJs(Napi::Env env, Function callback, Context *context, // Is the JavaScript environment still available to call into, eg. the TSFN is // not aborted if (env != nullptr) { - // On N-API 5+, the `callback` parameter is optional; however, this example + // On Node-API 5+, the `callback` parameter is optional; however, this example // does ensure a callback is provided. if (callback != nullptr) { callback.Call(context->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..1cdc48321 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 @@ -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; 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/napi-inl.h b/napi-inl.h index a5bdff854..5e51cc32b 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. @@ -13,6 +14,7 @@ #include #include #include +#include namespace Napi { @@ -376,6 +378,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 //////////////////////////////////////////////////////////////////////////////// @@ -425,21 +493,41 @@ 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 +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> @@ -657,32 +745,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); } //////////////////////////////////////////////////////////////////////////////// @@ -899,6 +987,12 @@ 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) { + // 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); NAPI_THROW_IF_FAILED(env, status, String()); @@ -907,6 +1001,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); @@ -993,8 +1093,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 MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +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() { @@ -1109,12 +1257,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; } @@ -1147,199 +1306,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 void 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_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void 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_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void 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_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline void Object::Set(const std::string& utf8name, const ValueType& value) { - Set(utf8name.c_str(), 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 void 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_VOID(_env, status); + 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 void 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_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline void Object::DefineProperties(const std::initializer_list& properties) { +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_VOID(_env, status); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline void Object::DefineProperties(const std::vector& properties) { +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_VOID(_env, status); + 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 @@ -1378,6 +1530,18 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } +#if NAPI_VERSION >= 8 +inline MaybeOrValue Object::Freeze() { + napi_status status = napi_object_freeze(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Seal() { + napi_status status = napi_object_seal(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif // NAPI_VERSION >= 8 + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// @@ -1881,8 +2045,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; @@ -1991,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, @@ -2021,53 +2187,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, @@ -2075,24 +2249,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); } //////////////////////////////////////////////////////////////////////////////// @@ -2364,7 +2541,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; @@ -2373,12 +2557,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) { @@ -2559,7 +2769,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; } @@ -2567,7 +2777,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; } @@ -2655,99 +2865,149 @@ 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 void ObjectReference::Set(const char* utf8name, napi_value value) { +inline MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 { +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 void ObjectReference::Set(uint32_t index, napi_value value) { +inline MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue 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 MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) { HandleScope scope(_env); - Value().Set(index, numberValue); + return Value().Set(index, numberValue); } //////////////////////////////////////////////////////////////////////////////// @@ -2779,105 +3039,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 } //////////////////////////////////////////////////////////////////////////////// @@ -3615,10 +3970,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 @@ -4148,10 +4503,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); @@ -5639,6 +5993,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 3db1b6da6..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,17 +121,20 @@ 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) + //////////////////////////////////////////////////////////////////////////////// -/// 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,25 +185,93 @@ 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. + /// 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 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 { + 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); @@ -194,9 +288,17 @@ 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 + CleanupHook AddCleanupHook(Hook hook); + + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 #if NAPI_VERSION > 5 template T* GetInstanceData(); @@ -216,7 +318,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. /// @@ -232,7 +355,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 +373,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; @@ -307,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; @@ -322,80 +450,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 +544,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,117 +560,145 @@ 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 MaybeOrValue WellKnown(napi_env, const std::string& name); + + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static MaybeOrValue For(napi_env env, + const std::string& description); + + // Create a symbol in the global registry, C style string (null terminated) + static MaybeOrValue For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static MaybeOrValue For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static MaybeOrValue 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. }; /// 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; @@ -549,15 +716,15 @@ namespace Napi { Key _key; friend class Napi::Object; - }; + }; /// 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 []( @@ -575,174 +742,183 @@ 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 - void 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 - void 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 - void Set( - const char* utf8name, ///< UTF-8 encoded null-terminated property name - const ValueType& value - ); + MaybeOrValue 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 + 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 - void 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. - void DefineProperty( - const PropertyDescriptor& property ///< Descriptor for the property to be defined + /// + /// 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. - void DefineProperties( - const std::initializer_list& properties + /// + /// 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. - void DefineProperties( - const std::vector& properties + /// + /// 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 ); /// 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 @@ -752,6 +928,18 @@ namespace Napi { inline void AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint); +#if NAPI_VERSION >= 8 + /// 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 }; template @@ -792,46 +980,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 +1054,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 +1110,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 +1130,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 +1191,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. @@ -1059,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 { @@ -1175,9 +1395,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. + // 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: @@ -1204,28 +1426,28 @@ 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; - 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); - - 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); + 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: + protected: ObjectReference(const ObjectReference&); }; @@ -1241,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. @@ -1274,43 +1506,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; @@ -1320,38 +1557,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 }); @@ -1360,8 +1601,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 @@ -2287,10 +2528,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 @@ -2414,9 +2655,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; 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', + }, + }], + ], } diff --git a/package.json b/package.json index 7538c84f9..c3cbb5d07 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" @@ -115,6 +119,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 +147,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 +167,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 +191,10 @@ "name": "legendecas", "url": "https://github.com/legendecas" }, + { + "name": "LongYinan", + "url": "https://github.com/Brooooooklyn" + }, { "name": "Lovell Fuller", "url": "https://github.com/lovell" @@ -259,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" @@ -275,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" @@ -296,13 +324,19 @@ "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", "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", "safe-buffer": "^5.1.1" }, @@ -323,7 +357,6 @@ "license": "MIT", "main": "index.js", "name": "node-addon-api", - "optionalDependencies": {}, "readme": "README.md", "repository": { "type": "git", @@ -344,10 +377,12 @@ "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": "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)", + "preunit": "filter=\"$npm_config_filter\" node-gyp rebuild -C unit-test", + "unit": "filter=\"$npm_config_filter\" node unit-test/test" }, "pre-commit": "lint", - "version": "3.1.0", + "version": "4.2.0", "support": true } 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.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.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/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.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 91% rename from test/arraybuffer.js rename to test/array_buffer.js index 363de17d9..942686f2f 100644 --- a/test/arraybuffer.js +++ b/test/array_buffer.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.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 63% rename from test/asynccontext.js rename to test/async_context.js index e9b4aabc3..d6a3fc5aa 100644 --- a/test/asynccontext.js +++ b/test/async_context.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.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 82% rename from test/asyncprogressqueueworker.js rename to test/async_progress_queue_worker.js index 4bb525e9a..e7150012d 100644 --- a/test/asyncprogressqueueworker.js +++ b/test/async_progress_queue_worker.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.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 85% rename from test/asyncprogressworker.js rename to test/async_progress_worker.js index 285fd2a91..2a81b204d 100644 --- a/test/asyncprogressworker.js +++ b/test/async_progress_worker.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.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 90% rename from test/asyncworker.js rename to test/async_worker.js index 0f008bb33..46ab14340 100644 --- a/test/asyncworker.js +++ b/test/async_worker.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/asyncworker-nocallback.js b/test/async_worker_nocallback.js similarity index 59% rename from test/asyncworker-nocallback.js rename to test/async_worker_nocallback.js index fa9c172c0..d4e28ce6a 100644 --- a/test/asyncworker-nocallback.js +++ b/test/async_worker_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.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 56% rename from test/asyncworker-persistent.js rename to test/async_worker_persistent.js index d584086e7..f4febac45 100644 --- a/test/asyncworker-persistent.js +++ b/test/async_worker_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/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.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/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.cc b/test/bigint.cc index 1f89db84a..300e5a498 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) { @@ -44,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); @@ -59,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); } 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/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.cc b/test/binding.cc index fa37a8d53..389f61b97 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -30,9 +30,11 @@ 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); +Object InitFunctionReference(Env env); Object InitHandleScope(Env env); Object InitMovableCallbacks(Env env); Object InitMemoryManagement(Env env); @@ -57,7 +59,9 @@ 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); Object InitObjectWrapConstructorException(Env env); Object InitObjectWrapRemoveWrap(Env env); @@ -66,6 +70,13 @@ 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 + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +Object InitMaybeCheck(Env env); +#endif Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -78,6 +89,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)); @@ -97,9 +109,13 @@ 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)); + exports.Set("functionreference", InitFunctionReference(env)); exports.Set("name", InitName(env)); exports.Set("handlescope", InitHandleScope(env)); exports.Set("movable_callbacks", InitMovableCallbacks(env)); @@ -110,13 +126,14 @@ 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)); 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", @@ -139,6 +156,13 @@ 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 + +#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 c9f82ee20..2969dc1f9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,15 +1,17 @@ { 'target_defaults': { 'includes': ['../common.gypi'], - 'sources': [ + 'include_dirs': ['./common'], + 'variables': { + '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', @@ -21,23 +23,33 @@ 'callbackscope.cc', 'dataview/dataview.cc', 'dataview/dataview_read_write.cc', + 'env_cleanup.cc', 'error.cc', 'external.cc', 'function.cc', + 'function_reference.cc', 'handlescope.cc', + 'maybe/check.cc', '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', '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', 'run_script.cc', + "symbol.cc", 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_existing_tsfn.cc', 'threadsafe_function/threadsafe_function_ptr.cc', @@ -53,27 +65,52 @@ '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', ], + '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_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, + { + '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/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..9f29c07a7 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 = () => {}; @@ -74,3 +75,39 @@ exports.mustNotCall = function(msg) { assert.fail(msg || 'function should not have been called'); }; }; + +exports.runTest = async function(test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { + buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; + + const bindings = [ + 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) { + await Promise.resolve(test(require(item))) + .finally(exports.mustCall()); + } +} + +exports.runTestWithBindingPath = async function(test, buildType, buildPathRoot = process.env.BUILD_PATH || '') { + buildType = buildType || process.config.target_defaults.default_configuration || 'Release'; + + const bindings = [ + 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) { + 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/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/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/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/error.cc b/test/error.cc index 832cad525..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(); } @@ -165,7 +222,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; @@ -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.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/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/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.cc b/test/function.cc index b0ae92c7d..3c5033d8a 100644 --- a/test/function.cc +++ b/test/function.cc @@ -1,4 +1,6 @@ +#include #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,6 +8,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 +54,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 MaybeUnwrap( + func.Call(std::initializer_list{info[1], info[2], info[3]})); } Value CallWithVector(const CallbackInfo& info) { @@ -56,13 +66,35 @@ 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) { + 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 MaybeUnwrap(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 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) { @@ -73,17 +105,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) { @@ -93,7 +127,17 @@ 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) { + 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 MaybeUnwrap(func.New(args.size(), args.data())); } void IsConstructCall(const CallbackInfo& info) { @@ -102,11 +146,65 @@ void IsConstructCall(const CallbackInfo& info) { 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 MaybeUnwrap(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 +213,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 +246,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 +259,37 @@ 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; + + 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 8ab742c27..7536f62e8 100644 --- a/test/function.js +++ b/test/function.js @@ -1,13 +1,17 @@ '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); + testLambda(binding.function.lambda); +}); 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 +30,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 +86,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 +105,17 @@ 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(() => {}); + }); +} + +function testLambda(binding) { + assert.ok(binding.lambdaWithNoCapture()); + assert.ok(binding.lambdaWithCapture()); + assert.ok(binding.lambdaWithMoveOnlyCapture()); } diff --git a/test/function_reference.cc b/test/function_reference.cc new file mode 100644 index 000000000..aaa899e11 --- /dev/null +++ b/test/function_reference.cc @@ -0,0 +1,31 @@ +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +namespace { +Value Call(const CallbackInfo& info) { + HandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return MaybeUnwrapOr(ref.Call({}), Value()); +} + +Value Construct(const CallbackInfo& info) { + HandleScope scope(info.Env()); + FunctionReference ref; + ref.Reset(info[0].As()); + + return MaybeUnwrapOr(ref.New({}), Object()); +} +} // 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/function_reference.js b/test/function_reference.js new file mode 100644 index 000000000..3266f0031 --- /dev/null +++ b/test/function_reference.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/); +} 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..295ed3f36 --- /dev/null +++ b/test/globalObject/global_object_delete_property.cc @@ -0,0 +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(), 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(), + 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(), + 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(), MaybeUnwrap(globalObject.Delete(key))); +} diff --git a/test/globalObject/global_object_delete_property.js b/test/globalObject/global_object_delete_property.js new file mode 100644 index 000000000..c3f3dad95 --- /dev/null +++ b/test/globalObject/global_object_delete_property.js @@ -0,0 +1,61 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +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(propertyCheckExistenceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistenceFunction(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..dd112043c --- /dev/null +++ b/test/globalObject/global_object_get_property.cc @@ -0,0 +1,40 @@ +#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 MaybeUnwrap(globalObject.Get(key)); +} + +Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + Number key = info[0].As(); + return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); +} + +Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { + Object globalObject = info.Env().Global(); + String cStrkey = info[0].As(); + 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 MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); +} + +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..83b337368 --- /dev/null +++ b/test/globalObject/global_object_get_property.js @@ -0,0 +1,57 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +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..89c299913 --- /dev/null +++ b/test/globalObject/global_object_has_own_property.cc @@ -0,0 +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(), + 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(), + 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(), + MaybeUnwrap(globalObject.HasOwnProperty(key))); +} 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..30deeecdd --- /dev/null +++ b/test/globalObject/global_object_has_own_property.js @@ -0,0 +1,48 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +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(propertyCheckExistenceFunction, errMsg) { + assert.throws(() => { + propertyCheckExistenceFunction(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..d934efae9 --- /dev/null +++ b/test/globalObject/global_object_set_property.js @@ -0,0 +1,58 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +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/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 35db3187b..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); @@ -82,11 +100,12 @@ 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); } -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); @@ -97,33 +116,38 @@ 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 && !filterConditionFiles.length) { + testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1); } (async function() { -console.log(`Testing with N-API Version '${napiVersion}'.`); + 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) { - 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/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/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.cc b/test/name.cc index 3a296ec58..d8556e154 100644 --- a/test/name.cc +++ b/test/name.cc @@ -82,11 +82,24 @@ Value CheckSymbol(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].Type() == napi_symbol); } +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, 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 4e9659312..717f1862f 100644 --- a/test/name.js +++ b/test/name.js @@ -1,13 +1,17 @@ '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'; + + assert.throws(binding.name.nullStringShouldThrow, { + name: 'Error', + message: 'Error in native callback', + }); 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)); diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index bd2488435..ca69e5387 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -1,27 +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(), 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/delete_property.js b/test/object/delete_property.js index 8c313d03e..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) { @@ -25,6 +23,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/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.cc b/test/object/get_property.cc index 0cdaa50d7..523f99199 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -1,27 +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 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/get_property.js b/test/object/get_property.js index 8028165c3..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) { @@ -23,6 +21,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_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_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.cc b/test/object/has_property.cc index 0a1a45942..fa410833f 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -1,27 +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(), + 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/has_property.js b/test/object/has_property.js index a1b942dfb..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) { @@ -24,6 +22,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..a73b8e354 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -1,20 +1,23 @@ #include "napi.h" +#include "test_helper.h" 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); 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); Value DeletePropertyWithNapiValue(const CallbackInfo& info); Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info); Value DeletePropertyWithCStyleString(const CallbackInfo& info); @@ -27,6 +30,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); @@ -92,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; } @@ -252,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) { @@ -265,6 +269,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 +280,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 +292,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); 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.cc b/test/object/object_freeze_seal.cc new file mode 100644 index 000000000..40aaeb467 --- /dev/null +++ b/test/object/object_freeze_seal.cc @@ -0,0 +1,25 @@ +#include "napi.h" +#include "test_helper.h" + +#if (NAPI_VERSION > 7) + +using namespace Napi; + +Value Freeze(const CallbackInfo& info) { + Object obj = info[0].As(); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Freeze(), false)); +} + +Value Seal(const CallbackInfo& info) { + Object obj = info[0].As(); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Seal(), false)); +} + +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..62c1c48f3 --- /dev/null +++ b/test/object/object_freeze_seal.js @@ -0,0 +1,61 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('../common').runTest(test); + +function test(binding) { + { + const obj = { x: 'a', y: 'b', z: 'c' }; + assert.strictEqual(binding.object_freeze_seal.freeze(obj), true); + 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 = 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' }; + assert.strictEqual(binding.object_freeze_seal.seal(obj), true); + 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'; + } + + { + const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { + preventExtensions() { + throw new Error('foo'); + }, + }); + + assert.throws(() => { + binding.object_freeze_seal.seal(obj); + }, /foo/); + } +} diff --git a/test/object/set_property.cc b/test/object/set_property.cc index 19ab245b4..5f10b4f09 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -1,31 +1,37 @@ #include "napi.h" +#include "test_helper.h" 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(), + MaybeUnwrapOr(obj.Set(static_cast(key), value), false)); } -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(), MaybeUnwrapOr(obj.Set(key, value), false)); } -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(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value().c_str(), value), false)); } -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(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); } diff --git a/test/object/set_property.js b/test/object/set_property.js index 9b64cc5dd..86fab3b75 100644 --- a/test/object/set_property.js +++ b/test/object/set_property.js @@ -1,15 +1,13 @@ '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) { const obj = {}; - nativeSetProperty(obj, 'test', 1); + assert.strictEqual(nativeSetProperty(obj, 'test', 1), true); assert.strictEqual(obj.test, 1); } 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.cc b/test/object_reference.cc similarity index 90% rename from test/objectreference.cc rename to test/object_reference.cc index 3143216dc..34f952088 100644 --- a/test/objectreference.cc +++ b/test/object_reference.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/objectreference.js b/test/object_reference.js similarity index 97% rename from test/objectreference.js rename to test/object_reference.js index 55b95dba6..daecb5958 100644 --- a/test/objectreference.js +++ b/test/object_reference.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.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/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-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 85% rename from test/objectwrap-removewrap.js rename to 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_worker_thread.js b/test/objectwrap_worker_thread.js index 5e5e50b7e..e5cfdb81a 100644 --- a/test/objectwrap_worker_thread.js +++ b/test/objectwrap_worker_thread.js @@ -1,15 +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; - test(require(`./build/${buildType}/binding.node`)); - test(require(`./build/${buildType}/binding_noexcept.node`)); +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/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.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/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/symbol.cc b/test/symbol.cc new file mode 100644 index 000000000..08ea80393 --- /dev/null +++ b/test/symbol.cc @@ -0,0 +1,79 @@ +#include +#include "test_helper.h" +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 MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), registrySymbol.Utf8Value().c_str())); +} + +Symbol FetchSymbolFromGlobalRegistry(const Napi::CallbackInfo& info) { + String registrySymbol = info[0].As(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), registrySymbol)); +} + +Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value())); +} + +Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + return MaybeUnwrap( + Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str())); +} + +Symbol TestUndefinedSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return MaybeUnwrap(Napi::Symbol::For(env, env.Undefined())); +} + +Symbol TestNullSymbolsCanBeCreated(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return MaybeUnwrap(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..7601feb2f --- /dev/null +++ b/test/symbol.js @@ -0,0 +1,73 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + + +function test(binding) +{ + const majorNodeVersion = process.versions.node.split('.')[0]; + + let wellKnownSymbolFunctions = ['asyncIterator','hasInstance','isConcatSpreadable', 'iterator','match','replace','search','split','species','toPrimitive','toStringTag','unscopables']; + if (majorNodeVersion >= 12) { + wellKnownSymbolFunctions.push('matchAll'); + } + + 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()); +} 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/threadsafe_function/threadsafe_function.js b/test/threadsafe_function/threadsafe_function.js index 419c214f8..5236f28ea 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) { @@ -29,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/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.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_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.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/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.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(); } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function.js b/test/typed_threadsafe_function/typed_threadsafe_function.js index 7aa8cc2ad..51bbf2f73 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) { @@ -29,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') { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js index 2651586a0..f43da1d38 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js +++ b/test/typed_threadsafe_function/typed_threadsafe_function_ctx.js @@ -1,14 +1,12 @@ '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 = { }; - 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/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_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.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( 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 = {}; 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. 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();