Skip to content

Commit

Permalink
Auto merge of #271 - jrvidal:refactor, r=fmoko
Browse files Browse the repository at this point in the history
refactor: exercise evaluation

After working a bit on #270, I realized that it'd be useful to first perform a minor refactor of exercise evaluation.

* Now we have standard methods to compile + execute that return `Result`s.
* Success/failure messages are standardized.
  • Loading branch information
bors committed Feb 26, 2020
2 parents 9835859 + 43dc311 commit 7e8530b
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 93 deletions.
76 changes: 63 additions & 13 deletions src/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<ExerciseOutput, ExerciseOutput> {
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<CompiledExercise, ExerciseOutput> {
let cmd = match self.mode {
Mode::Compile => Command::new("rustc")
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
.args(RUSTC_COLOR_ARGS)
Expand All @@ -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<ExerciseOutput, ExerciseOutput> {
let cmd = Command::new(&temp_file())
.output()
.expect("Failed to run 'run' command")
}
.expect("Failed to run 'run' command");

let output = ExerciseOutput {
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
};

pub fn clean(&self) {
let _ignored = remove_file(&temp_file());
if cmd.status.success() {
Ok(output)
} else {
Err(output)
}
}

pub fn state(&self) -> State {
Expand Down Expand Up @@ -121,6 +166,10 @@ impl Display for Exercise {
}
}

fn clean() {
let _ignored = remove_file(&temp_file());
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -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());
}

Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
54 changes: 26 additions & 28 deletions src/run.rs
Original file line number Diff line number Diff line change
@@ -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<(), ()> {
Expand All @@ -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(())
}
}
23 changes: 23 additions & 0 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -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()
);
}};
}
104 changes: 52 additions & 52 deletions src/verify.rs
Original file line number Diff line number Diff line change
@@ -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<Item = &'a Exercise>) -> 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) {
Expand All @@ -15,78 +15,78 @@ pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<()
Ok(())
}

enum RunMode {
Interactive,
NonInteractive,
}

pub fn test(exercise: &Exercise) -> Result<(), ()> {
compile_and_test(exercise, true)?;
compile_and_test(exercise, RunMode::NonInteractive)?;
Ok(())
}

fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
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(())
}
}

fn compile_and_test_interactively(exercise: &Exercise) -> Result<bool, ()> {
compile_and_test(exercise, false)
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(exercise: &Exercise, skip_prompt: bool) -> Result<bool, ()> {
fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
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 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!("{}", output.stderr);
return Err(());
}
};

let runcmd = exercise.run();
progress_bar.finish_and_clear();
let result = compilation.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("⚠️ ", "!"),
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!("{}", style(formatstr).red());
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
exercise.clean();
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(())
}
}

Expand Down

0 comments on commit 7e8530b

Please sign in to comment.