Skip to content

Commit

Permalink
Add ExitStatus::into_std_lossy
Browse files Browse the repository at this point in the history
  • Loading branch information
dylni committed Aug 8, 2024
1 parent 763e5d2 commit 3bc3f60
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 24 deletions.
30 changes: 15 additions & 15 deletions src/control/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,17 @@ impl Process for &mut Child {
if let Some(memory_limit) = options.memory_limit {
handle.set_memory_limit(memory_limit)?;
}
handle.wait(options.time_limit).map(|x| x.map(ExitStatus))
let result = handle.wait(options.time_limit)?;
result
.map(|result| {
self.try_wait().map(|std_result| {
ExitStatus::new(
result,
std_result.expect("missing exit status"),
)
})
})
.transpose()
}
}

Expand Down Expand Up @@ -182,27 +192,17 @@ where
let _ = self.process.get().stdin.take();
let mut result = self.process.run_wait(self.options);

macro_rules! run_if_ok {
( $get_result_fn:expr ) => {
if result.is_ok() {
#[allow(clippy::redundant_closure_call)]
if let Err(error) = $get_result_fn() {
result = Err(error);
}
}
};
}

let process = self.process.get();
// If the process exited normally, identifier reuse might cause a
// different process to be terminated.
if self.terminate_for_timeout && !matches!(result, Ok(Some(_))) {
let next_result = process.kill().and_then(|()| process.wait());
if self.strict_errors {
run_if_ok!(|| next_result);
if self.strict_errors && result.is_ok() {
if let Err(error) = next_result {
result = Err(error);
}
}
}
run_if_ok!(|| process.try_wait());

result
}
Expand Down
103 changes: 97 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,36 +157,80 @@ macro_rules! unix_method {
#[inline]
#[must_use]
pub fn $method(&self) -> $return_type {
self.0.$method()
self.inner.$method()
}
};
}

/// Equivalent to [`process::ExitStatus`] but allows for greater accuracy.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[must_use]
pub struct ExitStatus(imp::ExitStatus);
pub struct ExitStatus {
inner: imp::ExitStatus,
std: process::ExitStatus,
}

impl ExitStatus {
fn new(inner: imp::ExitStatus, std: process::ExitStatus) -> Self {
debug_assert_eq!(inner, std.into());
Self { inner, std }
}

/// Equivalent to [`process::ExitStatus::success`].
#[inline]
#[must_use]
pub fn success(self) -> bool {
self.0.success()
self.inner.success()
}

/// Equivalent to [`process::ExitStatus::code`], but a more accurate value
/// will be returned if possible.
#[inline]
#[must_use]
pub fn code(self) -> Option<i64> {
self.0.code().map(Into::into)
self.inner.code().map(Into::into)
}

unix_method!(continued, bool);
unix_method!(core_dumped, bool);
unix_method!(signal, Option<c_int>);
unix_method!(stopped_signal, Option<c_int>);

/// Converts this structure to a corresponding [`process::ExitStatus`]
/// instance.
///
/// Since this type can represent more exit codes, it will attempt to
/// provide an equivalent representation using the standard library type.
/// However, if converted back to this structure, detailed information may
/// have been lost.
///
/// # Examples
///
/// ```
/// # use std::io;
/// use std::process::Command;
/// use std::time::Duration;
///
/// use process_control::ChildExt;
/// use process_control::Control;
///
/// let exit_status = Command::new("echo")
/// .spawn()?
/// .controlled()
/// .time_limit(Duration::from_secs(1))
/// .terminate_for_timeout()
/// .wait()?
/// .expect("process timed out");
/// assert!(exit_status.success());
/// assert!(exit_status.into_std_lossy().success());
/// #
/// # Ok::<_, io::Error>(())
/// ```
#[inline]
#[must_use]
pub fn into_std_lossy(self) -> process::ExitStatus {
self.std
}
}

impl AsMut<Self> for ExitStatus {
Expand All @@ -206,14 +250,14 @@ impl AsRef<Self> for ExitStatus {
impl Display for ExitStatus {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
Display::fmt(&self.inner, f)
}
}

impl From<process::ExitStatus> for ExitStatus {
#[inline]
fn from(value: process::ExitStatus) -> Self {
Self(value.into())
Self::new(value.into(), value)
}
}

Expand All @@ -232,6 +276,53 @@ pub struct Output {
pub stderr: Vec<u8>,
}

impl Output {
/// Converts this structure to the best equivalent [`process::Output`]
/// instance.
///
/// For more information, see [`ExitStatus`].
///
/// # Examples
///
/// ```
/// # use std::io;
/// use std::process::Command;
/// use std::process::Stdio;
/// use std::time::Duration;
///
/// use process_control::ChildExt;
/// use process_control::Control;
///
/// let message = "foobar";
/// let output = Command::new("echo")
/// .arg(message)
/// .stdout(Stdio::piped())
/// .spawn()?
/// .controlled_with_output()
/// .time_limit(Duration::from_secs(1))
/// .terminate_for_timeout()
/// .wait()?
/// .expect("process timed out");
/// assert!(output.status.success());
/// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
///
/// let output = output.into_std_lossy();
/// assert!(output.status.success());
/// assert_eq!(message.as_bytes(), &output.stdout[..message.len()]);
/// #
/// # Ok::<_, io::Error>(())
/// ```
#[inline]
#[must_use]
pub fn into_std_lossy(self) -> process::Output {
process::Output {
status: self.status.into_std_lossy(),
stdout: self.stdout,
stderr: self.stderr,
}
}
}

impl AsMut<ExitStatus> for Output {
#[inline]
fn as_mut(&mut self) -> &mut ExitStatus {
Expand Down
13 changes: 10 additions & 3 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,16 @@ impl __Test {
where
F: FnMut() -> io::Result<Result<ExitStatus, Handle>>,
{
let exit_code = wait_fn()
.expect("failed to run process")
.map(ExitStatus::code);
let exit_code =
wait_fn()
.expect("failed to run process")
.map(|exit_status| {
assert_eq!(
exit_status.code(),
exit_status.into_std_lossy().code().map(Into::into),
);
exit_status.code()
});
assert!((self.is_expected_fn)(exit_code.as_ref().ok().copied()));

if self.running {
Expand Down

0 comments on commit 3bc3f60

Please sign in to comment.