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#[derive(Debug, Error)]
58pub enum RunError {
59 #[error("Command timed out")]
61 TimedOut,
62
63 #[error("I/O error: {context}")]
65 Io {
66 context: IoErrorContext,
68 #[source]
70 error: io::Error,
71 },
72}
73
74#[derive(Debug, Error)]
76pub enum IoErrorContext {
77 #[error("Failed to execute command `{command}`")]
79 Command {
80 command: String,
82 },
83
84 #[error("Failed to wait with timeout")]
86 WaitWithTimeout,
87
88 #[error("Failed to kill process after timeout")]
90 KillProcess,
91
92 #[error("Failed to wait for process after killing it after timeout")]
94 WaitForProcess,
95}
96
97fn context(context: IoErrorContext) -> impl FnOnce(io::Error) -> RunError {
99 |error| RunError::Io { context, error }
100}