diff options
Diffstat (limited to 'src/exercise.rs')
| -rw-r--r-- | src/exercise.rs | 162 |
1 files changed, 133 insertions, 29 deletions
diff --git a/src/exercise.rs b/src/exercise.rs index 45ac208..21ae582 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -2,11 +2,55 @@ use anyhow::{Context, Result}; use crossterm::style::{style, StyledContent, Stylize}; use std::{ fmt::{self, Display, Formatter}, - path::Path, - process::{Command, Output}, + io::{Read, Write}, + process::{Command, Stdio}, }; -use crate::{info_file::Mode, terminal_link::TerminalFileLink, DEBUG_PROFILE}; +use crate::{in_official_repo, terminal_link::TerminalFileLink, DEBUG_PROFILE}; + +pub const OUTPUT_CAPACITY: usize = 1 << 14; + +fn run_command( + mut cmd: Command, + cmd_description: &str, + output: &mut Vec<u8>, + stderr: bool, +) -> Result<bool> { + let (mut reader, writer) = os_pipe::pipe().with_context(|| { + format!("Failed to create a pipe to run the command `{cmd_description}``") + })?; + + let (stdout, stderr) = if stderr { + ( + Stdio::from(writer.try_clone().with_context(|| { + format!("Failed to clone the pipe writer for the command `{cmd_description}`") + })?), + Stdio::from(writer), + ) + } else { + (Stdio::from(writer), Stdio::null()) + }; + + let mut handle = cmd + .stdout(stdout) + .stderr(stderr) + .spawn() + .with_context(|| format!("Failed to run the command `{cmd_description}`"))?; + + // Prevent pipe deadlock. + drop(cmd); + + reader + .read_to_end(output) + .with_context(|| format!("Failed to read the output of the command `{cmd_description}`"))?; + + output.push(b'\n'); + + handle + .wait() + .with_context(|| format!("Failed to wait on the command `{cmd_description}` to exit")) + .map(|status| status.success()) +} pub struct Exercise { pub dir: Option<&'static str>, @@ -14,21 +58,51 @@ pub struct Exercise { pub name: &'static str, // Exercise's path pub path: &'static str, - // The mode of the exercise - pub mode: Mode, + pub test: bool, + pub strict_clippy: bool, // The hint text associated with the exercise pub hint: String, pub done: bool, } impl Exercise { - fn cargo_cmd(&self, command: &str, args: &[&str]) -> Result<Output> { + fn run_bin(&self, output: &mut Vec<u8>) -> Result<bool> { + writeln!(output, "{}", "Output".underlined())?; + + let bin_path = format!("target/debug/{}", self.name); + let success = run_command(Command::new(&bin_path), &bin_path, output, true)?; + + if !success { + writeln!( + output, + "{}", + "The exercise didn't run successfully (nonzero exit code)" + .bold() + .red() + )?; + } + + Ok(success) + } + + fn cargo_cmd( + &self, + command: &str, + args: &[&str], + cmd_description: &str, + output: &mut Vec<u8>, + dev: bool, + stderr: bool, + ) -> Result<bool> { let mut cmd = Command::new("cargo"); cmd.arg(command); // A hack to make `cargo run` work when developing Rustlings. - if DEBUG_PROFILE && Path::new("tests").exists() { - cmd.arg("--manifest-path").arg("dev/Cargo.toml"); + if dev { + cmd.arg("--manifest-path") + .arg("dev/Cargo.toml") + .arg("--target-dir") + .arg("target"); } cmd.arg("--color") @@ -36,30 +110,60 @@ impl Exercise { .arg("-q") .arg("--bin") .arg(self.name) - .args(args) - .output() - .context("Failed to run Cargo") + .args(args); + + run_command(cmd, cmd_description, output, stderr) } - pub fn run(&self) -> Result<Output> { - match self.mode { - Mode::Run => self.cargo_cmd("run", &[]), - Mode::Test => self.cargo_cmd( - "test", - &[ - "--", - "--color", - "always", - "--nocapture", - "--format", - "pretty", - ], - ), - Mode::Clippy => self.cargo_cmd( - "clippy", - &["--", "-D", "warnings", "-D", "clippy::float_cmp"], - ), + pub fn run(&self, output: &mut Vec<u8>) -> Result<bool> { + output.clear(); + + // Developing the official Rustlings. + let dev = DEBUG_PROFILE && in_official_repo(); + + let build_success = self.cargo_cmd("build", &[], "cargo build …", output, dev, true)?; + if !build_success { + return Ok(false); } + + // Discard the output of `cargo build` because it will be shown again by the Cargo command. + output.clear(); + + let clippy_args: &[&str] = if self.strict_clippy { + &["--", "-D", "warnings"] + } else { + &[] + }; + let clippy_success = + self.cargo_cmd("clippy", clippy_args, "cargo clippy …", output, dev, true)?; + if !clippy_success { + return Ok(false); + } + + if !self.test { + return self.run_bin(output); + } + + let test_success = self.cargo_cmd( + "test", + &[ + "--", + "--color", + "always", + "--nocapture", + "--format", + "pretty", + ], + "cargo test …", + output, + dev, + // Hide warnings because they are shown by Clippy. + false, + )?; + + let run_success = self.run_bin(output)?; + + Ok(test_success && run_success) } pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> { |
