fix(tstest-logging): Improve log file handling with log rotation and diff reporting
This commit is contained in:
		| @@ -1,5 +1,13 @@ | |||||||
| # Changelog | # 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) | ## 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. | 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 = { | export const commitinfo = { | ||||||
|   name: '@git.zone/tstest', |   name: '@git.zone/tstest', | ||||||
|   version: '1.10.1', |   version: '1.10.2', | ||||||
|   description: 'a test utility to run tests that match test/**/*.ts' |   description: 'a test utility to run tests that match test/**/*.ts' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -38,6 +38,11 @@ export class TsTest { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   async run() { |   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 testGroups = await this.testDir.getTestFileGroups(); | ||||||
|     const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; |     const allFiles = [...testGroups.serial, ...Object.values(testGroups.parallelGroups).flat()]; | ||||||
|      |      | ||||||
| @@ -167,7 +172,7 @@ export class TsTest { | |||||||
|     }); |     }); | ||||||
|     server.addRoute( |     server.addRoute( | ||||||
|       '/test', |       '/test', | ||||||
|       new plugins.typedserver.servertools.Handler('GET', async (req, res) => { |       new plugins.typedserver.servertools.Handler('GET', async (_req, res) => { | ||||||
|         res.type('.html'); |         res.type('.html'); | ||||||
|         res.write(` |         res.write(` | ||||||
|         <html> |         <html> | ||||||
| @@ -202,7 +207,7 @@ export class TsTest { | |||||||
|  |  | ||||||
|     // lets do the browser bit |     // lets do the browser bit | ||||||
|     await this.smartbrowserInstance.start(); |     await this.smartbrowserInstance.start(); | ||||||
|     const evaluation = await this.smartbrowserInstance.evaluateOnPage( |     await this.smartbrowserInstance.evaluateOnPage( | ||||||
|       `http://localhost:3007/test?bundleName=${bundleFileName}`, |       `http://localhost:3007/test?bundleName=${bundleFileName}`, | ||||||
|       async () => { |       async () => { | ||||||
|         // lets enable real time comms |         // lets enable real time comms | ||||||
| @@ -215,12 +220,12 @@ export class TsTest { | |||||||
|         const originalError = console.error; |         const originalError = console.error; | ||||||
|  |  | ||||||
|         // Override console methods to capture the logs |         // Override console methods to capture the logs | ||||||
|         console.log = (...args) => { |         console.log = (...args: any[]) => { | ||||||
|           logStore.push(args.join(' ')); |           logStore.push(args.join(' ')); | ||||||
|           ws.send(args.join(' ')); |           ws.send(args.join(' ')); | ||||||
|           originalLog(...args); |           originalLog(...args); | ||||||
|         }; |         }; | ||||||
|         console.error = (...args) => { |         console.error = (...args: any[]) => { | ||||||
|           logStore.push(args.join(' ')); |           logStore.push(args.join(' ')); | ||||||
|           ws.send(args.join(' ')); |           ws.send(args.join(' ')); | ||||||
|           originalError(...args); |           originalError(...args); | ||||||
| @@ -271,4 +276,40 @@ export class TsTest { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   public async runInDeno() {} |   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 { | export class TsTestLogger { | ||||||
|   private options: LogOptions; |   public readonly options: LogOptions; | ||||||
|   private startTime: number; |   private startTime: number; | ||||||
|   private fileResults: TestFileResult[] = []; |   private fileResults: TestFileResult[] = []; | ||||||
|   private currentFileResult: TestFileResult | null = null; |   private currentFileResult: TestFileResult | null = null; | ||||||
| @@ -247,6 +247,36 @@ export class TsTestLogger { | |||||||
|       this.log(this.format(`   Summary: ${passed}/${total} ${status}`, color)); |       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 |     // Clear the current test log file reference only if using --logfile | ||||||
|     if (this.options.logFile) { |     if (this.options.logFile) { | ||||||
|       this.currentTestLogFile = null; |       this.currentTestLogFile = null; | ||||||
| @@ -254,7 +284,7 @@ export class TsTestLogger { | |||||||
|   } |   } | ||||||
|    |    | ||||||
|   // TAP output forwarding (for TAP protocol messages) |   // TAP output forwarding (for TAP protocol messages) | ||||||
|   tapOutput(message: string, isError: boolean = false) { |   tapOutput(message: string, _isError: boolean = false) { | ||||||
|     if (this.options.json) return; |     if (this.options.json) return; | ||||||
|      |      | ||||||
|     // Never show raw TAP protocol messages in console |     // 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