diff --git a/changelog.md b/changelog.md index 1369934..61ff3c0 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index faa8bfc..5b4dbd7 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -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' } diff --git a/ts/tstest.classes.tstest.ts b/ts/tstest.classes.tstest.ts index 0628ee0..c1c7e0e 100644 --- a/ts/tstest.classes.tstest.ts +++ b/ts/tstest.classes.tstest.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(` @@ -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; + } + } } diff --git a/ts/tstest.logging.ts b/ts/tstest.logging.ts index d05a71c..57ccc02 100644 --- a/ts/tstest.logging.ts +++ b/ts/tstest.logging.ts @@ -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; + } } \ No newline at end of file