Skip to content

Commit 43dc311

Browse files
committed
refactor: exercise evaluation
Exercise evaluation (compilation + execution) now uses Results Success/failure messages are standardized
1 parent 83bbd9e commit 43dc311

File tree

5 files changed

+167
-93
lines changed

5 files changed

+167
-93
lines changed

src/exercise.rs

+63-13
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fmt::{self, Display, Formatter};
44
use std::fs::{remove_file, File};
55
use std::io::Read;
66
use std::path::PathBuf;
7-
use std::process::{self, Command, Output};
7+
use std::process::{self, Command};
88

99
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
1010
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
@@ -47,9 +47,34 @@ pub struct ContextLine {
4747
pub important: bool,
4848
}
4949

50+
pub struct CompiledExercise<'a> {
51+
exercise: &'a Exercise,
52+
_handle: FileHandle,
53+
}
54+
55+
impl<'a> CompiledExercise<'a> {
56+
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
57+
self.exercise.run()
58+
}
59+
}
60+
61+
#[derive(Debug)]
62+
pub struct ExerciseOutput {
63+
pub stdout: String,
64+
pub stderr: String,
65+
}
66+
67+
struct FileHandle;
68+
69+
impl Drop for FileHandle {
70+
fn drop(&mut self) {
71+
clean();
72+
}
73+
}
74+
5075
impl Exercise {
51-
pub fn compile(&self) -> Output {
52-
match self.mode {
76+
pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
77+
let cmd = match self.mode {
5378
Mode::Compile => Command::new("rustc")
5479
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
5580
.args(RUSTC_COLOR_ARGS)
@@ -59,17 +84,37 @@ impl Exercise {
5984
.args(RUSTC_COLOR_ARGS)
6085
.output(),
6186
}
62-
.expect("Failed to run 'compile' command.")
87+
.expect("Failed to run 'compile' command.");
88+
89+
if cmd.status.success() {
90+
Ok(CompiledExercise {
91+
exercise: &self,
92+
_handle: FileHandle,
93+
})
94+
} else {
95+
clean();
96+
Err(ExerciseOutput {
97+
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
98+
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
99+
})
100+
}
63101
}
64102

65-
pub fn run(&self) -> Output {
66-
Command::new(&temp_file())
103+
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
104+
let cmd = Command::new(&temp_file())
67105
.output()
68-
.expect("Failed to run 'run' command")
69-
}
106+
.expect("Failed to run 'run' command");
107+
108+
let output = ExerciseOutput {
109+
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
110+
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
111+
};
70112

71-
pub fn clean(&self) {
72-
let _ignored = remove_file(&temp_file());
113+
if cmd.status.success() {
114+
Ok(output)
115+
} else {
116+
Err(output)
117+
}
73118
}
74119

75120
pub fn state(&self) -> State {
@@ -121,6 +166,10 @@ impl Display for Exercise {
121166
}
122167
}
123168

169+
fn clean() {
170+
let _ignored = remove_file(&temp_file());
171+
}
172+
124173
#[cfg(test)]
125174
mod test {
126175
use super::*;
@@ -131,11 +180,12 @@ mod test {
131180
File::create(&temp_file()).unwrap();
132181
let exercise = Exercise {
133182
name: String::from("example"),
134-
path: PathBuf::from("example.rs"),
135-
mode: Mode::Test,
183+
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
184+
mode: Mode::Compile,
136185
hint: String::from(""),
137186
};
138-
exercise.clean();
187+
let compiled = exercise.compile().unwrap();
188+
drop(compiled);
139189
assert!(!Path::new(&temp_file()).exists());
140190
}
141191

src/main.rs

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ use std::sync::{Arc, Mutex};
1515
use std::thread;
1616
use std::time::Duration;
1717

18+
#[macro_use]
19+
mod ui;
20+
1821
mod exercise;
1922
mod run;
2023
mod verify;

src/run.rs

+26-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::exercise::{Exercise, Mode};
22
use crate::verify::test;
3-
use console::{style, Emoji};
43
use indicatif::ProgressBar;
54

65
pub fn run(exercise: &Exercise) -> Result<(), ()> {
@@ -11,42 +10,41 @@ pub fn run(exercise: &Exercise) -> Result<(), ()> {
1110
Ok(())
1211
}
1312

14-
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
13+
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
1514
let progress_bar = ProgressBar::new_spinner();
1615
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
1716
progress_bar.enable_steady_tick(100);
1817

19-
let compilecmd = exercise.compile();
18+
let compilation_result = exercise.compile();
19+
let compilation = match compilation_result {
20+
Ok(compilation) => compilation,
21+
Err(output) => {
22+
progress_bar.finish_and_clear();
23+
warn!(
24+
"Compilation of {} failed!, Compiler error message:\n",
25+
exercise
26+
);
27+
println!("{}", output.stderr);
28+
return Err(());
29+
}
30+
};
31+
2032
progress_bar.set_message(format!("Running {}...", exercise).as_str());
21-
if compilecmd.status.success() {
22-
let runcmd = exercise.run();
23-
progress_bar.finish_and_clear();
33+
let result = compilation.run();
34+
progress_bar.finish_and_clear();
2435

25-
if runcmd.status.success() {
26-
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
27-
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise);
28-
println!("{}", style(formatstr).green());
29-
exercise.clean();
36+
match result {
37+
Ok(output) => {
38+
println!("{}", output.stdout);
39+
success!("Successfully ran {}", exercise);
3040
Ok(())
31-
} else {
32-
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
33-
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
41+
}
42+
Err(output) => {
43+
println!("{}", output.stdout);
44+
println!("{}", output.stderr);
3445

35-
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
36-
println!("{}", style(formatstr).red());
37-
exercise.clean();
46+
warn!("Ran {} with errors", exercise);
3847
Err(())
3948
}
40-
} else {
41-
progress_bar.finish_and_clear();
42-
let formatstr = format!(
43-
"{} Compilation of {} failed! Compiler error message:\n",
44-
Emoji("⚠️ ", "!"),
45-
exercise
46-
);
47-
println!("{}", style(formatstr).red());
48-
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
49-
exercise.clean();
50-
Err(())
5149
}
5250
}

src/ui.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
macro_rules! warn {
2+
($fmt:literal, $ex:expr) => {{
3+
use console::{style, Emoji};
4+
let formatstr = format!($fmt, $ex);
5+
println!(
6+
"{} {}",
7+
style(Emoji("⚠️ ", "!")).red(),
8+
style(formatstr).red()
9+
);
10+
}};
11+
}
12+
13+
macro_rules! success {
14+
($fmt:literal, $ex:expr) => {{
15+
use console::{style, Emoji};
16+
let formatstr = format!($fmt, $ex);
17+
println!(
18+
"{} {}",
19+
style(Emoji("✅", "✓")).green(),
20+
style(formatstr).green()
21+
);
22+
}};
23+
}

src/verify.rs

+52-52
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::exercise::{Exercise, Mode, State};
2-
use console::{style, Emoji};
2+
use console::style;
33
use indicatif::ProgressBar;
44

55
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
66
for exercise in start_at {
77
let compile_result = match exercise.mode {
8-
Mode::Test => compile_and_test_interactively(&exercise),
8+
Mode::Test => compile_and_test(&exercise, RunMode::Interactive),
99
Mode::Compile => compile_only(&exercise),
1010
};
1111
if !compile_result.unwrap_or(false) {
@@ -15,78 +15,78 @@ pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<()
1515
Ok(())
1616
}
1717

18+
enum RunMode {
19+
Interactive,
20+
NonInteractive,
21+
}
22+
1823
pub fn test(exercise: &Exercise) -> Result<(), ()> {
19-
compile_and_test(exercise, true)?;
24+
compile_and_test(exercise, RunMode::NonInteractive)?;
2025
Ok(())
2126
}
2227

2328
fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
2429
let progress_bar = ProgressBar::new_spinner();
2530
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
2631
progress_bar.enable_steady_tick(100);
27-
let compile_output = exercise.compile();
32+
let compilation_result = exercise.compile();
2833
progress_bar.finish_and_clear();
29-
if compile_output.status.success() {
30-
let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise);
31-
println!("{}", style(formatstr).green());
32-
exercise.clean();
33-
Ok(prompt_for_completion(&exercise))
34-
} else {
35-
let formatstr = format!(
36-
"{} Compilation of {} failed! Compiler error message:\n",
37-
Emoji("⚠️ ", "!"),
38-
exercise
39-
);
40-
println!("{}", style(formatstr).red());
41-
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
42-
exercise.clean();
43-
Err(())
44-
}
45-
}
4634

47-
fn compile_and_test_interactively(exercise: &Exercise) -> Result<bool, ()> {
48-
compile_and_test(exercise, false)
35+
match compilation_result {
36+
Ok(_) => {
37+
success!("Successfully compiled {}!", exercise);
38+
Ok(prompt_for_completion(&exercise))
39+
}
40+
Err(output) => {
41+
warn!(
42+
"Compilation of {} failed! Compiler error message:\n",
43+
exercise
44+
);
45+
println!("{}", output.stderr);
46+
Err(())
47+
}
48+
}
4949
}
5050

51-
fn compile_and_test(exercise: &Exercise, skip_prompt: bool) -> Result<bool, ()> {
51+
fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
5252
let progress_bar = ProgressBar::new_spinner();
5353
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
5454
progress_bar.enable_steady_tick(100);
5555

56-
let compile_output = exercise.compile();
57-
if compile_output.status.success() {
58-
progress_bar.set_message(format!("Running {}...", exercise).as_str());
56+
let compilation_result = exercise.compile();
57+
58+
let compilation = match compilation_result {
59+
Ok(compilation) => compilation,
60+
Err(output) => {
61+
progress_bar.finish_and_clear();
62+
warn!(
63+
"Compiling of {} failed! Please try again. Here's the output:",
64+
exercise
65+
);
66+
println!("{}", output.stderr);
67+
return Err(());
68+
}
69+
};
5970

60-
let runcmd = exercise.run();
61-
progress_bar.finish_and_clear();
71+
let result = compilation.run();
72+
progress_bar.finish_and_clear();
6273

63-
if runcmd.status.success() {
64-
let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise);
65-
println!("{}", style(formatstr).green());
66-
exercise.clean();
67-
Ok(skip_prompt || prompt_for_completion(exercise))
68-
} else {
69-
let formatstr = format!(
70-
"{} Testing of {} failed! Please try again. Here's the output:",
71-
Emoji("⚠️ ", "!"),
74+
match result {
75+
Ok(_) => {
76+
if let RunMode::Interactive = run_mode {
77+
Ok(prompt_for_completion(&exercise))
78+
} else {
79+
Ok(true)
80+
}
81+
}
82+
Err(output) => {
83+
warn!(
84+
"Testing of {} failed! Please try again. Here's the output:",
7285
exercise
7386
);
74-
println!("{}", style(formatstr).red());
75-
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
76-
exercise.clean();
87+
println!("{}", output.stdout);
7788
Err(())
7889
}
79-
} else {
80-
progress_bar.finish_and_clear();
81-
let formatstr = format!(
82-
"{} Compiling of {} failed! Please try again. Here's the output:",
83-
Emoji("⚠️ ", "!"),
84-
exercise
85-
);
86-
println!("{}", style(formatstr).red());
87-
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
88-
exercise.clean();
89-
Err(())
9090
}
9191
}
9292

0 commit comments

Comments
 (0)