Skip to content

Commit

Permalink
[test] Add test coverage for AsyncProgressWorker (#1307)
Browse files Browse the repository at this point in the history
* test: Adding test coverage for AsyncProgressWorker

* test: Refactor async hook installation
  • Loading branch information
JckXia committed Apr 26, 2023
1 parent 0e34f22 commit dfad6b4
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 0 deletions.
155 changes: 155 additions & 0 deletions test/async_progress_worker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

Expand All @@ -15,6 +16,157 @@ struct ProgressData {
size_t progress;
};

class TestWorkerWithNoCb : public AsyncProgressWorker<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 1: {
Function cb = info[0].As<Function>();
TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb);
worker->Queue();
} break;

case 2: {
std::string resName = info[0].As<String>();
Function cb = info[1].As<Function>();
TestWorkerWithNoCb* worker =
new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb);
worker->Queue();
} break;

case 3: {
std::string resName = info[0].As<String>();
Object resObject = info[1].As<Object>();
Function cb = info[2].As<Function>();
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<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 2: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb);
worker->Queue();
} break;

case 3: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
std::string resName = info[2].As<String>();
TestWorkerWithRecv* worker =
new TestWorkerWithRecv(recv, cb, resName.c_str());
worker->Queue();
} break;

case 4: {
Object recv = info[0].As<Object>();
Function cb = info[1].As<Function>();
std::string resName = info[2].As<String>();
Object resObject = info[3].As<Object>();
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<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
switch (info.Length()) {
case 1: {
Function cb = info[0].As<Function>();
TestWorkerWithCb* worker = new TestWorkerWithCb(cb);
worker->Queue();
} break;

case 2: {
Function cb = info[0].As<Function>();
std::string asyncResName = info[1].As<String>();
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<ProgressData> {
public:
static void DoWork(const CallbackInfo& info) {
Expand Down Expand Up @@ -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;
}

Expand Down
134 changes: 134 additions & 0 deletions test/async_progress_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
45 changes: 45 additions & 0 deletions test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
};
Expand Down

0 comments on commit dfad6b4

Please sign in to comment.