diff --git a/test/async_progress_worker.cc b/test/async_progress_worker.cc index 7b14f6ca5..17aaef3e5 100644 --- a/test/async_progress_worker.cc +++ b/test/async_progress_worker.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -15,6 +16,157 @@ struct ProgressData { size_t progress; }; +class TestWorkerWithNoCb : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb); + worker->Queue(); + } break; + + case 2: { + std::string resName = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb); + worker->Queue(); + } break; + + case 3: { + std::string resName = info[0].As(); + Object resObject = info[1].As(); + Function cb = info[2].As(); + TestWorkerWithNoCb* worker = + new TestWorkerWithNoCb(info.Env(), resName.c_str(), resObject, cb); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress& progress) override { + ProgressData data{1}; + progress.Send(&data, 1); + } + + void OnProgress(const ProgressData*, size_t /* count */) override { + _cb.Call({}); + } + + private: + TestWorkerWithNoCb(Napi::Env env, Function cb) : AsyncProgressWorker(env) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, const char* resourceName, Function cb) + : AsyncProgressWorker(env, resourceName) { + _cb.Reset(cb, 1); + } + TestWorkerWithNoCb(Napi::Env env, + const char* resourceName, + const Object& resourceObject, + Function cb) + : AsyncProgressWorker(env, resourceName, resourceObject) { + _cb.Reset(cb, 1); + } + FunctionReference _cb; +}; + +class TestWorkerWithRecv : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 2: { + Object recv = info[0].As(); + Function cb = info[1].As(); + TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb); + worker->Queue(); + } break; + + case 3: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str()); + worker->Queue(); + } break; + + case 4: { + Object recv = info[0].As(); + Function cb = info[1].As(); + std::string resName = info[2].As(); + Object resObject = info[3].As(); + TestWorkerWithRecv* worker = + new TestWorkerWithRecv(recv, cb, resName.c_str(), resObject); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithRecv(const Object& recv, const Function& cb) + : AsyncProgressWorker(recv, cb) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName) + : AsyncProgressWorker(recv, cb, resourceName) {} + TestWorkerWithRecv(const Object& recv, + const Function& cb, + const char* resourceName, + const Object& resourceObject) + : AsyncProgressWorker(recv, cb, resourceName, resourceObject) {} +}; + +class TestWorkerWithCb : public AsyncProgressWorker { + public: + static void DoWork(const CallbackInfo& info) { + switch (info.Length()) { + case 1: { + Function cb = info[0].As(); + TestWorkerWithCb* worker = new TestWorkerWithCb(cb); + worker->Queue(); + } break; + + case 2: { + Function cb = info[0].As(); + std::string asyncResName = info[1].As(); + TestWorkerWithCb* worker = + new TestWorkerWithCb(cb, asyncResName.c_str()); + worker->Queue(); + } break; + + default: + + break; + } + } + + protected: + void Execute(const ExecutionProgress&) override {} + + void OnProgress(const ProgressData*, size_t /* count */) override {} + + private: + TestWorkerWithCb(Function cb) : AsyncProgressWorker(cb) {} + TestWorkerWithCb(Function cb, const char* res_name) + : AsyncProgressWorker(cb, res_name) {} +}; + class TestWorker : public AsyncProgressWorker { public: static void DoWork(const CallbackInfo& info) { @@ -196,6 +348,9 @@ Object InitAsyncProgressWorker(Env env) { exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork); exports["doSignalAfterProgressTest"] = Function::New(env, SignalAfterProgressTestWorker::DoWork); + exports["runWorkerNoCb"] = Function::New(env, TestWorkerWithNoCb::DoWork); + exports["runWorkerWithRecv"] = Function::New(env, TestWorkerWithRecv::DoWork); + exports["runWorkerWithCb"] = Function::New(env, TestWorkerWithCb::DoWork); return exports; } diff --git a/test/async_progress_worker.js b/test/async_progress_worker.js index b7a1de367..78ba7b63d 100644 --- a/test/async_progress_worker.js +++ b/test/async_progress_worker.js @@ -4,12 +4,146 @@ const common = require('./common'); const assert = require('assert'); module.exports = common.runTest(test); +const nodeVersion = process.versions.node.split('.')[0]; + +let asyncHooks; +function checkAsyncHooks () { + if (nodeVersion >= 8) { + if (asyncHooks === undefined) { + asyncHooks = require('async_hooks'); + } + return true; + } + return false; +} async function test ({ asyncprogressworker }) { await success(asyncprogressworker); await fail(asyncprogressworker); await signalTest(asyncprogressworker.doMalignTest); await signalTest(asyncprogressworker.doSignalAfterProgressTest); + + await asyncProgressWorkerCallbackOverloads(asyncprogressworker.runWorkerWithCb); + await asyncProgressWorkerRecvOverloads(asyncprogressworker.runWorkerWithRecv); + await asyncProgressWorkerNoCbOverloads(asyncprogressworker.runWorkerNoCb); +} + +async function asyncProgressWorkerCallbackOverloads (bindingFunction) { + bindingFunction(common.mustCall()); + if (!checkAsyncHooks()) { + return; + } + + const hooks = common.installAysncHooks('cbResources'); + + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + bindingFunction(common.mustCall(), 'cbResources'); + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: 'cbResources', + triggerAsyncId: triggerAsyncId, + resource: {} + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); +} + +async function asyncProgressWorkerRecvOverloads (bindingFunction) { + const recvObject = { + a: 4 + }; + + function cb () { + assert.strictEqual(this.a, recvObject.a); + } + + bindingFunction(recvObject, common.mustCall(cb)); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'cbRecvResources', resObject: {} }, + { resName: 'cbRecvResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(recvObject, common.mustCall(cb), asyncResName); + } else { + bindingFunction(recvObject, common.mustCall(cb), asyncResName, asyncResObject); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId: triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); + } +} + +async function asyncProgressWorkerNoCbOverloads (bindingFunction) { + bindingFunction(common.mustCall(() => {})); + if (!checkAsyncHooks()) { + return; + } + const asyncResources = [ + { resName: 'noCbResources', resObject: {} }, + { resName: 'noCbResourcesObject', resObject: { foo: 'bar' } } + ]; + + for (const asyncResource of asyncResources) { + const asyncResName = asyncResource.resName; + const asyncResObject = asyncResource.resObject; + + const hooks = common.installAysncHooks(asyncResource.resName); + const triggerAsyncId = asyncHooks.executionAsyncId(); + await new Promise((resolve, reject) => { + if (Object.keys(asyncResObject).length === 0) { + bindingFunction(asyncResName, common.mustCall(() => {})); + } else { + bindingFunction(asyncResName, asyncResObject, common.mustCall(() => {})); + } + + hooks.then(actual => { + assert.deepStrictEqual(actual, [ + { + eventName: 'init', + type: asyncResName, + triggerAsyncId: triggerAsyncId, + resource: asyncResObject + }, + { eventName: 'before' }, + { eventName: 'after' }, + { eventName: 'destroy' } + ]); + }).catch(common.mustNotCall()); + resolve(); + }); + } } function success (binding) { diff --git a/test/common/index.js b/test/common/index.js index ea109dca9..2efd96614 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -32,6 +32,51 @@ function runCallChecks (exitCode) { if (failed.length) process.exit(1); } +exports.installAysncHooks = function (asyncResName) { + const asyncHooks = require('async_hooks'); + return new Promise((resolve, reject) => { + let id; + const events = []; + /** + * TODO(legendecas): investigate why resolving & disabling hooks in + * destroy callback causing crash with case 'callbackscope.js'. + */ + let destroyed = false; + const hook = asyncHooks.createHook({ + init (asyncId, type, triggerAsyncId, resource) { + if (id === undefined && type === asyncResName) { + id = asyncId; + events.push({ eventName: 'init', type, triggerAsyncId, resource }); + } + }, + before (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'before' }); + } + }, + after (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'after' }); + } + }, + destroy (asyncId) { + if (asyncId === id) { + events.push({ eventName: 'destroy' }); + destroyed = true; + } + } + }).enable(); + + const interval = setInterval(() => { + if (destroyed) { + hook.disable(); + clearInterval(interval); + resolve(events); + } + }, 10); + }); +}; + exports.mustCall = function (fn, exact) { return _mustCallInner(fn, exact, 'exact'); };