summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/exercise.rs44
-rw-r--r--src/main.rs20
-rw-r--r--src/run.rs11
-rw-r--r--src/verify.rs32
4 files changed, 88 insertions, 19 deletions
diff --git a/src/exercise.rs b/src/exercise.rs
index d1eaa1a..177b7f3 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -11,15 +11,21 @@ const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
const CONTEXT: usize = 2;
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
+// Get a temporary file name that is hopefully unique to this process
+#[inline]
fn temp_file() -> String {
format!("./temp_{}", process::id())
}
+// The mode of the exercise.
#[derive(Deserialize, Copy, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
+ // Indicates that the exercise should be compiled as a binary
Compile,
+ // Indicates that the exercise should be compiled as a test harness
Test,
+ // Indicates that the exercise should be linted with clippy
Clippy,
}
@@ -28,41 +34,60 @@ pub struct ExerciseList {
pub exercises: Vec<Exercise>,
}
+// A representation of a rustlings exercise.
+// This is deserialized from the accompanying info.toml file
#[derive(Deserialize)]
pub struct Exercise {
+ // Name of the exercise
pub name: String,
+ // The path to the file containing the exercise's source code
pub path: PathBuf,
+ // The mode of the exercise (Test, Compile, or Clippy)
pub mode: Mode,
+ // The hint text associated with the exercise
pub hint: String,
}
+// An enum to track of the state of an Exercise.
+// An Exercise can be either Done or Pending
#[derive(PartialEq, Debug)]
pub enum State {
+ // The state of the exercise once it's been completed
Done,
+ // The state of the exercise while it's not completed yet
Pending(Vec<ContextLine>),
}
+// The context information of a pending exercise
#[derive(PartialEq, Debug)]
pub struct ContextLine {
+ // The source code that is still pending completion
pub line: String,
+ // The line number of the source code still pending completion
pub number: usize,
+ // Whether or not this is important
pub important: bool,
}
+// The result of compiling an exercise
pub struct CompiledExercise<'a> {
exercise: &'a Exercise,
_handle: FileHandle,
}
impl<'a> CompiledExercise<'a> {
+ // Run the compiled exercise
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
self.exercise.run()
}
}
+// A representation of an already executed binary
#[derive(Debug)]
pub struct ExerciseOutput {
+ // The textual contents of the standard output of the binary
pub stdout: String,
+ // The textual contents of the standard error of the binary
pub stderr: String,
}
@@ -140,7 +165,11 @@ path = "{}.rs""#,
}
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
- let cmd = Command::new(&temp_file())
+ let arg = match self.mode {
+ Mode::Test => "--show-output",
+ _ => ""
+ };
+ let cmd = Command::new(&temp_file()).arg(arg)
.output()
.expect("Failed to run 'run' command");
@@ -205,6 +234,7 @@ impl Display for Exercise {
}
}
+#[inline]
fn clean() {
let _ignored = remove_file(&temp_file());
}
@@ -280,4 +310,16 @@ mod test {
assert_eq!(exercise.state(), State::Done);
}
+
+ #[test]
+ fn test_exercise_with_output() {
+ let exercise = Exercise {
+ name: "finished_exercise".into(),
+ path: PathBuf::from("tests/fixture/success/testSuccess.rs"),
+ mode: Mode::Test,
+ hint: String::new(),
+ };
+ let out = exercise.compile().unwrap().run().unwrap();
+ assert!(out.stdout.contains("THIS TEST TOO SHALL PASS"));
+ }
}
diff --git a/src/main.rs b/src/main.rs
index f3f7f07..9c64de2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -28,10 +28,9 @@ fn main() {
.author("Olivia Hugger, Carol Nichols")
.about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
.arg(
- Arg::with_name("verbose")
- .short("V")
- .long("verbose")
- .help("Show tests' standard output")
+ Arg::with_name("nocapture")
+ .long("nocapture")
+ .help("Show outputs from the test exercises")
)
.subcommand(
SubCommand::with_name("verify")
@@ -87,6 +86,7 @@ fn main() {
let toml_str = &fs::read_to_string("info.toml").unwrap();
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
+ let verbose = matches.is_present("nocapture");
if let Some(ref matches) = matches.subcommand_matches("run") {
let name = matches.value_of("name").unwrap();
@@ -98,7 +98,7 @@ fn main() {
std::process::exit(1)
});
- run(&exercise).unwrap_or_else(|_| std::process::exit(1));
+ run(&exercise, verbose).unwrap_or_else(|_| std::process::exit(1));
}
if let Some(ref matches) = matches.subcommand_matches("hint") {
@@ -116,10 +116,10 @@ fn main() {
}
if matches.subcommand_matches("verify").is_some() {
- verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
+ verify(&exercises, verbose).unwrap_or_else(|_| std::process::exit(1));
}
- if matches.subcommand_matches("watch").is_some() && watch(&exercises).is_ok() {
+ if matches.subcommand_matches("watch").is_some() && watch(&exercises, verbose).is_ok() {
println!(
"{emoji} All exercises completed! {emoji}",
emoji = Emoji("🎉", "★")
@@ -161,7 +161,7 @@ fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>) {
});
}
-fn watch(exercises: &[Exercise]) -> notify::Result<()> {
+fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<()> {
/* Clears the terminal with an ANSI escape code.
Works in UNIX and newer Windows terminals. */
fn clear_screen() {
@@ -176,7 +176,7 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> {
clear_screen();
let to_owned_hint = |t: &Exercise| t.hint.to_owned();
- let failed_exercise_hint = match verify(exercises.iter()) {
+ let failed_exercise_hint = match verify(exercises.iter(), verbose) {
Ok(_) => return Ok(()),
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
};
@@ -191,7 +191,7 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> {
.iter()
.skip_while(|e| !filepath.ends_with(&e.path));
clear_screen();
- match verify(pending_exercises) {
+ match verify(pending_exercises, verbose) {
Ok(_) => return Ok(()),
Err(exercise) => {
let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
diff --git a/src/run.rs b/src/run.rs
index ebb0ae6..fdabb3e 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -2,15 +2,22 @@ use crate::exercise::{Exercise, Mode};
use crate::verify::test;
use indicatif::ProgressBar;
-pub fn run(exercise: &Exercise) -> Result<(), ()> {
+// Invoke the rust compiler on the path of the given exercise,
+// and run the ensuing binary.
+// The verbose argument helps determine whether or not to show
+// the output from the test harnesses (if the mode of the exercise is test)
+pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
match exercise.mode {
- Mode::Test => test(exercise)?,
+ Mode::Test => test(exercise, verbose)?,
Mode::Compile => compile_and_run(exercise)?,
Mode::Clippy => compile_and_run(exercise)?,
}
Ok(())
}
+// Invoke the rust compiler on the path of the given exercise
+// and run the ensuing binary.
+// This is strictly for non-test binaries, so output is displayed
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
diff --git a/src/verify.rs b/src/verify.rs
index 6e0e45e..fac0491 100644
--- a/src/verify.rs
+++ b/src/verify.rs
@@ -2,10 +2,18 @@ use crate::exercise::{CompiledExercise, Exercise, Mode, State};
use console::style;
use indicatif::ProgressBar;
-pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
+// Verify that the provided container of Exercise objects
+// can be compiled and run without any failures.
+// Any such failures will be reported to the end user.
+// If the Exercise being verified is a test, the verbose boolean
+// determines whether or not the test harness outputs are displayed.
+pub fn verify<'a>(
+ start_at: impl IntoIterator<Item = &'a Exercise>,
+ verbose: bool
+) -> Result<(), &'a Exercise> {
for exercise in start_at {
let compile_result = match exercise.mode {
- Mode::Test => compile_and_test(&exercise, RunMode::Interactive),
+ Mode::Test => compile_and_test(&exercise, RunMode::Interactive, verbose),
Mode::Compile => compile_and_run_interactively(&exercise),
Mode::Clippy => compile_only(&exercise),
};
@@ -21,11 +29,13 @@ enum RunMode {
NonInteractive,
}
-pub fn test(exercise: &Exercise) -> Result<(), ()> {
- compile_and_test(exercise, RunMode::NonInteractive)?;
+// Compile and run the resulting test harness of the given Exercise
+pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
+ compile_and_test(exercise, RunMode::NonInteractive, verbose)?;
Ok(())
}
+// Invoke the rust compiler without running the resulting binary
fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
@@ -38,6 +48,7 @@ fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
Ok(prompt_for_completion(&exercise, None))
}
+// Compile the given Exercise and run the resulting binary in an interactive mode
fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
@@ -63,7 +74,11 @@ fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> {
Ok(prompt_for_completion(&exercise, Some(output.stdout)))
}
-fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
+// Compile the given Exercise as a test harness and display
+// the output if verbose is set to true
+fn compile_and_test(
+ exercise: &Exercise, run_mode: RunMode, verbose: bool
+) -> Result<bool, ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
@@ -73,7 +88,10 @@ fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()>
progress_bar.finish_and_clear();
match result {
- Ok(_) => {
+ Ok(output) => {
+ if verbose {
+ println!("{}", output.stdout);
+ }
success!("Successfully tested {}", &exercise);
if let RunMode::Interactive = run_mode {
Ok(prompt_for_completion(&exercise, None))
@@ -92,6 +110,8 @@ fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()>
}
}
+// Compile the given Exercise and return an object with information
+// about the state of the compilation
fn compile<'a, 'b>(
exercise: &'a Exercise,
progress_bar: &'b ProgressBar,