use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};
#[derive(Debug, Clone)]
pub struct ErrorCluster {
pub id: String,
pub error_code: String,
pub description: String,
pub frequency: u32,
pub severity: u8,
pub examples: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct FailurePattern {
pub id: String,
pub error_code: String,
pub description: String,
pub category: PatternCategory,
pub affected_count: u32,
pub fix_complexity: u8,
pub trigger_example: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PatternCategory {
TypeInference,
ExternalDeps,
Borrowing,
ControlFlow,
Miscellaneous,
}
impl std::fmt::Display for PatternCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PatternCategory::TypeInference => write!(f, "Type Inference"),
PatternCategory::ExternalDeps => write!(f, "External Dependencies"),
PatternCategory::Borrowing => write!(f, "Borrowing"),
PatternCategory::ControlFlow => write!(f, "Control Flow"),
PatternCategory::Miscellaneous => write!(f, "Miscellaneous"),
}
}
}
#[derive(Debug, Clone)]
pub struct PrioritizedPattern {
pub pattern: FailurePattern,
pub priority: f64,
}
impl PartialEq for PrioritizedPattern {
fn eq(&self, other: &Self) -> bool {
self.priority == other.priority
}
}
impl Eq for PrioritizedPattern {}
impl PartialOrd for PrioritizedPattern {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PrioritizedPattern {
fn cmp(&self, other: &Self) -> Ordering {
self.priority
.partial_cmp(&other.priority)
.unwrap_or(Ordering::Equal)
}
}
#[derive(Debug)]
pub struct HuntPlanner {
error_clusters: Vec<ErrorCluster>,
priority_queue: BinaryHeap<PrioritizedPattern>,
processed: HashMap<String, bool>,
}
impl HuntPlanner {
pub fn new() -> Self {
Self {
error_clusters: Vec::new(),
priority_queue: BinaryHeap::new(),
processed: HashMap::new(),
}
}
pub fn add_clusters(&mut self, clusters: Vec<ErrorCluster>) {
self.error_clusters.extend(clusters);
}
pub fn build_priority_queue(&mut self) {
for cluster in &self.error_clusters {
let pattern = self.cluster_to_pattern(cluster);
let priority = self.calculate_priority(&pattern);
self.priority_queue
.push(PrioritizedPattern { pattern, priority });
}
}
pub fn select_next_target(&mut self) -> Option<FailurePattern> {
while let Some(prioritized) = self.priority_queue.pop() {
let pattern_id = &prioritized.pattern.id;
if self.processed.get(pattern_id).copied().unwrap_or(false) {
continue;
}
self.processed.insert(pattern_id.clone(), true);
return Some(prioritized.pattern);
}
None
}
fn calculate_priority(&self, pattern: &FailurePattern) -> f64 {
let frequency = pattern.affected_count as f64;
let complexity = pattern.fix_complexity as f64;
let complexity = complexity.max(1.0);
(frequency * 10.0) / complexity
}
fn cluster_to_pattern(&self, cluster: &ErrorCluster) -> FailurePattern {
let category = self.categorize_error(&cluster.error_code);
FailurePattern {
id: format!("pattern_{}", cluster.id),
error_code: cluster.error_code.clone(),
description: cluster.description.clone(),
category,
affected_count: cluster.frequency,
fix_complexity: self.estimate_complexity(&cluster.error_code),
trigger_example: cluster.examples.first().cloned().unwrap_or_default(),
}
}
fn categorize_error(&self, error_code: &str) -> PatternCategory {
match error_code {
"E0308" | "E0277" | "E0282" => PatternCategory::TypeInference,
"E0432" | "E0433" => PatternCategory::ExternalDeps,
"E0502" | "E0503" | "E0505" | "E0506" | "E0507" => PatternCategory::Borrowing,
"E0382" | "E0383" => PatternCategory::Borrowing,
"E0106" | "E0621" | "E0623" => PatternCategory::Borrowing,
"E0004" | "E0005" => PatternCategory::ControlFlow,
_ => PatternCategory::Miscellaneous,
}
}
fn estimate_complexity(&self, error_code: &str) -> u8 {
match error_code {
"E0432" | "E0433" => 2,
"E0308" => 4,
"E0277" => 5,
"E0502" | "E0503" | "E0505" | "E0506" | "E0507" => 7,
"E0106" | "E0621" | "E0623" => 8,
_ => 5,
}
}
pub fn remaining_count(&self) -> usize {
self.priority_queue.len()
}
pub fn clusters(&self) -> &[ErrorCluster] {
&self.error_clusters
}
}
impl Default for HuntPlanner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_cluster(id: &str, code: &str, freq: u32) -> ErrorCluster {
ErrorCluster {
id: id.to_string(),
error_code: code.to_string(),
description: format!("Test error {}", code),
frequency: freq,
severity: 5,
examples: vec!["example".to_string()],
}
}
#[test]
fn test_planner_new() {
let planner = HuntPlanner::new();
assert_eq!(planner.remaining_count(), 0);
assert!(planner.clusters().is_empty());
}
#[test]
fn test_add_clusters() {
let mut planner = HuntPlanner::new();
planner.add_clusters(vec![
create_test_cluster("1", "E0308", 10),
create_test_cluster("2", "E0432", 20),
]);
assert_eq!(planner.clusters().len(), 2);
}
#[test]
fn test_build_priority_queue() {
let mut planner = HuntPlanner::new();
planner.add_clusters(vec![
create_test_cluster("1", "E0308", 10),
create_test_cluster("2", "E0432", 20),
]);
planner.build_priority_queue();
assert_eq!(planner.remaining_count(), 2);
}
#[test]
fn test_select_next_target_priority_order() {
let mut planner = HuntPlanner::new();
planner.add_clusters(vec![
create_test_cluster("low", "E0308", 5), create_test_cluster("high", "E0432", 50), ]);
planner.build_priority_queue();
let first = planner.select_next_target().unwrap();
assert_eq!(first.error_code, "E0432"); }
#[test]
fn test_select_next_target_no_duplicates() {
let mut planner = HuntPlanner::new();
planner.add_clusters(vec![create_test_cluster("1", "E0308", 10)]);
planner.build_priority_queue();
assert!(planner.select_next_target().is_some());
assert!(planner.select_next_target().is_none()); }
#[test]
fn test_categorize_error() {
let planner = HuntPlanner::new();
assert_eq!(
planner.categorize_error("E0308"),
PatternCategory::TypeInference
);
assert_eq!(
planner.categorize_error("E0432"),
PatternCategory::ExternalDeps
);
assert_eq!(
planner.categorize_error("E0502"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0004"),
PatternCategory::ControlFlow
);
assert_eq!(
planner.categorize_error("E9999"),
PatternCategory::Miscellaneous
);
}
#[test]
fn test_estimate_complexity() {
let planner = HuntPlanner::new();
assert!(planner.estimate_complexity("E0432") < 5);
assert!(planner.estimate_complexity("E0106") > 5);
}
#[test]
fn test_pattern_category_display() {
assert_eq!(
format!("{}", PatternCategory::TypeInference),
"Type Inference"
);
assert_eq!(
format!("{}", PatternCategory::ExternalDeps),
"External Dependencies"
);
}
#[test]
fn test_error_cluster_debug() {
let cluster = create_test_cluster("1", "E0308", 10);
let debug_str = format!("{:?}", cluster);
assert!(debug_str.contains("ErrorCluster"));
assert!(debug_str.contains("E0308"));
}
#[test]
fn test_error_cluster_clone() {
let cluster = create_test_cluster("orig", "E0432", 20);
let cloned = cluster.clone();
assert_eq!(cloned.id, "orig");
assert_eq!(cloned.error_code, "E0432");
assert_eq!(cloned.frequency, 20);
}
#[test]
fn test_failure_pattern_debug() {
let planner = HuntPlanner::new();
let cluster = create_test_cluster("1", "E0308", 10);
let pattern = planner.cluster_to_pattern(&cluster);
let debug_str = format!("{:?}", pattern);
assert!(debug_str.contains("FailurePattern"));
assert!(debug_str.contains("E0308"));
}
#[test]
fn test_failure_pattern_clone() {
let planner = HuntPlanner::new();
let cluster = create_test_cluster("1", "E0277", 15);
let pattern = planner.cluster_to_pattern(&cluster);
let cloned = pattern.clone();
assert_eq!(cloned.error_code, "E0277");
assert_eq!(cloned.affected_count, 15);
}
#[test]
fn test_pattern_category_debug() {
let cat = PatternCategory::TypeInference;
let debug_str = format!("{:?}", cat);
assert!(debug_str.contains("TypeInference"));
}
#[test]
fn test_pattern_category_copy() {
let cat = PatternCategory::Borrowing;
let copied = cat;
assert_eq!(copied, PatternCategory::Borrowing);
}
#[test]
fn test_pattern_category_eq() {
assert_eq!(PatternCategory::ControlFlow, PatternCategory::ControlFlow);
assert_ne!(PatternCategory::ControlFlow, PatternCategory::Borrowing);
}
#[test]
fn test_pattern_category_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(PatternCategory::TypeInference);
set.insert(PatternCategory::TypeInference);
set.insert(PatternCategory::ExternalDeps);
assert_eq!(set.len(), 2);
}
#[test]
fn test_pattern_category_display_all() {
assert_eq!(format!("{}", PatternCategory::Borrowing), "Borrowing");
assert_eq!(format!("{}", PatternCategory::ControlFlow), "Control Flow");
assert_eq!(
format!("{}", PatternCategory::Miscellaneous),
"Miscellaneous"
);
}
#[test]
fn test_prioritized_pattern_debug() {
let planner = HuntPlanner::new();
let cluster = create_test_cluster("1", "E0308", 10);
let pattern = planner.cluster_to_pattern(&cluster);
let prioritized = PrioritizedPattern {
pattern,
priority: 5.0,
};
let debug_str = format!("{:?}", prioritized);
assert!(debug_str.contains("PrioritizedPattern"));
assert!(debug_str.contains("priority"));
}
#[test]
fn test_prioritized_pattern_clone() {
let planner = HuntPlanner::new();
let cluster = create_test_cluster("1", "E0432", 25);
let pattern = planner.cluster_to_pattern(&cluster);
let prioritized = PrioritizedPattern {
pattern,
priority: 12.5,
};
let cloned = prioritized.clone();
assert_eq!(cloned.priority, 12.5);
assert_eq!(cloned.pattern.error_code, "E0432");
}
#[test]
fn test_prioritized_pattern_eq() {
let planner = HuntPlanner::new();
let cluster1 = create_test_cluster("1", "E0308", 10);
let cluster2 = create_test_cluster("2", "E0432", 20);
let pattern1 = planner.cluster_to_pattern(&cluster1);
let pattern2 = planner.cluster_to_pattern(&cluster2);
let p1 = PrioritizedPattern {
pattern: pattern1,
priority: 5.0,
};
let p2 = PrioritizedPattern {
pattern: pattern2,
priority: 5.0,
};
assert_eq!(p1, p2); }
#[test]
fn test_prioritized_pattern_ord() {
let planner = HuntPlanner::new();
let cluster = create_test_cluster("1", "E0308", 10);
let pattern = planner.cluster_to_pattern(&cluster);
let low = PrioritizedPattern {
pattern: pattern.clone(),
priority: 1.0,
};
let high = PrioritizedPattern {
pattern,
priority: 10.0,
};
assert!(high > low);
assert!(low < high);
}
#[test]
fn test_hunt_planner_default() {
let planner: HuntPlanner = Default::default();
assert!(planner.clusters().is_empty());
assert_eq!(planner.remaining_count(), 0);
}
#[test]
fn test_hunt_planner_debug() {
let planner = HuntPlanner::new();
let debug_str = format!("{:?}", planner);
assert!(debug_str.contains("HuntPlanner"));
}
#[test]
fn test_remaining_count_after_selection() {
let mut planner = HuntPlanner::new();
planner.add_clusters(vec![
create_test_cluster("1", "E0308", 10),
create_test_cluster("2", "E0432", 20),
]);
planner.build_priority_queue();
assert_eq!(planner.remaining_count(), 2);
planner.select_next_target();
assert_eq!(planner.remaining_count(), 1);
planner.select_next_target();
assert_eq!(planner.remaining_count(), 0);
}
#[test]
fn test_calculate_priority() {
let planner = HuntPlanner::new();
let mut cluster = create_test_cluster("1", "E0308", 100);
cluster.frequency = 100;
let pattern = planner.cluster_to_pattern(&cluster);
let priority = planner.calculate_priority(&pattern);
assert!(priority > 200.0);
}
#[test]
fn test_calculate_priority_zero_complexity() {
let planner = HuntPlanner::new();
let pattern = FailurePattern {
id: "test".to_string(),
error_code: "E0000".to_string(),
description: "test".to_string(),
category: PatternCategory::Miscellaneous,
affected_count: 10,
fix_complexity: 0, trigger_example: String::new(),
};
let priority = planner.calculate_priority(&pattern);
assert!(priority > 0.0);
}
#[test]
fn test_categorize_all_error_codes() {
let planner = HuntPlanner::new();
assert_eq!(
planner.categorize_error("E0282"),
PatternCategory::TypeInference
);
assert_eq!(
planner.categorize_error("E0433"),
PatternCategory::ExternalDeps
);
assert_eq!(
planner.categorize_error("E0503"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0505"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0506"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0507"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0382"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0383"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0106"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0621"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0623"),
PatternCategory::Borrowing
);
assert_eq!(
planner.categorize_error("E0005"),
PatternCategory::ControlFlow
);
}
#[test]
fn test_estimate_complexity_all_codes() {
let planner = HuntPlanner::new();
assert!(planner.estimate_complexity("E0433") <= 3);
let e0308_complexity = planner.estimate_complexity("E0308");
assert!((3..=5).contains(&e0308_complexity));
assert!(planner.estimate_complexity("E0503") >= 6);
assert!(planner.estimate_complexity("E0505") >= 6);
assert!(planner.estimate_complexity("E0621") >= 7);
assert!(planner.estimate_complexity("E0623") >= 7);
}
#[test]
fn test_cluster_to_pattern_with_examples() {
let planner = HuntPlanner::new();
let mut cluster = create_test_cluster("1", "E0308", 10);
cluster.examples = vec!["first example".to_string(), "second".to_string()];
let pattern = planner.cluster_to_pattern(&cluster);
assert_eq!(pattern.trigger_example, "first example");
}
#[test]
fn test_cluster_to_pattern_no_examples() {
let planner = HuntPlanner::new();
let mut cluster = create_test_cluster("1", "E0308", 10);
cluster.examples.clear();
let pattern = planner.cluster_to_pattern(&cluster);
assert!(pattern.trigger_example.is_empty());
}
#[test]
fn test_select_empty_queue() {
let mut planner = HuntPlanner::new();
assert!(planner.select_next_target().is_none());
}
}