runner_utils/
lib.rs

1use process::{Command, ExitStatus};
2use std::{io, path::Path, process, time::Duration};
3use thiserror::Error;
4use wait_timeout::ChildExt;
5
6pub fn binary_kind(binary_path: &Path) -> BinaryKind {
7    let exe_parent = binary_path.parent();
8    let parent_dir_name = exe_parent
9        .and_then(|p| p.file_name())
10        .and_then(|name| name.to_str());
11    match parent_dir_name {
12        Some("deps") => BinaryKind::Test,
13        Some(name) if name.starts_with("rustdoctest") => BinaryKind::DocTest,
14        _other => BinaryKind::Other,
15    }
16}
17
18#[derive(Debug, Eq, PartialEq, Copy, Clone)]
19pub enum BinaryKind {
20    Test,
21    DocTest,
22    Other,
23}
24
25impl BinaryKind {
26    pub fn is_test(&self) -> bool {
27        match self {
28            BinaryKind::Test | BinaryKind::DocTest => true,
29            BinaryKind::Other => false,
30        }
31    }
32}
33
34pub fn run_with_timeout(command: &mut Command, timeout: Duration) -> Result<ExitStatus, RunError> {
35    let mut child = command.spawn().map_err(|error| RunError::Io {
36        context: IoErrorContext::Command {
37            command: format!("{:?}", command),
38        },
39        error,
40    })?;
41    match child
42        .wait_timeout(timeout)
43        .map_err(context(IoErrorContext::WaitWithTimeout))?
44    {
45        None => {
46            child.kill().map_err(context(IoErrorContext::KillProcess))?;
47            child
48                .wait()
49                .map_err(context(IoErrorContext::WaitForProcess))?;
50            Err(RunError::TimedOut)
51        }
52        Some(exit_status) => Ok(exit_status),
53    }
54}
55
56/// Running the disk image failed.
57#[derive(Debug, Error)]
58pub enum RunError {
59    /// Command timed out
60    #[error("Command timed out")]
61    TimedOut,
62
63    /// An I/O error occured
64    #[error("I/O error: {context}")]
65    Io {
66        /// The operation that caused the I/O error.
67        context: IoErrorContext,
68        /// The I/O error that occured.
69        #[source]
70        error: io::Error,
71    },
72}
73
74/// An I/O error occured while trying to run the disk image.
75#[derive(Debug, Error)]
76pub enum IoErrorContext {
77    /// Failed to execute command
78    #[error("Failed to execute command `{command}`")]
79    Command {
80        /// The Command that was executed
81        command: String,
82    },
83
84    /// Waiting with timeout failed
85    #[error("Failed to wait with timeout")]
86    WaitWithTimeout,
87
88    /// Failed to kill process after timeout
89    #[error("Failed to kill process after timeout")]
90    KillProcess,
91
92    /// Failed to wait for process after killing it after timeout
93    #[error("Failed to wait for process after killing it after timeout")]
94    WaitForProcess,
95}
96
97/// Helper function for IO error construction
98fn context(context: IoErrorContext) -> impl FnOnce(io::Error) -> RunError {
99    |error| RunError::Io { context, error }
100}