use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IgnoredTestGovernance {
pub inventory: IgnoredTestInventory,
pub baseline_management: BaselineManagement,
pub quality_gates: QualityGates,
pub reporting: ReportingConfiguration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IgnoredTestInventory {
pub total_count: usize,
pub by_category: HashMap<TestCategory, usize>,
pub by_crate: HashMap<String, usize>,
pub by_priority: HashMap<u8, usize>,
pub last_updated: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BaselineManagement {
pub baseline_count: usize,
pub max_deviation: usize,
pub deviation_threshold_percent: f64,
pub baseline_date: SystemTime,
pub next_review_date: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityGates {
pub pre_commit: PreCommitValidation,
pub ci_validation: CiValidation,
pub metrics_tracking: MetricsTracking,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PreCommitValidation {
pub require_justification: bool,
pub max_new_ignored_per_commit: usize,
pub documentation_requirements: DocumentationRequirements,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentationRequirements {
pub require_issue_reference: bool,
pub require_timeline: bool,
pub require_success_criteria: bool,
pub require_complexity_assessment: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiValidation {
pub block_on_count_increase: bool,
pub max_ignored_per_crate: HashMap<String, usize>,
pub min_quality_score: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsTracking {
pub track_trend: bool,
pub trend_window_days: u32,
pub alert_on_negative_trend: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportingConfiguration {
pub daily_reports: bool,
pub weekly_trends: bool,
pub monthly_summaries: bool,
pub output_formats: Vec<ReportFormat>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ReportFormat {
Json,
Markdown,
Html,
Csv,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TestCategory {
CriticalLsp,
Infrastructure,
AdvancedSyntax,
EdgeCases,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IgnoredTestMetadata {
pub test_id: String,
pub file_path: PathBuf,
pub test_name: String,
pub category: TestCategory,
pub priority: u8,
pub ignore_reason: String,
pub complexity: ComplexityLevel,
pub target_timeline: Duration,
pub dependencies: Vec<String>,
pub success_criteria: Vec<String>,
pub workflow_integration: LspWorkflowStage,
pub performance_requirements: Option<PerformanceRequirements>,
pub last_assessed: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ComplexityLevel {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LspWorkflowStage {
Parse,
Index,
Navigate,
Complete,
Analyze,
CrossCutting,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PerformanceRequirements {
pub max_latency_ms: u64,
pub max_memory_mb: u64,
pub min_throughput: Option<f64>,
}
pub struct IgnoredTestGuardian {
pub baseline_tracker: BaselineTracker,
pub governance: IgnoredTestGovernance,
}
pub struct BaselineTracker {
pub current_baseline: usize,
pub historical_data: Vec<(SystemTime, usize)>,
}
impl IgnoredTestGuardian {
pub fn new(governance: IgnoredTestGovernance) -> Self {
Self {
baseline_tracker: BaselineTracker {
current_baseline: governance.baseline_management.baseline_count,
historical_data: Vec::new(),
},
governance,
}
}
pub fn validate_new_ignored_test(&self, test_info: &IgnoredTestMetadata) -> ValidationResult {
let mut errors = Vec::new();
let mut warnings = Vec::new();
if self
.governance
.quality_gates
.pre_commit
.documentation_requirements
.require_issue_reference
{
if !test_info.ignore_reason.contains('#') && !test_info.ignore_reason.contains("issue")
{
errors.push("Ignored test must reference an issue".to_string());
}
}
if self.governance.quality_gates.pre_commit.documentation_requirements.require_timeline {
if test_info.target_timeline.as_secs() == 0 {
errors.push("target implementation timeline must be specified".to_string());
}
}
if self
.governance
.quality_gates
.pre_commit
.documentation_requirements
.require_success_criteria
{
if test_info.success_criteria.is_empty() {
errors.push("success criteria must be specified".to_string());
}
}
if self
.governance
.quality_gates
.pre_commit
.documentation_requirements
.require_complexity_assessment
{
if test_info.complexity == ComplexityLevel::Low
&& test_info.target_timeline > Duration::from_secs(7 * 24 * 3600)
{
warnings.push("Low complexity test should have shorter timeline".to_string());
}
}
ValidationResult {
is_valid: errors.is_empty(),
errors,
warnings,
quality_score: self.calculate_quality_score(test_info),
}
}
pub fn check_baseline_regression(&self, current_count: usize) -> RegressionResult {
let baseline = self.baseline_tracker.current_baseline;
let max_deviation = self.governance.baseline_management.max_deviation;
let threshold_percent = self.governance.baseline_management.deviation_threshold_percent;
let absolute_increase = current_count.saturating_sub(baseline);
let percentage_increase =
if baseline > 0 { (absolute_increase as f64 / baseline as f64) * 100.0 } else { 0.0 };
let is_regression =
absolute_increase > max_deviation || percentage_increase > threshold_percent;
RegressionResult {
is_regression,
current_count,
baseline_count: baseline,
absolute_increase,
percentage_increase,
threshold_exceeded: if absolute_increase > max_deviation {
Some(format!(
"Absolute increase {} > max deviation {}",
absolute_increase, max_deviation
))
} else if percentage_increase > threshold_percent {
Some(format!(
"Percentage increase {:.1}% > threshold {:.1}%",
percentage_increase, threshold_percent
))
} else {
None
},
}
}
pub fn generate_trend_report(&self) -> TrendReport {
let current_time = SystemTime::now();
let window_duration = Duration::from_secs(
self.governance.reporting.monthly_summaries as u64 * 30 * 24 * 3600,
);
let recent_data: Vec<_> = self
.baseline_tracker
.historical_data
.iter()
.filter(|(timestamp, _)| {
current_time.duration_since(*timestamp).unwrap_or(Duration::MAX) <= window_duration
})
.cloned()
.collect();
let trend_direction = if recent_data.len() >= 2 {
let first = recent_data[0].1 as f64;
let last = recent_data[recent_data.len() - 1].1 as f64;
if last > first * 1.1 {
TrendDirection::Increasing
} else if last < first * 0.9 {
TrendDirection::Decreasing
} else {
TrendDirection::Stable
}
} else {
TrendDirection::Unknown
};
let average_count = if !recent_data.is_empty() {
recent_data.iter().map(|(_, count)| *count as f64).sum::<f64>()
/ recent_data.len() as f64
} else {
0.0
};
TrendReport {
period_start: recent_data.first().map(|(t, _)| *t),
period_end: recent_data.last().map(|(t, _)| *t),
recommendations: self.generate_trend_recommendations(&trend_direction, &recent_data),
data_points: recent_data,
trend_direction,
average_count,
}
}
fn calculate_quality_score(&self, test_info: &IgnoredTestMetadata) -> f64 {
let mut score: f64 = 100.0;
if test_info.ignore_reason.len() < 20 {
score -= 20.0;
}
if test_info.success_criteria.is_empty() {
score -= 30.0;
}
if test_info.dependencies.is_empty() && test_info.complexity != ComplexityLevel::Low {
score -= 10.0;
}
if let Ok(duration) = SystemTime::now().duration_since(test_info.last_assessed) {
if duration > Duration::from_secs(90 * 24 * 3600) {
score -= 25.0;
}
}
if test_info.success_criteria.len() >= 3 {
score += 5.0;
}
score.clamp(0.0, 100.0)
}
fn generate_trend_recommendations(
&self,
direction: &TrendDirection,
data: &[(SystemTime, usize)],
) -> Vec<String> {
let mut recommendations = Vec::new();
match direction {
TrendDirection::Increasing => {
recommendations.push(
"Consider implementing systematic ignored test resolution plan".to_string(),
);
recommendations.push("Review test categorization and prioritization".to_string());
recommendations
.push("Allocate development resources for test implementation".to_string());
}
TrendDirection::Decreasing => {
recommendations.push("Excellent progress on ignored test reduction".to_string());
recommendations.push("Document successful implementation strategies".to_string());
recommendations.push("Maintain current implementation pace".to_string());
}
TrendDirection::Stable => {
recommendations
.push("Evaluate whether current ignored test count is acceptable".to_string());
recommendations
.push("Consider setting more aggressive reduction targets".to_string());
}
TrendDirection::Unknown => {
recommendations.push("Collect more historical data for trend analysis".to_string());
recommendations.push("Establish baseline measurement practices".to_string());
}
}
if data.len() > 10 {
let recent_variance = self.calculate_variance(&data[data.len().saturating_sub(10)..]);
if recent_variance > 10.0 {
recommendations.push(
"High variance in ignored test count indicates inconsistent progress"
.to_string(),
);
}
}
recommendations
}
fn calculate_variance(&self, data: &[(SystemTime, usize)]) -> f64 {
if data.len() < 2 {
return 0.0;
}
let mean = data.iter().map(|(_, count)| *count as f64).sum::<f64>() / data.len() as f64;
data.iter().map(|(_, count)| (*count as f64 - mean).powi(2)).sum::<f64>()
/ data.len() as f64
}
pub fn set_historical_data(&mut self, data: Vec<(SystemTime, usize)>) {
self.baseline_tracker.historical_data = data;
}
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub quality_score: f64,
}
#[derive(Debug, Clone)]
pub struct RegressionResult {
pub is_regression: bool,
pub current_count: usize,
pub baseline_count: usize,
pub absolute_increase: usize,
pub percentage_increase: f64,
pub threshold_exceeded: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TrendReport {
pub period_start: Option<SystemTime>,
pub period_end: Option<SystemTime>,
pub data_points: Vec<(SystemTime, usize)>,
pub trend_direction: TrendDirection,
pub average_count: f64,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TrendDirection {
Increasing,
Decreasing,
Stable,
Unknown,
}