Skip to content

Commit 70b51c8

Browse files
digitalinfinityjasnell
authored andcommitted
test: port test for make_callback to n-api
Improved test coverage for napi_make_callback by porting the existing addons/make_callback test to n-api PR-URL: #12409 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent ccdcd91 commit 70b51c8

File tree

6 files changed

+330
-0
lines changed

6 files changed

+330
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <node_api.h>
2+
#include "../common.h"
3+
#include <vector>
4+
5+
namespace {
6+
7+
napi_value MakeCallback(napi_env env, napi_callback_info info) {
8+
constexpr int kMaxArgs = 10;
9+
size_t argc = kMaxArgs;
10+
napi_value args[kMaxArgs];
11+
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
12+
13+
NAPI_ASSERT(env, argc > 0, "Wrong number of arguments");
14+
15+
napi_value recv = args[0];
16+
napi_value func = args[1];
17+
18+
std::vector<napi_value> argv;
19+
for (size_t n = 2; n < argc; n += 1) {
20+
argv.push_back(args[n]);
21+
}
22+
23+
napi_valuetype func_type;
24+
25+
NAPI_CALL(env, napi_typeof(env, func, &func_type));
26+
27+
napi_value result;
28+
if (func_type == napi_function) {
29+
NAPI_CALL(env,
30+
napi_make_callback(env, recv, func, argv.size(), argv.data(), &result));
31+
} else {
32+
NAPI_ASSERT(env, false, "Unexpected argument type");
33+
}
34+
35+
return result;
36+
}
37+
38+
void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
39+
napi_value fn;
40+
NAPI_CALL_RETURN_VOID(env,
41+
napi_create_function(env, NULL, MakeCallback, NULL, &fn));
42+
NAPI_CALL_RETURN_VOID(env,
43+
napi_set_named_property(env, exports, "makeCallback", fn));
44+
}
45+
46+
} // namespace
47+
48+
NAPI_MODULE(binding, Init)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
6+
'sources': [ 'binding.cc' ]
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const assert = require('assert');
5+
const vm = require('vm');
6+
const binding = require(`./build/${common.buildType}/binding`);
7+
const makeCallback = binding.makeCallback;
8+
9+
function myMultiArgFunc(arg1, arg2, arg3) {
10+
console.log(`MyFunc was called with ${arguments.length} arguments`);
11+
assert.strictEqual(arg1, 1);
12+
assert.strictEqual(arg2, 2);
13+
assert.strictEqual(arg3, 3);
14+
return 42;
15+
}
16+
17+
assert.strictEqual(42, makeCallback(process, common.mustCall(function() {
18+
assert.strictEqual(0, arguments.length);
19+
assert.strictEqual(this, process);
20+
return 42;
21+
})));
22+
23+
assert.strictEqual(42, makeCallback(process, common.mustCall(function(x) {
24+
assert.strictEqual(1, arguments.length);
25+
assert.strictEqual(this, process);
26+
assert.strictEqual(x, 1337);
27+
return 42;
28+
}), 1337));
29+
30+
assert.strictEqual(42,
31+
makeCallback(this,
32+
common.mustCall(myMultiArgFunc), 1, 2, 3));
33+
34+
// TODO(node-api): napi_make_callback needs to support
35+
// strings passed for the func argument
36+
/*
37+
const recv = {
38+
one: common.mustCall(function() {
39+
assert.strictEqual(0, arguments.length);
40+
assert.strictEqual(this, recv);
41+
return 42;
42+
}),
43+
two: common.mustCall(function(x) {
44+
assert.strictEqual(1, arguments.length);
45+
assert.strictEqual(this, recv);
46+
assert.strictEqual(x, 1337);
47+
return 42;
48+
}),
49+
};
50+
51+
assert.strictEqual(42, makeCallback(recv, 'one'));
52+
assert.strictEqual(42, makeCallback(recv, 'two', 1337));
53+
54+
// Check that callbacks on a receiver from a different context works.
55+
const foreignObject = vm.runInNewContext('({ fortytwo() { return 42; } })');
56+
assert.strictEqual(42, makeCallback(foreignObject, 'fortytwo'));
57+
*/
58+
59+
// Check that the callback is made in the context of the receiver.
60+
const target = vm.runInNewContext(`
61+
(function($Object) {
62+
if (Object === $Object)
63+
throw new Error('bad');
64+
return Object;
65+
})
66+
`);
67+
assert.notStrictEqual(Object, makeCallback(process, target, Object));
68+
69+
// Runs in inner context.
70+
const forward = vm.runInNewContext(`
71+
(function(forward) {
72+
return forward(Object);
73+
})
74+
`);
75+
// Runs in outer context.
76+
const endpoint = function($Object) {
77+
if (Object === $Object)
78+
throw new Error('bad');
79+
return Object;
80+
};
81+
assert.strictEqual(Object, makeCallback(process, forward, endpoint));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include <node_api.h>
2+
#include "../common.h"
3+
#include <vector>
4+
5+
namespace {
6+
7+
napi_value MakeCallback(napi_env env, napi_callback_info info) {
8+
size_t argc = 2;
9+
napi_value args[2];
10+
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
11+
12+
napi_value recv = args[0];
13+
napi_value func = args[1];
14+
15+
napi_make_callback(env,
16+
recv, func, 0 /* argc */, nullptr /* argv */, nullptr /* result */);
17+
18+
return recv;
19+
}
20+
21+
void Init(napi_env env, napi_value exports, napi_value module, void* priv) {
22+
napi_value fn;
23+
NAPI_CALL_RETURN_VOID(env,
24+
napi_create_function(env, NULL, MakeCallback, NULL, &fn));
25+
NAPI_CALL_RETURN_VOID(env,
26+
napi_set_named_property(env, exports, "makeCallback", fn));
27+
}
28+
29+
30+
} // namespace
31+
32+
NAPI_MODULE(binding, Init)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
6+
'sources': [ 'binding.cc' ]
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const assert = require('assert');
5+
const domain = require('domain');
6+
const binding = require(`./build/${common.buildType}/binding`);
7+
const makeCallback = binding.makeCallback;
8+
9+
// Make sure this is run in the future.
10+
const mustCallCheckDomains = common.mustCall(checkDomains);
11+
12+
13+
// Make sure that using MakeCallback allows the error to propagate.
14+
assert.throws(function() {
15+
makeCallback({}, function() {
16+
throw new Error('hi from domain error');
17+
});
18+
}, /^Error: hi from domain error$/);
19+
20+
21+
// Check the execution order of the nextTickQueue and MicrotaskQueue in
22+
// relation to running multiple MakeCallback's from bootstrap,
23+
// node::MakeCallback() and node::AsyncWrap::MakeCallback().
24+
// TODO(trevnorris): Is there a way to verify this is being run during
25+
// bootstrap?
26+
(function verifyExecutionOrder(arg) {
27+
const results = [];
28+
29+
// Processing of the MicrotaskQueue is manually handled by node. They are not
30+
// processed until after the nextTickQueue has been processed.
31+
Promise.resolve(1).then(common.mustCall(function() {
32+
results.push(7);
33+
}));
34+
35+
// The nextTick should run after all immediately invoked calls.
36+
process.nextTick(common.mustCall(function() {
37+
results.push(3);
38+
39+
// Run same test again but while processing the nextTickQueue to make sure
40+
// the following MakeCallback call breaks in the middle of processing the
41+
// queue and allows the script to run normally.
42+
process.nextTick(common.mustCall(function() {
43+
results.push(6);
44+
}));
45+
46+
makeCallback({}, common.mustCall(function() {
47+
results.push(4);
48+
}));
49+
50+
results.push(5);
51+
}));
52+
53+
results.push(0);
54+
55+
// MakeCallback is calling the function immediately, but should then detect
56+
// that a script is already in the middle of execution and return before
57+
// either the nextTickQueue or MicrotaskQueue are processed.
58+
makeCallback({}, common.mustCall(function() {
59+
results.push(1);
60+
}));
61+
62+
// This should run before either the nextTickQueue or MicrotaskQueue are
63+
// processed. Previously MakeCallback would not detect this circumstance
64+
// and process them immediately.
65+
results.push(2);
66+
67+
setImmediate(common.mustCall(function() {
68+
for (let i = 0; i < results.length; i++) {
69+
assert.strictEqual(results[i], i,
70+
`verifyExecutionOrder(${arg}) results: ${results}`);
71+
}
72+
if (arg === 1) {
73+
// The tests are first run on bootstrap during LoadEnvironment() in
74+
// src/node.cc. Now run the tests through node::MakeCallback().
75+
setImmediate(function() {
76+
makeCallback({}, common.mustCall(function() {
77+
verifyExecutionOrder(2);
78+
}));
79+
});
80+
} else if (arg === 2) {
81+
// setTimeout runs via the TimerWrap, which runs through
82+
// AsyncWrap::MakeCallback(). Make sure there are no conflicts using
83+
// node::MakeCallback() within it.
84+
setTimeout(common.mustCall(function() {
85+
verifyExecutionOrder(3);
86+
}), 10);
87+
} else if (arg === 3) {
88+
mustCallCheckDomains();
89+
} else {
90+
throw new Error('UNREACHABLE');
91+
}
92+
}));
93+
}(1));
94+
95+
96+
function checkDomains() {
97+
// Check that domains are properly entered/exited when called in multiple
98+
// levels from both node::MakeCallback() and AsyncWrap::MakeCallback
99+
setImmediate(common.mustCall(function() {
100+
const d1 = domain.create();
101+
const d2 = domain.create();
102+
const d3 = domain.create();
103+
104+
makeCallback({domain: d1}, common.mustCall(function() {
105+
assert.strictEqual(d1, process.domain);
106+
makeCallback({domain: d2}, common.mustCall(function() {
107+
assert.strictEqual(d2, process.domain);
108+
makeCallback({domain: d3}, common.mustCall(function() {
109+
assert.strictEqual(d3, process.domain);
110+
}));
111+
assert.strictEqual(d2, process.domain);
112+
}));
113+
assert.strictEqual(d1, process.domain);
114+
}));
115+
}));
116+
117+
setTimeout(common.mustCall(function() {
118+
const d1 = domain.create();
119+
const d2 = domain.create();
120+
const d3 = domain.create();
121+
122+
makeCallback({domain: d1}, common.mustCall(function() {
123+
assert.strictEqual(d1, process.domain);
124+
makeCallback({domain: d2}, common.mustCall(function() {
125+
assert.strictEqual(d2, process.domain);
126+
makeCallback({domain: d3}, common.mustCall(function() {
127+
assert.strictEqual(d3, process.domain);
128+
}));
129+
assert.strictEqual(d2, process.domain);
130+
}));
131+
assert.strictEqual(d1, process.domain);
132+
}));
133+
}), 1);
134+
135+
function testTimer(id) {
136+
// Make sure nextTick, setImmediate and setTimeout can all recover properly
137+
// after a thrown makeCallback call.
138+
const d = domain.create();
139+
d.on('error', common.mustCall(function(e) {
140+
assert.strictEqual(e.message, `throw from domain ${id}`);
141+
}));
142+
makeCallback({domain: d}, function() {
143+
throw new Error(`throw from domain ${id}`);
144+
});
145+
throw new Error('UNREACHABLE');
146+
}
147+
148+
process.nextTick(common.mustCall(testTimer), 3);
149+
setImmediate(common.mustCall(testTimer), 2);
150+
setTimeout(common.mustCall(testTimer), 1, 1);
151+
}

0 commit comments

Comments
 (0)