rust/src/libstd/io/process.rs
Alex Crichton 13a8fcd3e9 windows: Fix the test_exists unit test
Turns out the `timeout` command was exiting immediately because it didn't like
its output piped. Instead use `ping` repeatedly to get a process that will sleep
for awhile.

cc #12516
2014-02-24 12:33:06 -08:00

836 lines
27 KiB
Rust

// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Bindings for executing child processes
#[deny(missing_doc)];
use prelude::*;
use fmt;
use io::IoResult;
use io;
use libc;
use rt::rtio::{RtioProcess, IoFactory, LocalIo};
/// Signal a process to exit, without forcibly killing it. Corresponds to
/// SIGTERM on unix platforms.
#[cfg(windows)] pub static PleaseExitSignal: int = 15;
/// Signal a process to exit immediately, forcibly killing it. Corresponds to
/// SIGKILL on unix platforms.
#[cfg(windows)] pub static MustDieSignal: int = 9;
/// Signal a process to exit, without forcibly killing it. Corresponds to
/// SIGTERM on unix platforms.
#[cfg(not(windows))] pub static PleaseExitSignal: int = libc::SIGTERM as int;
/// Signal a process to exit immediately, forcibly killing it. Corresponds to
/// SIGKILL on unix platforms.
#[cfg(not(windows))] pub static MustDieSignal: int = libc::SIGKILL as int;
/// Representation of a running or exited child process.
///
/// This structure is used to create, run, and manage child processes. A process
/// is configured with the `ProcessConfig` struct which contains specific
/// options for dictating how the child is spawned.
///
/// # Example
///
/// ```should_fail
/// use std::io::Process;
///
/// let mut child = match Process::new("/bin/cat", [~"file.txt"]) {
/// Ok(child) => child,
/// Err(e) => fail!("failed to execute child: {}", e),
/// };
///
/// let contents = child.stdout.get_mut_ref().read_to_end();
/// assert!(child.wait().success());
/// ```
pub struct Process {
priv handle: ~RtioProcess,
/// Handle to the child's stdin, if the `stdin` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stdin: Option<io::PipeStream>,
/// Handle to the child's stdout, if the `stdout` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stdout: Option<io::PipeStream>,
/// Handle to the child's stderr, if the `stderr` field of this process's
/// `ProcessConfig` was `CreatePipe`. By default, this handle is `Some`.
stderr: Option<io::PipeStream>,
/// Extra I/O handles as configured by the original `ProcessConfig` when
/// this process was created. This is by default empty.
extra_io: ~[Option<io::PipeStream>],
}
/// This configuration describes how a new process should be spawned. A blank
/// configuration can be created with `ProcessConfig::new()`. It is also
/// recommented to use a functional struct update pattern when creating process
/// configuration:
///
/// ```
/// use std::io::ProcessConfig;
///
/// let config = ProcessConfig {
/// program: "/bin/sh",
/// args: &[~"-c", ~"true"],
/// .. ProcessConfig::new()
/// };
/// ```
pub struct ProcessConfig<'a> {
/// Path to the program to run
program: &'a str,
/// Arguments to pass to the program (doesn't include the program itself)
args: &'a [~str],
/// Optional environment to specify for the program. If this is None, then
/// it will inherit the current process's environment.
env: Option<&'a [(~str, ~str)]>,
/// Optional working directory for the new process. If this is None, then
/// the current directory of the running process is inherited.
cwd: Option<&'a Path>,
/// Configuration for the child process's stdin handle (file descriptor 0).
/// This field defaults to `CreatePipe(true, false)` so the input can be
/// written to.
stdin: StdioContainer,
/// Configuration for the child process's stdout handle (file descriptor 1).
/// This field defaults to `CreatePipe(false, true)` so the output can be
/// collected.
stdout: StdioContainer,
/// Configuration for the child process's stdout handle (file descriptor 2).
/// This field defaults to `CreatePipe(false, true)` so the output can be
/// collected.
stderr: StdioContainer,
/// Any number of streams/file descriptors/pipes may be attached to this
/// process. This list enumerates the file descriptors and such for the
/// process to be spawned, and the file descriptors inherited will start at
/// 3 and go to the length of this array. The first three file descriptors
/// (stdin/stdout/stderr) are configured with the `stdin`, `stdout`, and
/// `stderr` fields.
extra_io: &'a [StdioContainer],
/// Sets the child process's user id. This translates to a `setuid` call in
/// the child process. Setting this value on windows will cause the spawn to
/// fail. Failure in the `setuid` call on unix will also cause the spawn to
/// fail.
uid: Option<uint>,
/// Similar to `uid`, but sets the group id of the child process. This has
/// the same semantics as the `uid` field.
gid: Option<uint>,
/// If true, the child process is spawned in a detached state. On unix, this
/// means that the child is the leader of a new process group.
detach: bool,
}
/// The output of a finished process.
pub struct ProcessOutput {
/// The status (exit code) of the process.
status: ProcessExit,
/// The data that the process wrote to stdout.
output: ~[u8],
/// The data that the process wrote to stderr.
error: ~[u8],
}
/// Describes what to do with a standard io stream for a child process.
pub enum StdioContainer {
/// This stream will be ignored. This is the equivalent of attaching the
/// stream to `/dev/null`
Ignored,
/// The specified file descriptor is inherited for the stream which it is
/// specified for.
InheritFd(libc::c_int),
/// Creates a pipe for the specified file descriptor which will be created
/// when the process is spawned.
///
/// The first boolean argument is whether the pipe is readable, and the
/// second is whether it is writable. These properties are from the view of
/// the *child* process, not the parent process.
CreatePipe(bool /* readable */, bool /* writable */),
}
/// Describes the result of a process after it has terminated.
/// Note that Windows have no signals, so the result is usually ExitStatus.
#[deriving(Eq)]
pub enum ProcessExit {
/// Normal termination with an exit status.
ExitStatus(int),
/// Termination by signal, with the signal number.
ExitSignal(int),
}
impl fmt::Show for ProcessExit {
/// Format a ProcessExit enum, to nicely present the information.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ExitStatus(code) => write!(f.buf, "exit code: {}", code),
ExitSignal(code) => write!(f.buf, "signal: {}", code),
}
}
}
impl ProcessExit {
/// Was termination successful? Signal termination not considered a success,
/// and success is defined as a zero exit status.
pub fn success(&self) -> bool {
return self.matches_exit_status(0);
}
/// Checks whether this ProcessExit matches the given exit status.
/// Termination by signal will never match an exit code.
pub fn matches_exit_status(&self, wanted: int) -> bool {
*self == ExitStatus(wanted)
}
}
impl<'a> ProcessConfig<'a> {
/// Creates a new configuration with blanks as all of the defaults. This is
/// useful when using functional struct updates:
///
/// ```rust
/// use std::io::process::{ProcessConfig, Process};
///
/// let config = ProcessConfig {
/// program: "/bin/sh",
/// args: &[~"-c", ~"echo hello"],
/// .. ProcessConfig::new()
/// };
///
/// let p = Process::configure(config);
/// ```
///
pub fn new<'a>() -> ProcessConfig<'a> {
ProcessConfig {
program: "",
args: &[],
env: None,
cwd: None,
stdin: CreatePipe(true, false),
stdout: CreatePipe(false, true),
stderr: CreatePipe(false, true),
extra_io: &[],
uid: None,
gid: None,
detach: false,
}
}
}
impl Process {
/// Creates a new process for the specified program/arguments, using
/// otherwise default configuration.
///
/// By default, new processes have their stdin/stdout/stderr handles created
/// as pipes the can be manipulated through the respective fields of the
/// returned `Process`.
///
/// # Example
///
/// ```
/// use std::io::Process;
///
/// let mut process = match Process::new("sh", &[~"c", ~"echo hello"]) {
/// Ok(p) => p,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// let output = process.stdout.get_mut_ref().read_to_end();
/// ```
pub fn new(prog: &str, args: &[~str]) -> IoResult<Process> {
Process::configure(ProcessConfig {
program: prog,
args: args,
.. ProcessConfig::new()
})
}
/// Executes the specified program with arguments, waiting for it to finish
/// and collecting all of its output.
///
/// # Example
///
/// ```
/// use std::io::Process;
/// use std::str;
///
/// let output = match Process::output("cat", [~"foo.txt"]) {
/// Ok(output) => output,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// println!("status: {}", output.status);
/// println!("stdout: {}", str::from_utf8_lossy(output.output));
/// println!("stderr: {}", str::from_utf8_lossy(output.error));
/// ```
pub fn output(prog: &str, args: &[~str]) -> IoResult<ProcessOutput> {
Process::new(prog, args).map(|mut p| p.wait_with_output())
}
/// Executes a child process and collects its exit status. This will block
/// waiting for the child to exit.
///
/// # Example
///
/// ```
/// use std::io::Process;
///
/// let status = match Process::status("ls", []) {
/// Ok(status) => status,
/// Err(e) => fail!("failed to execute process: {}", e),
/// };
///
/// println!("process exited with: {}", status);
/// ```
pub fn status(prog: &str, args: &[~str]) -> IoResult<ProcessExit> {
Process::new(prog, args).map(|mut p| p.wait())
}
/// Creates a new process with the specified configuration.
pub fn configure(config: ProcessConfig) -> IoResult<Process> {
let mut config = Some(config);
LocalIo::maybe_raise(|io| {
io.spawn(config.take_unwrap()).map(|(p, io)| {
let mut io = io.move_iter().map(|p| {
p.map(|p| io::PipeStream::new(p))
});
Process {
handle: p,
stdin: io.next().unwrap(),
stdout: io.next().unwrap(),
stderr: io.next().unwrap(),
extra_io: io.collect(),
}
})
})
}
/// Sends `signal` to another process in the system identified by `id`.
///
/// Note that windows doesn't quite have the same model as unix, so some
/// unix signals are mapped to windows signals. Notably, unix termination
/// signals (SIGTERM/SIGKILL/SIGINT) are translated to `TerminateProcess`.
///
/// Additionally, a signal number of 0 can check for existence of the target
/// process.
pub fn kill(id: libc::pid_t, signal: int) -> IoResult<()> {
LocalIo::maybe_raise(|io| io.kill(id, signal))
}
/// Returns the process id of this child process
pub fn id(&self) -> libc::pid_t { self.handle.id() }
/// Sends the specified signal to the child process, returning whether the
/// signal could be delivered or not.
///
/// Note that this is purely a wrapper around libuv's `uv_process_kill`
/// function.
///
/// If the signal delivery fails, the corresponding error is returned.
pub fn signal(&mut self, signal: int) -> IoResult<()> {
self.handle.kill(signal)
}
/// Sends a signal to this child requesting that it exits. This is
/// equivalent to sending a SIGTERM on unix platforms.
pub fn signal_exit(&mut self) -> IoResult<()> {
self.signal(PleaseExitSignal)
}
/// Sends a signal to this child forcing it to exit. This is equivalent to
/// sending a SIGKILL on unix platforms.
pub fn signal_kill(&mut self) -> IoResult<()> {
self.signal(MustDieSignal)
}
/// Wait for the child to exit completely, returning the status that it
/// exited with. This function will continue to have the same return value
/// after it has been called at least once.
///
/// The stdin handle to the child process will be closed before waiting.
pub fn wait(&mut self) -> ProcessExit {
drop(self.stdin.take());
self.handle.wait()
}
/// Simultaneously wait for the child to exit and collect all remaining
/// output on the stdout/stderr handles, returning a `ProcessOutput`
/// instance.
///
/// The stdin handle to the child is closed before waiting.
pub fn wait_with_output(&mut self) -> ProcessOutput {
drop(self.stdin.take());
fn read(stream: Option<io::PipeStream>) -> Port<IoResult<~[u8]>> {
let (p, c) = Chan::new();
match stream {
Some(stream) => spawn(proc() {
let mut stream = stream;
c.send(stream.read_to_end())
}),
None => c.send(Ok(~[]))
}
p
}
let stdout = read(self.stdout.take());
let stderr = read(self.stderr.take());
let status = self.wait();
ProcessOutput { status: status,
output: stdout.recv().ok().unwrap_or(~[]),
error: stderr.recv().ok().unwrap_or(~[]) }
}
}
impl Drop for Process {
fn drop(&mut self) {
// Close all I/O before exiting to ensure that the child doesn't wait
// forever to print some text or something similar.
drop(self.stdin.take());
drop(self.stdout.take());
drop(self.stderr.take());
loop {
match self.extra_io.pop() {
Some(_) => (),
None => break,
}
}
self.wait();
}
}
#[cfg(test)]
mod tests {
use io::process::{ProcessConfig, Process};
use prelude::*;
// FIXME(#10380) these tests should not all be ignored on android.
#[cfg(not(target_os="android"))]
iotest!(fn smoke() {
let args = ProcessConfig {
program: "true",
.. ProcessConfig::new()
};
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.wait().success());
})
#[cfg(not(target_os="android"))]
iotest!(fn smoke_failure() {
let args = ProcessConfig {
program: "if-this-is-a-binary-then-the-world-has-ended",
.. ProcessConfig::new()
};
match Process::configure(args) {
Ok(..) => fail!(),
Err(..) => {}
}
})
#[cfg(not(target_os="android"))]
iotest!(fn exit_reported_right() {
let args = ProcessConfig {
program: "false",
.. ProcessConfig::new()
};
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.wait().matches_exit_status(1));
})
#[cfg(unix, not(target_os="android"))]
iotest!(fn signal_reported_right() {
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"kill -1 $$"],
.. ProcessConfig::new()
};
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
match p.wait() {
process::ExitSignal(1) => {},
result => fail!("not terminated by signal 1 (instead, {})", result),
}
})
pub fn read_all(input: &mut Reader) -> ~str {
input.read_to_str().unwrap()
}
pub fn run_output(args: ProcessConfig) -> ~str {
let p = Process::configure(args);
assert!(p.is_ok());
let mut p = p.unwrap();
assert!(p.stdout.is_some());
let ret = read_all(p.stdout.get_mut_ref() as &mut Reader);
assert!(p.wait().success());
return ret;
}
#[cfg(not(target_os="android"))]
iotest!(fn stdout_works() {
let args = ProcessConfig {
program: "echo",
args: &[~"foobar"],
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
assert_eq!(run_output(args), ~"foobar\n");
})
#[cfg(unix, not(target_os="android"))]
iotest!(fn set_cwd_works() {
let cwd = Path::new("/");
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"pwd"],
cwd: Some(&cwd),
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
assert_eq!(run_output(args), ~"/\n");
})
#[cfg(unix, not(target_os="android"))]
iotest!(fn stdin_works() {
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"read line; echo $line"],
stdin: CreatePipe(true, false),
stdout: CreatePipe(false, true),
.. ProcessConfig::new()
};
let mut p = Process::configure(args).unwrap();
p.stdin.get_mut_ref().write("foobar".as_bytes()).unwrap();
drop(p.stdin.take());
let out = read_all(p.stdout.get_mut_ref() as &mut Reader);
assert!(p.wait().success());
assert_eq!(out, ~"foobar\n");
})
#[cfg(not(target_os="android"))]
iotest!(fn detach_works() {
let args = ProcessConfig {
program: "true",
detach: true,
.. ProcessConfig::new()
};
let mut p = Process::configure(args).unwrap();
assert!(p.wait().success());
})
#[cfg(windows)]
iotest!(fn uid_fails_on_windows() {
let args = ProcessConfig {
program: "test",
uid: Some(10),
.. ProcessConfig::new()
};
assert!(Process::configure(args).is_err());
})
#[cfg(unix, not(target_os="android"))]
iotest!(fn uid_works() {
use libc;
let args = ProcessConfig {
program: "/bin/sh",
args: &[~"-c", ~"true"],
uid: Some(unsafe { libc::getuid() as uint }),
gid: Some(unsafe { libc::getgid() as uint }),
.. ProcessConfig::new()
};
let mut p = Process::configure(args).unwrap();
assert!(p.wait().success());
})
#[cfg(unix, not(target_os="android"))]
iotest!(fn uid_to_root_fails() {
use libc;
// if we're already root, this isn't a valid test. Most of the bots run
// as non-root though (android is an exception).
if unsafe { libc::getuid() == 0 } { return }
let args = ProcessConfig {
program: "/bin/ls",
uid: Some(0),
gid: Some(0),
.. ProcessConfig::new()
};
assert!(Process::configure(args).is_err());
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_status() {
let mut status = Process::status("false", []).unwrap();
assert!(status.matches_exit_status(1));
status = Process::status("true", []).unwrap();
assert!(status.success());
})
iotest!(fn test_process_output_fail_to_start() {
match Process::output("/no-binary-by-this-name-should-exist", []) {
Err(e) => assert_eq!(e.kind, FileNotFound),
Ok(..) => fail!()
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_output_output() {
let ProcessOutput {status, output, error}
= Process::output("echo", [~"hello"]).unwrap();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_process_output_error() {
let ProcessOutput {status, output, error}
= Process::output("mkdir", [~"."]).unwrap();
assert!(status.matches_exit_status(1));
assert_eq!(output, ~[]);
assert!(!error.is_empty());
})
#[cfg(not(target_os="android"))]
iotest!(fn test_finish_once() {
let mut prog = Process::new("false", []).unwrap();
assert!(prog.wait().matches_exit_status(1));
})
#[cfg(not(target_os="android"))]
iotest!(fn test_finish_twice() {
let mut prog = Process::new("false", []).unwrap();
assert!(prog.wait().matches_exit_status(1));
assert!(prog.wait().matches_exit_status(1));
})
#[cfg(not(target_os="android"))]
iotest!(fn test_wait_with_output_once() {
let mut prog = Process::new("echo", [~"hello"]).unwrap();
let ProcessOutput {status, output, error} = prog.wait_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(not(target_os="android"))]
iotest!(fn test_wait_with_output_twice() {
let mut prog = Process::new("echo", [~"hello"]).unwrap();
let ProcessOutput {status, output, error} = prog.wait_with_output();
let output_str = str::from_utf8_owned(output).unwrap();
assert!(status.success());
assert_eq!(output_str.trim().to_owned(), ~"hello");
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
let ProcessOutput {status, output, error} = prog.wait_with_output();
assert!(status.success());
assert_eq!(output, ~[]);
// FIXME #7224
if !running_on_valgrind() {
assert_eq!(error, ~[]);
}
})
#[cfg(unix,not(target_os="android"))]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "pwd",
cwd: dir,
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(target_os="android")]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "/system/bin/sh",
args: &[~"-c",~"pwd"],
cwd: dir.map(|a| &*a),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(windows)]
pub fn run_pwd(dir: Option<&Path>) -> Process {
Process::configure(ProcessConfig {
program: "cmd",
args: &[~"/c", ~"cd"],
cwd: dir.map(|a| &*a),
.. ProcessConfig::new()
}).unwrap()
}
iotest!(fn test_keep_current_working_dir() {
use os;
let mut prog = run_pwd(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let parent_dir = os::getcwd();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
})
iotest!(fn test_change_working_directory() {
use os;
// test changing to the parent of os::getcwd() because we know
// the path exists (and os::getcwd() is not expected to be root)
let parent_dir = os::getcwd().dir_path();
let mut prog = run_pwd(Some(&parent_dir));
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let child_dir = Path::new(output.trim());
let parent_stat = parent_dir.stat().unwrap();
let child_stat = child_dir.stat().unwrap();
assert_eq!(parent_stat.unstable.device, child_stat.unstable.device);
assert_eq!(parent_stat.unstable.inode, child_stat.unstable.inode);
})
#[cfg(unix,not(target_os="android"))]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "env",
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(target_os="android")]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "/system/bin/sh",
args: &[~"-c",~"set"],
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(windows)]
pub fn run_env(env: Option<~[(~str, ~str)]>) -> Process {
Process::configure(ProcessConfig {
program: "cmd",
args: &[~"/c", ~"set"],
env: env.as_ref().map(|e| e.as_slice()),
.. ProcessConfig::new()
}).unwrap()
}
#[cfg(not(target_os="android"))]
iotest!(fn test_inherit_env() {
use os;
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check windows magical empty-named variables
assert!(k.is_empty() || output.contains(format!("{}={}", *k, *v)));
}
})
#[cfg(target_os="android")]
iotest!(fn test_inherit_env() {
use os;
if running_on_valgrind() { return; }
let mut prog = run_env(None);
let output = str::from_utf8_owned(prog.wait_with_output().output).unwrap();
let r = os::env();
for &(ref k, ref v) in r.iter() {
// don't check android RANDOM variables
if *k != ~"RANDOM" {
assert!(output.contains(format!("{}={}", *k, *v)) ||
output.contains(format!("{}=\'{}\'", *k, *v)));
}
}
})
iotest!(fn test_add_to_env() {
let new_env = ~[(~"RUN_TEST_NEW_ENV", ~"123")];
let mut prog = run_env(Some(new_env));
let result = prog.wait_with_output();
let output = str::from_utf8_lossy(result.output).into_owned();
assert!(output.contains("RUN_TEST_NEW_ENV=123"),
"didn't find RUN_TEST_NEW_ENV inside of:\n\n{}", output);
})
#[cfg(unix)]
pub fn sleeper() -> Process {
Process::new("sleep", [~"1000"]).unwrap()
}
#[cfg(windows)]
pub fn sleeper() -> Process {
// 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 inbetweeen so we're sure this process is alive for awhile
Process::new("ping", [~"127.0.0.1", ~"-n", ~"1000"]).unwrap()
}
iotest!(fn test_kill() {
let mut p = sleeper();
Process::kill(p.id(), PleaseExitSignal).unwrap();
assert!(!p.wait().success());
})
iotest!(fn test_exists() {
let mut p = sleeper();
assert!(Process::kill(p.id(), 0).is_ok());
p.signal_kill().unwrap();
assert!(!p.wait().success());
})
}