diff --git a/e2e/src/utils/test-alert.ts b/e2e/src/utils/test-alert.ts new file mode 100644 index 0000000..ba2a012 --- /dev/null +++ b/e2e/src/utils/test-alert.ts @@ -0,0 +1,154 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +interface Alert { + id: string; + timestamp: number; + severity: 'low' | 'medium' | 'high' | 'critical'; + message: string; + tier: string; + metrics: any; + resolved: boolean; +} + +export class TestAlertManager { + private alerts: Alert[] = []; + private alertFile: string; + + constructor(alertFile: string = 'test-results/alerts.json') { + this.alertFile = alertFile; + this.loadAlerts(); + } + + private loadAlerts(): void { + if (fs.existsSync(this.alertFile)) { + const data = fs.readFileSync(this.alertFile, 'utf-8'); + this.alerts = JSON.parse(data); + } + } + + private saveAlerts(): void { + const dir = path.dirname(this.alertFile); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(this.alertFile, JSON.stringify(this.alerts, null, 2)); + } + + createAlert( + severity: 'low' | 'medium' | 'high' | 'critical', + message: string, + tier: string, + metrics: any + ): Alert { + const alert: Alert = { + id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: Date.now(), + severity, + message, + tier, + metrics, + resolved: false, + }; + + this.alerts.push(alert); + this.saveAlerts(); + + return alert; + } + + resolveAlert(alertId: string): void { + const alert = this.alerts.find(a => a.id === alertId); + if (alert) { + alert.resolved = true; + this.saveAlerts(); + } + } + + getActiveAlerts(): Alert[] { + return this.alerts.filter(a => !a.resolved); + } + + getAlertsBySeverity(severity: 'low' | 'medium' | 'high' | 'critical'): Alert[] { + return this.alerts.filter(a => a.severity === severity && !a.resolved); + } + + getAlertsByTier(tier: string): Alert[] { + return this.alerts.filter(a => a.tier === tier && !a.resolved); + } + + getRecentAlerts(hours: number = 24): Alert[] { + const cutoff = Date.now() - hours * 60 * 60 * 1000; + return this.alerts.filter(a => a.timestamp >= cutoff && !a.resolved); + } + + getAlertSummary(): { + total: number; + active: number; + bySeverity: Record; + byTier: Record; + } { + const activeAlerts = this.getActiveAlerts(); + + const bySeverity: Record = { + low: 0, + medium: 0, + high: 0, + critical: 0, + }; + + const byTier: Record = {}; + + activeAlerts.forEach(alert => { + bySeverity[alert.severity]++; + byTier[alert.tier] = (byTier[alert.tier] || 0) + 1; + }); + + return { + total: this.alerts.length, + active: activeAlerts.length, + bySeverity, + byTier, + }; + } + + clearOldAlerts(days: number = 30): void { + const cutoff = Date.now() - days * 24 * 60 * 60 * 1000; + this.alerts = this.alerts.filter(a => a.timestamp >= cutoff); + this.saveAlerts(); + } + + exportReport(): string { + const summary = this.getAlertSummary(); + const activeAlerts = this.getActiveAlerts(); + + let report = '🚨 测试告警报告\n'; + report += '='.repeat(50) + '\n\n'; + report += `总告警数: ${summary.total}\n`; + report += `活跃告警: ${summary.active}\n\n`; + + report += '按严重程度统计:\n'; + report += ` Critical: ${summary.bySeverity.critical}\n`; + report += ` High: ${summary.bySeverity.high}\n`; + report += ` Medium: ${summary.bySeverity.medium}\n`; + report += ` Low: ${summary.bySeverity.low}\n\n`; + + if (activeAlerts.length > 0) { + report += '活跃告警详情:\n'; + activeAlerts.forEach((alert, index) => { + report += `\n${index + 1}. [${alert.severity.toUpperCase()}] ${alert.message}\n`; + report += ` Tier: ${alert.tier}\n`; + report += ` Time: ${new Date(alert.timestamp).toLocaleString('zh-CN')}\n`; + }); + } else { + report += '✅ 当前无活跃告警\n'; + } + + return report; + } + + clearAllAlerts(): void { + this.alerts = []; + this.saveAlerts(); + } +} \ No newline at end of file diff --git a/e2e/src/utils/test-monitor.ts b/e2e/src/utils/test-monitor.ts new file mode 100644 index 0000000..443de55 --- /dev/null +++ b/e2e/src/utils/test-monitor.ts @@ -0,0 +1,147 @@ +interface TestMetric { + timestamp: number; + tier: string; + totalTests: number; + passedTests: number; + failedTests: number; + skippedTests: number; + duration: number; + successRate: number; +} + +interface AlertRule { + name: string; + condition: (metrics: TestMetric) => boolean; + severity: 'low' | 'medium' | 'high' | 'critical'; + message: string; +} + +export class TestMonitor { + private metrics: TestMetric[] = []; + private alertRules: AlertRule[] = []; + + constructor() { + this.initializeDefaultRules(); + } + + private initializeDefaultRules(): void { + this.alertRules = [ + { + name: 'success-rate-low', + condition: (m) => m.successRate < 0.8, + severity: 'critical', + message: '测试通过率低于80%', + }, + { + name: 'success-rate-medium', + condition: (m) => m.successRate < 0.9 && m.successRate >= 0.8, + severity: 'high', + message: '测试通过率低于90%', + }, + { + name: 'duration-exceeded', + condition: (m) => m.duration > 30 * 60 * 1000, + severity: 'medium', + message: '测试执行时间超过30分钟', + }, + { + name: 'failed-tests-high', + condition: (m) => m.failedTests > 10, + severity: 'high', + message: '失败测试数量超过10个', + }, + { + name: 'tier-deep-failed', + condition: (m) => m.tier === 'deep' && m.failedTests > 0, + severity: 'critical', + message: '深度层测试存在失败', + }, + ]; + } + + recordMetric(metric: TestMetric): void { + this.metrics.push(metric); + this.checkAlerts(metric); + } + + private checkAlerts(metric: TestMetric): void { + const triggeredAlerts = this.alertRules.filter(rule => rule.condition(metric)); + + if (triggeredAlerts.length > 0) { + triggeredAlerts.forEach(alert => { + this.triggerAlert(alert, metric); + }); + } + } + + private triggerAlert(alert: AlertRule, metric: TestMetric): void { + const alertMessage = { + timestamp: new Date().toISOString(), + alert: alert.name, + severity: alert.severity, + message: alert.message, + tier: metric.tier, + metrics: { + total: metric.totalTests, + passed: metric.passedTests, + failed: metric.failedTests, + skipped: metric.skippedTests, + duration: metric.duration, + successRate: metric.successRate, + }, + }; + + console.log(`🚨 [${alert.severity.toUpperCase()}] ${alert.message}`); + console.log(JSON.stringify(alertMessage, null, 2)); + } + + getMetricsByTier(tier: string): TestMetric[] { + return this.metrics.filter(m => m.tier === tier); + } + + getAverageSuccessRate(tier?: string): number { + const filteredMetrics = tier + ? this.getMetricsByTier(tier) + : this.metrics; + + if (filteredMetrics.length === 0) return 0; + + const sum = filteredMetrics.reduce((acc, m) => acc + m.successRate, 0); + return sum / filteredMetrics.length; + } + + getTrend(tier: string, window: number = 5): 'improving' | 'declining' | 'stable' { + const tierMetrics = this.getMetricsByTier(tier); + const recentMetrics = tierMetrics.slice(-window); + + if (recentMetrics.length < 2) return 'stable'; + + const firstHalf = recentMetrics.slice(0, Math.floor(recentMetrics.length / 2)); + const secondHalf = recentMetrics.slice(Math.floor(recentMetrics.length / 2)); + + const firstAvg = firstHalf.reduce((acc, m) => acc + m.successRate, 0) / firstHalf.length; + const secondAvg = secondHalf.reduce((acc, m) => acc + m.successRate, 0) / secondHalf.length; + + const diff = secondAvg - firstAvg; + + if (diff > 0.05) return 'improving'; + if (diff < -0.05) return 'declining'; + return 'stable'; + } + + addAlertRule(rule: AlertRule): void { + this.alertRules.push(rule); + } + + removeAlertRule(ruleName: string): void { + this.alertRules = this.alertRules.filter(r => r.name !== ruleName); + } + + getMetrics(): TestMetric[] { + return [...this.metrics]; + } + + clearMetrics(): void { + this.metrics = []; + } +} \ No newline at end of file diff --git a/e2e/test-monitor-simple-test.js b/e2e/test-monitor-simple-test.js new file mode 100644 index 0000000..f7754a1 --- /dev/null +++ b/e2e/test-monitor-simple-test.js @@ -0,0 +1,103 @@ +const fs = require('fs'); +const path = require('path'); + +console.log('🔍 测试监控和告警系统...'); + +const alertFile = 'test-results/alerts.json'; + +const mockMetrics = [ + { + timestamp: Date.now() - 3600000, + tier: 'fast', + totalTests: 100, + passedTests: 95, + failedTests: 5, + skippedTests: 0, + duration: 60000, + successRate: 0.95, + }, + { + timestamp: Date.now() - 1800000, + tier: 'standard', + totalTests: 200, + passedTests: 180, + failedTests: 15, + skippedTests: 5, + duration: 180000, + successRate: 0.9, + }, + { + timestamp: Date.now(), + tier: 'deep', + totalTests: 50, + passedTests: 40, + failedTests: 10, + skippedTests: 0, + duration: 600000, + successRate: 0.8, + }, +]; + +console.log('📊 模拟测试指标...'); + +const alertManager = { + alerts: [], + alertFile, + + createAlert(severity, message, tier, metrics) { + const alert = { + id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: Date.now(), + severity, + message, + tier, + metrics, + resolved: false, + }; + this.alerts.push(alert); + console.log(`🚨 [${severity.toUpperCase()}] ${message}`); + return alert; + }, + + getAlertSummary() { + const activeAlerts = this.alerts.filter(a => !a.resolved); + const bySeverity = { low: 0, medium: 0, high: 0, critical: 0 }; + activeAlerts.forEach(a => bySeverity[a.severity]++); + return { total: this.alerts.length, active: activeAlerts.length, bySeverity }; + }, +}; + +console.log('🔍 检查告警规则...'); + +for (const metric of mockMetrics) { + if (metric.successRate < 0.8) { + alertManager.createAlert('critical', '测试通过率低于80%', metric.tier, metric); + } else if (metric.successRate < 0.9) { + alertManager.createAlert('high', '测试通过率低于90%', metric.tier, metric); + } + + if (metric.failedTests > 10) { + alertManager.createAlert('high', '失败测试数量超过10个', metric.tier, metric); + } + + if (metric.tier === 'deep' && metric.failedTests > 0) { + alertManager.createAlert('critical', '深度层测试存在失败', metric.tier, metric); + } +} + +const summary = alertManager.getAlertSummary(); +console.log('\n📊 告警统计:'); +console.log(` 总告警数: ${summary.total}`); +console.log(` 活跃告警: ${summary.active}`); +console.log(` Critical: ${summary.bySeverity.critical}`); +console.log(` High: ${summary.bySeverity.high}`); +console.log(` Medium: ${summary.bySeverity.medium}`); +console.log(` Low: ${summary.bySeverity.low}`); + +if (summary.active > 0) { + console.log('\n✅ 监控系统工作正常,已生成告警'); + process.exit(0); +} else { + console.log('\n⚠️ 未生成告警'); + process.exit(1); +} \ No newline at end of file