From 43dc31193afddc15c78d2ceb57f7c68da90e8a46 Mon Sep 17 00:00:00 2001 From: Roberto Vidal Date: Thu, 20 Feb 2020 20:11:53 +0100 Subject: [PATCH] refactor: exercise evaluation Exercise evaluation (compilation + execution) now uses Results Success/failure messages are standardized --- src/exercise.rs | 76 ++++++++++++++++++++++++++++------ src/main.rs | 3 ++ src/run.rs | 54 ++++++++++++------------ src/ui.rs | 23 +++++++++++ src/verify.rs | 106 ++++++++++++++++++++++++------------------------ 5 files changed, 168 insertions(+), 94 deletions(-) create mode 100644 src/ui.rs diff --git a/src/exercise.rs b/src/exercise.rs index b6c28da..d72eeb5 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter}; use std::fs::{remove_file, File}; use std::io::Read; use std::path::PathBuf; -use std::process::{self, Command, Output}; +use std::process::{self, Command}; const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; @@ -47,9 +47,34 @@ pub struct ContextLine { pub important: bool, } +pub struct CompiledExercise<'a> { + exercise: &'a Exercise, + _handle: FileHandle, +} + +impl<'a> CompiledExercise<'a> { + pub fn run(&self) -> Result { + self.exercise.run() + } +} + +#[derive(Debug)] +pub struct ExerciseOutput { + pub stdout: String, + pub stderr: String, +} + +struct FileHandle; + +impl Drop for FileHandle { + fn drop(&mut self) { + clean(); + } +} + impl Exercise { - pub fn compile(&self) -> Output { - match self.mode { + pub fn compile(&self) -> Result { + let cmd = match self.mode { Mode::Compile => Command::new("rustc") .args(&[self.path.to_str().unwrap(), "-o", &temp_file()]) .args(RUSTC_COLOR_ARGS) @@ -59,17 +84,37 @@ impl Exercise { .args(RUSTC_COLOR_ARGS) .output(), } - .expect("Failed to run 'compile' command.") + .expect("Failed to run 'compile' command."); + + if cmd.status.success() { + Ok(CompiledExercise { + exercise: &self, + _handle: FileHandle, + }) + } else { + clean(); + Err(ExerciseOutput { + stdout: String::from_utf8_lossy(&cmd.stdout).to_string(), + stderr: String::from_utf8_lossy(&cmd.stderr).to_string(), + }) + } } - pub fn run(&self) -> Output { - Command::new(&temp_file()) + fn run(&self) -> Result { + let cmd = Command::new(&temp_file()) .output() - .expect("Failed to run 'run' command") - } + .expect("Failed to run 'run' command"); - pub fn clean(&self) { - let _ignored = remove_file(&temp_file()); + let output = ExerciseOutput { + stdout: String::from_utf8_lossy(&cmd.stdout).to_string(), + stderr: String::from_utf8_lossy(&cmd.stderr).to_string(), + }; + + if cmd.status.success() { + Ok(output) + } else { + Err(output) + } } pub fn state(&self) -> State { @@ -121,6 +166,10 @@ impl Display for Exercise { } } +fn clean() { + let _ignored = remove_file(&temp_file()); +} + #[cfg(test)] mod test { use super::*; @@ -131,11 +180,12 @@ mod test { File::create(&temp_file()).unwrap(); let exercise = Exercise { name: String::from("example"), - path: PathBuf::from("example.rs"), - mode: Mode::Test, + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: Mode::Compile, hint: String::from(""), }; - exercise.clean(); + let compiled = exercise.compile().unwrap(); + drop(compiled); assert!(!Path::new(&temp_file()).exists()); } diff --git a/src/main.rs b/src/main.rs index dd060ac..4fd6083 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,9 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; +#[macro_use] +mod ui; + mod exercise; mod run; mod verify; diff --git a/src/run.rs b/src/run.rs index 1484351..cfde7ab 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,6 +1,5 @@ use crate::exercise::{Exercise, Mode}; use crate::verify::test; -use console::{style, Emoji}; use indicatif::ProgressBar; pub fn run(exercise: &Exercise) -> Result<(), ()> { @@ -11,42 +10,41 @@ pub fn run(exercise: &Exercise) -> Result<(), ()> { Ok(()) } -pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> { +fn compile_and_run(exercise: &Exercise) -> Result<(), ()> { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Compiling {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); - let compilecmd = exercise.compile(); + let compilation_result = exercise.compile(); + let compilation = match compilation_result { + Ok(compilation) => compilation, + Err(output) => { + progress_bar.finish_and_clear(); + warn!( + "Compilation of {} failed!, Compiler error message:\n", + exercise + ); + println!("{}", output.stderr); + return Err(()); + } + }; + progress_bar.set_message(format!("Running {}...", exercise).as_str()); - if compilecmd.status.success() { - let runcmd = exercise.run(); - progress_bar.finish_and_clear(); + let result = compilation.run(); + progress_bar.finish_and_clear(); - if runcmd.status.success() { - println!("{}", String::from_utf8_lossy(&runcmd.stdout)); - let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise); - println!("{}", style(formatstr).green()); - exercise.clean(); + match result { + Ok(output) => { + println!("{}", output.stdout); + success!("Successfully ran {}", exercise); Ok(()) - } else { - println!("{}", String::from_utf8_lossy(&runcmd.stdout)); - println!("{}", String::from_utf8_lossy(&runcmd.stderr)); + } + Err(output) => { + println!("{}", output.stdout); + println!("{}", output.stderr); - let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise); - println!("{}", style(formatstr).red()); - exercise.clean(); + warn!("Ran {} with errors", exercise); Err(()) } - } else { - progress_bar.finish_and_clear(); - let formatstr = format!( - "{} Compilation of {} failed! Compiler error message:\n", - Emoji("⚠️ ", "!"), - exercise - ); - println!("{}", style(formatstr).red()); - println!("{}", String::from_utf8_lossy(&compilecmd.stderr)); - exercise.clean(); - Err(()) } } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..38cbaa4 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,23 @@ +macro_rules! warn { + ($fmt:literal, $ex:expr) => {{ + use console::{style, Emoji}; + let formatstr = format!($fmt, $ex); + println!( + "{} {}", + style(Emoji("⚠️ ", "!")).red(), + style(formatstr).red() + ); + }}; +} + +macro_rules! success { + ($fmt:literal, $ex:expr) => {{ + use console::{style, Emoji}; + let formatstr = format!($fmt, $ex); + println!( + "{} {}", + style(Emoji("✅", "✓")).green(), + style(formatstr).green() + ); + }}; +} diff --git a/src/verify.rs b/src/verify.rs index 3796bbd..3d14896 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,11 +1,11 @@ use crate::exercise::{Exercise, Mode, State}; -use console::{style, Emoji}; +use console::style; use indicatif::ProgressBar; pub fn verify<'a>(start_at: impl IntoIterator) -> Result<(), &'a Exercise> { for exercise in start_at { let compile_result = match exercise.mode { - Mode::Test => compile_and_test_interactively(&exercise), + Mode::Test => compile_and_test(&exercise, RunMode::Interactive), Mode::Compile => compile_only(&exercise), }; if !compile_result.unwrap_or(false) { @@ -15,8 +15,13 @@ pub fn verify<'a>(start_at: impl IntoIterator) -> Result<() Ok(()) } +enum RunMode { + Interactive, + NonInteractive, +} + pub fn test(exercise: &Exercise) -> Result<(), ()> { - compile_and_test(exercise, true)?; + compile_and_test(exercise, RunMode::NonInteractive)?; Ok(()) } @@ -24,69 +29,64 @@ fn compile_only(exercise: &Exercise) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Compiling {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); - let compile_output = exercise.compile(); + let compilation_result = exercise.compile(); progress_bar.finish_and_clear(); - if compile_output.status.success() { - let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise); - println!("{}", style(formatstr).green()); - exercise.clean(); - Ok(prompt_for_completion(&exercise)) - } else { - let formatstr = format!( - "{} Compilation of {} failed! Compiler error message:\n", - Emoji("⚠️ ", "!"), - exercise - ); - println!("{}", style(formatstr).red()); - println!("{}", String::from_utf8_lossy(&compile_output.stderr)); - exercise.clean(); - Err(()) + + match compilation_result { + Ok(_) => { + success!("Successfully compiled {}!", exercise); + Ok(prompt_for_completion(&exercise)) + } + Err(output) => { + warn!( + "Compilation of {} failed! Compiler error message:\n", + exercise + ); + println!("{}", output.stderr); + Err(()) + } } } -fn compile_and_test_interactively(exercise: &Exercise) -> Result { - compile_and_test(exercise, false) -} - -fn compile_and_test(exercise: &Exercise, skip_prompt: bool) -> Result { +fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Testing {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); - let compile_output = exercise.compile(); - if compile_output.status.success() { - progress_bar.set_message(format!("Running {}...", exercise).as_str()); + let compilation_result = exercise.compile(); - let runcmd = exercise.run(); - progress_bar.finish_and_clear(); - - if runcmd.status.success() { - let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise); - println!("{}", style(formatstr).green()); - exercise.clean(); - Ok(skip_prompt || prompt_for_completion(exercise)) - } else { - let formatstr = format!( - "{} Testing of {} failed! Please try again. Here's the output:", - Emoji("⚠️ ", "!"), + let compilation = match compilation_result { + Ok(compilation) => compilation, + Err(output) => { + progress_bar.finish_and_clear(); + warn!( + "Compiling of {} failed! Please try again. Here's the output:", exercise ); - println!("{}", style(formatstr).red()); - println!("{}", String::from_utf8_lossy(&runcmd.stdout)); - exercise.clean(); + println!("{}", output.stderr); + return Err(()); + } + }; + + let result = compilation.run(); + progress_bar.finish_and_clear(); + + match result { + Ok(_) => { + if let RunMode::Interactive = run_mode { + Ok(prompt_for_completion(&exercise)) + } else { + Ok(true) + } + } + Err(output) => { + warn!( + "Testing of {} failed! Please try again. Here's the output:", + exercise + ); + println!("{}", output.stdout); Err(()) } - } else { - progress_bar.finish_and_clear(); - let formatstr = format!( - "{} Compiling of {} failed! Please try again. Here's the output:", - Emoji("⚠️ ", "!"), - exercise - ); - println!("{}", style(formatstr).red()); - println!("{}", String::from_utf8_lossy(&compile_output.stderr)); - exercise.clean(); - Err(()) } }