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:
Philipp Kunz 2025-05-23 23:18:35 +00:00
parent e0d8ede450
commit 763dc89f59
5 changed files with 61 additions and 27 deletions

View File

@ -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

View File

@ -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'
} }

View File

@ -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;

View File

@ -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.'):

View File

@ -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'));