summaryrefslogtreecommitdiff
path: root/src/cmd.rs
blob: 6092f531ca0b0b35f814352bc38d0da322cd6777 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use anyhow::{Context, Result};
use std::{io::Read, path::Path, process::Command};

/// Run a command with a description for a possible error and append the merged stdout and stderr.
/// The boolean in the returned `Result` is true if the command's exit status is success.
pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec<u8>) -> Result<bool> {
    let (mut reader, writer) = os_pipe::pipe()
        .with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?;

    let writer_clone = writer.try_clone().with_context(|| {
        format!("Failed to clone the pipe writer for the command `{description}`")
    })?;

    let mut handle = cmd
        .stdout(writer_clone)
        .stderr(writer)
        .spawn()
        .with_context(|| format!("Failed to run the command `{description}`"))?;

    // Prevent pipe deadlock.
    drop(cmd);

    reader
        .read_to_end(output)
        .with_context(|| format!("Failed to read the output of the command `{description}`"))?;

    output.push(b'\n');

    handle
        .wait()
        .with_context(|| format!("Failed to wait on the command `{description}` to exit"))
        .map(|status| status.success())
}

pub struct CargoCmd<'a> {
    pub subcommand: &'a str,
    pub args: &'a [&'a str],
    pub bin_name: &'a str,
    pub description: &'a str,
    /// RUSTFLAGS="-A warnings"
    pub hide_warnings: bool,
    /// Added as `--target-dir` if `Self::dev` is true.
    pub target_dir: &'a Path,
    /// The output buffer to append the merged stdout and stderr.
    pub output: &'a mut Vec<u8>,
    /// true while developing Rustlings.
    pub dev: bool,
}

impl<'a> CargoCmd<'a> {
    /// Run `cargo SUBCOMMAND --bin EXERCISE_NAME … ARGS`.
    pub fn run(&mut self) -> Result<bool> {
        let mut cmd = Command::new("cargo");
        cmd.arg(self.subcommand);

        // A hack to make `cargo run` work when developing Rustlings.
        if self.dev {
            cmd.arg("--manifest-path")
                .arg("dev/Cargo.toml")
                .arg("--target-dir")
                .arg(self.target_dir);
        }

        cmd.arg("--color")
            .arg("always")
            .arg("-q")
            .arg("--bin")
            .arg(self.bin_name)
            .args(self.args);

        if self.hide_warnings {
            cmd.env("RUSTFLAGS", "-A warnings");
        }

        run_cmd(cmd, self.description, self.output)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_run_cmd() {
        let mut cmd = Command::new("echo");
        cmd.arg("Hello");

        let mut output = Vec::with_capacity(8);
        run_cmd(cmd, "echo …", &mut output).unwrap();

        assert_eq!(output, b"Hello\n\n");
    }
}