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 | # 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) | ## 2025-05-23 - 1.10.0 - feat(cli) | ||||||
| Add --startFrom and --stopAt options to filter test files by range | Add --startFrom and --stopAt options to filter test files by range | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@git.zone/tstest', |   name: '@git.zone/tstest', | ||||||
|   version: '1.10.0', |   version: '1.10.1', | ||||||
|   description: 'a test utility to run tests that match test/**/*.ts' |   description: 'a test utility to run tests that match test/**/*.ts' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { TsTestLogger } from './tstest.logging.js'; | |||||||
|  |  | ||||||
| export class TapCombinator { | export class TapCombinator { | ||||||
|   tapParserStore: TapParser[] = []; |   tapParserStore: TapParser[] = []; | ||||||
|  |   skippedFiles: string[] = []; | ||||||
|   private logger: TsTestLogger; |   private logger: TsTestLogger; | ||||||
|    |    | ||||||
|   constructor(logger: TsTestLogger) { |   constructor(logger: TsTestLogger) { | ||||||
| @@ -19,10 +20,14 @@ export class TapCombinator { | |||||||
|   addTapParser(tapParserArg: TapParser) { |   addTapParser(tapParserArg: TapParser) { | ||||||
|     this.tapParserStore.push(tapParserArg); |     this.tapParserStore.push(tapParserArg); | ||||||
|   } |   } | ||||||
|  |    | ||||||
|  |   addSkippedFile(filename: string) { | ||||||
|  |     this.skippedFiles.push(filename); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   evaluate() { |   evaluate() { | ||||||
|     // Call the logger's summary method |     // Call the logger's summary method with skipped files | ||||||
|     this.logger.summary(); |     this.logger.summary(this.skippedFiles); | ||||||
|      |      | ||||||
|     // Check for failures |     // Check for failures | ||||||
|     let failGlobal = false; |     let failGlobal = false; | ||||||
|   | |||||||
| @@ -39,26 +39,9 @@ export class TsTest { | |||||||
|  |  | ||||||
|   async run() { |   async run() { | ||||||
|     const testGroups = await this.testDir.getTestFileGroups(); |     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 |     // Log test discovery - always show full count | ||||||
|     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( |     this.logger.testDiscovery( | ||||||
|       allFiles.length,  |       allFiles.length,  | ||||||
|       this.testDir.testPath, |       this.testDir.testPath, | ||||||
| @@ -71,7 +54,7 @@ export class TsTest { | |||||||
|     // Execute serial tests first |     // Execute serial tests first | ||||||
|     for (const fileNameArg of testGroups.serial) { |     for (const fileNameArg of testGroups.serial) { | ||||||
|       fileIndex++; |       fileIndex++; | ||||||
|       await this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator); |       await this.runSingleTestOrSkip(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Execute parallel groups sequentially |     // Execute parallel groups sequentially | ||||||
| @@ -85,7 +68,7 @@ export class TsTest { | |||||||
|         // Run all tests in this group in parallel |         // Run all tests in this group in parallel | ||||||
|         const parallelPromises = groupFiles.map(async (fileNameArg) => { |         const parallelPromises = groupFiles.map(async (fileNameArg) => { | ||||||
|           fileIndex++; |           fileIndex++; | ||||||
|           return this.runSingleTest(fileNameArg, fileIndex, allFiles.length, tapCombinator); |           return this.runSingleTestOrSkip(fileNameArg, fileIndex, allFiles.length, tapCombinator); | ||||||
|         }); |         }); | ||||||
|          |          | ||||||
|         await Promise.all(parallelPromises); |         await Promise.all(parallelPromises); | ||||||
| @@ -96,6 +79,24 @@ export class TsTest { | |||||||
|     tapCombinator.evaluate(); |     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) { |   private async runSingleTest(fileNameArg: string, fileIndex: number, totalFiles: number, tapCombinator: TapCombinator) { | ||||||
|     switch (true) { |     switch (true) { | ||||||
|       case process.env.CI && fileNameArg.includes('.nonci.'): |       case process.env.CI && fileNameArg.includes('.nonci.'): | ||||||
|   | |||||||
| @@ -30,8 +30,10 @@ export interface TestSummary { | |||||||
|   totalTests: number; |   totalTests: number; | ||||||
|   totalPassed: number; |   totalPassed: number; | ||||||
|   totalFailed: number; |   totalFailed: number; | ||||||
|  |   totalSkipped: number; | ||||||
|   totalDuration: number; |   totalDuration: number; | ||||||
|   fileResults: TestFileResult[]; |   fileResults: TestFileResult[]; | ||||||
|  |   skippedFiles: string[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class TsTestLogger { | 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 |   // Browser console | ||||||
|   browserConsole(message: string, level: string = 'log') { |   browserConsole(message: string, level: string = 'log') { | ||||||
|     if (this.options.json) { |     if (this.options.json) { | ||||||
| @@ -317,15 +332,17 @@ export class TsTestLogger { | |||||||
|   } |   } | ||||||
|    |    | ||||||
|   // Final summary |   // Final summary | ||||||
|   summary() { |   summary(skippedFiles: string[] = []) { | ||||||
|     const totalDuration = Date.now() - this.startTime; |     const totalDuration = Date.now() - this.startTime; | ||||||
|     const summary: TestSummary = { |     const summary: TestSummary = { | ||||||
|       totalFiles: this.fileResults.length, |       totalFiles: this.fileResults.length + skippedFiles.length, | ||||||
|       totalTests: this.fileResults.reduce((sum, r) => sum + r.total, 0), |       totalTests: this.fileResults.reduce((sum, r) => sum + r.total, 0), | ||||||
|       totalPassed: this.fileResults.reduce((sum, r) => sum + r.passed, 0), |       totalPassed: this.fileResults.reduce((sum, r) => sum + r.passed, 0), | ||||||
|       totalFailed: this.fileResults.reduce((sum, r) => sum + r.failed, 0), |       totalFailed: this.fileResults.reduce((sum, r) => sum + r.failed, 0), | ||||||
|  |       totalSkipped: skippedFiles.length, | ||||||
|       totalDuration, |       totalDuration, | ||||||
|       fileResults: this.fileResults |       fileResults: this.fileResults, | ||||||
|  |       skippedFiles | ||||||
|     }; |     }; | ||||||
|      |      | ||||||
|     if (this.options.json) { |     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(`│ Total Tests:    ${summary.totalTests.toString().padStart(14)} │`, 'white')); | ||||||
|     this.log(this.format(`│ Passed:         ${summary.totalPassed.toString().padStart(14)} │`, 'green')); |     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')); |     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(`│ Duration:       ${totalDuration.toString().padStart(14)}ms │`, 'white')); | ||||||
|     this.log(this.format('└────────────────────────────────┘', 'dim')); |     this.log(this.format('└────────────────────────────────┘', 'dim')); | ||||||
|      |      | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user