about summary refs log tree commit diff
path: root/tests/ui/process
diff options
context:
space:
mode:
Diffstat (limited to 'tests/ui/process')
-rw-r--r--tests/ui/process/core-run-destroy.rs88
-rw-r--r--tests/ui/process/fds-are-cloexec.rs82
-rw-r--r--tests/ui/process/issue-13304.rs39
-rw-r--r--tests/ui/process/issue-14456.rs37
-rw-r--r--tests/ui/process/issue-14940.rs19
-rw-r--r--tests/ui/process/issue-16272.rs23
-rw-r--r--tests/ui/process/issue-20091.rs24
-rw-r--r--tests/ui/process/multi-panic.rs38
-rw-r--r--tests/ui/process/no-stdio.rs139
-rw-r--r--tests/ui/process/nofile-limit.rs46
-rw-r--r--tests/ui/process/process-envs.rs54
-rw-r--r--tests/ui/process/process-exit.rs26
-rw-r--r--tests/ui/process/process-panic-after-fork.rs197
-rw-r--r--tests/ui/process/process-remove-from-env.rs48
-rw-r--r--tests/ui/process/process-sigpipe.rs37
-rw-r--r--tests/ui/process/process-spawn-nonexistent.rs17
-rw-r--r--tests/ui/process/process-spawn-with-unicode-params.rs77
-rw-r--r--tests/ui/process/process-status-inherits-stdin.rs35
-rw-r--r--tests/ui/process/signal-exit-status.rs19
-rw-r--r--tests/ui/process/sigpipe-should-be-ignored.rs33
-rw-r--r--tests/ui/process/tls-exit-status.rs11
-rw-r--r--tests/ui/process/try-wait.rs60
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 00000000000..d0e97bf01f3
--- /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 00000000000..4482b7032a7
--- /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 00000000000..b10f6d57255
--- /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 00000000000..52a56eb77f7
--- /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 00000000000..98a4af0c467
--- /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 00000000000..5cf3fd94928
--- /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 00000000000..86cc79d6b42
--- /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 00000000000..a1887c2180e
--- /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 00000000000..24985386a97
--- /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 00000000000..3ddf8d6ef24
--- /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 00000000000..f3a46979142
--- /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 00000000000..d193e073e7f
--- /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 00000000000..da268312173
--- /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 00000000000..ad027d68588
--- /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 00000000000..107eba45dc2
--- /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 00000000000..9dd608986df
--- /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 00000000000..16dba6292db
--- /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 00000000000..7719dd9ad85
--- /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 00000000000..9519ed7b4c7
--- /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 00000000000..144eeca2318
--- /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 00000000000..6296e5042d2
--- /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 00000000000..692197210b1
--- /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());
+}