UNPKG

peter

Version:
165 lines (139 loc) 4.74 kB
/** * @file src/runner.js * @author Ryan Rossiter, ryan@kingsds.network * @date July 2020 * * This class module is used to handle running the test files provided * to it. It manages how many tests are running concurrently and in * what order they are run in. */ const path = require('path'); const testLogWrapper = require('./utils/test-log-wrapper'); const { getDriver } = require('./drivers'); const ConcurrencyLockPool = require('./utils/concurrency-lock-pool'); const deferPromise = require('./utils/defer-promise'); const isWin = require('os').platform() === 'win32'; /** * @typedef { import('./drivers').TestDriverResult } TestDriverResult */ /** * @typedef {object} RunnerOptions * @property {number} concurrency * @property {boolean} verbose * @property {boolean} debug * @property {number} timeout * @property {number} repeat * @property {string[]} childArgv *//** * @typedef {object} TestResult * @property {string} testFile * @property {() => Promise<TestDriverResult>} resolveResult */ module.exports = class Runner { /** * @constructor * @param {string[]} testFiles * @param {RunnerOptions} options */ constructor(testFiles, options) { this.testFiles = testFiles; this.concurrencyLockPool = new ConcurrencyLockPool(Number(options.concurrency)); this.testNum = 0; this.options = options; } mkTestLabelSuffix(repetition) { if (!this.options.repeat) return ''; const digits = Math.floor(Math.log(this.testFiles.length) / Math.log(10)) + 1; return `-#${String(repetition).padEnd(digits, ' ')}`; } /** * @param {string} testFile * @param {string} prefix portion of filename common to all tests * * @returns {Promise<TestDriverResult>} */ runTestFile(testPath, repetition, commonDetails) { const testLabelSuffix = this.mkTestLabelSuffix(repetition); const testFile = (isWin ? path.win32 : path).resolve(testPath); const knownFailure = testFile.endsWith('.failing'); // Immediate logging options. Tests that are expected to pass that fail will have their stderr dumped if no quiet flag const immediateLogging = { 'log': Boolean(this.options.verbose), 'error': Boolean(this.options.verbose), } const testInfo = {}; testInfo.env = Object.assign({ NODE_PATH: path.resolve(__dirname, '../node_modules') }, process.env); testInfo.testNum = this.testNum++; testInfo.invert = path.basename(testFile)[0] === '¬'; testInfo.failing = knownFailure; testInfo.commonDetails = commonDetails; testInfo.immediateLogging = immediateLogging; const testData = Object.assign({}, commonDetails, testInfo, this.options, { testFile, testLabelSuffix }); var ext; if (knownFailure) ext = path.extname(testFile.slice(0,-8)).slice(1); else ext = path.extname(testFile).slice(1); const testPromise = getDriver(ext).run(testFile, testData); return testLogWrapper(testPromise, testData); } /** * @returns {Promise<TestResult[]>} */ async start() { /** @type {TestResult[]} */ const testResults = []; const prefix = longestCommonPrefix(this.testFiles); const templateSuffix = this.options.repeat ? this.options.repeat : 0; const largestSuffix = this.mkTestLabelSuffix(templateSuffix).length; const maxLength = longestLength(this.testFiles); + largestSuffix; for (let i = 0; i < this.testFiles.length; i++) { const repetition = i % ((this.options.repeat || 0) + 1); const testFile = this.testFiles[i]; const releaseLock = await this.concurrencyLockPool.acquire(); let testPromise = this.runTestFile(testFile, repetition, { prefix, maxLength }); testPromise .catch((error) => { console.error(`error in ${testFile}`, error) }) .finally(releaseLock); testResults.push({ testFile, resolveResult: deferPromise(testPromise), }); } await this.concurrencyLockPool.waitUntilEmpty(); return testResults; } } function longestLength(list) { var longest = 0; for (let element of list) if (element.length > longest) longest = element.length; return longest; } function longestCommonPrefix(list) { var prefix = list[0]; for (let element of list) { if (element.startsWith(prefix)) continue; for (let i=0; i < prefix.length; i++) { if (element[i] != prefix[i]) { if(prefix.slice(i, prefix.length).includes('/')) prefix = prefix.slice(0, i); else prefix = prefix.slice(0, prefix.lastIndexOf('/') + 1); if (prefix.length === 0) return ''; } } } return prefix; }