Compare commits

...

9 commits

Author SHA1 Message Date
Ben daa5492f35
Merge 7ceeb81e61 into 8d0aa11a35 2024-01-30 20:12:58 +08:00
liv 8d0aa11a35
Merge pull request #1826 from rust-lang/all-contributors/add-gerases
docs: add gerases as a contributor for content
2024-01-15 14:52:04 +00:00
allcontributors[bot] e2674498c6
docs: update .all-contributorsrc [skip ci] 2024-01-15 14:51:48 +00:00
allcontributors[bot] 3200581d4d
docs: update AUTHORS.md [skip ci] 2024-01-15 14:51:47 +00:00
liv 6afc4840b4
Merge pull request #1819 from gerases/grammar-fix
Correct for more standard English
2024-01-15 14:51:31 +00:00
Sergei Gerasenko 93aef73eb5 Correct for more standard English 2024-01-09 10:17:03 -06:00
Ben 7ceeb81e61
chore: resolve missing parentheses in comment 2023-11-03 20:45:40 +00:00
Ben 84b1cf8849 chore: fix format 2023-11-03 20:39:51 +00:00
Ben 3f23d26600 feat: add support for exercises that depend on crates 2023-11-03 20:19:25 +00:00
9 changed files with 173 additions and 26 deletions

View file

@ -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,

View file

@ -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>

View file

@ -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.
} }

View 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"

View 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");
}

View file

@ -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."""

View file

@ -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 {

View file

@ -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(())

View file

@ -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!();