Skip to content

Instantly share code, notes, and snippets.

Last active April 10, 2024 12:47
Show Gist options
  • Save boneskull/7fe75b63d613fa940db7ec990a5f5843 to your computer and use it in GitHub Desktop.
Save boneskull/7fe75b63d613fa940db7ec990a5f5843 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();
});
});
});
Copy link
Author

...and if you're spinning your wheels, nobody is telling you that you can't just add --exit and move on to something else!

Copy link

nuest commented Oct 27, 2017

This was very helpful - thanks!

Copy link

Great work!

Copy link

I am spinning my wheels and --exit has not worked for me... it still hangs at the end of the tests and I have to kill the process manually.

Copy link

@boneskull --exit works for me. Thank you!

Copy link

@boneskull thank you

Copy link

RdC1965 commented Jun 5, 2019

Thanks a lot!
--exit works for me. In package.json:
"test": "(dropdb --if-exists myDb && createdb myDb) && NODE_ENV=test mocha --exit"

Copy link
Author

Copy link

RdC1965 commented Jun 5, 2019 via email

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