1use std::cmp::Ordering;
9use std::collections::{BinaryHeap, HashMap};
10
11#[derive(Debug, Clone)]
13pub struct ErrorCluster {
14 pub id: String,
16 pub error_code: String,
18 pub description: String,
20 pub frequency: u32,
22 pub severity: u8,
24 pub examples: Vec<String>,
26}
27
28#[derive(Debug, Clone)]
30pub struct FailurePattern {
31 pub id: String,
33 pub error_code: String,
35 pub description: String,
37 pub category: PatternCategory,
39 pub affected_count: u32,
41 pub fix_complexity: u8,
43 pub trigger_example: String,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub enum PatternCategory {
50 TypeInference,
52 ExternalDeps,
54 Borrowing,
56 ControlFlow,
58 Miscellaneous,
60}
61
62impl std::fmt::Display for PatternCategory {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 PatternCategory::TypeInference => write!(f, "Type Inference"),
66 PatternCategory::ExternalDeps => write!(f, "External Dependencies"),
67 PatternCategory::Borrowing => write!(f, "Borrowing"),
68 PatternCategory::ControlFlow => write!(f, "Control Flow"),
69 PatternCategory::Miscellaneous => write!(f, "Miscellaneous"),
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct PrioritizedPattern {
77 pub pattern: FailurePattern,
78 pub priority: f64,
80}
81
82impl PartialEq for PrioritizedPattern {
83 fn eq(&self, other: &Self) -> bool {
84 self.priority == other.priority
85 }
86}
87
88impl Eq for PrioritizedPattern {}
89
90impl PartialOrd for PrioritizedPattern {
91 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
92 Some(self.cmp(other))
93 }
94}
95
96impl Ord for PrioritizedPattern {
97 fn cmp(&self, other: &Self) -> Ordering {
98 self.priority
100 .partial_cmp(&other.priority)
101 .unwrap_or(Ordering::Equal)
102 }
103}
104
105#[derive(Debug)]
109pub struct HuntPlanner {
110 error_clusters: Vec<ErrorCluster>,
112 priority_queue: BinaryHeap<PrioritizedPattern>,
114 processed: HashMap<String, bool>,
116}
117
118impl HuntPlanner {
119 pub fn new() -> Self {
121 Self {
122 error_clusters: Vec::new(),
123 priority_queue: BinaryHeap::new(),
124 processed: HashMap::new(),
125 }
126 }
127
128 pub fn add_clusters(&mut self, clusters: Vec<ErrorCluster>) {
130 self.error_clusters.extend(clusters);
131 }
132
133 pub fn build_priority_queue(&mut self) {
137 for cluster in &self.error_clusters {
138 let pattern = self.cluster_to_pattern(cluster);
139 let priority = self.calculate_priority(&pattern);
140
141 self.priority_queue
142 .push(PrioritizedPattern { pattern, priority });
143 }
144 }
145
146 pub fn select_next_target(&mut self) -> Option<FailurePattern> {
151 while let Some(prioritized) = self.priority_queue.pop() {
152 let pattern_id = &prioritized.pattern.id;
153
154 if self.processed.get(pattern_id).copied().unwrap_or(false) {
156 continue;
157 }
158
159 self.processed.insert(pattern_id.clone(), true);
160 return Some(prioritized.pattern);
161 }
162 None
163 }
164
165 fn calculate_priority(&self, pattern: &FailurePattern) -> f64 {
170 let frequency = pattern.affected_count as f64;
171 let complexity = pattern.fix_complexity as f64;
172
173 let complexity = complexity.max(1.0);
175
176 (frequency * 10.0) / complexity
177 }
178
179 fn cluster_to_pattern(&self, cluster: &ErrorCluster) -> FailurePattern {
181 let category = self.categorize_error(&cluster.error_code);
182
183 FailurePattern {
184 id: format!("pattern_{}", cluster.id),
185 error_code: cluster.error_code.clone(),
186 description: cluster.description.clone(),
187 category,
188 affected_count: cluster.frequency,
189 fix_complexity: self.estimate_complexity(&cluster.error_code),
190 trigger_example: cluster.examples.first().cloned().unwrap_or_default(),
191 }
192 }
193
194 fn categorize_error(&self, error_code: &str) -> PatternCategory {
196 match error_code {
197 "E0308" | "E0277" | "E0282" => PatternCategory::TypeInference,
199 "E0432" | "E0433" => PatternCategory::ExternalDeps,
201 "E0502" | "E0503" | "E0505" | "E0506" | "E0507" => PatternCategory::Borrowing,
203 "E0382" | "E0383" => PatternCategory::Borrowing,
205 "E0106" | "E0621" | "E0623" => PatternCategory::Borrowing,
207 "E0004" | "E0005" => PatternCategory::ControlFlow,
209 _ => PatternCategory::Miscellaneous,
211 }
212 }
213
214 fn estimate_complexity(&self, error_code: &str) -> u8 {
216 match error_code {
217 "E0432" | "E0433" => 2,
219 "E0308" => 4,
221 "E0277" => 5,
223 "E0502" | "E0503" | "E0505" | "E0506" | "E0507" => 7,
225 "E0106" | "E0621" | "E0623" => 8,
227 _ => 5,
229 }
230 }
231
232 pub fn remaining_count(&self) -> usize {
234 self.priority_queue.len()
235 }
236
237 pub fn clusters(&self) -> &[ErrorCluster] {
239 &self.error_clusters
240 }
241}
242
243impl Default for HuntPlanner {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 fn create_test_cluster(id: &str, code: &str, freq: u32) -> ErrorCluster {
254 ErrorCluster {
255 id: id.to_string(),
256 error_code: code.to_string(),
257 description: format!("Test error {}", code),
258 frequency: freq,
259 severity: 5,
260 examples: vec!["example".to_string()],
261 }
262 }
263
264 #[test]
265 fn test_planner_new() {
266 let planner = HuntPlanner::new();
267 assert_eq!(planner.remaining_count(), 0);
268 assert!(planner.clusters().is_empty());
269 }
270
271 #[test]
272 fn test_add_clusters() {
273 let mut planner = HuntPlanner::new();
274 planner.add_clusters(vec![
275 create_test_cluster("1", "E0308", 10),
276 create_test_cluster("2", "E0432", 20),
277 ]);
278 assert_eq!(planner.clusters().len(), 2);
279 }
280
281 #[test]
282 fn test_build_priority_queue() {
283 let mut planner = HuntPlanner::new();
284 planner.add_clusters(vec![
285 create_test_cluster("1", "E0308", 10),
286 create_test_cluster("2", "E0432", 20),
287 ]);
288 planner.build_priority_queue();
289 assert_eq!(planner.remaining_count(), 2);
290 }
291
292 #[test]
293 fn test_select_next_target_priority_order() {
294 let mut planner = HuntPlanner::new();
295 planner.add_clusters(vec![
296 create_test_cluster("low", "E0308", 5), create_test_cluster("high", "E0432", 50), ]);
299 planner.build_priority_queue();
300
301 let first = planner.select_next_target().unwrap();
303 assert_eq!(first.error_code, "E0432"); }
305
306 #[test]
307 fn test_select_next_target_no_duplicates() {
308 let mut planner = HuntPlanner::new();
309 planner.add_clusters(vec![create_test_cluster("1", "E0308", 10)]);
310 planner.build_priority_queue();
311
312 assert!(planner.select_next_target().is_some());
313 assert!(planner.select_next_target().is_none()); }
315
316 #[test]
317 fn test_categorize_error() {
318 let planner = HuntPlanner::new();
319
320 assert_eq!(
321 planner.categorize_error("E0308"),
322 PatternCategory::TypeInference
323 );
324 assert_eq!(
325 planner.categorize_error("E0432"),
326 PatternCategory::ExternalDeps
327 );
328 assert_eq!(
329 planner.categorize_error("E0502"),
330 PatternCategory::Borrowing
331 );
332 assert_eq!(
333 planner.categorize_error("E0004"),
334 PatternCategory::ControlFlow
335 );
336 assert_eq!(
337 planner.categorize_error("E9999"),
338 PatternCategory::Miscellaneous
339 );
340 }
341
342 #[test]
343 fn test_estimate_complexity() {
344 let planner = HuntPlanner::new();
345
346 assert!(planner.estimate_complexity("E0432") < 5);
348 assert!(planner.estimate_complexity("E0106") > 5);
350 }
351
352 #[test]
353 fn test_pattern_category_display() {
354 assert_eq!(
355 format!("{}", PatternCategory::TypeInference),
356 "Type Inference"
357 );
358 assert_eq!(
359 format!("{}", PatternCategory::ExternalDeps),
360 "External Dependencies"
361 );
362 }
363
364 #[test]
367 fn test_error_cluster_debug() {
368 let cluster = create_test_cluster("1", "E0308", 10);
369 let debug_str = format!("{:?}", cluster);
370 assert!(debug_str.contains("ErrorCluster"));
371 assert!(debug_str.contains("E0308"));
372 }
373
374 #[test]
375 fn test_error_cluster_clone() {
376 let cluster = create_test_cluster("orig", "E0432", 20);
377 let cloned = cluster.clone();
378 assert_eq!(cloned.id, "orig");
379 assert_eq!(cloned.error_code, "E0432");
380 assert_eq!(cloned.frequency, 20);
381 }
382
383 #[test]
384 fn test_failure_pattern_debug() {
385 let planner = HuntPlanner::new();
386 let cluster = create_test_cluster("1", "E0308", 10);
387 let pattern = planner.cluster_to_pattern(&cluster);
388
389 let debug_str = format!("{:?}", pattern);
390 assert!(debug_str.contains("FailurePattern"));
391 assert!(debug_str.contains("E0308"));
392 }
393
394 #[test]
395 fn test_failure_pattern_clone() {
396 let planner = HuntPlanner::new();
397 let cluster = create_test_cluster("1", "E0277", 15);
398 let pattern = planner.cluster_to_pattern(&cluster);
399 let cloned = pattern.clone();
400
401 assert_eq!(cloned.error_code, "E0277");
402 assert_eq!(cloned.affected_count, 15);
403 }
404
405 #[test]
406 fn test_pattern_category_debug() {
407 let cat = PatternCategory::TypeInference;
408 let debug_str = format!("{:?}", cat);
409 assert!(debug_str.contains("TypeInference"));
410 }
411
412 #[test]
413 fn test_pattern_category_copy() {
414 let cat = PatternCategory::Borrowing;
415 let copied = cat;
416 assert_eq!(copied, PatternCategory::Borrowing);
417 }
418
419 #[test]
420 fn test_pattern_category_eq() {
421 assert_eq!(PatternCategory::ControlFlow, PatternCategory::ControlFlow);
422 assert_ne!(PatternCategory::ControlFlow, PatternCategory::Borrowing);
423 }
424
425 #[test]
426 fn test_pattern_category_hash() {
427 use std::collections::HashSet;
428 let mut set = HashSet::new();
429 set.insert(PatternCategory::TypeInference);
430 set.insert(PatternCategory::TypeInference);
431 set.insert(PatternCategory::ExternalDeps);
432 assert_eq!(set.len(), 2);
433 }
434
435 #[test]
436 fn test_pattern_category_display_all() {
437 assert_eq!(format!("{}", PatternCategory::Borrowing), "Borrowing");
438 assert_eq!(format!("{}", PatternCategory::ControlFlow), "Control Flow");
439 assert_eq!(
440 format!("{}", PatternCategory::Miscellaneous),
441 "Miscellaneous"
442 );
443 }
444
445 #[test]
446 fn test_prioritized_pattern_debug() {
447 let planner = HuntPlanner::new();
448 let cluster = create_test_cluster("1", "E0308", 10);
449 let pattern = planner.cluster_to_pattern(&cluster);
450 let prioritized = PrioritizedPattern {
451 pattern,
452 priority: 5.0,
453 };
454
455 let debug_str = format!("{:?}", prioritized);
456 assert!(debug_str.contains("PrioritizedPattern"));
457 assert!(debug_str.contains("priority"));
458 }
459
460 #[test]
461 fn test_prioritized_pattern_clone() {
462 let planner = HuntPlanner::new();
463 let cluster = create_test_cluster("1", "E0432", 25);
464 let pattern = planner.cluster_to_pattern(&cluster);
465 let prioritized = PrioritizedPattern {
466 pattern,
467 priority: 12.5,
468 };
469 let cloned = prioritized.clone();
470
471 assert_eq!(cloned.priority, 12.5);
472 assert_eq!(cloned.pattern.error_code, "E0432");
473 }
474
475 #[test]
476 fn test_prioritized_pattern_eq() {
477 let planner = HuntPlanner::new();
478 let cluster1 = create_test_cluster("1", "E0308", 10);
479 let cluster2 = create_test_cluster("2", "E0432", 20);
480 let pattern1 = planner.cluster_to_pattern(&cluster1);
481 let pattern2 = planner.cluster_to_pattern(&cluster2);
482
483 let p1 = PrioritizedPattern {
484 pattern: pattern1,
485 priority: 5.0,
486 };
487 let p2 = PrioritizedPattern {
488 pattern: pattern2,
489 priority: 5.0,
490 };
491
492 assert_eq!(p1, p2); }
494
495 #[test]
496 fn test_prioritized_pattern_ord() {
497 let planner = HuntPlanner::new();
498 let cluster = create_test_cluster("1", "E0308", 10);
499 let pattern = planner.cluster_to_pattern(&cluster);
500
501 let low = PrioritizedPattern {
502 pattern: pattern.clone(),
503 priority: 1.0,
504 };
505 let high = PrioritizedPattern {
506 pattern,
507 priority: 10.0,
508 };
509
510 assert!(high > low);
511 assert!(low < high);
512 }
513
514 #[test]
515 fn test_hunt_planner_default() {
516 let planner: HuntPlanner = Default::default();
517 assert!(planner.clusters().is_empty());
518 assert_eq!(planner.remaining_count(), 0);
519 }
520
521 #[test]
522 fn test_hunt_planner_debug() {
523 let planner = HuntPlanner::new();
524 let debug_str = format!("{:?}", planner);
525 assert!(debug_str.contains("HuntPlanner"));
526 }
527
528 #[test]
529 fn test_remaining_count_after_selection() {
530 let mut planner = HuntPlanner::new();
531 planner.add_clusters(vec![
532 create_test_cluster("1", "E0308", 10),
533 create_test_cluster("2", "E0432", 20),
534 ]);
535 planner.build_priority_queue();
536
537 assert_eq!(planner.remaining_count(), 2);
538 planner.select_next_target();
539 assert_eq!(planner.remaining_count(), 1);
540 planner.select_next_target();
541 assert_eq!(planner.remaining_count(), 0);
542 }
543
544 #[test]
545 fn test_calculate_priority() {
546 let planner = HuntPlanner::new();
547 let mut cluster = create_test_cluster("1", "E0308", 100);
548 cluster.frequency = 100;
549 let pattern = planner.cluster_to_pattern(&cluster);
550
551 let priority = planner.calculate_priority(&pattern);
554 assert!(priority > 200.0);
555 }
556
557 #[test]
558 fn test_calculate_priority_zero_complexity() {
559 let planner = HuntPlanner::new();
560 let pattern = FailurePattern {
561 id: "test".to_string(),
562 error_code: "E0000".to_string(),
563 description: "test".to_string(),
564 category: PatternCategory::Miscellaneous,
565 affected_count: 10,
566 fix_complexity: 0, trigger_example: String::new(),
568 };
569
570 let priority = planner.calculate_priority(&pattern);
572 assert!(priority > 0.0);
573 }
574
575 #[test]
576 fn test_categorize_all_error_codes() {
577 let planner = HuntPlanner::new();
578
579 assert_eq!(
581 planner.categorize_error("E0282"),
582 PatternCategory::TypeInference
583 );
584
585 assert_eq!(
587 planner.categorize_error("E0433"),
588 PatternCategory::ExternalDeps
589 );
590
591 assert_eq!(
593 planner.categorize_error("E0503"),
594 PatternCategory::Borrowing
595 );
596 assert_eq!(
597 planner.categorize_error("E0505"),
598 PatternCategory::Borrowing
599 );
600 assert_eq!(
601 planner.categorize_error("E0506"),
602 PatternCategory::Borrowing
603 );
604 assert_eq!(
605 planner.categorize_error("E0507"),
606 PatternCategory::Borrowing
607 );
608 assert_eq!(
609 planner.categorize_error("E0382"),
610 PatternCategory::Borrowing
611 );
612 assert_eq!(
613 planner.categorize_error("E0383"),
614 PatternCategory::Borrowing
615 );
616
617 assert_eq!(
619 planner.categorize_error("E0106"),
620 PatternCategory::Borrowing
621 );
622 assert_eq!(
623 planner.categorize_error("E0621"),
624 PatternCategory::Borrowing
625 );
626 assert_eq!(
627 planner.categorize_error("E0623"),
628 PatternCategory::Borrowing
629 );
630
631 assert_eq!(
633 planner.categorize_error("E0005"),
634 PatternCategory::ControlFlow
635 );
636 }
637
638 #[test]
639 fn test_estimate_complexity_all_codes() {
640 let planner = HuntPlanner::new();
641
642 assert!(planner.estimate_complexity("E0433") <= 3);
644
645 let e0308_complexity = planner.estimate_complexity("E0308");
647 assert!((3..=5).contains(&e0308_complexity));
648
649 assert!(planner.estimate_complexity("E0503") >= 6);
651 assert!(planner.estimate_complexity("E0505") >= 6);
652
653 assert!(planner.estimate_complexity("E0621") >= 7);
655 assert!(planner.estimate_complexity("E0623") >= 7);
656 }
657
658 #[test]
659 fn test_cluster_to_pattern_with_examples() {
660 let planner = HuntPlanner::new();
661 let mut cluster = create_test_cluster("1", "E0308", 10);
662 cluster.examples = vec!["first example".to_string(), "second".to_string()];
663
664 let pattern = planner.cluster_to_pattern(&cluster);
665 assert_eq!(pattern.trigger_example, "first example");
666 }
667
668 #[test]
669 fn test_cluster_to_pattern_no_examples() {
670 let planner = HuntPlanner::new();
671 let mut cluster = create_test_cluster("1", "E0308", 10);
672 cluster.examples.clear();
673
674 let pattern = planner.cluster_to_pattern(&cluster);
675 assert!(pattern.trigger_example.is_empty());
676 }
677
678 #[test]
679 fn test_select_empty_queue() {
680 let mut planner = HuntPlanner::new();
681 assert!(planner.select_next_target().is_none());
682 }
683}