From 2031eacbab664080e31a17e64c465ddb2905172d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= <39484203+jieyouxu@users.noreply.github.com> Date: Thu, 5 Sep 2024 07:28:52 +0800 Subject: [PATCH 1/3] run_make_support: rename `Command::stdin` to `stdin_buf` and add `std{in,out,err}` config helpers Previously `Command::stdin` was actually just a stdin buffer helper, but this is different from `std::process::Command::stdin`. This is needlessly confusing, and blocks support to add `std{in,out,err}` config that tests may want to use to e.g. redirect to `/dev/ptmx`. --- src/tools/run-make-support/src/command.rs | 68 +++++++++++++++---- .../src/external_deps/llvm.rs | 7 +- .../src/external_deps/rustc.rs | 6 +- .../src/external_deps/rustdoc.rs | 6 +- src/tools/run-make-support/src/lib.rs | 2 + 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs index a0b96e25a0cba..3f2ae078229dd 100644 --- a/src/tools/run-make-support/src/command.rs +++ b/src/tools/run-make-support/src/command.rs @@ -29,7 +29,14 @@ use crate::{ #[derive(Debug)] pub struct Command { cmd: StdCommand, - stdin: Option>, + // Convience for providing a quick stdin buffer. + stdin_buf: Option>, + + // Configurations for child process's std{in,out,err} handles. + stdin: Option, + stdout: Option, + stderr: Option, + drop_bomb: DropBomb, } @@ -37,12 +44,43 @@ impl Command { #[track_caller] pub fn new>(program: P) -> Self { let program = program.as_ref(); - Self { cmd: StdCommand::new(program), stdin: None, drop_bomb: DropBomb::arm(program) } + Self { + cmd: StdCommand::new(program), + stdin_buf: None, + drop_bomb: DropBomb::arm(program), + stdin: None, + stdout: None, + stderr: None, + } + } + + /// Specify a stdin input buffer. This is a convenience helper, + pub fn stdin_buf>(&mut self, input: I) -> &mut Self { + self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice()); + self + } + + /// Configuration for the child process’s standard input (stdin) handle. + /// + /// See [`std::process::Command::stdin`]. + pub fn stdin>(&mut self, cfg: T) -> &mut Self { + self.stdin = Some(cfg.into()); + self + } + + /// Configuration for the child process’s standard output (stdout) handle. + /// + /// See [`std::process::Command::stdout`]. + pub fn stdout>(&mut self, cfg: T) -> &mut Self { + self.stdout = Some(cfg.into()); + self } - /// Specify a stdin input - pub fn stdin>(&mut self, input: I) -> &mut Self { - self.stdin = Some(input.as_ref().to_vec().into_boxed_slice()); + /// Configuration for the child process’s standard error (stderr) handle. + /// + /// See [`std::process::Command::stderr`]. + pub fn stderr>(&mut self, cfg: T) -> &mut Self { + self.stderr = Some(cfg.into()); self } @@ -105,6 +143,8 @@ impl Command { } /// Run the constructed command and assert that it is successfully run. + /// + /// By default, std{in,out,err} are [`Stdio::piped()`]. #[track_caller] pub fn run(&mut self) -> CompletedProcess { let output = self.command_output(); @@ -115,6 +155,8 @@ impl Command { } /// Run the constructed command and assert that it does not successfully run. + /// + /// By default, std{in,out,err} are [`Stdio::piped()`]. #[track_caller] pub fn run_fail(&mut self) -> CompletedProcess { let output = self.command_output(); @@ -124,10 +166,10 @@ impl Command { output } - /// Run the command but do not check its exit status. - /// Only use if you explicitly don't care about the exit status. - /// Prefer to use [`Self::run`] and [`Self::run_fail`] - /// whenever possible. + /// Run the command but do not check its exit status. Only use if you explicitly don't care + /// about the exit status. + /// + /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible. #[track_caller] pub fn run_unchecked(&mut self) -> CompletedProcess { self.command_output() @@ -137,11 +179,11 @@ impl Command { fn command_output(&mut self) -> CompletedProcess { self.drop_bomb.defuse(); // let's make sure we piped all the input and outputs - self.cmd.stdin(Stdio::piped()); - self.cmd.stdout(Stdio::piped()); - self.cmd.stderr(Stdio::piped()); + self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped())); + self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped())); + self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped())); - let output = if let Some(input) = &self.stdin { + let output = if let Some(input) = &self.stdin_buf { let mut child = self.cmd.spawn().unwrap(); { diff --git a/src/tools/run-make-support/src/external_deps/llvm.rs b/src/tools/run-make-support/src/external_deps/llvm.rs index e9315856cd775..2522c4aeb9365 100644 --- a/src/tools/run-make-support/src/external_deps/llvm.rs +++ b/src/tools/run-make-support/src/external_deps/llvm.rs @@ -227,9 +227,10 @@ impl LlvmFilecheck { Self { cmd } } - /// Pipe a read file into standard input containing patterns that will be matched against the .patterns(path) call. - pub fn stdin>(&mut self, input: I) -> &mut Self { - self.cmd.stdin(input); + /// Provide a buffer representing standard input containing patterns that will be matched + /// against the `.patterns(path)` call. + pub fn stdin_buf>(&mut self, input: I) -> &mut Self { + self.cmd.stdin_buf(input); self } diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs index cece58d29566c..f60ea972839c1 100644 --- a/src/tools/run-make-support/src/external_deps/rustc.rs +++ b/src/tools/run-make-support/src/external_deps/rustc.rs @@ -291,9 +291,9 @@ impl Rustc { self } - /// Specify a stdin input - pub fn stdin>(&mut self, input: I) -> &mut Self { - self.cmd.stdin(input); + /// Specify a stdin input buffer. + pub fn stdin_buf>(&mut self, input: I) -> &mut Self { + self.cmd.stdin_buf(input); self } diff --git a/src/tools/run-make-support/src/external_deps/rustdoc.rs b/src/tools/run-make-support/src/external_deps/rustdoc.rs index 96b1c719e2e3e..df5e5d8a2e84a 100644 --- a/src/tools/run-make-support/src/external_deps/rustdoc.rs +++ b/src/tools/run-make-support/src/external_deps/rustdoc.rs @@ -85,9 +85,9 @@ impl Rustdoc { self } - /// Specify a stdin input - pub fn stdin>(&mut self, input: I) -> &mut Self { - self.cmd.stdin(input); + /// Specify a stdin input buffer. + pub fn stdin_buf>(&mut self, input: I) -> &mut Self { + self.cmd.stdin_buf(input); self } diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs index 989d00d4c2f97..fefafb95b33b1 100644 --- a/src/tools/run-make-support/src/lib.rs +++ b/src/tools/run-make-support/src/lib.rs @@ -34,6 +34,7 @@ pub mod rfs { } // Re-exports of third-party library crates. +// tidy-alphabetical-start pub use bstr; pub use gimli; pub use libc; @@ -41,6 +42,7 @@ pub use object; pub use regex; pub use serde_json; pub use wasmparser; +// tidy-alphabetical-end // Re-exports of external dependencies. pub use external_deps::{c_build, cc, clang, htmldocck, llvm, python, rustc, rustdoc}; From afed862b26335b37539ec2cd295e34ef3fb844be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= <39484203+jieyouxu@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:02:32 +0800 Subject: [PATCH 2/3] tests: use renamed `stdin_buf` --- tests/run-make/clear-error-blank-output/rmake.rs | 2 +- tests/run-make/comment-section/rmake.rs | 2 +- tests/run-make/compile-stdin/rmake.rs | 2 +- tests/run-make/libtest-junit/rmake.rs | 2 +- tests/run-make/llvm-ident/rmake.rs | 2 +- tests/run-make/llvm-outputs/rmake.rs | 4 ++-- tests/run-make/no-builtins-attribute/rmake.rs | 2 +- tests/run-make/pgo-branch-weights/rmake.rs | 5 ++++- tests/run-make/pgo-indirect-call-promotion/rmake.rs | 5 ++++- tests/run-make/pgo-use/rmake.rs | 2 +- tests/run-make/separate-link/rmake.rs | 2 +- tests/run-make/static-pie/rmake.rs | 2 +- tests/run-make/stdin-rustc/rmake.rs | 4 ++-- tests/run-make/stdin-rustdoc/rmake.rs | 4 ++-- tests/run-make/sysroot-crates-are-unstable/rmake.rs | 2 +- tests/run-make/unknown-mod-stdin/rmake.rs | 2 +- .../x86_64-fortanix-unknown-sgx-lvi/rmake.rs | 12 ++++++++---- 17 files changed, 33 insertions(+), 23 deletions(-) diff --git a/tests/run-make/clear-error-blank-output/rmake.rs b/tests/run-make/clear-error-blank-output/rmake.rs index e0f042cf805b5..22e030691f289 100644 --- a/tests/run-make/clear-error-blank-output/rmake.rs +++ b/tests/run-make/clear-error-blank-output/rmake.rs @@ -8,6 +8,6 @@ use run_make_support::rustc; fn main() { - let output = rustc().output("").stdin(b"fn main() {}").run_fail(); + let output = rustc().output("").stdin_buf(b"fn main() {}").run_fail(); output.assert_stderr_not_contains("panic"); } diff --git a/tests/run-make/comment-section/rmake.rs b/tests/run-make/comment-section/rmake.rs index 1557f50dbd024..ccfc38e870d88 100644 --- a/tests/run-make/comment-section/rmake.rs +++ b/tests/run-make/comment-section/rmake.rs @@ -14,7 +14,7 @@ fn main() { rustc() .arg("-") - .stdin("fn main() {}") + .stdin_buf("fn main() {}") .emit("link,obj") .arg("-Csave-temps") .target(target) diff --git a/tests/run-make/compile-stdin/rmake.rs b/tests/run-make/compile-stdin/rmake.rs index f93080dfdc4fe..b8244335855c5 100644 --- a/tests/run-make/compile-stdin/rmake.rs +++ b/tests/run-make/compile-stdin/rmake.rs @@ -8,6 +8,6 @@ use run_make_support::{run, rustc}; fn main() { - rustc().arg("-").stdin("fn main() {}").run(); + rustc().arg("-").stdin_buf("fn main() {}").run(); run("rust_out"); } diff --git a/tests/run-make/libtest-junit/rmake.rs b/tests/run-make/libtest-junit/rmake.rs index d631313ed9210..5917660b6c7ff 100644 --- a/tests/run-make/libtest-junit/rmake.rs +++ b/tests/run-make/libtest-junit/rmake.rs @@ -21,7 +21,7 @@ fn run_tests(extra_args: &[&str], expected_file: &str) { .run_fail(); let test_stdout = &cmd_out.stdout_utf8(); - python_command().arg("validate_junit.py").stdin(test_stdout).run(); + python_command().arg("validate_junit.py").stdin_buf(test_stdout).run(); diff() .expected_file(expected_file) diff --git a/tests/run-make/llvm-ident/rmake.rs b/tests/run-make/llvm-ident/rmake.rs index 9699d0579f6ef..47e6fc4de15b3 100644 --- a/tests/run-make/llvm-ident/rmake.rs +++ b/tests/run-make/llvm-ident/rmake.rs @@ -17,7 +17,7 @@ fn main() { .codegen_units(16) .opt_level("2") .target(&env_var("TARGET")) - .stdin("fn main(){}") + .stdin_buf("fn main(){}") .run(); // `llvm-dis` is used here since `--emit=llvm-ir` does not emit LLVM IR diff --git a/tests/run-make/llvm-outputs/rmake.rs b/tests/run-make/llvm-outputs/rmake.rs index b8ac21c98a010..632e9a09ba503 100644 --- a/tests/run-make/llvm-outputs/rmake.rs +++ b/tests/run-make/llvm-outputs/rmake.rs @@ -11,8 +11,8 @@ fn main() { let p = cwd(); path_bc = p.join("nonexistant_dir_bc"); path_ir = p.join("nonexistant_dir_ir"); - rustc().input("-").stdin("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run(); - rustc().input("-").stdin("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run(); + rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run(); + rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run(); assert!(path_bc.exists()); assert!(path_ir.exists()); }); diff --git a/tests/run-make/no-builtins-attribute/rmake.rs b/tests/run-make/no-builtins-attribute/rmake.rs index 1e15b0c03f1d9..7182c65a2c59e 100644 --- a/tests/run-make/no-builtins-attribute/rmake.rs +++ b/tests/run-make/no-builtins-attribute/rmake.rs @@ -9,5 +9,5 @@ use run_make_support::{llvm_filecheck, rfs, rustc}; fn main() { rustc().input("no_builtins.rs").emit("link").run(); rustc().input("main.rs").emit("llvm-ir").run(); - llvm_filecheck().patterns("filecheck.main.txt").stdin(rfs::read("main.ll")).run(); + llvm_filecheck().patterns("filecheck.main.txt").stdin_buf(rfs::read("main.ll")).run(); } diff --git a/tests/run-make/pgo-branch-weights/rmake.rs b/tests/run-make/pgo-branch-weights/rmake.rs index ef33d009dbb20..105c2fafc5a10 100644 --- a/tests/run-make/pgo-branch-weights/rmake.rs +++ b/tests/run-make/pgo-branch-weights/rmake.rs @@ -35,5 +35,8 @@ fn main() { .codegen_units(1) .emit("llvm-ir") .run(); - llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run(); + llvm_filecheck() + .patterns("filecheck-patterns.txt") + .stdin_buf(rfs::read("interesting.ll")) + .run(); } diff --git a/tests/run-make/pgo-indirect-call-promotion/rmake.rs b/tests/run-make/pgo-indirect-call-promotion/rmake.rs index d0ccfd8a4d7e4..28232eb256621 100644 --- a/tests/run-make/pgo-indirect-call-promotion/rmake.rs +++ b/tests/run-make/pgo-indirect-call-promotion/rmake.rs @@ -29,5 +29,8 @@ fn main() { .codegen_units(1) .emit("llvm-ir") .run(); - llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run(); + llvm_filecheck() + .patterns("filecheck-patterns.txt") + .stdin_buf(rfs::read("interesting.ll")) + .run(); } diff --git a/tests/run-make/pgo-use/rmake.rs b/tests/run-make/pgo-use/rmake.rs index cad49372266e8..276af9ea263a7 100644 --- a/tests/run-make/pgo-use/rmake.rs +++ b/tests/run-make/pgo-use/rmake.rs @@ -51,5 +51,5 @@ fn main() { let lines: Vec<_> = ir.lines().rev().collect(); let mut reversed_ir = lines.join("\n"); reversed_ir.push('\n'); - llvm_filecheck().patterns("filecheck-patterns.txt").stdin(reversed_ir.as_bytes()).run(); + llvm_filecheck().patterns("filecheck-patterns.txt").stdin_buf(reversed_ir.as_bytes()).run(); } diff --git a/tests/run-make/separate-link/rmake.rs b/tests/run-make/separate-link/rmake.rs index e91b25489bc59..0ec7334e75de7 100644 --- a/tests/run-make/separate-link/rmake.rs +++ b/tests/run-make/separate-link/rmake.rs @@ -8,7 +8,7 @@ use run_make_support::{run, rustc}; fn main() { - rustc().stdin(b"fn main(){}").arg("-Zno-link").arg("-").run(); + rustc().stdin_buf(b"fn main(){}").arg("-Zno-link").arg("-").run(); rustc().arg("-Zlink-only").input("rust_out.rlink").run(); run("rust_out"); } diff --git a/tests/run-make/static-pie/rmake.rs b/tests/run-make/static-pie/rmake.rs index 74f86bb8163b0..1557c170f56de 100644 --- a/tests/run-make/static-pie/rmake.rs +++ b/tests/run-make/static-pie/rmake.rs @@ -28,7 +28,7 @@ fn ok_compiler_version(compiler: &str) -> bool { } let compiler_output = - cmd(compiler).stdin(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8(); + cmd(compiler).stdin_buf(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8(); let re = Regex::new(r"(?m)^(\d+)").unwrap(); let version: u32 = re.captures(&compiler_output).unwrap().get(1).unwrap().as_str().parse().unwrap(); diff --git a/tests/run-make/stdin-rustc/rmake.rs b/tests/run-make/stdin-rustc/rmake.rs index 68e9cf2fbd803..2d634dd455eb2 100644 --- a/tests/run-make/stdin-rustc/rmake.rs +++ b/tests/run-make/stdin-rustc/rmake.rs @@ -14,7 +14,7 @@ const NOT_UTF8: &[u8] = &[0xff, 0xff, 0xff]; fn main() { // echo $HELLO_WORLD | rustc - - rustc().arg("-").stdin(HELLO_WORLD).run(); + rustc().arg("-").stdin_buf(HELLO_WORLD).run(); assert!( PathBuf::from(if !is_windows() { "rust_out" } else { "rust_out.exe" }) .try_exists() @@ -22,7 +22,7 @@ fn main() { ); // echo $NOT_UTF8 | rustc - - rustc().arg("-").stdin(NOT_UTF8).run_fail().assert_stderr_contains( + rustc().arg("-").stdin_buf(NOT_UTF8).run_fail().assert_stderr_contains( "error: couldn't read from stdin, as it did not contain valid UTF-8", ); } diff --git a/tests/run-make/stdin-rustdoc/rmake.rs b/tests/run-make/stdin-rustdoc/rmake.rs index de47a24fbdd4f..30f97b8a2cd75 100644 --- a/tests/run-make/stdin-rustdoc/rmake.rs +++ b/tests/run-make/stdin-rustdoc/rmake.rs @@ -15,11 +15,11 @@ fn main() { let out_dir = PathBuf::from("doc"); // rustdoc - - rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run(); + rustdoc().arg("-").out_dir(&out_dir).stdin_buf(INPUT).run(); assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap()); // rustdoc --test - - rustdoc().arg("--test").arg("-").stdin(INPUT).run(); + rustdoc().arg("--test").arg("-").stdin_buf(INPUT).run(); // rustdoc file.rs - rustdoc().arg("file.rs").arg("-").run_fail(); diff --git a/tests/run-make/sysroot-crates-are-unstable/rmake.rs b/tests/run-make/sysroot-crates-are-unstable/rmake.rs index 2240d87237b1a..c81c7fafdab0b 100644 --- a/tests/run-make/sysroot-crates-are-unstable/rmake.rs +++ b/tests/run-make/sysroot-crates-are-unstable/rmake.rs @@ -34,7 +34,7 @@ fn check_crate_is_unstable(cr: &Crate) { .target(target()) .extern_(name, path) .input("-") - .stdin(format!("extern crate {name};")) + .stdin_buf(format!("extern crate {name};")) .run_fail(); // Make sure it failed for the intended reason, not some other reason. diff --git a/tests/run-make/unknown-mod-stdin/rmake.rs b/tests/run-make/unknown-mod-stdin/rmake.rs index 0fe5c78ed0ff0..6be3119c0fd15 100644 --- a/tests/run-make/unknown-mod-stdin/rmake.rs +++ b/tests/run-make/unknown-mod-stdin/rmake.rs @@ -12,7 +12,7 @@ use run_make_support::{diff, rustc}; fn main() { - let out = rustc().crate_type("rlib").stdin(b"mod unknown;").arg("-").run_fail(); + let out = rustc().crate_type("rlib").stdin_buf(b"mod unknown;").arg("-").run_fail(); diff() .actual_text("actual-stdout", out.stdout_utf8()) .expected_file("unknown-mod.stdout") diff --git a/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs b/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs index 130781a429371..e58762aeb6db7 100644 --- a/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs +++ b/tests/run-make/x86_64-fortanix-unknown-sgx-lvi/rmake.rs @@ -78,19 +78,23 @@ fn check(func_re: &str, mut checks: &str) { // This is because frame pointers are optional, and them being enabled requires // an additional `popq` in the pattern checking file. if func_re == "std::io::stdio::_print::[[:alnum:]]+" { - let output = llvm_filecheck().stdin(&dump).patterns(checks).run_unchecked(); + let output = llvm_filecheck().stdin_buf(&dump).patterns(checks).run_unchecked(); if !output.status().success() { checks = "print.without_frame_pointers.checks"; - llvm_filecheck().stdin(&dump).patterns(checks).run(); + llvm_filecheck().stdin_buf(&dump).patterns(checks).run(); } } else { - llvm_filecheck().stdin(&dump).patterns(checks).run(); + llvm_filecheck().stdin_buf(&dump).patterns(checks).run(); } if !["rust_plus_one_global_asm", "cmake_plus_one_c_global_asm", "cmake_plus_one_cxx_global_asm"] .contains(&func_re) { // The assembler cannot avoid explicit `ret` instructions. Sequences // of `shlq $0x0, (%rsp); lfence; retq` are used instead. - llvm_filecheck().args(&["--implicit-check-not", "ret"]).stdin(dump).patterns(checks).run(); + llvm_filecheck() + .args(&["--implicit-check-not", "ret"]) + .stdin_buf(dump) + .patterns(checks) + .run(); } } From 8871ce0e1f4bc9dbca91d567ab0332746f7facaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= Date: Thu, 5 Sep 2024 09:14:08 +0000 Subject: [PATCH 3/3] run_make_support: make each command invocation only-run-once --- src/tools/run-make-support/src/command.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs index 3f2ae078229dd..6b58173b34304 100644 --- a/src/tools/run-make-support/src/command.rs +++ b/src/tools/run-make-support/src/command.rs @@ -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, stderr: Option, + // 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()));