feat: add test execution history manager
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
interface TestExecutionRecord {
|
||||
testId: string;
|
||||
file: string;
|
||||
title: string;
|
||||
duration: number;
|
||||
timestamp: number;
|
||||
success: boolean;
|
||||
flaky: boolean;
|
||||
}
|
||||
|
||||
interface TestHistory {
|
||||
records: TestExecutionRecord[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
const HISTORY_FILE = path.join(__dirname, '../../test-history.json');
|
||||
|
||||
export class TestHistoryManager {
|
||||
private history: TestHistory;
|
||||
|
||||
constructor() {
|
||||
this.loadHistory();
|
||||
}
|
||||
|
||||
private loadHistory(): void {
|
||||
if (fs.existsSync(HISTORY_FILE)) {
|
||||
const data = fs.readFileSync(HISTORY_FILE, 'utf-8');
|
||||
this.history = JSON.parse(data);
|
||||
} else {
|
||||
this.history = { records: [], lastUpdated: Date.now() };
|
||||
}
|
||||
}
|
||||
|
||||
private saveHistory(): void {
|
||||
this.history.lastUpdated = Date.now();
|
||||
fs.writeFileSync(HISTORY_FILE, JSON.stringify(this.history, null, 2));
|
||||
}
|
||||
|
||||
recordExecution(testId: string, file: string, title: string, duration: number, success: boolean): void {
|
||||
const record: TestExecutionRecord = {
|
||||
testId,
|
||||
file,
|
||||
title,
|
||||
duration,
|
||||
timestamp: Date.now(),
|
||||
success,
|
||||
flaky: this.isFlaky(testId),
|
||||
};
|
||||
|
||||
this.history.records.push(record);
|
||||
|
||||
if (this.history.records.length > 1000) {
|
||||
this.history.records = this.history.records.slice(-1000);
|
||||
}
|
||||
|
||||
this.saveHistory();
|
||||
}
|
||||
|
||||
getAverageDuration(testId: string): number {
|
||||
const testRecords = this.history.records.filter(r => r.testId === testId);
|
||||
if (testRecords.length === 0) return 0;
|
||||
|
||||
const durations = testRecords.map(r => r.duration);
|
||||
return durations.reduce((a, b) => a + b, 0) / durations.length;
|
||||
}
|
||||
|
||||
isFlaky(testId: string): boolean {
|
||||
const testRecords = this.history.records
|
||||
.filter(r => r.testId === testId)
|
||||
.slice(-10);
|
||||
|
||||
if (testRecords.length < 5) return false;
|
||||
|
||||
const failureCount = testRecords.filter(r => !r.success).length;
|
||||
return failureCount >= 3;
|
||||
}
|
||||
|
||||
getSlowTests(threshold: number = 2): TestExecutionRecord[] {
|
||||
const avgDurations = new Map<string, number>();
|
||||
|
||||
this.history.records.forEach(record => {
|
||||
const avg = avgDurations.get(record.testId) || 0;
|
||||
const count = this.history.records.filter(r => r.testId === record.testId).length;
|
||||
avgDurations.set(record.testId, avg + record.duration / count);
|
||||
});
|
||||
|
||||
return Array.from(avgDurations.entries())
|
||||
.filter(([_, avg]) => avg > threshold * 60000)
|
||||
.map(([testId, avg]) => ({
|
||||
testId,
|
||||
file: this.history.records.find(r => r.testId === testId)!.file,
|
||||
title: this.history.records.find(r => r.testId === testId)!.title,
|
||||
duration: avg,
|
||||
timestamp: Date.now(),
|
||||
success: true,
|
||||
flaky: false,
|
||||
}))
|
||||
.sort((a, b) => b.duration - a.duration);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user