use crate::mcp_server::tools::analyze_symbols::{AnalyzeSymbolContextTool, AnalyzerResult};
use crate::project::{ProjectScanner, WorkspaceSession};
use crate::test_utils::{DEFAULT_INDEXING_TIMEOUT, integration::TestProject};
use tracing::info;
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_call_hierarchy_function() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "factorial".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "factorial");
assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(analyzer_result.call_hierarchy.is_some());
let hierarchy = analyzer_result.call_hierarchy.unwrap();
info!("factorial callers: {:?}", hierarchy.callers);
info!("factorial callees: {:?}", hierarchy.callees);
assert!(!hierarchy.callers.is_empty());
info!(
"factorial call hierarchy - callers: {} callees: {}",
hierarchy.callers.len(),
hierarchy.callees.len()
);
}
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_call_hierarchy_method() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "Math::Complex::add".to_string(), build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "add"); assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(analyzer_result.call_hierarchy.is_some());
let hierarchy = analyzer_result.call_hierarchy.unwrap();
info!("add method callers: {:?}", hierarchy.callers);
info!("add method callees: {:?}", hierarchy.callees);
assert!(!hierarchy.callers.is_empty());
info!(
"add method call hierarchy - callers: {} callees: {}",
hierarchy.callers.len(),
hierarchy.callees.len()
);
}
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_call_hierarchy_non_function() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let tool = AnalyzeSymbolContextTool {
symbol: "Math".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let result = tool.call_tool(component_session, &workspace).await;
assert!(result.is_ok());
let call_result = result.unwrap();
let text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = call_result.content.first()
{
text
} else {
panic!("Expected TextContent in call_result");
};
let analyzer_result: AnalyzerResult = serde_json::from_str(text).unwrap();
assert_eq!(analyzer_result.symbol.name, "Math");
assert_eq!(analyzer_result.symbol.kind, lsp_types::SymbolKind::CLASS);
assert!(analyzer_result.call_hierarchy.is_none());
assert!(analyzer_result.type_hierarchy.is_some());
info!(
"Math class - has type hierarchy: {}, has call hierarchy: {}",
analyzer_result.type_hierarchy.is_some(),
analyzer_result.call_hierarchy.is_some()
);
}
#[cfg(feature = "clangd-integration-tests")]
#[tokio::test]
async fn test_analyzer_call_hierarchy_coherence() {
let test_project = TestProject::new().await.unwrap();
test_project.cmake_configure().await.unwrap();
let scanner = ProjectScanner::with_default_providers();
let workspace = scanner
.scan_project(&test_project.project_root, 3, None)
.expect("Failed to scan test project");
let clangd_path = crate::test_utils::get_test_clangd_path();
let workspace_session = WorkspaceSession::new(workspace.clone(), clangd_path)
.expect("Failed to create workspace session");
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
component_session
.ensure_indexed(DEFAULT_INDEXING_TIMEOUT)
.await
.expect("Indexing should complete successfully for call hierarchy test");
info!("Indexing completed, proceeding with call hierarchy coherence test");
let test_project_include = workspace.project_root_path.join("include/Math.hpp");
let canonical_path = test_project_include
.canonicalize()
.expect("Failed to canonicalize Math.hpp path");
let variance_location = format!("{}:431:19", canonical_path.display());
let variance_tool = AnalyzeSymbolContextTool {
symbol: "variance".to_string(),
build_directory: None,
max_examples: Some(2),
location_hint: Some(variance_location),
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let variance_result = variance_tool
.call_tool(component_session, &workspace)
.await
.expect("Failed to analyze variance");
let variance_text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = variance_result.content.first()
{
text
} else {
panic!("Expected TextContent in variance_result");
};
let variance_analysis: AnalyzerResult = serde_json::from_str(variance_text).unwrap();
assert_eq!(variance_analysis.symbol.name, "variance");
assert_eq!(variance_analysis.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(variance_analysis.call_hierarchy.is_some());
if let Some(ref status) = variance_analysis.index_status {
assert!(
status.state.contains("Completed") && status.indexed_files == status.total_files,
"Indexing should have completed successfully for variance analysis. Index status: {:?}",
variance_analysis.index_status
);
}
let variance_hierarchy = variance_analysis.call_hierarchy.unwrap();
info!("variance callers: {:?}", variance_hierarchy.callers);
info!("variance callees: {:?}", variance_hierarchy.callees);
let mean_tool = AnalyzeSymbolContextTool {
symbol: "Math::mean".to_string(), build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let mean_result = mean_tool
.call_tool(component_session, &workspace)
.await
.expect("Failed to analyze mean");
let mean_text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = mean_result.content.first()
{
text
} else {
panic!("Expected TextContent in mean_result");
};
let mean_analysis: AnalyzerResult = serde_json::from_str(mean_text).unwrap();
assert_eq!(mean_analysis.symbol.name, "mean");
assert_eq!(mean_analysis.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(mean_analysis.call_hierarchy.is_some());
if let Some(ref status) = mean_analysis.index_status {
assert!(
status.state.contains("Completed") && status.indexed_files == status.total_files,
"Indexing should have completed successfully for mean analysis. Index status: {:?}",
mean_analysis.index_status
);
}
let mean_hierarchy = mean_analysis.call_hierarchy.unwrap();
info!("mean callers: {:?}", mean_hierarchy.callers);
info!("mean callees: {:?}", mean_hierarchy.callees);
let std_dev_tool = AnalyzeSymbolContextTool {
symbol: "Math::standardDeviation".to_string(), build_directory: None,
max_examples: Some(2),
location_hint: None,
wait_timeout: None,
};
let component_session = workspace_session
.get_component_session(test_project.build_dir.clone())
.await
.unwrap();
let std_dev_result = std_dev_tool
.call_tool(component_session, &workspace)
.await
.expect("Failed to analyze standardDeviation");
let std_dev_text = if let Some(rust_mcp_sdk::schema::ContentBlock::TextContent(
rust_mcp_sdk::schema::TextContent { text, .. },
)) = std_dev_result.content.first()
{
text
} else {
panic!("Expected TextContent in std_dev_result");
};
let std_dev_analysis: AnalyzerResult = serde_json::from_str(std_dev_text).unwrap();
assert_eq!(std_dev_analysis.symbol.name, "standardDeviation");
assert_eq!(std_dev_analysis.symbol.kind, lsp_types::SymbolKind::METHOD);
assert!(std_dev_analysis.call_hierarchy.is_some());
if let Some(ref status) = std_dev_analysis.index_status {
assert!(
status.state.contains("Completed") && status.indexed_files == status.total_files,
"Indexing should have completed successfully for standardDeviation analysis. Index status: {:?}",
std_dev_analysis.index_status
);
}
let std_dev_hierarchy = std_dev_analysis.call_hierarchy.unwrap();
info!("standardDeviation callers: {:?}", std_dev_hierarchy.callers);
info!("standardDeviation callees: {:?}", std_dev_hierarchy.callees);
assert!(
std_dev_hierarchy
.callees
.iter()
.any(|c| c.contains("variance")),
"standardDeviation should call variance"
);
assert!(
variance_hierarchy
.callers
.iter()
.any(|c| c.contains("standardDeviation")),
"variance should be called by standardDeviation"
);
assert!(
variance_hierarchy
.callees
.iter()
.any(|c| c.contains("mean")),
"variance should call mean"
);
assert!(
mean_hierarchy
.callers
.iter()
.any(|c| c.contains("variance")),
"mean should be called by variance"
);
assert!(
mean_hierarchy
.callers
.iter()
.any(|c| c.contains("variance")),
"mean should be called by variance (completing the call chain)"
);
info!(
"Call hierarchy coherence validated successfully:\n\
- standardDeviation -> variance: bidirectional relationship confirmed\n\
- variance -> mean: bidirectional relationship confirmed\n\
- standardDeviation -> mean (direct): relationship confirmed"
);
}