batuta/bug_hunter/localization/
scoring.rs1use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use crate::bug_hunter::types::{ChannelWeights, SbflFormula};
10
11#[derive(Debug, Clone)]
13pub struct ScoredLocation {
14 pub file: PathBuf,
15 pub line: usize,
16 pub spectrum_score: f64,
18 pub mutation_score: f64,
20 pub static_score: f64,
22 pub semantic_score: f64,
24 pub quality_score: f64,
26 pub final_score: f64,
28}
29
30impl ScoredLocation {
31 pub fn new(file: PathBuf, line: usize) -> Self {
32 Self {
33 file,
34 line,
35 spectrum_score: 0.0,
36 mutation_score: 0.0,
37 static_score: 0.0,
38 semantic_score: 0.0,
39 quality_score: 0.0,
40 final_score: 0.0,
41 }
42 }
43
44 pub fn compute_final_score(&mut self, weights: &ChannelWeights) {
46 self.final_score = weights.combine(
47 self.spectrum_score,
48 self.mutation_score,
49 self.static_score,
50 self.semantic_score,
51 self.quality_score,
52 );
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct TestCoverage {
59 pub test_name: String,
60 pub passed: bool,
61 pub executed_lines: HashMap<(PathBuf, usize), usize>,
63}
64
65#[derive(Debug, Default)]
67pub struct SpectrumData {
68 pub failed_coverage: HashMap<(PathBuf, usize), usize>,
70 pub passed_coverage: HashMap<(PathBuf, usize), usize>,
72 pub total_failed: usize,
74 pub total_passed: usize,
76}
77
78impl SpectrumData {
79 pub fn compute_score(&self, file: &Path, line: usize, formula: SbflFormula) -> f64 {
81 let key = (file.to_path_buf(), line);
82 let ef = *self.failed_coverage.get(&key).unwrap_or(&0) as f64;
83 let ep = *self.passed_coverage.get(&key).unwrap_or(&0) as f64;
84 let nf = (self.total_failed as f64) - ef;
85
86 match formula {
87 SbflFormula::Tarantula => sbfl_tarantula(ef, ep, self.total_failed, self.total_passed),
88 SbflFormula::Ochiai => sbfl_ochiai(ef, ep, nf),
89 SbflFormula::DStar2 => sbfl_dstar(ef, ep, nf, 2),
90 SbflFormula::DStar3 => sbfl_dstar(ef, ep, nf, 3),
91 }
92 }
93}
94
95fn sbfl_tarantula(ef: f64, ep: f64, total_failed: usize, total_passed: usize) -> f64 {
96 let fail_ratio = if total_failed > 0 { ef / total_failed as f64 } else { 0.0 };
97 let pass_ratio = if total_passed > 0 { ep / total_passed as f64 } else { 0.0 };
98 let sum = fail_ratio + pass_ratio;
99 if sum > 0.0 {
100 fail_ratio / sum
101 } else {
102 0.0
103 }
104}
105
106fn sbfl_ochiai(ef: f64, ep: f64, nf: f64) -> f64 {
107 let denom = ((ef + nf) * (ef + ep)).sqrt();
108 if denom > 0.0 {
109 ef / denom
110 } else {
111 0.0
112 }
113}
114
115fn sbfl_dstar(ef: f64, ep: f64, nf: f64, power: u32) -> f64 {
116 let denom = ep + nf;
117 if denom > 0.0 {
118 ef.powi(power as i32) / denom
119 } else if ef > 0.0 {
120 f64::MAX
121 } else {
122 0.0
123 }
124}
125
126#[derive(Debug, Default)]
128pub struct MutationData {
129 pub mutants: HashMap<(PathBuf, usize), (usize, usize)>,
131}
132
133impl MutationData {
134 pub fn compute_score(&self, file: &Path, line: usize) -> f64 {
137 let key = (file.to_path_buf(), line);
138 if let Some((total, killed)) = self.mutants.get(&key) {
139 if *total > 0 {
140 return *killed as f64 / *total as f64;
141 }
142 }
143 0.0
144 }
145}