use anyhow::Result;
use serde_json::json;
use test_support::IpcClient;
fn assert_tool_response(response: &serde_json::Value) {
assert!(
response["error"].is_null(),
"Tool call returned error: {:?}",
response["error"]
);
assert!(
response["content"].is_array(),
"Response should have content array"
);
assert!(
!response["content"].as_array().unwrap().is_empty(),
"Content array should not be empty"
);
}
#[tokio::test]
async fn test_file_diagnostics() -> Result<()> {
let mut client = IpcClient::get_or_create("test-project-diagnostics").await?;
let workspace_path = client.workspace_path();
let errors_path = workspace_path.join("src/errors.rs");
let timeout_ms = if std::env::var("CI").is_ok() {
1000
} else {
500
};
let max_attempts = if std::env::var("CI").is_ok() { 30 } else { 20 };
let mut parsed = serde_json::Value::Null;
for attempt in 0..max_attempts {
let response = client
.call_tool(
"rust_analyzer_diagnostics",
json!({
"file_path": errors_path.to_str().unwrap()
}),
)
.await?;
assert_tool_response(&response);
let content = response["content"][0]["text"].as_str().unwrap();
parsed = serde_json::from_str(content).unwrap();
let diagnostics = parsed["diagnostics"].as_array().unwrap();
if !diagnostics.is_empty() {
break;
}
if attempt < max_attempts - 1 {
eprintln!(
"Attempt {}: No diagnostics yet, waiting for rust-analyzer...",
attempt + 1
);
tokio::time::sleep(tokio::time::Duration::from_millis(timeout_ms)).await;
}
}
assert!(parsed["diagnostics"].is_array());
let diagnostics = parsed["diagnostics"].as_array().unwrap();
assert!(
!diagnostics.is_empty(),
"Should have diagnostics for file with errors. Got: {}",
serde_json::to_string_pretty(&parsed).unwrap()
);
let summary = &parsed["summary"];
let error_count = summary["errors"].as_u64().unwrap_or(0);
let warning_count = summary["warnings"].as_u64().unwrap_or(0);
let hint_count = summary["hints"].as_u64().unwrap_or(0);
assert!(
error_count > 0 || warning_count > 0 || hint_count > 0,
"Should have at least some diagnostics (errors, warnings, or hints). Summary: {:?}",
summary
);
if !diagnostics.is_empty() {
let first_diag = &diagnostics[0];
assert!(first_diag["severity"].is_string());
assert!(first_diag["message"].is_string());
assert!(first_diag["range"].is_object());
}
Ok(())
}
#[tokio::test]
async fn test_file_diagnostics_clean_file() -> Result<()> {
let mut client = IpcClient::get_or_create("test-project-diagnostics").await?;
let workspace_path = client.workspace_path();
let clean_path = workspace_path.join("src/clean.rs");
eprintln!("Using clean.rs file for clean file test");
eprintln!("Workspace fully initialized with all modules resolved");
let mut last_error = None;
for attempt in 1..=3 {
let response = client
.call_tool(
"rust_analyzer_diagnostics",
json!({
"file_path": clean_path.to_str().unwrap()
}),
)
.await?;
assert_tool_response(&response);
let content = response["content"][0]["text"].as_str().unwrap();
let parsed: serde_json::Value = serde_json::from_str(content).unwrap();
let summary = &parsed["summary"];
let error_count = summary["errors"].as_u64().unwrap_or(0);
if error_count == 0 {
if let Some(diagnostics) = parsed["diagnostics"].as_array() {
let has_errors = diagnostics
.iter()
.any(|d| d["severity"].as_str() == Some("error"));
if !has_errors {
return Ok(());
}
} else {
return Ok(());
}
}
eprintln!("Attempt {}: Found {} errors", attempt, error_count);
if attempt == 1 {
eprintln!("Full diagnostic response for src/clean.rs:");
eprintln!("{}", serde_json::to_string_pretty(&parsed).unwrap());
}
last_error = Some(format!(
"Clean file (src/clean.rs) should have no errors. Summary: {:?}",
summary
));
if attempt < 3 {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
}
}
Err(anyhow::anyhow!(last_error.unwrap()))
}
#[tokio::test]
async fn test_workspace_diagnostics() -> Result<()> {
let mut client = IpcClient::get_or_create("test-project-diagnostics").await?;
let workspace_path = client.workspace_path();
let errors_path = workspace_path.join("src/errors.rs");
let _warnings_path = workspace_path.join("src/warnings.rs");
let timeout_ms = if std::env::var("CI").is_ok() {
1000
} else {
500
};
let max_attempts = if std::env::var("CI").is_ok() { 20 } else { 10 };
for attempt in 0..max_attempts {
let response = client
.call_tool(
"rust_analyzer_diagnostics",
json!({
"file_path": errors_path.to_str().unwrap()
}),
)
.await?;
let content = response["content"][0]["text"].as_str().unwrap();
let parsed: serde_json::Value = serde_json::from_str(content).unwrap();
let diagnostics = parsed["diagnostics"].as_array().unwrap();
if !diagnostics.is_empty() {
break;
}
if attempt < max_attempts - 1 {
eprintln!(
"Attempt {}: Waiting for initial diagnostics...",
attempt + 1
);
tokio::time::sleep(tokio::time::Duration::from_millis(timeout_ms)).await;
}
}
let response = client
.call_tool("rust_analyzer_workspace_diagnostics", json!({}))
.await?;
assert_tool_response(&response);
let content = response["content"][0]["text"].as_str().unwrap();
let parsed: serde_json::Value = serde_json::from_str(content).unwrap();
assert!(parsed["workspace"].is_string());
if parsed["files"].is_object() {
assert!(parsed["summary"]["total_files"].is_number());
} else if parsed["diagnostics"].is_array() {
assert!(parsed["summary"]["total_diagnostics"].is_number());
}
Ok(())
}
#[tokio::test]
async fn test_diagnostics_invalid_file() -> Result<()> {
let mut client = IpcClient::get_or_create("test-project").await?;
let response = client
.call_tool(
"rust_analyzer_diagnostics",
json!({
"file_path": "src/nonexistent.rs"
}),
)
.await;
match response {
Ok(response) => {
assert_tool_response(&response);
let content = response["content"][0]["text"].as_str().unwrap();
let parsed: serde_json::Value = serde_json::from_str(content).unwrap();
let summary = &parsed["summary"];
assert_eq!(summary["errors"].as_u64().unwrap_or(0), 0);
}
Err(e) => {
assert!(
e.to_string().contains("No such file") || e.to_string().contains("not found"),
"Expected file not found error, got: {}",
e
);
}
}
Ok(())
}
#[tokio::test]
async fn test_diagnostics_severity_levels() -> Result<()> {
let mut client = IpcClient::get_or_create("test-project-diagnostics").await?;
let workspace_path = client.workspace_path();
let errors_path = workspace_path.join("src/errors.rs");
let _warnings_path = workspace_path.join("src/warnings.rs");
let timeout_ms = if std::env::var("CI").is_ok() {
1000
} else {
500
};
let max_attempts = if std::env::var("CI").is_ok() { 20 } else { 10 };
let mut diagnostics = vec![];
for attempt in 0..max_attempts {
let response = client
.call_tool(
"rust_analyzer_diagnostics",
json!({
"file_path": errors_path.to_str().unwrap()
}),
)
.await?;
assert_tool_response(&response);
let content = response["content"][0]["text"].as_str().unwrap();
let parsed: serde_json::Value = serde_json::from_str(content).unwrap();
diagnostics = parsed["diagnostics"].as_array().unwrap().clone();
if !diagnostics.is_empty() {
break;
}
if attempt < max_attempts - 1 {
eprintln!(
"Attempt {}: No diagnostics yet, waiting for rust-analyzer...",
attempt + 1
);
tokio::time::sleep(tokio::time::Duration::from_millis(timeout_ms)).await;
}
}
eprintln!("Diagnostics count: {}", diagnostics.len());
for (i, diag) in diagnostics.iter().enumerate() {
eprintln!(
"Diagnostic {}: severity={:?}, message={:?}",
i, diag["severity"], diag["message"]
);
}
assert!(
!diagnostics.is_empty(),
"Should have diagnostics for file with errors"
);
let mut has_error = false;
let mut has_warning = false;
for diag in &diagnostics {
match diag["severity"].as_str() {
Some("error") => has_error = true,
Some("warning") => has_warning = true,
_ => {}
}
}
assert!(
has_error || has_warning || !diagnostics.is_empty(),
"Should have at least errors or warnings, found {} diagnostics",
diagnostics.len()
);
Ok(())
}