Skip to content

Commit dfad6b4

Browse files
authoredApr 26, 2023
[test] Add test coverage for AsyncProgressWorker (#1307)
* test: Adding test coverage for AsyncProgressWorker * test: Refactor async hook installation
1 parent 0e34f22 commit dfad6b4

File tree

3 files changed

+334
-0
lines changed

3 files changed

+334
-0
lines changed
 

Diff for: ‎test/async_progress_worker.cc

+155
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <chrono>
44
#include <condition_variable>
5+
#include <iostream>
56
#include <mutex>
67
#include <thread>
78

@@ -15,6 +16,157 @@ struct ProgressData {
1516
size_t progress;
1617
};
1718

19+
class TestWorkerWithNoCb : public AsyncProgressWorker<ProgressData> {
20+
public:
21+
static void DoWork(const CallbackInfo& info) {
22+
switch (info.Length()) {
23+
case 1: {
24+
Function cb = info[0].As<Function>();
25+
TestWorkerWithNoCb* worker = new TestWorkerWithNoCb(info.Env(), cb);
26+
worker->Queue();
27+
} break;
28+
29+
case 2: {
30+
std::string resName = info[0].As<String>();
31+
Function cb = info[1].As<Function>();
32+
TestWorkerWithNoCb* worker =
33+
new TestWorkerWithNoCb(info.Env(), resName.c_str(), cb);
34+
worker->Queue();
35+
} break;
36+
37+
case 3: {
38+
std::string resName = info[0].As<String>();
39+
Object resObject = info[1].As<Object>();
40+
Function cb = info[2].As<Function>();
41+
TestWorkerWithNoCb* worker =
42+
new TestWorkerWithNoCb(info.Env(), resName.c_str(), resObject, cb);
43+
worker->Queue();
44+
} break;
45+
46+
default:
47+
48+
break;
49+
}
50+
}
51+
52+
protected:
53+
void Execute(const ExecutionProgress& progress) override {
54+
ProgressData data{1};
55+
progress.Send(&data, 1);
56+
}
57+
58+
void OnProgress(const ProgressData*, size_t /* count */) override {
59+
_cb.Call({});
60+
}
61+
62+
private:
63+
TestWorkerWithNoCb(Napi::Env env, Function cb) : AsyncProgressWorker(env) {
64+
_cb.Reset(cb, 1);
65+
}
66+
TestWorkerWithNoCb(Napi::Env env, const char* resourceName, Function cb)
67+
: AsyncProgressWorker(env, resourceName) {
68+
_cb.Reset(cb, 1);
69+
}
70+
TestWorkerWithNoCb(Napi::Env env,
71+
const char* resourceName,
72+
const Object& resourceObject,
73+
Function cb)
74+
: AsyncProgressWorker(env, resourceName, resourceObject) {
75+
_cb.Reset(cb, 1);
76+
}
77+
FunctionReference _cb;
78+
};
79+
80+
class TestWorkerWithRecv : public AsyncProgressWorker<ProgressData> {
81+
public:
82+
static void DoWork(const CallbackInfo& info) {
83+
switch (info.Length()) {
84+
case 2: {
85+
Object recv = info[0].As<Object>();
86+
Function cb = info[1].As<Function>();
87+
TestWorkerWithRecv* worker = new TestWorkerWithRecv(recv, cb);
88+
worker->Queue();
89+
} break;
90+
91+
case 3: {
92+
Object recv = info[0].As<Object>();
93+
Function cb = info[1].As<Function>();
94+
std::string resName = info[2].As<String>();
95+
TestWorkerWithRecv* worker =
96+
new TestWorkerWithRecv(recv, cb, resName.c_str());
97+
worker->Queue();
98+
} break;
99+
100+
case 4: {
101+
Object recv = info[0].As<Object>();
102+
Function cb = info[1].As<Function>();
103+
std::string resName = info[2].As<String>();
104+
Object resObject = info[3].As<Object>();
105+
TestWorkerWithRecv* worker =
106+
new TestWorkerWithRecv(recv, cb, resName.c_str(), resObject);
107+
worker->Queue();
108+
} break;
109+
110+
default:
111+
112+
break;
113+
}
114+
}
115+
116+
protected:
117+
void Execute(const ExecutionProgress&) override {}
118+
119+
void OnProgress(const ProgressData*, size_t /* count */) override {}
120+
121+
private:
122+
TestWorkerWithRecv(const Object& recv, const Function& cb)
123+
: AsyncProgressWorker(recv, cb) {}
124+
TestWorkerWithRecv(const Object& recv,
125+
const Function& cb,
126+
const char* resourceName)
127+
: AsyncProgressWorker(recv, cb, resourceName) {}
128+
TestWorkerWithRecv(const Object& recv,
129+
const Function& cb,
130+
const char* resourceName,
131+
const Object& resourceObject)
132+
: AsyncProgressWorker(recv, cb, resourceName, resourceObject) {}
133+
};
134+
135+
class TestWorkerWithCb : public AsyncProgressWorker<ProgressData> {
136+
public:
137+
static void DoWork(const CallbackInfo& info) {
138+
switch (info.Length()) {
139+
case 1: {
140+
Function cb = info[0].As<Function>();
141+
TestWorkerWithCb* worker = new TestWorkerWithCb(cb);
142+
worker->Queue();
143+
} break;
144+
145+
case 2: {
146+
Function cb = info[0].As<Function>();
147+
std::string asyncResName = info[1].As<String>();
148+
TestWorkerWithCb* worker =
149+
new TestWorkerWithCb(cb, asyncResName.c_str());
150+
worker->Queue();
151+
} break;
152+
153+
default:
154+
155+
break;
156+
}
157+
}
158+
159+
protected:
160+
void Execute(const ExecutionProgress&) override {}
161+
162+
void OnProgress(const ProgressData*, size_t /* count */) override {}
163+
164+
private:
165+
TestWorkerWithCb(Function cb) : AsyncProgressWorker(cb) {}
166+
TestWorkerWithCb(Function cb, const char* res_name)
167+
: AsyncProgressWorker(cb, res_name) {}
168+
};
169+
18170
class TestWorker : public AsyncProgressWorker<ProgressData> {
19171
public:
20172
static void DoWork(const CallbackInfo& info) {
@@ -196,6 +348,9 @@ Object InitAsyncProgressWorker(Env env) {
196348
exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork);
197349
exports["doSignalAfterProgressTest"] =
198350
Function::New(env, SignalAfterProgressTestWorker::DoWork);
351+
exports["runWorkerNoCb"] = Function::New(env, TestWorkerWithNoCb::DoWork);
352+
exports["runWorkerWithRecv"] = Function::New(env, TestWorkerWithRecv::DoWork);
353+
exports["runWorkerWithCb"] = Function::New(env, TestWorkerWithCb::DoWork);
199354
return exports;
200355
}
201356

Diff for: ‎test/async_progress_worker.js

+134
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,146 @@ const common = require('./common');
44
const assert = require('assert');
55

66
module.exports = common.runTest(test);
7+
const nodeVersion = process.versions.node.split('.')[0];
8+
9+
let asyncHooks;
10+
function checkAsyncHooks () {
11+
if (nodeVersion >= 8) {
12+
if (asyncHooks === undefined) {
13+
asyncHooks = require('async_hooks');
14+
}
15+
return true;
16+
}
17+
return false;
18+
}
719

820
async function test ({ asyncprogressworker }) {
921
await success(asyncprogressworker);
1022
await fail(asyncprogressworker);
1123
await signalTest(asyncprogressworker.doMalignTest);
1224
await signalTest(asyncprogressworker.doSignalAfterProgressTest);
25+
26+
await asyncProgressWorkerCallbackOverloads(asyncprogressworker.runWorkerWithCb);
27+
await asyncProgressWorkerRecvOverloads(asyncprogressworker.runWorkerWithRecv);
28+
await asyncProgressWorkerNoCbOverloads(asyncprogressworker.runWorkerNoCb);
29+
}
30+
31+
async function asyncProgressWorkerCallbackOverloads (bindingFunction) {
32+
bindingFunction(common.mustCall());
33+
if (!checkAsyncHooks()) {
34+
return;
35+
}
36+
37+
const hooks = common.installAysncHooks('cbResources');
38+
39+
const triggerAsyncId = asyncHooks.executionAsyncId();
40+
await new Promise((resolve, reject) => {
41+
bindingFunction(common.mustCall(), 'cbResources');
42+
hooks.then(actual => {
43+
assert.deepStrictEqual(actual, [
44+
{
45+
eventName: 'init',
46+
type: 'cbResources',
47+
triggerAsyncId: triggerAsyncId,
48+
resource: {}
49+
},
50+
{ eventName: 'before' },
51+
{ eventName: 'after' },
52+
{ eventName: 'destroy' }
53+
]);
54+
}).catch(common.mustNotCall());
55+
resolve();
56+
});
57+
}
58+
59+
async function asyncProgressWorkerRecvOverloads (bindingFunction) {
60+
const recvObject = {
61+
a: 4
62+
};
63+
64+
function cb () {
65+
assert.strictEqual(this.a, recvObject.a);
66+
}
67+
68+
bindingFunction(recvObject, common.mustCall(cb));
69+
if (!checkAsyncHooks()) {
70+
return;
71+
}
72+
const asyncResources = [
73+
{ resName: 'cbRecvResources', resObject: {} },
74+
{ resName: 'cbRecvResourcesObject', resObject: { foo: 'bar' } }
75+
];
76+
77+
for (const asyncResource of asyncResources) {
78+
const asyncResName = asyncResource.resName;
79+
const asyncResObject = asyncResource.resObject;
80+
81+
const hooks = common.installAysncHooks(asyncResource.resName);
82+
const triggerAsyncId = asyncHooks.executionAsyncId();
83+
await new Promise((resolve, reject) => {
84+
if (Object.keys(asyncResObject).length === 0) {
85+
bindingFunction(recvObject, common.mustCall(cb), asyncResName);
86+
} else {
87+
bindingFunction(recvObject, common.mustCall(cb), asyncResName, asyncResObject);
88+
}
89+
90+
hooks.then(actual => {
91+
assert.deepStrictEqual(actual, [
92+
{
93+
eventName: 'init',
94+
type: asyncResName,
95+
triggerAsyncId: triggerAsyncId,
96+
resource: asyncResObject
97+
},
98+
{ eventName: 'before' },
99+
{ eventName: 'after' },
100+
{ eventName: 'destroy' }
101+
]);
102+
}).catch(common.mustNotCall());
103+
resolve();
104+
});
105+
}
106+
}
107+
108+
async function asyncProgressWorkerNoCbOverloads (bindingFunction) {
109+
bindingFunction(common.mustCall(() => {}));
110+
if (!checkAsyncHooks()) {
111+
return;
112+
}
113+
const asyncResources = [
114+
{ resName: 'noCbResources', resObject: {} },
115+
{ resName: 'noCbResourcesObject', resObject: { foo: 'bar' } }
116+
];
117+
118+
for (const asyncResource of asyncResources) {
119+
const asyncResName = asyncResource.resName;
120+
const asyncResObject = asyncResource.resObject;
121+
122+
const hooks = common.installAysncHooks(asyncResource.resName);
123+
const triggerAsyncId = asyncHooks.executionAsyncId();
124+
await new Promise((resolve, reject) => {
125+
if (Object.keys(asyncResObject).length === 0) {
126+
bindingFunction(asyncResName, common.mustCall(() => {}));
127+
} else {
128+
bindingFunction(asyncResName, asyncResObject, common.mustCall(() => {}));
129+
}
130+
131+
hooks.then(actual => {
132+
assert.deepStrictEqual(actual, [
133+
{
134+
eventName: 'init',
135+
type: asyncResName,
136+
triggerAsyncId: triggerAsyncId,
137+
resource: asyncResObject
138+
},
139+
{ eventName: 'before' },
140+
{ eventName: 'after' },
141+
{ eventName: 'destroy' }
142+
]);
143+
}).catch(common.mustNotCall());
144+
resolve();
145+
});
146+
}
13147
}
14148

15149
function success (binding) {

Diff for: ‎test/common/index.js

+45
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,51 @@ function runCallChecks (exitCode) {
3232
if (failed.length) process.exit(1);
3333
}
3434

35+
exports.installAysncHooks = function (asyncResName) {
36+
const asyncHooks = require('async_hooks');
37+
return new Promise((resolve, reject) => {
38+
let id;
39+
const events = [];
40+
/**
41+
* TODO(legendecas): investigate why resolving & disabling hooks in
42+
* destroy callback causing crash with case 'callbackscope.js'.
43+
*/
44+
let destroyed = false;
45+
const hook = asyncHooks.createHook({
46+
init (asyncId, type, triggerAsyncId, resource) {
47+
if (id === undefined && type === asyncResName) {
48+
id = asyncId;
49+
events.push({ eventName: 'init', type, triggerAsyncId, resource });
50+
}
51+
},
52+
before (asyncId) {
53+
if (asyncId === id) {
54+
events.push({ eventName: 'before' });
55+
}
56+
},
57+
after (asyncId) {
58+
if (asyncId === id) {
59+
events.push({ eventName: 'after' });
60+
}
61+
},
62+
destroy (asyncId) {
63+
if (asyncId === id) {
64+
events.push({ eventName: 'destroy' });
65+
destroyed = true;
66+
}
67+
}
68+
}).enable();
69+
70+
const interval = setInterval(() => {
71+
if (destroyed) {
72+
hook.disable();
73+
clearInterval(interval);
74+
resolve(events);
75+
}
76+
}, 10);
77+
});
78+
};
79+
3580
exports.mustCall = function (fn, exact) {
3681
return _mustCallInner(fn, exact, 'exact');
3782
};

0 commit comments

Comments
 (0)