Summarize Cargo failures in tool metadata#1636
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a cargo_failure_summary module to extract actionable failure signals from Cargo output, categorizing them into test failures, compilation errors, or general cargo failures. This functionality is integrated into the shell and test runner tools to enrich command metadata. Reviewers suggested several improvements: using an enum for failure types to enhance type safety, expanding the recognized Cargo command aliases (e.g., cargo t, cargo b), and broadening the detection of primary error lines. Additionally, feedback pointed out a redundant empty-string check and a potential bug in the character truncation logic when handling very small limits.
| pub(crate) struct CargoFailureSummary { | ||
| pub(crate) kind: String, | ||
| pub(crate) summary: String, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub(crate) failing_tests: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub(crate) error_codes: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub(crate) primary_errors: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Vec::is_empty")] | ||
| pub(crate) panic_locations: Vec<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| pub(crate) test_result: Option<String>, | ||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||
| pub(crate) final_error: Option<String>, | ||
| } |
There was a problem hiding this comment.
Using a String for the kind field is less idiomatic in Rust than using an enum. An enum with #[serde(rename_all = "snake_case")] would provide better type safety and clarity for the possible failure categories.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum CargoFailureKind {
TestFailure,
CompileError,
CargoFailure,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct CargoFailureSummary {
pub(crate) kind: CargoFailureKind,
pub(crate) summary: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) failing_tests: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) error_codes: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) primary_errors: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub(crate) panic_locations: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) test_result: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) final_error: Option<String>,
}| if summary.trim().is_empty() { | ||
| return None; | ||
| } |
| fn looks_like_cargo_command(command: &str) -> bool { | ||
| let lower = command.to_ascii_lowercase(); | ||
| lower.contains("cargo test") | ||
| || lower.contains("cargo check") | ||
| || lower.contains("cargo build") | ||
| || lower.contains("cargo clippy") | ||
| } |
There was a problem hiding this comment.
The current check misses common Cargo aliases like cargo t, cargo c, cargo b, and cargo r. It also excludes cargo run, which can fail during compilation. Consider expanding the list of recognized commands.
fn looks_like_cargo_command(command: &str) -> bool {
let lower = command.to_ascii_lowercase();
[
"cargo test",
"cargo check",
"cargo build",
"cargo clippy",
"cargo run",
"cargo t",
"cargo c",
"cargo b",
"cargo r",
]
.iter()
.any(|&cmd| lower.contains(cmd))
}| fn is_primary_error_line(line: &str) -> bool { | ||
| line.starts_with("error[") | ||
| || line.starts_with("error: could not compile") | ||
| || line.starts_with("error: aborting due to") | ||
| } |
There was a problem hiding this comment.
This function misses many rustc error messages that don't have an error code (e.g., error: cannot find value...). Including all lines starting with error: (while excluding known test failure summaries) would capture more actionable signals for the primary_errors list.
fn is_primary_error_line(line: &str) -> bool {
line.starts_with("error[")
|| (line.starts_with("error:") && !line.starts_with("error: test failed"))
}| fn truncate_chars(text: &str, max_chars: usize) -> String { | ||
| if text.chars().count() <= max_chars { | ||
| return text.to_string(); | ||
| } | ||
| let mut end = text.len(); | ||
| for (count, (idx, _)) in text.char_indices().enumerate() { | ||
| if count == max_chars.saturating_sub(3) { | ||
| end = idx; | ||
| break; | ||
| } | ||
| } | ||
| format!("{}...", &text[..end]) | ||
| } |
There was a problem hiding this comment.
There is a minor bug where if max_chars is less than 3, the function returns "...", which is longer than the requested limit. Additionally, text.chars().count() is an O(N) operation that can be avoided by checking the length during iteration.
| fn truncate_chars(text: &str, max_chars: usize) -> String { | |
| if text.chars().count() <= max_chars { | |
| return text.to_string(); | |
| } | |
| let mut end = text.len(); | |
| for (count, (idx, _)) in text.char_indices().enumerate() { | |
| if count == max_chars.saturating_sub(3) { | |
| end = idx; | |
| break; | |
| } | |
| } | |
| format!("{}...", &text[..end]) | |
| } | |
| fn truncate_chars(text: &str, max_chars: usize) -> String { | |
| if let Some((idx, _)) = text.char_indices().nth(max_chars) { | |
| if max_chars < 3 { | |
| return text[..idx].to_string(); | |
| } | |
| let truncate_at = text | |
| .char_indices() | |
| .nth(max_chars - 3) | |
| .map(|(i, _)| i) | |
| .unwrap_or(0); | |
| format!("{}...", &text[..truncate_at]) | |
| } else { | |
| text.to_string() | |
| } | |
| } |
91f2eed to
f1a7c34
Compare
|
Updated #1636 to address the review comments:
Validation:
|
|
Follow-up after an independent code quality review:
Validation rerun:
|
|
This PR was opened before the v0.8.41 rebrand and is now stale. Feel free to rebase onto current |
Summary
Adds compact Cargo failure summaries to tool metadata so coding turns preserve the actionable test/compiler failure signal without carrying all raw output.
This is separate from #1585 and #1634. It does not change execution policy or patch behavior.
Changes
cargo_failure_summaryhelper for Cargo output classification.test_failure,compile_error, orcargo_failure.test result:lines, and final Cargo errors.summaryandcargo_failure_summarymetadata to failingrun_testsresults.exec_shell/exec_shell_waitCargo command results, including background wait deltas.Validation
cargo fmt --checkcargo test -p deepseek-tui --bin deepseek-tui cargo_failure_summarycargo test -p deepseek-tui --bin deepseek-tui run_testscargo test -p deepseek-tui --bin deepseek-tui shell_delta_result_includes_cargo_failure_summarycargo check --workspace