use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScoringMode {
Quick,
#[default]
Fast,
Full,
}
#[derive(Debug, Clone, Default)]
pub struct SingleShotScore {
pub total: u8,
pub compilation: u8,
pub type_inference: u8,
pub test_coverage: u8,
pub code_quality: u8,
pub semantic_equivalence: u8,
pub gateway_passed: bool,
pub mode: ScoringMode,
}
#[derive(Debug, Clone, Default)]
pub struct CategoryBreakdown {
pub a1_parse: u8,
pub a2_type_check: u8,
pub a3_cargo_build: u8,
pub b1_no_e0308: u8,
pub b2_no_e0599: u8,
pub b3_no_e0425: u8,
pub c1_doctest: u8,
pub c2_unit_test: u8,
pub c3_property_test: u8,
pub d1_clippy: u8,
pub d2_tdg: u8,
pub d3_complexity: u8,
pub e1_trace_match: u8,
pub e2_output_equiv: u8,
}
#[derive(Debug, Clone)]
pub struct CompilationError {
pub code: String,
pub message: String,
pub location: Option<String>,
pub line: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TranspilerDecision {
TypeInference {
variable: String,
inferred_type: String,
},
MethodTranslation {
python_method: String,
rust_method: String,
},
ImportMapping {
python_import: String,
rust_import: String,
},
ValueFallback { context: String },
Other(String),
}
#[derive(Debug, Clone)]
pub struct SingleShotResult {
pub file_path: PathBuf,
pub score: SingleShotScore,
pub category_breakdown: CategoryBreakdown,
pub error_details: Vec<CompilationError>,
pub transpiler_decisions: Vec<TranspilerDecision>,
}
#[derive(Debug, Clone)]
pub struct ScoringConfig {
pub gateway_threshold: f32,
pub weights: CategoryWeights,
pub enable_semantic_check: bool,
pub oracle_feedback: bool,
}
impl Default for ScoringConfig {
fn default() -> Self {
Self {
gateway_threshold: 0.6,
weights: CategoryWeights::default(),
enable_semantic_check: true,
oracle_feedback: true,
}
}
}
#[derive(Debug, Clone)]
pub struct CategoryWeights {
pub compilation: f32,
pub type_inference: f32,
pub test_coverage: f32,
pub code_quality: f32,
pub semantic_equiv: f32,
}
impl Default for CategoryWeights {
fn default() -> Self {
Self {
compilation: 0.40,
type_inference: 0.25,
test_coverage: 0.15,
code_quality: 0.10,
semantic_equiv: 0.10,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Human,
Json,
Parquet,
Markdown,
}
#[derive(Debug, Clone)]
pub struct CorpusScoreReport {
pub results: Vec<SingleShotResult>,
pub aggregate_score: f32,
pub grade: Grade,
pub category_averages: CategoryBreakdown,
pub top_blockers: Vec<Blocker>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Grade {
APlus, A, AMinus, BPlus, B, C, D, F, }
impl Grade {
pub fn from_score(score: f32) -> Self {
match score as u8 {
95..=100 => Grade::APlus,
90..=94 => Grade::A,
85..=89 => Grade::AMinus,
80..=84 => Grade::BPlus,
70..=79 => Grade::B,
60..=69 => Grade::C,
50..=59 => Grade::D,
_ => Grade::F,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Grade::APlus => "A+",
Grade::A => "A",
Grade::AMinus => "A-",
Grade::BPlus => "B+",
Grade::B => "B",
Grade::C => "C",
Grade::D => "D",
Grade::F => "F",
}
}
}
#[derive(Debug, Clone)]
pub struct Blocker {
pub pattern: String,
pub affected_files: usize,
pub avg_points_lost: f32,
}
#[derive(Debug, Clone, Default)]
pub struct BreakdownInput<'a> {
pub parse_ok: bool,
pub type_check_ok: bool,
pub build_ok: bool,
pub errors: &'a [CompilationError],
pub doctest_pass: bool,
pub unit_test_pass: bool,
pub property_test_pass: bool,
pub clippy_clean: bool,
pub tdg_grade_b_or_better: bool,
pub complexity_ok: bool,
pub trace_match: bool,
pub output_equiv: bool,
}
pub struct ScoreCalculator {
config: ScoringConfig,
}
impl ScoreCalculator {
pub fn new() -> Self {
Self {
config: ScoringConfig::default(),
}
}
pub fn with_config(config: ScoringConfig) -> Self {
Self { config }
}
pub fn calculate(&self, breakdown: &CategoryBreakdown, mode: ScoringMode) -> SingleShotScore {
let compilation = breakdown.a1_parse + breakdown.a2_type_check + breakdown.a3_cargo_build;
let type_inference = breakdown.b1_no_e0308 + breakdown.b2_no_e0599 + breakdown.b3_no_e0425;
let test_coverage =
breakdown.c1_doctest + breakdown.c2_unit_test + breakdown.c3_property_test;
let code_quality = breakdown.d1_clippy + breakdown.d2_tdg + breakdown.d3_complexity;
let semantic_equivalence = breakdown.e1_trace_match + breakdown.e2_output_equiv;
let gateway_threshold = (40.0 * self.config.gateway_threshold) as u8; let gateway_passed = compilation >= gateway_threshold;
let total = if gateway_passed {
compilation + type_inference + test_coverage + code_quality + semantic_equivalence
} else {
0
};
SingleShotScore {
total,
compilation,
type_inference,
test_coverage,
code_quality,
semantic_equivalence,
gateway_passed,
mode,
}
}
pub fn breakdown_from_errors(&self, input: &BreakdownInput<'_>) -> CategoryBreakdown {
let e0308_count = input.errors.iter().filter(|e| e.code == "E0308").count();
let e0599_count = input.errors.iter().filter(|e| e.code == "E0599").count();
let e0425_count = input.errors.iter().filter(|e| e.code == "E0425").count();
let total_errors = input.errors.len().max(1);
let e0308_ratio = e0308_count as f32 / total_errors as f32;
let e0599_ratio = e0599_count as f32 / total_errors as f32;
let e0425_ratio = e0425_count as f32 / total_errors as f32;
CategoryBreakdown {
a1_parse: if input.parse_ok { 10 } else { 0 },
a2_type_check: if input.type_check_ok { 15 } else { 0 },
a3_cargo_build: if input.build_ok { 15 } else { 0 },
b1_no_e0308: ((1.0 - e0308_ratio) * 10.0) as u8,
b2_no_e0599: ((1.0 - e0599_ratio) * 8.0) as u8,
b3_no_e0425: ((1.0 - e0425_ratio) * 7.0) as u8,
c1_doctest: if input.doctest_pass { 5 } else { 0 },
c2_unit_test: if input.unit_test_pass { 5 } else { 0 },
c3_property_test: if input.property_test_pass { 5 } else { 0 },
d1_clippy: if input.clippy_clean { 5 } else { 0 },
d2_tdg: if input.tdg_grade_b_or_better { 3 } else { 0 },
d3_complexity: if input.complexity_ok { 2 } else { 0 },
e1_trace_match: if input.trace_match { 5 } else { 0 },
e2_output_equiv: if input.output_equiv { 5 } else { 0 },
}
}
pub fn aggregate(&self, results: &[SingleShotResult]) -> CorpusScoreReport {
if results.is_empty() {
return CorpusScoreReport {
results: vec![],
aggregate_score: 0.0,
grade: Grade::F,
category_averages: CategoryBreakdown::default(),
top_blockers: vec![],
};
}
let n = results.len() as f32;
let aggregate_score: f32 = results.iter().map(|r| r.score.total as f32).sum::<f32>() / n;
let category_averages = CategoryBreakdown {
a1_parse: (results
.iter()
.map(|r| r.category_breakdown.a1_parse as f32)
.sum::<f32>()
/ n) as u8,
a2_type_check: (results
.iter()
.map(|r| r.category_breakdown.a2_type_check as f32)
.sum::<f32>()
/ n) as u8,
a3_cargo_build: (results
.iter()
.map(|r| r.category_breakdown.a3_cargo_build as f32)
.sum::<f32>()
/ n) as u8,
b1_no_e0308: (results
.iter()
.map(|r| r.category_breakdown.b1_no_e0308 as f32)
.sum::<f32>()
/ n) as u8,
b2_no_e0599: (results
.iter()
.map(|r| r.category_breakdown.b2_no_e0599 as f32)
.sum::<f32>()
/ n) as u8,
b3_no_e0425: (results
.iter()
.map(|r| r.category_breakdown.b3_no_e0425 as f32)
.sum::<f32>()
/ n) as u8,
c1_doctest: (results
.iter()
.map(|r| r.category_breakdown.c1_doctest as f32)
.sum::<f32>()
/ n) as u8,
c2_unit_test: (results
.iter()
.map(|r| r.category_breakdown.c2_unit_test as f32)
.sum::<f32>()
/ n) as u8,
c3_property_test: (results
.iter()
.map(|r| r.category_breakdown.c3_property_test as f32)
.sum::<f32>()
/ n) as u8,
d1_clippy: (results
.iter()
.map(|r| r.category_breakdown.d1_clippy as f32)
.sum::<f32>()
/ n) as u8,
d2_tdg: (results
.iter()
.map(|r| r.category_breakdown.d2_tdg as f32)
.sum::<f32>()
/ n) as u8,
d3_complexity: (results
.iter()
.map(|r| r.category_breakdown.d3_complexity as f32)
.sum::<f32>()
/ n) as u8,
e1_trace_match: (results
.iter()
.map(|r| r.category_breakdown.e1_trace_match as f32)
.sum::<f32>()
/ n) as u8,
e2_output_equiv: (results
.iter()
.map(|r| r.category_breakdown.e2_output_equiv as f32)
.sum::<f32>()
/ n) as u8,
};
let mut error_counts: HashMap<String, (usize, f32)> = HashMap::new();
for result in results {
for error in &result.error_details {
let entry = error_counts.entry(error.code.clone()).or_insert((0, 0.0));
entry.0 += 1;
entry.1 += 100.0 - result.score.total as f32;
}
}
let mut top_blockers: Vec<Blocker> = error_counts
.into_iter()
.map(|(code, (count, total_lost))| Blocker {
pattern: code,
affected_files: count,
avg_points_lost: total_lost / count as f32,
})
.collect();
top_blockers.sort_by(|a, b| b.affected_files.cmp(&a.affected_files));
top_blockers.truncate(5);
CorpusScoreReport {
results: results.to_vec(),
aggregate_score,
grade: Grade::from_score(aggregate_score),
category_averages,
top_blockers,
}
}
}
impl Default for ScoreCalculator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TarantulaScore {
pub suspiciousness: f32,
pub failed_count: usize,
pub passed_count: usize,
}
#[derive(Debug, Clone, Default)]
pub struct DecisionStats {
pub failed_count: usize,
pub passed_count: usize,
}
impl DecisionStats {
pub fn tarantula_score(&self, total_failed: usize, total_passed: usize) -> TarantulaScore {
let failed_ratio = if total_failed > 0 {
self.failed_count as f32 / total_failed as f32
} else {
0.0
};
let passed_ratio = if total_passed > 0 {
self.passed_count as f32 / total_passed as f32
} else {
0.0
};
let suspiciousness = if failed_ratio + passed_ratio > 0.0 {
failed_ratio / (failed_ratio + passed_ratio)
} else {
0.0
};
TarantulaScore {
suspiciousness,
failed_count: self.failed_count,
passed_count: self.passed_count,
}
}
}
pub fn analyze_score_failures(
results: &[SingleShotResult],
) -> HashMap<TranspilerDecision, TarantulaScore> {
let mut stats: HashMap<TranspilerDecision, DecisionStats> = HashMap::new();
let mut total_failed = 0;
let mut total_passed = 0;
for result in results {
let failed = result.score.total < 80;
if failed {
total_failed += 1;
} else {
total_passed += 1;
}
for decision in &result.transpiler_decisions {
let entry = stats.entry(decision.clone()).or_default();
if failed {
entry.failed_count += 1;
} else {
entry.passed_count += 1;
}
}
}
stats
.into_iter()
.map(|(d, s)| (d, s.tarantula_score(total_failed, total_passed)))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scoring_mode_default() {
let mode = ScoringMode::default();
assert_eq!(mode, ScoringMode::Fast);
}
#[test]
fn test_scoring_mode_quick() {
let mode = ScoringMode::Quick;
assert_eq!(mode, ScoringMode::Quick);
}
#[test]
fn test_scoring_mode_full() {
let mode = ScoringMode::Full;
assert_eq!(mode, ScoringMode::Full);
}
#[test]
fn test_scoring_mode_clone() {
let mode = ScoringMode::Fast;
let cloned = mode;
assert_eq!(cloned, ScoringMode::Fast);
}
#[test]
fn test_scoring_mode_debug() {
let debug = format!("{:?}", ScoringMode::Quick);
assert!(debug.contains("Quick"));
}
#[test]
fn test_single_shot_score_default() {
let score = SingleShotScore::default();
assert_eq!(score.total, 0);
assert_eq!(score.compilation, 0);
assert!(!score.gateway_passed);
}
#[test]
fn test_single_shot_score_clone() {
let score = SingleShotScore {
total: 75,
compilation: 35,
type_inference: 20,
test_coverage: 10,
code_quality: 5,
semantic_equivalence: 5,
gateway_passed: true,
mode: ScoringMode::Fast,
};
let cloned = score.clone();
assert_eq!(cloned.total, 75);
assert_eq!(cloned.compilation, 35);
}
#[test]
fn test_single_shot_score_debug() {
let score = SingleShotScore::default();
let debug = format!("{:?}", score);
assert!(debug.contains("SingleShotScore"));
}
#[test]
fn test_category_breakdown_default() {
let breakdown = CategoryBreakdown::default();
assert_eq!(breakdown.a1_parse, 0);
assert_eq!(breakdown.a2_type_check, 0);
assert_eq!(breakdown.b1_no_e0308, 0);
}
#[test]
fn test_category_breakdown_clone() {
let breakdown = CategoryBreakdown {
a1_parse: 10,
a2_type_check: 15,
..Default::default()
};
let cloned = breakdown.clone();
assert_eq!(cloned.a1_parse, 10);
assert_eq!(cloned.a2_type_check, 15);
}
#[test]
fn test_category_breakdown_debug() {
let breakdown = CategoryBreakdown::default();
let debug = format!("{:?}", breakdown);
assert!(debug.contains("CategoryBreakdown"));
}
#[test]
fn test_compilation_error_fields() {
let error = CompilationError {
code: "E0308".to_string(),
message: "mismatched types".to_string(),
location: Some("src/lib.rs".to_string()),
line: Some(42),
};
assert_eq!(error.code, "E0308");
assert_eq!(error.message, "mismatched types");
assert_eq!(error.location, Some("src/lib.rs".to_string()));
assert_eq!(error.line, Some(42));
}
#[test]
fn test_compilation_error_none_fields() {
let error = CompilationError {
code: "E0001".to_string(),
message: "error".to_string(),
location: None,
line: None,
};
assert!(error.location.is_none());
assert!(error.line.is_none());
}
#[test]
fn test_compilation_error_clone() {
let error = CompilationError {
code: "E0308".to_string(),
message: "test".to_string(),
location: None,
line: None,
};
let cloned = error.clone();
assert_eq!(cloned.code, error.code);
}
#[test]
fn test_compilation_error_debug() {
let error = CompilationError {
code: "E0308".to_string(),
message: "test".to_string(),
location: None,
line: None,
};
let debug = format!("{:?}", error);
assert!(debug.contains("CompilationError"));
}
#[test]
fn test_transpiler_decision_type_inference() {
let decision = TranspilerDecision::TypeInference {
variable: "x".to_string(),
inferred_type: "i32".to_string(),
};
assert!(matches!(decision, TranspilerDecision::TypeInference { .. }));
}
#[test]
fn test_transpiler_decision_method_translation() {
let decision = TranspilerDecision::MethodTranslation {
python_method: "append".to_string(),
rust_method: "push".to_string(),
};
assert!(matches!(
decision,
TranspilerDecision::MethodTranslation { .. }
));
}
#[test]
fn test_transpiler_decision_import_mapping() {
let decision = TranspilerDecision::ImportMapping {
python_import: "json".to_string(),
rust_import: "serde_json".to_string(),
};
assert!(matches!(decision, TranspilerDecision::ImportMapping { .. }));
}
#[test]
fn test_transpiler_decision_value_fallback() {
let decision = TranspilerDecision::ValueFallback {
context: "unknown type".to_string(),
};
assert!(matches!(decision, TranspilerDecision::ValueFallback { .. }));
}
#[test]
fn test_transpiler_decision_other() {
let decision = TranspilerDecision::Other("custom decision".to_string());
assert!(matches!(decision, TranspilerDecision::Other(_)));
}
#[test]
fn test_transpiler_decision_clone() {
let decision = TranspilerDecision::TypeInference {
variable: "x".to_string(),
inferred_type: "i32".to_string(),
};
let cloned = decision.clone();
assert_eq!(cloned, decision);
}
#[test]
fn test_transpiler_decision_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
let d1 = TranspilerDecision::Other("a".to_string());
let d2 = TranspilerDecision::Other("b".to_string());
set.insert(d1.clone());
set.insert(d2.clone());
assert_eq!(set.len(), 2);
assert!(set.contains(&d1));
}
#[test]
fn test_scoring_config_default() {
let config = ScoringConfig::default();
assert!((config.gateway_threshold - 0.6).abs() < f32::EPSILON);
assert!(config.enable_semantic_check);
assert!(config.oracle_feedback);
}
#[test]
fn test_scoring_config_clone() {
let config = ScoringConfig::default();
let cloned = config.clone();
assert_eq!(cloned.gateway_threshold, config.gateway_threshold);
}
#[test]
fn test_scoring_config_debug() {
let config = ScoringConfig::default();
let debug = format!("{:?}", config);
assert!(debug.contains("ScoringConfig"));
}
#[test]
fn test_category_weights_default() {
let weights = CategoryWeights::default();
assert!((weights.compilation - 0.40).abs() < f32::EPSILON);
assert!((weights.type_inference - 0.25).abs() < f32::EPSILON);
assert!((weights.test_coverage - 0.15).abs() < f32::EPSILON);
assert!((weights.code_quality - 0.10).abs() < f32::EPSILON);
assert!((weights.semantic_equiv - 0.10).abs() < f32::EPSILON);
}
#[test]
fn test_category_weights_sum_to_one() {
let weights = CategoryWeights::default();
let sum = weights.compilation
+ weights.type_inference
+ weights.test_coverage
+ weights.code_quality
+ weights.semantic_equiv;
assert!((sum - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_category_weights_clone() {
let weights = CategoryWeights::default();
let cloned = weights.clone();
assert_eq!(cloned.compilation, weights.compilation);
}
#[test]
fn test_output_format_default() {
let format = OutputFormat::default();
assert_eq!(format, OutputFormat::Human);
}
#[test]
fn test_output_format_json() {
let format = OutputFormat::Json;
assert_eq!(format, OutputFormat::Json);
}
#[test]
fn test_output_format_parquet() {
let format = OutputFormat::Parquet;
assert_eq!(format, OutputFormat::Parquet);
}
#[test]
fn test_output_format_markdown() {
let format = OutputFormat::Markdown;
assert_eq!(format, OutputFormat::Markdown);
}
#[test]
fn test_output_format_debug() {
let debug = format!("{:?}", OutputFormat::Json);
assert!(debug.contains("Json"));
}
#[test]
fn test_grade_as_str() {
assert_eq!(Grade::APlus.as_str(), "A+");
assert_eq!(Grade::A.as_str(), "A");
assert_eq!(Grade::AMinus.as_str(), "A-");
assert_eq!(Grade::BPlus.as_str(), "B+");
assert_eq!(Grade::B.as_str(), "B");
assert_eq!(Grade::C.as_str(), "C");
assert_eq!(Grade::D.as_str(), "D");
assert_eq!(Grade::F.as_str(), "F");
}
#[test]
fn test_grade_clone() {
let grade = Grade::APlus;
let cloned = grade;
assert_eq!(cloned, Grade::APlus);
}
#[test]
fn test_grade_debug() {
let debug = format!("{:?}", Grade::APlus);
assert!(debug.contains("APlus"));
}
#[test]
fn test_grade_eq() {
assert_eq!(Grade::A, Grade::A);
assert_ne!(Grade::A, Grade::B);
}
#[test]
fn test_blocker_fields() {
let blocker = Blocker {
pattern: "E0308".to_string(),
affected_files: 5,
avg_points_lost: 15.5,
};
assert_eq!(blocker.pattern, "E0308");
assert_eq!(blocker.affected_files, 5);
assert!((blocker.avg_points_lost - 15.5).abs() < f32::EPSILON);
}
#[test]
fn test_blocker_clone() {
let blocker = Blocker {
pattern: "test".to_string(),
affected_files: 3,
avg_points_lost: 10.0,
};
let cloned = blocker.clone();
assert_eq!(cloned.pattern, blocker.pattern);
}
#[test]
fn test_blocker_debug() {
let blocker = Blocker {
pattern: "test".to_string(),
affected_files: 1,
avg_points_lost: 5.0,
};
let debug = format!("{:?}", blocker);
assert!(debug.contains("Blocker"));
}
#[test]
fn test_breakdown_input_default() {
let input = BreakdownInput::default();
assert!(!input.parse_ok);
assert!(!input.type_check_ok);
assert!(!input.build_ok);
}
#[test]
fn test_breakdown_input_clone() {
let empty: Vec<CompilationError> = vec![];
let input = BreakdownInput {
parse_ok: true,
type_check_ok: true,
build_ok: false,
errors: &empty,
..Default::default()
};
let cloned = input.clone();
assert_eq!(cloned.parse_ok, input.parse_ok);
}
#[test]
fn test_breakdown_input_debug() {
let input = BreakdownInput::default();
let debug = format!("{:?}", input);
assert!(debug.contains("BreakdownInput"));
}
#[test]
fn test_score_calculator_new() {
let calc = ScoreCalculator::new();
assert!((calc.config.gateway_threshold - 0.6).abs() < f32::EPSILON);
}
#[test]
fn test_score_calculator_default() {
let calc = ScoreCalculator::default();
assert!((calc.config.gateway_threshold - 0.6).abs() < f32::EPSILON);
}
#[test]
fn test_score_calculator_with_config() {
let config = ScoringConfig {
gateway_threshold: 0.8,
..Default::default()
};
let calc = ScoreCalculator::with_config(config);
assert!((calc.config.gateway_threshold - 0.8).abs() < f32::EPSILON);
}
#[test]
fn test_score_calculation_perfect() {
let calculator = ScoreCalculator::new();
let breakdown = CategoryBreakdown {
a1_parse: 10,
a2_type_check: 15,
a3_cargo_build: 15,
b1_no_e0308: 10,
b2_no_e0599: 8,
b3_no_e0425: 7,
c1_doctest: 5,
c2_unit_test: 5,
c3_property_test: 5,
d1_clippy: 5,
d2_tdg: 3,
d3_complexity: 2,
e1_trace_match: 5,
e2_output_equiv: 5,
};
let score = calculator.calculate(&breakdown, ScoringMode::Full);
assert_eq!(score.total, 100);
assert_eq!(score.compilation, 40);
assert_eq!(score.type_inference, 25);
assert_eq!(score.test_coverage, 15);
assert_eq!(score.code_quality, 10);
assert_eq!(score.semantic_equivalence, 10);
assert!(score.gateway_passed);
}
#[test]
fn test_gateway_blocks_when_compilation_fails() {
let calculator = ScoreCalculator::new();
let breakdown = CategoryBreakdown {
a1_parse: 10,
a2_type_check: 5, a3_cargo_build: 0, b1_no_e0308: 10,
b2_no_e0599: 8,
b3_no_e0425: 7,
c1_doctest: 5,
c2_unit_test: 5,
c3_property_test: 5,
d1_clippy: 5,
d2_tdg: 3,
d3_complexity: 2,
e1_trace_match: 5,
e2_output_equiv: 5,
};
let score = calculator.calculate(&breakdown, ScoringMode::Full);
assert_eq!(score.compilation, 15);
assert!(!score.gateway_passed);
assert_eq!(score.total, 0); }
#[test]
fn test_gateway_passes_at_threshold() {
let calculator = ScoreCalculator::new();
let breakdown = CategoryBreakdown {
a1_parse: 10,
a2_type_check: 14, a3_cargo_build: 0, ..Default::default()
};
let score = calculator.calculate(&breakdown, ScoringMode::Quick);
assert_eq!(score.compilation, 24);
assert!(score.gateway_passed);
}
#[test]
fn test_grade_mapping() {
assert_eq!(Grade::from_score(100.0), Grade::APlus);
assert_eq!(Grade::from_score(95.0), Grade::APlus);
assert_eq!(Grade::from_score(94.0), Grade::A);
assert_eq!(Grade::from_score(90.0), Grade::A);
assert_eq!(Grade::from_score(89.0), Grade::AMinus);
assert_eq!(Grade::from_score(85.0), Grade::AMinus);
assert_eq!(Grade::from_score(84.0), Grade::BPlus);
assert_eq!(Grade::from_score(80.0), Grade::BPlus);
assert_eq!(Grade::from_score(79.0), Grade::B);
assert_eq!(Grade::from_score(70.0), Grade::B);
assert_eq!(Grade::from_score(69.0), Grade::C);
assert_eq!(Grade::from_score(60.0), Grade::C);
assert_eq!(Grade::from_score(59.0), Grade::D);
assert_eq!(Grade::from_score(50.0), Grade::D);
assert_eq!(Grade::from_score(49.0), Grade::F);
assert_eq!(Grade::from_score(0.0), Grade::F);
}
#[test]
fn test_breakdown_from_errors() {
let calculator = ScoreCalculator::new();
let errors = vec![
CompilationError {
code: "E0308".to_string(),
message: "type mismatch".to_string(),
location: None,
line: None,
},
CompilationError {
code: "E0308".to_string(),
message: "type mismatch 2".to_string(),
location: None,
line: None,
},
CompilationError {
code: "E0599".to_string(),
message: "method not found".to_string(),
location: None,
line: None,
},
];
let breakdown = calculator.breakdown_from_errors(&BreakdownInput {
parse_ok: true,
type_check_ok: false,
build_ok: false,
errors: &errors,
doctest_pass: false,
unit_test_pass: false,
property_test_pass: false,
clippy_clean: false,
tdg_grade_b_or_better: false,
complexity_ok: true,
trace_match: false,
output_equiv: false,
});
assert_eq!(breakdown.a1_parse, 10);
assert_eq!(breakdown.a2_type_check, 0);
assert_eq!(breakdown.a3_cargo_build, 0);
assert!(breakdown.b1_no_e0308 <= 4);
assert!(breakdown.b2_no_e0599 >= 5);
assert_eq!(breakdown.b3_no_e0425, 7);
assert_eq!(breakdown.d3_complexity, 2);
}
#[test]
fn test_breakdown_from_errors_all_pass() {
let calculator = ScoreCalculator::new();
let empty: Vec<CompilationError> = vec![];
let breakdown = calculator.breakdown_from_errors(&BreakdownInput {
parse_ok: true,
type_check_ok: true,
build_ok: true,
errors: &empty,
doctest_pass: true,
unit_test_pass: true,
property_test_pass: true,
clippy_clean: true,
tdg_grade_b_or_better: true,
complexity_ok: true,
trace_match: true,
output_equiv: true,
});
assert_eq!(breakdown.a1_parse, 10);
assert_eq!(breakdown.a2_type_check, 15);
assert_eq!(breakdown.a3_cargo_build, 15);
assert_eq!(breakdown.c1_doctest, 5);
assert_eq!(breakdown.c2_unit_test, 5);
assert_eq!(breakdown.c3_property_test, 5);
assert_eq!(breakdown.d1_clippy, 5);
assert_eq!(breakdown.d2_tdg, 3);
assert_eq!(breakdown.d3_complexity, 2);
assert_eq!(breakdown.e1_trace_match, 5);
assert_eq!(breakdown.e2_output_equiv, 5);
}
#[test]
fn test_tarantula_score() {
let stats = DecisionStats {
failed_count: 8,
passed_count: 2,
};
let tarantula = stats.tarantula_score(10, 10);
assert!((tarantula.suspiciousness - 0.8).abs() < 0.01);
}
#[test]
fn test_tarantula_score_zero_failed() {
let stats = DecisionStats {
failed_count: 0,
passed_count: 5,
};
let tarantula = stats.tarantula_score(0, 10);
assert_eq!(tarantula.suspiciousness, 0.0);
}
#[test]
fn test_tarantula_score_zero_passed() {
let stats = DecisionStats {
failed_count: 5,
passed_count: 0,
};
let tarantula = stats.tarantula_score(10, 0);
assert!((tarantula.suspiciousness - 1.0).abs() < 0.01);
}
#[test]
fn test_tarantula_score_both_zero() {
let stats = DecisionStats {
failed_count: 0,
passed_count: 0,
};
let tarantula = stats.tarantula_score(0, 0);
assert_eq!(tarantula.suspiciousness, 0.0);
}
#[test]
fn test_tarantula_score_clone() {
let score = TarantulaScore {
suspiciousness: 0.5,
failed_count: 3,
passed_count: 7,
};
let cloned = score.clone();
assert_eq!(cloned.suspiciousness, score.suspiciousness);
}
#[test]
fn test_tarantula_score_debug() {
let score = TarantulaScore {
suspiciousness: 0.5,
failed_count: 3,
passed_count: 7,
};
let debug = format!("{:?}", score);
assert!(debug.contains("TarantulaScore"));
}
#[test]
fn test_decision_stats_default() {
let stats = DecisionStats::default();
assert_eq!(stats.failed_count, 0);
assert_eq!(stats.passed_count, 0);
}
#[test]
fn test_decision_stats_clone() {
let stats = DecisionStats {
failed_count: 5,
passed_count: 10,
};
let cloned = stats.clone();
assert_eq!(cloned.failed_count, stats.failed_count);
}
#[test]
fn test_decision_stats_debug() {
let stats = DecisionStats::default();
let debug = format!("{:?}", stats);
assert!(debug.contains("DecisionStats"));
}
#[test]
fn test_corpus_aggregation() {
let calculator = ScoreCalculator::new();
let results = vec![
SingleShotResult {
file_path: PathBuf::from("a.py"),
score: SingleShotScore {
total: 80,
compilation: 40,
type_inference: 20,
test_coverage: 10,
code_quality: 5,
semantic_equivalence: 5,
gateway_passed: true,
mode: ScoringMode::Fast,
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![],
},
SingleShotResult {
file_path: PathBuf::from("b.py"),
score: SingleShotScore {
total: 60,
compilation: 30,
type_inference: 15,
test_coverage: 10,
code_quality: 3,
semantic_equivalence: 2,
gateway_passed: true,
mode: ScoringMode::Fast,
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![],
},
];
let report = calculator.aggregate(&results);
assert!((report.aggregate_score - 70.0).abs() < 0.01);
assert_eq!(report.grade, Grade::B);
}
#[test]
fn test_corpus_aggregation_empty() {
let calculator = ScoreCalculator::new();
let results: Vec<SingleShotResult> = vec![];
let report = calculator.aggregate(&results);
assert_eq!(report.aggregate_score, 0.0);
assert_eq!(report.grade, Grade::F);
assert!(report.results.is_empty());
assert!(report.top_blockers.is_empty());
}
#[test]
fn test_corpus_aggregation_with_errors() {
let calculator = ScoreCalculator::new();
let results = vec![
SingleShotResult {
file_path: PathBuf::from("a.py"),
score: SingleShotScore {
total: 50,
..Default::default()
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![CompilationError {
code: "E0308".to_string(),
message: "type mismatch".to_string(),
location: None,
line: None,
}],
transpiler_decisions: vec![],
},
SingleShotResult {
file_path: PathBuf::from("b.py"),
score: SingleShotScore {
total: 50,
..Default::default()
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![CompilationError {
code: "E0308".to_string(),
message: "type mismatch".to_string(),
location: None,
line: None,
}],
transpiler_decisions: vec![],
},
];
let report = calculator.aggregate(&results);
assert!(!report.top_blockers.is_empty());
assert_eq!(report.top_blockers[0].pattern, "E0308");
assert_eq!(report.top_blockers[0].affected_files, 2);
}
#[test]
fn test_corpus_score_report_clone() {
let report = CorpusScoreReport {
results: vec![],
aggregate_score: 75.0,
grade: Grade::B,
category_averages: CategoryBreakdown::default(),
top_blockers: vec![],
};
let cloned = report.clone();
assert_eq!(cloned.aggregate_score, report.aggregate_score);
}
#[test]
fn test_corpus_score_report_debug() {
let report = CorpusScoreReport {
results: vec![],
aggregate_score: 0.0,
grade: Grade::F,
category_averages: CategoryBreakdown::default(),
top_blockers: vec![],
};
let debug = format!("{:?}", report);
assert!(debug.contains("CorpusScoreReport"));
}
#[test]
fn test_analyze_score_failures_empty() {
let results: Vec<SingleShotResult> = vec![];
let analysis = analyze_score_failures(&results);
assert!(analysis.is_empty());
}
#[test]
fn test_analyze_score_failures_with_decisions() {
let results = vec![
SingleShotResult {
file_path: PathBuf::from("a.py"),
score: SingleShotScore {
total: 50, ..Default::default()
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![TranspilerDecision::TypeInference {
variable: "x".to_string(),
inferred_type: "Value".to_string(),
}],
},
SingleShotResult {
file_path: PathBuf::from("b.py"),
score: SingleShotScore {
total: 90, ..Default::default()
},
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![TranspilerDecision::TypeInference {
variable: "x".to_string(),
inferred_type: "Value".to_string(),
}],
},
];
let analysis = analyze_score_failures(&results);
assert_eq!(analysis.len(), 1);
let decision = TranspilerDecision::TypeInference {
variable: "x".to_string(),
inferred_type: "Value".to_string(),
};
let score = analysis.get(&decision).unwrap();
assert!((score.suspiciousness - 0.5).abs() < 0.01);
}
#[test]
fn test_single_shot_result_clone() {
let result = SingleShotResult {
file_path: PathBuf::from("test.py"),
score: SingleShotScore::default(),
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![],
};
let cloned = result.clone();
assert_eq!(cloned.file_path, result.file_path);
}
#[test]
fn test_single_shot_result_debug() {
let result = SingleShotResult {
file_path: PathBuf::from("test.py"),
score: SingleShotScore::default(),
category_breakdown: CategoryBreakdown::default(),
error_details: vec![],
transpiler_decisions: vec![],
};
let debug = format!("{:?}", result);
assert!(debug.contains("SingleShotResult"));
}
}