mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
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: https://github.com/nodejs/node/pull/22712 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michael Dawson <Michael_Dawson@ca.ibm.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
This commit is contained in:
committed by
Gireesh Punathil
parent
7f4053ed13
commit
55e0ad9ae6
@@ -2233,6 +2233,12 @@ size.
|
||||
This `Error` is thrown when a read is attempted on a TTY `WriteStream`,
|
||||
such as `process.stdout.on('data')`.
|
||||
|
||||
<a id="ERR_SYNTHETIC"></a>
|
||||
#### ERR_SYNTHETIC
|
||||
|
||||
An artifical error object used to capture call stack when diagnostic report
|
||||
is produced.
|
||||
|
||||
|
||||
[`'uncaughtException'`]: process.html#process_event_uncaughtexception
|
||||
[`--force-fips`]: cli.html#cli_force_fips
|
||||
|
||||
@@ -709,7 +709,6 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
|
||||
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
|
||||
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
|
||||
E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError);
|
||||
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
|
||||
E('ERR_INVALID_ARG_TYPE',
|
||||
(name, expected, actual) => {
|
||||
assert(typeof name === 'string', "'name' must be a string");
|
||||
@@ -924,6 +923,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT',
|
||||
'stream.unshift() after end event', Error);
|
||||
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
|
||||
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
|
||||
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
|
||||
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError);
|
||||
E('ERR_TLS_CERT_ALTNAME_INVALID',
|
||||
'Hostname/IP does not match certificate\'s altnames: %s', Error);
|
||||
|
||||
@@ -632,6 +632,12 @@ function skipIfInspectorDisabled() {
|
||||
}
|
||||
}
|
||||
|
||||
function skipIfReportDisabled() {
|
||||
if (!process.config.variables.node_report) {
|
||||
skip('Node Report is disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function skipIf32Bits() {
|
||||
if (bits < 64) {
|
||||
skip('The tested feature is not available in 32bit builds');
|
||||
@@ -758,6 +764,7 @@ module.exports = {
|
||||
skipIf32Bits,
|
||||
skipIfEslintMissing,
|
||||
skipIfInspectorDisabled,
|
||||
skipIfReportDisabled,
|
||||
skipIfWorker,
|
||||
|
||||
get localhostIPv6() { return '::1'; },
|
||||
|
||||
43
test/common/report.js
Normal file
43
test/common/report.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REPORT_SECTIONS = [ 'header',
|
||||
'javascriptStack',
|
||||
'nativeStack',
|
||||
'javascriptHeap',
|
||||
'libuv',
|
||||
'environmentVariables',
|
||||
'sharedObjects' ];
|
||||
|
||||
let tmppath = '';
|
||||
|
||||
exports.findReports = (pid, path) => {
|
||||
// Default filenames are of the form
|
||||
// report.<date>.<time>.<pid>.<seq>.json
|
||||
tmppath = path;
|
||||
const format = '^report\\.\\d+\\.\\d+\\.' + pid + '\\.\\d+\\.json$';
|
||||
const filePattern = new RegExp(format);
|
||||
const files = fs.readdirSync(path);
|
||||
return files.filter((file) => filePattern.test(file));
|
||||
};
|
||||
|
||||
exports.validate = (report, options) => {
|
||||
const jtmp = path.join(tmppath, report);
|
||||
fs.readFile(jtmp, (err, data) => {
|
||||
this.validateContent(data, options);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.validateContent = function validateContent(data, options) {
|
||||
const report = JSON.parse(data);
|
||||
const comp = Object.keys(report);
|
||||
|
||||
// Check all sections are present
|
||||
REPORT_SECTIONS.forEach((section) => {
|
||||
assert.ok(comp.includes(section));
|
||||
});
|
||||
};
|
||||
28
test/node-report/test-api-getreport.js
Normal file
28
test/node-report/test-api-getreport.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
// Testcase for returning report as a string via API call
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
const assert = require('assert');
|
||||
if (process.argv[2] === 'child') {
|
||||
console.log(process.report.getReport());
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
|
||||
const args = ['--experimental-report', __filename, 'child'];
|
||||
const child = spawnSync(process.execPath, args, { cwd: tmpdir.path });
|
||||
const report_msg = 'Found report files';
|
||||
const std_msg = 'Found messages on stderr';
|
||||
assert.ok(child.stderr.toString().includes(
|
||||
`(node:${child.pid}) ExperimentalWarning: report is an` +
|
||||
' experimental feature. This feature could change at any time'), std_msg);
|
||||
const reportFiles = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.deepStrictEqual(reportFiles, [], report_msg);
|
||||
helper.validateContent(child.stdout, { pid: child.pid,
|
||||
commandline: process.execPath +
|
||||
' ' + args.join(' ')
|
||||
});
|
||||
}
|
||||
30
test/node-report/test-api-nohooks.js
Normal file
30
test/node-report/test-api-nohooks.js
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
// Testcase to produce report via API call, using the no-hooks/no-signal
|
||||
// interface - i.e. require('node-report/api')
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
if (process.argv[2] === 'child') {
|
||||
process.report.triggerReport();
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const spawn = require('child_process').spawn;
|
||||
const assert = require('assert');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
|
||||
const child = spawn(process.execPath, ['--experimental-report',
|
||||
__filename, 'child'],
|
||||
{ cwd: tmpdir.path });
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
assert.strictEqual(code, 0, process_msg + ':' + code);
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1, report_msg);
|
||||
const report = reports[0];
|
||||
helper.validate(report, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' ')
|
||||
});
|
||||
}));
|
||||
}
|
||||
33
test/node-report/test-api-pass-error.js
Normal file
33
test/node-report/test-api-pass-error.js
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
// Testcase for passing an error object to the API call.
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
const assert = require('assert');
|
||||
if (process.argv[2] === 'child') {
|
||||
try {
|
||||
throw new Error('Testing error handling');
|
||||
} catch {
|
||||
process.report.triggerReport();
|
||||
}
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const spawn = require('child_process').spawn;
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
const child = spawn(process.execPath, ['--experimental-report',
|
||||
__filename, 'child'],
|
||||
{ cwd: tmpdir.path });
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
assert.strictEqual(code, 0, process_msg);
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1, report_msg);
|
||||
const report = reports[0];
|
||||
helper.validate(report, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' '),
|
||||
expectedException: 'Testing error handling',
|
||||
});
|
||||
}));
|
||||
}
|
||||
133
test/node-report/test-api-uvhandles.js
Normal file
133
test/node-report/test-api-uvhandles.js
Normal file
@@ -0,0 +1,133 @@
|
||||
'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');
|
||||
udp_socket.bind({});
|
||||
|
||||
// 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(process.report.getReport());
|
||||
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);
|
||||
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 ?
|
||||
'fs.watch() unavailable' :
|
||||
false) };
|
||||
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 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);
|
||||
|
||||
const report = JSON.parse(stdout);
|
||||
let fs = 0;
|
||||
let poll = 0;
|
||||
let process = 0;
|
||||
let timer = 0;
|
||||
let pipe = 0;
|
||||
let tcp = 0;
|
||||
let udp = 0;
|
||||
const fs_msg = 'fs_event not found';
|
||||
const poll_msg = 'poll_event not found';
|
||||
const process_msg = 'process event not found';
|
||||
const timer_msg = 'timer event not found';
|
||||
const pipe_msg = 'pipe event not found';
|
||||
const tcp_msg = 'tcp event not found';
|
||||
const udp_msg = 'udp event not found';
|
||||
for (const entry in report.libuv) {
|
||||
if (report.libuv[entry].type === 'fs_event') fs = 1;
|
||||
else if (report.libuv[entry].type === 'fs_poll') poll = 1;
|
||||
else if (report.libuv[entry].type === 'process') process = 1;
|
||||
else if (report.libuv[entry].type === 'timer') timer = 1;
|
||||
else if (report.libuv[entry].type === 'pipe') pipe = 1;
|
||||
else if (report.libuv[entry].type === 'tcp') tcp = 1;
|
||||
else if (report.libuv[entry].type === 'udp') udp = 1;
|
||||
}
|
||||
assert.deepStrictEqual(fs, 1, fs_msg);
|
||||
assert.deepStrictEqual(poll, 1, poll_msg);
|
||||
assert.deepStrictEqual(process, 1, process_msg);
|
||||
assert.deepStrictEqual(timer, 1, timer_msg);
|
||||
assert.deepStrictEqual(pipe, 1, pipe_msg);
|
||||
assert.deepStrictEqual(tcp, 1, tcp_msg);
|
||||
assert.deepStrictEqual(udp, 1, udp_msg);
|
||||
|
||||
// Common report tests.
|
||||
helper.validateContent(stdout, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' ')
|
||||
});
|
||||
}));
|
||||
}
|
||||
29
test/node-report/test-api.js
Normal file
29
test/node-report/test-api.js
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
// Testcase to produce report via API call
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
if (process.argv[2] === 'child') {
|
||||
process.report.triggerReport();
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const spawn = require('child_process').spawn;
|
||||
const assert = require('assert');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
|
||||
const child = spawn(process.execPath,
|
||||
['--experimental-report', __filename, 'child'],
|
||||
{ cwd: tmpdir.path });
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
assert.strictEqual(code, 0, process_msg + ':' + code);
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1, report_msg);
|
||||
const report = reports[0];
|
||||
helper.validate(report, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' ')
|
||||
});
|
||||
}));
|
||||
}
|
||||
44
test/node-report/test-exception.js
Normal file
44
test/node-report/test-exception.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
// Testcase to produce report on uncaught exception
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
if (process.argv[2] === 'child') {
|
||||
function myException(request, response) {
|
||||
const m = '*** test-exception.js: throwing uncaught Error';
|
||||
throw new Error(m);
|
||||
}
|
||||
|
||||
myException();
|
||||
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
const spawn = require('child_process').spawn;
|
||||
const assert = require('assert');
|
||||
|
||||
const child = spawn(process.execPath,
|
||||
['--experimental-report',
|
||||
'--diagnostic-report-uncaught-exception',
|
||||
__filename, 'child'],
|
||||
{ cwd: tmpdir.path });
|
||||
// Capture stderr output from the child process
|
||||
let stderr = '';
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
assert.strictEqual(code, 1, process_msg + ':' + code);
|
||||
assert.ok(new RegExp('myException').test(stderr),
|
||||
'Check for expected stack trace frame in stderr');
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1, report_msg);
|
||||
const report = reports[0];
|
||||
helper.validate(report, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' ')
|
||||
});
|
||||
}));
|
||||
}
|
||||
44
test/node-report/test-fatal-error.js
Normal file
44
test/node-report/test-fatal-error.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
const assert = require('assert');
|
||||
// Testcase to produce report on fatal error (javascript heap OOM)
|
||||
if (process.argv[2] === 'child') {
|
||||
|
||||
const list = [];
|
||||
while (true) {
|
||||
const record = new MyRecord();
|
||||
list.push(record);
|
||||
}
|
||||
|
||||
function MyRecord() {
|
||||
this.name = 'foo';
|
||||
this.id = 128;
|
||||
this.account = 98454324;
|
||||
}
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
const spawn = require('child_process').spawn;
|
||||
const args = ['--experimental-report',
|
||||
'--diagnostic-report-on-fatalerror',
|
||||
'--max-old-space-size=20',
|
||||
__filename,
|
||||
'child'];
|
||||
const child = spawn(process.execPath, args, { cwd: tmpdir.path });
|
||||
child.on('exit', common.mustCall((code) => {
|
||||
assert.notStrictEqual(code, 0, 'Process exited unexpectedly');
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.strictEqual(reports.length, 1);
|
||||
const report = reports[0];
|
||||
const options = { pid: child.pid };
|
||||
// Node.js currently overwrites the command line on AIX
|
||||
// https://github.com/nodejs/node/issues/10607
|
||||
if (!(common.isAIX || common.isSunOS)) {
|
||||
options.commandline = child.spawnargs.join(' ');
|
||||
}
|
||||
helper.validate(report, options);
|
||||
}));
|
||||
}
|
||||
80
test/node-report/test-signal.js
Normal file
80
test/node-report/test-signal.js
Normal file
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
// Testcase to produce report on signal interrupting a js busy-loop,
|
||||
// showing it is interruptible.
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
|
||||
if (common.isWindows) return common.skip('Unsupported on Windows.');
|
||||
|
||||
if (process.argv[2] === 'child') {
|
||||
// Exit on loss of parent process
|
||||
const exit = () => process.exit(2);
|
||||
process.on('disconnect', exit);
|
||||
|
||||
function busyLoop() {
|
||||
setInterval(() => {
|
||||
const list = [];
|
||||
for (let i = 0; i < 1e3; i++) {
|
||||
for (let j = 0; j < 1000; j++) {
|
||||
list.push(new MyRecord());
|
||||
}
|
||||
for (let k = 0; k < 1000; k++) {
|
||||
list[k].id += 1;
|
||||
list[k].account += 2;
|
||||
}
|
||||
for (let l = 0; l < 1000; l++) {
|
||||
list.pop();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function MyRecord() {
|
||||
this.name = 'foo';
|
||||
this.id = 128;
|
||||
this.account = 98454324;
|
||||
}
|
||||
process.send('child started', busyLoop);
|
||||
|
||||
|
||||
} else {
|
||||
const helper = require('../common/report.js');
|
||||
const fork = require('child_process').fork;
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
tmpdir.refresh();
|
||||
const assert = require('assert');
|
||||
if (common.isWindows) {
|
||||
assert.fail('Unsupported on Windows', { skip: true });
|
||||
return;
|
||||
}
|
||||
console.log(tmpdir.path);
|
||||
const options = { stdio: 'pipe', encoding: 'utf8', cwd: tmpdir.path };
|
||||
const child = fork('--experimental-report',
|
||||
['--diagnostic-report-on-signal', __filename, 'child'],
|
||||
options);
|
||||
// Wait for child to indicate it is ready before sending signal
|
||||
child.on('message', () => child.kill('SIGUSR2'));
|
||||
let stderr = '';
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += chunk;
|
||||
// Terminate the child after the report has been written
|
||||
if (stderr.includes('Node.js report completed')) {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
});
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
console.log('child exited');
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
const signal_msg = 'Process exited with unexpected signal';
|
||||
assert.strictEqual(code, null, process_msg + ':' + code);
|
||||
assert.deepStrictEqual(signal, 'SIGTERM',
|
||||
signal_msg + ':' + signal);
|
||||
const reports = helper.findReports(child.pid, tmpdir.path);
|
||||
assert.deepStrictEqual(reports.length, 1, report_msg);
|
||||
const report = reports[0];
|
||||
helper.validate(report, { pid: child.pid,
|
||||
commandline: child.spawnargs.join(' ')
|
||||
});
|
||||
}));
|
||||
}
|
||||
6
test/node-report/testcfg.py
Normal file
6
test/node-report/testcfg.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys, os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
import testpy
|
||||
|
||||
def GetConfiguration(context, root):
|
||||
return testpy.ParallelTestConfiguration(context, root, 'node-report')
|
||||
Reference in New Issue
Block a user