Merge pull request #422 from AbdouSeck/show-tests-prints
This commit is contained in:
commit
e1e453075f
@ -804,7 +804,7 @@ name = "try_from_into"
|
|||||||
path = "exercises/conversions/try_from_into.rs"
|
path = "exercises/conversions/try_from_into.rs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Follow the steps provided right before the `From` implementation.
|
Follow the steps provided right before the `TryFrom` implementation.
|
||||||
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html"""
|
You can also use the example at https://doc.rust-lang.org/std/convert/trait.TryFrom.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
@ -821,4 +821,4 @@ mode = "test"
|
|||||||
hint = """
|
hint = """
|
||||||
The implementation of FromStr should return an Ok with a Person object,
|
The implementation of FromStr should return an Ok with a Person object,
|
||||||
or an Err with a string if the string is not valid.
|
or an Err with a string if the string is not valid.
|
||||||
This is a some like an `try_from_into` exercise."""
|
This is almost like the `try_from_into` exercise."""
|
||||||
|
22
install.sh
22
install.sh
@ -102,12 +102,30 @@ Path=${1:-rustlings/}
|
|||||||
echo "Cloning Rustlings at $Path..."
|
echo "Cloning Rustlings at $Path..."
|
||||||
git clone -q https://github.com/rust-lang/rustlings $Path
|
git clone -q https://github.com/rust-lang/rustlings $Path
|
||||||
|
|
||||||
|
cd $Path
|
||||||
|
|
||||||
Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | ${PY} -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']);")
|
Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | ${PY} -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']);")
|
||||||
CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin"
|
CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin"
|
||||||
|
|
||||||
|
if [[ -z ${Version} ]]
|
||||||
|
then
|
||||||
|
echo "The latest tag version could not be fetched remotely."
|
||||||
|
echo "Using the local git repository..."
|
||||||
|
Version=$(ls -tr .git/refs/tags/ | tail -1)
|
||||||
|
if [[ -z ${Version} ]]
|
||||||
|
then
|
||||||
|
echo "No valid tag version found"
|
||||||
|
echo "Rustlings will be installed using the master branch"
|
||||||
|
Version="master"
|
||||||
|
else
|
||||||
|
Version="tags/${Version}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
Version="tags/${Version}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Checking out version $Version..."
|
echo "Checking out version $Version..."
|
||||||
cd $Path
|
git checkout -q ${Version}
|
||||||
git checkout -q tags/$Version
|
|
||||||
|
|
||||||
echo "Installing the 'rustlings' executable..."
|
echo "Installing the 'rustlings' executable..."
|
||||||
cargo install --force --path .
|
cargo install --force --path .
|
||||||
|
@ -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 CONTEXT: usize = 2;
|
||||||
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
|
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 {
|
fn temp_file() -> String {
|
||||||
format!("./temp_{}", process::id())
|
format!("./temp_{}", process::id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The mode of the exercise.
|
||||||
#[derive(Deserialize, Copy, Clone)]
|
#[derive(Deserialize, Copy, Clone)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
// Indicates that the exercise should be compiled as a binary
|
||||||
Compile,
|
Compile,
|
||||||
|
// Indicates that the exercise should be compiled as a test harness
|
||||||
Test,
|
Test,
|
||||||
|
// Indicates that the exercise should be linted with clippy
|
||||||
Clippy,
|
Clippy,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,41 +34,60 @@ pub struct ExerciseList {
|
|||||||
pub exercises: Vec<Exercise>,
|
pub exercises: Vec<Exercise>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A representation of a rustlings exercise.
|
||||||
|
// This is deserialized from the accompanying info.toml file
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Exercise {
|
pub struct Exercise {
|
||||||
|
// Name of the exercise
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
// The path to the file containing the exercise's source code
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
// The mode of the exercise (Test, Compile, or Clippy)
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
|
// The hint text associated with the exercise
|
||||||
pub hint: String,
|
pub hint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An enum to track of the state of an Exercise.
|
||||||
|
// An Exercise can be either Done or Pending
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
|
// The state of the exercise once it's been completed
|
||||||
Done,
|
Done,
|
||||||
|
// The state of the exercise while it's not completed yet
|
||||||
Pending(Vec<ContextLine>),
|
Pending(Vec<ContextLine>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The context information of a pending exercise
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct ContextLine {
|
pub struct ContextLine {
|
||||||
|
// The source code that is still pending completion
|
||||||
pub line: String,
|
pub line: String,
|
||||||
|
// The line number of the source code still pending completion
|
||||||
pub number: usize,
|
pub number: usize,
|
||||||
|
// Whether or not this is important
|
||||||
pub important: bool,
|
pub important: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The result of compiling an exercise
|
||||||
pub struct CompiledExercise<'a> {
|
pub struct CompiledExercise<'a> {
|
||||||
exercise: &'a Exercise,
|
exercise: &'a Exercise,
|
||||||
_handle: FileHandle,
|
_handle: FileHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompiledExercise<'a> {
|
impl<'a> CompiledExercise<'a> {
|
||||||
|
// Run the compiled exercise
|
||||||
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||||
self.exercise.run()
|
self.exercise.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A representation of an already executed binary
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ExerciseOutput {
|
pub struct ExerciseOutput {
|
||||||
|
// The textual contents of the standard output of the binary
|
||||||
pub stdout: String,
|
pub stdout: String,
|
||||||
|
// The textual contents of the standard error of the binary
|
||||||
pub stderr: String,
|
pub stderr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +165,11 @@ path = "{}.rs""#,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
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()
|
.output()
|
||||||
.expect("Failed to run 'run' command");
|
.expect("Failed to run 'run' command");
|
||||||
|
|
||||||
@ -205,6 +234,7 @@ impl Display for Exercise {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn clean() {
|
fn clean() {
|
||||||
let _ignored = remove_file(&temp_file());
|
let _ignored = remove_file(&temp_file());
|
||||||
}
|
}
|
||||||
@ -280,4 +310,16 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(exercise.state(), State::Done);
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
38
src/main.rs
38
src/main.rs
@ -27,8 +27,21 @@ fn main() {
|
|||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.author("Olivia Hugger, Carol Nichols")
|
.author("Olivia Hugger, Carol Nichols")
|
||||||
.about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
|
.about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
|
||||||
.subcommand(SubCommand::with_name("verify").alias("v").about("Verifies all exercises according to the recommended order"))
|
.arg(
|
||||||
.subcommand(SubCommand::with_name("watch").alias("w").about("Reruns `verify` when files were edited"))
|
Arg::with_name("nocapture")
|
||||||
|
.long("nocapture")
|
||||||
|
.help("Show outputs from the test exercises")
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("verify")
|
||||||
|
.alias("v")
|
||||||
|
.about("Verifies all exercises according to the recommended order")
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("watch")
|
||||||
|
.alias("w")
|
||||||
|
.about("Reruns `verify` when files were edited")
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("run")
|
SubCommand::with_name("run")
|
||||||
.alias("r")
|
.alias("r")
|
||||||
@ -43,7 +56,7 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
if None == matches.subcommand_name() {
|
if matches.subcommand_name().is_none() {
|
||||||
println!();
|
println!();
|
||||||
println!(r#" welcome to... "#);
|
println!(r#" welcome to... "#);
|
||||||
println!(r#" _ _ _ "#);
|
println!(r#" _ _ _ "#);
|
||||||
@ -73,6 +86,7 @@ fn main() {
|
|||||||
|
|
||||||
let toml_str = &fs::read_to_string("info.toml").unwrap();
|
let toml_str = &fs::read_to_string("info.toml").unwrap();
|
||||||
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
|
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") {
|
if let Some(ref matches) = matches.subcommand_matches("run") {
|
||||||
let name = matches.value_of("name").unwrap();
|
let name = matches.value_of("name").unwrap();
|
||||||
@ -84,7 +98,7 @@ fn main() {
|
|||||||
std::process::exit(1)
|
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") {
|
if let Some(ref matches) = matches.subcommand_matches("hint") {
|
||||||
@ -102,26 +116,24 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches.subcommand_matches("verify").is_some() {
|
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() {
|
if matches.subcommand_matches("watch").is_some() && watch(&exercises, verbose).is_ok() {
|
||||||
if watch(&exercises).is_ok() {
|
|
||||||
println!(
|
println!(
|
||||||
"{emoji} All exercises completed! {emoji}",
|
"{emoji} All exercises completed! {emoji}",
|
||||||
emoji = Emoji("🎉", "★")
|
emoji = Emoji("🎉", "★")
|
||||||
);
|
);
|
||||||
println!("");
|
println!();
|
||||||
println!("We hope you enjoyed learning about the various aspects of Rust!");
|
println!("We hope you enjoyed learning about the various aspects of Rust!");
|
||||||
println!(
|
println!(
|
||||||
"If you noticed any issues, please don't hesitate to report them to our repo."
|
"If you noticed any issues, please don't hesitate to report them to our repo."
|
||||||
);
|
);
|
||||||
println!("You can also contribute your own exercises to help the greater community!");
|
println!("You can also contribute your own exercises to help the greater community!");
|
||||||
println!("");
|
println!();
|
||||||
println!("Before reporting an issue or contributing, please read our guidelines:");
|
println!("Before reporting an issue or contributing, please read our guidelines:");
|
||||||
println!("https://github.com/rust-lang/rustlings/blob/master/CONTRIBUTING.md");
|
println!("https://github.com/rust-lang/rustlings/blob/master/CONTRIBUTING.md");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if matches.subcommand_name().is_none() {
|
if matches.subcommand_name().is_none() {
|
||||||
let text = fs::read_to_string("default_out.txt").unwrap();
|
let text = fs::read_to_string("default_out.txt").unwrap();
|
||||||
@ -149,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.
|
/* Clears the terminal with an ANSI escape code.
|
||||||
Works in UNIX and newer Windows terminals. */
|
Works in UNIX and newer Windows terminals. */
|
||||||
fn clear_screen() {
|
fn clear_screen() {
|
||||||
@ -164,7 +176,7 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> {
|
|||||||
clear_screen();
|
clear_screen();
|
||||||
|
|
||||||
let to_owned_hint = |t: &Exercise| t.hint.to_owned();
|
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(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
|
Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))),
|
||||||
};
|
};
|
||||||
@ -179,7 +191,7 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> {
|
|||||||
.iter()
|
.iter()
|
||||||
.skip_while(|e| !filepath.ends_with(&e.path));
|
.skip_while(|e| !filepath.ends_with(&e.path));
|
||||||
clear_screen();
|
clear_screen();
|
||||||
match verify(pending_exercises) {
|
match verify(pending_exercises, verbose) {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(exercise) => {
|
Err(exercise) => {
|
||||||
let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
|
let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap();
|
||||||
|
11
src/run.rs
11
src/run.rs
@ -2,15 +2,22 @@ use crate::exercise::{Exercise, Mode};
|
|||||||
use crate::verify::test;
|
use crate::verify::test;
|
||||||
use indicatif::ProgressBar;
|
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 {
|
match exercise.mode {
|
||||||
Mode::Test => test(exercise)?,
|
Mode::Test => test(exercise, verbose)?,
|
||||||
Mode::Compile => compile_and_run(exercise)?,
|
Mode::Compile => compile_and_run(exercise)?,
|
||||||
Mode::Clippy => compile_and_run(exercise)?,
|
Mode::Clippy => compile_and_run(exercise)?,
|
||||||
}
|
}
|
||||||
Ok(())
|
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<(), ()> {
|
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||||
|
@ -2,10 +2,18 @@ use crate::exercise::{CompiledExercise, Exercise, Mode, State};
|
|||||||
use console::style;
|
use console::style;
|
||||||
use indicatif::ProgressBar;
|
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 {
|
for exercise in start_at {
|
||||||
let compile_result = match exercise.mode {
|
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::Compile => compile_and_run_interactively(&exercise),
|
||||||
Mode::Clippy => compile_only(&exercise),
|
Mode::Clippy => compile_only(&exercise),
|
||||||
};
|
};
|
||||||
@ -21,11 +29,13 @@ enum RunMode {
|
|||||||
NonInteractive,
|
NonInteractive,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test(exercise: &Exercise) -> Result<(), ()> {
|
// Compile and run the resulting test harness of the given Exercise
|
||||||
compile_and_test(exercise, RunMode::NonInteractive)?;
|
pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
|
||||||
|
compile_and_test(exercise, RunMode::NonInteractive, verbose)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke the rust compiler without running the resulting binary
|
||||||
fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
|
fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
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))
|
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, ()> {
|
fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> {
|
||||||
let progress_bar = ProgressBar::new_spinner();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
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)))
|
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();
|
let progress_bar = ProgressBar::new_spinner();
|
||||||
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
||||||
progress_bar.enable_steady_tick(100);
|
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();
|
progress_bar.finish_and_clear();
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => {
|
Ok(output) => {
|
||||||
|
if verbose {
|
||||||
|
println!("{}", output.stdout);
|
||||||
|
}
|
||||||
success!("Successfully tested {}", &exercise);
|
success!("Successfully tested {}", &exercise);
|
||||||
if let RunMode::Interactive = run_mode {
|
if let RunMode::Interactive = run_mode {
|
||||||
Ok(prompt_for_completion(&exercise, None))
|
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>(
|
fn compile<'a, 'b>(
|
||||||
exercise: &'a Exercise,
|
exercise: &'a Exercise,
|
||||||
progress_bar: &'b ProgressBar,
|
progress_bar: &'b ProgressBar,
|
||||||
@ -124,16 +144,16 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>) ->
|
|||||||
Mode::Clippy => "The code is compiling, and 📎 Clippy 📎 is happy!",
|
Mode::Clippy => "The code is compiling, and 📎 Clippy 📎 is happy!",
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("");
|
println!();
|
||||||
println!("🎉 🎉 {} 🎉 🎉", success_msg);
|
println!("🎉 🎉 {} 🎉 🎉", success_msg);
|
||||||
println!("");
|
println!();
|
||||||
|
|
||||||
if let Some(output) = prompt_output {
|
if let Some(output) = prompt_output {
|
||||||
println!("Output:");
|
println!("Output:");
|
||||||
println!("{}", separator());
|
println!("{}", separator());
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
println!("{}", separator());
|
println!("{}", separator());
|
||||||
println!("");
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("You can keep working on this exercise,");
|
println!("You can keep working on this exercise,");
|
||||||
@ -141,12 +161,12 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>) ->
|
|||||||
"or jump into the next one by removing the {} comment:",
|
"or jump into the next one by removing the {} comment:",
|
||||||
style("`I AM NOT DONE`").bold()
|
style("`I AM NOT DONE`").bold()
|
||||||
);
|
);
|
||||||
println!("");
|
println!();
|
||||||
for context_line in context {
|
for context_line in context {
|
||||||
let formatted_line = if context_line.important {
|
let formatted_line = if context_line.important {
|
||||||
format!("{}", style(context_line.line).bold())
|
format!("{}", style(context_line.line).bold())
|
||||||
} else {
|
} else {
|
||||||
format!("{}", context_line.line)
|
context_line.line.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#[test]
|
#[test]
|
||||||
fn passing() {
|
fn passing() {
|
||||||
|
println!("THIS TEST TOO SHALL PASS");
|
||||||
assert!(true);
|
assert!(true);
|
||||||
}
|
}
|
||||||
|
@ -159,3 +159,25 @@ fn run_test_exercise_does_not_prompt() {
|
|||||||
.code(0)
|
.code(0)
|
||||||
.stdout(predicates::str::contains("I AM NOT DONE").not());
|
.stdout(predicates::str::contains("I AM NOT DONE").not());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_single_test_success_with_output() {
|
||||||
|
Command::cargo_bin("rustlings")
|
||||||
|
.unwrap()
|
||||||
|
.args(&["--nocapture", "r", "testSuccess"])
|
||||||
|
.current_dir("tests/fixture/success/")
|
||||||
|
.assert()
|
||||||
|
.code(0)
|
||||||
|
.stdout(predicates::str::contains("THIS TEST TOO SHALL PAS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_single_test_success_without_output() {
|
||||||
|
Command::cargo_bin("rustlings")
|
||||||
|
.unwrap()
|
||||||
|
.args(&["r", "testSuccess"])
|
||||||
|
.current_dir("tests/fixture/success/")
|
||||||
|
.assert()
|
||||||
|
.code(0)
|
||||||
|
.stdout(predicates::str::contains("THIS TEST TOO SHALL PAS").not());
|
||||||
|
}
|
Reference in New Issue
Block a user