fix(tstest): Improve timeout warning timer management and summary output formatting in the test runner.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-05-26 - 2.2.6 - fix(tstest) | ||||||
|  | Improve timeout warning timer management and summary output formatting in the test runner. | ||||||
|  |  | ||||||
|  | - Removed the global timeoutWarningTimer and replaced it with local warning timers in runInNode and runInChrome methods. | ||||||
|  | - Added warnings when test files run for over one minute if no timeout is specified. | ||||||
|  | - Ensured proper clearing of warning timers on successful completion or timeout. | ||||||
|  | - Enhanced quiet mode summary output to clearly display passed and failed test counts. | ||||||
|  |  | ||||||
| ## 2025-05-26 - 2.2.5 - fix(protocol) | ## 2025-05-26 - 2.2.5 - fix(protocol) | ||||||
| Fix inline timing metadata parsing and enhance test coverage for performance metrics and timing edge cases | Fix inline timing metadata parsing and enhance test coverage for performance metrics and timing edge cases | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@git.zone/tstest', |   name: '@git.zone/tstest', | ||||||
|   version: '2.2.5', |   version: '2.2.6', | ||||||
|   description: 'a test utility to run tests that match test/**/*.ts' |   description: 'a test utility to run tests that match test/**/*.ts' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ export class TsTest { | |||||||
|   public startFromFile: number | null; |   public startFromFile: number | null; | ||||||
|   public stopAtFile: number | null; |   public stopAtFile: number | null; | ||||||
|   public timeoutSeconds: number | null; |   public timeoutSeconds: number | null; | ||||||
|   private timeoutWarningTimer: NodeJS.Timeout | null = null; |  | ||||||
|  |  | ||||||
|   public smartshellInstance = new plugins.smartshell.Smartshell({ |   public smartshellInstance = new plugins.smartshell.Smartshell({ | ||||||
|     executor: 'bash', |     executor: 'bash', | ||||||
| @@ -45,15 +44,6 @@ export class TsTest { | |||||||
|       await this.movePreviousLogFiles(); |       await this.movePreviousLogFiles(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Start timeout warning timer if no timeout was specified |  | ||||||
|     if (this.timeoutSeconds === null) { |  | ||||||
|       this.timeoutWarningTimer = setTimeout(() => { |  | ||||||
|         this.logger.warning('Test is running for more than 1 minute.'); |  | ||||||
|         this.logger.warning('Consider using --timeout option to set a timeout for test files.'); |  | ||||||
|         this.logger.warning('Example: tstest test --timeout=300 (for 5 minutes)'); |  | ||||||
|       }, 60000); // 1 minute |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const testGroups = await this.testDir.getTestFileGroups(); |     const testGroups = await this.testDir.getTestFileGroups(); | ||||||
|     const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; |     const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; | ||||||
|      |      | ||||||
| @@ -92,12 +82,6 @@ export class TsTest { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Clear the timeout warning timer if it was set |  | ||||||
|     if (this.timeoutWarningTimer) { |  | ||||||
|       clearTimeout(this.timeoutWarningTimer); |  | ||||||
|       this.timeoutWarningTimer = null; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     tapCombinator.evaluate(); |     tapCombinator.evaluate(); | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -272,6 +256,19 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|       execResultStreaming.childProcess.on('error', cleanup); |       execResultStreaming.childProcess.on('error', cleanup); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // Start warning timer if no timeout was specified | ||||||
|  |     let warningTimer: NodeJS.Timeout | null = null; | ||||||
|  |     if (this.timeoutSeconds === null) { | ||||||
|  |       warningTimer = setTimeout(() => { | ||||||
|  |         console.error(''); | ||||||
|  |         console.error(cs('⚠️  WARNING: Test file is running for more than 1 minute', 'orange')); | ||||||
|  |         console.error(cs(`   File: ${fileNameArg}`, 'orange')); | ||||||
|  |         console.error(cs('   Consider using --timeout option to set a timeout for test files.', 'orange')); | ||||||
|  |         console.error(cs('   Example: tstest test --timeout=300 (for 5 minutes)', 'orange')); | ||||||
|  |         console.error(''); | ||||||
|  |       }, 60000); // 1 minute | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // Handle timeout if specified |     // Handle timeout if specified | ||||||
|     if (this.timeoutSeconds !== null) { |     if (this.timeoutSeconds !== null) { | ||||||
|       const timeoutMs = this.timeoutSeconds * 1000; |       const timeoutMs = this.timeoutSeconds * 1000; | ||||||
| @@ -293,6 +290,10 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|         // Clear timeout if test completed successfully |         // Clear timeout if test completed successfully | ||||||
|         clearTimeout(timeoutId); |         clearTimeout(timeoutId); | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|  |         // Clear warning timer if it was set | ||||||
|  |         if (warningTimer) { | ||||||
|  |           clearTimeout(warningTimer); | ||||||
|  |         } | ||||||
|         // Handle timeout error |         // Handle timeout error | ||||||
|         tapParser.handleTimeout(this.timeoutSeconds); |         tapParser.handleTimeout(this.timeoutSeconds); | ||||||
|         // Ensure entire process tree is killed if still running |         // Ensure entire process tree is killed if still running | ||||||
| @@ -307,6 +308,11 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|       await tapParser.handleTapProcess(execResultStreaming.childProcess); |       await tapParser.handleTapProcess(execResultStreaming.childProcess); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // Clear warning timer if it was set | ||||||
|  |     if (warningTimer) { | ||||||
|  |       clearTimeout(warningTimer); | ||||||
|  |     } | ||||||
|  |      | ||||||
|     return tapParser; |     return tapParser; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -425,6 +431,19 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|       } |       } | ||||||
|     ); |     ); | ||||||
|      |      | ||||||
|  |     // Start warning timer if no timeout was specified | ||||||
|  |     let warningTimer: NodeJS.Timeout | null = null; | ||||||
|  |     if (this.timeoutSeconds === null) { | ||||||
|  |       warningTimer = setTimeout(() => { | ||||||
|  |         console.error(''); | ||||||
|  |         console.error(cs('⚠️  WARNING: Test file is running for more than 1 minute', 'orange')); | ||||||
|  |         console.error(cs(`   File: ${fileNameArg}`, 'orange')); | ||||||
|  |         console.error(cs('   Consider using --timeout option to set a timeout for test files.', 'orange')); | ||||||
|  |         console.error(cs('   Example: tstest test --timeout=300 (for 5 minutes)', 'orange')); | ||||||
|  |         console.error(''); | ||||||
|  |       }, 60000); // 1 minute | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // Handle timeout if specified |     // Handle timeout if specified | ||||||
|     if (this.timeoutSeconds !== null) { |     if (this.timeoutSeconds !== null) { | ||||||
|       const timeoutMs = this.timeoutSeconds * 1000; |       const timeoutMs = this.timeoutSeconds * 1000; | ||||||
| @@ -444,6 +463,10 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|         // Clear timeout if test completed successfully |         // Clear timeout if test completed successfully | ||||||
|         clearTimeout(timeoutId); |         clearTimeout(timeoutId); | ||||||
|       } catch (error) { |       } catch (error) { | ||||||
|  |         // Clear warning timer if it was set | ||||||
|  |         if (warningTimer) { | ||||||
|  |           clearTimeout(warningTimer); | ||||||
|  |         } | ||||||
|         // Handle timeout error |         // Handle timeout error | ||||||
|         tapParser.handleTimeout(this.timeoutSeconds); |         tapParser.handleTimeout(this.timeoutSeconds); | ||||||
|       } |       } | ||||||
| @@ -451,6 +474,11 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|       await evaluatePromise; |       await evaluatePromise; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // Clear warning timer if it was set | ||||||
|  |     if (warningTimer) { | ||||||
|  |       clearTimeout(warningTimer); | ||||||
|  |     } | ||||||
|  |      | ||||||
|     // Always clean up resources, even on timeout |     // Always clean up resources, even on timeout | ||||||
|     try { |     try { | ||||||
|       await this.smartbrowserInstance.stop(); |       await this.smartbrowserInstance.stop(); | ||||||
| @@ -488,10 +516,10 @@ import '${absoluteTestFile.replace(/\\/g, '/')}'; | |||||||
|      |      | ||||||
|     try { |     try { | ||||||
|       // Delete 00err and 00diff directories if they exist |       // Delete 00err and 00diff directories if they exist | ||||||
|       if (await plugins.smartfile.fs.isDirectory(errDir)) { |       if (plugins.smartfile.fs.isDirectorySync(errDir)) { | ||||||
|         plugins.smartfile.fs.removeSync(errDir); |         plugins.smartfile.fs.removeSync(errDir); | ||||||
|       } |       } | ||||||
|       if (await plugins.smartfile.fs.isDirectory(diffDir)) { |       if (plugins.smartfile.fs.isDirectorySync(diffDir)) { | ||||||
|         plugins.smartfile.fs.removeSync(diffDir); |         plugins.smartfile.fs.removeSync(diffDir); | ||||||
|       } |       } | ||||||
|        |        | ||||||
|   | |||||||
| @@ -242,9 +242,11 @@ export class TsTestLogger { | |||||||
|      |      | ||||||
|     if (!this.options.quiet) { |     if (!this.options.quiet) { | ||||||
|       const total = passed + failed; |       const total = passed + failed; | ||||||
|       const status = failed === 0 ? 'PASSED' : 'FAILED'; |       if (failed === 0) { | ||||||
|       const color = failed === 0 ? 'green' : 'red'; |         this.log(this.format(`   Summary: ${passed}/${total} PASSED`, 'green')); | ||||||
|       this.log(this.format(`   Summary: ${passed}/${total} ${status}`, color)); |       } else { | ||||||
|  |         this.log(this.format(`   Summary: ${passed} passed, ${failed} failed of ${total} tests`, 'red')); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // If using --logfile, handle error copy and diff detection |     // If using --logfile, handle error copy and diff detection | ||||||
| @@ -390,7 +392,11 @@ export class TsTestLogger { | |||||||
|      |      | ||||||
|     if (this.options.quiet) { |     if (this.options.quiet) { | ||||||
|       const status = summary.totalFailed === 0 ? 'PASSED' : 'FAILED'; |       const status = summary.totalFailed === 0 ? 'PASSED' : 'FAILED'; | ||||||
|       this.log(`\nSummary: ${summary.totalPassed}/${summary.totalTests} | ${totalDuration}ms | ${status}`); |       if (summary.totalFailed === 0) { | ||||||
|  |         this.log(`\nSummary: ${summary.totalPassed}/${summary.totalTests} | ${totalDuration}ms | ${status}`); | ||||||
|  |       } else { | ||||||
|  |         this.log(`\nSummary: ${summary.totalPassed} passed, ${summary.totalFailed} failed of ${summary.totalTests} tests | ${totalDuration}ms | ${status}`); | ||||||
|  |       } | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user