feat(cli): Add --startFrom and --stopAt options to filter test files by range

This commit is contained in:
Philipp Kunz 2025-05-23 23:05:38 +00:00
parent 83b324b09f
commit 27c950c1a1
4 changed files with 80 additions and 10 deletions

View File

@ -1,5 +1,13 @@
# Changelog # Changelog
## 2025-05-23 - 1.10.0 - feat(cli)
Add --startFrom and --stopAt options to filter test files by range
- Introduced CLI options --startFrom and --stopAt in ts/index.ts for selective test execution
- Added validation to ensure provided range values are positive and startFrom is not greater than stopAt
- Propagated file range filtering into test grouping in tstest.classes.tstest.ts, applying the range filter across serial and parallel groups
- Updated usage messages to include the new options
## 2025-05-23 - 1.9.4 - fix(docs) ## 2025-05-23 - 1.9.4 - fix(docs)
Update documentation and configuration for legal notices and CI permissions. This commit adds a new local settings file for tool permissions, refines the legal and trademark sections in the readme, and improves glob test files with clearer log messages. Update documentation and configuration for legal notices and CI permissions. This commit adds a new local settings file for tool permissions, refines the legal and trademark sections in the readme, and improves glob test files with clearer log messages.

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/tstest', name: '@git.zone/tstest',
version: '1.9.4', version: '1.10.0',
description: 'a test utility to run tests that match test/**/*.ts' description: 'a test utility to run tests that match test/**/*.ts'
} }

View File

@ -13,6 +13,8 @@ export const runCli = async () => {
const logOptions: LogOptions = {}; const logOptions: LogOptions = {};
let testPath: string | null = null; let testPath: string | null = null;
let tags: string[] = []; let tags: string[] = [];
let startFromFile: number | null = null;
let stopAtFile: number | null = null;
// Parse options // Parse options
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
@ -42,6 +44,32 @@ export const runCli = async () => {
tags = args[++i].split(','); tags = args[++i].split(',');
} }
break; break;
case '--startFrom':
if (i + 1 < args.length) {
const value = parseInt(args[++i], 10);
if (isNaN(value) || value < 1) {
console.error('Error: --startFrom must be a positive integer');
process.exit(1);
}
startFromFile = value;
} else {
console.error('Error: --startFrom requires a number argument');
process.exit(1);
}
break;
case '--stopAt':
if (i + 1 < args.length) {
const value = parseInt(args[++i], 10);
if (isNaN(value) || value < 1) {
console.error('Error: --stopAt must be a positive integer');
process.exit(1);
}
stopAtFile = value;
} else {
console.error('Error: --stopAt requires a number argument');
process.exit(1);
}
break;
default: default:
if (!arg.startsWith('-')) { if (!arg.startsWith('-')) {
testPath = arg; testPath = arg;
@ -49,16 +77,24 @@ export const runCli = async () => {
} }
} }
// Validate test file range options
if (startFromFile !== null && stopAtFile !== null && startFromFile > stopAtFile) {
console.error('Error: --startFrom cannot be greater than --stopAt');
process.exit(1);
}
if (!testPath) { if (!testPath) {
console.error('You must specify a test directory/file/pattern as argument. Please try again.'); console.error('You must specify a test directory/file/pattern as argument. Please try again.');
console.error('\nUsage: tstest <path> [options]'); console.error('\nUsage: tstest <path> [options]');
console.error('\nOptions:'); console.error('\nOptions:');
console.error(' --quiet, -q Minimal output'); console.error(' --quiet, -q Minimal output');
console.error(' --verbose, -v Verbose output'); console.error(' --verbose, -v Verbose output');
console.error(' --no-color Disable colored output'); console.error(' --no-color Disable colored output');
console.error(' --json Output results as JSON'); console.error(' --json Output results as JSON');
console.error(' --logfile Write logs to .nogit/testlogs/[testfile].log'); console.error(' --logfile Write logs to .nogit/testlogs/[testfile].log');
console.error(' --tags Run only tests with specified tags (comma-separated)'); console.error(' --tags <tags> Run only tests with specified tags (comma-separated)');
console.error(' --startFrom <n> Start running from test file number n');
console.error(' --stopAt <n> Stop running at test file number n');
process.exit(1); process.exit(1);
} }
@ -73,6 +109,11 @@ export const runCli = async () => {
executionMode = TestExecutionMode.DIRECTORY; executionMode = TestExecutionMode.DIRECTORY;
} }
const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode, logOptions, tags); const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode, logOptions, tags, startFromFile, stopAtFile);
await tsTestInstance.run(); await tsTestInstance.run();
}; };
// Execute CLI when this file is run directly
if (import.meta.url === `file://${process.argv[1]}`) {
runCli();
}

View File

@ -16,6 +16,8 @@ export class TsTest {
public executionMode: TestExecutionMode; public executionMode: TestExecutionMode;
public logger: TsTestLogger; public logger: TsTestLogger;
public filterTags: string[]; public filterTags: string[];
public startFromFile: number | null;
public stopAtFile: number | null;
public smartshellInstance = new plugins.smartshell.Smartshell({ public smartshellInstance = new plugins.smartshell.Smartshell({
executor: 'bash', executor: 'bash',
@ -26,16 +28,35 @@ export class TsTest {
public tsbundleInstance = new plugins.tsbundle.TsBundle(); public tsbundleInstance = new plugins.tsbundle.TsBundle();
constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = []) { constructor(cwdArg: string, testPathArg: string, executionModeArg: TestExecutionMode, logOptions: LogOptions = {}, tags: string[] = [], startFromFile: number | null = null, stopAtFile: number | null = null) {
this.executionMode = executionModeArg; this.executionMode = executionModeArg;
this.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg); this.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg);
this.logger = new TsTestLogger(logOptions); this.logger = new TsTestLogger(logOptions);
this.filterTags = tags; this.filterTags = tags;
this.startFromFile = startFromFile;
this.stopAtFile = stopAtFile;
} }
async run() { async run() {
const testGroups = await this.testDir.getTestFileGroups(); const testGroups = await this.testDir.getTestFileGroups();
const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; let allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()];
// Apply file range filtering if specified
if (this.startFromFile !== null || this.stopAtFile !== null) {
const startIndex = this.startFromFile ? this.startFromFile - 1 : 0; // Convert to 0-based index
const endIndex = this.stopAtFile ? this.stopAtFile : allFiles.length;
allFiles = allFiles.slice(startIndex, endIndex);
// Filter the serial and parallel groups based on remaining files
testGroups.serial = testGroups.serial.filter(file => allFiles.includes(file));
Object.keys(testGroups.parallelGroups).forEach(groupName => {
testGroups.parallelGroups[groupName] = testGroups.parallelGroups[groupName].filter(file => allFiles.includes(file));
// Remove empty groups
if (testGroups.parallelGroups[groupName].length === 0) {
delete testGroups.parallelGroups[groupName];
}
});
}
// Log test discovery // Log test discovery
this.logger.testDiscovery( this.logger.testDiscovery(