From 24f90eeb2f5d56a13b600ee00fc3a47cab052e8e Mon Sep 17 00:00:00 2001 From: Kyle Farnung Date: Thu, 29 Jun 2017 15:41:06 -0700 Subject: [PATCH] Add support for Error::Fatal * Added static method `Error::Fatal` to invoke `napi_fatal_error` * Added `node_internals.cc/h` to shim missing internal functions * Added a test for `Error::Fatal` * Replaced usage of assert with calls to `Error::Fatal` --- napi-inl.h | 30 ++++++---- napi.h | 2 + src/node_api.cc | 133 ++++++++++++++++++++++++++++++++++-------- src/node_api.gyp | 1 + src/node_api.h | 23 +++++++- src/node_api_types.h | 2 +- src/node_internals.cc | 97 ++++++++++++++++++++++++++++++ src/node_internals.h | 69 ++++++++++++++++++++++ test/error.cc | 5 ++ test/error.js | 20 ++++++- test/index.js | 6 +- test/napi_child.js | 7 +++ 12 files changed, 350 insertions(+), 45 deletions(-) create mode 100644 src/node_internals.cc create mode 100644 src/node_internals.h create mode 100644 test/napi_child.js diff --git a/napi-inl.h b/napi-inl.h index 22cbccdc7..dec582001 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -9,7 +9,6 @@ // Note: Do not include this file directly! Include "napi.h" instead. -#include #include namespace Napi { @@ -42,6 +41,13 @@ namespace details { #endif // NAPI_CPP_EXCEPTIONS +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + do { \ + if ((status) != napi_ok) { \ + Error::Fatal((location), (message)); \ + } \ + } while (0) + // For use in JS to C++ callback wrappers to catch any Napi::Error exceptions // and rethrow them as JavaScript exceptions before returning from the callback. template @@ -1418,12 +1424,12 @@ inline Error Error::New(napi_env env) { const napi_extended_error_info* info; status = napi_get_last_error_info(env, &info); - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); if (status == napi_ok) { if (info->error_code == napi_pending_exception) { status = napi_get_and_clear_last_exception(env, &error); - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); } else { const char* error_message = info->error_message != nullptr ? @@ -1431,11 +1437,11 @@ inline Error Error::New(napi_env env) { bool isExceptionPending; status = napi_is_exception_pending(env, &isExceptionPending); - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); if (isExceptionPending) { status = napi_get_and_clear_last_exception(env, &error); - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception"); } napi_value message; @@ -1444,7 +1450,7 @@ inline Error Error::New(napi_env env) { error_message, std::strlen(error_message), &message); - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); if (status == napi_ok) { switch (info->error_code) { @@ -1458,7 +1464,7 @@ inline Error Error::New(napi_env env) { status = napi_create_error(env, message, &error); break; } - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); } } } @@ -1474,6 +1480,10 @@ inline Error Error::New(napi_env env, const std::string& message) { return Error::New(env, message.c_str(), message.size(), napi_create_error); } +inline NAPI_NO_RETURN void Error::Fatal(const char* location, const char* message) { + napi_fatal_error(location, message); +} + inline Error::Error() : ObjectReference(), _message(nullptr) { } @@ -1483,7 +1493,7 @@ inline Error::Error(napi_env env, napi_value value) : ObjectReference(env, nullp // Avoid infinite recursion in the failure case. // Don't try to construct & throw another Error instance. - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); } } @@ -1661,9 +1671,7 @@ inline Reference::Reference(const Reference& other) // Copying is a limited scenario (currently only used for Error object) and always creates a // strong reference to the given value even if the incoming reference is weak. napi_status status = napi_create_reference(_env, value, 1, &_ref); - - // TODO - Switch to napi_fatal_error() once it exists. - assert(status == napi_ok); + NAPI_FATAL_IF_FAILED(status, "Reference::Reference", "napi_create_reference"); } } diff --git a/napi.h b/napi.h index 9862771e1..1c1c15b10 100644 --- a/napi.h +++ b/napi.h @@ -1067,6 +1067,8 @@ namespace Napi { static Error New(napi_env env, const char* message); static Error New(napi_env env, const std::string& message); + static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + Error(); Error(napi_env env, napi_value value); diff --git a/src/node_api.cc b/src/node_api.cc index 6dcd0a7c7..942af0b29 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -17,6 +17,9 @@ #include #include "uv.h" #include "node_api.h" +#include "node_internals.h" + +#define NAPI_VERSION 1 static napi_status napi_set_last_error(napi_env env, napi_status error_code, @@ -154,14 +157,20 @@ class HandleScopeWrapper { // across different versions. class EscapableHandleScopeWrapper { public: - explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {} + explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) + : scope(isolate), escape_called_(false) {} + bool escape_called() const { + return escape_called_; + } template v8::Local Escape(v8::Local handle) { + escape_called_ = true; return scope.Escape(handle); } private: v8::EscapableHandleScope scope; + bool escape_called_; }; napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) { @@ -716,7 +725,8 @@ const char* error_messages[] = {nullptr, "An array was expected", "Unknown failure", "An exception is pending", - "The async work item was cancelled"}; + "The async work item was cancelled", + "napi_escape_handle already called on scope"}; static napi_status napi_clear_last_error(napi_env env) { CHECK_ENV(env); @@ -744,10 +754,14 @@ napi_status napi_get_last_error_info(napi_env env, CHECK_ENV(env); CHECK_ARG(env, result); + // you must update this assert to reference the last message + // in the napi_status enum each time a new error message is added. + // We don't have a napi_status_last as this would result in an ABI + // change each time a message was added. static_assert( - (sizeof (error_messages) / sizeof (*error_messages)) == napi_status_last, + node::arraysize(error_messages) == napi_escape_called_twice + 1, "Count of error messages must match count of error values"); - assert(env->last_error.error_code < napi_status_last); + assert(env->last_error.error_code <= napi_escape_called_twice); // Wait until someone requests the last error information to fetch the error // message string @@ -758,6 +772,11 @@ napi_status napi_get_last_error_info(napi_env env, return napi_ok; } +NAPI_NO_RETURN void napi_fatal_error(const char* location, + const char* message) { + node::FatalError(location, message); +} + napi_status napi_create_function(napi_env env, const char* utf8name, napi_callback cb, @@ -817,9 +836,6 @@ napi_status napi_define_class(napi_env env, v8::Local tpl = v8::FunctionTemplate::New( isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); - // we need an internal field to stash the wrapped object - tpl->InstanceTemplate()->SetInternalFieldCount(1); - v8::Local name_string; CHECK_NEW_FROM_UTF8(env, name_string, utf8name); tpl->SetClassName(name_string); @@ -991,6 +1007,28 @@ napi_status napi_get_property(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, key); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local k = v8impl::V8LocalValueFromJsValue(key); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, k); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != NULL) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, @@ -1128,6 +1166,26 @@ napi_status napi_get_element(napi_env env, return GET_RETURN_STATUS(env); } +napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result) { + NAPI_PREAMBLE(env); + + v8::Isolate* isolate = env->isolate; + v8::Local context = isolate->GetCurrentContext(); + v8::Local obj; + + CHECK_TO_OBJECT(env, context, obj, object); + v8::Maybe delete_maybe = obj->Delete(context, index); + CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure); + + if (result != NULL) + *result = delete_maybe.FromMaybe(false); + + return GET_RETURN_STATUS(env); +} + napi_status napi_define_properties(napi_env env, napi_value object, size_t property_count, @@ -1948,14 +2006,24 @@ napi_status napi_wrap(napi_env env, CHECK_ARG(env, js_object); v8::Isolate* isolate = env->isolate; - v8::Local obj = - v8impl::V8LocalValueFromJsValue(js_object).As(); + v8::Local context = isolate->GetCurrentContext(); + + v8::Local value = v8impl::V8LocalValueFromJsValue(js_object); + RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); + v8::Local obj = value.As(); - // Only objects that were created from a NAPI constructor's prototype - // via napi_define_class() can be (un)wrapped. - RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg); + // Create a wrapper object with an internal field to hold the wrapped pointer. + v8::Local wrapperTemplate = + v8::ObjectTemplate::New(isolate); + wrapperTemplate->SetInternalFieldCount(1); + v8::Local wrapper = + wrapperTemplate->NewInstance(context).ToLocalChecked(); + wrapper->SetInternalField(0, v8::External::New(isolate, native_object)); - obj->SetInternalField(0, v8::External::New(isolate, native_object)); + // Insert the wrapper into the object's prototype chain. + v8::Local proto = obj->GetPrototype(); + CHECK(wrapper->SetPrototype(context, proto).FromJust()); + CHECK(obj->SetPrototype(context, wrapper).FromJust()); if (result != nullptr) { // The returned reference should be deleted via napi_delete_reference() @@ -1986,11 +2054,18 @@ napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); v8::Local obj = value.As(); - // Only objects that were created from a NAPI constructor's prototype - // via napi_define_class() can be (un)wrapped. - RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg); - - v8::Local unwrappedValue = obj->GetInternalField(0); + // Search the object's prototype chain for the wrapper with an internal field. + // Usually the wrapper would be the first in the chain, but it is OK for + // other objects to be inserted in the prototype chain. + v8::Local wrapper = obj; + do { + v8::Local proto = wrapper->GetPrototype(); + RETURN_STATUS_IF_FALSE( + env, !proto.IsEmpty() && proto->IsObject(), napi_invalid_arg); + wrapper = proto.As(); + } while (wrapper->InternalFieldCount() != 1); + + v8::Local unwrappedValue = wrapper->GetInternalField(0); RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg); *result = unwrappedValue.As()->Value(); @@ -2195,9 +2270,12 @@ napi_status napi_escape_handle(napi_env env, v8impl::EscapableHandleScopeWrapper* s = v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope); - *result = v8impl::JsValueFromV8LocalValue( - s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); - return napi_clear_last_error(env); + if (!s->escape_called()) { + *result = v8impl::JsValueFromV8LocalValue( + s->Escape(v8impl::V8LocalValueFromJsValue(escapee))); + return napi_clear_last_error(env); + } + return napi_set_last_error(env, napi_escape_called_twice); } napi_status napi_new_instance(napi_env env, @@ -2250,7 +2328,7 @@ napi_status napi_instanceof(napi_env env, } if (env->has_instance_available) { - napi_value value, js_result, has_instance = nullptr; + napi_value value, js_result = nullptr, has_instance = nullptr; napi_status status = napi_generic_failure; napi_valuetype value_type; @@ -2530,7 +2608,7 @@ napi_status napi_create_arraybuffer(napi_env env, v8::ArrayBuffer::New(isolate, byte_length); // Optionally return a pointer to the buffer's data, to avoid another call to - // retreive it. + // retrieve it. if (data != nullptr) { *data = buffer->GetContents().Data(); } @@ -2713,6 +2791,13 @@ napi_status napi_get_typedarray_info(napi_env env, return napi_clear_last_error(env); } +napi_status napi_get_version(napi_env env, uint32_t* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = NAPI_VERSION; + return napi_clear_last_error(env); +} + namespace uvimpl { static napi_status ConvertUVErrorCode(int code) { @@ -2781,7 +2866,7 @@ class Work { // report it as a fatal exception. (There is no JavaScript on the // callstack that can possibly handle it.) if (!env->last_exception.IsEmpty()) { - v8::TryCatch try_catch; + v8::TryCatch try_catch(env->isolate); env->isolate->ThrowException( v8::Local::New(env->isolate, env->last_exception)); node::FatalException(env->isolate, try_catch); diff --git a/src/node_api.gyp b/src/node_api.gyp index a23d9f581..07921db26 100644 --- a/src/node_api.gyp +++ b/src/node_api.gyp @@ -8,6 +8,7 @@ 'type': 'static_library', 'sources': [ 'node_api.cc', + 'node_internals.cc', ], 'defines': [ 'EXTERNAL_NAPI', diff --git a/src/node_api.h b/src/node_api.h index 29c5c513f..96864d681 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -37,6 +37,12 @@ # define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) #endif +#ifdef __GNUC__ +#define NAPI_NO_RETURN __attribute__((noreturn)) +#else +#define NAPI_NO_RETURN +#endif + typedef void (*napi_addon_register_func)(napi_env env, napi_value exports, @@ -104,6 +110,9 @@ NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); +NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, + const char* message); + // Getters for defined singletons NAPI_EXTERN napi_status napi_get_undefined(napi_env env, napi_value* result); NAPI_EXTERN napi_status napi_get_null(napi_env env, napi_value* result); @@ -226,6 +235,10 @@ NAPI_EXTERN napi_status napi_get_property(napi_env env, napi_value object, napi_value key, napi_value* result); +NAPI_EXTERN napi_status napi_delete_property(napi_env env, + napi_value object, + napi_value key, + bool* result); NAPI_EXTERN napi_status napi_set_named_property(napi_env env, napi_value object, const char* utf8name, @@ -250,6 +263,10 @@ NAPI_EXTERN napi_status napi_get_element(napi_env env, napi_value object, uint32_t index, napi_value* result); +NAPI_EXTERN napi_status napi_delete_element(napi_env env, + napi_value object, + uint32_t index, + bool* result); NAPI_EXTERN napi_status napi_define_properties(napi_env env, napi_value object, @@ -478,6 +495,10 @@ NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, napi_async_work work); + +// version management +NAPI_EXTERN napi_status napi_get_version(napi_env env, uint32_t* result); + EXTERN_C_END -#endif // SRC_NODE_API_H__ +#endif // SRC_NODE_API_H_ diff --git a/src/node_api_types.h b/src/node_api_types.h index 4bf1b8263..43102c519 100644 --- a/src/node_api_types.h +++ b/src/node_api_types.h @@ -67,7 +67,7 @@ typedef enum { napi_generic_failure, napi_pending_exception, napi_cancelled, - napi_status_last + napi_escape_called_twice } napi_status; typedef napi_value (*napi_callback)(napi_env env, diff --git a/src/node_internals.cc b/src/node_internals.cc new file mode 100644 index 000000000..802282ef1 --- /dev/null +++ b/src/node_internals.cc @@ -0,0 +1,97 @@ +#include "node_internals.h" +#include +#include +#include +#include "uv.h" + +#if defined(_MSC_VER) +#define getpid GetCurrentProcessId +#else +#include // getpid +#endif + +namespace node { + +static void PrintErrorString(const char* format, ...) { + va_list ap; + va_start(ap, format); +#ifdef _WIN32 + HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + + // Check if stderr is something other than a tty/console + if (stderr_handle == INVALID_HANDLE_VALUE || + stderr_handle == nullptr || + uv_guess_handle(_fileno(stderr)) != UV_TTY) { + vfprintf(stderr, format, ap); + va_end(ap); + return; + } + + // Fill in any placeholders + int n = _vscprintf(format, ap); + std::vector out(n + 1); + vsprintf(out.data(), format, ap); + + // Get required wide buffer size + n = MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, nullptr, 0); + + std::vector wbuf(n); + MultiByteToWideChar(CP_UTF8, 0, out.data(), -1, wbuf.data(), n); + + // Don't include the null character in the output + CHECK_GT(n, 0); + WriteConsoleW(stderr_handle, wbuf.data(), n - 1, nullptr, nullptr); +#else + vfprintf(stderr, format, ap); +#endif + va_end(ap); +} + +void DumpBacktrace(FILE* fp) { +} + +NO_RETURN void Abort() { + DumpBacktrace(stderr); + fflush(stderr); + ABORT_NO_BACKTRACE(); +} + +NO_RETURN void Assert(const char* const (*args)[4]) { + auto filename = (*args)[0]; + auto linenum = (*args)[1]; + auto message = (*args)[2]; + auto function = (*args)[3]; + + char exepath[256]; + size_t exepath_size = sizeof(exepath); + if (uv_exepath(exepath, &exepath_size)) + snprintf(exepath, sizeof(exepath), "node"); + + char pid[12] = {0}; + snprintf(pid, sizeof(pid), "[%u]", getpid()); + + fprintf(stderr, "%s%s: %s:%s:%s%s Assertion `%s' failed.\n", + exepath, pid, filename, linenum, + function, *function ? ":" : "", message); + fflush(stderr); + + Abort(); +} + +static void OnFatalError(const char* location, const char* message) { + if (location) { + PrintErrorString("FATAL ERROR: %s %s\n", location, message); + } else { + PrintErrorString("FATAL ERROR: %s\n", message); + } + fflush(stderr); + ABORT(); +} + +NO_RETURN void FatalError(const char* location, const char* message) { + OnFatalError(location, message); + // to suppress compiler warning + ABORT(); +} + +} // namespace node diff --git a/src/node_internals.h b/src/node_internals.h new file mode 100644 index 000000000..7d2b37910 --- /dev/null +++ b/src/node_internals.h @@ -0,0 +1,69 @@ +#ifndef SRC_NODE_INTERNALS_H_ +#define SRC_NODE_INTERNALS_H_ + +// +// This is a stripped down shim to allow node_api.cc to build outside of the node source tree. +// + +#include + +// Windows 8+ does not like abort() in Release mode +#ifdef _WIN32 +#define ABORT_NO_BACKTRACE() raise(SIGABRT) +#else +#define ABORT_NO_BACKTRACE() abort() +#endif + +#define ABORT() node::Abort() + +#ifdef __GNUC__ +#define LIKELY(expr) __builtin_expect(!!(expr), 1) +#define UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ +#else +#define LIKELY(expr) expr +#define UNLIKELY(expr) expr +#define PRETTY_FUNCTION_NAME "" +#endif + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define CHECK(expr) \ + do { \ + if (UNLIKELY(!(expr))) { \ + static const char* const args[] = { __FILE__, STRINGIFY(__LINE__), \ + #expr, PRETTY_FUNCTION_NAME }; \ + node::Assert(&args); \ + } \ + } while (0) + +#define CHECK_EQ(a, b) CHECK((a) == (b)) +#define CHECK_GE(a, b) CHECK((a) >= (b)) +#define CHECK_GT(a, b) CHECK((a) > (b)) +#define CHECK_LE(a, b) CHECK((a) <= (b)) +#define CHECK_LT(a, b) CHECK((a) < (b)) +#define CHECK_NE(a, b) CHECK((a) != (b)) + +#ifdef __GNUC__ +#define NO_RETURN __attribute__((noreturn)) +#else +#define NO_RETURN +#endif + +namespace node { + +// The slightly odd function signature for Assert() is to ease +// instruction cache pressure in calls from ASSERT and CHECK. +NO_RETURN void Abort(); +NO_RETURN void Assert(const char* const (*args)[4]); +void DumpBacktrace(FILE* fp); + +template +constexpr size_t arraysize(const T(&)[N]) { return N; } + +NO_RETURN void FatalError(const char* location, const char* message); + +} // namespace node + +#endif // SRC_NODE_INTERNALS_H_ diff --git a/test/error.cc b/test/error.cc index 169c2f2ed..7b2d54a62 100644 --- a/test/error.cc +++ b/test/error.cc @@ -154,6 +154,10 @@ void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { #endif // NAPI_CPP_EXCEPTIONS +void ThrowFatalError(const CallbackInfo& info) { + Error::Fatal("Error::ThrowFatalError", "This is a fatal error"); +} + } // end anonymous namespace Object InitError(Env env) { @@ -169,5 +173,6 @@ Object InitError(Env env) { exports["throwErrorThatEscapesScope"] = Function::New(env, ThrowErrorThatEscapesScope); exports["catchAndRethrowErrorThatEscapesScope"] = Function::New(env, CatchAndRethrowErrorThatEscapesScope); + exports["throwFatalError"] = Function::New(env, ThrowFatalError); return exports; } diff --git a/test/error.js b/test/error.js index 5b62fa0de..d85f8202f 100644 --- a/test/error.js +++ b/test/error.js @@ -2,10 +2,18 @@ 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`)); +if (process.argv[2] === 'fatal') { + const binding = require(process.argv[3]); + binding.error.throwFatalError(); + return; +} + +test(`./build/${buildType}/binding.node`); +test(`./build/${buildType}/binding_noexcept.node`); + +function test(bindingPath) { + const binding = require(bindingPath); -function test(binding) { assert.throws(() => binding.error.throwApiError('test'), err => { return err instanceof Error && err.message.includes('Invalid'); }); @@ -56,4 +64,10 @@ function test(binding) { assert.throws(() => binding.error.catchAndRethrowErrorThatEscapesScope('test'), err => { return err instanceof Error && err.message === 'test' && err.caught; }); + + const p = require('./napi_child').spawnSync( + process.execPath, [ __filename, 'fatal', bindingPath ]); + assert.ifError(p.error); + assert.ok(p.stderr.toString().includes( + 'FATAL ERROR: Error::ThrowFatalError This is a fatal error')); } diff --git a/test/index.js b/test/index.js index 5a61d6707..72a7bfda5 100644 --- a/test/index.js +++ b/test/index.js @@ -24,11 +24,7 @@ if (typeof global.gc === 'function') { console.log('\nAll tests passed!'); } else { // Make it easier to run with the correct (version-dependent) command-line args. - const args = [ '--expose-gc', __filename ]; - if (require('../index').isNodeApiBuiltin) { - args.splice(0, 0, '--napi-modules'); - } - const child = require('child_process').spawnSync(process.argv[0], args, { + const child = require('./napi_child').spawnSync(process.argv[0], [ '--expose-gc', __filename ], { stdio: 'inherit', }); process.exitCode = child.status; diff --git a/test/napi_child.js b/test/napi_child.js new file mode 100644 index 000000000..a3f0de10b --- /dev/null +++ b/test/napi_child.js @@ -0,0 +1,7 @@ +// Makes sure that child processes are spawned appropriately. +exports.spawnSync = function(command, args, options) { + if (require('../index').isNodeApiBuiltin) { + args.splice(0, 0, '--napi-modules'); + } + return require('child_process').spawnSync(command, args, options); +};