nodejs-mozilla/test/abort/test-addon-uv-handle-leak.js

129 lines
4.0 KiB
JavaScript

'use strict';
const common = require('../common');
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const { spawnSync } = require('child_process');
// This is a sibling test to test/addons/uv-handle-leak.
const bindingPath = path.resolve(
__dirname, '..', 'addons', 'uv-handle-leak', 'build',
`${common.buildType}/binding.node`);
if (!fs.existsSync(bindingPath))
common.skip('binding not built yet');
if (process.argv[2] === 'child') {
const { Worker } = require('worker_threads');
// The worker thread loads and then unloads `bindingPath`. Because of this the
// symbols in `bindingPath` are lost when the worker thread quits, but the
// number of open handles in the worker thread's event loop is assessed in the
// main thread afterwards, and the names of the callbacks associated with the
// open handles is retrieved at that time as well. Thus, we require
// `bindingPath` here so that the symbols and their names survive the life
// cycle of the worker thread.
require(bindingPath);
new Worker(`
const binding = require(${JSON.stringify(bindingPath)});
binding.leakHandle();
binding.leakHandle(0);
binding.leakHandle(0x42);
`, { eval: true });
} else {
const child = cp.spawnSync(process.execPath, [__filename, 'child']);
const stderr = child.stderr.toString();
assert.strictEqual(child.stdout.toString(), '');
const lines = stderr.split('\n');
let state = 'initial';
// Parse output that is formatted like this:
// uv loop at [0x559b65ed5770] has open handles:
// [0x7f2de0018430] timer (active)
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: 0x7f2df33df140 example_instance [...]
// (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
// [0x7f2de000b870] timer
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: (nil)
// [0x7f2de000b910] timer
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
// Data: 0x42
// uv loop at [0x559b65ed5770] has 3 open handles in total
function isGlibc() {
try {
const lddOut = spawnSync('ldd', [process.execPath]).stdout;
const libcInfo = lddOut.toString().split('\n').map(
(line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
if (libcInfo.length === 0)
return false;
const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
if (/gnu_get_libc_version/.test(nmOut))
return true;
} catch {
return false;
}
}
if (!(common.isFreeBSD ||
common.isAIX ||
(common.isLinux && !isGlibc()) ||
common.isWindows)) {
assert(stderr.includes('ExampleOwnerClass'), stderr);
assert(stderr.includes('CloseCallback'), stderr);
assert(stderr.includes('example_instance'), stderr);
}
while (lines.length > 0) {
const line = lines.shift().trim();
switch (state) {
case 'initial':
assert(/^uv loop at \[.+\] has open handles:$/.test(line), line);
state = 'handle-start';
break;
case 'handle-start':
if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) {
state = 'assertion-failure';
break;
}
assert(/^\[.+\] timer( \(active\))?$/.test(line), line);
state = 'close-callback';
break;
case 'close-callback':
assert(/^Close callback:/.test(line), line);
state = 'data';
break;
case 'data':
assert(/^Data: .+$/.test(line), line);
state = 'maybe-first-field';
break;
case 'maybe-first-field':
if (!/^\(First field\)/.test(line)) {
lines.unshift(line);
}
state = 'handle-start';
break;
case 'assertion-failure':
assert(/Assertion .+ failed/.test(line), line);
state = 'done';
break;
case 'done':
break;
}
}
assert.strictEqual(state, 'done');
}