1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 //! Provides utility functions used by multiple fixture files.
6
7 use std::env;
8 use std::io::ErrorKind;
9 #[cfg(unix)]
10 use std::os::unix::process::ExitStatusExt;
11 use std::path::PathBuf;
12 use std::process::Command;
13 use std::process::ExitStatus;
14 use std::process::Output;
15 use std::sync::mpsc::sync_channel;
16 use std::sync::mpsc::RecvTimeoutError;
17 use std::thread;
18 use std::time::Duration;
19 use std::time::SystemTime;
20
21 use anyhow::anyhow;
22 use anyhow::Result;
23
24 use crate::sys::binary_name;
25
26 /// Returns the path to the crosvm binary to be tested.
27 ///
28 /// The crosvm binary is expected to be alongside to the integration tests
29 /// binary. Alternatively in the parent directory (cargo will put the
30 /// test binary in target/debug/deps/ but the crosvm binary in target/debug)
find_crosvm_binary() -> PathBuf31 pub fn find_crosvm_binary() -> PathBuf {
32 let binary_name = binary_name();
33 let exe_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();
34 let first = exe_dir.join(binary_name);
35 if first.exists() {
36 return first;
37 }
38 let second = exe_dir.parent().unwrap().join(binary_name);
39 if second.exists() {
40 return second;
41 }
42 panic!(
43 "Cannot find {} in ./ or ../ alongside test binary.",
44 binary_name
45 );
46 }
47
48 /// Run the provided closure in a separate thread and return it's result. If the closure does not
49 /// finish before the timeout is reached, an Error is returned instead.
50 ///
51 /// WARNING: It is not possible to kill the closure if a timeout occurs. It is advised to panic
52 /// when an error is returned.
run_with_timeout<F, U>(closure: F, timeout: Duration) -> Result<U> where F: FnOnce() -> U + Send + 'static, U: Send + 'static,53 pub fn run_with_timeout<F, U>(closure: F, timeout: Duration) -> Result<U>
54 where
55 F: FnOnce() -> U + Send + 'static,
56 U: Send + 'static,
57 {
58 let (tx, rx) = sync_channel::<()>(1);
59 let handle = thread::spawn(move || {
60 let result = closure();
61 // Notify main thread the closure is done. Fail silently if it's not listening anymore.
62 let _ = tx.send(());
63 result
64 });
65 match rx.recv_timeout(timeout) {
66 Ok(_) => Ok(handle.join().unwrap()),
67 Err(RecvTimeoutError::Timeout) => Err(anyhow!("closure timed out after {timeout:?}")),
68 Err(RecvTimeoutError::Disconnected) => Err(anyhow!("closure paniced")),
69 }
70 }
71
72 #[derive(Debug)]
73 pub enum CommandError {
74 IoError(std::io::Error),
75 ErrorCode(i32),
76 Signal(i32),
77 }
78
79 /// Extension trait for utilities on std::process::Command
80 pub trait CommandExt {
81 /// Same as Command::output() but will treat non-success status of the Command as an
82 /// error.
output_checked(&mut self) -> std::result::Result<Output, CommandError>83 fn output_checked(&mut self) -> std::result::Result<Output, CommandError>;
84
85 /// Print the command to be executed
log(&mut self) -> &mut Self86 fn log(&mut self) -> &mut Self;
87 }
88
89 impl CommandExt for Command {
output_checked(&mut self) -> std::result::Result<Output, CommandError>90 fn output_checked(&mut self) -> std::result::Result<Output, CommandError> {
91 let output = self.output().map_err(CommandError::IoError)?;
92 if !output.status.success() {
93 if let Some(code) = output.status.code() {
94 return Err(CommandError::ErrorCode(code));
95 } else {
96 #[cfg(unix)]
97 if let Some(signal) = output.status.signal() {
98 return Err(CommandError::Signal(signal));
99 }
100 panic!("No error code and no signal should never happen.");
101 }
102 }
103 Ok(output)
104 }
105
log(&mut self) -> &mut Self106 fn log(&mut self) -> &mut Self {
107 println!("$ {:?}", self);
108 self
109 }
110 }
111
112 /// Extension trait for utilities on std::process::Child
113 pub trait ChildExt {
114 /// Same as Child.wait(), but will return with an error after the specified timeout.
wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>115 fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>;
116 }
117
118 impl ChildExt for std::process::Child {
wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>119 fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>> {
120 let start_time = SystemTime::now();
121 while SystemTime::now().duration_since(start_time).unwrap() < timeout {
122 if let Ok(status) = self.try_wait() {
123 return Ok(status);
124 }
125 thread::sleep(Duration::from_millis(10));
126 }
127 Err(std::io::Error::new(
128 ErrorKind::TimedOut,
129 "Timeout while waiting for child",
130 ))
131 }
132 }
133
134 /// Calls the `closure` until it returns a non-error Result.
135 /// If it has been re-tried `retries` times, the last result is returned.
retry<F, T, E>(mut closure: F, retries: usize) -> Result<T, E> where F: FnMut() -> Result<T, E>, E: std::fmt::Debug,136 pub fn retry<F, T, E>(mut closure: F, retries: usize) -> Result<T, E>
137 where
138 F: FnMut() -> Result<T, E>,
139 E: std::fmt::Debug,
140 {
141 let mut attempts_left = retries + 1;
142 loop {
143 let result = closure();
144 attempts_left -= 1;
145 if result.is_ok() || attempts_left == 0 {
146 break result;
147 } else {
148 println!("Attempt failed: {:?}", result.err());
149 }
150 }
151 }
152