summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Vidal <vidal.roberto.j@gmail.com>2020-02-20 20:11:53 +0100
committerRoberto Vidal <vidal.roberto.j@gmail.com>2020-02-20 20:27:05 +0100
commit43dc31193afddc15c78d2ceb57f7c68da90e8a46 (patch)
tree83e1bc003d79916aa934d7b42f05e94ce073f2ed
parent83bbd9e82e98b4ec1ab81a1d55bb8688e920a905 (diff)
refactor: exercise evaluation
Exercise evaluation (compilation + execution) now uses Results Success/failure messages are standardized
-rw-r--r--src/exercise.rs76
-rw-r--r--src/main.rs3
-rw-r--r--src/run.rs54
-rw-r--r--src/ui.rs23
-rw-r--r--src/verify.rs104
5 files changed, 167 insertions, 93 deletions
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<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)
@@ -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 {
@@ -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<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) {
@@ -15,8 +15,13 @@ 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(())
}
@@ -24,69 +29,64 @@ 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(())
}
}