feat(cli): Add --startFrom and --stopAt options to filter test files by range
This commit is contained in:
		| @@ -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. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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' | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								ts/index.ts
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								ts/index.ts
									
									
									
									
									
								
							| @@ -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(); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user