fix(tstest-logging): Improve log file handling with log rotation and diff reporting
This commit is contained in:
		| @@ -1,5 +1,13 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-05-24 - 1.10.2 - fix(tstest-logging) | ||||
| Improve log file handling with log rotation and diff reporting | ||||
|  | ||||
| - Add .claude/settings.local.json to configure allowed shell and web operations | ||||
| - Introduce movePreviousLogFiles function to archive previous log files when --logfile is used | ||||
| - Enhance logging to generate error copies and diff reports between current and previous logs | ||||
| - Add type annotations for console overrides in browser evaluations for improved stability | ||||
|  | ||||
| ## 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. | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@git.zone/tstest', | ||||
|   version: '1.10.1', | ||||
|   version: '1.10.2', | ||||
|   description: 'a test utility to run tests that match test/**/*.ts' | ||||
| } | ||||
|   | ||||
| @@ -38,6 +38,11 @@ export class TsTest { | ||||
|   } | ||||
|  | ||||
|   async run() { | ||||
|     // Move previous log files if --logfile option is used | ||||
|     if (this.logger.options.logFile) { | ||||
|       await this.movePreviousLogFiles(); | ||||
|     } | ||||
|      | ||||
|     const testGroups = await this.testDir.getTestFileGroups(); | ||||
|     const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; | ||||
|      | ||||
| @@ -167,7 +172,7 @@ export class TsTest { | ||||
|     }); | ||||
|     server.addRoute( | ||||
|       '/test', | ||||
|       new plugins.typedserver.servertools.Handler('GET', async (req, res) => { | ||||
|       new plugins.typedserver.servertools.Handler('GET', async (_req, res) => { | ||||
|         res.type('.html'); | ||||
|         res.write(` | ||||
|         <html> | ||||
| @@ -202,7 +207,7 @@ export class TsTest { | ||||
|  | ||||
|     // lets do the browser bit | ||||
|     await this.smartbrowserInstance.start(); | ||||
|     const evaluation = await this.smartbrowserInstance.evaluateOnPage( | ||||
|     await this.smartbrowserInstance.evaluateOnPage( | ||||
|       `http://localhost:3007/test?bundleName=${bundleFileName}`, | ||||
|       async () => { | ||||
|         // lets enable real time comms | ||||
| @@ -215,12 +220,12 @@ export class TsTest { | ||||
|         const originalError = console.error; | ||||
|  | ||||
|         // Override console methods to capture the logs | ||||
|         console.log = (...args) => { | ||||
|         console.log = (...args: any[]) => { | ||||
|           logStore.push(args.join(' ')); | ||||
|           ws.send(args.join(' ')); | ||||
|           originalLog(...args); | ||||
|         }; | ||||
|         console.error = (...args) => { | ||||
|         console.error = (...args: any[]) => { | ||||
|           logStore.push(args.join(' ')); | ||||
|           ws.send(args.join(' ')); | ||||
|           originalError(...args); | ||||
| @@ -271,4 +276,40 @@ export class TsTest { | ||||
|   } | ||||
|  | ||||
|   public async runInDeno() {} | ||||
|    | ||||
|   private async movePreviousLogFiles() { | ||||
|     const logDir = plugins.path.join('.nogit', 'testlogs'); | ||||
|     const previousDir = plugins.path.join('.nogit', 'testlogs', 'previous'); | ||||
|      | ||||
|     try { | ||||
|       // Get all files in log directory | ||||
|       const files = await plugins.smartfile.fs.listFileTree(logDir, '*.log'); | ||||
|       if (files.length === 0) { | ||||
|         return; | ||||
|       } | ||||
|        | ||||
|       // Ensure previous directory exists | ||||
|       await plugins.smartfile.fs.ensureDir(previousDir); | ||||
|        | ||||
|       // Move each file to previous directory | ||||
|       for (const file of files) { | ||||
|         const filename = plugins.path.basename(file); | ||||
|         const sourcePath = plugins.path.join(logDir, filename); | ||||
|         const destPath = plugins.path.join(previousDir, filename); | ||||
|          | ||||
|         try { | ||||
|           // Read file content and write to new location | ||||
|           const content = await plugins.smartfile.fs.toStringSync(sourcePath); | ||||
|           await plugins.smartfile.fs.toFs(content, destPath); | ||||
|           // Remove original file | ||||
|           await plugins.smartfile.fs.remove(sourcePath); | ||||
|         } catch (error) { | ||||
|           // Silently continue if a file can't be moved | ||||
|         } | ||||
|       } | ||||
|     } catch (error) { | ||||
|       // Directory might not exist, which is fine | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -37,7 +37,7 @@ export interface TestSummary { | ||||
| } | ||||
|  | ||||
| export class TsTestLogger { | ||||
|   private options: LogOptions; | ||||
|   public readonly options: LogOptions; | ||||
|   private startTime: number; | ||||
|   private fileResults: TestFileResult[] = []; | ||||
|   private currentFileResult: TestFileResult | null = null; | ||||
| @@ -247,6 +247,36 @@ export class TsTestLogger { | ||||
|       this.log(this.format(`   Summary: ${passed}/${total} ${status}`, color)); | ||||
|     } | ||||
|      | ||||
|     // If using --logfile, handle error copy and diff detection | ||||
|     if (this.options.logFile && this.currentTestLogFile) { | ||||
|       try { | ||||
|         const logContent = fs.readFileSync(this.currentTestLogFile, 'utf-8'); | ||||
|         const logDir = path.dirname(this.currentTestLogFile); | ||||
|         const logBasename = path.basename(this.currentTestLogFile); | ||||
|          | ||||
|         // Create error copy if there were failures | ||||
|         if (failed > 0) { | ||||
|           const errorLogPath = path.join(logDir, `00err_${logBasename}`); | ||||
|           fs.writeFileSync(errorLogPath, logContent); | ||||
|         } | ||||
|          | ||||
|         // Check for previous version and create diff if changed | ||||
|         const previousLogPath = path.join(logDir, 'previous', logBasename); | ||||
|         if (fs.existsSync(previousLogPath)) { | ||||
|           const previousContent = fs.readFileSync(previousLogPath, 'utf-8'); | ||||
|            | ||||
|           // Simple check if content differs | ||||
|           if (previousContent !== logContent) { | ||||
|             const diffLogPath = path.join(logDir, `00diff_${logBasename}`); | ||||
|             const diffContent = this.createDiff(previousContent, logContent, logBasename); | ||||
|             fs.writeFileSync(diffLogPath, diffContent); | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         // Silently fail to avoid disrupting the test run | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // Clear the current test log file reference only if using --logfile | ||||
|     if (this.options.logFile) { | ||||
|       this.currentTestLogFile = null; | ||||
| @@ -254,7 +284,7 @@ export class TsTestLogger { | ||||
|   } | ||||
|    | ||||
|   // TAP output forwarding (for TAP protocol messages) | ||||
|   tapOutput(message: string, isError: boolean = false) { | ||||
|   tapOutput(message: string, _isError: boolean = false) { | ||||
|     if (this.options.json) return; | ||||
|      | ||||
|     // Never show raw TAP protocol messages in console | ||||
| @@ -424,4 +454,48 @@ export class TsTestLogger { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Create a diff between two log contents | ||||
|   private createDiff(previousContent: string, currentContent: string, filename: string): string { | ||||
|     const previousLines = previousContent.split('\n'); | ||||
|     const currentLines = currentContent.split('\n'); | ||||
|      | ||||
|     let diff = `DIFF REPORT: ${filename}\n`; | ||||
|     diff += `Generated: ${new Date().toISOString()}\n`; | ||||
|     diff += '='.repeat(80) + '\n\n'; | ||||
|      | ||||
|     // Simple line-by-line comparison | ||||
|     const maxLines = Math.max(previousLines.length, currentLines.length); | ||||
|     let hasChanges = false; | ||||
|      | ||||
|     for (let i = 0; i < maxLines; i++) { | ||||
|       const prevLine = previousLines[i] || ''; | ||||
|       const currLine = currentLines[i] || ''; | ||||
|        | ||||
|       if (prevLine !== currLine) { | ||||
|         hasChanges = true; | ||||
|         if (i < previousLines.length && i >= currentLines.length) { | ||||
|           // Line was removed | ||||
|           diff += `- [Line ${i + 1}] ${prevLine}\n`; | ||||
|         } else if (i >= previousLines.length && i < currentLines.length) { | ||||
|           // Line was added | ||||
|           diff += `+ [Line ${i + 1}] ${currLine}\n`; | ||||
|         } else { | ||||
|           // Line was modified | ||||
|           diff += `- [Line ${i + 1}] ${prevLine}\n`; | ||||
|           diff += `+ [Line ${i + 1}] ${currLine}\n`; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     if (!hasChanges) { | ||||
|       diff += 'No changes detected.\n'; | ||||
|     } | ||||
|      | ||||
|     diff += '\n' + '='.repeat(80) + '\n'; | ||||
|     diff += `Previous version had ${previousLines.length} lines\n`; | ||||
|     diff += `Current version has ${currentLines.length} lines\n`; | ||||
|      | ||||
|     return diff; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user