Skip to content

Instantly share code, notes, and snippets.

@subfuzion
Forked from boneskull/README.md
Created November 7, 2017 18:33
Show Gist options
  • Save subfuzion/54a9413d02c6d9ba223076bebd49e38f to your computer and use it in GitHub Desktop.
Save subfuzion/54a9413d02c6d9ba223076bebd49e38f to your computer and use it in GitHub Desktop.
example of how to debug mocha v4 if hanging

Here's an example of how to debug Mocha v4 if it hangs.

Ensure you're using a Node.js 8 or newer (or any version with async_hooks support).

If you run your test, you'll notice it hangs:

$ mocha test.js


  how to debug Mocha when it hangs
    ✓ should complete, but Mocha should not exit


  1 passing (25ms)

You can include a module like async-dump.js to print information about what's happening. First, add this hook somewhere in the describe block:

after(function () {
  global.asyncDump();
});

Then, you can --require async-dump.js with Mocha:

$ mocha --require async-dump test.js


  how to debug Mocha when it hangs
    ✓ should complete, but Mocha should not exit

STUFF STILL IN THE EVENT LOOP:
Type: SIGNALWRAP
Error
    at AsyncHook.init (async-dump.js:12:62)
    at Signal.emitInitNative (async_hooks.js:458:43)
    at process.<anonymous> (internal/process.js:209:20)
    at startup (bootstrap_node.js:201:16)
    at bootstrap_node.js:626:3


Type: TCPWRAP
Error
    at AsyncHook.init (async-dump.js:12:62)
    at TCP.emitInitNative (async_hooks.js:458:43)
    at createServerHandle (net.js:1267:14)
    at Server.setupListenHandle [as _listen2] (net.js:1310:14)
    at listenInCluster (net.js:1391:12)
    at Server.listen (net.js:1474:7)
    at Context.<anonymous> (debug-hanging-mocha.js:9:12)


Type: TCPWRAP
Error
    at AsyncHook.init (async-dump.js:12:62)
    at TCP.emitInitNative (async_hooks.js:458:43)
    at Socket.connect (net.js:997:40)
    at Object.connect (net.js:104:35)
    at Context.<anonymous> (debug-hanging-mocha.js:17:22)


Type: DNSCHANNEL
Error
    at AsyncHook.init (async-dump.js:12:62)
    at ChannelWrap.emitInitNative (async_hooks.js:458:43)
    at new Resolver (dns.js:249:20)
    at dns.js:380:25
    at NativeModule.compile (bootstrap_node.js:614:7)
    at NativeModule.require (bootstrap_node.js:559:18)
    at lazyDns (net.js:1376:11)
    at lookupAndConnect (net.js:1026:15)
    at Socket.connect (net.js:1019:5)
    at Object.connect (net.js:104:35)
    at Context.<anonymous> (debug-hanging-mocha.js:17:22)


Type: TCPWRAP
Error
    at AsyncHook.init (async-dump.js:12:62)
    at TCP.emitInitNative (async_hooks.js:458:43)


Type: Immediate
Error
    at AsyncHook.init (async-dump.js:12:62)
    at emitInitNative (async_hooks.js:458:43)
    at emitInitScript (async_hooks.js:361:3)


  1 passing (25ms)

These are not Errors; everything that's printed above are async tasks that have been "started" but not "finished".

From the above, you will notice that the TCPWRAP resource(s) are interesting. Within the stack trace, you can see where exactly these happen in your code:

Type: TCPWRAP
Error
    at AsyncHook.init (async-dump.js:12:62)
    at TCP.emitInitNative (async_hooks.js:458:43)
    at createServerHandle (net.js:1267:14)
    at Server.setupListenHandle [as _listen2] (net.js:1310:14)
    at listenInCluster (net.js:1391:12)
    at Server.listen (net.js:1474:7)
    at Context.<anonymous> (debug-hanging-mocha.js:9:12)

On line 9, we're listening on a port, but we never shut down the server.

async-dump.js could certainly be improved upon, but this is a rough guide.

The Node.js inspector (use --inspect-brk, etc.) will also give you similar information, but it's not so easily filtered.

why-is-node-running can also be used with some fiddling (do node --expose-internals /path/to/_mocha test.js then require('why-is-node-running')() at the top of test.js), but YMMV.

'use strict';
const {createHook} = require('async_hooks');
const {stackTraceFilter} = require('mocha/lib/utils');
const allResources = new Map();
// this will pull Mocha internals out of the stacks
const filterStack = stackTraceFilter();
const hook = createHook({
init(asyncId, type, triggerAsyncId) {
allResources.set(asyncId, {type, triggerAsyncId, stack: (new Error()).stack});
},
destroy(asyncId) {
allResources.delete(asyncId);
}
}).enable();
global.asyncDump = module.exports = () => {
hook.disable();
console.error(`
STUFF STILL IN THE EVENT LOOP:`)
allResources.forEach(value=> {
console.error(`Type: ${value.type}`);
console.error(filterStack(value.stack));
console.error('\n');
});
};
'use strict';
const net = require('net');
const assert = require('assert');
describe('how to debug Mocha when it hangs', function () {
before(function (done) {
const server = net.createServer();
server.listen(10101, done);
});
it('should complete, but Mocha should not exit', function(done) {
const sock = net.createConnection(10101, () => {
assert.deepEqual(sock.address().family, 'IPv4');
done();
});
});
});
@subfuzion
Copy link
Author

Hi @jric, thanks for asking, but I can't take credit for this. I forked this from boneskull/README.md. I had done a little bit of work playing around with the code to wrap it, but alas it's long gone and I never thought to publish it as an npm module. I think I just got better at releasing resources. :) Anyway, I think you've got a nice idea, though. Good luck!

@jric
Copy link

jric commented Aug 7, 2022

Thanks @subfuzion , that led me to this: https://www.npmjs.com/package/wtfnode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment