1use crate::ml_features::{InferredOwnership, OwnershipDefect};
39use serde::{Deserialize, Serialize};
40use std::collections::HashMap;
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct InferenceError {
45 pub id: u64,
47 pub variable: String,
49 pub source_file: String,
51 pub source_line: u32,
53 pub predicted: InferredOwnership,
55 pub expected: InferredOwnership,
57 pub confidence: f64,
59 pub c_features: Vec<String>,
61 pub rust_error: Option<String>,
63 pub defect: OwnershipDefect,
65 pub timestamp: u64,
67}
68
69impl InferenceError {
70 pub fn new(
72 variable: impl Into<String>,
73 source_file: impl Into<String>,
74 source_line: u32,
75 predicted: InferredOwnership,
76 expected: InferredOwnership,
77 confidence: f64,
78 defect: OwnershipDefect,
79 ) -> Self {
80 let now = std::time::SystemTime::now()
81 .duration_since(std::time::UNIX_EPOCH)
82 .unwrap_or_default()
83 .as_millis() as u64;
84
85 Self {
86 id: 0,
87 variable: variable.into(),
88 source_file: source_file.into(),
89 source_line,
90 predicted,
91 expected,
92 confidence: confidence.clamp(0.0, 1.0),
93 c_features: Vec::new(),
94 rust_error: None,
95 defect,
96 timestamp: now,
97 }
98 }
99
100 pub fn with_features(mut self, features: Vec<String>) -> Self {
102 self.c_features = features;
103 self
104 }
105
106 pub fn with_rust_error(mut self, error: impl Into<String>) -> Self {
108 self.rust_error = Some(error.into());
109 self
110 }
111}
112
113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
115pub struct PatternStats {
116 pub count: u64,
118 pub failure_count: u64,
120 pub success_count: u64,
122 pub suspiciousness: f64,
124}
125
126impl PatternStats {
127 pub fn failure_rate(&self) -> f64 {
129 if self.count == 0 {
130 0.0
131 } else {
132 self.failure_count as f64 / self.count as f64
133 }
134 }
135
136 pub fn record(&mut self, is_failure: bool) {
138 self.count += 1;
139 if is_failure {
140 self.failure_count += 1;
141 } else {
142 self.success_count += 1;
143 }
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct FeatureSuspiciousness {
150 pub feature: String,
152 pub score: f64,
154 pub total_count: u64,
156 pub failure_count: u64,
158 pub success_count: u64,
160}
161
162impl FeatureSuspiciousness {
163 pub fn is_suspicious(&self) -> bool {
165 self.score > 0.5
166 }
167
168 pub fn is_highly_suspicious(&self) -> bool {
170 self.score > 0.7
171 }
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct ErrorTracker {
177 errors: Vec<InferenceError>,
179 feature_stats: HashMap<String, PatternStats>,
181 defect_stats: HashMap<String, PatternStats>,
183 feature_defect_stats: HashMap<(String, String), PatternStats>,
185 total_successes: u64,
187 total_failures: u64,
189 next_id: u64,
191}
192
193impl Default for ErrorTracker {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199impl ErrorTracker {
200 pub fn new() -> Self {
202 Self {
203 errors: Vec::new(),
204 feature_stats: HashMap::new(),
205 defect_stats: HashMap::new(),
206 feature_defect_stats: HashMap::new(),
207 total_successes: 0,
208 total_failures: 0,
209 next_id: 1,
210 }
211 }
212
213 pub fn record_error(&mut self, mut error: InferenceError) {
215 error.id = self.next_id;
216 self.next_id += 1;
217 self.total_failures += 1;
218
219 for feature in &error.c_features {
221 self.feature_stats
222 .entry(feature.clone())
223 .or_default()
224 .record(true);
225 }
226
227 let defect_key = format!("{:?}", error.defect);
229 self.defect_stats
230 .entry(defect_key.clone())
231 .or_default()
232 .record(true);
233
234 for feature in &error.c_features {
236 self.feature_defect_stats
237 .entry((feature.clone(), defect_key.clone()))
238 .or_default()
239 .record(true);
240 }
241
242 self.errors.push(error);
243 }
244
245 pub fn record_success(&mut self, c_features: &[String]) {
247 self.total_successes += 1;
248
249 for feature in c_features {
250 self.feature_stats
251 .entry(feature.clone())
252 .or_default()
253 .record(false);
254 }
255 }
256
257 pub fn errors(&self) -> &[InferenceError] {
259 &self.errors
260 }
261
262 pub fn error_count(&self) -> usize {
264 self.errors.len()
265 }
266
267 pub fn success_count(&self) -> u64 {
269 self.total_successes
270 }
271
272 pub fn calculate_suspiciousness(&mut self) -> Vec<FeatureSuspiciousness> {
277 let total_failed = self.total_failures.max(1) as f64;
278 let total_passed = self.total_successes.max(1) as f64;
279
280 let mut results = Vec::new();
281
282 for (feature, stats) in &mut self.feature_stats {
283 let failed_ratio = stats.failure_count as f64 / total_failed;
284 let passed_ratio = stats.success_count as f64 / total_passed;
285
286 let suspiciousness = if failed_ratio + passed_ratio > 0.0 {
287 failed_ratio / (failed_ratio + passed_ratio)
288 } else {
289 0.0
290 };
291
292 stats.suspiciousness = suspiciousness;
293
294 results.push(FeatureSuspiciousness {
295 feature: feature.clone(),
296 score: suspiciousness,
297 total_count: stats.count,
298 failure_count: stats.failure_count,
299 success_count: stats.success_count,
300 });
301 }
302
303 results.sort_by(|a, b| {
305 b.score
306 .partial_cmp(&a.score)
307 .unwrap_or(std::cmp::Ordering::Equal)
308 });
309
310 results
311 }
312
313 pub fn top_suspicious(&mut self, n: usize) -> Vec<FeatureSuspiciousness> {
315 let all = self.calculate_suspiciousness();
316 all.into_iter().take(n).collect()
317 }
318
319 pub fn errors_by_defect(&self, defect: &OwnershipDefect) -> Vec<&InferenceError> {
321 let defect_key = format!("{:?}", defect);
322 self.errors
323 .iter()
324 .filter(|e| format!("{:?}", e.defect) == defect_key)
325 .collect()
326 }
327
328 pub fn errors_by_feature(&self, feature: &str) -> Vec<&InferenceError> {
330 self.errors
331 .iter()
332 .filter(|e| e.c_features.contains(&feature.to_string()))
333 .collect()
334 }
335
336 pub fn defect_distribution(&self) -> HashMap<String, u64> {
338 let mut dist = HashMap::new();
339 for error in &self.errors {
340 *dist.entry(format!("{:?}", error.defect)).or_insert(0) += 1;
341 }
342 dist
343 }
344
345 pub fn feature_distribution(&self) -> HashMap<String, u64> {
347 let mut dist = HashMap::new();
348 for error in &self.errors {
349 for feature in &error.c_features {
350 *dist.entry(feature.clone()).or_insert(0) += 1;
351 }
352 }
353 dist
354 }
355
356 pub fn feature_defect_correlation(&self) -> Vec<(String, String, u64)> {
358 self.feature_defect_stats
359 .iter()
360 .map(|((f, d), stats)| (f.clone(), d.clone(), stats.failure_count))
361 .collect()
362 }
363
364 pub fn generate_suggestions(&mut self) -> Vec<ImprovementSuggestion> {
366 let suspicious = self.top_suspicious(5);
367 let defect_dist = self.defect_distribution();
368
369 let mut suggestions = Vec::new();
370
371 for fs in suspicious {
373 if fs.is_highly_suspicious() {
374 suggestions.push(ImprovementSuggestion {
375 priority: SuggestionPriority::High,
376 category: SuggestionCategory::FeatureHandling,
377 description: format!(
378 "Improve handling of '{}' (suspiciousness: {:.2}, {} failures)",
379 fs.feature, fs.score, fs.failure_count
380 ),
381 affected_feature: Some(fs.feature),
382 affected_defect: None,
383 });
384 }
385 }
386
387 let mut defects: Vec<_> = defect_dist.into_iter().collect();
389 defects.sort_by(|a, b| b.1.cmp(&a.1));
390
391 for (defect, count) in defects.iter().take(3) {
392 if *count > 5 {
393 suggestions.push(ImprovementSuggestion {
394 priority: if *count > 20 {
395 SuggestionPriority::High
396 } else {
397 SuggestionPriority::Medium
398 },
399 category: SuggestionCategory::DefectPrevention,
400 description: format!(
401 "Address {} defect category ({} occurrences)",
402 defect, count
403 ),
404 affected_feature: None,
405 affected_defect: Some(defect.clone()),
406 });
407 }
408 }
409
410 suggestions
411 }
412
413 pub fn generate_markdown_report(&mut self) -> String {
415 let suspicious = self.top_suspicious(10);
416 let defect_dist = self.defect_distribution();
417 let suggestions = self.generate_suggestions();
418
419 let mut report = String::from("## Error Tracking Report (CITL Analysis)\n\n");
420
421 report.push_str(&format!(
423 "### Summary\n\n\
424 | Metric | Value |\n\
425 |--------|-------|\n\
426 | Total Errors | {} |\n\
427 | Total Successes | {} |\n\
428 | Error Rate | {:.1}% |\n\n",
429 self.error_count(),
430 self.success_count(),
431 if self.error_count() + self.success_count() as usize > 0 {
432 (self.error_count() as f64
433 / (self.error_count() + self.success_count() as usize) as f64)
434 * 100.0
435 } else {
436 0.0
437 }
438 ));
439
440 report.push_str("### Top Suspicious Features (Tarantula)\n\n");
442 report.push_str("| Feature | Score | Failures | Successes |\n");
443 report.push_str("|---------|-------|----------|----------|\n");
444 for fs in suspicious.iter().take(5) {
445 report.push_str(&format!(
446 "| {} | {:.2} | {} | {} |\n",
447 fs.feature, fs.score, fs.failure_count, fs.success_count
448 ));
449 }
450 report.push('\n');
451
452 report.push_str("### Defect Distribution\n\n");
454 let mut defects: Vec<_> = defect_dist.into_iter().collect();
455 defects.sort_by(|a, b| b.1.cmp(&a.1));
456 for (defect, count) in defects.iter().take(5) {
457 report.push_str(&format!(
458 "- {}: {} ({:.1}%)\n",
459 defect,
460 count,
461 (*count as f64 / self.error_count().max(1) as f64) * 100.0
462 ));
463 }
464 report.push('\n');
465
466 if !suggestions.is_empty() {
468 report.push_str("### Improvement Suggestions\n\n");
469 for (i, s) in suggestions.iter().enumerate() {
470 report.push_str(&format!(
471 "{}. **[{:?}]** {}\n",
472 i + 1,
473 s.priority,
474 s.description
475 ));
476 }
477 }
478
479 report
480 }
481}
482
483#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
485pub enum SuggestionPriority {
486 Critical,
488 High,
490 Medium,
492 Low,
494}
495
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
498pub enum SuggestionCategory {
499 FeatureHandling,
501 DefectPrevention,
503 TrainingData,
505 Configuration,
507}
508
509#[derive(Debug, Clone, Serialize, Deserialize)]
511pub struct ImprovementSuggestion {
512 pub priority: SuggestionPriority,
514 pub category: SuggestionCategory,
516 pub description: String,
518 pub affected_feature: Option<String>,
520 pub affected_defect: Option<String>,
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
533 fn inference_error_new() {
534 let error = InferenceError::new(
535 "ptr",
536 "test.c",
537 42,
538 InferredOwnership::Borrowed,
539 InferredOwnership::Owned,
540 0.6,
541 OwnershipDefect::PointerMisclassification,
542 );
543
544 assert_eq!(error.variable, "ptr");
545 assert_eq!(error.source_file, "test.c");
546 assert_eq!(error.source_line, 42);
547 }
548
549 #[test]
550 fn inference_error_with_features() {
551 let error = InferenceError::new(
552 "ptr",
553 "test.c",
554 42,
555 InferredOwnership::Borrowed,
556 InferredOwnership::Owned,
557 0.6,
558 OwnershipDefect::PointerMisclassification,
559 )
560 .with_features(vec![
561 "malloc_free".to_string(),
562 "pointer_arithmetic".to_string(),
563 ]);
564
565 assert_eq!(error.c_features.len(), 2);
566 }
567
568 #[test]
569 fn inference_error_with_rust_error() {
570 let error = InferenceError::new(
571 "ptr",
572 "test.c",
573 42,
574 InferredOwnership::Borrowed,
575 InferredOwnership::Owned,
576 0.6,
577 OwnershipDefect::PointerMisclassification,
578 )
579 .with_rust_error("E0382");
580
581 assert_eq!(error.rust_error, Some("E0382".to_string()));
582 }
583
584 #[test]
589 fn pattern_stats_default() {
590 let stats = PatternStats::default();
591 assert_eq!(stats.count, 0);
592 assert_eq!(stats.failure_rate(), 0.0);
593 }
594
595 #[test]
596 fn pattern_stats_record() {
597 let mut stats = PatternStats::default();
598 stats.record(true); stats.record(false); stats.record(true); assert_eq!(stats.count, 3);
603 assert_eq!(stats.failure_count, 2);
604 assert_eq!(stats.success_count, 1);
605 assert!((stats.failure_rate() - 0.666).abs() < 0.01);
606 }
607
608 #[test]
613 fn feature_suspiciousness_thresholds() {
614 let low = FeatureSuspiciousness {
615 feature: "test".to_string(),
616 score: 0.3,
617 total_count: 10,
618 failure_count: 3,
619 success_count: 7,
620 };
621 assert!(!low.is_suspicious());
622 assert!(!low.is_highly_suspicious());
623
624 let medium = FeatureSuspiciousness {
625 feature: "test".to_string(),
626 score: 0.6,
627 total_count: 10,
628 failure_count: 6,
629 success_count: 4,
630 };
631 assert!(medium.is_suspicious());
632 assert!(!medium.is_highly_suspicious());
633
634 let high = FeatureSuspiciousness {
635 feature: "test".to_string(),
636 score: 0.8,
637 total_count: 10,
638 failure_count: 8,
639 success_count: 2,
640 };
641 assert!(high.is_suspicious());
642 assert!(high.is_highly_suspicious());
643 }
644
645 #[test]
650 fn error_tracker_new() {
651 let tracker = ErrorTracker::new();
652 assert_eq!(tracker.error_count(), 0);
653 assert_eq!(tracker.success_count(), 0);
654 }
655
656 #[test]
657 fn error_tracker_record_error() {
658 let mut tracker = ErrorTracker::new();
659
660 let error = InferenceError::new(
661 "ptr",
662 "test.c",
663 42,
664 InferredOwnership::Borrowed,
665 InferredOwnership::Owned,
666 0.6,
667 OwnershipDefect::PointerMisclassification,
668 )
669 .with_features(vec!["malloc_free".to_string()]);
670
671 tracker.record_error(error);
672
673 assert_eq!(tracker.error_count(), 1);
674 assert_eq!(tracker.errors()[0].id, 1);
675 }
676
677 #[test]
678 fn error_tracker_record_success() {
679 let mut tracker = ErrorTracker::new();
680
681 tracker.record_success(&["malloc_free".to_string()]);
682
683 assert_eq!(tracker.success_count(), 1);
684 assert!(tracker.feature_stats.contains_key("malloc_free"));
685 }
686
687 #[test]
688 fn error_tracker_calculate_suspiciousness() {
689 let mut tracker = ErrorTracker::new();
690
691 for _ in 0..8 {
693 let error = InferenceError::new(
694 "ptr",
695 "test.c",
696 42,
697 InferredOwnership::Borrowed,
698 InferredOwnership::Owned,
699 0.6,
700 OwnershipDefect::PointerMisclassification,
701 )
702 .with_features(vec!["feature_a".to_string()]);
703 tracker.record_error(error);
704 }
705 for _ in 0..2 {
706 tracker.record_success(&["feature_a".to_string()]);
707 }
708
709 for _ in 0..2 {
711 let error = InferenceError::new(
712 "ptr",
713 "test.c",
714 42,
715 InferredOwnership::Borrowed,
716 InferredOwnership::Owned,
717 0.6,
718 OwnershipDefect::PointerMisclassification,
719 )
720 .with_features(vec!["feature_b".to_string()]);
721 tracker.record_error(error);
722 }
723 for _ in 0..8 {
724 tracker.record_success(&["feature_b".to_string()]);
725 }
726
727 let suspicious = tracker.calculate_suspiciousness();
728
729 let feature_a = suspicious
731 .iter()
732 .find(|s| s.feature == "feature_a")
733 .unwrap();
734 let feature_b = suspicious
735 .iter()
736 .find(|s| s.feature == "feature_b")
737 .unwrap();
738
739 assert!(feature_a.score > feature_b.score);
740 }
741
742 #[test]
743 fn error_tracker_top_suspicious() {
744 let mut tracker = ErrorTracker::new();
745
746 for (i, feature) in ["a", "b", "c"].iter().enumerate() {
748 for _ in 0..(i + 1) * 3 {
749 let error = InferenceError::new(
750 "ptr",
751 "test.c",
752 42,
753 InferredOwnership::Borrowed,
754 InferredOwnership::Owned,
755 0.6,
756 OwnershipDefect::PointerMisclassification,
757 )
758 .with_features(vec![feature.to_string()]);
759 tracker.record_error(error);
760 }
761 }
762
763 let top = tracker.top_suspicious(2);
764 assert_eq!(top.len(), 2);
765 }
766
767 #[test]
768 fn error_tracker_errors_by_defect() {
769 let mut tracker = ErrorTracker::new();
770
771 let error1 = InferenceError::new(
773 "ptr1",
774 "test.c",
775 1,
776 InferredOwnership::Borrowed,
777 InferredOwnership::Owned,
778 0.6,
779 OwnershipDefect::PointerMisclassification,
780 );
781 let error2 = InferenceError::new(
782 "ptr2",
783 "test.c",
784 2,
785 InferredOwnership::Borrowed,
786 InferredOwnership::Owned,
787 0.6,
788 OwnershipDefect::LifetimeInferenceGap,
789 );
790 let error3 = InferenceError::new(
791 "ptr3",
792 "test.c",
793 3,
794 InferredOwnership::Borrowed,
795 InferredOwnership::Owned,
796 0.6,
797 OwnershipDefect::PointerMisclassification,
798 );
799
800 tracker.record_error(error1);
801 tracker.record_error(error2);
802 tracker.record_error(error3);
803
804 let pointer_errors = tracker.errors_by_defect(&OwnershipDefect::PointerMisclassification);
805 assert_eq!(pointer_errors.len(), 2);
806 }
807
808 #[test]
809 fn error_tracker_errors_by_feature() {
810 let mut tracker = ErrorTracker::new();
811
812 let error1 = InferenceError::new(
813 "ptr1",
814 "test.c",
815 1,
816 InferredOwnership::Borrowed,
817 InferredOwnership::Owned,
818 0.6,
819 OwnershipDefect::PointerMisclassification,
820 )
821 .with_features(vec!["malloc_free".to_string()]);
822
823 let error2 = InferenceError::new(
824 "ptr2",
825 "test.c",
826 2,
827 InferredOwnership::Borrowed,
828 InferredOwnership::Owned,
829 0.6,
830 OwnershipDefect::PointerMisclassification,
831 )
832 .with_features(vec!["pointer_arithmetic".to_string()]);
833
834 tracker.record_error(error1);
835 tracker.record_error(error2);
836
837 let malloc_errors = tracker.errors_by_feature("malloc_free");
838 assert_eq!(malloc_errors.len(), 1);
839 }
840
841 #[test]
842 fn error_tracker_defect_distribution() {
843 let mut tracker = ErrorTracker::new();
844
845 for _ in 0..3 {
846 tracker.record_error(InferenceError::new(
847 "ptr",
848 "test.c",
849 1,
850 InferredOwnership::Borrowed,
851 InferredOwnership::Owned,
852 0.6,
853 OwnershipDefect::PointerMisclassification,
854 ));
855 }
856 for _ in 0..2 {
857 tracker.record_error(InferenceError::new(
858 "ptr",
859 "test.c",
860 1,
861 InferredOwnership::Borrowed,
862 InferredOwnership::Owned,
863 0.6,
864 OwnershipDefect::LifetimeInferenceGap,
865 ));
866 }
867
868 let dist = tracker.defect_distribution();
869 assert_eq!(dist.get("PointerMisclassification"), Some(&3));
870 assert_eq!(dist.get("LifetimeInferenceGap"), Some(&2));
871 }
872
873 #[test]
874 fn error_tracker_generate_suggestions() {
875 let mut tracker = ErrorTracker::new();
876
877 for _ in 0..25 {
879 let error = InferenceError::new(
880 "ptr",
881 "test.c",
882 42,
883 InferredOwnership::Borrowed,
884 InferredOwnership::Owned,
885 0.6,
886 OwnershipDefect::PointerMisclassification,
887 )
888 .with_features(vec!["problematic_feature".to_string()]);
889 tracker.record_error(error);
890 }
891
892 for _ in 0..5 {
894 tracker.record_success(&["problematic_feature".to_string()]);
895 }
896
897 let suggestions = tracker.generate_suggestions();
898 assert!(!suggestions.is_empty());
899 }
900
901 #[test]
902 fn error_tracker_generate_markdown_report() {
903 let mut tracker = ErrorTracker::new();
904
905 let error = InferenceError::new(
906 "ptr",
907 "test.c",
908 42,
909 InferredOwnership::Borrowed,
910 InferredOwnership::Owned,
911 0.6,
912 OwnershipDefect::PointerMisclassification,
913 )
914 .with_features(vec!["malloc_free".to_string()]);
915
916 tracker.record_error(error);
917 tracker.record_success(&["other_feature".to_string()]);
918
919 let md = tracker.generate_markdown_report();
920 assert!(md.contains("Error Tracking Report"));
921 assert!(md.contains("CITL Analysis"));
922 assert!(md.contains("Tarantula"));
923 }
924
925 #[test]
930 fn improvement_suggestion_structure() {
931 let suggestion = ImprovementSuggestion {
932 priority: SuggestionPriority::High,
933 category: SuggestionCategory::FeatureHandling,
934 description: "Test suggestion".to_string(),
935 affected_feature: Some("malloc_free".to_string()),
936 affected_defect: None,
937 };
938
939 assert_eq!(suggestion.priority, SuggestionPriority::High);
940 assert_eq!(suggestion.category, SuggestionCategory::FeatureHandling);
941 }
942
943 #[test]
948 fn feature_distribution_multiple_features() {
949 let mut tracker = ErrorTracker::new();
950 let error1 = InferenceError::new(
951 "ptr1", "test.c", 1,
952 InferredOwnership::Borrowed, InferredOwnership::Owned,
953 0.6, OwnershipDefect::PointerMisclassification,
954 ).with_features(vec!["malloc_free".into(), "pointer_arithmetic".into()]);
955 let error2 = InferenceError::new(
956 "ptr2", "test.c", 2,
957 InferredOwnership::Borrowed, InferredOwnership::Owned,
958 0.6, OwnershipDefect::PointerMisclassification,
959 ).with_features(vec!["malloc_free".into(), "struct_field".into()]);
960 tracker.record_error(error1);
961 tracker.record_error(error2);
962
963 let dist = tracker.feature_distribution();
964 assert_eq!(dist.get("malloc_free"), Some(&2));
965 assert_eq!(dist.get("pointer_arithmetic"), Some(&1));
966 assert_eq!(dist.get("struct_field"), Some(&1));
967 }
968
969 #[test]
970 fn feature_defect_correlation_test() {
971 let mut tracker = ErrorTracker::new();
972 let error = InferenceError::new(
973 "ptr", "test.c", 1,
974 InferredOwnership::Borrowed, InferredOwnership::Owned,
975 0.6, OwnershipDefect::PointerMisclassification,
976 ).with_features(vec!["malloc_free".into()]);
977 tracker.record_error(error);
978
979 let correlations = tracker.feature_defect_correlation();
980 assert!(!correlations.is_empty());
981 let (feature, defect, count) = &correlations[0];
982 assert_eq!(feature, "malloc_free");
983 assert!(defect.contains("PointerMisclassification"));
984 assert_eq!(*count, 1);
985 }
986
987 #[test]
988 fn calculate_suspiciousness_zero_ratio_edge() {
989 let mut tracker = ErrorTracker::new();
990 let suspicious = tracker.calculate_suspiciousness();
993 assert!(suspicious.is_empty());
994 }
995
996 #[test]
997 fn generate_suggestions_medium_priority() {
998 let mut tracker = ErrorTracker::new();
999 for i in 0..10 {
1001 tracker.record_error(InferenceError::new(
1002 format!("ptr{}", i), "test.c", i as u32,
1003 InferredOwnership::Borrowed, InferredOwnership::Owned,
1004 0.6, OwnershipDefect::LifetimeInferenceGap,
1005 ));
1006 }
1007 for _ in 0..20 {
1009 tracker.record_success(&["some_feature".into()]);
1010 }
1011
1012 let suggestions = tracker.generate_suggestions();
1013 let defect_suggestions: Vec<_> = suggestions.iter()
1014 .filter(|s| s.category == SuggestionCategory::DefectPrevention)
1015 .collect();
1016 assert!(!defect_suggestions.is_empty());
1017 assert_eq!(defect_suggestions[0].priority, SuggestionPriority::Medium);
1018 }
1019
1020 #[test]
1021 fn generate_suggestions_high_priority_defect() {
1022 let mut tracker = ErrorTracker::new();
1023 for i in 0..25 {
1025 tracker.record_error(InferenceError::new(
1026 format!("ptr{}", i), "test.c", i as u32,
1027 InferredOwnership::Borrowed, InferredOwnership::Owned,
1028 0.6, OwnershipDefect::PointerMisclassification,
1029 ));
1030 }
1031 for _ in 0..5 {
1032 tracker.record_success(&["x".into()]);
1033 }
1034
1035 let suggestions = tracker.generate_suggestions();
1036 let defect_suggestions: Vec<_> = suggestions.iter()
1037 .filter(|s| s.category == SuggestionCategory::DefectPrevention)
1038 .collect();
1039 assert!(!defect_suggestions.is_empty());
1040 assert_eq!(defect_suggestions[0].priority, SuggestionPriority::High);
1041 }
1042
1043 #[test]
1044 fn suggestion_priority_all_variants() {
1045 let _c = SuggestionPriority::Critical;
1046 let _h = SuggestionPriority::High;
1047 let _m = SuggestionPriority::Medium;
1048 let _l = SuggestionPriority::Low;
1049 assert_ne!(SuggestionPriority::Critical, SuggestionPriority::Low);
1050 }
1051
1052 #[test]
1053 fn suggestion_category_all_variants() {
1054 let _f = SuggestionCategory::FeatureHandling;
1055 let _d = SuggestionCategory::DefectPrevention;
1056 let _t = SuggestionCategory::TrainingData;
1057 let _c = SuggestionCategory::Configuration;
1058 assert_ne!(SuggestionCategory::TrainingData, SuggestionCategory::Configuration);
1059 }
1060
1061 #[test]
1062 fn error_tracker_default_trait() {
1063 let tracker = ErrorTracker::default();
1064 assert_eq!(tracker.error_count(), 0);
1065 assert_eq!(tracker.success_count(), 0);
1066 }
1067
1068 #[test]
1069 fn generate_markdown_report_empty() {
1070 let mut tracker = ErrorTracker::new();
1071 let md = tracker.generate_markdown_report();
1072 assert!(md.contains("Total Errors | 0"));
1073 assert!(md.contains("Total Successes | 0"));
1074 }
1075
1076 #[test]
1077 fn generate_markdown_report_with_suggestions() {
1078 let mut tracker = ErrorTracker::new();
1079 for i in 0..30 {
1081 let error = InferenceError::new(
1082 format!("ptr{}", i), "test.c", i as u32,
1083 InferredOwnership::Borrowed, InferredOwnership::Owned,
1084 0.6, OwnershipDefect::PointerMisclassification,
1085 ).with_features(vec!["problematic".into()]);
1086 tracker.record_error(error);
1087 }
1088 for _ in 0..2 {
1089 tracker.record_success(&["problematic".into()]);
1090 }
1091
1092 let md = tracker.generate_markdown_report();
1093 assert!(md.contains("Improvement Suggestions"));
1094 assert!(md.contains("problematic"));
1095 }
1096
1097 #[test]
1098 fn inference_error_confidence_clamp() {
1099 let error = InferenceError::new(
1100 "ptr", "test.c", 1,
1101 InferredOwnership::Borrowed, InferredOwnership::Owned,
1102 1.5, OwnershipDefect::PointerMisclassification,
1104 );
1105 assert_eq!(error.confidence, 1.0);
1106
1107 let error2 = InferenceError::new(
1108 "ptr", "test.c", 1,
1109 InferredOwnership::Borrowed, InferredOwnership::Owned,
1110 -0.5, OwnershipDefect::PointerMisclassification,
1112 );
1113 assert_eq!(error2.confidence, 0.0);
1114 }
1115
1116 #[test]
1117 fn serialization_round_trip_inference_error() {
1118 let error = InferenceError::new(
1119 "ptr", "test.c", 1,
1120 InferredOwnership::Borrowed, InferredOwnership::Owned,
1121 0.6, OwnershipDefect::PointerMisclassification,
1122 ).with_features(vec!["malloc_free".into()]).with_rust_error("E0382");
1123
1124 let json = serde_json::to_string(&error).unwrap();
1125 let restored: InferenceError = serde_json::from_str(&json).unwrap();
1126 assert_eq!(restored.variable, "ptr");
1127 assert_eq!(restored.source_file, "test.c");
1128 assert_eq!(restored.rust_error, Some("E0382".into()));
1129 assert_eq!(restored.c_features.len(), 1);
1130 }
1131
1132 #[test]
1133 fn serialization_round_trip_suggestion() {
1134 let suggestion = ImprovementSuggestion {
1135 priority: SuggestionPriority::Critical,
1136 category: SuggestionCategory::TrainingData,
1137 description: "Add more training data".into(),
1138 affected_feature: None,
1139 affected_defect: Some("LifetimeInferenceGap".into()),
1140 };
1141 let json = serde_json::to_string(&suggestion).unwrap();
1142 let restored: ImprovementSuggestion = serde_json::from_str(&json).unwrap();
1143 assert_eq!(restored.priority, SuggestionPriority::Critical);
1144 assert_eq!(restored.category, SuggestionCategory::TrainingData);
1145 }
1146
1147 #[test]
1148 fn multiple_errors_auto_increments_ids() {
1149 let mut tracker = ErrorTracker::new();
1150 for i in 0..5 {
1151 tracker.record_error(InferenceError::new(
1152 format!("ptr{}", i), "test.c", i as u32,
1153 InferredOwnership::Borrowed, InferredOwnership::Owned,
1154 0.6, OwnershipDefect::PointerMisclassification,
1155 ));
1156 }
1157 let ids: Vec<u64> = tracker.errors().iter().map(|e| e.id).collect();
1158 assert_eq!(ids, vec![1, 2, 3, 4, 5]);
1159 }
1160
1161 #[test]
1166 fn generate_markdown_report_error_rate_percentage() {
1167 let mut tracker = ErrorTracker::new();
1168 for i in 0..3 {
1170 tracker.record_error(InferenceError::new(
1171 format!("ptr{}", i), "test.c", i as u32,
1172 InferredOwnership::Borrowed, InferredOwnership::Owned,
1173 0.6, OwnershipDefect::PointerMisclassification,
1174 ).with_features(vec!["feat_a".into()]));
1175 }
1176 for _ in 0..7 {
1177 tracker.record_success(&["feat_a".into()]);
1178 }
1179
1180 let md = tracker.generate_markdown_report();
1181 assert!(md.contains("Total Errors | 3"));
1183 assert!(md.contains("Total Successes | 7"));
1184 assert!(md.contains("Error Rate | 30.0%"));
1185 }
1186
1187 #[test]
1188 fn generate_markdown_report_suspicious_features_table() {
1189 let mut tracker = ErrorTracker::new();
1190 for i in 0..10 {
1192 let feature = format!("feature_{}", i % 3);
1193 tracker.record_error(InferenceError::new(
1194 format!("ptr{}", i), "test.c", i as u32,
1195 InferredOwnership::Borrowed, InferredOwnership::Owned,
1196 0.5, OwnershipDefect::PointerMisclassification,
1197 ).with_features(vec![feature.clone()]));
1198 }
1199 for i in 0..5 {
1200 let feature = format!("feature_{}", i % 3);
1201 tracker.record_success(&[feature]);
1202 }
1203
1204 let md = tracker.generate_markdown_report();
1205 assert!(md.contains("Top Suspicious Features (Tarantula)"));
1207 assert!(md.contains("| Feature | Score | Failures | Successes |"));
1208 assert!(md.contains("feature_"));
1210 }
1211
1212 #[test]
1213 fn generate_markdown_report_defect_distribution_section() {
1214 let mut tracker = ErrorTracker::new();
1215 for _ in 0..5 {
1217 tracker.record_error(InferenceError::new(
1218 "ptr", "test.c", 1,
1219 InferredOwnership::Borrowed, InferredOwnership::Owned,
1220 0.6, OwnershipDefect::PointerMisclassification,
1221 ));
1222 }
1223 for _ in 0..3 {
1224 tracker.record_error(InferenceError::new(
1225 "ptr", "test.c", 2,
1226 InferredOwnership::Borrowed, InferredOwnership::Owned,
1227 0.6, OwnershipDefect::LifetimeInferenceGap,
1228 ));
1229 }
1230 tracker.record_error(InferenceError::new(
1231 "ptr", "test.c", 3,
1232 InferredOwnership::Borrowed, InferredOwnership::Owned,
1233 0.6, OwnershipDefect::DanglingPointerRisk,
1234 ));
1235
1236 let md = tracker.generate_markdown_report();
1237 assert!(md.contains("Defect Distribution"));
1238 assert!(md.contains("PointerMisclassification"));
1240 assert!(md.contains("LifetimeInferenceGap"));
1241 assert!(md.contains("DanglingPointerRisk"));
1242 assert!(md.contains("%"));
1244 }
1245
1246 #[test]
1247 fn generate_markdown_report_no_suggestions_section_when_few_errors() {
1248 let mut tracker = ErrorTracker::new();
1249 for i in 0..2 {
1251 tracker.record_error(InferenceError::new(
1252 format!("ptr{}", i), "test.c", i as u32,
1253 InferredOwnership::Borrowed, InferredOwnership::Owned,
1254 0.6, OwnershipDefect::PointerMisclassification,
1255 ));
1256 }
1257 for _ in 0..20 {
1258 tracker.record_success(&["safe_feature".into()]);
1259 }
1260
1261 let md = tracker.generate_markdown_report();
1262 assert!(md.contains("Error Tracking Report"));
1265 }
1266
1267 #[test]
1268 fn generate_markdown_report_improvement_suggestions_numbered() {
1269 let mut tracker = ErrorTracker::new();
1270 for i in 0..30 {
1272 tracker.record_error(InferenceError::new(
1273 format!("ptr{}", i), "test.c", i as u32,
1274 InferredOwnership::Borrowed, InferredOwnership::Owned,
1275 0.6, OwnershipDefect::PointerMisclassification,
1276 ).with_features(vec!["dangerous_pattern".into()]));
1277 }
1278 for _ in 0..3 {
1279 tracker.record_success(&["dangerous_pattern".into()]);
1280 }
1281
1282 let md = tracker.generate_markdown_report();
1283 assert!(md.contains("Improvement Suggestions"));
1284 assert!(md.contains("1. **["));
1286 }
1287
1288 #[test]
1293 fn calculate_suspiciousness_only_failures() {
1294 let mut tracker = ErrorTracker::new();
1295 for _ in 0..5 {
1297 tracker.record_error(InferenceError::new(
1298 "ptr", "test.c", 1,
1299 InferredOwnership::Borrowed, InferredOwnership::Owned,
1300 0.6, OwnershipDefect::PointerMisclassification,
1301 ).with_features(vec!["only_failures".into()]));
1302 }
1303
1304 let suspicious = tracker.calculate_suspiciousness();
1305 let feat = suspicious.iter().find(|s| s.feature == "only_failures").unwrap();
1306 assert!((feat.score - 1.0).abs() < 0.001);
1308 assert_eq!(feat.failure_count, 5);
1309 assert_eq!(feat.success_count, 0);
1310 assert!(feat.is_highly_suspicious());
1311 }
1312
1313 #[test]
1314 fn calculate_suspiciousness_only_successes() {
1315 let mut tracker = ErrorTracker::new();
1316 tracker.record_error(InferenceError::new(
1318 "ptr", "test.c", 1,
1319 InferredOwnership::Borrowed, InferredOwnership::Owned,
1320 0.6, OwnershipDefect::PointerMisclassification,
1321 ).with_features(vec!["has_failure".into()]));
1322
1323 for _ in 0..5 {
1325 tracker.record_success(&["only_successes".into()]);
1326 }
1327
1328 let suspicious = tracker.calculate_suspiciousness();
1329 let feat = suspicious.iter().find(|s| s.feature == "only_successes").unwrap();
1330 assert!((feat.score - 0.0).abs() < 0.001);
1332 assert_eq!(feat.failure_count, 0);
1333 assert_eq!(feat.success_count, 5);
1334 assert!(!feat.is_suspicious());
1335 }
1336
1337 #[test]
1338 fn calculate_suspiciousness_mixed_features_sorted() {
1339 let mut tracker = ErrorTracker::new();
1340 for _ in 0..9 {
1342 tracker.record_error(InferenceError::new(
1343 "ptr", "test.c", 1,
1344 InferredOwnership::Borrowed, InferredOwnership::Owned,
1345 0.6, OwnershipDefect::PointerMisclassification,
1346 ).with_features(vec!["high_susp".into()]));
1347 }
1348 tracker.record_success(&["high_susp".into()]);
1349
1350 tracker.record_error(InferenceError::new(
1352 "ptr", "test.c", 1,
1353 InferredOwnership::Borrowed, InferredOwnership::Owned,
1354 0.6, OwnershipDefect::PointerMisclassification,
1355 ).with_features(vec!["low_susp".into()]));
1356 for _ in 0..9 {
1357 tracker.record_success(&["low_susp".into()]);
1358 }
1359
1360 let suspicious = tracker.calculate_suspiciousness();
1361 assert!(suspicious.len() >= 2);
1363 assert!(suspicious[0].score >= suspicious[1].score);
1364 assert_eq!(suspicious[0].feature, "high_susp");
1366 }
1367
1368 #[test]
1369 fn calculate_suspiciousness_updates_stats_in_place() {
1370 let mut tracker = ErrorTracker::new();
1371 for _ in 0..3 {
1372 tracker.record_error(InferenceError::new(
1373 "ptr", "test.c", 1,
1374 InferredOwnership::Borrowed, InferredOwnership::Owned,
1375 0.6, OwnershipDefect::PointerMisclassification,
1376 ).with_features(vec!["feat".into()]));
1377 }
1378 for _ in 0..2 {
1379 tracker.record_success(&["feat".into()]);
1380 }
1381
1382 let results1 = tracker.calculate_suspiciousness();
1384 let results2 = tracker.calculate_suspiciousness();
1386
1387 assert_eq!(results1.len(), results2.len());
1388 assert!((results1[0].score - results2[0].score).abs() < 0.001);
1389 }
1390
1391 #[test]
1392 fn calculate_suspiciousness_multiple_features_per_error() {
1393 let mut tracker = ErrorTracker::new();
1394 tracker.record_error(InferenceError::new(
1396 "ptr", "test.c", 1,
1397 InferredOwnership::Borrowed, InferredOwnership::Owned,
1398 0.6, OwnershipDefect::PointerMisclassification,
1399 ).with_features(vec!["feat_x".into(), "feat_y".into()]));
1400
1401 tracker.record_success(&["feat_y".into()]);
1402
1403 let suspicious = tracker.calculate_suspiciousness();
1404 assert!(suspicious.iter().any(|s| s.feature == "feat_x"));
1406 assert!(suspicious.iter().any(|s| s.feature == "feat_y"));
1407
1408 let feat_x = suspicious.iter().find(|s| s.feature == "feat_x").unwrap();
1410 let feat_y = suspicious.iter().find(|s| s.feature == "feat_y").unwrap();
1411 assert!(feat_x.score > feat_y.score, "feat_x should be more suspicious");
1412 }
1413
1414 #[test]
1419 fn generate_suggestions_no_highly_suspicious_no_common_defects() {
1420 let mut tracker = ErrorTracker::new();
1421 for i in 0..3 {
1423 tracker.record_error(InferenceError::new(
1424 format!("ptr{}", i), "test.c", i as u32,
1425 InferredOwnership::Borrowed, InferredOwnership::Owned,
1426 0.6, OwnershipDefect::PointerMisclassification,
1427 ).with_features(vec![format!("unique_feat_{}", i)]));
1428 }
1429 for i in 0..3 {
1431 for _ in 0..20 {
1432 tracker.record_success(&[format!("unique_feat_{}", i)]);
1433 }
1434 }
1435
1436 let suggestions = tracker.generate_suggestions();
1437 let feature_suggestions: Vec<_> = suggestions.iter()
1441 .filter(|s| s.category == SuggestionCategory::FeatureHandling)
1442 .collect();
1443 assert!(feature_suggestions.is_empty(), "Features should not be highly suspicious");
1444 }
1445
1446 #[test]
1447 fn generate_suggestions_multiple_defect_categories() {
1448 let mut tracker = ErrorTracker::new();
1449 for _ in 0..25 {
1451 tracker.record_error(InferenceError::new(
1452 "ptr", "test.c", 1,
1453 InferredOwnership::Borrowed, InferredOwnership::Owned,
1454 0.6, OwnershipDefect::PointerMisclassification,
1455 ));
1456 }
1457 for _ in 0..10 {
1458 tracker.record_error(InferenceError::new(
1459 "ptr", "test.c", 2,
1460 InferredOwnership::Borrowed, InferredOwnership::Owned,
1461 0.6, OwnershipDefect::LifetimeInferenceGap,
1462 ));
1463 }
1464 for _ in 0..8 {
1465 tracker.record_error(InferenceError::new(
1466 "ptr", "test.c", 3,
1467 InferredOwnership::Borrowed, InferredOwnership::Owned,
1468 0.6, OwnershipDefect::DanglingPointerRisk,
1469 ));
1470 }
1471 for _ in 0..5 {
1472 tracker.record_success(&["x".into()]);
1473 }
1474
1475 let suggestions = tracker.generate_suggestions();
1476 let defect_suggestions: Vec<_> = suggestions.iter()
1477 .filter(|s| s.category == SuggestionCategory::DefectPrevention)
1478 .collect();
1479
1480 assert!(defect_suggestions.len() >= 2);
1482 assert!(defect_suggestions.iter().any(|s| s.priority == SuggestionPriority::High));
1484 assert!(defect_suggestions.iter().any(|s| s.priority == SuggestionPriority::Medium));
1486 }
1487
1488 #[test]
1493 fn record_error_updates_feature_defect_stats() {
1494 let mut tracker = ErrorTracker::new();
1495 let error = InferenceError::new(
1496 "ptr", "test.c", 1,
1497 InferredOwnership::Borrowed, InferredOwnership::Owned,
1498 0.6, OwnershipDefect::AliasViolation,
1499 ).with_features(vec!["feature_alpha".into(), "feature_beta".into()]);
1500
1501 tracker.record_error(error);
1502
1503 let defect_dist = tracker.defect_distribution();
1505 assert_eq!(defect_dist.get("AliasViolation"), Some(&1));
1506
1507 let correlations = tracker.feature_defect_correlation();
1509 assert!(correlations.iter().any(|(f, d, c)| f == "feature_alpha" && d.contains("AliasViolation") && *c == 1));
1510 assert!(correlations.iter().any(|(f, d, c)| f == "feature_beta" && d.contains("AliasViolation") && *c == 1));
1511 }
1512
1513 #[test]
1514 fn record_multiple_errors_same_feature_different_defects() {
1515 let mut tracker = ErrorTracker::new();
1516 tracker.record_error(InferenceError::new(
1517 "ptr1", "test.c", 1,
1518 InferredOwnership::Borrowed, InferredOwnership::Owned,
1519 0.6, OwnershipDefect::PointerMisclassification,
1520 ).with_features(vec!["shared_feature".into()]));
1521
1522 tracker.record_error(InferenceError::new(
1523 "ptr2", "test.c", 2,
1524 InferredOwnership::Borrowed, InferredOwnership::Owned,
1525 0.6, OwnershipDefect::DanglingPointerRisk,
1526 ).with_features(vec!["shared_feature".into()]));
1527
1528 let suspicious = tracker.calculate_suspiciousness();
1530 let feat = suspicious.iter().find(|s| s.feature == "shared_feature").unwrap();
1531 assert_eq!(feat.failure_count, 2);
1532
1533 let correlations = tracker.feature_defect_correlation();
1535 let shared_correlations: Vec<_> = correlations.iter()
1536 .filter(|(f, _, _)| f == "shared_feature")
1537 .collect();
1538 assert_eq!(shared_correlations.len(), 2);
1539 }
1540}