test: adding tests for initHooks API
Async wrap providers tested: - crypto.randomBytes - crypto.pbkdf2 - fs event wrap - fsreqwrap access - fsreqwrap readFile - getaddrinforeq wrap - getnameinforeq wrap - pipe connect wrap - query wrap - pipewrap - processwrap - shutdown wrap - tcpwrap - udpwrap - send wrap - detailed signal wrap - statwatcher - timerwrap via setTimeout - timerwrap via setInterval - for Immediate - http parser request - http parser response - connection via ssl server - tls wrap - write wrap - ttywrap via readstream - ttywrap via wriream - zctx via zlib binding deflate Embedder API: - async-event tests - one test looks at the happy paths - another ensures that in cases of events emitted in an order that doesn't make sense, the order is enforced by async hooks throwing a meaningful error - embedder enforcement tests are split up since async hook stack corruption now the process - therefore we launch a child and check for error output of the offending code Additional tests: - tests that show that we can enable/disable hooks inside their lifetime events - tests that verify the graph of resources triggering the creation of other resources Test Helpers: - init-hooks: - returns one collector instance - when created an async hook is created and the lifetime events are registered to call the appropriate collector functions - the collector also exposes `enable` and `disable` functions which call through to the async hook - hook checks: - checks invocations of life time hooks against the actual invocations that were collected - in some cases like `destroy` a min/max range of invocations can be supplied since in these cases the exact number is non-deterministic - verify graph: - verifies the triggerIds of specific async resources are as expected, i.e. the creation of resources was triggered by the resource we expect - includes a printGraph function to generate easily readable test input for verify graph - both functions prune TickObjects to create less brittle and easier to understand tests PR-URL: https://github.com/nodejs/node/pull/12892 Ref: https://github.com/nodejs/node/pull/11883 Ref: https://github.com/nodejs/node/pull/8531 Reviewed-By: Andreas Madsen <amwebdk@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
parent
4a7233c178
commit
e3e56f1d71
32
test/async-hooks/coverage.md
Normal file
32
test/async-hooks/coverage.md
Normal file
@ -0,0 +1,32 @@
|
||||
## AsyncHooks Coverage Overview
|
||||
|
||||
Showing which kind of async resource is covered by which test:
|
||||
|
||||
| Resource Type | Test |
|
||||
|----------------------|----------------------------------------|
|
||||
| CONNECTION | test-connection.ssl.js |
|
||||
| FSEVENTWRAP | test-fseventwrap.js |
|
||||
| FSREQWRAP | test-fsreqwrap-{access,readFile}.js |
|
||||
| GETADDRINFOREQWRAP | test-getaddrinforeqwrap.js |
|
||||
| GETNAMEINFOREQWRAP | test-getnameinforeqwrap.js |
|
||||
| HTTPPARSER | test-httpparser.{request,response}.js |
|
||||
| Immediate | test-immediate.js |
|
||||
| JSSTREAM | TODO (crashes when accessing directly) |
|
||||
| PBKDF2REQUEST | test-crypto-pbkdf2.js |
|
||||
| PIPECONNECTWRAP | test-pipeconnectwrap.js |
|
||||
| PIPEWRAP | test-pipewrap.js |
|
||||
| PROCESSWRAP | test-pipewrap.js |
|
||||
| QUERYWRAP | test-querywrap.js |
|
||||
| RANDOMBYTESREQUEST | test-crypto-randomBytes.js |
|
||||
| SHUTDOWNWRAP | test-shutdownwrap.js |
|
||||
| SIGNALWRAP | test-signalwrap.js |
|
||||
| STATWATCHER | test-statwatcher.js |
|
||||
| TCPCONNECTWRAP | test-tcpwrap.js |
|
||||
| TCPWRAP | test-tcpwrap.js |
|
||||
| TIMERWRAP | test-timerwrap.set{Timeout,Interval}.js|
|
||||
| TLSWRAP | test-tlswrap.js |
|
||||
| TTYWRAP | test-ttywrap.{read,write}stream.js |
|
||||
| UDPSENDWRAP | test-udpsendwrap.js |
|
||||
| UDPWRAP | test-udpwrap.js |
|
||||
| WRITEWRAP | test-writewrap.js |
|
||||
| ZLIB | test-zlib.zlib-binding.deflate.js |
|
54
test/async-hooks/hook-checks.js
Normal file
54
test/async-hooks/hook-checks.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
require('../common');
|
||||
|
||||
/**
|
||||
* Checks the expected invocations against the invocations that actually
|
||||
* occurred.
|
||||
*
|
||||
* @name checkInvocations
|
||||
* @function
|
||||
* @param {Object} activity including timestamps for each life time event,
|
||||
* i.e. init, before ...
|
||||
* @param {Object} hooks the expected life time event invocations with a count
|
||||
* indicating how oftn they should have been invoked,
|
||||
* i.e. `{ init: 1, before: 2, after: 2 }`
|
||||
* @param {String} stage the name of the stage in the test at which we are
|
||||
* checking the invocations
|
||||
*/
|
||||
exports.checkInvocations = function checkInvocations(activity, hooks, stage) {
|
||||
const stageInfo = `Checking invocations at stage "${stage}":\n `;
|
||||
|
||||
assert.ok(activity != null,
|
||||
`${stageInfo} Trying to check invocation for an activity, ` +
|
||||
'but it was empty/undefined.'
|
||||
);
|
||||
|
||||
// Check that actual invocations for all hooks match the expected invocations
|
||||
[ 'init', 'before', 'after', 'destroy' ].forEach(checkHook);
|
||||
|
||||
function checkHook(k) {
|
||||
const val = hooks[k];
|
||||
// Not expected ... all good
|
||||
if (val == null) return;
|
||||
|
||||
if (val === 0) {
|
||||
// Didn't expect any invocations, but it was actually invoked
|
||||
const invocations = activity[k].length;
|
||||
const msg = `${stageInfo} Called "${k}" ${invocations} time(s), ` +
|
||||
'but expected no invocations.';
|
||||
assert(activity[k] === null && activity[k] === undefined, msg);
|
||||
} else {
|
||||
// Expected some invocations, make sure that it was invoked at all
|
||||
const msg1 = `${stageInfo} Never called "${k}", ` +
|
||||
`but expected ${val} invocation(s).`;
|
||||
assert(activity[k] !== null && activity[k] !== undefined, msg1);
|
||||
|
||||
// Now make sure that the expected count and
|
||||
// the actual invocation count match
|
||||
const msg2 = `${stageInfo} Called "${k}" ${activity[k].length} ` +
|
||||
`time(s), but expected ${val} invocation(s).`;
|
||||
assert.strictEqual(activity[k].length, val, msg2);
|
||||
}
|
||||
}
|
||||
};
|
219
test/async-hooks/init-hooks.js
Normal file
219
test/async-hooks/init-hooks.js
Normal file
@ -0,0 +1,219 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const util = require('util');
|
||||
const print = process._rawDebug;
|
||||
require('../common');
|
||||
|
||||
if (typeof global.gc === 'function') {
|
||||
(function exity(cntr) {
|
||||
process.once('beforeExit', () => {
|
||||
global.gc();
|
||||
if (cntr < 4) setImmediate(() => exity(cntr + 1));
|
||||
});
|
||||
})(0);
|
||||
}
|
||||
|
||||
function noop() {}
|
||||
|
||||
class ActivityCollector {
|
||||
constructor(start, {
|
||||
allowNoInit = false,
|
||||
oninit,
|
||||
onbefore,
|
||||
onafter,
|
||||
ondestroy,
|
||||
logid = null,
|
||||
logtype = null
|
||||
} = {}) {
|
||||
this._start = start;
|
||||
this._allowNoInit = allowNoInit;
|
||||
this._activities = new Map();
|
||||
this._logid = logid;
|
||||
this._logtype = logtype;
|
||||
|
||||
// register event handlers if provided
|
||||
this.oninit = typeof oninit === 'function' ? oninit : noop;
|
||||
this.onbefore = typeof onbefore === 'function' ? onbefore : noop;
|
||||
this.onafter = typeof onafter === 'function' ? onafter : noop;
|
||||
this.ondestroy = typeof ondestroy === 'function' ? ondestroy : noop;
|
||||
|
||||
// create the hook with which we'll collect activity data
|
||||
this._asyncHook = async_hooks.createHook({
|
||||
init: this._init.bind(this),
|
||||
before: this._before.bind(this),
|
||||
after: this._after.bind(this),
|
||||
destroy: this._destroy.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._asyncHook.enable();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._asyncHook.disable();
|
||||
}
|
||||
|
||||
sanityCheck(types) {
|
||||
if (types != null && !Array.isArray(types)) types = [ types ];
|
||||
|
||||
function activityString(a) {
|
||||
return util.inspect(a, false, 5, true);
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
function v(msg) { violations.push(msg); }
|
||||
for (const a of this._activities.values()) {
|
||||
if (types != null && types.indexOf(a.type) < 0) continue;
|
||||
|
||||
if (a.init && a.init.length > 1) {
|
||||
v('Activity inited twice\n' + activityString(a) +
|
||||
'\nExpected "init" to be called at most once');
|
||||
}
|
||||
if (a.destroy && a.destroy.length > 1) {
|
||||
v('Activity destroyed twice\n' + activityString(a) +
|
||||
'\nExpected "destroy" to be called at most once');
|
||||
}
|
||||
if (a.before && a.after) {
|
||||
if (a.before.length < a.after.length) {
|
||||
v('Activity called "after" without calling "before"\n' +
|
||||
activityString(a) +
|
||||
'\nExpected no "after" call without a "before"');
|
||||
}
|
||||
if (a.before.some((x, idx) => x > a.after[idx])) {
|
||||
v('Activity had an instance where "after" ' +
|
||||
'was invoked before "before"\n' +
|
||||
activityString(a) +
|
||||
'\nExpected "after" to be called after "before"');
|
||||
}
|
||||
}
|
||||
if (a.before && a.destroy) {
|
||||
if (a.before.some((x, idx) => x > a.destroy[idx])) {
|
||||
v('Activity had an instance where "destroy" ' +
|
||||
'was invoked before "before"\n' +
|
||||
activityString(a) +
|
||||
'\nExpected "destroy" to be called after "before"');
|
||||
}
|
||||
}
|
||||
if (a.after && a.destroy) {
|
||||
if (a.after.some((x, idx) => x > a.destroy[idx])) {
|
||||
v('Activity had an instance where "destroy" ' +
|
||||
'was invoked before "after"\n' +
|
||||
activityString(a) +
|
||||
'\nExpected "destroy" to be called after "after"');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (violations.length) {
|
||||
console.error(violations.join('\n'));
|
||||
assert.fail(violations.length, 0, 'Failed sanity check');
|
||||
}
|
||||
}
|
||||
|
||||
inspect(opts = {}) {
|
||||
if (typeof opts === 'string') opts = { types: opts };
|
||||
const { types = null, depth = 5, stage = null } = opts;
|
||||
const activities = types == null ?
|
||||
Array.from(this._activities.values()) :
|
||||
this.activitiesOfTypes(types);
|
||||
|
||||
if (stage != null) console.log('\n%s', stage);
|
||||
console.log(util.inspect(activities, false, depth, true));
|
||||
}
|
||||
|
||||
activitiesOfTypes(types) {
|
||||
if (!Array.isArray(types)) types = [ types ];
|
||||
return this.activities.filter((x) => types.indexOf(x.type) >= 0);
|
||||
}
|
||||
|
||||
get activities() {
|
||||
return Array.from(this._activities.values());
|
||||
}
|
||||
|
||||
_stamp(h, hook) {
|
||||
if (h == null) return;
|
||||
if (h[hook] == null) h[hook] = [];
|
||||
const time = process.hrtime(this._start);
|
||||
h[hook].push((time[0] * 1e9) + time[1]);
|
||||
}
|
||||
|
||||
_getActivity(uid, hook) {
|
||||
const h = this._activities.get(uid);
|
||||
if (!h) {
|
||||
// if we allowed handles without init we ignore any further life time
|
||||
// events this makes sense for a few tests in which we enable some hooks
|
||||
// later
|
||||
if (this._allowNoInit) {
|
||||
const stub = { uid, type: 'Unknown' };
|
||||
this._activities.set(uid, stub);
|
||||
return stub;
|
||||
} else {
|
||||
const err = new Error('Found a handle who\'s ' + hook +
|
||||
' hook was invoked but not it\'s init hook');
|
||||
// Don't throw if we see invocations due to an assertion in a test
|
||||
// failing since we want to list the assertion failure instead
|
||||
if (/process\._fatalException/.test(err.stack)) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
_init(uid, type, triggerId, handle) {
|
||||
const activity = { uid, type, triggerId };
|
||||
this._stamp(activity, 'init');
|
||||
this._activities.set(uid, activity);
|
||||
this._maybeLog(uid, type, 'init');
|
||||
this.oninit(uid, type, triggerId, handle);
|
||||
}
|
||||
|
||||
_before(uid) {
|
||||
const h = this._getActivity(uid, 'before');
|
||||
this._stamp(h, 'before');
|
||||
this._maybeLog(uid, h && h.type, 'before');
|
||||
this.onbefore(uid);
|
||||
}
|
||||
|
||||
_after(uid) {
|
||||
const h = this._getActivity(uid, 'after');
|
||||
this._stamp(h, 'after');
|
||||
this._maybeLog(uid, h && h.type, 'after');
|
||||
this.onafter(uid);
|
||||
}
|
||||
|
||||
_destroy(uid) {
|
||||
const h = this._getActivity(uid, 'destroy');
|
||||
this._stamp(h, 'destroy');
|
||||
this._maybeLog(uid, h && h.type, 'destroy');
|
||||
this.ondestroy(uid);
|
||||
}
|
||||
|
||||
_maybeLog(uid, type, name) {
|
||||
if (this._logid &&
|
||||
(type == null || this._logtype == null || this._logtype === type)) {
|
||||
print(this._logid + '.' + name + '.uid-' + uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports = module.exports = function initHooks({
|
||||
oninit,
|
||||
onbefore,
|
||||
onafter,
|
||||
ondestroy,
|
||||
allowNoInit,
|
||||
logid,
|
||||
logtype } = {}) {
|
||||
return new ActivityCollector(process.hrtime(), {
|
||||
oninit,
|
||||
onbefore,
|
||||
onafter,
|
||||
ondestroy,
|
||||
allowNoInit,
|
||||
logid,
|
||||
logtype
|
||||
});
|
||||
};
|
90
test/async-hooks/test-connection.ssl.js
Normal file
90
test/async-hooks/test-connection.ssl.js
Normal file
@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
const tick = require('./tick');
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const tls = require('tls');
|
||||
const Connection = process.binding('crypto').Connection;
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
function createServerConnection(
|
||||
onhandshakestart,
|
||||
certificate = null,
|
||||
isServer = true,
|
||||
servername = 'some server',
|
||||
rejectUnauthorized
|
||||
) {
|
||||
if (certificate == null) certificate = tls.createSecureContext();
|
||||
const ssl = new Connection(
|
||||
certificate.context, isServer, servername, rejectUnauthorized
|
||||
);
|
||||
if (isServer) {
|
||||
ssl.onhandshakestart = onhandshakestart;
|
||||
ssl.lastHandshakeTime = 0;
|
||||
}
|
||||
return ssl;
|
||||
}
|
||||
|
||||
// creating first server connection
|
||||
const sc1 = createServerConnection(common.mustCall(onfirstHandShake));
|
||||
|
||||
let as = hooks.activitiesOfTypes('CONNECTION');
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one CONNECTION after first connection created');
|
||||
const f1 = as[0];
|
||||
assert.strictEqual(f1.type, 'CONNECTION', 'connection');
|
||||
assert.strictEqual(typeof f1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof f1.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(f1, { init: 1 }, 'first connection, when first created');
|
||||
|
||||
// creating second server connection
|
||||
const sc2 = createServerConnection(common.mustCall(onsecondHandShake));
|
||||
|
||||
as = hooks.activitiesOfTypes('CONNECTION');
|
||||
assert.strictEqual(as.length, 2,
|
||||
'two CONNECTIONs after second connection created');
|
||||
const f2 = as[1];
|
||||
assert.strictEqual(f2.type, 'CONNECTION', 'connection');
|
||||
assert.strictEqual(typeof f2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof f2.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(f1, { init: 1 }, 'first connection, when second created');
|
||||
checkInvocations(f2, { init: 1 }, 'second connection, when second created');
|
||||
|
||||
// starting the connections which results in handshake starts
|
||||
sc1.start();
|
||||
sc2.start();
|
||||
|
||||
function onfirstHandShake() {
|
||||
checkInvocations(f1, { init: 1, before: 1 },
|
||||
'first connection, when first handshake');
|
||||
checkInvocations(f2, { init: 1 }, 'second connection, when first handshake');
|
||||
}
|
||||
|
||||
function onsecondHandShake() {
|
||||
checkInvocations(f1, { init: 1, before: 1, after: 1 },
|
||||
'first connection, when second handshake');
|
||||
checkInvocations(f2, { init: 1, before: 1 },
|
||||
'second connection, when second handshake');
|
||||
tick(1E4);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('CONNECTION');
|
||||
|
||||
checkInvocations(f1, { init: 1, before: 1, after: 1 },
|
||||
'first connection, when process exits');
|
||||
checkInvocations(f2, { init: 1, before: 1, after: 1 },
|
||||
'second connection, when process exits');
|
||||
}
|
42
test/async-hooks/test-crypto-pbkdf2.js
Normal file
42
test/async-hooks/test-crypto-pbkdf2.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const crypto = require('crypto');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
|
||||
crypto.pbkdf2('password', 'salt', 1, 20, 'sha256', common.mustCall(onpbkdf2));
|
||||
|
||||
function onpbkdf2() {
|
||||
const as = hooks.activitiesOfTypes('PBKDF2REQUEST');
|
||||
const a = as[0];
|
||||
checkInvocations(a, { init: 1, before: 1 }, 'while in onpbkdf2 callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('PBKDF2REQUEST');
|
||||
|
||||
const as = hooks.activitiesOfTypes('PBKDF2REQUEST');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'PBKDF2REQUEST', 'random byte request');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
43
test/async-hooks/test-crypto-randomBytes.js
Normal file
43
test/async-hooks/test-crypto-randomBytes.js
Normal file
@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const crypto = require('crypto');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
crypto.randomBytes(1, common.mustCall(onrandomBytes));
|
||||
|
||||
function onrandomBytes() {
|
||||
const as = hooks.activitiesOfTypes('RANDOMBYTESREQUEST');
|
||||
const a = as[0];
|
||||
checkInvocations(a, { init: 1, before: 1 },
|
||||
'while in onrandomBytes callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('RANDOMBYTESREQUEST');
|
||||
|
||||
const as = hooks.activitiesOfTypes('RANDOMBYTESREQUEST');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'RANDOMBYTESREQUEST', 'random byte request');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { AsyncEvent } = async_hooks;
|
||||
const { spawn } = require('child_process');
|
||||
const corruptedMsg = /async hook stack has become corrupted/;
|
||||
const heartbeatMsg = /heartbeat: still alive/;
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// once 'destroy' has been emitted, we can no longer emit 'after'
|
||||
|
||||
// Emitting 'before', 'after' and then 'destroy'
|
||||
const event1 = new AsyncEvent('event1', async_hooks.currentId());
|
||||
event1.emitBefore();
|
||||
event1.emitAfter();
|
||||
event1.emitDestroy();
|
||||
|
||||
// Emitting 'after' after 'destroy'
|
||||
const event2 = new AsyncEvent('event2', async_hooks.currentId());
|
||||
event2.emitDestroy();
|
||||
|
||||
console.log('heartbeat: still alive');
|
||||
event2.emitAfter();
|
||||
|
||||
} else {
|
||||
const args = process.argv.slice(1).concat('child');
|
||||
let errData = Buffer.from('');
|
||||
let outData = Buffer.from('');
|
||||
|
||||
const child = spawn(process.execPath, args);
|
||||
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
|
||||
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
|
||||
|
||||
child.on('close', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 1, 'exit code 1');
|
||||
assert.ok(heartbeatMsg.test(outData.toString()),
|
||||
'did not crash until we reached offending line of code');
|
||||
assert.ok(corruptedMsg.test(errData.toString()),
|
||||
'printed error contains corrupted message');
|
||||
}));
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { AsyncEvent } = async_hooks;
|
||||
const { spawn } = require('child_process');
|
||||
const corruptedMsg = /async hook stack has become corrupted/;
|
||||
const heartbeatMsg = /heartbeat: still alive/;
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// once 'destroy' has been emitted, we can no longer emit 'before'
|
||||
|
||||
// Emitting 'before', 'after' and then 'destroy'
|
||||
const event1 = new AsyncEvent('event1', async_hooks.currentId());
|
||||
event1.emitBefore();
|
||||
event1.emitAfter();
|
||||
event1.emitDestroy();
|
||||
|
||||
// Emitting 'before' after 'destroy'
|
||||
const event2 = new AsyncEvent('event2', async_hooks.currentId());
|
||||
event2.emitDestroy();
|
||||
|
||||
console.log('heartbeat: still alive');
|
||||
event2.emitBefore();
|
||||
|
||||
} else {
|
||||
const args = process.argv.slice(1).concat('child');
|
||||
let errData = Buffer.from('');
|
||||
let outData = Buffer.from('');
|
||||
|
||||
const child = spawn(process.execPath, args);
|
||||
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
|
||||
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
|
||||
|
||||
child.on('close', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 1, 'exit code 1');
|
||||
assert.ok(heartbeatMsg.test(outData.toString()),
|
||||
'did not crash until we reached offending line of code');
|
||||
assert.ok(corruptedMsg.test(errData.toString()),
|
||||
'printed error contains corrupted message');
|
||||
}));
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { AsyncEvent } = async_hooks;
|
||||
const { spawn } = require('child_process');
|
||||
const corruptedMsg = /async hook stack has become corrupted/;
|
||||
const heartbeatMsg = /heartbeat: still alive/;
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// async hooks enforce proper order of 'before' and 'after' invocations
|
||||
|
||||
// Proper ordering
|
||||
const event1 = new AsyncEvent('event1', async_hooks.currentId());
|
||||
event1.emitBefore();
|
||||
event1.emitAfter();
|
||||
|
||||
// Improper ordering
|
||||
// Emitting 'after' without 'before' which is illegal
|
||||
const event2 = new AsyncEvent('event2', async_hooks.currentId());
|
||||
|
||||
console.log('heartbeat: still alive');
|
||||
event2.emitAfter();
|
||||
} else {
|
||||
const args = process.argv.slice(1).concat('child');
|
||||
let errData = Buffer.from('');
|
||||
let outData = Buffer.from('');
|
||||
|
||||
const child = spawn(process.execPath, args);
|
||||
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
|
||||
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
|
||||
|
||||
child.on('close', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 1, 'exit code 1');
|
||||
assert.ok(heartbeatMsg.test(outData.toString()),
|
||||
'did not crash until we reached offending line of code');
|
||||
assert.ok(corruptedMsg.test(errData.toString()),
|
||||
'printed error contains corrupted message');
|
||||
}));
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { AsyncEvent } = async_hooks;
|
||||
const { spawn } = require('child_process');
|
||||
const corruptedMsg = /async hook stack has become corrupted/;
|
||||
const heartbeatMsg = /heartbeat: still alive/;
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// In both the below two cases 'before' of event2 is nested inside 'before'
|
||||
// of event1.
|
||||
// Therefore the 'after' of event2 needs to occur before the
|
||||
// 'after' of event 1.
|
||||
// The first test of the two below follows that rule,
|
||||
// the second one doesnt.
|
||||
|
||||
const event1 = new AsyncEvent('event1', async_hooks.currentId());
|
||||
const event2 = new AsyncEvent('event2', async_hooks.currentId());
|
||||
|
||||
// Proper unwind
|
||||
event1.emitBefore();
|
||||
event2.emitBefore();
|
||||
event2.emitAfter();
|
||||
event1.emitAfter();
|
||||
|
||||
// Improper unwind
|
||||
event1.emitBefore();
|
||||
event2.emitBefore();
|
||||
|
||||
console.log('heartbeat: still alive');
|
||||
event1.emitAfter();
|
||||
} else {
|
||||
const args = process.argv.slice(1).concat('child');
|
||||
let errData = Buffer.from('');
|
||||
let outData = Buffer.from('');
|
||||
|
||||
const child = spawn(process.execPath, args);
|
||||
child.stderr.on('data', (d) => { errData = Buffer.concat([ errData, d ]); });
|
||||
child.stdout.on('data', (d) => { outData = Buffer.concat([ outData, d ]); });
|
||||
|
||||
child.on('close', common.mustCall((code) => {
|
||||
assert.strictEqual(code, 1, 'exit code 1');
|
||||
assert.ok(heartbeatMsg.test(outData.toString()),
|
||||
'did not crash until we reached offending line of code');
|
||||
assert.ok(corruptedMsg.test(errData.toString()),
|
||||
'printed error contains corrupted message');
|
||||
}));
|
||||
}
|
85
test/async-hooks/test-embedder.api.async-event.js
Normal file
85
test/async-hooks/test-embedder.api.async-event.js
Normal file
@ -0,0 +1,85 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const async_hooks = require('async_hooks');
|
||||
const { AsyncEvent } = async_hooks;
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// create first custom event 'alcazares' with triggerId derived
|
||||
// from async_hooks currentId
|
||||
const alcaTriggerId = async_hooks.currentId();
|
||||
const alcaEvent = new AsyncEvent('alcazares', alcaTriggerId);
|
||||
const alcazaresActivities = hooks.activitiesOfTypes([ 'alcazares' ]);
|
||||
|
||||
// alcazares event was constructed and thus only has an `init` call
|
||||
assert.strictEqual(alcazaresActivities.length, 1,
|
||||
'one alcazares activity after one was constructed');
|
||||
const alcazares = alcazaresActivities[0];
|
||||
assert.strictEqual(alcazares.type, 'alcazares', 'alcazares');
|
||||
assert.strictEqual(typeof alcazares.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(alcazares.triggerId, alcaTriggerId,
|
||||
'triggerId is the one supplied');
|
||||
checkInvocations(alcazares, { init: 1 }, 'alcazares constructed');
|
||||
|
||||
alcaEvent.emitBefore();
|
||||
checkInvocations(alcazares, { init: 1, before: 1 },
|
||||
'alcazares emitted before');
|
||||
alcaEvent.emitAfter();
|
||||
checkInvocations(alcazares, { init: 1, before: 1, after: 1 },
|
||||
'alcazares emitted after');
|
||||
alcaEvent.emitBefore();
|
||||
checkInvocations(alcazares, { init: 1, before: 2, after: 1 },
|
||||
'alcazares emitted before again');
|
||||
alcaEvent.emitAfter();
|
||||
checkInvocations(alcazares, { init: 1, before: 2, after: 2 },
|
||||
'alcazares emitted after again');
|
||||
alcaEvent.emitDestroy();
|
||||
tick(1, common.mustCall(tick1));
|
||||
|
||||
function tick1() {
|
||||
checkInvocations(alcazares, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'alcazares emitted destroy');
|
||||
|
||||
// The below shows that we can pass any number as a trigger id
|
||||
const pobTriggerId = 111;
|
||||
const pobEvent = new AsyncEvent('poblado', pobTriggerId);
|
||||
const pobladoActivities = hooks.activitiesOfTypes([ 'poblado' ]);
|
||||
const poblado = pobladoActivities[0];
|
||||
assert.strictEqual(poblado.type, 'poblado', 'poblado');
|
||||
assert.strictEqual(typeof poblado.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(poblado.triggerId, pobTriggerId,
|
||||
'triggerId is the one supplied');
|
||||
checkInvocations(poblado, { init: 1 }, 'poblado constructed');
|
||||
pobEvent.emitBefore();
|
||||
checkInvocations(poblado, { init: 1, before: 1 },
|
||||
'poblado emitted before');
|
||||
|
||||
pobEvent.emitAfter();
|
||||
checkInvocations(poblado, { init: 1, before: 1, after: 1 },
|
||||
'poblado emitted after');
|
||||
|
||||
// after we disable the hooks we shouldn't receive any events anymore
|
||||
hooks.disable();
|
||||
alcaEvent.emitDestroy();
|
||||
tick(1, common.mustCall(tick2));
|
||||
|
||||
function tick2() {
|
||||
checkInvocations(
|
||||
alcazares, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'alcazares emitted destroy a second time after hooks disabled');
|
||||
pobEvent.emitDestroy();
|
||||
tick(1, common.mustCall(tick3));
|
||||
}
|
||||
|
||||
function tick3() {
|
||||
checkInvocations(poblado, { init: 1, before: 1, after: 1 },
|
||||
'poblado emitted destroy after hooks disabled');
|
||||
}
|
||||
}
|
274
test/async-hooks/test-enable-disable.js
Normal file
274
test/async-hooks/test-enable-disable.js
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Test Steps Explained
|
||||
* ====================
|
||||
*
|
||||
* Initializing hooks:
|
||||
*
|
||||
* We initialize 3 hooks. For hook2 and hook3 we register a callback for the
|
||||
* "before" and in case of hook3 also for the "after" invocations.
|
||||
*
|
||||
* Enabling hooks initially:
|
||||
*
|
||||
* We only enable hook1 and hook3 initially.
|
||||
*
|
||||
* Enabling hook2:
|
||||
*
|
||||
* When hook3's "before" invocation occurs we enable hook2. Since this
|
||||
* happens right before calling `onfirstImmediate` hook2 will miss all hook
|
||||
* invocations until then, including the "init" and "before" of the first
|
||||
* Immediate.
|
||||
* However afterwards it collects all invocations that follow on the first
|
||||
* Immediate as well as all invocations on the second Immediate.
|
||||
*
|
||||
* This shows that a hook can enable another hook inside a life time event
|
||||
* callback.
|
||||
*
|
||||
*
|
||||
* Disabling hook1
|
||||
*
|
||||
* Since we registered the "before" callback for hook2 it will execute it
|
||||
* right before `onsecondImmediate` is called.
|
||||
* At that point we disable hook1 which is why it will miss all invocations
|
||||
* afterwards and thus won't include the second "after" as well as the
|
||||
* "destroy" invocations
|
||||
*
|
||||
* This shows that a hook can disable another hook inside a life time event
|
||||
* callback.
|
||||
*
|
||||
* Disabling hook3
|
||||
*
|
||||
* When the second "after" invocation occurs (after onsecondImmediate), hook3
|
||||
* disables itself.
|
||||
* As a result it will not receive the "destroy" invocation.
|
||||
*
|
||||
* This shows that a hook can disable itself inside a life time event callback.
|
||||
*
|
||||
* Sample Test Log
|
||||
* ===============
|
||||
*
|
||||
* - setting up first Immediate
|
||||
* hook1.init.uid-5
|
||||
* hook3.init.uid-5
|
||||
* - finished setting first Immediate
|
||||
|
||||
* hook1.before.uid-5
|
||||
* hook3.before.uid-5
|
||||
* - enabled hook2
|
||||
* - entering onfirstImmediate
|
||||
|
||||
* - setting up second Immediate
|
||||
* hook1.init.uid-6
|
||||
* hook3.init.uid-6
|
||||
* hook2.init.uid-6
|
||||
* - finished setting second Immediate
|
||||
|
||||
* - exiting onfirstImmediate
|
||||
* hook1.after.uid-5
|
||||
* hook3.after.uid-5
|
||||
* hook2.after.uid-5
|
||||
* hook1.destroy.uid-5
|
||||
* hook3.destroy.uid-5
|
||||
* hook2.destroy.uid-5
|
||||
* hook1.before.uid-6
|
||||
* hook3.before.uid-6
|
||||
* hook2.before.uid-6
|
||||
* - disabled hook1
|
||||
* - entering onsecondImmediate
|
||||
* - exiting onsecondImmediate
|
||||
* hook3.after.uid-6
|
||||
* - disabled hook3
|
||||
* hook2.after.uid-6
|
||||
* hook2.destroy.uid-6
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
// Include "Unknown"s because hook2 will not be able to identify
|
||||
// the type of the first Immediate since it will miss its `init` invocation.
|
||||
const types = [ 'Immediate', 'Unknown' ];
|
||||
|
||||
//
|
||||
// Initializing hooks
|
||||
//
|
||||
const hook1 = initHooks();
|
||||
const hook2 = initHooks({ onbefore: onhook2Before, allowNoInit: true });
|
||||
const hook3 = initHooks({ onbefore: onhook3Before, onafter: onhook3After });
|
||||
|
||||
//
|
||||
// Enabling hook1 and hook3 only, hook2 is still disabled
|
||||
//
|
||||
hook1.enable();
|
||||
hook3.enable();
|
||||
|
||||
//
|
||||
// Enabling hook2
|
||||
//
|
||||
let enabledHook2 = false;
|
||||
function onhook3Before() {
|
||||
if (enabledHook2) return;
|
||||
hook2.enable();
|
||||
enabledHook2 = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Disabling hook1
|
||||
//
|
||||
let disabledHook3 = false;
|
||||
function onhook2Before() {
|
||||
if (disabledHook3) return;
|
||||
hook1.disable();
|
||||
disabledHook3 = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Disabling hook3 during the second "after" invocations it sees
|
||||
//
|
||||
let count = 2;
|
||||
function onhook3After() {
|
||||
if (!--count) {
|
||||
hook3.disable();
|
||||
}
|
||||
}
|
||||
|
||||
setImmediate(common.mustCall(onfirstImmediate));
|
||||
|
||||
//
|
||||
// onfirstImmediate is called after all "init" and "before" callbacks of the
|
||||
// active hooks were invoked
|
||||
//
|
||||
function onfirstImmediate() {
|
||||
const as1 = hook1.activitiesOfTypes(types);
|
||||
const as2 = hook2.activitiesOfTypes(types);
|
||||
const as3 = hook3.activitiesOfTypes(types);
|
||||
assert.strictEqual(as1.length, 1,
|
||||
'hook1 captured one immediate on first callback');
|
||||
// hook2 was not enabled yet .. it is enabled after hook3's "before" completed
|
||||
assert.strictEqual(as2.length, 0,
|
||||
'hook2 captured no immediate on first callback');
|
||||
assert.strictEqual(as3.length, 1,
|
||||
'hook3 captured one immediate on first callback');
|
||||
|
||||
// Check that hook1 and hook3 captured the same Immediate and that it is valid
|
||||
const firstImmediate = as1[0];
|
||||
assert.strictEqual(as3[0].uid, as1[0].uid,
|
||||
'hook1 and hook3 captured same first immediate');
|
||||
assert.strictEqual(firstImmediate.type, 'Immediate', 'immediate');
|
||||
assert.strictEqual(typeof firstImmediate.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof firstImmediate.triggerId,
|
||||
'number', 'triggerId is a number');
|
||||
checkInvocations(as1[0], { init: 1, before: 1 },
|
||||
'hook1[0]: on first immediate');
|
||||
checkInvocations(as3[0], { init: 1, before: 1 },
|
||||
'hook3[0]: on first immediate');
|
||||
|
||||
// Setup the second Immediate, note that now hook2 is enabled and thus
|
||||
// will capture all lifetime events of this Immediate
|
||||
setImmediate(common.mustCall(onsecondImmediate));
|
||||
}
|
||||
|
||||
//
|
||||
// Once we exit onfirstImmediate the "after" callbacks of the active hooks are
|
||||
// invoked
|
||||
//
|
||||
|
||||
let hook1First, hook2First, hook3First;
|
||||
let hook1Second, hook2Second, hook3Second;
|
||||
|
||||
//
|
||||
// onsecondImmediate is called after all "before" callbacks of the active hooks
|
||||
// are invoked again
|
||||
//
|
||||
function onsecondImmediate() {
|
||||
const as1 = hook1.activitiesOfTypes(types);
|
||||
const as2 = hook2.activitiesOfTypes(types);
|
||||
const as3 = hook3.activitiesOfTypes(types);
|
||||
assert.strictEqual(
|
||||
as1.length, 2,
|
||||
'hook1 captured first and second immediate on second callback');
|
||||
assert.strictEqual(
|
||||
as2.length, 2,
|
||||
'hook2 captured first and second immediate on second callback');
|
||||
assert.strictEqual(
|
||||
as3.length, 2,
|
||||
'hook3 captured first and second immediate on second callback');
|
||||
|
||||
// Assign the info collected by each hook for each immediate for easier
|
||||
// reference.
|
||||
// hook2 saw the "init" of the second immediate before the
|
||||
// "after" of the first which is why they are ordered the opposite way
|
||||
hook1First = as1[0];
|
||||
hook1Second = as1[1];
|
||||
hook2First = as2[1];
|
||||
hook2Second = as2[0];
|
||||
hook3First = as3[0];
|
||||
hook3Second = as3[1];
|
||||
|
||||
// Check that all hooks captured the same Immediate and that it is valid
|
||||
const secondImmediate = hook1Second;
|
||||
assert.strictEqual(hook2Second.uid, hook3Second.uid,
|
||||
'hook2 and hook3 captured same second immediate');
|
||||
assert.strictEqual(hook1Second.uid, hook3Second.uid,
|
||||
'hook1 and hook3 captured same second immediate');
|
||||
assert.strictEqual(secondImmediate.type, 'Immediate', 'immediate');
|
||||
assert.strictEqual(typeof secondImmediate.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof secondImmediate.triggerId, 'number',
|
||||
'triggerId is a number');
|
||||
|
||||
checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'hook1First: on second immediate');
|
||||
checkInvocations(hook1Second, { init: 1, before: 1 },
|
||||
'hook1Second: on second immediate');
|
||||
// hook2 missed the "init" and "before" since it was enabled after they
|
||||
// occurred
|
||||
checkInvocations(hook2First, { after: 1, destroy: 1 },
|
||||
'hook2First: on second immediate');
|
||||
checkInvocations(hook2Second, { init: 1, before: 1 },
|
||||
'hook2Second: on second immediate');
|
||||
checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'hook3First: on second immediate');
|
||||
checkInvocations(hook3Second, { init: 1, before: 1 },
|
||||
'hook3Second: on second immediate');
|
||||
tick(1);
|
||||
}
|
||||
|
||||
//
|
||||
// Once we exit onsecondImmediate the "after" callbacks of the active hooks are
|
||||
// invoked again.
|
||||
// During this second "after" invocation hook3 disables itself
|
||||
// (see onhook3After).
|
||||
//
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hook1.disable();
|
||||
hook2.disable();
|
||||
hook3.disable();
|
||||
hook1.sanityCheck();
|
||||
hook2.sanityCheck();
|
||||
hook3.sanityCheck();
|
||||
|
||||
checkInvocations(hook1First, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'hook1First: when process exits');
|
||||
// hook1 was disabled during hook2's "before" of the second immediate
|
||||
// and thus did not see "after" and "destroy"
|
||||
checkInvocations(hook1Second, { init: 1, before: 1 },
|
||||
'hook1Second: when process exits');
|
||||
// hook2 missed the "init" and "before" since it was enabled after they
|
||||
// occurred
|
||||
checkInvocations(hook2First, { after: 1, destroy: 1 },
|
||||
'hook2First: when process exits');
|
||||
checkInvocations(hook2Second, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'hook2Second: when process exits');
|
||||
checkInvocations(hook3First, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'hook3First: when process exits');
|
||||
// we don't see a "destroy" invocation here since hook3 disabled itself
|
||||
// during its "after" invocation
|
||||
checkInvocations(hook3Second, { init: 1, before: 1, after: 1 },
|
||||
'hook3Second: when process exits');
|
||||
}
|
33
test/async-hooks/test-fseventwrap.js
Normal file
33
test/async-hooks/test-fseventwrap.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const tick = require('./tick');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
const watcher = fs.watch(__dirname, onwatcherChanged);
|
||||
function onwatcherChanged() { }
|
||||
|
||||
watcher.close();
|
||||
tick(2);
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('FSEVENTWRAP');
|
||||
|
||||
const as = hooks.activitiesOfTypes('FSEVENTWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'FSEVENTWRAP', 'fs event wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(a, { init: 1, destroy: 1 }, 'when process exits');
|
||||
}
|
37
test/async-hooks/test-fsreqwrap-access.js
Normal file
37
test/async-hooks/test-fsreqwrap-access.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
fs.access(__filename, common.mustCall(onaccess));
|
||||
|
||||
function onaccess() {
|
||||
const as = hooks.activitiesOfTypes('FSREQWRAP');
|
||||
const a = as[0];
|
||||
checkInvocations(a, { init: 1, before: 1 },
|
||||
'while in onaccess callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('FSREQWRAP');
|
||||
|
||||
const as = hooks.activitiesOfTypes('FSREQWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'FSREQWRAP', 'fs req wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
48
test/async-hooks/test-fsreqwrap-readFile.js
Normal file
48
test/async-hooks/test-fsreqwrap-readFile.js
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
fs.readFile(__filename, common.mustCall(onread));
|
||||
|
||||
function onread() {
|
||||
const as = hooks.activitiesOfTypes('FSREQWRAP');
|
||||
let lastParent = 1;
|
||||
for (let i = 0; i < as.length; i++) {
|
||||
const a = as[i];
|
||||
assert.strictEqual(a.type, 'FSREQWRAP', 'fs req wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, lastParent, 'parent uid 1');
|
||||
lastParent = a.uid;
|
||||
}
|
||||
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'reqwrap[0]: while in onread callback');
|
||||
checkInvocations(as[1], { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'reqwrap[1]: while in onread callback');
|
||||
checkInvocations(as[2], { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'reqwrap[2]: while in onread callback');
|
||||
|
||||
// this callback is called from within the last fs req callback therefore
|
||||
// the last req is still going and after/destroy haven't been called yet
|
||||
checkInvocations(as[3], { init: 1, before: 1 },
|
||||
'reqwrap[3]: while in onread callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('FSREQWRAP');
|
||||
const as = hooks.activitiesOfTypes('FSREQWRAP');
|
||||
const a = as.pop();
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
39
test/async-hooks/test-getaddrinforeqwrap.js
Normal file
39
test/async-hooks/test-getaddrinforeqwrap.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const dns = require('dns');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
dns.lookup('www.google.com', 4, common.mustCall(onlookup));
|
||||
function onlookup(err_, ip, family) {
|
||||
// we don't care about the error here in order to allow
|
||||
// tests to run offline (lookup will fail in that case and the err be set);
|
||||
|
||||
const as = hooks.activitiesOfTypes('GETADDRINFOREQWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'GETADDRINFOREQWRAP', 'getaddrinforeq wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(a, { init: 1, before: 1 }, 'while in onlookup callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('GETADDRINFOREQWRAP');
|
||||
|
||||
const as = hooks.activitiesOfTypes('GETADDRINFOREQWRAP');
|
||||
const a = as[0];
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
40
test/async-hooks/test-getnameinforeqwrap.js
Normal file
40
test/async-hooks/test-getnameinforeqwrap.js
Normal file
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const dns = require('dns');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
dns.lookupService('127.0.0.1', 80, common.mustCall(onlookupService));
|
||||
function onlookupService(err_, ip, family) {
|
||||
// we don't care about the error here in order to allow
|
||||
// tests to run offline (lookup will fail in that case and the err be set)
|
||||
|
||||
const as = hooks.activitiesOfTypes('GETNAMEINFOREQWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one activity');
|
||||
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'GETNAMEINFOREQWRAP', 'getnameinforeq wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(a.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(a, { init: 1, before: 1 },
|
||||
'while in onlookupService callback');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('GETNAMEINFOREQWRAP');
|
||||
|
||||
const as = hooks.activitiesOfTypes('GETNAMEINFOREQWRAP');
|
||||
const a = as[0];
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
56
test/async-hooks/test-graph.connection.js
Normal file
56
test/async-hooks/test-graph.connection.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const initHooks = require('./init-hooks');
|
||||
const common = require('../common');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const tls = require('tls');
|
||||
const Connection = process.binding('crypto').Connection;
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
function createServerConnection(
|
||||
onhandshakestart,
|
||||
certificate = null,
|
||||
isServer = true,
|
||||
servername = 'some server',
|
||||
rejectUnauthorized
|
||||
) {
|
||||
if (certificate == null) certificate = tls.createSecureContext();
|
||||
const ssl = new Connection(
|
||||
certificate.context, isServer, servername, rejectUnauthorized
|
||||
);
|
||||
if (isServer) {
|
||||
ssl.onhandshakestart = onhandshakestart;
|
||||
ssl.lastHandshakeTime = 0;
|
||||
}
|
||||
return ssl;
|
||||
}
|
||||
|
||||
// creating first server connection and start it
|
||||
const sc1 = createServerConnection(common.mustCall(onfirstHandShake));
|
||||
sc1.start();
|
||||
|
||||
function onfirstHandShake() {
|
||||
// Create second connection inside handshake of first to show
|
||||
// that the triggerId of the second will be set to id of the first
|
||||
const sc2 = createServerConnection(common.mustCall(onsecondHandShake));
|
||||
sc2.start();
|
||||
}
|
||||
function onsecondHandShake() { }
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'CONNECTION', id: 'connection:1', triggerId: null },
|
||||
{ type: 'CONNECTION', id: 'connection:2', triggerId: 'connection:1' } ]
|
||||
);
|
||||
}
|
26
test/async-hooks/test-graph.fsreq-readFile.js
Normal file
26
test/async-hooks/test-graph.fsreq-readFile.js
Normal file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
fs.readFile(__filename, common.mustCall(onread));
|
||||
|
||||
function onread() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'FSREQWRAP', id: 'fsreq:1', triggerId: null },
|
||||
{ type: 'FSREQWRAP', id: 'fsreq:2', triggerId: 'fsreq:1' },
|
||||
{ type: 'FSREQWRAP', id: 'fsreq:3', triggerId: 'fsreq:2' },
|
||||
{ type: 'FSREQWRAP', id: 'fsreq:4', triggerId: 'fsreq:3' } ]
|
||||
);
|
||||
}
|
37
test/async-hooks/test-graph.intervals.js
Normal file
37
test/async-hooks/test-graph.intervals.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const TIMEOUT = 1;
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
let count = 0;
|
||||
const iv1 = setInterval(common.mustCall(onfirstInterval, 3), TIMEOUT);
|
||||
let iv2;
|
||||
|
||||
function onfirstInterval() {
|
||||
if (++count === 3) {
|
||||
clearInterval(iv1);
|
||||
iv2 = setInterval(common.mustCall(onsecondInterval, 1), TIMEOUT + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function onsecondInterval() {
|
||||
clearInterval(iv2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'Timeout', id: 'timeout:1', triggerId: null },
|
||||
{ type: 'TIMERWRAP', id: 'timer:1', triggerId: null },
|
||||
{ type: 'Timeout', id: 'timeout:2', triggerId: 'timeout:1' },
|
||||
{ type: 'TIMERWRAP', id: 'timer:2', triggerId: 'timeout:1' } ]
|
||||
);
|
||||
}
|
32
test/async-hooks/test-graph.pipe.js
Normal file
32
test/async-hooks/test-graph.pipe.js
Normal file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
const sleep = spawn('sleep', [ '0.1' ]);
|
||||
|
||||
sleep
|
||||
.on('exit', common.mustCall(onsleepExit))
|
||||
.on('close', common.mustCall(onsleepClose));
|
||||
|
||||
function onsleepExit(code) {}
|
||||
|
||||
function onsleepClose() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'PROCESSWRAP', id: 'process:1', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:1', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:2', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:3', triggerId: null } ]
|
||||
);
|
||||
}
|
37
test/async-hooks/test-graph.pipeconnect.js
Normal file
37
test/async-hooks/test-graph.pipeconnect.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
|
||||
const net = require('net');
|
||||
|
||||
common.refreshTmpDir();
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
net.createServer(function(c) {
|
||||
c.end();
|
||||
this.close();
|
||||
}).listen(common.PIPE, common.mustCall(onlisten));
|
||||
|
||||
function onlisten() {
|
||||
net.connect(common.PIPE, common.mustCall(onconnect));
|
||||
}
|
||||
|
||||
function onconnect() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'PIPEWRAP', id: 'pipe:1', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:2', triggerId: 'pipe:1' },
|
||||
{ type: 'PIPECONNECTWRAP', id: 'pipeconnect:1', triggerId: 'pipe:2' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:3', triggerId: 'pipe:1' },
|
||||
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerId: 'pipe:3' } ]
|
||||
);
|
||||
}
|
49
test/async-hooks/test-graph.shutdown.js
Normal file
49
test/async-hooks/test-graph.shutdown.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
|
||||
if (!common.hasIPv6) {
|
||||
common.skip('IPv6 support required');
|
||||
return;
|
||||
}
|
||||
|
||||
const net = require('net');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const server = net
|
||||
.createServer(onconnection)
|
||||
.on('listening', common.mustCall(onlistening));
|
||||
server.listen();
|
||||
function onlistening() {
|
||||
net.connect(server.address().port, common.mustCall(onconnected));
|
||||
}
|
||||
|
||||
function onconnection(c) {
|
||||
c.end();
|
||||
this.close(onserverClosed);
|
||||
}
|
||||
|
||||
function onconnected() {}
|
||||
|
||||
function onserverClosed() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'TCPWRAP', id: 'tcp:1', triggerId: null },
|
||||
{ type: 'TCPWRAP', id: 'tcp:2', triggerId: 'tcp:1' },
|
||||
{ type: 'GETADDRINFOREQWRAP',
|
||||
id: 'getaddrinforeq:1', triggerId: 'tcp:2' },
|
||||
{ type: 'TCPCONNECTWRAP',
|
||||
id: 'tcpconnect:1', triggerId: 'tcp:2' },
|
||||
{ type: 'TCPWRAP', id: 'tcp:3', triggerId: 'tcp:1' },
|
||||
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerId: 'tcp:3' } ]
|
||||
);
|
||||
}
|
54
test/async-hooks/test-graph.signal.js
Normal file
54
test/async-hooks/test-graph.signal.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
process.on('SIGUSR2', common.mustCall(onsigusr2, 2));
|
||||
|
||||
let count = 0;
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
|
||||
function onsigusr2() {
|
||||
count++;
|
||||
|
||||
if (count === 1) {
|
||||
// trigger same signal handler again
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
} else {
|
||||
// install another signal handler
|
||||
process.removeAllListeners('SIGUSR2');
|
||||
process.on('SIGUSR2', common.mustCall(onsigusr2Again));
|
||||
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
}
|
||||
}
|
||||
|
||||
function onsigusr2Again() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'SIGNALWRAP', id: 'signal:1', triggerId: null },
|
||||
{ type: 'PROCESSWRAP', id: 'process:1', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:1', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:2', triggerId: null },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:3', triggerId: null },
|
||||
{ type: 'PROCESSWRAP', id: 'process:2', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:4', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:5', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:6', triggerId: 'signal:1' },
|
||||
{ type: 'SIGNALWRAP', id: 'signal:2', triggerId: 'signal:1' },
|
||||
{ type: 'PROCESSWRAP', id: 'process:3', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:7', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:8', triggerId: 'signal:1' },
|
||||
{ type: 'PIPEWRAP', id: 'pipe:9', triggerId: 'signal:1' } ]
|
||||
);
|
||||
}
|
34
test/async-hooks/test-graph.statwatcher.js
Normal file
34
test/async-hooks/test-graph.statwatcher.js
Normal file
@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const commonPath = require.resolve('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
function onchange() { }
|
||||
// install first file watcher
|
||||
fs.watchFile(__filename, onchange);
|
||||
|
||||
// install second file watcher
|
||||
fs.watchFile(commonPath, onchange);
|
||||
|
||||
// remove first file watcher
|
||||
fs.unwatchFile(__filename);
|
||||
|
||||
// remove second file watcher
|
||||
fs.unwatchFile(commonPath);
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'STATWATCHER', id: 'statwatcher:1', triggerId: null },
|
||||
{ type: 'STATWATCHER', id: 'statwatcher:2', triggerId: null } ]
|
||||
);
|
||||
}
|
51
test/async-hooks/test-graph.tcp.js
Normal file
51
test/async-hooks/test-graph.tcp.js
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
|
||||
if (!common.hasIPv6) {
|
||||
common.skip('IPv6 support required');
|
||||
return;
|
||||
}
|
||||
|
||||
const net = require('net');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const server = net
|
||||
.createServer(common.mustCall(onconnection))
|
||||
.on('listening', common.mustCall(onlistening));
|
||||
|
||||
server.listen(common.PORT);
|
||||
|
||||
net.connect({ port: server.address().port, host: server.address().address },
|
||||
common.mustCall(onconnected));
|
||||
|
||||
function onlistening() {}
|
||||
|
||||
function onconnected() {}
|
||||
|
||||
function onconnection(c) {
|
||||
c.end();
|
||||
this.close(common.mustCall(onserverClosed));
|
||||
}
|
||||
|
||||
function onserverClosed() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'TCPWRAP', id: 'tcp:1', triggerId: null },
|
||||
{ type: 'TCPWRAP', id: 'tcp:2', triggerId: null },
|
||||
{ type: 'TCPCONNECTWRAP',
|
||||
id: 'tcpconnect:1', triggerId: 'tcp:2' },
|
||||
{ type: 'TCPWRAP', id: 'tcp:3', triggerId: 'tcp:1' },
|
||||
{ type: 'SHUTDOWNWRAP', id: 'shutdown:1', triggerId: 'tcp:3' } ]
|
||||
);
|
||||
}
|
35
test/async-hooks/test-graph.timeouts.js
Normal file
35
test/async-hooks/test-graph.timeouts.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const TIMEOUT = 1;
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
setTimeout(common.mustCall(ontimeout), TIMEOUT);
|
||||
function ontimeout() {
|
||||
setTimeout(onsecondTimeout, TIMEOUT + 1);
|
||||
}
|
||||
|
||||
function onsecondTimeout() {
|
||||
setTimeout(onthirdTimeout, TIMEOUT + 2);
|
||||
}
|
||||
|
||||
function onthirdTimeout() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'Timeout', id: 'timeout:1', triggerId: null },
|
||||
{ type: 'TIMERWRAP', id: 'timer:1', triggerId: null },
|
||||
{ type: 'Timeout', id: 'timeout:2', triggerId: 'timeout:1' },
|
||||
{ type: 'TIMERWRAP', id: 'timer:2', triggerId: 'timeout:1' },
|
||||
{ type: 'Timeout', id: 'timeout:3', triggerId: 'timeout:2' },
|
||||
{ type: 'TIMERWRAP', id: 'timer:3', triggerId: 'timeout:2' } ]
|
||||
);
|
||||
}
|
79
test/async-hooks/test-graph.tls-write.js
Normal file
79
test/async-hooks/test-graph.tls-write.js
Normal file
@ -0,0 +1,79 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const initHooks = require('./init-hooks');
|
||||
const verifyGraph = require('./verify-graph');
|
||||
const fs = require('fs');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!common.hasIPv6) {
|
||||
common.skip('IPv6 support required');
|
||||
return;
|
||||
}
|
||||
|
||||
const tls = require('tls');
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
//
|
||||
// Creating server and listening on port
|
||||
//
|
||||
const server = tls
|
||||
.createServer({
|
||||
cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem'),
|
||||
key: fs.readFileSync(common.fixturesDir + '/test_key.pem')
|
||||
})
|
||||
.on('listening', common.mustCall(onlistening))
|
||||
.on('secureConnection', common.mustCall(onsecureConnection))
|
||||
.listen(common.PORT);
|
||||
|
||||
function onlistening() {
|
||||
//
|
||||
// Creating client and connecting it to server
|
||||
//
|
||||
tls
|
||||
.connect(common.PORT, { rejectUnauthorized: false })
|
||||
.on('secureConnect', common.mustCall(onsecureConnect));
|
||||
}
|
||||
|
||||
function onsecureConnection() {}
|
||||
|
||||
function onsecureConnect() {
|
||||
// Destroying client socket
|
||||
this.destroy();
|
||||
|
||||
// Closing server
|
||||
server.close(common.mustCall(onserverClosed));
|
||||
}
|
||||
|
||||
function onserverClosed() {}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
|
||||
verifyGraph(
|
||||
hooks,
|
||||
[ { type: 'TCPWRAP', id: 'tcp:1', triggerId: null },
|
||||
{ type: 'TCPWRAP', id: 'tcp:2', triggerId: 'tcp:1' },
|
||||
{ type: 'TLSWRAP', id: 'tls:1', triggerId: 'tcp:1' },
|
||||
{ type: 'GETADDRINFOREQWRAP',
|
||||
id: 'getaddrinforeq:1', triggerId: 'tls:1' },
|
||||
{ type: 'TCPCONNECTWRAP',
|
||||
id: 'tcpconnect:1', triggerId: 'tcp:2' },
|
||||
{ type: 'WRITEWRAP', id: 'write:1', triggerId: 'tcpconnect:1' },
|
||||
{ type: 'TCPWRAP', id: 'tcp:3', triggerId: 'tcp:1' },
|
||||
{ type: 'TLSWRAP', id: 'tls:2', triggerId: 'tcp:1' },
|
||||
{ type: 'TIMERWRAP', id: 'timer:1', triggerId: 'tcp:1' },
|
||||
{ type: 'WRITEWRAP', id: 'write:2', triggerId: null },
|
||||
{ type: 'WRITEWRAP', id: 'write:3', triggerId: null },
|
||||
{ type: 'WRITEWRAP', id: 'write:4', triggerId: null },
|
||||
{ type: 'Immediate', id: 'immediate:1', triggerId: 'tcp:2' },
|
||||
{ type: 'Immediate', id: 'immediate:2', triggerId: 'tcp:3' } ]
|
||||
);
|
||||
}
|
58
test/async-hooks/test-httpparser.request.js
Normal file
58
test/async-hooks/test-httpparser.request.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const binding = process.binding('http_parser');
|
||||
const HTTPParser = binding.HTTPParser;
|
||||
|
||||
const CRLF = '\r\n';
|
||||
const REQUEST = HTTPParser.REQUEST;
|
||||
|
||||
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
|
||||
const request = Buffer.from(
|
||||
'GET /hello HTTP/1.1' + CRLF + CRLF
|
||||
);
|
||||
|
||||
const parser = new HTTPParser(REQUEST);
|
||||
const as = hooks.activitiesOfTypes('HTTPPARSER');
|
||||
const httpparser = as[0];
|
||||
|
||||
assert.strictEqual(
|
||||
as.length, 1,
|
||||
'1 httpparser created synchronously when creating new httpparser');
|
||||
assert.strictEqual(typeof httpparser.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof httpparser.triggerId,
|
||||
'number', 'triggerId is a number');
|
||||
checkInvocations(httpparser, { init: 1 }, 'when created new Httphttpparser');
|
||||
|
||||
parser[kOnHeadersComplete] = common.mustCall(onheadersComplete);
|
||||
parser.execute(request, 0, request.length);
|
||||
|
||||
function onheadersComplete() {
|
||||
checkInvocations(httpparser, { init: 1, before: 1 },
|
||||
'when onheadersComplete called');
|
||||
tick(1, common.mustCall(tick1));
|
||||
}
|
||||
|
||||
function tick1() {
|
||||
parser.close();
|
||||
tick(1);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('HTTPPARSER');
|
||||
checkInvocations(httpparser, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
68
test/async-hooks/test-httpparser.response.js
Normal file
68
test/async-hooks/test-httpparser.response.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const binding = process.binding('http_parser');
|
||||
const HTTPParser = binding.HTTPParser;
|
||||
|
||||
const CRLF = '\r\n';
|
||||
const RESPONSE = HTTPParser.RESPONSE;
|
||||
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
|
||||
const kOnBody = HTTPParser.kOnBody | 0;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
|
||||
const request = Buffer.from(
|
||||
'HTTP/1.1 200 OK' + CRLF +
|
||||
'Content-types: text/plain' + CRLF +
|
||||
'Content-Length: 4' + CRLF +
|
||||
CRLF +
|
||||
'pong'
|
||||
);
|
||||
|
||||
const parser = new HTTPParser(RESPONSE);
|
||||
const as = hooks.activitiesOfTypes('HTTPPARSER');
|
||||
const httpparser = as[0];
|
||||
|
||||
assert.strictEqual(
|
||||
as.length, 1,
|
||||
'1 httpparser created synchronously when creating new httpparser');
|
||||
assert.strictEqual(typeof httpparser.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof httpparser.triggerId,
|
||||
'number', 'triggerId is a number');
|
||||
checkInvocations(httpparser, { init: 1 }, 'when created new Httphttpparser');
|
||||
|
||||
parser[kOnHeadersComplete] = common.mustCall(onheadersComplete);
|
||||
parser[kOnBody] = common.mustCall(onbody);
|
||||
parser.execute(request, 0, request.length);
|
||||
|
||||
function onheadersComplete() {
|
||||
checkInvocations(httpparser, { init: 1, before: 1 },
|
||||
'when onheadersComplete called');
|
||||
}
|
||||
|
||||
function onbody(buf, start, len) {
|
||||
checkInvocations(httpparser, { init: 1, before: 2, after: 1 },
|
||||
'when onbody called');
|
||||
tick(1, common.mustCall(tick1));
|
||||
}
|
||||
|
||||
function tick1() {
|
||||
parser.close();
|
||||
tick(1);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('HTTPPARSER');
|
||||
checkInvocations(httpparser, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
66
test/async-hooks/test-immediate.js
Normal file
66
test/async-hooks/test-immediate.js
Normal file
@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// install first immediate
|
||||
setImmediate(common.mustCall(onimmediate));
|
||||
|
||||
const as = hooks.activitiesOfTypes('Immediate');
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one immediate when first set immediate installed');
|
||||
const imd1 = as[0];
|
||||
assert.strictEqual(imd1.type, 'Immediate', 'immediate');
|
||||
assert.strictEqual(typeof imd1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof imd1.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(imd1, { init: 1 },
|
||||
'imd1: when first set immediate installed');
|
||||
|
||||
let imd2;
|
||||
|
||||
function onimmediate() {
|
||||
let as = hooks.activitiesOfTypes('Immediate');
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one immediate when first set immediate triggered');
|
||||
checkInvocations(imd1, { init: 1, before: 1 },
|
||||
'imd1: when first set immediate triggered');
|
||||
|
||||
// install second immediate
|
||||
setImmediate(common.mustCall(onimmediateTwo));
|
||||
as = hooks.activitiesOfTypes('Immediate');
|
||||
assert.strictEqual(as.length, 2,
|
||||
'two immediates when second set immediate installed');
|
||||
imd2 = as[1];
|
||||
assert.strictEqual(imd2.type, 'Immediate', 'immediate');
|
||||
assert.strictEqual(typeof imd2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof imd2.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(imd1, { init: 1, before: 1 },
|
||||
'imd1: when second set immediate installed');
|
||||
checkInvocations(imd2, { init: 1 },
|
||||
'imd2: when second set immediate installed');
|
||||
}
|
||||
|
||||
function onimmediateTwo() {
|
||||
checkInvocations(imd1, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'imd1: when second set immediate triggered');
|
||||
checkInvocations(imd2, { init: 1, before: 1 },
|
||||
'imd2: when second set immediate triggered');
|
||||
tick(1);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('Immediate');
|
||||
checkInvocations(imd1, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'imd1: when process exits');
|
||||
checkInvocations(imd2, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'imd2: when process exits');
|
||||
}
|
104
test/async-hooks/test-pipeconnectwrap.js
Normal file
104
test/async-hooks/test-pipeconnectwrap.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const net = require('net');
|
||||
|
||||
common.refreshTmpDir();
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
let pipe1, pipe2, pipe3;
|
||||
let pipeconnect;
|
||||
|
||||
net.createServer(common.mustCall(function(c) {
|
||||
c.end();
|
||||
this.close();
|
||||
process.nextTick(maybeOnconnect.bind(null, 'server'));
|
||||
})).listen(common.PIPE, common.mustCall(onlisten));
|
||||
|
||||
function onlisten() {
|
||||
let pipes = hooks.activitiesOfTypes('PIPEWRAP');
|
||||
let pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
|
||||
assert.strictEqual(
|
||||
pipes.length, 1,
|
||||
'one pipe wrap created when net server is listening');
|
||||
assert.strictEqual(
|
||||
pipeconnects.length, 0,
|
||||
'no pipeconnect wrap created when net server is listening');
|
||||
|
||||
net.connect(common.PIPE,
|
||||
common.mustCall(maybeOnconnect.bind(null, 'client')));
|
||||
|
||||
pipes = hooks.activitiesOfTypes('PIPEWRAP');
|
||||
pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
|
||||
assert.strictEqual(pipes.length, 2,
|
||||
'2 pipe wraps created when connecting client');
|
||||
assert.strictEqual(pipeconnects.length, 1,
|
||||
'1 connectwrap created when connecting client');
|
||||
|
||||
pipe1 = pipes[0];
|
||||
pipe2 = pipes[1];
|
||||
pipeconnect = pipeconnects[0];
|
||||
|
||||
assert.strictEqual(pipe1.type, 'PIPEWRAP', 'first is pipe wrap');
|
||||
assert.strictEqual(pipe2.type, 'PIPEWRAP', 'second is pipe wrap');
|
||||
assert.strictEqual(pipeconnect.type, 'PIPECONNECTWRAP',
|
||||
'third is pipeconnect wrap');
|
||||
[ pipe1, pipe2, pipeconnect ].forEach(check);
|
||||
|
||||
function check(a) {
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof a.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(a, { init: 1 }, 'after net.connect');
|
||||
}
|
||||
}
|
||||
|
||||
const awaitOnconnectCalls = new Set(['server', 'client']);
|
||||
function maybeOnconnect(source) {
|
||||
// both server and client must call onconnect. On most OS's waiting for
|
||||
// the client is sufficient, but on CertOS 5 the sever needs to respond too.
|
||||
assert.ok(awaitOnconnectCalls.size > 0);
|
||||
awaitOnconnectCalls.delete(source);
|
||||
if (awaitOnconnectCalls.size > 0) return;
|
||||
|
||||
const pipes = hooks.activitiesOfTypes('PIPEWRAP');
|
||||
const pipeconnects = hooks.activitiesOfTypes('PIPECONNECTWRAP');
|
||||
|
||||
assert.strictEqual(pipes.length, 3,
|
||||
'3 pipe wraps created when client connected');
|
||||
assert.strictEqual(pipeconnects.length, 1,
|
||||
'1 connectwrap created when client connected');
|
||||
pipe3 = pipes[2];
|
||||
assert.strictEqual(typeof pipe3.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof pipe3.triggerId, 'number', 'triggerId is a number');
|
||||
|
||||
checkInvocations(pipe1, { init: 1, before: 1, after: 1 },
|
||||
'pipe1, client connected');
|
||||
checkInvocations(pipe2, { init: 1 }, 'pipe2, client connected');
|
||||
checkInvocations(pipeconnect, { init: 1, before: 1 },
|
||||
'pipeconnect, client connected');
|
||||
checkInvocations(pipe3, { init: 1 }, 'pipe3, client connected');
|
||||
tick(5);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('PIPEWRAP');
|
||||
hooks.sanityCheck('PIPECONNECTWRAP');
|
||||
// TODO(thlorenz) why have some of those 'before' and 'after' called twice
|
||||
checkInvocations(pipe1, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'pipe1, process exiting');
|
||||
checkInvocations(pipe2, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'pipe2, process exiting');
|
||||
checkInvocations(pipeconnect, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'pipeconnect, process exiting');
|
||||
checkInvocations(pipe3, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'pipe3, process exiting');
|
||||
}
|
89
test/async-hooks/test-pipewrap.js
Normal file
89
test/async-hooks/test-pipewrap.js
Normal file
@ -0,0 +1,89 @@
|
||||
// NOTE: this also covers process wrap as one is created along with the pipes
|
||||
// when we launch the sleep process
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
const nodeVersionSpawn = spawn(process.execPath, [ '--version' ]);
|
||||
|
||||
nodeVersionSpawn
|
||||
.on('exit', common.mustCall(onsleepExit))
|
||||
.on('close', common.mustCall(onsleepClose));
|
||||
|
||||
// a process wrap and 3 pipe wraps for std{in,out,err} are initialized
|
||||
// synchronously
|
||||
const processes = hooks.activitiesOfTypes('PROCESSWRAP');
|
||||
const pipes = hooks.activitiesOfTypes('PIPEWRAP');
|
||||
assert.strictEqual(processes.length, 1,
|
||||
'1 processwrap created when process created');
|
||||
assert.strictEqual(pipes.length, 3,
|
||||
'3 pipe wraps created when process created');
|
||||
|
||||
const processwrap = processes[0];
|
||||
const pipe1 = pipes[0];
|
||||
const pipe2 = pipes[1];
|
||||
const pipe3 = pipes[2];
|
||||
|
||||
assert.strictEqual(processwrap.type, 'PROCESSWRAP', 'process wrap type');
|
||||
assert.strictEqual(processwrap.triggerId, 1, 'processwrap triggerId is 1');
|
||||
checkInvocations(processwrap, { init: 1 },
|
||||
'processwrap when sleep.spawn was called');
|
||||
|
||||
[ pipe1, pipe2, pipe3 ].forEach((x) => {
|
||||
assert(x.type, 'PIPEWRAP', 'pipe wrap type');
|
||||
assert.strictEqual(x.triggerId, 1, 'pipe wrap triggerId is 1');
|
||||
checkInvocations(x, { init: 1 }, 'pipe wrap when sleep.spawn was called');
|
||||
});
|
||||
|
||||
function onsleepExit(code) {
|
||||
checkInvocations(processwrap, { init: 1, before: 1 },
|
||||
'processwrap while in onsleepExit callback');
|
||||
}
|
||||
|
||||
function onsleepClose() {
|
||||
tick(1, () =>
|
||||
checkInvocations(
|
||||
processwrap,
|
||||
{ init: 1, before: 1, after: 1 },
|
||||
'processwrap while in onsleepClose callback')
|
||||
);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('PROCESSWRAP');
|
||||
hooks.sanityCheck('PIPEWRAP');
|
||||
|
||||
checkInvocations(
|
||||
processwrap,
|
||||
{ init: 1, before: 1, after: 1 },
|
||||
'processwrap while in onsleepClose callback');
|
||||
|
||||
[ pipe1, pipe2, pipe3 ].forEach((x) => {
|
||||
assert(x.type, 'PIPEWRAP', 'pipe wrap type');
|
||||
assert.strictEqual(x.triggerId, 1, 'pipe wrap triggerId is 1');
|
||||
});
|
||||
|
||||
const ioEvents = Math.min(pipe2.before.length, pipe2.after.length);
|
||||
// 2 events without any IO and at least one more for the node version data.
|
||||
// Usually it is just one event, but it can be more.
|
||||
assert.ok(ioEvents >= 3, 'at least 3 stdout io events.');
|
||||
|
||||
checkInvocations(pipe1, { init: 1, before: 2, after: 2 },
|
||||
'pipe wrap when sleep.spawn was called');
|
||||
checkInvocations(pipe2, { init: 1, before: ioEvents, after: ioEvents },
|
||||
'pipe wrap when sleep.spawn was called');
|
||||
checkInvocations(pipe3, { init: 1, before: 2, after: 2 },
|
||||
'pipe wrap when sleep.spawn was called');
|
||||
}
|
40
test/async-hooks/test-querywrap.js
Normal file
40
test/async-hooks/test-querywrap.js
Normal file
@ -0,0 +1,40 @@
|
||||
'use strict';
|
||||
// Flags: --expose-gc
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const dns = require('dns');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
// uses cares for queryA which in turn uses QUERYWRAP
|
||||
dns.resolve('localhost', common.mustCall(onresolved));
|
||||
|
||||
function onresolved() {
|
||||
const as = hooks.activitiesOfTypes('QUERYWRAP');
|
||||
const a = as[0];
|
||||
assert.strictEqual(as.length, 1, 'one activity in onresolved callback');
|
||||
checkInvocations(a, { init: 1, before: 1 }, 'while in onresolved callback');
|
||||
tick(1E4);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('QUERYWRAP');
|
||||
|
||||
const as = hooks.activitiesOfTypes('QUERYWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one activity on process exit');
|
||||
const a = as[0];
|
||||
|
||||
assert.strictEqual(a.type, 'QUERYWRAP', 'query wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof a.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(a, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
69
test/async-hooks/test-shutdownwrap.js
Normal file
69
test/async-hooks/test-shutdownwrap.js
Normal file
@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const net = require('net');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const server = net
|
||||
.createServer(onconnection)
|
||||
.on('listening', common.mustCall(onlistening));
|
||||
server.listen();
|
||||
function onlistening() {
|
||||
net.connect(server.address().port, common.mustCall(onconnected));
|
||||
}
|
||||
|
||||
// It is non-deterministic in which order onconnection and onconnected fire.
|
||||
// Therefore we track here if we ended the connection already or not.
|
||||
let endedConnection = false;
|
||||
function onconnection(c) {
|
||||
assert.strictEqual(hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 0,
|
||||
'no shutdown wrap before ending the client connection');
|
||||
c.end();
|
||||
endedConnection = true;
|
||||
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
|
||||
assert.strictEqual(
|
||||
as.length, 1,
|
||||
'one shutdown wrap created sync after ending the client connection');
|
||||
checkInvocations(as[0], { init: 1 }, 'after ending client connection');
|
||||
this.close(onserverClosed);
|
||||
}
|
||||
|
||||
function onconnected() {
|
||||
if (endedConnection) {
|
||||
assert.strictEqual(
|
||||
hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 1,
|
||||
'one shutdown wrap when client connected but server ended connection');
|
||||
|
||||
} else {
|
||||
assert.strictEqual(
|
||||
hooks.activitiesOfTypes('SHUTDOWNWRAP').length, 0,
|
||||
'no shutdown wrap when client connected and server did not end connection'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onserverClosed() {
|
||||
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
|
||||
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when server closed');
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('SHUTDOWNWRAP');
|
||||
const as = hooks.activitiesOfTypes('SHUTDOWNWRAP');
|
||||
const a = as[0];
|
||||
assert.strictEqual(a.type, 'SHUTDOWNWRAP', 'shutdown wrap');
|
||||
assert.strictEqual(typeof a.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof a.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(as[0], { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
91
test/async-hooks/test-signalwrap.js
Normal file
91
test/async-hooks/test-signalwrap.js
Normal file
@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const exec = require('child_process').exec;
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
process.on('SIGUSR2', common.mustCall(onsigusr2, 2));
|
||||
|
||||
const as = hooks.activitiesOfTypes('SIGNALWRAP');
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one signal wrap when SIGUSR2 handler is set up');
|
||||
const signal1 = as[0];
|
||||
assert.strictEqual(signal1.type, 'SIGNALWRAP', 'signal wrap');
|
||||
assert.strictEqual(typeof signal1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof signal1.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(signal1, { init: 1 }, 'when SIGUSR2 handler is set up');
|
||||
|
||||
let count = 0;
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
|
||||
let signal2;
|
||||
|
||||
function onsigusr2() {
|
||||
count++;
|
||||
|
||||
if (count === 1) {
|
||||
// first invocation
|
||||
checkInvocations(
|
||||
signal1, { init: 1, before: 1 },
|
||||
' signal1: when first SIGUSR2 handler is called for the first time');
|
||||
|
||||
// trigger same signal handler again
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
} else {
|
||||
// second invocation
|
||||
checkInvocations(
|
||||
signal1, { init: 1, before: 2, after: 1 },
|
||||
'signal1: when first SIGUSR2 handler is called for the second time');
|
||||
|
||||
// install another signal handler
|
||||
process.removeAllListeners('SIGUSR2');
|
||||
process.on('SIGUSR2', common.mustCall(onsigusr2Again));
|
||||
|
||||
const as = hooks.activitiesOfTypes('SIGNALWRAP');
|
||||
assert.strictEqual(
|
||||
as.length, 2,
|
||||
'two signal wraps when second SIGUSR2 handler is set up');
|
||||
signal2 = as[1];
|
||||
assert.strictEqual(signal2.type, 'SIGNALWRAP', 'signal wrap');
|
||||
assert.strictEqual(typeof signal2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof signal2.triggerId, 'number',
|
||||
'triggerId is a number');
|
||||
|
||||
checkInvocations(
|
||||
signal1, { init: 1, before: 2, after: 1 },
|
||||
'signal1: when second SIGUSR2 handler is set up');
|
||||
checkInvocations(
|
||||
signal2, { init: 1 },
|
||||
'signal2: when second SIGUSR2 handler is setup');
|
||||
|
||||
exec('kill -USR2 ' + process.pid);
|
||||
}
|
||||
}
|
||||
|
||||
function onsigusr2Again() {
|
||||
checkInvocations(
|
||||
signal1, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'signal1: when second SIGUSR2 handler is called');
|
||||
checkInvocations(
|
||||
signal2, { init: 1, before: 1 },
|
||||
'signal2: when second SIGUSR2 handler is called');
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('SIGNALWRAP');
|
||||
checkInvocations(
|
||||
signal1, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'signal1: when second SIGUSR2 process exits');
|
||||
// second signal not destroyed yet since its event listener is still active
|
||||
checkInvocations(
|
||||
signal2, { init: 1, before: 1, after: 1 },
|
||||
'signal2: when second SIGUSR2 process exits');
|
||||
}
|
64
test/async-hooks/test-statwatcher.js
Normal file
64
test/async-hooks/test-statwatcher.js
Normal file
@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const commonPath = require.resolve('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const fs = require('fs');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
function onchange() {}
|
||||
// install first file watcher
|
||||
fs.watchFile(__filename, onchange);
|
||||
|
||||
let as = hooks.activitiesOfTypes('STATWATCHER');
|
||||
assert.strictEqual(as.length, 1, 'one stat watcher when watching one file');
|
||||
|
||||
const statwatcher1 = as[0];
|
||||
assert.strictEqual(statwatcher1.type, 'STATWATCHER', 'stat watcher');
|
||||
assert.strictEqual(typeof statwatcher1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(statwatcher1.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(statwatcher1, { init: 1 },
|
||||
'watcher1: when started to watch file');
|
||||
|
||||
// install second file watcher
|
||||
fs.watchFile(commonPath, onchange);
|
||||
as = hooks.activitiesOfTypes('STATWATCHER');
|
||||
assert.strictEqual(as.length, 2, 'two stat watchers when watching two files');
|
||||
|
||||
const statwatcher2 = as[1];
|
||||
assert.strictEqual(statwatcher2.type, 'STATWATCHER', 'stat watcher');
|
||||
assert.strictEqual(typeof statwatcher2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(statwatcher2.triggerId, 1, 'parent uid 1');
|
||||
checkInvocations(statwatcher1, { init: 1 },
|
||||
'watcher1: when started to watch second file');
|
||||
checkInvocations(statwatcher2, { init: 1 },
|
||||
'watcher2: when started to watch second file');
|
||||
|
||||
// remove first file watcher
|
||||
fs.unwatchFile(__filename);
|
||||
checkInvocations(statwatcher1, { init: 1, before: 1, after: 1 },
|
||||
'watcher:1 when unwatched first file');
|
||||
checkInvocations(statwatcher2, { init: 1 },
|
||||
'watcher2: when unwatched first file');
|
||||
|
||||
// remove second file watcher
|
||||
fs.unwatchFile(commonPath);
|
||||
checkInvocations(statwatcher1, { init: 1, before: 1, after: 1 },
|
||||
'watcher:1 when unwatched second file');
|
||||
checkInvocations(statwatcher2, { init: 1, before: 1, after: 1 },
|
||||
'watcher2: when unwatched second file');
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('STATWATCHER');
|
||||
checkInvocations(statwatcher1, { init: 1, before: 1, after: 1 },
|
||||
'watcher:1 when process exits');
|
||||
checkInvocations(statwatcher2, { init: 1, before: 1, after: 1 },
|
||||
'watcher2: when process exits');
|
||||
}
|
177
test/async-hooks/test-tcpwrap.js
Normal file
177
test/async-hooks/test-tcpwrap.js
Normal file
@ -0,0 +1,177 @@
|
||||
// Covers TCPWRAP and related TCPCONNECTWRAP
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
if (!common.hasIPv6) {
|
||||
common.skip('IPv6 support required');
|
||||
return;
|
||||
}
|
||||
|
||||
const net = require('net');
|
||||
|
||||
let tcp1, tcp2, tcp3;
|
||||
let tcpconnect;
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const server = net
|
||||
.createServer(common.mustCall(onconnection))
|
||||
.on('listening', common.mustCall(onlistening));
|
||||
|
||||
// Calling server.listen creates a TCPWRAP synchronously
|
||||
{
|
||||
server.listen(common.PORT);
|
||||
const tcps = hooks.activitiesOfTypes('TCPWRAP');
|
||||
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
|
||||
assert.strictEqual(
|
||||
tcps.length, 1,
|
||||
'one TCPWRAP created synchronously when calling server.listen');
|
||||
assert.strictEqual(
|
||||
tcpconnects.length, 0,
|
||||
'no TCPCONNECTWRAP created synchronously when calling server.listen');
|
||||
tcp1 = tcps[0];
|
||||
assert.strictEqual(tcp1.type, 'TCPWRAP', 'tcp wrap');
|
||||
assert.strictEqual(typeof tcp1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tcp1.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(tcp1, { init: 1 }, 'when calling server.listen');
|
||||
}
|
||||
|
||||
// Calling net.connect creates another TCPWRAP synchronously
|
||||
{
|
||||
net.connect(
|
||||
{ port: server.address().port, host: server.address().address },
|
||||
common.mustCall(onconnected));
|
||||
const tcps = hooks.activitiesOfTypes('TCPWRAP');
|
||||
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
|
||||
assert.strictEqual(
|
||||
tcps.length, 2,
|
||||
'2 TCPWRAPs present when client is connecting');
|
||||
assert.strictEqual(
|
||||
tcpconnects.length, 1,
|
||||
'1 TCPCONNECTWRAP present when client is connecting');
|
||||
tcp2 = tcps[1];
|
||||
assert.strictEqual(tcps.length, 2,
|
||||
'2 TCPWRAP present when client is connecting');
|
||||
assert.strictEqual(tcp2.type, 'TCPWRAP', 'tcp wrap');
|
||||
assert.strictEqual(typeof tcp2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tcp2.triggerId, 'number', 'triggerId is a number');
|
||||
|
||||
checkInvocations(tcp1, { init: 1 }, 'tcp1 when client is connecting');
|
||||
checkInvocations(tcp2, { init: 1 }, 'tcp2 when client is connecting');
|
||||
}
|
||||
|
||||
function onlistening() {
|
||||
assert.strictEqual(hooks.activitiesOfTypes('TCPWRAP').length, 2,
|
||||
'two TCPWRAPs when server is listening');
|
||||
}
|
||||
|
||||
// Depending on timing we see client: onconnected or server: onconnection first
|
||||
// Therefore we can't depend on any ordering, but when we see a connection for
|
||||
// the first time we assign the tcpconnectwrap.
|
||||
function ontcpConnection(serverConnection) {
|
||||
if (tcpconnect != null) {
|
||||
// When client receives connection first ('onconnected') and the server
|
||||
// second then we see an 'after' here, otherwise not
|
||||
const expected = serverConnection ?
|
||||
{ init: 1, before: 1, after: 1 } :
|
||||
{ init: 1, before: 1 };
|
||||
checkInvocations(
|
||||
tcpconnect, expected,
|
||||
'tcpconnect: when both client and server received connection');
|
||||
return;
|
||||
}
|
||||
|
||||
// only focusing on TCPCONNECTWRAP here
|
||||
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
|
||||
assert.strictEqual(
|
||||
tcpconnects.length, 1,
|
||||
'one TCPCONNECTWRAP present on tcp connection');
|
||||
tcpconnect = tcpconnects[0];
|
||||
assert.strictEqual(tcpconnect.type, 'TCPCONNECTWRAP', 'tcpconnect wrap');
|
||||
assert.strictEqual(typeof tcpconnect.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tcpconnect.triggerId,
|
||||
'number', 'triggerId is a number');
|
||||
// When client receives connection first ('onconnected'), we 'before' has
|
||||
// been invoked at this point already, otherwise it only was 'init'ed
|
||||
const expected = serverConnection ? { init: 1 } : { init: 1, before: 1 };
|
||||
checkInvocations(tcpconnect, expected,
|
||||
'tcpconnect: when tcp connection is established');
|
||||
}
|
||||
|
||||
let serverConnected = false;
|
||||
function onconnected() {
|
||||
ontcpConnection(false);
|
||||
// In the case that the client connects before the server TCPWRAP 'before'
|
||||
// and 'after' weren't invoked yet. Also @see ontcpConnection.
|
||||
const expected = serverConnected ?
|
||||
{ init: 1, before: 1, after: 1 } :
|
||||
{ init: 1 };
|
||||
checkInvocations(tcp1, expected, 'tcp1 when client connects');
|
||||
checkInvocations(tcp2, { init: 1 }, 'tcp2 when client connects');
|
||||
}
|
||||
|
||||
function onconnection(c) {
|
||||
serverConnected = true;
|
||||
ontcpConnection(true);
|
||||
|
||||
const tcps = hooks.activitiesOfTypes([ 'TCPWRAP' ]);
|
||||
const tcpconnects = hooks.activitiesOfTypes('TCPCONNECTWRAP');
|
||||
assert.strictEqual(
|
||||
tcps.length, 3,
|
||||
'3 TCPWRAPs present when server receives connection');
|
||||
assert.strictEqual(
|
||||
tcpconnects.length, 1,
|
||||
'one TCPCONNECTWRAP present when server receives connection');
|
||||
tcp3 = tcps[2];
|
||||
assert.strictEqual(tcp3.type, 'TCPWRAP', 'tcp wrap');
|
||||
assert.strictEqual(typeof tcp3.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tcp3.triggerId, 'number', 'triggerId is a number');
|
||||
|
||||
checkInvocations(tcp1, { init: 1, before: 1 },
|
||||
'tcp1 when server receives connection');
|
||||
checkInvocations(tcp2, { init: 1 }, 'tcp2 when server receives connection');
|
||||
checkInvocations(tcp3, { init: 1 }, 'tcp3 when server receives connection');
|
||||
|
||||
c.end();
|
||||
this.close(common.mustCall(onserverClosed));
|
||||
}
|
||||
|
||||
function onserverClosed() {
|
||||
checkInvocations(tcp1, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'tcp1 when server is closed');
|
||||
checkInvocations(tcp2, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'tcp2 when server is closed');
|
||||
checkInvocations(tcp3, { init: 1, before: 1, after: 1 },
|
||||
'tcp3 synchronously when server is closed');
|
||||
tick(2, () => {
|
||||
checkInvocations(tcp3, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'tcp3 when server is closed');
|
||||
checkInvocations(tcpconnect, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'tcpconnect when server is closed');
|
||||
});
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck([ 'TCPWRAP', 'TCPCONNECTWRAP' ]);
|
||||
|
||||
checkInvocations(tcp1, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'tcp1 when process exits');
|
||||
checkInvocations(
|
||||
tcp2, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'tcp2 when process exits');
|
||||
checkInvocations(
|
||||
tcp3, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
'tcp3 when process exits');
|
||||
checkInvocations(
|
||||
tcpconnect, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'tcpconnect when process exits');
|
||||
}
|
56
test/async-hooks/test-timerwrap.setInterval.js
Normal file
56
test/async-hooks/test-timerwrap.setInterval.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const TIMEOUT = 1;
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
let count = 0;
|
||||
const iv = setInterval(common.mustCall(oninterval, 3), TIMEOUT);
|
||||
|
||||
const as = hooks.activitiesOfTypes('TIMERWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one timer wrap when interval installed');
|
||||
const t = as[0];
|
||||
assert.strictEqual(t.type, 'TIMERWRAP', 'timer wrap');
|
||||
assert.strictEqual(typeof t.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof t.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(t, { init: 1 }, 't: when first timer installed');
|
||||
|
||||
function oninterval() {
|
||||
count++;
|
||||
assert.strictEqual(as.length, 1, 'one timer wrap when timer is triggered');
|
||||
switch (count) {
|
||||
case 1: {
|
||||
checkInvocations(t, { init: 1, before: 1 },
|
||||
't: when first timer triggered first time');
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
checkInvocations(t, { init: 1, before: 2, after: 1 },
|
||||
't: when first timer triggered second time');
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
clearInterval(iv);
|
||||
checkInvocations(t, { init: 1, before: 3, after: 2 },
|
||||
't: when first timer triggered third time');
|
||||
tick(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('TIMERWRAP');
|
||||
|
||||
checkInvocations(t, { init: 1, before: 3, after: 3, destroy: 1 },
|
||||
't: when process exits');
|
||||
}
|
78
test/async-hooks/test-timerwrap.setTimeout.js
Normal file
78
test/async-hooks/test-timerwrap.setTimeout.js
Normal file
@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const TIMEOUT = common.platformTimeout(100);
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
// install first timeout
|
||||
setTimeout(common.mustCall(ontimeout), TIMEOUT);
|
||||
const as = hooks.activitiesOfTypes('TIMERWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one timer wrap when first timeout installed');
|
||||
const t1 = as[0];
|
||||
assert.strictEqual(t1.type, 'TIMERWRAP', 'timer wrap');
|
||||
assert.strictEqual(typeof t1.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof t1.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(t1, { init: 1 }, 't1: when first timer installed');
|
||||
|
||||
function ontimeout() {
|
||||
checkInvocations(t1, { init: 1, before: 1 }, 't1: when first timer fired');
|
||||
|
||||
// install second timeout with same TIMEOUT to see timer wrap being reused
|
||||
setTimeout(onsecondTimeout, TIMEOUT);
|
||||
const as = hooks.activitiesOfTypes('TIMERWRAP');
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one timer wrap when second timer installed');
|
||||
checkInvocations(t1, { init: 1, before: 1 },
|
||||
't1: when second timer installed');
|
||||
}
|
||||
|
||||
// even though we install 3 timers we only have two timerwrap resources created
|
||||
// as one is reused for the two timers with the same timeout
|
||||
let t2;
|
||||
|
||||
function onsecondTimeout() {
|
||||
let as = hooks.activitiesOfTypes('TIMERWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one timer wrap when second timer fired');
|
||||
checkInvocations(t1, { init: 1, before: 2, after: 1 },
|
||||
't1: when second timer fired');
|
||||
|
||||
// install third timeout with different TIMEOUT
|
||||
setTimeout(onthirdTimeout, TIMEOUT + 1);
|
||||
as = hooks.activitiesOfTypes('TIMERWRAP');
|
||||
assert.strictEqual(as.length, 2,
|
||||
'two timer wraps when third timer installed');
|
||||
t2 = as[1];
|
||||
assert.strictEqual(t2.type, 'TIMERWRAP', 'timer wrap');
|
||||
assert.strictEqual(typeof t2.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof t2.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(t1, { init: 1, before: 2, after: 1 },
|
||||
't1: when third timer installed');
|
||||
checkInvocations(t2, { init: 1 },
|
||||
't2: when third timer installed');
|
||||
}
|
||||
|
||||
function onthirdTimeout() {
|
||||
checkInvocations(t1, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
't1: when third timer fired');
|
||||
checkInvocations(t2, { init: 1, before: 1 },
|
||||
't2: when third timer fired');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('TIMERWRAP');
|
||||
|
||||
checkInvocations(t1, { init: 1, before: 2, after: 2, destroy: 1 },
|
||||
't1: when process exits');
|
||||
checkInvocations(t2, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
't2: when process exits');
|
||||
}
|
133
test/async-hooks/test-tlswrap.js
Normal file
133
test/async-hooks/test-tlswrap.js
Normal file
@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const fs = require('fs');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const tls = require('tls');
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
//
|
||||
// Creating server and listening on port
|
||||
//
|
||||
const server = tls
|
||||
.createServer({
|
||||
cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem'),
|
||||
key: fs.readFileSync(common.fixturesDir + '/test_key.pem')
|
||||
})
|
||||
.on('listening', common.mustCall(onlistening))
|
||||
.on('secureConnection', common.mustCall(onsecureConnection))
|
||||
.listen(common.PORT);
|
||||
|
||||
let svr, client;
|
||||
function onlistening() {
|
||||
//
|
||||
// Creating client and connecting it to server
|
||||
//
|
||||
tls
|
||||
.connect(common.PORT, { rejectUnauthorized: false })
|
||||
.on('secureConnect', common.mustCall(onsecureConnect));
|
||||
|
||||
const as = hooks.activitiesOfTypes('TLSWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one TLSWRAP when client connecting');
|
||||
svr = as[0];
|
||||
|
||||
assert.strictEqual(svr.type, 'TLSWRAP', 'tls wrap');
|
||||
assert.strictEqual(typeof svr.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof svr.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(svr, { init: 1 }, 'server: when client connecting');
|
||||
}
|
||||
|
||||
function onsecureConnection() {
|
||||
//
|
||||
// Server received client connection
|
||||
//
|
||||
const as = hooks.activitiesOfTypes('TLSWRAP');
|
||||
assert.strictEqual(as.length, 2,
|
||||
'two TLSWRAPs when server has secure connection');
|
||||
client = as[1];
|
||||
assert.strictEqual(client.type, 'TLSWRAP', 'tls wrap');
|
||||
assert.strictEqual(typeof client.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof client.triggerId, 'number',
|
||||
'triggerId is a number');
|
||||
|
||||
// TODO(thlorenz) which callback did the server wrap execute that already
|
||||
// finished as well?
|
||||
checkInvocations(svr, { init: 1, before: 1, after: 1 },
|
||||
'server: when server has secure connection');
|
||||
|
||||
checkInvocations(client, { init: 1, before: 2, after: 1 },
|
||||
'client: when server has secure connection');
|
||||
}
|
||||
|
||||
function onsecureConnect() {
|
||||
//
|
||||
// Client connected to server
|
||||
//
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 1 },
|
||||
'server: when client connected');
|
||||
checkInvocations(client, { init: 1, before: 2, after: 2 },
|
||||
'client: when client connected');
|
||||
|
||||
//
|
||||
// Destroying client socket
|
||||
//
|
||||
this.destroy();
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 1 },
|
||||
'server: when destroying client');
|
||||
checkInvocations(client, { init: 1, before: 2, after: 2 },
|
||||
'client: when destroying client');
|
||||
|
||||
tick(5, tick1);
|
||||
function tick1() {
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 2 },
|
||||
'server: when client destroyed');
|
||||
// TODO: why is client not destroyed here even after 5 ticks?
|
||||
// or could it be that it isn't actually destroyed until
|
||||
// the server is closed?
|
||||
checkInvocations(client, { init: 1, before: 3, after: 3 },
|
||||
'client: when client destroyed');
|
||||
//
|
||||
// Closing server
|
||||
//
|
||||
server.close(common.mustCall(onserverClosed));
|
||||
// No changes to invocations until server actually closed below
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 2 },
|
||||
'server: when closing server');
|
||||
checkInvocations(client, { init: 1, before: 3, after: 3 },
|
||||
'client: when closing server');
|
||||
}
|
||||
}
|
||||
|
||||
function onserverClosed() {
|
||||
//
|
||||
// Server closed
|
||||
//
|
||||
tick(1E4, common.mustCall(() => {
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 2 },
|
||||
'server: when server closed');
|
||||
checkInvocations(client, { init: 1, before: 3, after: 3 },
|
||||
'client: when server closed');
|
||||
}));
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('TLSWRAP');
|
||||
|
||||
checkInvocations(svr, { init: 1, before: 2, after: 2 },
|
||||
'server: when process exits');
|
||||
checkInvocations(client, { init: 1, before: 3, after: 3 },
|
||||
'client: when process exits');
|
||||
}
|
42
test/async-hooks/test-ttywrap.readstream.js
Normal file
42
test/async-hooks/test-ttywrap.readstream.js
Normal file
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const ReadStream = require('tty').ReadStream;
|
||||
const ttyStream = new ReadStream(0);
|
||||
|
||||
const as = hooks.activitiesOfTypes('TTYWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one TTYWRAP when tty created');
|
||||
const tty = as[0];
|
||||
assert.strictEqual(tty.type, 'TTYWRAP', 'tty wrap');
|
||||
assert.strictEqual(typeof tty.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tty.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(tty, { init: 1 }, 'when tty created');
|
||||
|
||||
ttyStream.end(common.mustCall(onend));
|
||||
|
||||
checkInvocations(tty, { init: 1 }, 'when tty.end() was invoked ');
|
||||
|
||||
function onend() {
|
||||
tick(2, common.mustCall(() =>
|
||||
checkInvocations(
|
||||
tty, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when tty ended ')
|
||||
));
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('TTYWRAP');
|
||||
checkInvocations(tty, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
62
test/async-hooks/test-ttywrap.writestream.js
Normal file
62
test/async-hooks/test-ttywrap.writestream.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const tty_fd = common.getTTYfd();
|
||||
|
||||
if (tty_fd < 0)
|
||||
return common.skip('no valid TTY fd available');
|
||||
const ttyStream = (() => {
|
||||
try {
|
||||
return new (require('tty').WriteStream)(tty_fd);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
if (ttyStream === null)
|
||||
return common.skip('no valid TTY fd available');
|
||||
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
const as = hooks.activitiesOfTypes('TTYWRAP');
|
||||
assert.strictEqual(as.length, 1, 'one TTYWRAP when tty created');
|
||||
const tty = as[0];
|
||||
assert.strictEqual(tty.type, 'TTYWRAP', 'tty wrap');
|
||||
assert.strictEqual(typeof tty.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof tty.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(tty, { init: 1 }, 'when tty created');
|
||||
|
||||
ttyStream
|
||||
.on('finish', common.mustCall(onfinish))
|
||||
.end(common.mustCall(onend));
|
||||
|
||||
checkInvocations(tty, { init: 1}, 'when tty.end() was invoked ');
|
||||
|
||||
function onend() {
|
||||
tick(2, common.mustCall(() =>
|
||||
checkInvocations(
|
||||
tty, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when tty ended ')
|
||||
));
|
||||
}
|
||||
|
||||
function onfinish() {
|
||||
tick(2, common.mustCall(() =>
|
||||
checkInvocations(
|
||||
tty, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when tty ended ')
|
||||
));
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('TTYWRAP');
|
||||
checkInvocations(tty, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
58
test/async-hooks/test-udpsendwrap.js
Normal file
58
test/async-hooks/test-udpsendwrap.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const dgram = require('dgram');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
let send;
|
||||
|
||||
const sock = dgram
|
||||
.createSocket('udp4')
|
||||
.on('listening', common.mustCall(onlistening))
|
||||
.bind();
|
||||
|
||||
function onlistening() {
|
||||
sock.send(
|
||||
new Buffer(2), 0, 2, sock.address().port,
|
||||
undefined, common.mustCall(onsent));
|
||||
|
||||
// init not called synchronously because dns lookup alwasy wraps
|
||||
// callback in a next tick even if no lookup is needed
|
||||
// TODO (trevnorris) submit patch to fix creation of tick objects and instead
|
||||
// create the send wrap synchronously.
|
||||
assert.strictEqual(
|
||||
hooks.activitiesOfTypes('UDPSENDWRAP').length, 0,
|
||||
'no udpsendwrap after sock connected and sock.send called');
|
||||
}
|
||||
|
||||
function onsent() {
|
||||
const as = hooks.activitiesOfTypes('UDPSENDWRAP');
|
||||
send = as[0];
|
||||
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one UDPSENDWRAP created synchronously when message sent');
|
||||
assert.strictEqual(send.type, 'UDPSENDWRAP', 'send wrap');
|
||||
assert.strictEqual(typeof send.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof send.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(send, { init: 1, before: 1 }, 'when message sent');
|
||||
|
||||
sock.close(common.mustCall(onsockClosed));
|
||||
}
|
||||
|
||||
function onsockClosed() {
|
||||
checkInvocations(send, { init: 1, before: 1, after: 1 }, 'when sock closed');
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('UDPSENDWRAP');
|
||||
checkInvocations(send, { init: 1, before: 1, after: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
38
test/async-hooks/test-udpwrap.js
Normal file
38
test/async-hooks/test-udpwrap.js
Normal file
@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const tick = require('./tick');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
const dgram = require('dgram');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
const sock = dgram.createSocket('udp4');
|
||||
|
||||
const as = hooks.activitiesOfTypes('UDPWRAP');
|
||||
const udpwrap = as[0];
|
||||
assert.strictEqual(as.length, 1,
|
||||
'one UDPWRAP handle after dgram.createSocket call');
|
||||
assert.strictEqual(udpwrap.type, 'UDPWRAP', 'udp wrap');
|
||||
assert.strictEqual(typeof udpwrap.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof udpwrap.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(udpwrap, { init: 1 }, 'after dgram.createSocket call');
|
||||
|
||||
sock.close(common.mustCall(onsockClosed));
|
||||
|
||||
function onsockClosed() {
|
||||
checkInvocations(udpwrap, { init: 1 }, 'when socket is closed');
|
||||
tick(2);
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('UDPWRAP');
|
||||
checkInvocations(udpwrap, { init: 1, destroy: 1 },
|
||||
'when process exits');
|
||||
}
|
98
test/async-hooks/test-writewrap.js
Normal file
98
test/async-hooks/test-writewrap.js
Normal file
@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const fs = require('fs');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('missing crypto');
|
||||
return;
|
||||
}
|
||||
|
||||
const tls = require('tls');
|
||||
const hooks = initHooks();
|
||||
hooks.enable();
|
||||
|
||||
//
|
||||
// Creating server and listening on port
|
||||
//
|
||||
const server = tls
|
||||
.createServer({
|
||||
cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem'),
|
||||
key: fs.readFileSync(common.fixturesDir + '/test_key.pem')
|
||||
})
|
||||
.on('listening', common.mustCall(onlistening))
|
||||
.on('secureConnection', common.mustCall(onsecureConnection))
|
||||
.listen(common.PORT);
|
||||
|
||||
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0,
|
||||
'no WRITEWRAP when server created');
|
||||
|
||||
function onlistening() {
|
||||
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0,
|
||||
'no WRITEWRAP when server is listening');
|
||||
//
|
||||
// Creating client and connecting it to server
|
||||
//
|
||||
tls
|
||||
.connect(common.PORT, { rejectUnauthorized: false })
|
||||
.on('secureConnect', common.mustCall(onsecureConnect));
|
||||
|
||||
assert.strictEqual(hooks.activitiesOfTypes('WRITEWRAP').length, 0,
|
||||
'no WRITEWRAP when client created');
|
||||
}
|
||||
|
||||
function checkDestroyedWriteWraps(n, stage) {
|
||||
const as = hooks.activitiesOfTypes('WRITEWRAP');
|
||||
assert.strictEqual(as.length, n, n + ' WRITEWRAPs when ' + stage);
|
||||
|
||||
function checkValidWriteWrap(w) {
|
||||
assert.strictEqual(w.type, 'WRITEWRAP', 'write wrap');
|
||||
assert.strictEqual(typeof w.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof w.triggerId, 'number', 'triggerId is a number');
|
||||
|
||||
checkInvocations(w, { init: 1, destroy: 1 }, 'when ' + stage);
|
||||
}
|
||||
as.forEach(checkValidWriteWrap);
|
||||
}
|
||||
|
||||
function onsecureConnection() {
|
||||
//
|
||||
// Server received client connection
|
||||
//
|
||||
checkDestroyedWriteWraps(3, 'server got secure connection');
|
||||
}
|
||||
|
||||
function onsecureConnect() {
|
||||
//
|
||||
// Client connected to server
|
||||
//
|
||||
checkDestroyedWriteWraps(4, 'client connected');
|
||||
|
||||
//
|
||||
// Destroying client socket
|
||||
//
|
||||
this.destroy();
|
||||
|
||||
checkDestroyedWriteWraps(4, 'client destroyed');
|
||||
|
||||
//
|
||||
// Closing server
|
||||
//
|
||||
server.close(common.mustCall(onserverClosed));
|
||||
checkDestroyedWriteWraps(4, 'server closing');
|
||||
}
|
||||
|
||||
function onserverClosed() {
|
||||
checkDestroyedWriteWraps(4, 'server closed');
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('WRITEWRAP');
|
||||
checkDestroyedWriteWraps(4, 'process exits');
|
||||
}
|
62
test/async-hooks/test-zlib.zlib-binding.deflate.js
Normal file
62
test/async-hooks/test-zlib.zlib-binding.deflate.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const initHooks = require('./init-hooks');
|
||||
const { checkInvocations } = require('./hook-checks');
|
||||
|
||||
const hooks = initHooks();
|
||||
|
||||
hooks.enable();
|
||||
const Zlib = process.binding('zlib').Zlib;
|
||||
const constants = process.binding('constants').zlib;
|
||||
|
||||
const handle = new Zlib(constants.DEFLATE);
|
||||
|
||||
const as = hooks.activitiesOfTypes('ZLIB');
|
||||
assert.strictEqual(as.length, 1, 'one zlib on when created handle');
|
||||
const hdl = as[0];
|
||||
assert.strictEqual(hdl.type, 'ZLIB', 'zlib');
|
||||
assert.strictEqual(typeof hdl.uid, 'number', 'uid is a number');
|
||||
assert.strictEqual(typeof hdl.triggerId, 'number', 'triggerId is a number');
|
||||
checkInvocations(hdl, { init: 1 }, 'when created handle');
|
||||
|
||||
handle.init(
|
||||
constants.Z_DEFAULT_WINDOWBITS,
|
||||
constants.Z_MIN_LEVEL,
|
||||
constants.Z_DEFAULT_MEMLEVEL,
|
||||
constants.Z_DEFAULT_STRATEGY,
|
||||
Buffer.from('')
|
||||
);
|
||||
checkInvocations(hdl, { init: 1 }, 'when initialized handle');
|
||||
|
||||
const inBuf = Buffer.from('x');
|
||||
const outBuf = Buffer.allocUnsafe(1);
|
||||
|
||||
let count = 2;
|
||||
handle.callback = common.mustCall(onwritten, 2);
|
||||
handle.write(true, inBuf, 0, 1, outBuf, 0, 1);
|
||||
checkInvocations(hdl, { init: 1 }, 'when invoked write() on handle');
|
||||
|
||||
function onwritten() {
|
||||
if (--count) {
|
||||
// first write
|
||||
checkInvocations(hdl, { init: 1, before: 1 },
|
||||
'when wrote to handle the first time');
|
||||
handle.write(true, inBuf, 0, 1, outBuf, 0, 1);
|
||||
} else {
|
||||
// second write
|
||||
checkInvocations(hdl, { init: 1, before: 2, after: 1 },
|
||||
'when wrote to handle the second time');
|
||||
}
|
||||
}
|
||||
|
||||
process.on('exit', onexit);
|
||||
|
||||
function onexit() {
|
||||
hooks.disable();
|
||||
hooks.sanityCheck('ZLIB');
|
||||
// TODO: destroy never called here even with large amounts of ticks
|
||||
// is that correct?
|
||||
checkInvocations(hdl, { init: 1, before: 2, after: 2 }, 'when process exits');
|
||||
}
|
6
test/async-hooks/testcfg.py
Normal file
6
test/async-hooks/testcfg.py
Normal file
@ -0,0 +1,6 @@
|
||||
import sys, os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import testpy
|
||||
|
||||
def GetConfiguration(context, root):
|
||||
return testpy.AsyncHooksTestConfiguration(context, root, 'async-hooks')
|
13
test/async-hooks/tick.js
Normal file
13
test/async-hooks/tick.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
|
||||
module.exports = function tick(x, cb) {
|
||||
function ontick() {
|
||||
if (--x === 0) {
|
||||
if (typeof cb === 'function') cb();
|
||||
} else {
|
||||
setImmediate(ontick);
|
||||
}
|
||||
}
|
||||
setImmediate(ontick);
|
||||
};
|
114
test/async-hooks/verify-graph.js
Normal file
114
test/async-hooks/verify-graph.js
Normal file
@ -0,0 +1,114 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
require('../common');
|
||||
|
||||
function findInGraph(graph, type, n) {
|
||||
let found = 0;
|
||||
for (let i = 0; i < graph.length; i++) {
|
||||
const node = graph[i];
|
||||
if (node.type === type) found++;
|
||||
if (found === n) return node;
|
||||
}
|
||||
}
|
||||
|
||||
function pruneTickObjects(activities) {
|
||||
// remove one TickObject on each pass until none is left anymore
|
||||
// not super efficient, but simplest especially to handle
|
||||
// multiple TickObjects in a row
|
||||
let foundTickObject = true;
|
||||
|
||||
while (foundTickObject) {
|
||||
foundTickObject = false;
|
||||
let tickObjectIdx = -1;
|
||||
for (let i = 0; i < activities.length; i++) {
|
||||
if (activities[i].type !== 'TickObject') continue;
|
||||
tickObjectIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tickObjectIdx >= 0) {
|
||||
foundTickObject = true;
|
||||
|
||||
// point all triggerIds that point to the tickObject
|
||||
// to its triggerId and findally remove it from the activities
|
||||
const tickObject = activities[tickObjectIdx];
|
||||
const newTriggerId = tickObject.triggerId;
|
||||
const oldTriggerId = tickObject.uid;
|
||||
activities.forEach(function repointTriggerId(x) {
|
||||
if (x.triggerId === oldTriggerId) x.triggerId = newTriggerId;
|
||||
});
|
||||
activities.splice(tickObjectIdx, 1);
|
||||
}
|
||||
}
|
||||
return activities;
|
||||
}
|
||||
|
||||
module.exports = function verifyGraph(hooks, graph) {
|
||||
pruneTickObjects(hooks);
|
||||
|
||||
// map actual ids to standin ids defined in the graph
|
||||
const idtouid = {};
|
||||
const uidtoid = {};
|
||||
const typeSeen = {};
|
||||
const errors = [];
|
||||
|
||||
const activities = pruneTickObjects(hooks.activities);
|
||||
activities.forEach(processActivity);
|
||||
|
||||
function processActivity(x) {
|
||||
if (!typeSeen[x.type]) typeSeen[x.type] = 0;
|
||||
typeSeen[x.type]++;
|
||||
|
||||
const node = findInGraph(graph, x.type, typeSeen[x.type]);
|
||||
if (node == null) return;
|
||||
|
||||
idtouid[node.id] = x.uid;
|
||||
uidtoid[x.uid] = node.id;
|
||||
if (node.triggerId == null) return;
|
||||
|
||||
const tid = idtouid[node.triggerId];
|
||||
if (x.triggerId === tid) return;
|
||||
|
||||
errors.push({
|
||||
id: node.id,
|
||||
expectedTid: node.triggerId,
|
||||
actualTid: uidtoid[x.triggerId]
|
||||
});
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach((x) =>
|
||||
console.error(
|
||||
`'${x.id}' expected to be triggered by '${x.expectedTid}', ` +
|
||||
`but was triggered by '${x.actualTid}' instead.`
|
||||
)
|
||||
);
|
||||
}
|
||||
assert.strictEqual(errors.length, 0, 'Found errors while verifying graph.');
|
||||
};
|
||||
|
||||
//
|
||||
// Helper to generate the input to the verifyGraph tests
|
||||
//
|
||||
function inspect(obj, depth) {
|
||||
console.error(require('util').inspect(obj, false, depth || 5, true));
|
||||
}
|
||||
|
||||
module.exports.printGraph = function printGraph(hooks) {
|
||||
const ids = {};
|
||||
const uidtoid = {};
|
||||
const activities = pruneTickObjects(hooks.activities);
|
||||
const graph = [];
|
||||
activities.forEach(procesNode);
|
||||
|
||||
function procesNode(x) {
|
||||
const key = x.type.replace(/WRAP/, '').toLowerCase();
|
||||
if (!ids[key]) ids[key] = 1;
|
||||
const id = key + ':' + ids[key]++;
|
||||
uidtoid[x.uid] = id;
|
||||
const triggerId = uidtoid[x.triggerId] || null;
|
||||
graph.push({ type: x.type, id, triggerId });
|
||||
}
|
||||
inspect(graph);
|
||||
};
|
@ -168,3 +168,15 @@ class AddonTestConfiguration(SimpleTestConfiguration):
|
||||
result.append(
|
||||
SimpleTestCase(test, file_path, arch, mode, self.context, self, self.additional_flags))
|
||||
return result
|
||||
|
||||
class AsyncHooksTestConfiguration(SimpleTestConfiguration):
|
||||
def __init__(self, context, root, section, additional=None):
|
||||
super(AsyncHooksTestConfiguration, self).__init__(context, root, section,
|
||||
additional)
|
||||
|
||||
def ListTests(self, current_path, path, arch, mode):
|
||||
result = super(AsyncHooksTestConfiguration, self).ListTests(
|
||||
current_path, path, arch, mode)
|
||||
for test in result:
|
||||
test.parallel = True
|
||||
return result
|
||||
|
@ -1528,6 +1528,7 @@ BUILT_IN_TESTS = [
|
||||
'debugger',
|
||||
'doctool',
|
||||
'inspector',
|
||||
'async-hooks',
|
||||
]
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user