-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add addons test for MakeCallback
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected PR-URL: #4507 Reviewed-By: Fedor Indutny <fedor@indutny.com>
- Loading branch information
1 parent
830bb04
commit e7bf951
Showing
3 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#include "node.h" | ||
#include "v8.h" | ||
|
||
#include "../../../src/util.h" | ||
|
||
using v8::Function; | ||
using v8::FunctionCallbackInfo; | ||
using v8::Isolate; | ||
using v8::Local; | ||
using v8::Object; | ||
using v8::Value; | ||
|
||
namespace { | ||
|
||
void MakeCallback(const FunctionCallbackInfo<Value>& args) { | ||
CHECK(args[0]->IsObject()); | ||
CHECK(args[1]->IsFunction()); | ||
Isolate* isolate = args.GetIsolate(); | ||
Local<Object> recv = args[0].As<Object>(); | ||
Local<Function> method = args[1].As<Function>(); | ||
|
||
node::MakeCallback(isolate, recv, method, 0, nullptr); | ||
} | ||
|
||
void Initialize(Local<Object> target) { | ||
NODE_SET_METHOD(target, "makeCallback", MakeCallback); | ||
} | ||
|
||
} // namespace anonymous | ||
|
||
NODE_MODULE(binding, Initialize) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
'targets': [ | ||
{ | ||
'target_name': 'binding', | ||
'sources': [ 'binding.cc' ] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
'use strict'; | ||
|
||
const common = require('../../common'); | ||
const assert = require('assert'); | ||
const domain = require('domain'); | ||
const binding = require('./build/Release/binding'); | ||
const makeCallback = binding.makeCallback; | ||
|
||
// Make sure this is run in the future. | ||
const mustCallCheckDomains = common.mustCall(checkDomains); | ||
|
||
|
||
// Make sure that using MakeCallback allows the error to propagate. | ||
assert.throws(function() { | ||
makeCallback({}, function() { | ||
throw new Error('hi from domain error'); | ||
}); | ||
}); | ||
|
||
|
||
// Check the execution order of the nextTickQueue and MicrotaskQueue in | ||
// relation to running multiple MakeCallback's from bootstrap, | ||
// node::MakeCallback() and node::AsyncWrap::MakeCallback(). | ||
// TODO(trevnorris): Is there a way to verify this is being run during | ||
// bootstrap? | ||
(function verifyExecutionOrder(arg) { | ||
const results_arr = []; | ||
|
||
// Processing of the MicrotaskQueue is manually handled by node. They are not | ||
// processed until after the nextTickQueue has been processed. | ||
Promise.resolve(1).then(common.mustCall(function() { | ||
results_arr.push(7); | ||
})); | ||
|
||
// The nextTick should run after all immediately invoked calls. | ||
process.nextTick(common.mustCall(function() { | ||
results_arr.push(3); | ||
|
||
// Run same test again but while processing the nextTickQueue to make sure | ||
// the following MakeCallback call breaks in the middle of processing the | ||
// queue and allows the script to run normally. | ||
process.nextTick(common.mustCall(function() { | ||
results_arr.push(6); | ||
})); | ||
|
||
makeCallback({}, common.mustCall(function() { | ||
results_arr.push(4); | ||
})); | ||
|
||
results_arr.push(5); | ||
})); | ||
|
||
results_arr.push(0); | ||
|
||
// MakeCallback is calling the function immediately, but should then detect | ||
// that a script is already in the middle of execution and return before | ||
// either the nextTickQueue or MicrotaskQueue are processed. | ||
makeCallback({}, common.mustCall(function() { | ||
results_arr.push(1); | ||
})); | ||
|
||
// This should run before either the nextTickQueue or MicrotaskQueue are | ||
// processed. Previously MakeCallback would not detect this circumstance | ||
// and process them immediately. | ||
results_arr.push(2); | ||
|
||
setImmediate(common.mustCall(function() { | ||
for (var i = 0; i < results_arr.length; i++) { | ||
assert.equal(results_arr[i], | ||
i, | ||
`verifyExecutionOrder(${arg}) results: ${results_arr}`); | ||
} | ||
if (arg === 1) { | ||
// The tests are first run on bootstrap during LoadEnvironment() in | ||
// src/node.cc. Now run the tests through node::MakeCallback(). | ||
setImmediate(function() { | ||
makeCallback({}, common.mustCall(function() { | ||
verifyExecutionOrder(2); | ||
})); | ||
}); | ||
} else if (arg === 2) { | ||
// setTimeout runs via the TimerWrap, which runs through | ||
// AsyncWrap::MakeCallback(). Make sure there are no conflicts using | ||
// node::MakeCallback() within it. | ||
setTimeout(common.mustCall(function() { | ||
verifyExecutionOrder(3); | ||
}), 10); | ||
} else if (arg === 3) { | ||
mustCallCheckDomains(); | ||
} else { | ||
throw new Error('UNREACHABLE'); | ||
} | ||
})); | ||
}(1)); | ||
|
||
|
||
function checkDomains() { | ||
// Check that domains are properly entered/exited when called in multiple | ||
// levels from both node::MakeCallback() and AsyncWrap::MakeCallback | ||
setImmediate(common.mustCall(function() { | ||
const d1 = domain.create(); | ||
const d2 = domain.create(); | ||
const d3 = domain.create(); | ||
|
||
makeCallback({ domain: d1 }, common.mustCall(function() { | ||
assert.equal(d1, process.domain); | ||
makeCallback({ domain: d2 }, common.mustCall(function() { | ||
assert.equal(d2, process.domain); | ||
makeCallback({ domain: d3 }, common.mustCall(function() { | ||
assert.equal(d3, process.domain); | ||
})); | ||
assert.equal(d2, process.domain); | ||
})); | ||
assert.equal(d1, process.domain); | ||
})); | ||
})); | ||
|
||
setTimeout(common.mustCall(function() { | ||
const d1 = domain.create(); | ||
const d2 = domain.create(); | ||
const d3 = domain.create(); | ||
|
||
makeCallback({ domain: d1 }, common.mustCall(function() { | ||
assert.equal(d1, process.domain); | ||
makeCallback({ domain: d2 }, common.mustCall(function() { | ||
assert.equal(d2, process.domain); | ||
makeCallback({ domain: d3 }, common.mustCall(function() { | ||
assert.equal(d3, process.domain); | ||
})); | ||
assert.equal(d2, process.domain); | ||
})); | ||
assert.equal(d1, process.domain); | ||
})); | ||
}), 1); | ||
|
||
// Make sure nextTick, setImmediate and setTimeout can all recover properly | ||
// after a thrown makeCallback call. | ||
process.nextTick(common.mustCall(function() { | ||
const d = domain.create(); | ||
d.on('error', common.mustCall(function(e) { | ||
assert.equal(e.message, 'throw from domain 3'); | ||
})); | ||
makeCallback({ domain: d }, function() { | ||
throw new Error('throw from domain 3'); | ||
}); | ||
throw new Error('UNREACHABLE'); | ||
})); | ||
|
||
setImmediate(common.mustCall(function() { | ||
const d = domain.create(); | ||
d.on('error', common.mustCall(function(e) { | ||
assert.equal(e.message, 'throw from domain 2'); | ||
})); | ||
makeCallback({ domain: d }, function() { | ||
throw new Error('throw from domain 2'); | ||
}); | ||
throw new Error('UNREACHABLE'); | ||
})); | ||
|
||
setTimeout(common.mustCall(function() { | ||
const d = domain.create(); | ||
d.on('error', common.mustCall(function(e) { | ||
assert.equal(e.message, 'throw from domain 1'); | ||
})); | ||
makeCallback({ domain: d }, function() { | ||
throw new Error('throw from domain 1'); | ||
}); | ||
throw new Error('UNREACHABLE'); | ||
})); | ||
} |