Skip to main content

volition_core/tools/
cargo.rs

1// volition-agent-core/src/tools/cargo.rs
2
3use super::CommandOutput;
4use crate::utils::truncate_string; // <-- Import the helper
5use anyhow::{Context, Result};
6use std::path::Path;
7use std::process::{Command, Stdio};
8use tracing::{debug, info};
9
10pub async fn execute_cargo_command(
11    command_name: &str,
12    command_args: &[String],
13    working_dir: &Path,
14) -> Result<CommandOutput> {
15    let full_command_log = format!("cargo {} {}", command_name, command_args.join(" "));
16    // Truncate command for logging
17    let command_display = truncate_string(&full_command_log, 60);
18    info!(
19        "Executing cargo command: {} in {:?}",
20        command_display, // <-- Use truncated version
21        working_dir
22    );
23
24    let output = Command::new("cargo")
25        .current_dir(working_dir)
26        .arg(command_name)
27        .args(command_args)
28        .stdout(Stdio::piped())
29        .stderr(Stdio::piped())
30        .output()
31        .with_context(|| format!("Failed to execute cargo command: {}", full_command_log))?;
32
33    let stdout = String::from_utf8_lossy(&output.stdout).to_string();
34    let stderr = String::from_utf8_lossy(&output.stderr).to_string();
35    let status = output.status.code().unwrap_or(-1);
36
37    debug!("cargo {} exit status: {}", full_command_log, status);
38
39    Ok(CommandOutput {
40        status,
41        stdout,
42        stderr,
43    })
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use std::path::{Path, PathBuf};
50    use tempfile::tempdir;
51    use tokio;
52
53    fn test_working_dir() -> PathBuf {
54        tempdir()
55            .map(|d| {
56                let path = d.into_path();
57                path
58            })
59            .unwrap_or_else(|_| PathBuf::from("."))
60    }
61
62    #[tokio::test]
63    async fn test_execute_cargo_check() {
64        let working_dir = test_working_dir();
65        if working_dir != Path::new(".") {
66            std::fs::write(
67                working_dir.join("Cargo.toml"),
68                "[package]\nname = \"test_crate\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
69            )
70            .unwrap();
71            std::fs::create_dir(working_dir.join("src")).unwrap();
72            std::fs::write(working_dir.join("src/lib.rs"), "pub fn hello() {}").unwrap();
73        }
74
75        let result = execute_cargo_command("check", &[], &working_dir).await;
76        assert!(result.is_ok(), "cargo check failed: {:?}", result.err());
77        let output = result.unwrap();
78        println!("Output: {:?}", output);
79        assert_eq!(output.status, 0);
80    }
81
82    #[tokio::test]
83    async fn test_execute_cargo_build_fail_no_src() {
84        let working_dir = test_working_dir();
85        if working_dir != Path::new(".") {
86            std::fs::write(
87                working_dir.join("Cargo.toml"),
88                "[package]\nname = \"test_crate_fail\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
89            )
90            .unwrap();
91        }
92
93        if working_dir != Path::new(".") {
94            let result = execute_cargo_command("build", &[], &working_dir).await;
95            assert!(result.is_ok());
96            let output = result.unwrap();
97            println!("Output: {:?}", output);
98            assert_ne!(output.status, 0);
99            assert!(
100                output
101                    .stderr
102                    .contains("no targets specified in the manifest")
103            );
104        }
105    }
106}