| use super::{Command, Output, Stdio}; | |
| use crate::io::prelude::*; | |
| use crate::io::{BorrowedBuf, ErrorKind}; | |
| use crate::mem::MaybeUninit; | |
| use crate::str; | |
| fn known_command() -> Command { | |
| if cfg!(windows) { | |
| Command::new("help") | |
| } else if cfg!(all(target_vendor = "apple", not(target_os = "macos"))) { | |
| // iOS/tvOS/watchOS/visionOS have a very limited set of commandline | |
| // binaries available. | |
| Command::new("log") | |
| } else { | |
| Command::new("echo") | |
| } | |
| } | |
| #[cfg(target_os = "android")] | |
| fn shell_cmd() -> Command { | |
| Command::new("/system/bin/sh") | |
| } | |
| #[cfg(not(target_os = "android"))] | |
| fn shell_cmd() -> Command { | |
| Command::new("/bin/sh") | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn smoke() { | |
| let p = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 0"]).spawn() | |
| } else { | |
| shell_cmd().arg("-c").arg("true").spawn() | |
| }; | |
| assert!(p.is_ok()); | |
| let mut p = p.unwrap(); | |
| assert!(p.wait().unwrap().success()); | |
| } | |
| #[test] | |
| #[cfg_attr(target_os = "android", ignore)] | |
| fn smoke_failure() { | |
| match Command::new("if-this-is-a-binary-then-the-world-has-ended").spawn() { | |
| Ok(..) => panic!(), | |
| Err(..) => {} | |
| } | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn exit_reported_right() { | |
| let p = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 1"]).spawn() | |
| } else { | |
| shell_cmd().arg("-c").arg("false").spawn() | |
| }; | |
| assert!(p.is_ok()); | |
| let mut p = p.unwrap(); | |
| assert!(p.wait().unwrap().code() == Some(1)); | |
| drop(p.wait()); | |
| } | |
| #[test] | |
| #[cfg(unix)] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn signal_reported_right() { | |
| use crate::os::unix::process::ExitStatusExt; | |
| let mut p = shell_cmd().arg("-c").arg("read a").stdin(Stdio::piped()).spawn().unwrap(); | |
| p.kill().unwrap(); | |
| match p.wait().unwrap().signal() { | |
| Some(9) => {} | |
| result => panic!("not terminated by signal 9 (instead, {result:?})"), | |
| } | |
| } | |
| pub fn run_output(mut cmd: Command) -> String { | |
| let p = cmd.spawn(); | |
| assert!(p.is_ok()); | |
| let mut p = p.unwrap(); | |
| assert!(p.stdout.is_some()); | |
| let mut ret = String::new(); | |
| p.stdout.as_mut().unwrap().read_to_string(&mut ret).unwrap(); | |
| assert!(p.wait().unwrap().success()); | |
| return ret; | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn stdout_works() { | |
| if cfg!(target_os = "windows") { | |
| let mut cmd = Command::new("cmd"); | |
| cmd.args(&["/C", "echo foobar"]).stdout(Stdio::piped()); | |
| assert_eq!(run_output(cmd), "foobar\r\n"); | |
| } else { | |
| let mut cmd = shell_cmd(); | |
| cmd.arg("-c").arg("echo foobar").stdout(Stdio::piped()); | |
| assert_eq!(run_output(cmd), "foobar\n"); | |
| } | |
| } | |
| #[test] | |
| #[cfg_attr(windows, ignore)] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn set_current_dir_works() { | |
| // On many Unix platforms this will use the posix_spawn path. | |
| let mut cmd = shell_cmd(); | |
| cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped()); | |
| assert_eq!(run_output(cmd), "/\n"); | |
| // Also test the fork/exec path by setting a pre_exec function. | |
| #[cfg(unix)] | |
| { | |
| use crate::os::unix::process::CommandExt; | |
| let mut cmd = shell_cmd(); | |
| cmd.arg("-c").arg("pwd").current_dir("/").stdout(Stdio::piped()); | |
| unsafe { | |
| cmd.pre_exec(|| Ok(())); | |
| } | |
| assert_eq!(run_output(cmd), "/\n"); | |
| } | |
| } | |
| #[test] | |
| #[cfg_attr(windows, ignore)] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn stdin_works() { | |
| let mut p = shell_cmd() | |
| .arg("-c") | |
| .arg("read line; echo $line") | |
| .stdin(Stdio::piped()) | |
| .stdout(Stdio::piped()) | |
| .spawn() | |
| .unwrap(); | |
| p.stdin.as_mut().unwrap().write("foobar".as_bytes()).unwrap(); | |
| drop(p.stdin.take()); | |
| let mut out = String::new(); | |
| p.stdout.as_mut().unwrap().read_to_string(&mut out).unwrap(); | |
| assert!(p.wait().unwrap().success()); | |
| assert_eq!(out, "foobar\n"); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn child_stdout_read_buf() { | |
| let mut cmd = if cfg!(target_os = "windows") { | |
| let mut cmd = Command::new("cmd"); | |
| cmd.arg("/C").arg("echo abc"); | |
| cmd | |
| } else { | |
| let mut cmd = shell_cmd(); | |
| cmd.arg("-c").arg("echo abc"); | |
| cmd | |
| }; | |
| cmd.stdin(Stdio::null()); | |
| cmd.stdout(Stdio::piped()); | |
| let child = cmd.spawn().unwrap(); | |
| let mut stdout = child.stdout.unwrap(); | |
| let mut buf: [MaybeUninit<u8>; 128] = [MaybeUninit::uninit(); 128]; | |
| let mut buf = BorrowedBuf::from(buf.as_mut_slice()); | |
| stdout.read_buf(buf.unfilled()).unwrap(); | |
| // ChildStdout::read_buf should omit buffer initialization. | |
| if cfg!(target_os = "windows") { | |
| assert_eq!(buf.filled(), b"abc\r\n"); | |
| assert!(!buf.is_init()); | |
| } else { | |
| assert_eq!(buf.filled(), b"abc\n"); | |
| assert!(!buf.is_init()); | |
| }; | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_process_status() { | |
| let mut status = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 1"]).status().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("false").status().unwrap() | |
| }; | |
| assert!(status.code() == Some(1)); | |
| status = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 0"]).status().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("true").status().unwrap() | |
| }; | |
| assert!(status.success()); | |
| } | |
| #[test] | |
| fn test_process_output_fail_to_start() { | |
| match Command::new("/no-binary-by-this-name-should-exist").output() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound), | |
| Ok(..) => panic!(), | |
| } | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_process_output_output() { | |
| let Output { status, stdout, stderr } = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "echo hello"]).output().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("echo hello").output().unwrap() | |
| }; | |
| let output_str = str::from_utf8(&stdout).unwrap(); | |
| assert!(status.success()); | |
| assert_eq!(output_str.trim().to_string(), "hello"); | |
| assert_eq!(stderr, Vec::new()); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_process_output_error() { | |
| let Output { status, stdout, stderr } = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "mkdir ."]).output().unwrap() | |
| } else { | |
| Command::new("mkdir").arg("./").output().unwrap() | |
| }; | |
| assert!(status.code().is_some()); | |
| assert!(status.code() != Some(0)); | |
| assert_eq!(stdout, Vec::new()); | |
| assert!(!stderr.is_empty()); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_finish_once() { | |
| let mut prog = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("false").spawn().unwrap() | |
| }; | |
| assert!(prog.wait().unwrap().code() == Some(1)); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_finish_twice() { | |
| let mut prog = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "exit 1"]).spawn().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("false").spawn().unwrap() | |
| }; | |
| assert!(prog.wait().unwrap().code() == Some(1)); | |
| assert!(prog.wait().unwrap().code() == Some(1)); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_wait_with_output_once() { | |
| let prog = if cfg!(target_os = "windows") { | |
| Command::new("cmd").args(&["/C", "echo hello"]).stdout(Stdio::piped()).spawn().unwrap() | |
| } else { | |
| shell_cmd().arg("-c").arg("echo hello").stdout(Stdio::piped()).spawn().unwrap() | |
| }; | |
| let Output { status, stdout, stderr } = prog.wait_with_output().unwrap(); | |
| let output_str = str::from_utf8(&stdout).unwrap(); | |
| assert!(status.success()); | |
| assert_eq!(output_str.trim().to_string(), "hello"); | |
| assert_eq!(stderr, Vec::new()); | |
| } | |
| #[cfg(all(unix, not(target_os = "android")))] | |
| pub fn env_cmd() -> Command { | |
| Command::new("env") | |
| } | |
| #[cfg(target_os = "android")] | |
| pub fn env_cmd() -> Command { | |
| let mut cmd = Command::new("/system/bin/sh"); | |
| cmd.arg("-c").arg("set"); | |
| cmd | |
| } | |
| #[cfg(windows)] | |
| pub fn env_cmd() -> Command { | |
| let mut cmd = Command::new("cmd"); | |
| cmd.arg("/c").arg("set"); | |
| cmd | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_override_env() { | |
| use crate::env; | |
| // In some build environments (such as chrooted Nix builds), `env` can | |
| // only be found in the explicitly-provided PATH env variable, not in | |
| // default places such as /bin or /usr/bin. So we need to pass through | |
| // PATH to our sub-process. | |
| let mut cmd = env_cmd(); | |
| cmd.env_clear().env("RUN_TEST_NEW_ENV", "123"); | |
| if let Some(p) = env::var_os("PATH") { | |
| cmd.env("PATH", &p); | |
| } | |
| let result = cmd.output().unwrap(); | |
| let output = String::from_utf8_lossy(&result.stdout).to_string(); | |
| assert!( | |
| output.contains("RUN_TEST_NEW_ENV=123"), | |
| "didn't find RUN_TEST_NEW_ENV inside of:\n\n{output}", | |
| ); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_add_to_env() { | |
| let result = env_cmd().env("RUN_TEST_NEW_ENV", "123").output().unwrap(); | |
| let output = String::from_utf8_lossy(&result.stdout).to_string(); | |
| assert!( | |
| output.contains("RUN_TEST_NEW_ENV=123"), | |
| "didn't find RUN_TEST_NEW_ENV inside of:\n\n{output}" | |
| ); | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no shell available" | |
| )] | |
| fn test_capture_env_at_spawn() { | |
| use crate::env; | |
| let mut cmd = env_cmd(); | |
| cmd.env("RUN_TEST_NEW_ENV1", "123"); | |
| // This variable will not be present if the environment has already | |
| // been captured above. | |
| unsafe { | |
| env::set_var("RUN_TEST_NEW_ENV2", "456"); | |
| } | |
| let result = cmd.output().unwrap(); | |
| unsafe { | |
| env::remove_var("RUN_TEST_NEW_ENV2"); | |
| } | |
| let output = String::from_utf8_lossy(&result.stdout).to_string(); | |
| assert!( | |
| output.contains("RUN_TEST_NEW_ENV1=123"), | |
| "didn't find RUN_TEST_NEW_ENV1 inside of:\n\n{output}" | |
| ); | |
| assert!( | |
| output.contains("RUN_TEST_NEW_ENV2=456"), | |
| "didn't find RUN_TEST_NEW_ENV2 inside of:\n\n{output}" | |
| ); | |
| } | |
| // Regression tests for #30858. | |
| #[test] | |
| fn test_interior_nul_in_progname_is_error() { | |
| match Command::new("has-some-\0\0s-inside").spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| #[test] | |
| fn test_interior_nul_in_arg_is_error() { | |
| match known_command().arg("has-some-\0\0s-inside").spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| #[test] | |
| fn test_interior_nul_in_args_is_error() { | |
| match known_command().args(&["has-some-\0\0s-inside"]).spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| #[test] | |
| fn test_interior_nul_in_current_dir_is_error() { | |
| match known_command().current_dir("has-some-\0\0s-inside").spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| // Regression tests for #30862. | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no `env` cmd available" | |
| )] | |
| fn test_interior_nul_in_env_key_is_error() { | |
| match env_cmd().env("has-some-\0\0s-inside", "value").spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| #[test] | |
| #[cfg_attr( | |
| any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), | |
| ignore = "no `env` cmd available" | |
| )] | |
| fn test_interior_nul_in_env_value_is_error() { | |
| match env_cmd().env("key", "has-some-\0\0s-inside").spawn() { | |
| Err(e) => assert_eq!(e.kind(), ErrorKind::InvalidInput), | |
| Ok(_) => panic!(), | |
| } | |
| } | |
| #[test] | |
| fn test_command_implements_send_sync() { | |
| fn take_send_sync_type<T: Send + Sync>(_: T) {} | |
| take_send_sync_type(Command::new("")) | |
| } | |
| // Ensure that starting a process with no environment variables works on Windows. | |
| // This will fail if the environment block is ill-formed. | |
| #[test] | |
| #[cfg(windows)] | |
| fn env_empty() { | |
| let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn(); | |
| assert!(p.is_ok()); | |
| } | |
| #[test] | |
| #[cfg(not(windows))] | |
| #[cfg_attr(any(target_os = "emscripten", target_env = "sgx"), ignore)] | |
| fn debug_print() { | |
| const PIDFD: &'static str = | |
| if cfg!(target_os = "linux") { " create_pidfd: false,\n" } else { "" }; | |
| let mut command = Command::new("some-boring-name"); | |
| assert_eq!(format!("{command:?}"), format!(r#""some-boring-name""#)); | |
| assert_eq!( | |
| format!("{command:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "some-boring-name", | |
| args: [ | |
| "some-boring-name", | |
| ], | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| command.args(&["1", "2", "3"]); | |
| assert_eq!(format!("{command:?}"), format!(r#""some-boring-name" "1" "2" "3""#)); | |
| assert_eq!( | |
| format!("{command:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "some-boring-name", | |
| args: [ | |
| "some-boring-name", | |
| "1", | |
| "2", | |
| "3", | |
| ], | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| crate::os::unix::process::CommandExt::arg0(&mut command, "exciting-name"); | |
| assert_eq!( | |
| format!("{command:?}"), | |
| format!(r#"["some-boring-name"] "exciting-name" "1" "2" "3""#) | |
| ); | |
| assert_eq!( | |
| format!("{command:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "some-boring-name", | |
| args: [ | |
| "exciting-name", | |
| "1", | |
| "2", | |
| "3", | |
| ], | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| let mut command_with_env_and_cwd = Command::new("boring-name"); | |
| command_with_env_and_cwd.current_dir("/some/path").env("FOO", "bar"); | |
| assert_eq!( | |
| format!("{command_with_env_and_cwd:?}"), | |
| r#"cd "/some/path" && FOO="bar" "boring-name""# | |
| ); | |
| assert_eq!( | |
| format!("{command_with_env_and_cwd:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "boring-name", | |
| args: [ | |
| "boring-name", | |
| ], | |
| env: CommandEnv {{ | |
| clear: false, | |
| vars: {{ | |
| "FOO": Some( | |
| "bar", | |
| ), | |
| }}, | |
| }}, | |
| cwd: Some( | |
| "/some/path", | |
| ), | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| let mut command_with_removed_env = Command::new("boring-name"); | |
| command_with_removed_env.env_remove("FOO").env_remove("BAR"); | |
| assert_eq!(format!("{command_with_removed_env:?}"), r#"env -u BAR -u FOO "boring-name""#); | |
| assert_eq!( | |
| format!("{command_with_removed_env:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "boring-name", | |
| args: [ | |
| "boring-name", | |
| ], | |
| env: CommandEnv {{ | |
| clear: false, | |
| vars: {{ | |
| "BAR": None, | |
| "FOO": None, | |
| }}, | |
| }}, | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| let mut command_with_cleared_env = Command::new("boring-name"); | |
| command_with_cleared_env.env_clear().env("BAR", "val").env_remove("FOO"); | |
| assert_eq!(format!("{command_with_cleared_env:?}"), r#"env -i BAR="val" "boring-name""#); | |
| assert_eq!( | |
| format!("{command_with_cleared_env:#?}"), | |
| format!( | |
| r#"Command {{ | |
| program: "boring-name", | |
| args: [ | |
| "boring-name", | |
| ], | |
| env: CommandEnv {{ | |
| clear: true, | |
| vars: {{ | |
| "BAR": Some( | |
| "val", | |
| ), | |
| }}, | |
| }}, | |
| {PIDFD}}}"# | |
| ) | |
| ); | |
| } | |
| // See issue #91991 | |
| #[test] | |
| #[cfg(windows)] | |
| fn run_bat_script() { | |
| let tempdir = crate::test_helpers::tmpdir(); | |
| let script_path = tempdir.join("hello.cmd"); | |
| crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap(); | |
| let output = Command::new(&script_path) | |
| .arg("fellow Rustaceans") | |
| .stdout(crate::process::Stdio::piped()) | |
| .spawn() | |
| .unwrap() | |
| .wait_with_output() | |
| .unwrap(); | |
| assert!(output.status.success()); | |
| assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!"); | |
| } | |
| // See issue #95178 | |
| #[test] | |
| #[cfg(windows)] | |
| fn run_canonical_bat_script() { | |
| let tempdir = crate::test_helpers::tmpdir(); | |
| let script_path = tempdir.join("hello.cmd"); | |
| crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap(); | |
| // Try using a canonical path | |
| let output = Command::new(&script_path.canonicalize().unwrap()) | |
| .arg("fellow Rustaceans") | |
| .stdout(crate::process::Stdio::piped()) | |
| .spawn() | |
| .unwrap() | |
| .wait_with_output() | |
| .unwrap(); | |
| assert!(output.status.success()); | |
| assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!"); | |
| } | |
| #[test] | |
| fn terminate_exited_process() { | |
| let mut cmd = if cfg!(target_os = "android") { | |
| let mut p = shell_cmd(); | |
| p.args(&["-c", "true"]); | |
| p | |
| } else { | |
| known_command() | |
| }; | |
| let mut p = cmd.stdout(Stdio::null()).spawn().unwrap(); | |
| p.wait().unwrap(); | |
| assert!(p.kill().is_ok()); | |
| assert!(p.kill().is_ok()); | |
| } |