From 763dc89f59a2ded678cc29e4b432c7765d0c7933 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Fri, 23 May 2025 23:18:35 +0000 Subject: [PATCH] fix(tstest): Improve file range filtering and summary logging by skipping test files outside the specified range and reporting them in the final summary. --- changelog.md | 8 ++++++ ts/00_commitinfo_data.ts | 2 +- ts/tstest.classes.tap.combinator.ts | 9 ++++-- ts/tstest.classes.tstest.ts | 43 +++++++++++++++-------------- ts/tstest.logging.ts | 26 +++++++++++++++-- 5 files changed, 61 insertions(+), 27 deletions(-) diff --git a/changelog.md b/changelog.md index d8f6b52..1369934 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index d1459a9..faa8bfc 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -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' } diff --git a/ts/tstest.classes.tap.combinator.ts b/ts/tstest.classes.tap.combinator.ts index d4514b5..d3d33ca 100644 --- a/ts/tstest.classes.tap.combinator.ts +++ b/ts/tstest.classes.tap.combinator.ts @@ -10,6 +10,7 @@ import { TsTestLogger } from './tstest.logging.js'; export class TapCombinator { tapParserStore: TapParser[] = []; + skippedFiles: string[] = []; private logger: TsTestLogger; constructor(logger: TsTestLogger) { @@ -19,10 +20,14 @@ export class TapCombinator { addTapParser(tapParserArg: TapParser) { 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; diff --git a/ts/tstest.classes.tstest.ts b/ts/tstest.classes.tstest.ts index b0221e6..0628ee0 100644 --- a/ts/tstest.classes.tstest.ts +++ b/ts/tstest.classes.tstest.ts @@ -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.'): diff --git a/ts/tstest.logging.ts b/ts/tstest.logging.ts index c0af145..d05a71c 100644 --- a/ts/tstest.logging.ts +++ b/ts/tstest.logging.ts @@ -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'));