use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
pub type TestId = ggen_test_audit::TestId;
pub type TestType = ggen_test_audit::TestType;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestValueScore {
pub test_id: TestId,
pub failure_freq_score: f64,
pub coverage_score: f64,
pub speed_score: f64,
pub criticality_score: f64,
pub budget_penalty: f64,
pub composite_value: f64,
}
impl TestValueScore {
#[must_use]
pub fn calculate_composite(
failure_freq: f64, coverage: f64, speed: f64, criticality: f64, budget_penalty: f64,
) -> f64 {
let weights = ScoringWeights::default();
(failure_freq * weights.failure_freq)
+ (coverage * weights.coverage)
+ (speed * weights.speed)
+ (criticality * weights.criticality)
- (budget_penalty * weights.budget_penalty)
}
}
impl PartialEq for TestValueScore {
fn eq(&self, other: &Self) -> bool {
self.composite_value == other.composite_value
}
}
impl Eq for TestValueScore {}
impl PartialOrd for TestValueScore {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TestValueScore {
fn cmp(&self, other: &Self) -> Ordering {
other
.composite_value
.partial_cmp(&self.composite_value)
.unwrap_or(Ordering::Equal)
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ScoringWeights {
pub failure_freq: f64,
pub coverage: f64,
pub speed: f64,
pub criticality: f64,
pub budget_penalty: f64,
}
impl Default for ScoringWeights {
fn default() -> Self {
Self {
failure_freq: 0.40,
coverage: 0.25,
speed: 0.15,
criticality: 0.15,
budget_penalty: 0.05,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestMetadata {
pub test_id: TestId,
pub failure_count: u32,
pub run_count: u32,
pub last_failure_date: Option<DateTime<Utc>>,
pub avg_execution_time_ms: u64,
pub unique_lines_covered: usize,
}
impl TestMetadata {
#[must_use]
pub fn failure_frequency(&self) -> f64 {
if self.run_count == 0 {
0.0
} else {
f64::from(self.failure_count) / f64::from(self.run_count)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BudgetViolation {
pub test_id: TestId,
pub exec_time_ms: u64,
pub budget_ms: u64,
pub excess_ms: u64,
pub severity: Severity,
}
impl BudgetViolation {
#[must_use]
pub fn new(test_id: TestId, exec_time_ms: u64, budget_ms: u64) -> Self {
let excess_ms = exec_time_ms.saturating_sub(budget_ms);
let excess_pct = (f64::from(excess_ms as u32) / f64::from(budget_ms as u32)) * 100.0;
let severity = if excess_pct <= 50.0 {
Severity::Warning
} else if excess_pct <= 100.0 {
Severity::Error
} else {
Severity::Critical
};
Self {
test_id,
exec_time_ms,
budget_ms,
excess_ms,
severity,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
Warning,
Error,
Critical,
}
#[derive(Debug, thiserror::Error)]
pub enum OptimizationError {
#[error("Budget exceeded: {0}")]
BudgetExceeded(String),
#[error("Insufficient coverage: {0}")]
InsufficientCoverage(String),
#[error("No tests selected: {0}")]
NoTestsSelected(String),
#[error("Invalid weights: {0}")]
InvalidWeights(String),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Audit error: {0}")]
AuditError(#[from] ggen_test_audit::AuditError),
}
pub type OptResult<T> = Result<T, OptimizationError>;
pub type OptimizationResult<T> = OptResult<T>;