feat(tstest): Enhance tstest with fluent API, suite grouping, tag filtering, fixture & snapshot testing, and parallel execution improvements

This commit is contained in:
2025-05-16 00:21:32 +00:00
parent 1c5cf46ba9
commit 2b01d949f2
30 changed files with 1504 additions and 173 deletions

View File

@@ -15,6 +15,7 @@ export class TsTest {
public testDir: TestDirectory;
public executionMode: TestExecutionMode;
public logger: TsTestLogger;
public filterTags: string[];
public smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash',
@@ -25,53 +26,81 @@ export class TsTest {
public tsbundleInstance = new plugins.tsbundle.TsBundle();
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}) {
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = []) {
this.executionMode = executionModeArg;
this.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg);
this.logger = new TsTestLogger(logOptions);
this.filterTags = tags;
}
async run() {
const fileNamesToRun: string[] = await this.testDir.getTestFilePathArray();
const testGroups = await this.testDir.getTestFileGroups();
const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()];
// Log test discovery
this.logger.testDiscovery(
fileNamesToRun.length,
allFiles.length,
this.testDir.testPath,
this.executionMode
);
const tapCombinator = new TapCombinator(this.logger); // lets create the TapCombinator
let fileIndex = 0;
for (const fileNameArg of fileNamesToRun) {
// Execute serial tests first
for (const fileNameArg of testGroups.serial) {
fileIndex++;
switch (true) {
case process.env.CI && fileNameArg.includes('.nonci.'):
this.logger.tapOutput(`Skipping ${fileNameArg} - marked as non-CI`);
break;
case fileNameArg.endsWith('.browser.ts') || fileNameArg.endsWith('.browser.nonci.ts'):
const tapParserBrowser = await this.runInChrome(fileNameArg, fileIndex, fileNamesToRun.length);
tapCombinator.addTapParser(tapParserBrowser);
break;
case fileNameArg.endsWith('.both.ts') || fileNameArg.endsWith('.both.nonci.ts'):
this.logger.sectionStart('Part 1: Chrome');
const tapParserBothBrowser = await this.runInChrome(fileNameArg, fileIndex, fileNamesToRun.length);
tapCombinator.addTapParser(tapParserBothBrowser);
this.logger.sectionEnd();
this.logger.sectionStart('Part 2: Node');
const tapParserBothNode = await this.runInNode(fileNameArg, fileIndex, fileNamesToRun.length);
tapCombinator.addTapParser(tapParserBothNode);
this.logger.sectionEnd();
break;
default:
const tapParserNode = await this.runInNode(fileNameArg, fileIndex, fileNamesToRun.length);
tapCombinator.addTapParser(tapParserNode);
break;
await this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator);
}
// Execute parallel groups sequentially
const groupNames = Object.keys(testGroups.parallelGroups).sort();
for (const groupName of groupNames) {
const groupFiles = testGroups.parallelGroups[groupName];
if (groupFiles.length > 0) {
this.logger.sectionStart(`Parallel Group: ${groupName}`);
// Run all tests in this group in parallel
const parallelPromises = groupFiles.map(async (fileNameArg) => {
fileIndex++;
return this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator);
});
await Promise.all(parallelPromises);
this.logger.sectionEnd();
}
}
tapCombinator.evaluate();
}
private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) {
switch (true) {
case process.env.CI && fileNameArg.includes('.nonci.'):
this.logger.tapOutput(`Skipping ${fileNameArg} - marked as non-CI`);
break;
case fileNameArg.endsWith('.browser.ts') || fileNameArg.endsWith('.browser.nonci.ts'):
const tapParserBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBrowser);
break;
case fileNameArg.endsWith('.both.ts') || fileNameArg.endsWith('.both.nonci.ts'):
this.logger.sectionStart('Part 1: Chrome');
const tapParserBothBrowser = await this.runInChrome(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBothBrowser);
this.logger.sectionEnd();
this.logger.sectionStart('Part 2: Node');
const tapParserBothNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserBothNode);
this.logger.sectionEnd();
break;
default:
const tapParserNode = await this.runInNode(fileNameArg, fileIndex, totalFiles);
tapCombinator.addTapParser(tapParserNode);
break;
}
}
public async runInNode(fileNameArg: string, index: number, total: number): Promise<TapParser> {
this.logger.testFileStart(fileNameArg, 'node.js', index, total);
@@ -82,6 +111,11 @@ export class TsTest {
if (process.argv.includes('--web')) {
tsrunOptions += ' --web';
}
// Set filter tags as environment variable
if (this.filterTags.length > 0) {
process.env.TSTEST_FILTER_TAGS = this.filterTags.join(',');
}
const execResultStreaming = await this.smartshellInstance.execStreamingSilent(
`tsrun ${fileNameArg}${tsrunOptions}`