diff --git a/test/addons/async-resource/binding.cc b/test/addons/async-resource/binding.cc new file mode 100644 index 00000000000000..093adfcba6aca0 --- /dev/null +++ b/test/addons/async-resource/binding.cc @@ -0,0 +1,112 @@ +#include "node.h" + +#include +#include + +namespace { + +using node::AsyncResource; +using v8::External; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::MaybeLocal; +using v8::Object; +using v8::String; +using v8::Value; + +void CreateAsyncResource(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + assert(args[0]->IsObject()); + AsyncResource* r; + if (args[1]->IsInt32()) { + r = new AsyncResource(isolate, args[0].As(), "foobär", + args[1].As()->Value()); + } else { + r = new AsyncResource(isolate, args[0].As(), "foobär"); + } + + args.GetReturnValue().Set( + External::New(isolate, static_cast(r))); +} + +void DestroyAsyncResource(const FunctionCallbackInfo& args) { + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + delete r; +} + +void CallViaFunction(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + + Local name = + String::NewFromUtf8(isolate, "methöd", v8::NewStringType::kNormal) + .ToLocalChecked(); + Local fn = + r->get_resource()->Get(isolate->GetCurrentContext(), name) + .ToLocalChecked(); + assert(fn->IsFunction()); + + Local arg = Integer::New(isolate, 42); + MaybeLocal ret = r->MakeCallback(fn.As(), 1, &arg); + args.GetReturnValue().Set(ret.FromMaybe(Local())); +} + +void CallViaString(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + + Local name = + String::NewFromUtf8(isolate, "methöd", v8::NewStringType::kNormal) + .ToLocalChecked(); + + Local arg = Integer::New(isolate, 42); + MaybeLocal ret = r->MakeCallback(name, 1, &arg); + args.GetReturnValue().Set(ret.FromMaybe(Local())); +} + +void CallViaUtf8Name(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + + Local arg = Integer::New(isolate, 42); + MaybeLocal ret = r->MakeCallback("methöd", 1, &arg); + args.GetReturnValue().Set(ret.FromMaybe(Local())); +} + +void GetUid(const FunctionCallbackInfo& args) { + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + args.GetReturnValue().Set(r->get_uid()); +} + +void GetResource(const FunctionCallbackInfo& args) { + assert(args[0]->IsExternal()); + auto r = static_cast(args[0].As()->Value()); + args.GetReturnValue().Set(r->get_resource()); +} + +void GetCurrentId(const FunctionCallbackInfo& args) { + args.GetReturnValue().Set(node::AsyncHooksGetCurrentId(args.GetIsolate())); +} + +void Initialize(Local exports) { + NODE_SET_METHOD(exports, "createAsyncResource", CreateAsyncResource); + NODE_SET_METHOD(exports, "destroyAsyncResource", DestroyAsyncResource); + NODE_SET_METHOD(exports, "callViaFunction", CallViaFunction); + NODE_SET_METHOD(exports, "callViaString", CallViaString); + NODE_SET_METHOD(exports, "callViaUtf8Name", CallViaUtf8Name); + NODE_SET_METHOD(exports, "getUid", GetUid); + NODE_SET_METHOD(exports, "getResource", GetResource); + NODE_SET_METHOD(exports, "getCurrentId", GetCurrentId); +} + +} // namespace + +NODE_MODULE(binding, Initialize) diff --git a/test/addons/async-resource/binding.gyp b/test/addons/async-resource/binding.gyp new file mode 100644 index 00000000000000..7ede63d94a0d77 --- /dev/null +++ b/test/addons/async-resource/binding.gyp @@ -0,0 +1,9 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/test/addons/async-resource/test.js b/test/addons/async-resource/test.js new file mode 100644 index 00000000000000..34017750ce500e --- /dev/null +++ b/test/addons/async-resource/test.js @@ -0,0 +1,80 @@ +'use strict'; + +const common = require('../../common'); +const assert = require('assert'); +const binding = require(`./build/${common.buildType}/binding`); +const async_hooks = require('async_hooks'); + +const kObjectTag = Symbol('kObjectTag'); + +const bindingUids = []; +let expectedTriggerId; +let before = 0; +let after = 0; +let destroy = 0; + +async_hooks.createHook({ + init(id, type, triggerId, resource) { + assert.strictEqual(typeof id, 'number'); + assert.strictEqual(typeof resource, 'object'); + assert(id > 1); + if (type === 'foobär') { + assert.strictEqual(resource.kObjectTag, kObjectTag); + assert.strictEqual(triggerId, expectedTriggerId); + bindingUids.push(id); + } + }, + + before(id) { + if (bindingUids.includes(id)) before++; + }, + + after(id) { + if (bindingUids.includes(id)) after++; + }, + + destroy(id) { + if (bindingUids.includes(id)) destroy++; + } +}).enable(); + +assert.strictEqual(binding.getCurrentId(), 1); + +for (const call of [binding.callViaFunction, + binding.callViaString, + binding.callViaUtf8Name]) { + for (const passedTriggerId of [undefined, 12345]) { + let uid; + const object = { + methöd(arg) { + assert.strictEqual(this, object); + assert.strictEqual(arg, 42); + assert.strictEqual(binding.getCurrentId(), uid); + return 'baz'; + }, + kObjectTag + }; + + if (passedTriggerId === undefined) + expectedTriggerId = 1; + else + expectedTriggerId = passedTriggerId; + + const resource = binding.createAsyncResource(object, passedTriggerId); + uid = bindingUids[bindingUids.length - 1]; + + const ret = call(resource); + assert.strictEqual(ret, 'baz'); + assert.strictEqual(binding.getResource(resource), object); + assert.strictEqual(binding.getUid(resource), uid); + + binding.destroyAsyncResource(resource); + } +} + +setImmediate(common.mustCall(() => { + assert.strictEqual(bindingUids.length, 6); + assert.strictEqual(before, bindingUids.length); + assert.strictEqual(after, bindingUids.length); + assert.strictEqual(destroy, bindingUids.length); +}));