peter
Version:
Peter Test Framework
165 lines (139 loc) • 4.74 kB
JavaScript
/**
* @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;
}