fix(tstest): Improve file range filtering and summary logging by skipping test files outside the specified range and reporting them in the final summary.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-05-23 - 1.10.1 - fix(tstest) | ||||
| Improve file range filtering and summary logging by skipping test files outside the specified range and reporting them in the final summary. | ||||
|  | ||||
| - Introduce runSingleTestOrSkip to check file index against startFrom/stopAt values. | ||||
| - Log skipped files with appropriate messages and add them to the summary. | ||||
| - Update the logger to include total skipped files in the test summary. | ||||
| - Add permission settings in .claude/settings.local.json to support new operations. | ||||
|  | ||||
| ## 2025-05-23 - 1.10.0 - feat(cli) | ||||
| Add --startFrom and --stopAt options to filter test files by range | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tstest', | ||||
|   version: '1.10.0', | ||||
|   version: '1.10.1', | ||||
|   description: 'a test utility to run tests that match test/**/*.ts' | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import { TsTestLogger } from './tstest.logging.js'; | ||||
|  | ||||
| export class TapCombinator { | ||||
|   tapParserStore: TapParser[] = []; | ||||
|   skippedFiles: string[] = []; | ||||
|   private logger: TsTestLogger; | ||||
|    | ||||
|   constructor(logger: TsTestLogger) { | ||||
| @@ -20,9 +21,13 @@ export class TapCombinator { | ||||
|     this.tapParserStore.push(tapParserArg); | ||||
|   } | ||||
|    | ||||
|   addSkippedFile(filename: string) { | ||||
|     this.skippedFiles.push(filename); | ||||
|   } | ||||
|  | ||||
|   evaluate() { | ||||
|     // Call the logger's summary method | ||||
|     this.logger.summary(); | ||||
|     // Call the logger's summary method with skipped files | ||||
|     this.logger.summary(this.skippedFiles); | ||||
|      | ||||
|     // Check for failures | ||||
|     let failGlobal = false; | ||||
|   | ||||
| @@ -39,26 +39,9 @@ export class TsTest { | ||||
|  | ||||
|   async run() { | ||||
|     const testGroups = await this.testDir.getTestFileGroups(); | ||||
|     let allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; | ||||
|     const 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 - always show full count | ||||
|     this.logger.testDiscovery( | ||||
|       allFiles.length,  | ||||
|       this.testDir.testPath, | ||||
| @@ -71,7 +54,7 @@ export class TsTest { | ||||
|     // Execute serial tests first | ||||
|     for (const fileNameArg of testGroups.serial) { | ||||
|       fileIndex++; | ||||
|       await this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||
|       await this.runSingleTestOrSkip(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||
|     } | ||||
|      | ||||
|     // Execute parallel groups sequentially | ||||
| @@ -85,7 +68,7 @@ export class TsTest { | ||||
|         // Run all tests in this group in parallel | ||||
|         const parallelPromises = groupFiles.map(async (fileNameArg) => { | ||||
|           fileIndex++; | ||||
|           return this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||
|           return this.runSingleTestOrSkip(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||
|         }); | ||||
|          | ||||
|         await Promise.all(parallelPromises); | ||||
| @@ -96,6 +79,24 @@ export class TsTest { | ||||
|     tapCombinator.evaluate(); | ||||
|   } | ||||
|    | ||||
|   private async runSingleTestOrSkip(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) { | ||||
|     // Check if this file should be skipped based on range | ||||
|     if (this.startFromFile !== null && fileIndex < this.startFromFile) { | ||||
|       this.logger.testFileSkipped(fileNameArg, fileIndex, totalFiles, `before start range (${this.startFromFile})`); | ||||
|       tapCombinator.addSkippedFile(fileNameArg); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     if (this.stopAtFile !== null && fileIndex > this.stopAtFile) { | ||||
|       this.logger.testFileSkipped(fileNameArg, fileIndex, totalFiles, `after stop range (${this.stopAtFile})`); | ||||
|       tapCombinator.addSkippedFile(fileNameArg); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     // File is in range, run it | ||||
|     await this.runSingleTest(fileNameArg, fileIndex, totalFiles, tapCombinator); | ||||
|   } | ||||
|    | ||||
|   private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) { | ||||
|     switch (true) { | ||||
|       case process.env.CI && fileNameArg.includes('.nonci.'): | ||||
|   | ||||
| @@ -30,8 +30,10 @@ export interface TestSummary { | ||||
|   totalTests: number; | ||||
|   totalPassed: number; | ||||
|   totalFailed: number; | ||||
|   totalSkipped: number; | ||||
|   totalDuration: number; | ||||
|   fileResults: TestFileResult[]; | ||||
|   skippedFiles: string[]; | ||||
| } | ||||
|  | ||||
| export class TsTestLogger { | ||||
| @@ -282,6 +284,19 @@ export class TsTestLogger { | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Skipped test file | ||||
|   testFileSkipped(filename: string, index: number, total: number, reason: string) { | ||||
|     if (this.options.json) { | ||||
|       this.logJson({ event: 'fileSkipped', filename, index, total, reason }); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     if (this.options.quiet) return; | ||||
|      | ||||
|     this.log(this.format(`\n⏭️  ${filename} (${index}/${total})`, 'yellow')); | ||||
|     this.log(this.format(`   Skipped: ${reason}`, 'dim')); | ||||
|   } | ||||
|    | ||||
|   // Browser console | ||||
|   browserConsole(message: string, level: string = 'log') { | ||||
|     if (this.options.json) { | ||||
| @@ -317,15 +332,17 @@ export class TsTestLogger { | ||||
|   } | ||||
|    | ||||
|   // Final summary | ||||
|   summary() { | ||||
|   summary(skippedFiles: string[] = []) { | ||||
|     const totalDuration = Date.now() - this.startTime; | ||||
|     const summary: TestSummary = { | ||||
|       totalFiles: this.fileResults.length, | ||||
|       totalFiles: this.fileResults.length + skippedFiles.length, | ||||
|       totalTests: this.fileResults.reduce((sum, r) => sum + r.total, 0), | ||||
|       totalPassed: this.fileResults.reduce((sum, r) => sum + r.passed, 0), | ||||
|       totalFailed: this.fileResults.reduce((sum, r) => sum + r.failed, 0), | ||||
|       totalSkipped: skippedFiles.length, | ||||
|       totalDuration, | ||||
|       fileResults: this.fileResults | ||||
|       fileResults: this.fileResults, | ||||
|       skippedFiles | ||||
|     }; | ||||
|      | ||||
|     if (this.options.json) { | ||||
| @@ -346,6 +363,9 @@ export class TsTestLogger { | ||||
|     this.log(this.format(`│ Total Tests:    ${summary.totalTests.toString().padStart(14)} │`, 'white')); | ||||
|     this.log(this.format(`│ Passed:         ${summary.totalPassed.toString().padStart(14)} │`, 'green')); | ||||
|     this.log(this.format(`│ Failed:         ${summary.totalFailed.toString().padStart(14)} │`, summary.totalFailed > 0 ? 'red' : 'green')); | ||||
|     if (summary.totalSkipped > 0) { | ||||
|       this.log(this.format(`│ Skipped:        ${summary.totalSkipped.toString().padStart(14)} │`, 'yellow')); | ||||
|     } | ||||
|     this.log(this.format(`│ Duration:       ${totalDuration.toString().padStart(14)}ms │`, 'white')); | ||||
|     this.log(this.format('└────────────────────────────────┘', 'dim')); | ||||
|      | ||||
|   | ||||
		Reference in New Issue
	
	Block a user