use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CompileResult {
pub name: String,
pub success: bool,
pub error_code: Option<String>,
pub error_message: Option<String>,
pub python_source: Option<String>,
}
impl CompileResult {
pub fn success(name: impl Into<String>) -> Self {
Self {
name: name.into(),
success: true,
error_code: None,
error_message: None,
python_source: None,
}
}
pub fn failure(
name: impl Into<String>,
code: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
name: name.into(),
success: false,
error_code: Some(code.into()),
error_message: Some(message.into()),
python_source: None,
}
}
pub fn with_source(mut self, source: impl Into<String>) -> Self {
self.python_source = Some(source.into());
self
}
}
#[derive(Debug, Clone, Default)]
pub struct ErrorTaxonomy {
pub count: usize,
pub samples: Vec<String>,
}
impl ErrorTaxonomy {
pub fn new() -> Self {
Self::default()
}
pub fn add_sample(&mut self, sample: impl Into<String>) {
self.count += 1;
if self.samples.len() < 3 {
self.samples.push(sample.into());
}
}
pub fn percentage(&self, total: usize) -> f64 {
if total == 0 {
0.0
} else {
(self.count as f64 / total as f64) * 100.0
}
}
}
pub fn analyze_results(
results: &[CompileResult],
) -> (usize, usize, HashMap<String, ErrorTaxonomy>) {
let mut pass = 0;
let mut fail = 0;
let mut taxonomy: HashMap<String, ErrorTaxonomy> = HashMap::new();
for result in results {
if result.success {
pass += 1;
} else {
fail += 1;
let error_code = result.error_code.as_deref().unwrap_or("unknown");
let entry = taxonomy.entry(error_code.to_string()).or_default();
entry.add_sample(&result.name);
}
}
(pass, fail, taxonomy)
}
pub fn calculate_rate(pass: usize, fail: usize) -> f64 {
let total = pass + fail;
if total == 0 {
0.0
} else {
(pass as f64 / total as f64) * 100.0
}
}
pub fn target_achieved(pass: usize, fail: usize, target_rate: f64) -> bool {
calculate_rate(pass, fail) >= target_rate * 100.0
}
pub fn extract_error(stderr: &str) -> (String, String) {
let code = stderr
.lines()
.find(|line| line.contains("error[E"))
.and_then(|line| {
let start = line.find("error[E")? + 6;
let end = line[start..].find(']')? + start;
Some(line[start..=end].to_string())
})
.unwrap_or_else(|| "unknown".to_string());
let message = stderr
.lines()
.find(|line| line.contains("error[E") || line.starts_with("error:"))
.map(|line| {
if let Some(pos) = line.find("]: ") {
line[pos + 3..].to_string()
} else if let Some(pos) = line.find("error: ") {
line[pos + 7..].to_string()
} else {
line.to_string()
}
})
.unwrap_or_else(|| "compilation error".to_string());
(code, message)
}
#[derive(Debug, Clone, Default)]
pub struct FilterStats {
pub total_files: usize,
pub files_after_pattern: usize,
pub files_after_tags: usize,
pub files_after_limit: usize,
pub files_after_sample: usize,
}
impl FilterStats {
pub fn new(total: usize) -> Self {
Self {
total_files: total,
files_after_pattern: total,
files_after_tags: total,
files_after_limit: total,
files_after_sample: total,
}
}
pub fn reduction_percent(&self) -> f64 {
if self.total_files == 0 {
0.0
} else {
let final_count = self.files_after_sample;
(1.0 - (final_count as f64 / self.total_files as f64)) * 100.0
}
}
pub fn final_count(&self) -> usize {
self.files_after_sample
}
}
#[derive(Debug, Clone)]
pub struct BisectionState {
pub files: Vec<String>,
pub low: usize,
pub high: usize,
pub iteration: usize,
pub max_iterations: usize,
pub result: Option<Vec<String>>,
}
impl BisectionState {
pub fn new(files: Vec<String>) -> Self {
let len = files.len();
Self {
files,
low: 0,
high: len,
iteration: 0,
max_iterations: ((len as f64).log2().ceil() as usize) + 5,
result: None,
}
}
pub fn is_complete(&self) -> bool {
self.result.is_some() || self.iteration >= self.max_iterations
}
pub fn current_test_set(&self) -> Vec<&String> {
let mid = (self.low + self.high) / 2;
self.files[self.low..mid].iter().collect()
}
pub fn advance(&mut self, has_failure: bool) {
self.iteration += 1;
let mid = (self.low + self.high) / 2;
if has_failure {
self.high = mid;
} else {
self.low = mid;
}
if self.high - self.low <= 1 {
self.result = Some(self.files[self.low..self.high].to_vec());
}
}
pub fn get_result(&self) -> Option<Vec<&String>> {
self.result.as_ref().map(|r| r.iter().collect())
}
pub fn remaining_space(&self) -> usize {
self.high - self.low
}
pub fn progress_percent(&self) -> f64 {
if self.files.is_empty() {
100.0
} else {
let initial = self.files.len();
let remaining = self.remaining_space();
(1.0 - (remaining as f64 / initial as f64)) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct ReportSummary {
pub total_files: usize,
pub passed: usize,
pub failed: usize,
pub pass_rate: f64,
pub target_rate: f64,
pub target_achieved: bool,
pub top_errors: Vec<(String, usize)>,
}
impl ReportSummary {
pub fn from_results(results: &[CompileResult], target_rate: f64) -> Self {
let (passed, failed, taxonomy) = analyze_results(results);
let rate = calculate_rate(passed, failed);
let mut top_errors: Vec<_> = taxonomy.iter().map(|(k, v)| (k.clone(), v.count)).collect();
top_errors.sort_by(|a, b| b.1.cmp(&a.1));
top_errors.truncate(5);
Self {
total_files: passed + failed,
passed,
failed,
pass_rate: rate,
target_rate,
target_achieved: rate >= target_rate * 100.0,
top_errors,
}
}
pub fn improvement_needed(&self) -> f64 {
if self.target_achieved {
0.0
} else {
(self.target_rate * 100.0) - self.pass_rate
}
}
pub fn files_to_fix(&self) -> usize {
if self.target_achieved {
0
} else {
let target_passed = (self.total_files as f64 * self.target_rate).ceil() as usize;
target_passed.saturating_sub(self.passed)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SemanticTag {
Dict,
List,
Tuple,
Set,
String,
Numeric,
IO,
Argparse,
Regex,
Json,
Async,
Class,
Generic,
Other(String),
}
impl SemanticTag {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"dict" | "dictionary" => Self::Dict,
"list" | "array" => Self::List,
"tuple" => Self::Tuple,
"set" => Self::Set,
"string" | "str" => Self::String,
"numeric" | "int" | "float" => Self::Numeric,
"io" | "file" => Self::IO,
"argparse" | "cli" | "args" => Self::Argparse,
"regex" | "re" => Self::Regex,
"json" => Self::Json,
"async" | "await" => Self::Async,
"class" | "oop" => Self::Class,
"generic" | "typing" => Self::Generic,
_ => Self::Other(s.to_string()),
}
}
pub fn matches_content(&self, content: &str) -> bool {
match self {
Self::Dict => {
content.contains("dict") || content.contains("Dict") || content.contains("{:")
}
Self::List => {
content.contains("list") || content.contains("List") || content.contains("[")
}
Self::Tuple => content.contains("tuple") || content.contains("Tuple"),
Self::Set => content.contains("set") || content.contains("Set"),
Self::String => content.contains("str") || content.contains("String"),
Self::Numeric => content.contains("int") || content.contains("float"),
Self::IO => {
content.contains("open(") || content.contains("read(") || content.contains("write(")
}
Self::Argparse => content.contains("argparse") || content.contains("ArgumentParser"),
Self::Regex => content.contains("import re") || content.contains("re."),
Self::Json => content.contains("import json") || content.contains("json."),
Self::Async => content.contains("async ") || content.contains("await "),
Self::Class => content.contains("class "),
Self::Generic => content.contains("TypeVar") || content.contains("Generic"),
Self::Other(_) => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_result_success() {
let result = CompileResult::success("test.py");
assert!(result.success);
assert!(result.error_code.is_none());
assert_eq!(result.name, "test.py");
}
#[test]
fn test_compile_result_failure() {
let result = CompileResult::failure("test.py", "E0308", "type mismatch");
assert!(!result.success);
assert_eq!(result.error_code, Some("E0308".to_string()));
assert_eq!(result.error_message, Some("type mismatch".to_string()));
}
#[test]
fn test_compile_result_with_source() {
let result = CompileResult::success("test.py").with_source("print('hello')");
assert_eq!(result.python_source, Some("print('hello')".to_string()));
}
#[test]
fn test_error_taxonomy_add_sample() {
let mut taxonomy = ErrorTaxonomy::new();
taxonomy.add_sample("file1.py");
taxonomy.add_sample("file2.py");
taxonomy.add_sample("file3.py");
taxonomy.add_sample("file4.py");
assert_eq!(taxonomy.count, 4);
assert_eq!(taxonomy.samples.len(), 3);
}
#[test]
fn test_error_taxonomy_percentage() {
let mut taxonomy = ErrorTaxonomy::new();
taxonomy.count = 25;
assert!((taxonomy.percentage(100) - 25.0).abs() < 0.001);
assert!((taxonomy.percentage(50) - 50.0).abs() < 0.001);
assert_eq!(taxonomy.percentage(0), 0.0);
}
#[test]
fn test_analyze_results() {
let results = vec![
CompileResult::success("a.py"),
CompileResult::success("b.py"),
CompileResult::failure("c.py", "E0308", "type mismatch"),
CompileResult::failure("d.py", "E0308", "type mismatch"),
CompileResult::failure("e.py", "E0277", "trait not satisfied"),
];
let (pass, fail, taxonomy) = analyze_results(&results);
assert_eq!(pass, 2);
assert_eq!(fail, 3);
assert_eq!(taxonomy.len(), 2);
assert_eq!(taxonomy.get("E0308").unwrap().count, 2);
assert_eq!(taxonomy.get("E0277").unwrap().count, 1);
}
#[test]
fn test_calculate_rate() {
assert!((calculate_rate(80, 20) - 80.0).abs() < 0.001);
assert!((calculate_rate(0, 100) - 0.0).abs() < 0.001);
assert!((calculate_rate(100, 0) - 100.0).abs() < 0.001);
assert_eq!(calculate_rate(0, 0), 0.0);
}
#[test]
fn test_target_achieved() {
assert!(target_achieved(95, 5, 0.90));
assert!(target_achieved(90, 10, 0.90));
assert!(!target_achieved(89, 11, 0.90));
assert!(!target_achieved(80, 20, 0.90));
}
#[test]
fn test_extract_error() {
let stderr = r#"error[E0308]: mismatched types
--> src/main.rs:10:5
|
10 | let x: i32 = "hello";
| --- ^^^^^^^^^ expected `i32`, found `&str`
"#;
let (code, message) = extract_error(stderr);
assert_eq!(code, "E0308]");
assert!(message.contains("mismatched types"));
}
#[test]
fn test_extract_error_unknown() {
let (code, message) = extract_error("some other error");
assert_eq!(code, "unknown");
assert_eq!(message, "compilation error");
}
#[test]
fn test_filter_stats() {
let mut stats = FilterStats::new(100);
stats.files_after_pattern = 80;
stats.files_after_tags = 60;
stats.files_after_limit = 50;
stats.files_after_sample = 25;
assert_eq!(stats.final_count(), 25);
assert!((stats.reduction_percent() - 75.0).abs() < 0.001);
}
#[test]
fn test_bisection_state_new() {
let files = vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
];
let state = BisectionState::new(files);
assert_eq!(state.low, 0);
assert_eq!(state.high, 4);
assert_eq!(state.iteration, 0);
assert!(!state.is_complete());
}
#[test]
fn test_bisection_state_advance() {
let files: Vec<String> = (0..8).map(|i| format!("file{}.py", i)).collect();
let mut state = BisectionState::new(files);
state.advance(true);
assert_eq!(state.high, 4);
assert_eq!(state.low, 0);
state.advance(true);
assert_eq!(state.high, 2);
}
#[test]
fn test_bisection_state_complete() {
let files = vec!["a".to_string(), "b".to_string()];
let mut state = BisectionState::new(files);
state.advance(true); assert!(state.is_complete());
assert!(state.get_result().is_some());
}
#[test]
fn test_bisection_progress() {
let files: Vec<String> = (0..16).map(|i| format!("file{}.py", i)).collect();
let mut state = BisectionState::new(files);
assert_eq!(state.progress_percent(), 0.0);
state.advance(true); assert!((state.progress_percent() - 50.0).abs() < 0.001);
state.advance(true); assert!((state.progress_percent() - 75.0).abs() < 0.001);
}
#[test]
fn test_report_summary() {
let results = vec![
CompileResult::success("a.py"),
CompileResult::success("b.py"),
CompileResult::failure("c.py", "E0308", "error"),
];
let summary = ReportSummary::from_results(&results, 0.80);
assert_eq!(summary.total_files, 3);
assert_eq!(summary.passed, 2);
assert_eq!(summary.failed, 1);
assert!((summary.pass_rate - 66.67).abs() < 0.1);
assert!(!summary.target_achieved);
}
#[test]
fn test_report_summary_improvement_needed() {
let results = vec![
CompileResult::success("a.py"),
CompileResult::failure("b.py", "E0308", "error"),
];
let summary = ReportSummary::from_results(&results, 0.80);
assert!((summary.improvement_needed() - 30.0).abs() < 0.1);
}
#[test]
fn test_report_summary_files_to_fix() {
let results: Vec<_> = (0..100)
.map(|i| {
if i < 70 {
CompileResult::success(format!("file{}.py", i))
} else {
CompileResult::failure(format!("file{}.py", i), "E0308", "error")
}
})
.collect();
let summary = ReportSummary::from_results(&results, 0.80);
assert_eq!(summary.files_to_fix(), 10); }
#[test]
fn test_semantic_tag_from_str() {
assert_eq!(SemanticTag::from_str("dict"), SemanticTag::Dict);
assert_eq!(SemanticTag::from_str("Dict"), SemanticTag::Dict);
assert_eq!(SemanticTag::from_str("list"), SemanticTag::List);
assert_eq!(SemanticTag::from_str("argparse"), SemanticTag::Argparse);
assert!(matches!(
SemanticTag::from_str("unknown"),
SemanticTag::Other(_)
));
}
#[test]
fn test_semantic_tag_matches_content() {
assert!(SemanticTag::Dict.matches_content("x: Dict[str, int] = {}"));
assert!(SemanticTag::List.matches_content("items: List[int] = []"));
assert!(SemanticTag::Argparse.matches_content("import argparse"));
assert!(SemanticTag::Async.matches_content("async def main():"));
assert!(SemanticTag::Class.matches_content("class Foo:"));
assert!(!SemanticTag::Dict.matches_content("x = 42"));
}
#[test]
fn test_semantic_tag_io() {
assert!(SemanticTag::IO.matches_content("with open('file.txt') as f:"));
assert!(SemanticTag::IO.matches_content("f.read()"));
assert!(SemanticTag::IO.matches_content("f.write('data')"));
}
#[test]
fn test_semantic_tag_json_regex() {
assert!(SemanticTag::Json.matches_content("import json\ndata = json.loads(s)"));
assert!(SemanticTag::Regex.matches_content("import re\nre.match(r'\\d+', s)"));
}
#[test]
fn test_compile_result_debug() {
let result = CompileResult::success("test.py");
let debug = format!("{:?}", result);
assert!(debug.contains("test.py"));
}
#[test]
fn test_compile_result_clone() {
let result = CompileResult::failure("a.py", "E0308", "error");
let cloned = result.clone();
assert_eq!(result.name, cloned.name);
assert_eq!(result.error_code, cloned.error_code);
}
#[test]
fn test_compile_result_chain() {
let result = CompileResult::failure("x.py", "E0001", "msg").with_source("def foo(): pass");
assert!(!result.success);
assert!(result.python_source.is_some());
}
#[test]
fn test_error_taxonomy_default() {
let taxonomy = ErrorTaxonomy::default();
assert_eq!(taxonomy.count, 0);
assert!(taxonomy.samples.is_empty());
}
#[test]
fn test_error_taxonomy_percentage_100() {
let mut taxonomy = ErrorTaxonomy::new();
taxonomy.count = 100;
assert!((taxonomy.percentage(100) - 100.0).abs() < 0.001);
}
#[test]
fn test_error_taxonomy_single_sample() {
let mut taxonomy = ErrorTaxonomy::new();
taxonomy.add_sample("only_one.py");
assert_eq!(taxonomy.count, 1);
assert_eq!(taxonomy.samples.len(), 1);
}
#[test]
fn test_analyze_results_all_success() {
let results = vec![
CompileResult::success("a.py"),
CompileResult::success("b.py"),
];
let (pass, fail, taxonomy) = analyze_results(&results);
assert_eq!(pass, 2);
assert_eq!(fail, 0);
assert!(taxonomy.is_empty());
}
#[test]
fn test_analyze_results_all_failure() {
let results = vec![
CompileResult::failure("a.py", "E0308", "e1"),
CompileResult::failure("b.py", "E0308", "e2"),
];
let (pass, fail, taxonomy) = analyze_results(&results);
assert_eq!(pass, 0);
assert_eq!(fail, 2);
assert_eq!(taxonomy.get("E0308").unwrap().count, 2);
}
#[test]
fn test_analyze_results_empty() {
let results: Vec<CompileResult> = vec![];
let (pass, fail, taxonomy) = analyze_results(&results);
assert_eq!(pass, 0);
assert_eq!(fail, 0);
assert!(taxonomy.is_empty());
}
#[test]
fn test_analyze_results_unknown_error() {
let mut result = CompileResult::failure("a.py", "E0000", "unknown");
result.error_code = None;
let results = vec![result];
let (_, fail, taxonomy) = analyze_results(&results);
assert_eq!(fail, 1);
assert!(taxonomy.contains_key("unknown"));
}
#[test]
fn test_extract_error_simple_error() {
let stderr = "error: linking with `cc` failed";
let (code, message) = extract_error(stderr);
assert_eq!(code, "unknown");
assert!(message.contains("linking"));
}
#[test]
fn test_extract_error_multiple_errors() {
let stderr = r#"error[E0308]: mismatched types
error[E0277]: trait bound not satisfied"#;
let (code, _) = extract_error(stderr);
assert!(code.contains("E0308"));
}
#[test]
fn test_extract_error_empty() {
let (code, message) = extract_error("");
assert_eq!(code, "unknown");
assert_eq!(message, "compilation error");
}
#[test]
fn test_filter_stats_default() {
let stats = FilterStats::default();
assert_eq!(stats.total_files, 0);
}
#[test]
fn test_filter_stats_no_reduction() {
let stats = FilterStats::new(100);
assert_eq!(stats.final_count(), 100);
assert_eq!(stats.reduction_percent(), 0.0);
}
#[test]
fn test_filter_stats_full_reduction() {
let mut stats = FilterStats::new(100);
stats.files_after_sample = 0;
assert_eq!(stats.reduction_percent(), 100.0);
}
#[test]
fn test_filter_stats_clone() {
let stats = FilterStats::new(50);
let cloned = stats.clone();
assert_eq!(stats.total_files, cloned.total_files);
}
#[test]
fn test_bisection_state_empty() {
let state = BisectionState::new(vec![]);
assert_eq!(state.remaining_space(), 0);
assert_eq!(state.progress_percent(), 100.0);
}
#[test]
fn test_bisection_state_single_file() {
let files = vec!["only.py".to_string()];
let mut state = BisectionState::new(files);
state.advance(true);
assert!(state.is_complete());
}
#[test]
fn test_bisection_state_advance_no_failure() {
let files: Vec<String> = (0..8).map(|i| format!("file{}.py", i)).collect();
let mut state = BisectionState::new(files);
state.advance(false); assert_eq!(state.low, 4);
assert_eq!(state.high, 8);
}
#[test]
fn test_bisection_state_current_test_set() {
let files: Vec<String> = (0..4).map(|i| format!("f{}.py", i)).collect();
let state = BisectionState::new(files);
let test_set = state.current_test_set();
assert_eq!(test_set.len(), 2);
}
#[test]
fn test_bisection_state_max_iterations() {
let files: Vec<String> = (0..16).map(|i| format!("f{}.py", i)).collect();
let state = BisectionState::new(files);
assert!(state.max_iterations > 4);
}
#[test]
fn test_bisection_state_clone() {
let files = vec!["a".to_string()];
let state = BisectionState::new(files);
let cloned = state.clone();
assert_eq!(state.low, cloned.low);
}
#[test]
fn test_report_summary_target_achieved() {
let results = vec![
CompileResult::success("a.py"),
CompileResult::success("b.py"),
CompileResult::success("c.py"),
CompileResult::success("d.py"),
CompileResult::failure("e.py", "E0001", "err"),
];
let summary = ReportSummary::from_results(&results, 0.80);
assert!(summary.target_achieved);
assert_eq!(summary.improvement_needed(), 0.0);
}
#[test]
fn test_report_summary_empty_results() {
let results: Vec<CompileResult> = vec![];
let summary = ReportSummary::from_results(&results, 0.80);
assert_eq!(summary.total_files, 0);
assert_eq!(summary.pass_rate, 0.0);
}
#[test]
fn test_report_summary_top_errors_limit() {
let results: Vec<_> = (0..10)
.map(|i| CompileResult::failure(format!("f{}.py", i), format!("E{:04}", i), "err"))
.collect();
let summary = ReportSummary::from_results(&results, 0.80);
assert!(summary.top_errors.len() <= 5);
}
#[test]
fn test_report_summary_files_to_fix_achieved() {
let results: Vec<_> = (0..100)
.map(|i| CompileResult::success(format!("f{}.py", i)))
.collect();
let summary = ReportSummary::from_results(&results, 0.80);
assert_eq!(summary.files_to_fix(), 0);
}
#[test]
fn test_report_summary_clone() {
let results = vec![CompileResult::success("a.py")];
let summary = ReportSummary::from_results(&results, 0.80);
let cloned = summary.clone();
assert_eq!(summary.total_files, cloned.total_files);
}
#[test]
fn test_semantic_tag_tuple() {
assert_eq!(SemanticTag::from_str("tuple"), SemanticTag::Tuple);
assert!(SemanticTag::Tuple.matches_content("x: Tuple[int, str]"));
}
#[test]
fn test_semantic_tag_set() {
assert_eq!(SemanticTag::from_str("set"), SemanticTag::Set);
assert!(SemanticTag::Set.matches_content("items: Set[int] = set()"));
}
#[test]
fn test_semantic_tag_string() {
assert_eq!(SemanticTag::from_str("str"), SemanticTag::String);
assert!(SemanticTag::String.matches_content("x: str = ''"));
}
#[test]
fn test_semantic_tag_numeric() {
assert_eq!(SemanticTag::from_str("int"), SemanticTag::Numeric);
assert_eq!(SemanticTag::from_str("float"), SemanticTag::Numeric);
assert!(SemanticTag::Numeric.matches_content("x: int = 42"));
}
#[test]
fn test_semantic_tag_generic() {
assert_eq!(SemanticTag::from_str("typing"), SemanticTag::Generic);
assert!(SemanticTag::Generic.matches_content("T = TypeVar('T')"));
}
#[test]
fn test_semantic_tag_cli() {
assert_eq!(SemanticTag::from_str("cli"), SemanticTag::Argparse);
assert_eq!(SemanticTag::from_str("args"), SemanticTag::Argparse);
}
#[test]
fn test_semantic_tag_other_matches_nothing() {
let tag = SemanticTag::Other("custom".to_string());
assert!(!tag.matches_content("anything"));
}
#[test]
fn test_semantic_tag_dictionary() {
assert_eq!(SemanticTag::from_str("dictionary"), SemanticTag::Dict);
}
#[test]
fn test_semantic_tag_array() {
assert_eq!(SemanticTag::from_str("array"), SemanticTag::List);
}
#[test]
fn test_semantic_tag_file() {
assert_eq!(SemanticTag::from_str("file"), SemanticTag::IO);
}
#[test]
fn test_semantic_tag_await() {
assert_eq!(SemanticTag::from_str("await"), SemanticTag::Async);
}
#[test]
fn test_semantic_tag_oop() {
assert_eq!(SemanticTag::from_str("oop"), SemanticTag::Class);
}
#[test]
fn test_semantic_tag_re() {
assert_eq!(SemanticTag::from_str("re"), SemanticTag::Regex);
}
#[test]
fn test_semantic_tag_eq() {
assert_eq!(SemanticTag::Dict, SemanticTag::Dict);
assert_ne!(SemanticTag::Dict, SemanticTag::List);
}
#[test]
fn test_semantic_tag_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(SemanticTag::Dict);
set.insert(SemanticTag::List);
set.insert(SemanticTag::Dict);
assert_eq!(set.len(), 2);
}
#[test]
fn test_semantic_tag_debug() {
let tag = SemanticTag::Dict;
let debug = format!("{:?}", tag);
assert!(debug.contains("Dict"));
}
#[test]
fn test_semantic_tag_clone() {
let tag = SemanticTag::Async;
let cloned = tag.clone();
assert_eq!(tag, cloned);
}
#[test]
fn test_calculate_rate_50_percent() {
assert!((calculate_rate(50, 50) - 50.0).abs() < 0.001);
}
#[test]
fn test_calculate_rate_large_numbers() {
assert!((calculate_rate(999999, 1) - 99.9999).abs() < 0.01);
}
#[test]
fn test_target_achieved_exact() {
assert!(target_achieved(80, 20, 0.80));
}
#[test]
fn test_target_achieved_zero_target() {
assert!(target_achieved(0, 100, 0.0));
}
#[test]
fn test_target_achieved_100_target() {
assert!(target_achieved(100, 0, 1.0));
assert!(!target_achieved(99, 1, 1.0));
}
}