diff --git a/test/binding.cc b/test/binding.cc index 0415c69bc..9e5aaaaa6 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -86,6 +86,7 @@ Object InitEnvMiscellaneous(Env env); #if defined(NODE_ADDON_API_ENABLE_MAYBE) Object InitMaybeCheck(Env env); #endif +Object InitFinalizerOrder(Env env); Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -186,6 +187,13 @@ Object Init(Env env, Object exports) { #if defined(NODE_ADDON_API_ENABLE_MAYBE) exports.Set("maybe_check", InitMaybeCheck(env)); #endif + + exports.Set("finalizer_order", InitFinalizerOrder(env)); + + exports.Set( + "isExperimental", + Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL)); + return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index e7bf253d0..28de3fe96 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -30,6 +30,7 @@ 'error.cc', 'error_handling_for_primitives.cc', 'external.cc', + 'finalizer_order.cc', 'function.cc', 'function_reference.cc', 'handlescope.cc', diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc new file mode 100644 index 000000000..e06a6cfe1 --- /dev/null +++ b/test/finalizer_order.cc @@ -0,0 +1,99 @@ +#include + +namespace { +class Test : public Napi::ObjectWrap { + public: + Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + nogcFinalizerCalled = false; + gcFinalizerCalled = false; + + if (info.Length() > 0) { + finalizeCb_ = Napi::Persistent(info[0].As()); + } + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + exports.Set("Test", + DefineClass(env, + "Test", + { + StaticAccessor("isNogcFinalizerCalled", + &IsNogcFinalizerCalled, + nullptr, + napi_default), + StaticAccessor("isGcFinalizerCalled", + &IsGcFinalizerCalled, + nullptr, + napi_default), + })); + } + + void Finalize(Napi::NogcEnv /*env*/) { nogcFinalizerCalled = true; } + + void Finalize(Napi::Env /*env*/) { + gcFinalizerCalled = true; + if (!finalizeCb_.IsEmpty()) { + finalizeCb_.Call({}); + } + } + + static Napi::Value IsNogcFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), nogcFinalizerCalled); + } + + static Napi::Value IsGcFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), gcFinalizerCalled); + } + + private: + Napi::FunctionReference finalizeCb_; + + static bool nogcFinalizerCalled; + static bool gcFinalizerCalled; +}; + +bool Test::nogcFinalizerCalled = false; +bool Test::gcFinalizerCalled = false; + +bool externalNogcFinalizerCalled = false; +bool externalGcFinalizerCalled = false; + +Napi::Value CreateExternalNogc(const Napi::CallbackInfo& info) { + externalNogcFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::NogcEnv /*env*/, int* data) { + externalNogcFinalizerCalled = true; + delete data; + }); +} + +Napi::Value CreateExternalGc(const Napi::CallbackInfo& info) { + externalGcFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::Env /*env*/, int* data) { + externalGcFinalizerCalled = true; + delete data; + }); +} + +Napi::Value IsExternalNogcFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalNogcFinalizerCalled); +} + +Napi::Value IsExternalGcFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalGcFinalizerCalled); +} + +} // namespace + +Napi::Object InitFinalizerOrder(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + Test::Initialize(env, exports); + exports["CreateExternalNogc"] = Napi::Function::New(env, CreateExternalNogc); + exports["CreateExternalGc"] = Napi::Function::New(env, CreateExternalGc); + exports["isExternalNogcFinalizerCalled"] = + Napi::Function::New(env, IsExternalNogcFinalizerCalled); + exports["isExternalGcFinalizerCalled"] = + Napi::Function::New(env, IsExternalGcFinalizerCalled); + return exports; +} diff --git a/test/finalizer_order.js b/test/finalizer_order.js new file mode 100644 index 000000000..91b66bf36 --- /dev/null +++ b/test/finalizer_order.js @@ -0,0 +1,66 @@ +'use strict'; + +/* eslint-disable no-unused-vars */ + +const assert = require('assert'); +const testUtil = require('./testUtil'); + +module.exports = require('./common').runTest(test); + +function test (binding) { + const { isExperimental } = binding; + + let isCallbackCalled = false; + + return testUtil.runGCTests([ + 'Finalizer Order - ObjectWrap', + () => { + let test = new binding.finalizer_order.Test(() => { isCallbackCalled = true; }); + test = null; + + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, false, 'Expected nogc finalizer to not be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, false, 'Expected gc finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.Test.isNogcFinalizerCalled, true, 'Expected nogc finalizer to be called [after ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isGcFinalizerCalled, true, 'Expected gc finalizer to be called [after ticking]'); + assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]'); + }, + + 'Finalizer Order - External with Nogc Finalizer', + () => { + let ext = new binding.finalizer_order.CreateExternalNogc(); + ext = null; + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), false, 'Expected External nogc finalizer to not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalNogcFinalizerCalled(), true, 'Expected External nogc finalizer to be called [after ticking]'); + }, + + 'Finalizer Order - External with Gc Finalizer', + () => { + let ext = new binding.finalizer_order.CreateExternalGc(); + ext = null; + global.gc(); + assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), false, 'Expected External gc finalizer to not be called [before ticking]'); + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalGcFinalizerCalled(), true, 'Expected External gc finalizer to be called [after ticking]'); + } + ]); +}