peter
Version:
Peter Test Framework
153 lines (132 loc) • 4.48 kB
JavaScript
/**
* @file src/drivers/tap.js
* @author Ryan Rossiter, ryan@kingsds.network
* @date July 2020
*
* This test driver executes .tap test suites.
*/
;
const TapParser = require('tap-parser');
const { spawn } = require('child_process');
const path = require('path');
const chalk = require('chalk');
const { registerDriver } = require('.');
const logStream = require('../utils/log-stream');
const debug = require('debug');
const { setTestTimeout } = require('../utils/test-timer');
/**
* Table of all living tap test child processes.
*
* Each time the process::CLEANUP event emitted, we kill every process in
* this table. Additionally, we hold an infinitely-long timer whenever
* the process count here is non-zero.
*/
const processTable = {};
// Kill every living process in the process table when the CLEANUP event is emitted
process.on('CLEANUP', () => {
for (const [pid, processData] of Object.entries(processTable)) {
// Remove processes from the process table that have exited
if (processData.testProcess.exitCode !== null) {
delete processTable[pid];
continue;
}
// Ensure output will be visible
if (!processData.options.quiet)
{
new logStream.LogStream({
stream: processData.testProcess.stdout,
name: chalk.yellow(processData.testPath),
immediate: true
});
}
processData.testProcess.exitCode !== null && processData.testProcess.kill();
setImmediate(() => processData.testProcess.exitCode !== null && processData.testProcess.kill(9));
delete processTable[pid];
}
return false;
});
registerDriver('tap', {
async run(testPath, options) {
var testProcess, testExitCode, result;
if (options.invert)
throw new Error(
`Cannot invert .tap test with '¬'. Please rename '${testPath}'.`,
);
const parser = new TapParser({ passes: true });
testProcess = spawn(
process.execPath,
['--unhandled-rejections=strict', testPath, ...options.childArgv],
{
env: options.env,
},
);
const streams = logStream.captureTest(testProcess, testPath, options);
let timeoutExceeded = null;
const testTimeoutStart = Date.now();
setTestTimeout(testProcess, testPath, options.timeout, testTimeoutStart).then((time) => { timeoutExceeded = time });
let stderr = '';
testProcess.stderr.on('readable', () => {
let chunk;
while ((chunk = testProcess.stderr.read()) !== null)
stderr += chunk.toString('utf8');
});
await new Promise((resolve, reject) => {
var done = false;
testProcess.on('error', (err) => {
if (done)
return;
done = true;
testExitCode = 1;
resolve(err || 1);
});
testProcess.on('close', (exitCode) => {
if (done)
return;
done = true;
testExitCode = exitCode;
resolve(exitCode);
});
processTable[testProcess.pid] = {
testProcess,
options,
testPath,
stdoutLog: streams[0],
stderrLog: streams[1],
};
testProcess.stdout.pipe(parser);
parser.on('complete', (parseResults) => {
result = parseResults;
const ansiCsRe = /\u001b\[[\u0030-\u003f]*[\u0020-\u002f]*[\u0040-u\007e]/g; /* Matches ANSI Control Sequences */
if (timeoutExceeded)
{
result.failures.push({
name: 'Tap test failed',
id: result.failures.length + 1,
stderr: stderr.length ? stderr.replace(ansiCsRe, '').split(/\r?\n/) : undefined,
diag: {
at: 'timeout',
operator: 'lt',
expected: `${options.timeout}s`,
actual: timeoutExceeded + 's',
},
});
}
else if (testExitCode || result.count === 0) /* count is 0 when no tap tests within testfile, or zora module is not available */
{
result.failures.push({
name: 'Tap test failed' + (options.failing ? '(expected)' : ''),
id: result.failures.length + 1,
stderr: stderr.length ? stderr.replace(ansiCsRe, '').split(/\r?\n/) : undefined,
diag: {
at: 'exit code',
operator: 'eq',
expected: 0,
actual: testExitCode,
},
});
}
});
});
return { result, streams, failureTypes: { timeoutExceeded, testExitCode } };
},
});