#![cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[tokio::test]
async fn test_service_creation() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
assert_eq!(service.config.target_coverage, 95.0);
}
#[test]
fn test_config_default_values() {
let config = CoverageImprovementConfig::default();
assert_eq!(config.project_path, PathBuf::from("."));
assert_eq!(config.target_coverage, 95.0);
assert_eq!(config.max_iterations, 10);
assert!(!config.fast_mode);
assert_eq!(config.mutation_threshold, 80.0);
assert!(config.focus_patterns.is_empty());
assert!(config.exclude_patterns.is_empty());
}
#[test]
fn test_config_custom_values() {
let config = CoverageImprovementConfig {
project_path: PathBuf::from("/custom/path"),
target_coverage: 85.0,
max_iterations: 5,
fast_mode: true,
mutation_threshold: 70.0,
focus_patterns: vec!["src/**/*.rs".to_string()],
exclude_patterns: vec!["**/tests/**".to_string()],
};
assert_eq!(config.project_path, PathBuf::from("/custom/path"));
assert_eq!(config.target_coverage, 85.0);
assert_eq!(config.max_iterations, 5);
assert!(config.fast_mode);
assert_eq!(config.mutation_threshold, 70.0);
assert_eq!(config.focus_patterns.len(), 1);
assert_eq!(config.exclude_patterns.len(), 1);
}
#[test]
fn test_iteration_report_serialization() {
let report = IterationReport {
iteration: 1,
files_targeted: vec![PathBuf::from("src/lib.rs")],
tests_generated: 5,
coverage_gain: 2.5,
mutation_score: 85.0,
};
let json = serde_json::to_string(&report).unwrap();
let deserialized: IterationReport = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.iteration, 1);
assert_eq!(deserialized.files_targeted.len(), 1);
assert_eq!(deserialized.tests_generated, 5);
assert!((deserialized.coverage_gain - 2.5).abs() < f64::EPSILON);
assert!((deserialized.mutation_score - 85.0).abs() < f64::EPSILON);
}
#[test]
fn test_coverage_improvement_report_serialization() {
let report = CoverageImprovementReport {
baseline_coverage: 50.0,
target_coverage: 95.0,
final_coverage: 75.0,
iterations: vec![
IterationReport {
iteration: 1,
files_targeted: vec![PathBuf::from("src/lib.rs")],
tests_generated: 5,
coverage_gain: 12.5,
mutation_score: 85.0,
},
IterationReport {
iteration: 2,
files_targeted: vec![PathBuf::from("src/main.rs")],
tests_generated: 3,
coverage_gain: 12.5,
mutation_score: 90.0,
},
],
success: false,
stop_reason: "Max iterations reached".to_string(),
};
let json = serde_json::to_string(&report).unwrap();
let deserialized: CoverageImprovementReport = serde_json::from_str(&json).unwrap();
assert!((deserialized.baseline_coverage - 50.0).abs() < f64::EPSILON);
assert!((deserialized.target_coverage - 95.0).abs() < f64::EPSILON);
assert!((deserialized.final_coverage - 75.0).abs() < f64::EPSILON);
assert_eq!(deserialized.iterations.len(), 2);
assert!(!deserialized.success);
assert_eq!(deserialized.stop_reason, "Max iterations reached");
}
#[test]
fn test_extract_file_path_from_line_rs_extension() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let result = service.extract_file_path_from_line("src/lib.rs 10 errors");
assert_eq!(result, Some(PathBuf::from("src/lib.rs")));
}
#[test]
fn test_extract_file_path_from_line_toml_extension() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let result = service.extract_file_path_from_line("Checking Cargo.toml for updates");
assert_eq!(result, Some(PathBuf::from("Cargo.toml")));
}
#[test]
fn test_extract_file_path_from_line_md_extension() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let result = service.extract_file_path_from_line("Updated README.md with new docs");
assert_eq!(result, Some(PathBuf::from("README.md")));
}
#[test]
fn test_extract_file_path_from_line_no_match() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let result = service.extract_file_path_from_line("No file path here");
assert!(result.is_none());
}
#[test]
fn test_extract_file_path_from_line_empty() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let result = service.extract_file_path_from_line("");
assert!(result.is_none());
}
#[test]
fn test_extract_files_from_json_with_file_key() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!({
"file": "src/lib.rs",
"complexity": 10
});
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert_eq!(file_scores.len(), 1);
assert!(
(file_scores.get(&PathBuf::from("src/lib.rs")).unwrap() - 0.5).abs() < f64::EPSILON
);
}
#[test]
fn test_extract_files_from_json_with_path_key() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!({
"path": "src/main.rs",
"lines": 100
});
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.3);
assert_eq!(file_scores.len(), 1);
assert!(
(file_scores.get(&PathBuf::from("src/main.rs")).unwrap() - 0.3).abs() < f64::EPSILON
);
}
#[test]
fn test_extract_files_from_json_with_file_path_key() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!({
"file_path": "src/utils.rs",
"errors": 5
});
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.2);
assert_eq!(file_scores.len(), 1);
assert!(
(file_scores.get(&PathBuf::from("src/utils.rs")).unwrap() - 0.2).abs() < f64::EPSILON
);
}
#[test]
fn test_extract_files_from_json_nested_objects() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!({
"results": {
"file": "src/nested.rs",
"inner": {
"path": "src/deeper.rs"
}
}
});
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 1.0);
assert_eq!(file_scores.len(), 2);
assert!(file_scores.contains_key(&PathBuf::from("src/nested.rs")));
assert!(file_scores.contains_key(&PathBuf::from("src/deeper.rs")));
}
#[test]
fn test_extract_files_from_json_array() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!([
{"file": "src/a.rs"},
{"file": "src/b.rs"},
{"file": "src/c.rs"}
]);
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.4);
assert_eq!(file_scores.len(), 3);
for (_, score) in &file_scores {
assert!((*score - 0.4).abs() < f64::EPSILON);
}
}
#[test]
fn test_extract_files_from_json_accumulates_scores() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!([
{"file": "src/lib.rs"},
{"path": "src/lib.rs"},
{"file_path": "src/lib.rs"}
]);
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.2);
assert_eq!(file_scores.len(), 1);
assert!(
(*file_scores.get(&PathBuf::from("src/lib.rs")).unwrap() - 0.6).abs() < f64::EPSILON
);
}
#[test]
fn test_extract_files_from_json_empty_object() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!({});
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert!(file_scores.is_empty());
}
#[test]
fn test_extract_files_from_json_primitive_values() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json: serde_json::Value = serde_json::json!("src/lib.rs");
let mut file_scores = std::collections::HashMap::new();
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert!(file_scores.is_empty());
let json: serde_json::Value = serde_json::json!(42);
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert!(file_scores.is_empty());
let json: serde_json::Value = serde_json::json!(true);
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert!(file_scores.is_empty());
let json: serde_json::Value = serde_json::json!(null);
service.extract_files_from_json(&json, &mut file_scores, 0.5);
assert!(file_scores.is_empty());
}
#[test]
fn test_parse_and_score_json_input() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let json_output = r#"{"files": [{"file": "src/lib.rs"}, {"file": "src/main.rs"}]}"#;
let mut file_scores = std::collections::HashMap::new();
let result = service.parse_and_score(json_output, &mut file_scores, 0.5);
assert!(result.is_ok());
assert_eq!(file_scores.len(), 2);
}
#[test]
fn test_parse_and_score_text_input() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let text_output = "Analyzing src/lib.rs\nFound issues in Cargo.toml\nProcessed README.md";
let mut file_scores = std::collections::HashMap::new();
let result = service.parse_and_score(text_output, &mut file_scores, 0.3);
assert!(result.is_ok());
assert_eq!(file_scores.len(), 3);
}
#[test]
fn test_parse_and_score_empty_input() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let mut file_scores = std::collections::HashMap::new();
let result = service.parse_and_score("", &mut file_scores, 0.5);
assert!(result.is_ok());
assert!(file_scores.is_empty());
}
#[test]
fn test_generate_strategy_for_type_integers() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("i32").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<i32>()");
let ty: syn::Type = syn::parse_str("i64").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<i64>()");
let ty: syn::Type = syn::parse_str("u32").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<u32>()");
let ty: syn::Type = syn::parse_str("usize").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<usize>()");
}
#[test]
fn test_generate_strategy_for_type_floats() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("f32").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<f32>()");
let ty: syn::Type = syn::parse_str("f64").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<f64>()");
}
#[test]
fn test_generate_strategy_for_type_bool_char() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("bool").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<bool>()");
let ty: syn::Type = syn::parse_str("char").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<char>()");
}
#[test]
fn test_generate_strategy_for_type_string() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("String").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), r#"".*""#);
}
#[test]
fn test_generate_strategy_for_type_vec_option() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("Vec<i32>").unwrap();
assert_eq!(
service.generate_strategy_for_type(&ty),
"prop::collection::vec(any::<i32>(), 0..100)"
);
let ty: syn::Type = syn::parse_str("Option<String>").unwrap();
assert_eq!(
service.generate_strategy_for_type(&ty),
"prop::option::of(any::<i32>())"
);
}
#[test]
fn test_generate_strategy_for_type_pathbuf() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("PathBuf").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), r#""[a-z0-9/]+""#);
}
#[test]
fn test_generate_strategy_for_type_reference() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("&str").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), r#"".*""#);
let ty: syn::Type = syn::parse_str("&i32").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<i32>()");
}
#[test]
fn test_generate_strategy_for_type_unknown() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let ty: syn::Type = syn::parse_str("CustomType").unwrap();
assert_eq!(service.generate_strategy_for_type(&ty), "any::<i32>()");
}
#[test]
fn test_extract_public_functions() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let code = r#"
pub fn public_fn() {}
fn private_fn() {}
pub fn another_public() -> i32 { 42 }
pub(crate) fn crate_public() {}
"#;
let syntax_tree = syn::parse_file(code).unwrap();
let functions = service.extract_public_functions(&syntax_tree);
assert_eq!(functions.len(), 2);
assert_eq!(functions[0].sig.ident.to_string(), "public_fn");
assert_eq!(functions[1].sig.ident.to_string(), "another_public");
}
#[test]
fn test_extract_public_functions_empty() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let code = r#"
fn private_fn() {}
fn another_private() {}
"#;
let syntax_tree = syn::parse_file(code).unwrap();
let functions = service.extract_public_functions(&syntax_tree);
assert!(functions.is_empty());
}
#[test]
fn test_generate_proptest_module_no_params() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let code = "pub fn no_args() {}";
let syntax_tree = syn::parse_file(code).unwrap();
let functions = service.extract_public_functions(&syntax_tree);
let target = PathBuf::from("src/test.rs");
let module = service
.generate_proptest_module(&target, &functions)
.unwrap();
assert!(module.contains("use proptest::prelude::*;"));
assert!(module.contains("fn proptest_no_args()"));
assert!(module.contains("let _result = no_args();"));
}
#[test]
fn test_generate_proptest_module_with_params() {
let config = CoverageImprovementConfig::default();
let service = CoverageImprovementService::new(config);
let code = "pub fn with_args(x: i32, y: String) -> bool { true }";
let syntax_tree = syn::parse_file(code).unwrap();
let functions = service.extract_public_functions(&syntax_tree);
let target = PathBuf::from("src/test.rs");
let module = service
.generate_proptest_module(&target, &functions)
.unwrap();
assert!(module.contains("proptest!"));
assert!(module.contains("fn proptest_with_args"));
assert!(module.contains("x in any::<i32>()"));
assert!(module.contains("let _result = with_args(x, y);"));
}
#[test]
fn test_find_makefile_directory_current() {
let temp_dir = tempfile::tempdir().unwrap();
std::fs::write(temp_dir.path().join("Makefile"), "all:\n\techo test").unwrap();
let config = CoverageImprovementConfig {
project_path: temp_dir.path().to_path_buf(),
..Default::default()
};
let service = CoverageImprovementService::new(config);
let result = service.find_makefile_directory();
assert!(result.is_ok());
assert_eq!(result.unwrap(), temp_dir.path().canonicalize().unwrap());
}
#[test]
fn test_find_makefile_directory_parent() {
let parent_dir = tempfile::tempdir().unwrap();
std::fs::write(parent_dir.path().join("Makefile"), "all:\n\techo test").unwrap();
let child_dir = parent_dir.path().join("child");
std::fs::create_dir(&child_dir).unwrap();
let config = CoverageImprovementConfig {
project_path: child_dir.clone(),
..Default::default()
};
let service = CoverageImprovementService::new(config);
let result = service.find_makefile_directory();
assert!(result.is_ok());
assert_eq!(result.unwrap(), parent_dir.path().canonicalize().unwrap());
}
#[test]
fn test_find_makefile_directory_not_found() {
let temp_dir = tempfile::tempdir().unwrap();
let deep_child = temp_dir
.path()
.join("a")
.join("b")
.join("c")
.join("d")
.join("e")
.join("f");
std::fs::create_dir_all(&deep_child).unwrap();
let config = CoverageImprovementConfig {
project_path: deep_child,
..Default::default()
};
let service = CoverageImprovementService::new(config);
let result = service.find_makefile_directory();
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Could not find Makefile"));
}
#[tokio::test]
#[ignore = "Integration test - requires Makefile and make coverage"]
async fn test_already_at_target_coverage() {
let config = CoverageImprovementConfig {
target_coverage: 45.0, ..Default::default()
};
let service = CoverageImprovementService::new(config);
let report = service.improve_coverage().await.expect("internal error");
assert!(report.success);
assert_eq!(report.iterations.len(), 0);
assert!(report.stop_reason.contains("Already at target"));
}
#[tokio::test]
#[ignore = "Integration test - requires Makefile and make coverage"]
async fn test_improvement_iterations() {
let config = CoverageImprovementConfig {
target_coverage: 95.0,
max_iterations: 3,
..Default::default()
};
let service = CoverageImprovementService::new(config);
let report = service.improve_coverage().await.expect("internal error");
assert!(!report.iterations.is_empty());
assert!(report.iterations.len() <= 3);
}
}