UNPKG

peter

Version:
153 lines (132 loc) 4.48 kB
/** * @file src/drivers/tap.js * @author Ryan Rossiter, ryan@kingsds.network * @date July 2020 * * This test driver executes .tap test suites. */ 'use strict'; 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 } }; }, });