@momsfriendlydevco/testa
Version:
Low-overhead, parallel-first testkit harness
123 lines (107 loc) • 4.42 kB
JavaScript
":" //# comment; exec /usr/bin/env node --no-warnings "$0" "$@"
// ^^^ Horrible kludge to get Node to STFU about experiemental feature warnings
import {glob} from 'node:fs/promises';
import {join as pathJoin} from 'node:path'; // eslint-disable-line unicorn/import-style
import {program} from 'commander'
import {regexpEscape} from './lib/utils.js';
import TestaBase from '#testa/base';
// Parse Command line
let args = program
.name('testa')
.description('Run testkits in parallel with dependencies')
.argument('[files...]')
.option('-l, --list', 'List all queued tests and exit')
.option('-b, --bail', 'Stop processing on the first error (implies `--serial`)')
.option('-s, --serial', 'Force run tests in serial (alias of `--limit 1`)')
.option('-p, --parallel <number>', 'Set number of tests to run in parallel', 5)
.option('-g, --grep <expression>', 'Add a grep expression filter for tests titles + IDs (can be specified multiple times)', (v, t) => t.concat([v]), [])
.option('-G, --invert-grep <expression>', 'Add an inverted grep expression filter for tests titles + IDs (can be specified multiple times)', (v, t) => t.concat([v]), [])
.option('-f, --fgrep <expression>', 'Add a raw string expression filter for tests titles + IDs (can be specified multiple times)', (v, t) => t.concat([v]), [])
.option('-F, --invert-fgrep <expression>', 'Add an inverted raw string expression filter for tests titles + IDs (can be specified multiple times)', (v, t) => t.concat([v]), [])
.option('--slow [timestring]', 'Set the amount of time before a test is considered slow to resolve. Can be any valid timestring', '75ms')
.option('--timeout [timestring]', 'Set the amount of time before a test times out. Can be any valid timestring', '2s')
.option('--ui [ui]', 'Set the UI environment to use', 'bdd')
.option('--debug', 'Turn on various internal debugging output')
.parse(process.argv);
args = { // Flatten into POJO of option keys + `args:Array<String>`
...args.opts(),
args: args.args,
};
// Populate options
if (args.bail) Object.assign(args, {serial: true});
if (args.serial) Object.assign(args, {parallel: 1});
TestaBase.basePath = process.cwd();
TestaBase.concurrency = args.parallel;
TestaBase.debug = !! args.debug;
TestaBase.bail = !! args.bail;
TestaBase.slow = args.slow;
TestaBase.timeout = args.timeout;
// Process --grep [expr] / --invert-grep [expr] {{{
if (args.grep.length > 0) {
args.grep.forEach(g => {
let re = new RegExp(g, 'i');
if (TestaBase.debug) console.log('Using test Grep filter:', re.toString());
TestaBase.filters.push(test =>
re.test(test._id) || re.test(test._title)
);
});
}
if (args.invertGrep.length > 0) {
args.invertGrep.forEach(g => {
let re = new RegExp(g, 'i');
if (TestaBase.debug) console.log('Using test Inverted Grep filter:', re.toString());
TestaBase.filters.push(test =>
!re.test(test._id) && !re.test(test._title)
);
});
}
// }}}
// Process --fgrep [str] / --invert-fgrep {{{
if (args.fgrep.length > 0) {
args.fgrep.forEach(g => {
let re = new RegExp(regexpEscape(g), 'i');
if (TestaBase.debug) console.log('Using test Fgrep filter:', re.toString());
TestaBase.filters.push(test =>
re.test(test._id) || re.test(test._title)
);
});
}
if (args.invertFgrep.length > 0) {
args.invertFgrep.forEach(g => {
let re = new RegExp(regexpEscape(g), 'i');
if (TestaBase.debug) console.log('Using test Inverted Fgrep filter:', re.toString());
TestaBase.filters.push(test =>
!re.test(test._id) && !re.test(test._title)
);
});
}
// }}}
// Calculate what files to import
let testFiles = args.args.length > 0
? args.args // Provided a list - use that
: await Array.fromAsync(glob('test/*.js')); // Default to globbing from process.cwd()
// Check we have at least one file included
if (testFiles.length == 0)
throw new Error('No testkit files to run');
// Import all provided files
await Promise.all(
testFiles.map(path =>
import(pathJoin(process.cwd(), path))
)
);
if (args.list) { // LIST MODE - List tests and exit
console.log('Testa Tests:');
TestaBase.
getFiltered()
.forEach(test => {
console.log('-', test.toString());
});
process.exitCode = 0;
} else { // RUN MODE - Actually run tests
// Load the UI
let {default: ui} = await import(`./ui/${args.ui}.js`);
await ui({TestaBase});
process.exitCode = 0;
}
// NOTE: We wait for promises to resolve before gracefully exiting