fix(tstest): Improve timeout and error handling in test execution along with TAP parser timeout logic improvements.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-05-24 - 1.11.2 - fix(tstest) | ||||
| Improve timeout and error handling in test execution along with TAP parser timeout logic improvements. | ||||
|  | ||||
| - In the TAP parser, ensure that expected tests are properly set when no tests are defined to avoid false negatives on timeout. | ||||
| - Use smartshell's terminate method and fallback kill to properly stop the entire process tree on timeout. | ||||
| - Clean up browser, server, and WebSocket instances reliably even when a timeout occurs. | ||||
| - Minor improvements in log file filtering and error logging for better clarity. | ||||
|  | ||||
| ## 2025-05-24 - 1.11.1 - fix(tstest) | ||||
| Clear timeout identifiers after successful test execution and add local CLAUDE settings | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tstest', | ||||
|   version: '1.11.1', | ||||
|   version: '1.11.2', | ||||
|   description: 'a test utility to run tests that match test/**/*.ts' | ||||
| } | ||||
|   | ||||
| @@ -36,18 +36,20 @@ export class TapParser { | ||||
|    * Handle test file timeout | ||||
|    */ | ||||
|   public handleTimeout(timeoutSeconds: number) { | ||||
|     // If no tests have been defined yet, set expected to 1 | ||||
|     if (this.expectedTests === 0) { | ||||
|       this.expectedTests = 1; | ||||
|     } | ||||
|      | ||||
|     // Create a fake failing test result for timeout | ||||
|     this._getNewTapTestResult(); | ||||
|     this.activeTapTestResult.testOk = false; | ||||
|     this.activeTapTestResult.testSettled = true; | ||||
|     this.testStore.push(this.activeTapTestResult); | ||||
|      | ||||
|     // Set expected vs received to force failure | ||||
|     this.expectedTests = 1; | ||||
|     this.receivedTests = 0; | ||||
|      | ||||
|     // Log the timeout error | ||||
|     if (this.logger) { | ||||
|       // First log the test result | ||||
|       this.logger.testResult( | ||||
|         `Test file timeout`, | ||||
|         false, | ||||
| @@ -55,9 +57,9 @@ export class TapParser { | ||||
|         `Error: Test file exceeded timeout of ${timeoutSeconds} seconds` | ||||
|       ); | ||||
|       this.logger.testErrorDetails(`Test execution was terminated after ${timeoutSeconds} seconds`); | ||||
|       // Force file end with failure | ||||
|       this.logger.testFileEnd(0, 1, timeoutSeconds * 1000); | ||||
|     } | ||||
|      | ||||
|     // Don't call evaluateFinalResult here, let the caller handle it | ||||
|   } | ||||
|  | ||||
|   private _getNewTapTestResult() { | ||||
|   | ||||
| @@ -156,8 +156,9 @@ export class TsTest { | ||||
|       let timeoutId: NodeJS.Timeout; | ||||
|        | ||||
|       const timeoutPromise = new Promise<void>((_resolve, reject) => { | ||||
|         timeoutId = setTimeout(() => { | ||||
|           execResultStreaming.childProcess.kill('SIGTERM'); | ||||
|         timeoutId = setTimeout(async () => { | ||||
|           // Use smartshell's terminate() to kill entire process tree | ||||
|           await execResultStreaming.terminate(); | ||||
|           reject(new Error(`Test file timed out after ${this.timeoutSeconds} seconds`)); | ||||
|         }, timeoutMs); | ||||
|       }); | ||||
| @@ -172,6 +173,12 @@ export class TsTest { | ||||
|       } catch (error) { | ||||
|         // Handle timeout error | ||||
|         tapParser.handleTimeout(this.timeoutSeconds); | ||||
|         // Ensure entire process tree is killed if still running | ||||
|         try { | ||||
|           await execResultStreaming.kill(); // This kills the entire process tree with SIGKILL | ||||
|         } catch (killError) { | ||||
|           // Process tree might already be dead | ||||
|         } | ||||
|       } | ||||
|     } else { | ||||
|       await tapParser.handleTapProcess(execResultStreaming.childProcess); | ||||
| @@ -296,6 +303,7 @@ export class TsTest { | ||||
|     ); | ||||
|      | ||||
|     // Handle timeout if specified | ||||
|     let hasTimedOut = false; | ||||
|     if (this.timeoutSeconds !== null) { | ||||
|       const timeoutMs = this.timeoutSeconds * 1000; | ||||
|       let timeoutId: NodeJS.Timeout; | ||||
| @@ -315,19 +323,36 @@ export class TsTest { | ||||
|         clearTimeout(timeoutId); | ||||
|       } catch (error) { | ||||
|         // Handle timeout error | ||||
|         hasTimedOut = true; | ||||
|         tapParser.handleTimeout(this.timeoutSeconds); | ||||
|       } | ||||
|     } else { | ||||
|       await evaluatePromise; | ||||
|     } | ||||
|      | ||||
|     // Always clean up resources, even on timeout | ||||
|     try { | ||||
|       await this.smartbrowserInstance.stop(); | ||||
|     } catch (error) { | ||||
|       // Browser might already be stopped | ||||
|     } | ||||
|      | ||||
|     try { | ||||
|       await server.stop(); | ||||
|     } catch (error) { | ||||
|       // Server might already be stopped | ||||
|     } | ||||
|      | ||||
|     try { | ||||
|       wss.close(); | ||||
|     } catch (error) { | ||||
|       // WebSocket server might already be closed | ||||
|     } | ||||
|      | ||||
|     console.log( | ||||
|       `${cs('=> ', 'blue')} Stopped ${cs(fileNameArg, 'orange')} chromium instance and server.` | ||||
|     ); | ||||
|     // lets create the tap parser | ||||
|     // Always evaluate final result (handleTimeout just sets up the test state) | ||||
|     await tapParser.evaluateFinalResult(); | ||||
|     return tapParser; | ||||
|   } | ||||
| @@ -351,7 +376,7 @@ export class TsTest { | ||||
|        | ||||
|       // Get all .log files in log directory (not in subdirectories) | ||||
|       const files = await plugins.smartfile.fs.listFileTree(logDir, '*.log'); | ||||
|       const logFiles = files.filter(file => !file.includes('/')); | ||||
|       const logFiles = files.filter((file: string) => !file.includes('/')); | ||||
|        | ||||
|       if (logFiles.length === 0) { | ||||
|         return; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user