mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
bff7a46f31
It's likely that anyone using `process.report.getReport()` will be processing the return value thereafter (e.g., filtering fields or redacting secrets). This change eliminates boilerplate by calling `JSON.parse()` on the return value. Also modified the `validateContent()` and `validate()` test helpers in `test/common/report.js` to be somewhat more obvious and helpful. Of note, a report failing validation will now be easier (though still not _easy_) to read when prepended to the stack trace. - Refs: https://github.com/nodejs/diagnostics/issues/315 PR-URL: https://github.com/nodejs/node/pull/28630 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Rich Trott <rtrott@gmail.com>
189 lines
6.9 KiB
JavaScript
189 lines
6.9 KiB
JavaScript
'use strict';
|
|
|
|
// Testcase to check reporting of uv handles.
|
|
const common = require('../common');
|
|
common.skipIfReportDisabled();
|
|
if (process.argv[2] === 'child') {
|
|
// Exit on loss of parent process
|
|
const exit = () => process.exit(2);
|
|
process.on('disconnect', exit);
|
|
|
|
const fs = require('fs');
|
|
const http = require('http');
|
|
const spawn = require('child_process').spawn;
|
|
|
|
// Watching files should result in fs_event/fs_poll uv handles.
|
|
let watcher;
|
|
try {
|
|
watcher = fs.watch(__filename);
|
|
} catch {
|
|
// fs.watch() unavailable
|
|
}
|
|
fs.watchFile(__filename, () => {});
|
|
|
|
// Child should exist when this returns as child_process.pid must be set.
|
|
const child_process = spawn(process.execPath,
|
|
['-e', "process.stdin.on('data', (x) => " +
|
|
'console.log(x.toString()));']);
|
|
|
|
const timeout = setInterval(() => {}, 1000);
|
|
// Make sure the timer doesn't keep the test alive and let
|
|
// us check we detect unref'd handles correctly.
|
|
timeout.unref();
|
|
|
|
// Datagram socket for udp uv handles.
|
|
const dgram = require('dgram');
|
|
const udp_socket = dgram.createSocket('udp4');
|
|
const connected_udp_socket = dgram.createSocket('udp4');
|
|
udp_socket.bind({}, common.mustCall(() => {
|
|
connected_udp_socket.connect(udp_socket.address().port);
|
|
}));
|
|
|
|
// Simple server/connection to create tcp uv handles.
|
|
const server = http.createServer((req, res) => {
|
|
req.on('end', () => {
|
|
// Generate the report while the connection is active.
|
|
console.log(JSON.stringify(process.report.getReport(), null, 2));
|
|
child_process.kill();
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
res.end();
|
|
|
|
// Tidy up to allow process to exit cleanly.
|
|
server.close(() => {
|
|
if (watcher) watcher.close();
|
|
fs.unwatchFile(__filename);
|
|
connected_udp_socket.close();
|
|
udp_socket.close();
|
|
process.removeListener('disconnect', exit);
|
|
});
|
|
});
|
|
req.resume();
|
|
});
|
|
server.listen(() => {
|
|
const data = { pid: child_process.pid,
|
|
tcp_address: server.address(),
|
|
udp_address: udp_socket.address(),
|
|
skip_fs_watch: (watcher === undefined) };
|
|
process.send(data);
|
|
http.get({ port: server.address().port });
|
|
});
|
|
} else {
|
|
const helper = require('../common/report.js');
|
|
const fork = require('child_process').fork;
|
|
const assert = require('assert');
|
|
const tmpdir = require('../common/tmpdir');
|
|
tmpdir.refresh();
|
|
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
|
|
const child = fork('--experimental-report', [__filename, 'child'], options);
|
|
let child_data;
|
|
child.on('message', (data) => { child_data = data; });
|
|
let stderr = '';
|
|
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
let stdout = '';
|
|
const std_msg = 'Found messages in stderr unexpectedly: ';
|
|
const report_msg = 'Report files were written: unexpectedly';
|
|
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
child.on('exit', common.mustCall((code, signal) => {
|
|
assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code: ' +
|
|
`${code}`);
|
|
assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
|
|
` but did not: ${signal}`);
|
|
assert.ok(stderr.match(
|
|
'(node:.*) ExperimentalWarning: report is an experimental' +
|
|
' feature. This feature could change at any time'),
|
|
std_msg);
|
|
|
|
const reports = helper.findReports(child.pid, tmpdir.path);
|
|
assert.deepStrictEqual(reports, [], report_msg, reports);
|
|
|
|
// Test libuv handle key order
|
|
{
|
|
const get_libuv = /"libuv":\s\[([\s\S]*?)\]/gm;
|
|
const get_handle_inner = /{([\s\S]*?),*?}/gm;
|
|
const libuv_handles_str = get_libuv.exec(stdout)[1];
|
|
const libuv_handles_array = libuv_handles_str.match(get_handle_inner);
|
|
for (const i of libuv_handles_array) {
|
|
// Exclude nested structure
|
|
if (i.includes('type')) {
|
|
const handle_keys = i.match(/(".*"):/gm);
|
|
assert(handle_keys[0], 'type');
|
|
assert(handle_keys[1], 'is_active');
|
|
}
|
|
}
|
|
}
|
|
|
|
const report = JSON.parse(stdout);
|
|
const prefix = common.isWindows ? '\\\\?\\' : '';
|
|
const expected_filename = `${prefix}${__filename}`;
|
|
const found_tcp = [];
|
|
const found_udp = [];
|
|
// Functions are named to aid debugging when they are not called.
|
|
const validators = {
|
|
fs_event: common.mustCall(function fs_event_validator(handle) {
|
|
if (!child_data.skip_fs_watch) {
|
|
assert.strictEqual(handle.filename, expected_filename);
|
|
assert(handle.is_referenced);
|
|
}
|
|
}),
|
|
fs_poll: common.mustCall(function fs_poll_validator(handle) {
|
|
assert.strictEqual(handle.filename, expected_filename);
|
|
assert(handle.is_referenced);
|
|
}),
|
|
pipe: common.mustCallAtLeast(function pipe_validator(handle) {
|
|
assert(handle.is_referenced);
|
|
}),
|
|
process: common.mustCall(function process_validator(handle) {
|
|
assert.strictEqual(handle.pid, child_data.pid);
|
|
assert(handle.is_referenced);
|
|
}),
|
|
tcp: common.mustCall(function tcp_validator(handle) {
|
|
// TCP handles. The report should contain three sockets:
|
|
// 1. The server's listening socket.
|
|
// 2. The inbound socket making the request.
|
|
// 3. The outbound socket sending the response.
|
|
const port = child_data.tcp_address.port;
|
|
if (handle.localEndpoint.port === port) {
|
|
if (handle.remoteEndpoint === null) {
|
|
found_tcp.push('listening');
|
|
} else {
|
|
found_tcp.push('inbound');
|
|
}
|
|
} else if (handle.remoteEndpoint.port === port) {
|
|
found_tcp.push('outbound');
|
|
}
|
|
assert(handle.is_referenced);
|
|
}, 3),
|
|
timer: common.mustCallAtLeast(function timer_validator(handle) {
|
|
assert(!handle.is_referenced);
|
|
assert.strictEqual(handle.repeat, 0);
|
|
}),
|
|
udp: common.mustCall(function udp_validator(handle) {
|
|
if (handle.remoteEndpoint === null) {
|
|
assert.strictEqual(handle.localEndpoint.port,
|
|
child_data.udp_address.port);
|
|
found_udp.push('unconnected');
|
|
} else {
|
|
assert.strictEqual(handle.remoteEndpoint.port,
|
|
child_data.udp_address.port);
|
|
found_udp.push('connected');
|
|
}
|
|
assert(handle.is_referenced);
|
|
}, 2),
|
|
};
|
|
console.log(report.libuv);
|
|
for (const entry of report.libuv) {
|
|
if (validators[entry.type]) validators[entry.type](entry);
|
|
}
|
|
for (const socket of ['listening', 'inbound', 'outbound']) {
|
|
assert(found_tcp.includes(socket), `${socket} TCP socket was not found`);
|
|
}
|
|
for (const socket of ['connected', 'unconnected']) {
|
|
assert(found_udp.includes(socket), `${socket} UDP socket was not found`);
|
|
}
|
|
|
|
// Common report tests.
|
|
helper.validateContent(stdout);
|
|
}));
|
|
}
|