mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 00:00:03 +03:00
Compare commits
9 commits
3c5bbfca6c
...
daa5492f35
Author | SHA1 | Date | |
---|---|---|---|
daa5492f35 | |||
8d0aa11a35 | |||
e2674498c6 | |||
3200581d4d | |||
6afc4840b4 | |||
93aef73eb5 | |||
7ceeb81e61 | |||
84b1cf8849 | |||
3f23d26600 |
|
@ -2550,6 +2550,15 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"content"
|
"content"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "gerases",
|
||||||
|
"name": "gerases",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/8953623?v=4",
|
||||||
|
"profile": "https://github.com/gerases",
|
||||||
|
"contributions": [
|
||||||
|
"content"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 8,
|
"contributorsPerLine": 8,
|
||||||
|
|
|
@ -360,6 +360,9 @@ authors.
|
||||||
<td align="center" valign="top" width="12.5%"><a href="https://github.com/neuschaefer"><img src="https://avatars.githubusercontent.com/u/1021512?v=4?s=100" width="100px;" alt="J. Neuschäfer"/><br /><sub><b>J. Neuschäfer</b></sub></a><br /><a href="https://github.com/rust-lang/rustlings/commits?author=neuschaefer" title="Code">💻</a></td>
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/neuschaefer"><img src="https://avatars.githubusercontent.com/u/1021512?v=4?s=100" width="100px;" alt="J. Neuschäfer"/><br /><sub><b>J. Neuschäfer</b></sub></a><br /><a href="https://github.com/rust-lang/rustlings/commits?author=neuschaefer" title="Code">💻</a></td>
|
||||||
<td align="center" valign="top" width="12.5%"><a href="https://scooterhacking.org"><img src="https://avatars.githubusercontent.com/u/58905488?v=4?s=100" width="100px;" alt="Bastian Pedersen"/><br /><sub><b>Bastian Pedersen</b></sub></a><br /><a href="#content-bastianpedersen" title="Content">🖋</a></td>
|
<td align="center" valign="top" width="12.5%"><a href="https://scooterhacking.org"><img src="https://avatars.githubusercontent.com/u/58905488?v=4?s=100" width="100px;" alt="Bastian Pedersen"/><br /><sub><b>Bastian Pedersen</b></sub></a><br /><a href="#content-bastianpedersen" title="Content">🖋</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" valign="top" width="12.5%"><a href="https://github.com/gerases"><img src="https://avatars.githubusercontent.com/u/8953623?v=4?s=100" width="100px;" alt="gerases"/><br /><sub><b>gerases</b></sub></a><br /><a href="#content-gerases" title="Content">🖋</a></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ fn build_scores_table(results: String) -> HashMap<String, Team> {
|
||||||
let team_2_score: u8 = v[3].parse().unwrap();
|
let team_2_score: u8 = v[3].parse().unwrap();
|
||||||
// TODO: Populate the scores table with details extracted from the
|
// TODO: Populate the scores table with details extracted from the
|
||||||
// current line. Keep in mind that goals scored by team_1
|
// current line. Keep in mind that goals scored by team_1
|
||||||
// will be the number of goals conceded from team_2, and similarly
|
// will be the number of goals conceded by team_2, and similarly
|
||||||
// goals scored by team_2 will be the number of goals conceded by
|
// goals scored by team_2 will be the number of goals conceded by
|
||||||
// team_1.
|
// team_1.
|
||||||
}
|
}
|
||||||
|
|
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."""
|
102
src/exercise.rs
102
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +171,21 @@ path = "{}.rs""#,
|
||||||
.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 +193,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 +205,71 @@ 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())
|
|
||||||
.arg(arg)
|
|
||||||
.output()
|
|
||||||
.expect("Failed to run 'run' command");
|
|
||||||
|
|
||||||
let output = ExerciseOutput {
|
let filename = self.get_compiled_filename_by_mode(compilation_stdout);
|
||||||
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
|
||||||
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
let command_output = Command::new(filename).arg(arg).output();
|
||||||
|
let result = match command_output {
|
||||||
|
Ok(cmd) => {
|
||||||
|
let output = ExerciseOutput {
|
||||||
|
stdout: String::from_utf8_lossy(&cmd.stdout).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,12 @@ 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 => {
|
||||||
Mode::Compile => compile_and_run_interactively(exercise, success_hints),
|
compile_and_test(exercise, RunMode::Interactive, verbose, 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 +168,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 +182,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