run exercises with CWD set to their directory

This commit is contained in:
Remo Senekowitsch 2026-08-10 21:50:15 +02:00
parent 0f0d3b78ca
commit 343e21b3bd
No known key found for this signature in database
4 changed files with 36 additions and 22 deletions

View File

@ -6,18 +6,14 @@
// Let's simulate this using asynchronous programming. Each person is // Let's simulate this using asynchronous programming. Each person is
// represented as an asynchronous task, which can be executed concurrently. // represented as an asynchronous task, which can be executed concurrently.
const SCORES_CLASS_A: &str = "exercises/24_async/scores_class_a.txt";
const SCORES_CLASS_B: &str = "exercises/24_async/scores_class_b.txt";
const SCORES_CLASS_C: &str = "exercises/24_async/scores_class_c.txt";
// Async tasks need to be executed by a "runtime", which is not provided by // Async tasks need to be executed by a "runtime", which is not provided by
// Rust's standard library. Here, we use the mainstream runtime `tokio`. // Rust's standard library. Here, we use the mainstream runtime `tokio`.
// The macro `tokio::main` wraps the entire main function in a runtime. // The macro `tokio::main` wraps the entire main function in a runtime.
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let mean_score_a = tokio::spawn(calculate_mean_score(SCORES_CLASS_A)); let mean_score_a = tokio::spawn(calculate_mean_score("scores_class_a.txt"));
let mean_score_b = tokio::spawn(calculate_mean_score(SCORES_CLASS_B)); let mean_score_b = tokio::spawn(calculate_mean_score("scores_class_b.txt"));
let mean_score_c = tokio::spawn(calculate_mean_score(SCORES_CLASS_C)); let mean_score_c = tokio::spawn(calculate_mean_score("scores_class_c.txt"));
// TODO: Await the spawned tasks to check their results. // TODO: Await the spawned tasks to check their results.
assert_eq!(mean_score_a, 84); // alice assert_eq!(mean_score_a, 84); // alice

View File

@ -6,18 +6,14 @@
// Let's simulate this using asynchronous programming. Each person is // Let's simulate this using asynchronous programming. Each person is
// represented as an asynchronous task, which can be executed concurrently. // represented as an asynchronous task, which can be executed concurrently.
const SCORES_CLASS_A: &str = "exercises/24_async/scores_class_a.txt";
const SCORES_CLASS_B: &str = "exercises/24_async/scores_class_b.txt";
const SCORES_CLASS_C: &str = "exercises/24_async/scores_class_c.txt";
// Async tasks need to be executed by a "runtime", which is not provided by // Async tasks need to be executed by a "runtime", which is not provided by
// Rust's standard library. Here, we use the mainstream runtime `tokio`. // Rust's standard library. Here, we use the mainstream runtime `tokio`.
// The macro `tokio::main` wraps the entire main function in a runtime. // The macro `tokio::main` wraps the entire main function in a runtime.
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let mean_score_a = tokio::spawn(calculate_mean_score(SCORES_CLASS_A)); let mean_score_a = tokio::spawn(calculate_mean_score("scores_class_a.txt"));
let mean_score_b = tokio::spawn(calculate_mean_score(SCORES_CLASS_B)); let mean_score_b = tokio::spawn(calculate_mean_score("scores_class_b.txt"));
let mean_score_c = tokio::spawn(calculate_mean_score(SCORES_CLASS_C)); let mean_score_c = tokio::spawn(calculate_mean_score("scores_class_c.txt"));
assert_eq!(mean_score_a.await.unwrap(), 84); // alice assert_eq!(mean_score_a.await.unwrap(), 84); // alice
assert_eq!(mean_score_b.await.unwrap(), 89); // bob assert_eq!(mean_score_b.await.unwrap(), 89); // bob

View File

@ -13,7 +13,12 @@ const TIMEOUT_SECS: u64 = 30;
/// Run a command with a description for a possible error and append the merged stdout and stderr. /// Run a command with a description for a possible error and append the merged stdout and stderr.
/// The boolean in the returned `Result` is true if the command's exit status is success. /// The boolean in the returned `Result` is true if the command's exit status is success.
fn run_cmd(mut cmd: Command, description: &str, output: Option<&mut Vec<u8>>) -> Result<bool> { fn run_cmd(
mut cmd: Command,
description: &str,
cwd: Option<&str>,
output: Option<&mut Vec<u8>>,
) -> Result<bool> {
let spawn = |mut cmd: Command| { let spawn = |mut cmd: Command| {
// The closure drops `cmd` which prevents a pipe deadlock. // The closure drops `cmd` which prevents a pipe deadlock.
cmd.stdin(Stdio::null()) cmd.stdin(Stdio::null())
@ -25,6 +30,9 @@ fn run_cmd(mut cmd: Command, description: &str, output: Option<&mut Vec<u8>>) ->
.wait_timeout(Duration::from_secs(TIMEOUT_SECS)) .wait_timeout(Duration::from_secs(TIMEOUT_SECS))
.with_context(|| format!("Failed to wait on `{description}` to exit")) .with_context(|| format!("Failed to wait on `{description}` to exit"))
}; };
if let Some(cwd) = cwd {
cmd.current_dir(cwd);
}
let mut handle = if let Some(output) = output { let mut handle = if let Some(output) = output {
let (mut reader, writer) = let (mut reader, writer) =
@ -133,7 +141,12 @@ impl CmdRunner {
} }
/// The boolean in the returned `Result` is true if the command's exit status is success. /// The boolean in the returned `Result` is true if the command's exit status is success.
pub fn run_debug_bin(&self, bin_name: &str, output: Option<&mut Vec<u8>>) -> Result<bool> { pub fn run_debug_bin(
&self,
bin_name: &str,
cwd: &str,
output: Option<&mut Vec<u8>>,
) -> Result<bool> {
// 7 = "/debug/".len() // 7 = "/debug/".len()
let mut bin_path = let mut bin_path =
PathBuf::with_capacity(self.target_dir.as_os_str().len() + 7 + bin_name.len()); PathBuf::with_capacity(self.target_dir.as_os_str().len() + 7 + bin_name.len());
@ -141,7 +154,7 @@ impl CmdRunner {
bin_path.push("debug"); bin_path.push("debug");
bin_path.push(bin_name); bin_path.push(bin_name);
run_cmd(Command::new(&bin_path), bin_name, output) run_cmd(Command::new(&bin_path), bin_name, Some(cwd), output)
} }
} }
@ -161,7 +174,7 @@ impl CargoSubcommand<'_> {
/// The boolean in the returned `Result` is true if the command's exit status is success. /// The boolean in the returned `Result` is true if the command's exit status is success.
pub fn run(self, description: &str) -> Result<bool> { pub fn run(self, description: &str) -> Result<bool> {
run_cmd(self.cmd, description, self.output) run_cmd(self.cmd, description, None, self.output)
} }
} }
@ -179,7 +192,7 @@ mod tests {
cmd.arg("Hello"); cmd.arg("Hello");
let mut output = Vec::with_capacity(8); let mut output = Vec::with_capacity(8);
run_cmd(cmd, "echo …", Some(&mut output)).unwrap(); run_cmd(cmd, "echo …", None, Some(&mut output)).unwrap();
assert_eq!(output, b"Hello\n\n"); assert_eq!(output, b"Hello\n\n");
} }

View File

@ -36,6 +36,7 @@ pub fn solution_link_line(
// Compilation must be done before calling this method. // Compilation must be done before calling this method.
fn run_bin( fn run_bin(
bin_name: &str, bin_name: &str,
cwd: &str,
mut output: Option<&mut Vec<u8>>, mut output: Option<&mut Vec<u8>>,
cmd_runner: &CmdRunner, cmd_runner: &CmdRunner,
) -> Result<bool> { ) -> Result<bool> {
@ -46,7 +47,7 @@ fn run_bin(
output.push(b'\n'); output.push(b'\n');
} }
let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?; let success = cmd_runner.run_debug_bin(bin_name, cwd, output.as_deref_mut())?;
if let Some(output) = output if let Some(output) = output
&& !success && !success
@ -123,6 +124,14 @@ pub trait RunnableExercise {
output.clear(); output.clear();
} }
let cwd_buf;
let cwd = if let Some(dir) = self.dir() {
cwd_buf = format!("exercises/{dir}");
cwd_buf.as_str()
} else {
"exercises"
};
if self.test() { if self.test() {
let output_is_some = output.is_some(); let output_is_some = output.is_some();
let mut test_cmd = cmd_runner.cargo("test", bin_name, output.as_deref_mut()); let mut test_cmd = cmd_runner.cargo("test", bin_name, output.as_deref_mut());
@ -131,7 +140,7 @@ pub trait RunnableExercise {
} }
let test_success = test_cmd.run("cargo test …")?; let test_success = test_cmd.run("cargo test …")?;
if !test_success { if !test_success {
run_bin(bin_name, output, cmd_runner)?; run_bin(bin_name, cwd, output, cmd_runner)?;
return Ok(false); return Ok(false);
} }
@ -151,7 +160,7 @@ pub trait RunnableExercise {
} }
let clippy_success = clippy_cmd.run("cargo clippy …")?; let clippy_success = clippy_cmd.run("cargo clippy …")?;
let run_success = run_bin(bin_name, output, cmd_runner)?; let run_success = run_bin(bin_name, cwd, output, cmd_runner)?;
Ok(clippy_success && run_success) Ok(clippy_success && run_success)
} }