mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-01-30 00:00:03 +03:00
feat: add support for exercises that depend on crates
This commit is contained in:
parent
02b1b5f6ab
commit
3f23d26600
11
exercises/crates/mockall/mocks1/Cargo.toml
Normal file
11
exercises/crates/mockall/mocks1/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "mocks1"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mockall = "0.11.4"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mocks1"
|
||||||
|
path = "mocks1.rs"
|
45
exercises/crates/mockall/mocks1/mocks1.rs
Normal file
45
exercises/crates/mockall/mocks1/mocks1.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// mocks1.rs
|
||||||
|
//
|
||||||
|
// Mockall is a powerful mock object library for Rust. It provides tools to create
|
||||||
|
// mock versions of almost any trait or struct. They can be used in unit tests as
|
||||||
|
// a stand-in for the real object.
|
||||||
|
//
|
||||||
|
// These tests each contain an expectation that defines some behaviour we expect on
|
||||||
|
// calls to the function "foo". Add the "foo" function call and get the tests to pass
|
||||||
|
//
|
||||||
|
// I AM NOT DONE
|
||||||
|
|
||||||
|
use mockall::*;
|
||||||
|
use mockall::predicate::*;
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
trait MyTrait {
|
||||||
|
fn foo(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn follow_path_from_trait(x: &dyn MyTrait) -> String {
|
||||||
|
if ??? {
|
||||||
|
String::from("Followed path A")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String::from("Followed path B")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_follow_path_a() {
|
||||||
|
let mut mock = MockMyTrait::new();
|
||||||
|
mock.expect_foo()
|
||||||
|
.times(1)
|
||||||
|
.returning(||true);
|
||||||
|
assert_eq!(follow_path_from_trait(&mock), "Followed path A");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_follow_path_b() {
|
||||||
|
let mut mock = MockMyTrait::new();
|
||||||
|
mock.expect_foo()
|
||||||
|
.times(1)
|
||||||
|
.returning(||false);
|
||||||
|
assert_eq!(follow_path_from_trait(&mock), "Followed path B");
|
||||||
|
}
|
|
@ -1319,3 +1319,10 @@ path = "exercises/23_conversions/as_ref_mut.rs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "mocks1"
|
||||||
|
path = "exercises/crates/mockall/mocks1/Cargo.toml"
|
||||||
|
mode = "cratetest"
|
||||||
|
hint = """
|
||||||
|
x.foo() needs to be called in the if conditional to get the tests to pass."""
|
101
src/exercise.rs
101
src/exercise.rs
|
@ -1,5 +1,6 @@
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use serde_json::Value;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::fs::{self, remove_file, File};
|
use std::fs::{self, remove_file, File};
|
||||||
|
@ -35,6 +36,10 @@ pub enum Mode {
|
||||||
Test,
|
Test,
|
||||||
// Indicates that the exercise should be linted with clippy
|
// Indicates that the exercise should be linted with clippy
|
||||||
Clippy,
|
Clippy,
|
||||||
|
// Indicates that the exercise should be compiled as a binary and requires a crate
|
||||||
|
CrateCompile,
|
||||||
|
// Indicates that the exercise should be compiled as a test harness and requires a crate
|
||||||
|
CrateTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -50,7 +55,7 @@ pub struct Exercise {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
// The path to the file containing the exercise's source code
|
// 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)
|
// The mode of the exercise (Test, Compile, Clippy, CrateCompile or CrateTest
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
// The hint text associated with the exercise
|
// The hint text associated with the exercise
|
||||||
pub hint: String,
|
pub hint: String,
|
||||||
|
@ -81,12 +86,13 @@ pub struct ContextLine {
|
||||||
pub struct CompiledExercise<'a> {
|
pub struct CompiledExercise<'a> {
|
||||||
exercise: &'a Exercise,
|
exercise: &'a Exercise,
|
||||||
_handle: FileHandle,
|
_handle: FileHandle,
|
||||||
|
pub stdout: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompiledExercise<'a> {
|
impl<'a> CompiledExercise<'a> {
|
||||||
// Run the compiled exercise
|
// Run the compiled exercise
|
||||||
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||||
self.exercise.run()
|
self.exercise.run(&self.stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +170,19 @@ path = "{}.rs""#,
|
||||||
.args(RUSTC_COLOR_ARGS)
|
.args(RUSTC_COLOR_ARGS)
|
||||||
.args(["--", "-D", "warnings", "-D", "clippy::float_cmp"])
|
.args(["--", "-D", "warnings", "-D", "clippy::float_cmp"])
|
||||||
.output()
|
.output()
|
||||||
|
},
|
||||||
|
Mode::CrateCompile => {
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(["build", "--manifest-path", self.path.to_str().unwrap(), "--target-dir", &temp_file()])
|
||||||
|
.output()
|
||||||
|
},
|
||||||
|
Mode::CrateTest => {
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(["test", "--no-run"])
|
||||||
|
.args(["--manifest-path", self.path.to_str().unwrap()])
|
||||||
|
.args(["--target-dir", &temp_file()])
|
||||||
|
.args(["--message-format", "json-render-diagnostics"])
|
||||||
|
.output()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.expect("Failed to run 'compile' command.");
|
.expect("Failed to run 'compile' command.");
|
||||||
|
@ -172,8 +191,10 @@ path = "{}.rs""#,
|
||||||
Ok(CompiledExercise {
|
Ok(CompiledExercise {
|
||||||
exercise: self,
|
exercise: self,
|
||||||
_handle: FileHandle,
|
_handle: FileHandle,
|
||||||
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
self.cleanup_temporary_dirs_by_mode();
|
||||||
clean();
|
clean();
|
||||||
Err(ExerciseOutput {
|
Err(ExerciseOutput {
|
||||||
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||||
|
@ -182,26 +203,72 @@ path = "{}.rs""#,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
fn get_crate_test_filename(&self, stdout: &str) -> Result<String, ()> {
|
||||||
|
let json_objects = stdout.split("\n");
|
||||||
|
for json_object in json_objects {
|
||||||
|
let parsed_json: Value = serde_json::from_str(json_object).unwrap();
|
||||||
|
if parsed_json["target"]["kind"][0] == "bin" {
|
||||||
|
return Ok(String::from(parsed_json["filenames"][0].as_str().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_compiled_filename_by_mode(&self, compilation_stdout: &str) -> String {
|
||||||
|
match self.mode {
|
||||||
|
Mode::CrateCompile => temp_file() + "/debug/" + &self.name,
|
||||||
|
Mode::CrateTest => {
|
||||||
|
let get_filename_result = self.get_crate_test_filename(&compilation_stdout);
|
||||||
|
match get_filename_result {
|
||||||
|
Ok(filename) => filename,
|
||||||
|
Err(()) => panic!("Failed to get crate test filename")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => temp_file()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_temporary_dirs_by_mode(&self) {
|
||||||
|
match self.mode {
|
||||||
|
Mode::CrateCompile | Mode::CrateTest => fs::remove_dir_all(temp_file())
|
||||||
|
.expect("Failed to cleanup temp build dir"),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, compilation_stdout: &str) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||||
let arg = match self.mode {
|
let arg = match self.mode {
|
||||||
Mode::Test => "--show-output",
|
Mode::Test | Mode::CrateTest => "--show-output",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
let cmd = Command::new(temp_file())
|
|
||||||
|
let filename = self.get_compiled_filename_by_mode(compilation_stdout);
|
||||||
|
|
||||||
|
let command_output = Command::new(filename)
|
||||||
.arg(arg)
|
.arg(arg)
|
||||||
.output()
|
.output();
|
||||||
.expect("Failed to run 'run' command");
|
let result = match command_output {
|
||||||
|
Ok(cmd) => {
|
||||||
let output = ExerciseOutput {
|
let output = ExerciseOutput {
|
||||||
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||||
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cleanup_temporary_dirs_by_mode();
|
||||||
|
|
||||||
|
if cmd.status.success() {
|
||||||
|
Ok(output)
|
||||||
|
} else {
|
||||||
|
Err(output)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(msg) => {
|
||||||
|
self.cleanup_temporary_dirs_by_mode();
|
||||||
|
println!("Error: {}", msg);
|
||||||
|
panic!("Failed to run 'run' command");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
result
|
||||||
if cmd.status.success() {
|
|
||||||
Ok(output)
|
|
||||||
} else {
|
|
||||||
Err(output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state(&self) -> State {
|
pub fn state(&self) -> State {
|
||||||
|
|
|
@ -11,8 +11,8 @@ use indicatif::ProgressBar;
|
||||||
// the output from the test harnesses (if the mode of the exercise is test)
|
// the output from the test harnesses (if the mode of the exercise is test)
|
||||||
pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
|
pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
|
||||||
match exercise.mode {
|
match exercise.mode {
|
||||||
Mode::Test => test(exercise, verbose)?,
|
Mode::Test | Mode::CrateTest => test(exercise, verbose)?,
|
||||||
Mode::Compile => compile_and_run(exercise)?,
|
Mode::Compile | Mode::CrateCompile => compile_and_run(exercise)?,
|
||||||
Mode::Clippy => compile_and_run(exercise)?,
|
Mode::Clippy => compile_and_run(exercise)?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -28,8 +28,8 @@ pub fn verify<'a>(
|
||||||
|
|
||||||
for exercise in exercises {
|
for exercise in exercises {
|
||||||
let compile_result = match exercise.mode {
|
let compile_result = match exercise.mode {
|
||||||
Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints),
|
Mode::Test | Mode::CrateTest => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints),
|
||||||
Mode::Compile => compile_and_run_interactively(exercise, success_hints),
|
Mode::Compile | Mode::CrateCompile => compile_and_run_interactively(exercise, success_hints),
|
||||||
Mode::Clippy => compile_only(exercise, success_hints),
|
Mode::Clippy => compile_only(exercise, success_hints),
|
||||||
};
|
};
|
||||||
if !compile_result.unwrap_or(false) {
|
if !compile_result.unwrap_or(false) {
|
||||||
|
@ -164,8 +164,8 @@ fn prompt_for_completion(
|
||||||
State::Pending(context) => context,
|
State::Pending(context) => context,
|
||||||
};
|
};
|
||||||
match exercise.mode {
|
match exercise.mode {
|
||||||
Mode::Compile => success!("Successfully ran {}!", exercise),
|
Mode::Compile | Mode::CrateCompile => success!("Successfully ran {}!", exercise),
|
||||||
Mode::Test => success!("Successfully tested {}!", exercise),
|
Mode::Test | Mode::CrateTest => success!("Successfully tested {}!", exercise),
|
||||||
Mode::Clippy => success!("Successfully compiled {}!", exercise),
|
Mode::Clippy => success!("Successfully compiled {}!", exercise),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +178,8 @@ fn prompt_for_completion(
|
||||||
};
|
};
|
||||||
|
|
||||||
let success_msg = match exercise.mode {
|
let success_msg = match exercise.mode {
|
||||||
Mode::Compile => "The code is compiling!",
|
Mode::Compile | Mode::CrateCompile => "The code is compiling!",
|
||||||
Mode::Test => "The code is compiling, and the tests pass!",
|
Mode::Test | Mode::CrateTest => "The code is compiling, and the tests pass!",
|
||||||
Mode::Clippy => clippy_success_msg,
|
Mode::Clippy => clippy_success_msg,
|
||||||
};
|
};
|
||||||
println!();
|
println!();
|
||||||
|
|
Loading…
Reference in a new issue