• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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