feat(cli): Add --startFrom and --stopAt options to filter test files by range
This commit is contained in:
		| @@ -1,5 +1,13 @@ | ||||
| # 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) | ||||
| 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. | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tstest', | ||||
|   version: '1.9.4', | ||||
|   version: '1.10.0', | ||||
|   description: 'a test utility to run tests that match test/**/*.ts' | ||||
| } | ||||
|   | ||||
							
								
								
									
										45
									
								
								ts/index.ts
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								ts/index.ts
									
									
									
									
									
								
							| @@ -13,6 +13,8 @@ export const runCli = async () => { | ||||
|   const logOptions: LogOptions = {}; | ||||
|   let testPath: string | null = null; | ||||
|   let tags: string[] = []; | ||||
|   let startFromFile: number | null = null; | ||||
|   let stopAtFile: number | null = null; | ||||
|    | ||||
|   // Parse options | ||||
|   for (let i = 0; i < args.length; i++) { | ||||
| @@ -42,6 +44,32 @@ export const runCli = async () => { | ||||
|           tags = args[++i].split(','); | ||||
|         } | ||||
|         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: | ||||
|         if (!arg.startsWith('-')) { | ||||
|           testPath = arg; | ||||
| @@ -49,6 +77,12 @@ 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) { | ||||
|     console.error('You must specify a test directory/file/pattern as argument. Please try again.'); | ||||
|     console.error('\nUsage: tstest <path> [options]'); | ||||
| @@ -58,7 +92,9 @@ export const runCli = async () => { | ||||
|     console.error('  --no-color        Disable colored output'); | ||||
|     console.error('  --json            Output results as JSON'); | ||||
|     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); | ||||
|   } | ||||
|    | ||||
| @@ -73,6 +109,11 @@ export const runCli = async () => { | ||||
|     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(); | ||||
| }; | ||||
|  | ||||
| // Execute CLI when this file is run directly | ||||
| if (import.meta.url === `file://${process.argv[1]}`) { | ||||
|   runCli(); | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,8 @@ export class TsTest { | ||||
|   public executionMode: TestExecutionMode; | ||||
|   public logger: TsTestLogger; | ||||
|   public filterTags: string[]; | ||||
|   public startFromFile: number | null; | ||||
|   public stopAtFile: number | null; | ||||
|  | ||||
|   public smartshellInstance = new plugins.smartshell.Smartshell({ | ||||
|     executor: 'bash', | ||||
| @@ -26,16 +28,35 @@ export class TsTest { | ||||
|  | ||||
|   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.testDir = new TestDirectory(cwdArg, testPathArg, executionModeArg); | ||||
|     this.logger = new TsTestLogger(logOptions); | ||||
|     this.filterTags = tags; | ||||
|     this.startFromFile = startFromFile; | ||||
|     this.stopAtFile = stopAtFile; | ||||
|   } | ||||
|  | ||||
|   async run() { | ||||
|     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 | ||||
|     this.logger.testDiscovery( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user