import * as plugins from './tstest.plugins.js'; import * as paths from './tstest.paths.js'; import { SmartFile } from '@push.rocks/smartfile'; import { TestExecutionMode } from './index.js'; // tap related stuff import { TapCombinator } from './tstest.classes.tap.combinator.js'; import { TapParser } from './tstest.classes.tap.parser.js'; import { TapTestResult } from './tstest.classes.tap.testresult.js'; export class TestDirectory { /** * the current working directory */ cwd: string; /** * the test path or pattern */ testPath: string; /** * the execution mode */ executionMode: TestExecutionMode; /** * an array of Smartfiles */ testfileArray: SmartFile[] = []; /** * the constructor for TestDirectory * @param cwdArg - the current working directory * @param testPathArg - the test path/pattern * @param executionModeArg - the execution mode */ constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode) { this.cwd = cwdArg; this.testPath = testPathArg; this.executionMode = executionModeArg; } private async _init() { switch (this.executionMode) { case TestExecutionMode.FILE: // Single file mode const filePath = plugins.path.isAbsolute(this.testPath) ? this.testPath : plugins.path.join(this.cwd, this.testPath); if (await plugins.smartfile.fs.fileExists(filePath)) { this.testfileArray = [await plugins.smartfile.SmartFile.fromFilePath(filePath)]; } else { throw new Error(`Test file not found: ${filePath}`); } break; case TestExecutionMode.GLOB: // Glob pattern mode - use listFileTree which supports glob patterns const globPattern = this.testPath; const matchedFiles = await plugins.smartfile.fs.listFileTree(this.cwd, globPattern); this.testfileArray = await Promise.all( matchedFiles.map(async (filePath) => { const absolutePath = plugins.path.isAbsolute(filePath) ? filePath : plugins.path.join(this.cwd, filePath); return await plugins.smartfile.SmartFile.fromFilePath(absolutePath); }) ); break; case TestExecutionMode.DIRECTORY: // Directory mode - now recursive with ** pattern const dirPath = plugins.path.join(this.cwd, this.testPath); const testPattern = '**/test*.ts'; const testFiles = await plugins.smartfile.fs.listFileTree(dirPath, testPattern); this.testfileArray = await Promise.all( testFiles.map(async (filePath) => { const absolutePath = plugins.path.isAbsolute(filePath) ? filePath : plugins.path.join(dirPath, filePath); return await plugins.smartfile.SmartFile.fromFilePath(absolutePath); }) ); break; } } async getTestFilePathArray() { await this._init(); const testFilePaths: string[] = []; for (const testFile of this.testfileArray) { // Use the path directly from the SmartFile testFilePaths.push(testFile.path); } return testFilePaths; } /** * Get test files organized by parallel execution groups * @returns An object with grouped tests */ async getTestFileGroups(): Promise<{ serial: string[]; parallelGroups: { [groupName: string]: string[] }; }> { await this._init(); const result = { serial: [] as string[], parallelGroups: {} as { [groupName: string]: string[] } }; for (const testFile of this.testfileArray) { const filePath = testFile.path; const fileName = plugins.path.basename(filePath); // Check if file has parallel group pattern const parallelMatch = fileName.match(/\.para__(\d+)\./); if (parallelMatch) { const groupNumber = parallelMatch[1]; const groupName = `para__${groupNumber}`; if (!result.parallelGroups[groupName]) { result.parallelGroups[groupName] = []; } result.parallelGroups[groupName].push(filePath); } else { // File runs serially result.serial.push(filePath); } } return result; } }