diff options
Diffstat (limited to 'tests/ui/process')
22 files changed, 1149 insertions, 0 deletions
diff --git a/tests/ui/process/core-run-destroy.rs b/tests/ui/process/core-run-destroy.rs new file mode 100644 index 000000000..d0e97bf01 --- /dev/null +++ b/tests/ui/process/core-run-destroy.rs @@ -0,0 +1,88 @@ +// run-pass + +#![allow(unused_must_use)] +#![allow(stable_features)] +#![allow(deprecated)] +#![allow(unused_imports)] +// compile-flags:--test +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-vxworks no 'cat' and 'sleep' +// ignore-fuchsia no 'cat' + +// N.B., these tests kill child processes. Valgrind sees these children as leaking +// memory, which makes for some *confusing* logs. That's why these are here +// instead of in std. + +#![feature(rustc_private, duration)] + +extern crate libc; + +use std::process::{self, Command, Child, Output, Stdio}; +use std::str; +use std::sync::mpsc::channel; +use std::thread; +use std::time::Duration; + +macro_rules! t { + ($e:expr) => (match $e { Ok(e) => e, Err(e) => panic!("error: {}", e) }) +} + +#[test] +fn test_destroy_once() { + let mut p = sleeper(); + t!(p.kill()); +} + +#[cfg(unix)] +pub fn sleeper() -> Child { + t!(Command::new("sleep").arg("1000").spawn()) +} +#[cfg(windows)] +pub fn sleeper() -> Child { + // There's a `timeout` command on windows, but it doesn't like having + // its output piped, so instead just ping ourselves a few times with + // gaps in between so we're sure this process is alive for awhile + t!(Command::new("ping").arg("127.0.0.1").arg("-n").arg("1000").spawn()) +} + +#[test] +fn test_destroy_twice() { + let mut p = sleeper(); + t!(p.kill()); // this shouldn't crash... + let _ = p.kill(); // ...and nor should this (and nor should the destructor) +} + +#[test] +fn test_destroy_actually_kills() { + let cmd = if cfg!(windows) { + "cmd" + } else if cfg!(target_os = "android") { + "/system/bin/cat" + } else { + "cat" + }; + + // this process will stay alive indefinitely trying to read from stdin + let mut p = t!(Command::new(cmd) + .stdin(Stdio::piped()) + .spawn()); + + t!(p.kill()); + + // Don't let this test time out, this should be quick + let (tx, rx) = channel(); + thread::spawn(move|| { + thread::sleep_ms(1000); + if rx.try_recv().is_err() { + process::exit(1); + } + }); + let code = t!(p.wait()).code(); + if cfg!(windows) { + assert!(code.is_some()); + } else { + assert!(code.is_none()); + } + tx.send(()); +} diff --git a/tests/ui/process/fds-are-cloexec.rs b/tests/ui/process/fds-are-cloexec.rs new file mode 100644 index 000000000..4482b7032 --- /dev/null +++ b/tests/ui/process/fds-are-cloexec.rs @@ -0,0 +1,82 @@ +// run-pass +// ignore-windows +// ignore-android +// ignore-emscripten no processes +// ignore-haiku +// ignore-sgx no processes + +#![feature(rustc_private)] + +extern crate libc; + +use std::env; +use std::fs::File; +use std::io; +use std::net::{TcpListener, TcpStream, UdpSocket}; +use std::os::unix::prelude::*; +use std::process::{Command, Stdio}; +use std::thread; + +fn main() { + let args = env::args().collect::<Vec<_>>(); + if args.len() == 1 { + parent() + } else { + child(&args) + } +} + +fn parent() { + let file = File::open(env::current_exe().unwrap()).unwrap(); + let tcp1 = TcpListener::bind("127.0.0.1:0").unwrap(); + let tcp2 = tcp1.try_clone().unwrap(); + let addr = tcp1.local_addr().unwrap(); + let t = thread::spawn(move || TcpStream::connect(addr).unwrap()); + let tcp3 = tcp1.accept().unwrap().0; + let tcp4 = t.join().unwrap(); + let tcp5 = tcp3.try_clone().unwrap(); + let tcp6 = tcp4.try_clone().unwrap(); + let udp1 = UdpSocket::bind("127.0.0.1:0").unwrap(); + let udp2 = udp1.try_clone().unwrap(); + + let mut child = Command::new(env::args().next().unwrap()) + .arg("100") + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn().unwrap(); + let pipe1 = child.stdin.take().unwrap(); + let pipe2 = child.stdout.take().unwrap(); + let pipe3 = child.stderr.take().unwrap(); + + + let status = Command::new(env::args().next().unwrap()) + .arg(file.as_raw_fd().to_string()) + .arg(tcp1.as_raw_fd().to_string()) + .arg(tcp2.as_raw_fd().to_string()) + .arg(tcp3.as_raw_fd().to_string()) + .arg(tcp4.as_raw_fd().to_string()) + .arg(tcp5.as_raw_fd().to_string()) + .arg(tcp6.as_raw_fd().to_string()) + .arg(udp1.as_raw_fd().to_string()) + .arg(udp2.as_raw_fd().to_string()) + .arg(pipe1.as_raw_fd().to_string()) + .arg(pipe2.as_raw_fd().to_string()) + .arg(pipe3.as_raw_fd().to_string()) + .status() + .unwrap(); + assert!(status.success()); + child.wait().unwrap(); +} + +fn child(args: &[String]) { + let mut b = [0u8; 2]; + for arg in &args[1..] { + let fd: libc::c_int = arg.parse().unwrap(); + unsafe { + assert_eq!(libc::read(fd, b.as_mut_ptr() as *mut _, 2), -1); + assert_eq!(io::Error::last_os_error().raw_os_error(), + Some(libc::EBADF)); + } + } +} diff --git a/tests/ui/process/issue-13304.rs b/tests/ui/process/issue-13304.rs new file mode 100644 index 000000000..b10f6d572 --- /dev/null +++ b/tests/ui/process/issue-13304.rs @@ -0,0 +1,39 @@ +// run-pass +#![allow(unused_mut)] +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::io::prelude::*; +use std::io; +use std::process::{Command, Stdio}; +use std::str; + +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() > 1 && args[1] == "child" { + child(); + } else { + parent(); + } +} + +fn parent() { + let args: Vec<String> = env::args().collect(); + let mut p = Command::new(&args[0]).arg("child") + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .spawn().unwrap(); + p.stdin.as_mut().unwrap().write_all(b"test1\ntest2\ntest3").unwrap(); + let out = p.wait_with_output().unwrap(); + assert!(out.status.success()); + let s = str::from_utf8(&out.stdout).unwrap(); + assert_eq!(s, "test1\ntest2\ntest3\n"); +} + +fn child() { + let mut stdin = io::stdin(); + for line in stdin.lock().lines() { + println!("{}", line.unwrap()); + } +} diff --git a/tests/ui/process/issue-14456.rs b/tests/ui/process/issue-14456.rs new file mode 100644 index 000000000..52a56eb77 --- /dev/null +++ b/tests/ui/process/issue-14456.rs @@ -0,0 +1,37 @@ +// run-pass +#![allow(unused_mut)] +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::io::prelude::*; +use std::io; +use std::process::{Command, Stdio}; + +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() > 1 && args[1] == "child" { + return child() + } + + test(); +} + +fn child() { + writeln!(&mut io::stdout(), "foo").unwrap(); + writeln!(&mut io::stderr(), "bar").unwrap(); + let mut stdin = io::stdin(); + let mut s = String::new(); + stdin.lock().read_line(&mut s).unwrap(); + assert_eq!(s.len(), 0); +} + +fn test() { + let args: Vec<String> = env::args().collect(); + let mut p = Command::new(&args[0]).arg("child") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn().unwrap(); + assert!(p.wait().unwrap().success()); +} diff --git a/tests/ui/process/issue-14940.rs b/tests/ui/process/issue-14940.rs new file mode 100644 index 000000000..98a4af0c4 --- /dev/null +++ b/tests/ui/process/issue-14940.rs @@ -0,0 +1,19 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::process::Command; +use std::io::{self, Write}; + +fn main() { + let mut args = env::args(); + if args.len() > 1 { + let mut out = io::stdout(); + out.write(&['a' as u8; 128 * 1024]).unwrap(); + } else { + let out = Command::new(&args.next().unwrap()).arg("child").output(); + let out = out.unwrap(); + assert!(out.status.success()); + } +} diff --git a/tests/ui/process/issue-16272.rs b/tests/ui/process/issue-16272.rs new file mode 100644 index 000000000..5cf3fd949 --- /dev/null +++ b/tests/ui/process/issue-16272.rs @@ -0,0 +1,23 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::process::Command; +use std::env; + +fn main() { + let len = env::args().len(); + + if len == 1 { + test(); + } else { + assert_eq!(len, 3); + } +} + +fn test() { + let status = Command::new(&env::current_exe().unwrap()) + .arg("foo").arg("") + .status().unwrap(); + assert!(status.success()); +} diff --git a/tests/ui/process/issue-20091.rs b/tests/ui/process/issue-20091.rs new file mode 100644 index 000000000..86cc79d6b --- /dev/null +++ b/tests/ui/process/issue-20091.rs @@ -0,0 +1,24 @@ +// run-pass +#![allow(stable_features)] + +// ignore-emscripten no processes +// ignore-sgx no processes + +#![feature(os)] + +#[cfg(unix)] +fn main() { + use std::process::Command; + use std::env; + use std::os::unix::prelude::*; + use std::ffi::OsStr; + + if env::args().len() == 1 { + assert!(Command::new(&env::current_exe().unwrap()) + .arg(<OsStr as OsStrExt>::from_bytes(b"\xff")) + .status().unwrap().success()) + } +} + +#[cfg(windows)] +fn main() {} diff --git a/tests/ui/process/multi-panic.rs b/tests/ui/process/multi-panic.rs new file mode 100644 index 000000000..a1887c218 --- /dev/null +++ b/tests/ui/process/multi-panic.rs @@ -0,0 +1,38 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes +// needs-unwind + +fn check_for_no_backtrace(test: std::process::Output) { + assert!(!test.status.success()); + let err = String::from_utf8_lossy(&test.stderr); + let mut it = err.lines(); + + assert_eq!(it.next().map(|l| l.starts_with("thread '<unnamed>' panicked at")), Some(true)); + assert_eq!(it.next(), Some("note: run with `RUST_BACKTRACE=1` \ + environment variable to display a backtrace")); + assert_eq!(it.next().map(|l| l.starts_with("thread 'main' panicked at")), Some(true)); + assert_eq!(it.next(), None); +} + +fn main() { + let args: Vec<String> = std::env::args().collect(); + if args.len() > 1 && args[1] == "run_test" { + let _ = std::thread::spawn(|| { + panic!(); + }).join(); + + panic!(); + } else { + let test = std::process::Command::new(&args[0]).arg("run_test") + .env_remove("RUST_BACKTRACE") + .output() + .unwrap(); + check_for_no_backtrace(test); + let test = std::process::Command::new(&args[0]).arg("run_test") + .env("RUST_BACKTRACE","0") + .output() + .unwrap(); + check_for_no_backtrace(test); + } +} diff --git a/tests/ui/process/no-stdio.rs b/tests/ui/process/no-stdio.rs new file mode 100644 index 000000000..24985386a --- /dev/null +++ b/tests/ui/process/no-stdio.rs @@ -0,0 +1,139 @@ +// run-pass +// ignore-android +// ignore-emscripten no processes +// ignore-sgx no processes +// revisions: mir thir +// [thir]compile-flags: -Zthir-unsafeck + +#![feature(rustc_private)] + +extern crate libc; + +use std::process::{Command, Stdio}; +use std::env; +use std::io::{self, Read, Write}; + +#[cfg(unix)] +unsafe fn without_stdio<R, F: FnOnce() -> R>(f: F) -> R { + let doit = |a| { + let r = libc::dup(a); + assert!(r >= 0); + return r + }; + let a = doit(0); + let b = doit(1); + let c = doit(2); + + assert!(libc::close(0) >= 0); + assert!(libc::close(1) >= 0); + assert!(libc::close(2) >= 0); + + let r = f(); + + assert!(libc::dup2(a, 0) >= 0); + assert!(libc::dup2(b, 1) >= 0); + assert!(libc::dup2(c, 2) >= 0); + + return r +} + +#[cfg(unix)] +fn assert_fd_is_valid(fd: libc::c_int) { + if unsafe { libc::fcntl(fd, libc::F_GETFD) == -1 } { + panic!("file descriptor {} is not valid: {}", fd, io::Error::last_os_error()); + } +} + +#[cfg(windows)] +fn assert_fd_is_valid(_fd: libc::c_int) {} + +#[cfg(windows)] +unsafe fn without_stdio<R, F: FnOnce() -> R>(f: F) -> R { + type DWORD = u32; + type HANDLE = *mut u8; + type BOOL = i32; + + const STD_INPUT_HANDLE: DWORD = -10i32 as DWORD; + const STD_OUTPUT_HANDLE: DWORD = -11i32 as DWORD; + const STD_ERROR_HANDLE: DWORD = -12i32 as DWORD; + const INVALID_HANDLE_VALUE: HANDLE = !0 as HANDLE; + + extern "system" { + fn GetStdHandle(which: DWORD) -> HANDLE; + fn SetStdHandle(which: DWORD, handle: HANDLE) -> BOOL; + } + + let doit = |id| { + let handle = GetStdHandle(id); + assert!(handle != INVALID_HANDLE_VALUE); + assert!(SetStdHandle(id, INVALID_HANDLE_VALUE) != 0); + return handle + }; + + let a = doit(STD_INPUT_HANDLE); + let b = doit(STD_OUTPUT_HANDLE); + let c = doit(STD_ERROR_HANDLE); + + let r = f(); + + let doit = |id, handle| { + assert!(SetStdHandle(id, handle) != 0); + }; + doit(STD_INPUT_HANDLE, a); + doit(STD_OUTPUT_HANDLE, b); + doit(STD_ERROR_HANDLE, c); + + return r +} + +fn main() { + if env::args().len() > 1 { + // Writing to stdout & stderr should not panic. + println!("test"); + assert!(io::stdout().write(b"test\n").is_ok()); + assert!(io::stderr().write(b"test\n").is_ok()); + + // Stdin should be at EOF. + assert_eq!(io::stdin().read(&mut [0; 10]).unwrap(), 0); + + // Standard file descriptors should be valid on UNIX: + assert_fd_is_valid(0); + assert_fd_is_valid(1); + assert_fd_is_valid(2); + return + } + + // First, make sure reads/writes without stdio work if stdio itself is + // missing. + let (a, b, c) = unsafe { + without_stdio(|| { + let a = io::stdout().write(b"test\n"); + let b = io::stderr().write(b"test\n"); + let c = io::stdin().read(&mut [0; 10]); + + (a, b, c) + }) + }; + + assert_eq!(a.unwrap(), 5); + assert_eq!(b.unwrap(), 5); + assert_eq!(c.unwrap(), 0); + + // Second, spawn a child and do some work with "null" descriptors to make + // sure it's ok + let me = env::current_exe().unwrap(); + let status = Command::new(&me) + .arg("next") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status().unwrap(); + assert!(status.success(), "{} isn't a success", status); + + // Finally, close everything then spawn a child to make sure everything is + // *still* ok. + let status = unsafe { + without_stdio(|| Command::new(&me).arg("next").status()) + }.unwrap(); + assert!(status.success(), "{} isn't a success", status); +} diff --git a/tests/ui/process/nofile-limit.rs b/tests/ui/process/nofile-limit.rs new file mode 100644 index 000000000..3ddf8d6ef --- /dev/null +++ b/tests/ui/process/nofile-limit.rs @@ -0,0 +1,46 @@ +// Check that statically linked binary executes successfully +// with RLIMIT_NOFILE resource lowered to zero. Regression +// test for issue #96621. +// +// run-pass +// dont-check-compiler-stderr +// only-linux +// no-prefer-dynamic +// compile-flags: -Ctarget-feature=+crt-static -Crpath=no -Crelocation-model=static +#![feature(exit_status_error)] +#![feature(rustc_private)] +extern crate libc; + +use std::os::unix::process::CommandExt; +use std::process::Command; + +fn main() { + let mut args = std::env::args(); + let this = args.next().unwrap(); + match args.next().as_deref() { + None => { + let mut cmd = Command::new(this); + cmd.arg("Ok!"); + unsafe { + cmd.pre_exec(|| { + let rlim = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } + }) + }; + let output = cmd.output().unwrap(); + println!("{:?}", output); + output.status.exit_ok().unwrap(); + assert!(output.stdout.starts_with(b"Ok!")); + } + Some(word) => { + println!("{}", word); + } + } +} diff --git a/tests/ui/process/process-envs.rs b/tests/ui/process/process-envs.rs new file mode 100644 index 000000000..f3a469791 --- /dev/null +++ b/tests/ui/process/process-envs.rs @@ -0,0 +1,54 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-vxworks no 'env' +// ignore-fuchsia no 'env' + +use std::process::Command; +use std::env; +use std::collections::HashMap; + +#[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 +} + +fn main() { + // save original environment + let old_env = env::var_os("RUN_TEST_NEW_ENV"); + + env::set_var("RUN_TEST_NEW_ENV", "123"); + + // create filtered environment vector + let filtered_env : HashMap<String, String> = + env::vars().filter(|&(ref k, _)| k == "PATH").collect(); + + let mut cmd = env_cmd(); + cmd.env_clear(); + cmd.envs(&filtered_env); + + // restore original environment + match old_env { + None => env::remove_var("RUN_TEST_NEW_ENV"), + Some(val) => env::set_var("RUN_TEST_NEW_ENV", &val) + } + + let result = cmd.output().unwrap(); + let output = String::from_utf8_lossy(&result.stdout); + + assert!(!output.contains("RUN_TEST_NEW_ENV"), + "found RUN_TEST_NEW_ENV inside of:\n\n{}", output); +} diff --git a/tests/ui/process/process-exit.rs b/tests/ui/process/process-exit.rs new file mode 100644 index 000000000..d193e073e --- /dev/null +++ b/tests/ui/process/process-exit.rs @@ -0,0 +1,26 @@ +// run-pass +#![allow(unused_imports)] +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::process::{self, Command, Stdio}; + +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() > 1 && args[1] == "child" { + child(); + } else { + parent(); + } +} + +fn parent() { + let args: Vec<String> = env::args().collect(); + let status = Command::new(&args[0]).arg("child").status().unwrap(); + assert_eq!(status.code(), Some(2)); +} + +fn child() -> i32 { + process::exit(2); +} diff --git a/tests/ui/process/process-panic-after-fork.rs b/tests/ui/process/process-panic-after-fork.rs new file mode 100644 index 000000000..da2683121 --- /dev/null +++ b/tests/ui/process/process-panic-after-fork.rs @@ -0,0 +1,197 @@ +// run-pass +// no-prefer-dynamic +// ignore-wasm32-bare no libc +// ignore-windows +// ignore-sgx no libc +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-fuchsia no fork + +#![feature(rustc_private)] +#![feature(never_type)] +#![feature(panic_always_abort)] + +extern crate libc; + +use std::alloc::{GlobalAlloc, Layout}; +use std::fmt; +use std::panic::{self, panic_any}; +use std::os::unix::process::{CommandExt, ExitStatusExt}; +use std::process::{self, Command, ExitStatus}; +use std::sync::atomic::{AtomicU32, Ordering}; + +use libc::c_int; + +/// This stunt allocator allows us to spot heap allocations in the child. +struct PidChecking<A> { + parent: A, + require_pid: AtomicU32, +} + +#[global_allocator] +static ALLOCATOR: PidChecking<std::alloc::System> = PidChecking { + parent: std::alloc::System, + require_pid: AtomicU32::new(0), +}; + +impl<A> PidChecking<A> { + fn engage(&self) { + let parent_pid = process::id(); + eprintln!("engaging allocator trap, parent pid={}", parent_pid); + self.require_pid.store(parent_pid, Ordering::Release); + } + fn check(&self) { + let require_pid = self.require_pid.load(Ordering::Acquire); + if require_pid != 0 { + let actual_pid = process::id(); + if require_pid != actual_pid { + unsafe { + libc::raise(libc::SIGUSR1); + } + } + } + } +} + +unsafe impl<A:GlobalAlloc> GlobalAlloc for PidChecking<A> { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.check(); + self.parent.alloc(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.check(); + self.parent.dealloc(ptr, layout) + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.check(); + self.parent.alloc_zeroed(layout) + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + self.check(); + self.parent.realloc(ptr, layout, new_size) + } +} + +fn expect_aborted(status: ExitStatus) { + dbg!(status); + let signal = status.signal().expect("expected child process to die of signal"); + + #[cfg(not(target_os = "android"))] + assert!(signal == libc::SIGABRT || signal == libc::SIGILL || signal == libc::SIGTRAP); + + #[cfg(target_os = "android")] + { + assert!(signal == libc::SIGABRT || signal == libc::SIGSEGV); + + if signal == libc::SIGSEGV { + // Pre-KitKat versions of Android signal an abort() with SIGSEGV at address 0xdeadbaad + // See e.g. https://groups.google.com/g/android-ndk/c/laW1CJc7Icc + // + // This behavior was changed in KitKat to send a standard SIGABRT signal. + // See: https://r.android.com/60341 + // + // Additional checks performed: + // 1. Find last tombstone (similar to coredump but in text format) from the + // same executable (path) as we are (must be because of usage of fork): + // This ensures that we look into the correct tombstone. + // 2. Cause of crash is a SIGSEGV with address 0xdeadbaad. + // 3. libc::abort call is in one of top two functions on callstack. + // The last two steps distinguish between a normal SIGSEGV and one caused + // by libc::abort. + + let this_exe = std::env::current_exe().unwrap().into_os_string().into_string().unwrap(); + let exe_string = format!(">>> {this_exe} <<<"); + let tombstone = (0..100) + .map(|n| format!("/data/tombstones/tombstone_{n:02}")) + .filter(|f| std::path::Path::new(&f).exists()) + .map(|f| std::fs::read_to_string(&f).expect("Cannot read tombstone file")) + .filter(|f| f.contains(&exe_string)) + .last() + .expect("no tombstone found"); + + println!("Content of tombstone:\n{tombstone}"); + + assert!(tombstone + .contains("signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad")); + let abort_on_top = tombstone + .lines() + .skip_while(|l| !l.contains("backtrace:")) + .skip(1) + .take_while(|l| l.starts_with(" #")) + .take(2) + .any(|f| f.contains("/system/lib/libc.so (abort")); + assert!(abort_on_top); + } + } +} + +fn main() { + ALLOCATOR.engage(); + + fn run(do_panic: &dyn Fn()) -> ExitStatus { + let child = unsafe { libc::fork() }; + assert!(child >= 0); + if child == 0 { + panic::always_abort(); + do_panic(); + process::exit(0); + } + let mut status: c_int = 0; + let got = unsafe { libc::waitpid(child, &mut status, 0) }; + assert_eq!(got, child); + let status = ExitStatus::from_raw(status.into()); + status + } + + fn one(do_panic: &dyn Fn()) { + let status = run(do_panic); + expect_aborted(status); + } + + one(&|| panic!()); + one(&|| panic!("some message")); + one(&|| panic!("message with argument: {}", 42)); + + #[derive(Debug)] + struct Wotsit { } + one(&|| panic_any(Wotsit { })); + + let mut c = Command::new("echo"); + unsafe { + c.pre_exec(|| panic!("{}", "crash now!")); + } + let st = c.status().expect("failed to get command status"); + expect_aborted(st); + + struct DisplayWithHeap; + impl fmt::Display for DisplayWithHeap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let s = vec![0; 100]; + let s = std::hint::black_box(s); + write!(f, "{:?}", s) + } + } + + // Some panics in the stdlib that we want not to allocate, as + // otherwise these facilities become impossible to use in the + // child after fork, which is really quite awkward. + + one(&|| { None::<DisplayWithHeap>.unwrap(); }); + one(&|| { None::<DisplayWithHeap>.expect("unwrapped a none"); }); + one(&|| { std::str::from_utf8(b"\xff").unwrap(); }); + one(&|| { + let x = [0, 1, 2, 3]; + let y = x[std::hint::black_box(4)]; + let _z = std::hint::black_box(y); + }); + + // Finally, check that our stunt allocator can actually catch an allocation after fork. + // ie, that our test is effective. + + let status = run(&|| panic!("allocating to display... {}", DisplayWithHeap)); + dbg!(status); + assert_eq!(status.signal(), Some(libc::SIGUSR1)); +} diff --git a/tests/ui/process/process-remove-from-env.rs b/tests/ui/process/process-remove-from-env.rs new file mode 100644 index 000000000..ad027d685 --- /dev/null +++ b/tests/ui/process/process-remove-from-env.rs @@ -0,0 +1,48 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-vxworks no 'env' +// ignore-fuchsia no 'env' + +use std::process::Command; +use std::env; + +#[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 +} + +fn main() { + // save original environment + let old_env = env::var_os("RUN_TEST_NEW_ENV"); + + env::set_var("RUN_TEST_NEW_ENV", "123"); + + let mut cmd = env_cmd(); + cmd.env_remove("RUN_TEST_NEW_ENV"); + + // restore original environment + match old_env { + None => env::remove_var("RUN_TEST_NEW_ENV"), + Some(val) => env::set_var("RUN_TEST_NEW_ENV", &val) + } + + let result = cmd.output().unwrap(); + let output = String::from_utf8_lossy(&result.stdout); + + assert!(!output.contains("RUN_TEST_NEW_ENV"), + "found RUN_TEST_NEW_ENV inside of:\n\n{}", output); +} diff --git a/tests/ui/process/process-sigpipe.rs b/tests/ui/process/process-sigpipe.rs new file mode 100644 index 000000000..107eba45d --- /dev/null +++ b/tests/ui/process/process-sigpipe.rs @@ -0,0 +1,37 @@ +// run-pass +#![allow(unused_imports)] +#![allow(deprecated)] + +// ignore-android since the dynamic linker sets a SIGPIPE handler (to do +// a crash report) so inheritance is moot on the entire platform + +// libstd ignores SIGPIPE, and other libraries may set signal masks. +// Make sure that these behaviors don't get inherited to children +// spawned via std::process, since they're needed for traditional UNIX +// filter behavior. This test checks that `yes | head` terminates +// (instead of running forever), and that it does not print an error +// message about a broken pipe. + +// ignore-emscripten no threads support +// ignore-vxworks no 'sh' +// ignore-fuchsia no 'sh' + +use std::process; +use std::thread; + +#[cfg(unix)] +fn main() { + // Just in case `yes` doesn't check for EPIPE... + thread::spawn(|| { + thread::sleep_ms(5000); + process::exit(1); + }); + let output = process::Command::new("sh").arg("-c").arg("yes | head").output().unwrap(); + assert!(output.status.success()); + assert!(output.stderr.len() == 0); +} + +#[cfg(not(unix))] +fn main() { + // Not worried about signal masks on other platforms +} diff --git a/tests/ui/process/process-spawn-nonexistent.rs b/tests/ui/process/process-spawn-nonexistent.rs new file mode 100644 index 000000000..9dd608986 --- /dev/null +++ b/tests/ui/process/process-spawn-nonexistent.rs @@ -0,0 +1,17 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-fuchsia ErrorKind not translated + +use std::io::ErrorKind; +use std::process::Command; + +fn main() { + let result = Command::new("nonexistent").spawn().unwrap_err().kind(); + + assert!(matches!( + result, + // Under WSL with appendWindowsPath=true, this fails with PermissionDenied + ErrorKind::NotFound | ErrorKind::PermissionDenied + )); +} diff --git a/tests/ui/process/process-spawn-with-unicode-params.rs b/tests/ui/process/process-spawn-with-unicode-params.rs new file mode 100644 index 000000000..16dba6292 --- /dev/null +++ b/tests/ui/process/process-spawn-with-unicode-params.rs @@ -0,0 +1,77 @@ +// run-pass +// no-prefer-dynamic + +// The test copies itself into a subdirectory with a non-ASCII name and then +// runs it as a child process within the subdirectory. The parent process +// also adds an environment variable and an argument, both containing +// non-ASCII characters. The child process ensures all the strings are +// intact. + +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-fuchsia Filesystem manipulation privileged + +use std::io::prelude::*; +use std::io; +use std::fs; +use std::process::Command; +use std::env; +use std::path::Path; + +fn main() { + let my_args = env::args().collect::<Vec<_>>(); + let my_cwd = env::current_dir().unwrap(); + let my_env = env::vars().collect::<Vec<_>>(); + let my_path = env::current_exe().unwrap(); + let my_dir = my_path.parent().unwrap(); + let my_ext = my_path.extension().and_then(|s| s.to_str()).unwrap_or(""); + + // some non-ASCII characters + let blah = "\u{3c0}\u{42f}\u{97f3}\u{e6}\u{221e}"; + + let child_name = "child"; + let child_dir = format!("process-spawn-with-unicode-params-{}", blah); + + // parameters sent to child / expected to be received from parent + let arg = blah; + let cwd = my_dir.join(&child_dir); + let env = ("RUST_TEST_PROC_SPAWN_UNICODE".to_string(), blah.to_string()); + + // am I the parent or the child? + if my_args.len() == 1 { // parent + + let child_filestem = Path::new(child_name); + let child_filename = child_filestem.with_extension(my_ext); + let child_path = cwd.join(&child_filename); + + // make a separate directory for the child + let _ = fs::create_dir(&cwd); + fs::copy(&my_path, &child_path).unwrap(); + + // run child + let p = Command::new(&child_path) + .arg(arg) + .current_dir(&cwd) + .env(&env.0, &env.1) + .spawn().unwrap().wait_with_output().unwrap(); + + // display the output + io::stdout().write_all(&p.stdout).unwrap(); + io::stderr().write_all(&p.stderr).unwrap(); + + // make sure the child succeeded + assert!(p.status.success()); + + } else { // child + + // check working directory (don't try to compare with `cwd` here!) + assert!(my_cwd.ends_with(&child_dir)); + + // check arguments + assert_eq!(&*my_args[1], arg); + + // check environment variable + assert!(my_env.contains(&env)); + + }; +} diff --git a/tests/ui/process/process-status-inherits-stdin.rs b/tests/ui/process/process-status-inherits-stdin.rs new file mode 100644 index 000000000..7719dd9ad --- /dev/null +++ b/tests/ui/process/process-status-inherits-stdin.rs @@ -0,0 +1,35 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::io; +use std::io::Write; +use std::process::{Command, Stdio}; + +fn main() { + let mut args = env::args(); + let me = args.next().unwrap(); + let arg = args.next(); + match arg.as_ref().map(|s| &s[..]) { + None => { + let mut s = Command::new(&me) + .arg("a1") + .stdin(Stdio::piped()) + .spawn() + .unwrap(); + s.stdin.take().unwrap().write_all(b"foo\n").unwrap(); + let s = s.wait().unwrap(); + assert!(s.success()); + } + Some("a1") => { + let s = Command::new(&me).arg("a2").status().unwrap(); + assert!(s.success()); + } + Some(..) => { + let mut s = String::new(); + io::stdin().read_line(&mut s).unwrap(); + assert_eq!(s, "foo\n"); + } + } +} diff --git a/tests/ui/process/signal-exit-status.rs b/tests/ui/process/signal-exit-status.rs new file mode 100644 index 000000000..9519ed7b4 --- /dev/null +++ b/tests/ui/process/signal-exit-status.rs @@ -0,0 +1,19 @@ +// run-pass +// ignore-emscripten no processes +// ignore-sgx no processes +// ignore-windows +// ignore-fuchsia code returned as ZX_TASK_RETCODE_EXCEPTION_KILL, FIXME (#58590) + +use std::env; +use std::process::Command; + +pub fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() >= 2 && args[1] == "signal" { + // Raise a segfault. + unsafe { *(1 as *mut isize) = 0; } + } else { + let status = Command::new(&args[0]).arg("signal").status().unwrap(); + assert!(status.code().is_none()); + } +} diff --git a/tests/ui/process/sigpipe-should-be-ignored.rs b/tests/ui/process/sigpipe-should-be-ignored.rs new file mode 100644 index 000000000..144eeca23 --- /dev/null +++ b/tests/ui/process/sigpipe-should-be-ignored.rs @@ -0,0 +1,33 @@ +// run-pass + +#![allow(unused_must_use)] +// Be sure that when a SIGPIPE would have been received that the entire process +// doesn't die in a ball of fire, but rather it's gracefully handled. + +// ignore-emscripten no processes +// ignore-sgx no processes + +use std::env; +use std::io::prelude::*; +use std::io; +use std::process::{Command, Stdio}; + +fn test() { + let _ = io::stdin().read_line(&mut String::new()); + io::stdout().write(&[1]); + assert!(io::stdout().flush().is_err()); +} + +fn main() { + let args: Vec<String> = env::args().collect(); + if args.len() > 1 && args[1] == "test" { + return test(); + } + + let mut p = Command::new(&args[0]) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .arg("test").spawn().unwrap(); + drop(p.stdout.take()); + assert!(p.wait().unwrap().success()); +} diff --git a/tests/ui/process/tls-exit-status.rs b/tests/ui/process/tls-exit-status.rs new file mode 100644 index 000000000..6296e5042 --- /dev/null +++ b/tests/ui/process/tls-exit-status.rs @@ -0,0 +1,11 @@ +// run-fail +// error-pattern:nonzero +// exec-env:RUST_NEWRT=1 +// ignore-emscripten no processes + +use std::env; + +fn main() { + env::args(); + panic!("please have a nonzero exit status"); +} diff --git a/tests/ui/process/try-wait.rs b/tests/ui/process/try-wait.rs new file mode 100644 index 000000000..692197210 --- /dev/null +++ b/tests/ui/process/try-wait.rs @@ -0,0 +1,60 @@ +// run-pass + +#![allow(stable_features)] +// ignore-emscripten no processes +// ignore-sgx no processes + +#![feature(process_try_wait)] + +use std::env; +use std::process::Command; +use std::thread; +use std::time::Duration; + +fn main() { + let args = env::args().collect::<Vec<_>>(); + if args.len() != 1 { + match &args[1][..] { + "sleep" => thread::sleep(Duration::new(1_000, 0)), + _ => {} + } + return + } + + let mut me = Command::new(env::current_exe().unwrap()) + .arg("sleep") + .spawn() + .unwrap(); + let maybe_status = me.try_wait().unwrap(); + assert!(maybe_status.is_none()); + let maybe_status = me.try_wait().unwrap(); + assert!(maybe_status.is_none()); + + me.kill().unwrap(); + me.wait().unwrap(); + + let status = me.try_wait().unwrap().unwrap(); + assert!(!status.success()); + let status = me.try_wait().unwrap().unwrap(); + assert!(!status.success()); + + let mut me = Command::new(env::current_exe().unwrap()) + .arg("return-quickly") + .spawn() + .unwrap(); + loop { + match me.try_wait() { + Ok(Some(res)) => { + assert!(res.success()); + break + } + Ok(None) => { + thread::sleep(Duration::from_millis(1)); + } + Err(e) => panic!("error in try_wait: {}", e), + } + } + + let status = me.try_wait().unwrap().unwrap(); + assert!(status.success()); +} |