run_make_support: make each command invocation only-run-once

This commit is contained in:
许杰友 Jieyou Xu (Joe) 2024-09-05 09:14:08 +00:00
parent afed862b26
commit 8871ce0e1f

View File

@ -15,7 +15,7 @@ use crate::{
/// This is a custom command wrapper that simplifies working with commands and makes it easier to
/// ensure that we check the exit status of executed processes.
///
/// # A [`Command`] must be executed
/// # A [`Command`] must be executed exactly once
///
/// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
/// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
@ -23,6 +23,12 @@ use crate::{
/// containing constructed but never executed commands is dangerous because it can give a false
/// sense of confidence.
///
/// Each [`Command`] invocation can also only be executed once, because we want to enforce
/// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
/// cloneable.
///
/// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
///
/// [`run`]: Self::run
/// [`run_fail`]: Self::run_fail
/// [`run_unchecked`]: Self::run_unchecked
@ -37,7 +43,9 @@ pub struct Command {
stdout: Option<Stdio>,
stderr: Option<Stdio>,
// Emulate linear type semantics.
drop_bomb: DropBomb,
already_executed: bool,
}
impl Command {
@ -51,6 +59,7 @@ impl Command {
stdin: None,
stdout: None,
stderr: None,
already_executed: false,
}
}
@ -177,6 +186,12 @@ impl Command {
#[track_caller]
fn command_output(&mut self) -> CompletedProcess {
if self.already_executed {
panic!("command was already executed");
} else {
self.already_executed = true;
}
self.drop_bomb.defuse();
// let's make sure we piped all the input and outputs
self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));