Skip to content

Instantly share code, notes, and snippets.

@boneskull
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();
});
});
});
@boneskull
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!

@nuest
Copy link

nuest commented Oct 27, 2017

This was very helpful - thanks!

@Xflofoxx
Copy link

Great work!

@tomasp1189
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.

@REWBrianH
Copy link

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

@dev-techmoe
Copy link

@boneskull thank you

@RdC1965
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"

@boneskull
Copy link
Author

@RdC1965
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