Skip to content

Commit 0800f91

Browse files
LakshmiSwethaGtargos
authored andcommitted
test: add node-report tests
One test per each API, so that additional tests in future are modular. test/common/report.js contain common functions that tests leverage. PR-URL: #22712 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Vse Mozhet Byt <[email protected]>
1 parent 4f38106 commit 0800f91

13 files changed

+484
-1
lines changed

doc/api/errors.md

+6
Original file line numberDiff line numberDiff line change
@@ -2227,6 +2227,12 @@ size.
22272227
This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
22282228
such as `process.stdout.on('data')`.
22292229

2230+
<a id="ERR_SYNTHETIC"></a>
2231+
#### ERR_SYNTHETIC
2232+
2233+
An artifical error object used to capture call stack when diagnostic report
2234+
is produced.
2235+
22302236

22312237
[`'uncaughtException'`]: process.html#process_event_uncaughtexception
22322238
[`--force-fips`]: cli.html#cli_force_fips

lib/internal/errors.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,6 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
709709
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
710710
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
711711
E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError);
712-
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
713712
E('ERR_INVALID_ARG_TYPE',
714713
(name, expected, actual) => {
715714
assert(typeof name === 'string', "'name' must be a string");
@@ -924,6 +923,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT',
924923
'stream.unshift() after end event', Error);
925924
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
926925
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
926+
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
927927
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError);
928928
E('ERR_TLS_CERT_ALTNAME_INVALID',
929929
'Hostname/IP does not match certificate\'s altnames: %s', Error);

test/common/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,12 @@ function skipIfInspectorDisabled() {
636636
}
637637
}
638638

639+
function skipIfReportDisabled() {
640+
if (!process.config.variables.node_report) {
641+
skip('Node Report is disabled');
642+
}
643+
}
644+
639645
function skipIf32Bits() {
640646
if (bits < 64) {
641647
skip('The tested feature is not available in 32bit builds');
@@ -762,6 +768,7 @@ module.exports = {
762768
skipIf32Bits,
763769
skipIfEslintMissing,
764770
skipIfInspectorDisabled,
771+
skipIfReportDisabled,
765772
skipIfWorker,
766773

767774
get localhostIPv6() { return '::1'; },

test/common/report.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
const REPORT_SECTIONS = [ 'header',
8+
'javascriptStack',
9+
'nativeStack',
10+
'javascriptHeap',
11+
'libuv',
12+
'environmentVariables',
13+
'sharedObjects' ];
14+
15+
let tmppath = '';
16+
17+
exports.findReports = (pid, path) => {
18+
// Default filenames are of the form
19+
// report.<date>.<time>.<pid>.<seq>.json
20+
tmppath = path;
21+
const format = '^report\\.\\d+\\.\\d+\\.' + pid + '\\.\\d+\\.json$';
22+
const filePattern = new RegExp(format);
23+
const files = fs.readdirSync(path);
24+
return files.filter((file) => filePattern.test(file));
25+
};
26+
27+
exports.validate = (report, options) => {
28+
const jtmp = path.join(tmppath, report);
29+
fs.readFile(jtmp, (err, data) => {
30+
this.validateContent(data, options);
31+
});
32+
};
33+
34+
35+
exports.validateContent = function validateContent(data, options) {
36+
const report = JSON.parse(data);
37+
const comp = Object.keys(report);
38+
39+
// Check all sections are present
40+
REPORT_SECTIONS.forEach((section) => {
41+
assert.ok(comp.includes(section));
42+
});
43+
};
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
// Testcase for returning report as a string via API call
4+
const common = require('../common');
5+
common.skipIfReportDisabled();
6+
const assert = require('assert');
7+
if (process.argv[2] === 'child') {
8+
console.log(process.report.getReport());
9+
} else {
10+
const helper = require('../common/report.js');
11+
const spawnSync = require('child_process').spawnSync;
12+
const tmpdir = require('../common/tmpdir');
13+
tmpdir.refresh();
14+
15+
const args = ['--experimental-report', __filename, 'child'];
16+
const child = spawnSync(process.execPath, args, { cwd: tmpdir.path });
17+
const report_msg = 'Found report files';
18+
const std_msg = 'Found messages on stderr';
19+
assert.ok(child.stderr.toString().includes(
20+
`(node:${child.pid}) ExperimentalWarning: report is an` +
21+
' experimental feature. This feature could change at any time'), std_msg);
22+
const reportFiles = helper.findReports(child.pid, tmpdir.path);
23+
assert.deepStrictEqual(reportFiles, [], report_msg);
24+
helper.validateContent(child.stdout, { pid: child.pid,
25+
commandline: process.execPath +
26+
' ' + args.join(' ')
27+
});
28+
}

test/node-report/test-api-nohooks.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
// Testcase to produce report via API call, using the no-hooks/no-signal
4+
// interface - i.e. require('node-report/api')
5+
const common = require('../common');
6+
common.skipIfReportDisabled();
7+
if (process.argv[2] === 'child') {
8+
process.report.triggerReport();
9+
} else {
10+
const helper = require('../common/report.js');
11+
const spawn = require('child_process').spawn;
12+
const assert = require('assert');
13+
const tmpdir = require('../common/tmpdir');
14+
tmpdir.refresh();
15+
16+
const child = spawn(process.execPath, ['--experimental-report',
17+
__filename, 'child'],
18+
{ cwd: tmpdir.path });
19+
child.on('exit', common.mustCall((code) => {
20+
const report_msg = 'No reports found';
21+
const process_msg = 'Process exited unexpectedly';
22+
assert.strictEqual(code, 0, process_msg + ':' + code);
23+
const reports = helper.findReports(child.pid, tmpdir.path);
24+
assert.strictEqual(reports.length, 1, report_msg);
25+
const report = reports[0];
26+
helper.validate(report, { pid: child.pid,
27+
commandline: child.spawnargs.join(' ')
28+
});
29+
}));
30+
}
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
// Testcase for passing an error object to the API call.
4+
const common = require('../common');
5+
common.skipIfReportDisabled();
6+
const assert = require('assert');
7+
if (process.argv[2] === 'child') {
8+
try {
9+
throw new Error('Testing error handling');
10+
} catch {
11+
process.report.triggerReport();
12+
}
13+
} else {
14+
const helper = require('../common/report.js');
15+
const spawn = require('child_process').spawn;
16+
const tmpdir = require('../common/tmpdir');
17+
tmpdir.refresh();
18+
const child = spawn(process.execPath, ['--experimental-report',
19+
__filename, 'child'],
20+
{ cwd: tmpdir.path });
21+
child.on('exit', common.mustCall((code) => {
22+
const report_msg = 'No reports found';
23+
const process_msg = 'Process exited unexpectedly';
24+
assert.strictEqual(code, 0, process_msg);
25+
const reports = helper.findReports(child.pid, tmpdir.path);
26+
assert.strictEqual(reports.length, 1, report_msg);
27+
const report = reports[0];
28+
helper.validate(report, { pid: child.pid,
29+
commandline: child.spawnargs.join(' '),
30+
expectedException: 'Testing error handling',
31+
});
32+
}));
33+
}
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
'use strict';
2+
3+
// Testcase to check reporting of uv handles.
4+
const common = require('../common');
5+
common.skipIfReportDisabled();
6+
if (process.argv[2] === 'child') {
7+
// Exit on loss of parent process
8+
const exit = () => process.exit(2);
9+
process.on('disconnect', exit);
10+
11+
const fs = require('fs');
12+
const http = require('http');
13+
const spawn = require('child_process').spawn;
14+
15+
// Watching files should result in fs_event/fs_poll uv handles.
16+
let watcher;
17+
try {
18+
watcher = fs.watch(__filename);
19+
} catch {
20+
// fs.watch() unavailable
21+
}
22+
fs.watchFile(__filename, () => {});
23+
24+
// Child should exist when this returns as child_process.pid must be set.
25+
const child_process = spawn(process.execPath,
26+
['-e', "process.stdin.on('data', (x) => " +
27+
'console.log(x.toString()));']);
28+
29+
const timeout = setInterval(() => {}, 1000);
30+
// Make sure the timer doesn't keep the test alive and let
31+
// us check we detect unref'd handles correctly.
32+
timeout.unref();
33+
34+
// Datagram socket for udp uv handles.
35+
const dgram = require('dgram');
36+
const udp_socket = dgram.createSocket('udp4');
37+
udp_socket.bind({});
38+
39+
// Simple server/connection to create tcp uv handles.
40+
const server = http.createServer((req, res) => {
41+
req.on('end', () => {
42+
// Generate the report while the connection is active.
43+
console.log(process.report.getReport());
44+
child_process.kill();
45+
46+
res.writeHead(200, { 'Content-Type': 'text/plain' });
47+
res.end();
48+
49+
// Tidy up to allow process to exit cleanly.
50+
server.close(() => {
51+
if (watcher) watcher.close();
52+
fs.unwatchFile(__filename);
53+
udp_socket.close();
54+
process.removeListener('disconnect', exit);
55+
});
56+
});
57+
req.resume();
58+
});
59+
server.listen(() => {
60+
const data = { pid: child_process.pid,
61+
tcp_address: server.address(),
62+
udp_address: udp_socket.address(),
63+
skip_fs_watch: (watcher === undefined ?
64+
'fs.watch() unavailable' :
65+
false) };
66+
process.send(data);
67+
http.get({ port: server.address().port });
68+
});
69+
} else {
70+
const helper = require('../common/report.js');
71+
const fork = require('child_process').fork;
72+
const assert = require('assert');
73+
const tmpdir = require('../common/tmpdir');
74+
tmpdir.refresh();
75+
const options = { encoding: 'utf8', silent: true, cwd: tmpdir.path };
76+
const child = fork('--experimental-report', [__filename, 'child'], options);
77+
let stderr = '';
78+
child.stderr.on('data', (chunk) => { stderr += chunk; });
79+
let stdout = '';
80+
const std_msg = 'Found messages in stderr unexpectedly: ';
81+
const report_msg = 'Report files were written: unexpectedly';
82+
child.stdout.on('data', (chunk) => { stdout += chunk; });
83+
child.on('exit', common.mustCall((code, signal) => {
84+
assert.deepStrictEqual(code, 0, 'Process exited unexpectedly with code' +
85+
`${code}`);
86+
assert.deepStrictEqual(signal, null, 'Process should have exited cleanly,' +
87+
` but did not: ${signal}`);
88+
assert.ok(stderr.match(
89+
'(node:.*) ExperimentalWarning: report is an experimental' +
90+
' feature. This feature could change at any time'),
91+
std_msg);
92+
93+
const reports = helper.findReports(child.pid, tmpdir.path);
94+
assert.deepStrictEqual(reports, [], report_msg, reports);
95+
96+
const report = JSON.parse(stdout);
97+
let fs = 0;
98+
let poll = 0;
99+
let process = 0;
100+
let timer = 0;
101+
let pipe = 0;
102+
let tcp = 0;
103+
let udp = 0;
104+
const fs_msg = 'fs_event not found';
105+
const poll_msg = 'poll_event not found';
106+
const process_msg = 'process event not found';
107+
const timer_msg = 'timer event not found';
108+
const pipe_msg = 'pipe event not found';
109+
const tcp_msg = 'tcp event not found';
110+
const udp_msg = 'udp event not found';
111+
for (const entry in report.libuv) {
112+
if (report.libuv[entry].type === 'fs_event') fs = 1;
113+
else if (report.libuv[entry].type === 'fs_poll') poll = 1;
114+
else if (report.libuv[entry].type === 'process') process = 1;
115+
else if (report.libuv[entry].type === 'timer') timer = 1;
116+
else if (report.libuv[entry].type === 'pipe') pipe = 1;
117+
else if (report.libuv[entry].type === 'tcp') tcp = 1;
118+
else if (report.libuv[entry].type === 'udp') udp = 1;
119+
}
120+
assert.deepStrictEqual(fs, 1, fs_msg);
121+
assert.deepStrictEqual(poll, 1, poll_msg);
122+
assert.deepStrictEqual(process, 1, process_msg);
123+
assert.deepStrictEqual(timer, 1, timer_msg);
124+
assert.deepStrictEqual(pipe, 1, pipe_msg);
125+
assert.deepStrictEqual(tcp, 1, tcp_msg);
126+
assert.deepStrictEqual(udp, 1, udp_msg);
127+
128+
// Common report tests.
129+
helper.validateContent(stdout, { pid: child.pid,
130+
commandline: child.spawnargs.join(' ')
131+
});
132+
}));
133+
}

test/node-report/test-api.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
// Testcase to produce report via API call
4+
const common = require('../common');
5+
common.skipIfReportDisabled();
6+
if (process.argv[2] === 'child') {
7+
process.report.triggerReport();
8+
} else {
9+
const helper = require('../common/report.js');
10+
const spawn = require('child_process').spawn;
11+
const assert = require('assert');
12+
const tmpdir = require('../common/tmpdir');
13+
tmpdir.refresh();
14+
15+
const child = spawn(process.execPath,
16+
['--experimental-report', __filename, 'child'],
17+
{ cwd: tmpdir.path });
18+
child.on('exit', common.mustCall((code) => {
19+
const report_msg = 'No reports found';
20+
const process_msg = 'Process exited unexpectedly';
21+
assert.strictEqual(code, 0, process_msg + ':' + code);
22+
const reports = helper.findReports(child.pid, tmpdir.path);
23+
assert.strictEqual(reports.length, 1, report_msg);
24+
const report = reports[0];
25+
helper.validate(report, { pid: child.pid,
26+
commandline: child.spawnargs.join(' ')
27+
});
28+
}));
29+
}

test/node-report/test-exception.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
// Testcase to produce report on uncaught exception
4+
const common = require('../common');
5+
common.skipIfReportDisabled();
6+
if (process.argv[2] === 'child') {
7+
function myException(request, response) {
8+
const m = '*** test-exception.js: throwing uncaught Error';
9+
throw new Error(m);
10+
}
11+
12+
myException();
13+
14+
} else {
15+
const helper = require('../common/report.js');
16+
const tmpdir = require('../common/tmpdir');
17+
tmpdir.refresh();
18+
const spawn = require('child_process').spawn;
19+
const assert = require('assert');
20+
21+
const child = spawn(process.execPath,
22+
['--experimental-report',
23+
'--diagnostic-report-uncaught-exception',
24+
__filename, 'child'],
25+
{ cwd: tmpdir.path });
26+
// Capture stderr output from the child process
27+
let stderr = '';
28+
child.stderr.on('data', (chunk) => {
29+
stderr += chunk;
30+
});
31+
child.on('exit', common.mustCall((code) => {
32+
const report_msg = 'No reports found';
33+
const process_msg = 'Process exited unexpectedly';
34+
assert.strictEqual(code, 1, process_msg + ':' + code);
35+
assert.ok(new RegExp('myException').test(stderr),
36+
'Check for expected stack trace frame in stderr');
37+
const reports = helper.findReports(child.pid, tmpdir.path);
38+
assert.strictEqual(reports.length, 1, report_msg);
39+
const report = reports[0];
40+
helper.validate(report, { pid: child.pid,
41+
commandline: child.spawnargs.join(' ')
42+
});
43+
}));
44+
}

0 commit comments

Comments
 (0)