UNPKG

peter

Version:
156 lines (137 loc) 5.21 kB
/** * @author Wes Garland, wes@kingsds.network * @date July 2020 * @file src/drivers/bash.js Test driver which executes .bash tests. * * @note These tests must not make use of /dev/fd, /dev/stderr, /dev/stdout, etc. * Redirection should be done via >&2 etc; this is to work around bugs * with NodeJS and pipes and ENXIO. If dependent scripts use these, the test * should create a dummy subshell which redirects these resources to files, * polling them in the background as necessary. * * Tests with lines matching '# peter-timeout: NUMBER" will have the timeout set to NUMBER seconds. */ 'use strict'; const fs = require('fs'); const path = require('path'); const childProcess = require('child_process'); const chalk = require('chalk'); const { registerDriver } = require('.'); const logStream = require('../utils/log-stream'); const { setTestTimeout } = require('../utils/test-timer'); const isWin = require('os').platform() === 'win32'; const bashPath = process.env.BASH || (isWin ? 'bash' : '/bin/bash'); const myPackage = require('../../package.json'); const debug = require('debug'); registerDriver('bash', { async run(testPath, options) { let fullTestPath = (isWin ? path.win32 : path).resolve(testPath); let testContents = await fs.promises.readFile(fullTestPath, { encoding: 'utf-8' }); let testExitCode; var cleanupHandler; const childEnv = { PETER_VERSION: myPackage.version, PETER: require.main.filename, PETER_TEST: fullTestPath, PETER_VERBOSE: String(options.verbose) }; Object.assign(childEnv, options.env); delete childEnv.DEBUG; /* don't pass along debugging of the parent to the children */ /* Figure out the timeouts; test can override command line, env vars override everything */ options = Object.assign({}, options); if ((/^#\s*peter-timeout:\s*/m).test(testContents)) options.timeout = testContents.match(/^#\s*peter-timeout:\s*(.*)/m)[1]; options.timeout = 0 || Number(process.env.PETER_BASH_TIMEOUT) || Number(process.env.PETER_TIMEOUT) || Number(options.timeout); const timeoutMs = (options.timeout * 1000) || undefined; /* 0 => no timeout */ const testProcess = childProcess.execFile(bashPath, [ fullTestPath ], { env: childEnv, timeout: timeoutMs, killSignal: 1, }); let timeoutExceeded = null; const testTimeoutStart = Date.now(); setTestTimeout(testProcess, testPath, options.timeout, testTimeoutStart).then((time) => { timeoutExceeded = time }); const streams = logStream.captureTest(testProcess, testPath, options); 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; cleanupHandler = function cleanupHandler() { if (testProcess) { testProcess.exitCode !== null && testProcess.kill(); setImmediate(() => testProcess.exitCode !== null && testProcess.kill(9)); } } function handleError(error) { if (done) return; testExitCode = 1; done = true; reject(error); } function handleExit(exitCode) { var success; if (done) return; done = true; success = (!timeoutExceeded && exitCode === 0); if (options.invert) success = !success; testExitCode = success ? 0 : 1; resolve(); } process.on('CLEANUP', cleanupHandler); testProcess.on('error', handleError); testProcess.on('exit', handleExit); const stdinFilename = path.join(fullTestPath + '.stdin'); if (fs.existsSync(stdinFilename)) testProcess.stdin.write(fs.readFileSync(stdinFilename), (err) => err && reject(err)); testProcess.stdin.end(); }); const ansiCsRe = /\u001b\[[\u0030-\u003f]*[\u0020-\u002f]*[\u0040-u\007e]/g; /* Matches ANSI Control Sequences */ const failures = []; if (timeoutExceeded) failures.push({ name: 'Test failed', 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) failures.push({ name: 'Test failed' + (options.failing ? '(expected)' : ''), stderr: stderr.length ? stderr.replace(ansiCsRe, '').split(/\r?\n/) : undefined, diag: { at: 'exit code', operator: 'eq', expected: 0, actual: testExitCode, }, }); const result = { type: 'bash', count: 1, pass: testExitCode === 0 ? 1 : 0, fail: testExitCode === 0 ? 0 : 1, skip: 0, ok: testExitCode === 0, failures, }; return { result, streams, failureTypes: { timeoutExceeded, testExitCode } }; }, });