use tempfile::TempDir;
#[test]
fn test_function_count_reflects_actual_functions() {
let project = create_rust_file_with_functions(3);
let context = generate_context_markdown(project.path());
assert!(context.is_ok(), "Context generation should succeed");
let output = context.unwrap();
assert!(
output.contains("Functions: 3") || output.contains("function_count: 3"),
"Output should show Functions: 3, got: {}",
extract_function_count_line(&output)
);
}
#[test]
fn test_function_count_zero_when_no_functions() {
let project = create_rust_file_no_functions();
let context = generate_context_markdown(project.path());
assert!(context.is_ok(), "Context generation should succeed");
let output = context.unwrap();
assert!(
output.contains("Functions: 0") || output.contains("function_count: 0"),
"Output should show Functions: 0"
);
}
#[test]
fn test_function_count_per_file() {
let project = create_multi_file_project();
let context = generate_context_markdown(project.path());
assert!(context.is_ok(), "Context generation should succeed");
let output = context.unwrap();
assert!(
output.contains("file1.rs") && count_functions_in_section(&output, "file1.rs") == 2,
"file1.rs should show 2 functions"
);
assert!(
output.contains("file2.rs") && count_functions_in_section(&output, "file2.rs") == 5,
"file2.rs should show 5 functions"
);
}
#[test]
fn test_function_count_includes_all_types() {
let project = create_rust_file_with_various_functions();
let context = generate_context_markdown(project.path());
assert!(context.is_ok(), "Context generation should succeed");
let output = context.unwrap();
assert!(
output.contains("Functions: 2") || output.contains("Functions: 4"),
"Output should show function count from analyzer (2 or 4), got: {}",
extract_function_count_line(&output)
);
assert!(
!output.contains("Functions: 0"),
"Should not show Functions: 0 when functions exist (BUG-007 core issue)"
);
}
#[test]
fn test_function_count_in_summary() {
let project = create_rust_file_with_functions(3);
let context = generate_context_markdown(project.path());
assert!(context.is_ok(), "Context generation should succeed");
let output = context.unwrap();
let has_summary = output.contains("File Complexity:") && output.contains("Functions:");
assert!(
has_summary,
"Output should have file summary with function count"
);
assert!(
!output.contains("Functions: 0"),
"Should not show Functions: 0 when functions exist"
);
}
fn create_rust_file_with_functions(count: usize) -> TempDir {
use std::fs;
let temp_dir = TempDir::new().unwrap();
let mut code = String::from("// Test file\n\n");
for i in 0..count {
code.push_str(&format!(
"pub fn function_{}() {{\n println!(\"Hello {}\");\n}}\n\n",
i, i
));
}
fs::write(temp_dir.path().join("main.rs"), code).unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
temp_dir
}
fn create_rust_file_no_functions() -> TempDir {
use std::fs;
let temp_dir = TempDir::new().unwrap();
let code = r#"
// File with no functions
const VALUE: i32 = 42;
static NAME: &str = "test";
struct MyStruct {
field: i32,
}
"#;
fs::write(temp_dir.path().join("constants.rs"), code).unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
temp_dir
}
fn create_multi_file_project() -> TempDir {
use std::fs;
let temp_dir = TempDir::new().unwrap();
fs::create_dir_all(temp_dir.path().join("src")).unwrap();
let file1 = r#"
pub fn func1() { }
pub fn func2() { }
"#;
fs::write(temp_dir.path().join("src/file1.rs"), file1).unwrap();
let file2 = r#"
pub fn func1() { }
pub fn func2() { }
pub fn func3() { }
pub fn func4() { }
pub fn func5() { }
"#;
fs::write(temp_dir.path().join("src/file2.rs"), file2).unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
temp_dir
}
fn create_rust_file_with_various_functions() -> TempDir {
use std::fs;
let temp_dir = TempDir::new().unwrap();
let code = r#"
// Standalone function
pub fn standalone_fn() { }
struct MyStruct;
// Impl method
impl MyStruct {
pub fn impl_method(&self) { }
}
// Trait implementation
impl std::fmt::Display for MyStruct {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "MyStruct")
}
}
// Async function
pub async fn async_fn() { }
"#;
fs::write(temp_dir.path().join("main.rs"), code).unwrap();
fs::write(
temp_dir.path().join("Cargo.toml"),
"[package]\nname = \"test\"\n",
)
.unwrap();
temp_dir
}
fn generate_context_markdown(path: &std::path::Path) -> Result<String, String> {
use pmat::services::deep_context::{
AnalysisType, CacheStrategy, DagType, DeepContextAnalyzer, DeepContextConfig,
};
let config = DeepContextConfig {
include_analyses: vec![AnalysisType::Ast, AnalysisType::Complexity],
period_days: 7,
dag_type: DagType::CallGraph,
complexity_thresholds: None,
max_depth: Some(3),
include_patterns: vec![],
exclude_patterns: vec!["**/target/**".to_string()],
cache_strategy: CacheStrategy::Normal,
parallel: 1,
file_classifier_config: None,
};
let analyzer = DeepContextAnalyzer::new(config);
let rt = tokio::runtime::Runtime::new().map_err(|e| e.to_string())?;
let path_buf = path.to_path_buf();
let context = rt
.block_on(analyzer.analyze_project(&path_buf))
.map_err(|e| e.to_string())?;
let mut output = String::new();
if let Some(complexity_report) = &context.analyses.complexity_report {
let total_functions: usize = complexity_report
.files
.iter()
.map(|f| f.functions.len())
.sum();
output.push_str(&format!("Functions: {}\n\n", total_functions));
for file in &complexity_report.files {
let function_count = file.functions.len();
let total_complexity = file.total_complexity.cyclomatic;
let filename = std::path::Path::new(&file.path)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&file.path);
output.push_str(&format!("### {}\n\n", filename));
output.push_str(&format!(
"File Complexity: {} | Functions: {}\n\n",
total_complexity, function_count
));
for func in &file.functions {
output.push_str(&format!(
"- **Function**: `{}` [complexity: {}]\n",
func.name, func.metrics.cyclomatic
));
}
output.push('\n');
}
} else {
output.push_str("Functions: 0\n\n");
}
Ok(output)
}
fn extract_function_count_line(output: &str) -> String {
output
.lines()
.find(|line| line.contains("Functions:"))
.unwrap_or("(no function count line found)")
.to_string()
}
fn count_functions_in_section(output: &str, filename: &str) -> usize {
let section = output
.split(&format!("### {}", filename))
.nth(1)
.and_then(|s| s.split("###").next())
.unwrap_or("");
section
.lines()
.filter(|line| line.contains("**Function**"))
.count()
}