1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2022 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker
5*bb4ee6a4SAndroid Build Coastguard Worker //! Provides utility functions used by multiple fixture files.
6*bb4ee6a4SAndroid Build Coastguard Worker
7*bb4ee6a4SAndroid Build Coastguard Worker use std::env;
8*bb4ee6a4SAndroid Build Coastguard Worker use std::io::ErrorKind;
9*bb4ee6a4SAndroid Build Coastguard Worker #[cfg(any(target_os = "android", target_os = "linux"))]
10*bb4ee6a4SAndroid Build Coastguard Worker use std::os::unix::process::ExitStatusExt;
11*bb4ee6a4SAndroid Build Coastguard Worker use std::path::Path;
12*bb4ee6a4SAndroid Build Coastguard Worker use std::path::PathBuf;
13*bb4ee6a4SAndroid Build Coastguard Worker use std::process::Command;
14*bb4ee6a4SAndroid Build Coastguard Worker use std::process::ExitStatus;
15*bb4ee6a4SAndroid Build Coastguard Worker use std::process::Output;
16*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::mpsc::sync_channel;
17*bb4ee6a4SAndroid Build Coastguard Worker use std::sync::mpsc::RecvTimeoutError;
18*bb4ee6a4SAndroid Build Coastguard Worker use std::thread;
19*bb4ee6a4SAndroid Build Coastguard Worker use std::time::Duration;
20*bb4ee6a4SAndroid Build Coastguard Worker use std::time::SystemTime;
21*bb4ee6a4SAndroid Build Coastguard Worker
22*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::bail;
23*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::Result;
24*bb4ee6a4SAndroid Build Coastguard Worker use tempfile::NamedTempFile;
25*bb4ee6a4SAndroid Build Coastguard Worker
26*bb4ee6a4SAndroid Build Coastguard Worker use crate::sys::binary_name;
27*bb4ee6a4SAndroid Build Coastguard Worker use crate::vhost_user::CmdType;
28*bb4ee6a4SAndroid Build Coastguard Worker use crate::vhost_user::Config as VuConfig;
29*bb4ee6a4SAndroid Build Coastguard Worker
30*bb4ee6a4SAndroid Build Coastguard Worker pub const DEFAULT_BLOCK_SIZE: u64 = 1024 * 1024;
31*bb4ee6a4SAndroid Build Coastguard Worker
32*bb4ee6a4SAndroid Build Coastguard Worker /// Returns the path to the crosvm binary to be tested.
33*bb4ee6a4SAndroid Build Coastguard Worker ///
34*bb4ee6a4SAndroid Build Coastguard Worker /// The crosvm binary is expected to be alongside to the integration tests
35*bb4ee6a4SAndroid Build Coastguard Worker /// binary. Alternatively in the parent directory (cargo will put the
36*bb4ee6a4SAndroid Build Coastguard Worker /// test binary in target/debug/deps/ but the crosvm binary in target/debug)
find_crosvm_binary() -> PathBuf37*bb4ee6a4SAndroid Build Coastguard Worker pub fn find_crosvm_binary() -> PathBuf {
38*bb4ee6a4SAndroid Build Coastguard Worker let binary_name = binary_name();
39*bb4ee6a4SAndroid Build Coastguard Worker let exe_dir = env::current_exe().unwrap().parent().unwrap().to_path_buf();
40*bb4ee6a4SAndroid Build Coastguard Worker let first = exe_dir.join(binary_name);
41*bb4ee6a4SAndroid Build Coastguard Worker if first.exists() {
42*bb4ee6a4SAndroid Build Coastguard Worker return first;
43*bb4ee6a4SAndroid Build Coastguard Worker }
44*bb4ee6a4SAndroid Build Coastguard Worker let second = exe_dir.parent().unwrap().join(binary_name);
45*bb4ee6a4SAndroid Build Coastguard Worker if second.exists() {
46*bb4ee6a4SAndroid Build Coastguard Worker return second;
47*bb4ee6a4SAndroid Build Coastguard Worker }
48*bb4ee6a4SAndroid Build Coastguard Worker panic!(
49*bb4ee6a4SAndroid Build Coastguard Worker "Cannot find {} in ./ or ../ alongside test binary.",
50*bb4ee6a4SAndroid Build Coastguard Worker binary_name
51*bb4ee6a4SAndroid Build Coastguard Worker );
52*bb4ee6a4SAndroid Build Coastguard Worker }
53*bb4ee6a4SAndroid Build Coastguard Worker
54*bb4ee6a4SAndroid Build Coastguard Worker /// Run the provided closure in a separate thread and return it's result. If the closure does not
55*bb4ee6a4SAndroid Build Coastguard Worker /// finish before the timeout is reached, an Error is returned instead.
56*bb4ee6a4SAndroid Build Coastguard Worker ///
57*bb4ee6a4SAndroid Build Coastguard Worker /// WARNING: It is not possible to kill the closure if a timeout occurs. It is advised to panic
58*bb4ee6a4SAndroid Build Coastguard Worker /// 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,59*bb4ee6a4SAndroid Build Coastguard Worker pub fn run_with_timeout<F, U>(closure: F, timeout: Duration) -> Result<U>
60*bb4ee6a4SAndroid Build Coastguard Worker where
61*bb4ee6a4SAndroid Build Coastguard Worker F: FnOnce() -> U + Send + 'static,
62*bb4ee6a4SAndroid Build Coastguard Worker U: Send + 'static,
63*bb4ee6a4SAndroid Build Coastguard Worker {
64*bb4ee6a4SAndroid Build Coastguard Worker run_with_status_check(closure, timeout, || false)
65*bb4ee6a4SAndroid Build Coastguard Worker }
66*bb4ee6a4SAndroid Build Coastguard Worker
67*bb4ee6a4SAndroid Build Coastguard Worker /// Run the provided closure in a separate thread and return it's result. If the closure does not
68*bb4ee6a4SAndroid Build Coastguard Worker /// finish, continue_fn is called periodically with interval while continue_fn return true. Once
69*bb4ee6a4SAndroid Build Coastguard Worker /// continue_fn return false, an Error is returned instead.
70*bb4ee6a4SAndroid Build Coastguard Worker ///
71*bb4ee6a4SAndroid Build Coastguard Worker /// WARNING: It is not possible to kill the closure if a timeout occurs. It is advised to panic
72*bb4ee6a4SAndroid Build Coastguard Worker /// when an error is returned.
run_with_status_check<F, U, C>( closure: F, interval: Duration, mut continue_fn: C, ) -> Result<U> where F: FnOnce() -> U + Send + 'static, U: Send + 'static, C: FnMut() -> bool,73*bb4ee6a4SAndroid Build Coastguard Worker pub fn run_with_status_check<F, U, C>(
74*bb4ee6a4SAndroid Build Coastguard Worker closure: F,
75*bb4ee6a4SAndroid Build Coastguard Worker interval: Duration,
76*bb4ee6a4SAndroid Build Coastguard Worker mut continue_fn: C,
77*bb4ee6a4SAndroid Build Coastguard Worker ) -> Result<U>
78*bb4ee6a4SAndroid Build Coastguard Worker where
79*bb4ee6a4SAndroid Build Coastguard Worker F: FnOnce() -> U + Send + 'static,
80*bb4ee6a4SAndroid Build Coastguard Worker U: Send + 'static,
81*bb4ee6a4SAndroid Build Coastguard Worker C: FnMut() -> bool,
82*bb4ee6a4SAndroid Build Coastguard Worker {
83*bb4ee6a4SAndroid Build Coastguard Worker let (tx, rx) = sync_channel::<()>(1);
84*bb4ee6a4SAndroid Build Coastguard Worker let handle = thread::spawn(move || {
85*bb4ee6a4SAndroid Build Coastguard Worker let result = closure();
86*bb4ee6a4SAndroid Build Coastguard Worker // Notify main thread the closure is done. Fail silently if it's not listening anymore.
87*bb4ee6a4SAndroid Build Coastguard Worker let _ = tx.send(());
88*bb4ee6a4SAndroid Build Coastguard Worker result
89*bb4ee6a4SAndroid Build Coastguard Worker });
90*bb4ee6a4SAndroid Build Coastguard Worker loop {
91*bb4ee6a4SAndroid Build Coastguard Worker match rx.recv_timeout(interval) {
92*bb4ee6a4SAndroid Build Coastguard Worker Ok(_) => {
93*bb4ee6a4SAndroid Build Coastguard Worker return Ok(handle.join().unwrap());
94*bb4ee6a4SAndroid Build Coastguard Worker }
95*bb4ee6a4SAndroid Build Coastguard Worker Err(RecvTimeoutError::Timeout) => {
96*bb4ee6a4SAndroid Build Coastguard Worker if !continue_fn() {
97*bb4ee6a4SAndroid Build Coastguard Worker bail!("closure timed out");
98*bb4ee6a4SAndroid Build Coastguard Worker }
99*bb4ee6a4SAndroid Build Coastguard Worker }
100*bb4ee6a4SAndroid Build Coastguard Worker Err(RecvTimeoutError::Disconnected) => bail!("closure panicked"),
101*bb4ee6a4SAndroid Build Coastguard Worker }
102*bb4ee6a4SAndroid Build Coastguard Worker }
103*bb4ee6a4SAndroid Build Coastguard Worker }
104*bb4ee6a4SAndroid Build Coastguard Worker
105*bb4ee6a4SAndroid Build Coastguard Worker #[derive(Debug)]
106*bb4ee6a4SAndroid Build Coastguard Worker pub enum CommandError {
107*bb4ee6a4SAndroid Build Coastguard Worker IoError(std::io::Error),
108*bb4ee6a4SAndroid Build Coastguard Worker ErrorCode(i32),
109*bb4ee6a4SAndroid Build Coastguard Worker Signal(i32),
110*bb4ee6a4SAndroid Build Coastguard Worker }
111*bb4ee6a4SAndroid Build Coastguard Worker
112*bb4ee6a4SAndroid Build Coastguard Worker /// Extension trait for utilities on std::process::Command
113*bb4ee6a4SAndroid Build Coastguard Worker pub trait CommandExt {
114*bb4ee6a4SAndroid Build Coastguard Worker /// Same as Command::output() but will treat non-success status of the Command as an
115*bb4ee6a4SAndroid Build Coastguard Worker /// error.
output_checked(&mut self) -> std::result::Result<Output, CommandError>116*bb4ee6a4SAndroid Build Coastguard Worker fn output_checked(&mut self) -> std::result::Result<Output, CommandError>;
117*bb4ee6a4SAndroid Build Coastguard Worker
118*bb4ee6a4SAndroid Build Coastguard Worker /// Print the command to be executed
log(&mut self) -> &mut Self119*bb4ee6a4SAndroid Build Coastguard Worker fn log(&mut self) -> &mut Self;
120*bb4ee6a4SAndroid Build Coastguard Worker }
121*bb4ee6a4SAndroid Build Coastguard Worker
122*bb4ee6a4SAndroid Build Coastguard Worker impl CommandExt for Command {
output_checked(&mut self) -> std::result::Result<Output, CommandError>123*bb4ee6a4SAndroid Build Coastguard Worker fn output_checked(&mut self) -> std::result::Result<Output, CommandError> {
124*bb4ee6a4SAndroid Build Coastguard Worker let output = self.output().map_err(CommandError::IoError)?;
125*bb4ee6a4SAndroid Build Coastguard Worker if !output.status.success() {
126*bb4ee6a4SAndroid Build Coastguard Worker if let Some(code) = output.status.code() {
127*bb4ee6a4SAndroid Build Coastguard Worker return Err(CommandError::ErrorCode(code));
128*bb4ee6a4SAndroid Build Coastguard Worker } else {
129*bb4ee6a4SAndroid Build Coastguard Worker #[cfg(any(target_os = "android", target_os = "linux"))]
130*bb4ee6a4SAndroid Build Coastguard Worker if let Some(signal) = output.status.signal() {
131*bb4ee6a4SAndroid Build Coastguard Worker return Err(CommandError::Signal(signal));
132*bb4ee6a4SAndroid Build Coastguard Worker }
133*bb4ee6a4SAndroid Build Coastguard Worker panic!("No error code and no signal should never happen.");
134*bb4ee6a4SAndroid Build Coastguard Worker }
135*bb4ee6a4SAndroid Build Coastguard Worker }
136*bb4ee6a4SAndroid Build Coastguard Worker Ok(output)
137*bb4ee6a4SAndroid Build Coastguard Worker }
138*bb4ee6a4SAndroid Build Coastguard Worker
log(&mut self) -> &mut Self139*bb4ee6a4SAndroid Build Coastguard Worker fn log(&mut self) -> &mut Self {
140*bb4ee6a4SAndroid Build Coastguard Worker println!("$ {:?}", self);
141*bb4ee6a4SAndroid Build Coastguard Worker self
142*bb4ee6a4SAndroid Build Coastguard Worker }
143*bb4ee6a4SAndroid Build Coastguard Worker }
144*bb4ee6a4SAndroid Build Coastguard Worker
145*bb4ee6a4SAndroid Build Coastguard Worker /// Extension trait for utilities on std::process::Child
146*bb4ee6a4SAndroid Build Coastguard Worker pub trait ChildExt {
147*bb4ee6a4SAndroid Build Coastguard Worker /// 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>>148*bb4ee6a4SAndroid Build Coastguard Worker fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>;
149*bb4ee6a4SAndroid Build Coastguard Worker }
150*bb4ee6a4SAndroid Build Coastguard Worker
151*bb4ee6a4SAndroid Build Coastguard Worker impl ChildExt for std::process::Child {
wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>>152*bb4ee6a4SAndroid Build Coastguard Worker fn wait_with_timeout(&mut self, timeout: Duration) -> std::io::Result<Option<ExitStatus>> {
153*bb4ee6a4SAndroid Build Coastguard Worker let start_time = SystemTime::now();
154*bb4ee6a4SAndroid Build Coastguard Worker while SystemTime::now().duration_since(start_time).unwrap() < timeout {
155*bb4ee6a4SAndroid Build Coastguard Worker if let Ok(status) = self.try_wait() {
156*bb4ee6a4SAndroid Build Coastguard Worker return Ok(status);
157*bb4ee6a4SAndroid Build Coastguard Worker }
158*bb4ee6a4SAndroid Build Coastguard Worker thread::sleep(Duration::from_millis(10));
159*bb4ee6a4SAndroid Build Coastguard Worker }
160*bb4ee6a4SAndroid Build Coastguard Worker Err(std::io::Error::new(
161*bb4ee6a4SAndroid Build Coastguard Worker ErrorKind::TimedOut,
162*bb4ee6a4SAndroid Build Coastguard Worker "Timeout while waiting for child",
163*bb4ee6a4SAndroid Build Coastguard Worker ))
164*bb4ee6a4SAndroid Build Coastguard Worker }
165*bb4ee6a4SAndroid Build Coastguard Worker }
166*bb4ee6a4SAndroid Build Coastguard Worker
167*bb4ee6a4SAndroid Build Coastguard Worker /// Calls the `closure` until it returns a non-error Result.
168*bb4ee6a4SAndroid Build Coastguard Worker /// 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,169*bb4ee6a4SAndroid Build Coastguard Worker pub fn retry<F, T, E>(mut closure: F, retries: usize) -> Result<T, E>
170*bb4ee6a4SAndroid Build Coastguard Worker where
171*bb4ee6a4SAndroid Build Coastguard Worker F: FnMut() -> Result<T, E>,
172*bb4ee6a4SAndroid Build Coastguard Worker E: std::fmt::Debug,
173*bb4ee6a4SAndroid Build Coastguard Worker {
174*bb4ee6a4SAndroid Build Coastguard Worker let mut attempts_left = retries + 1;
175*bb4ee6a4SAndroid Build Coastguard Worker loop {
176*bb4ee6a4SAndroid Build Coastguard Worker let result = closure();
177*bb4ee6a4SAndroid Build Coastguard Worker attempts_left -= 1;
178*bb4ee6a4SAndroid Build Coastguard Worker if result.is_ok() || attempts_left == 0 {
179*bb4ee6a4SAndroid Build Coastguard Worker break result;
180*bb4ee6a4SAndroid Build Coastguard Worker } else {
181*bb4ee6a4SAndroid Build Coastguard Worker println!("Attempt failed: {:?}", result.err());
182*bb4ee6a4SAndroid Build Coastguard Worker }
183*bb4ee6a4SAndroid Build Coastguard Worker }
184*bb4ee6a4SAndroid Build Coastguard Worker }
185*bb4ee6a4SAndroid Build Coastguard Worker
186*bb4ee6a4SAndroid Build Coastguard Worker /// Prepare a temporary ext4 disk file.
prepare_disk_img() -> NamedTempFile187*bb4ee6a4SAndroid Build Coastguard Worker pub fn prepare_disk_img() -> NamedTempFile {
188*bb4ee6a4SAndroid Build Coastguard Worker let mut disk = NamedTempFile::new().unwrap();
189*bb4ee6a4SAndroid Build Coastguard Worker disk.as_file_mut().set_len(DEFAULT_BLOCK_SIZE).unwrap();
190*bb4ee6a4SAndroid Build Coastguard Worker
191*bb4ee6a4SAndroid Build Coastguard Worker // Add /sbin and /usr/sbin to PATH since some distributions put mkfs.ext4 in one of those
192*bb4ee6a4SAndroid Build Coastguard Worker // directories but don't add them to non-root PATH.
193*bb4ee6a4SAndroid Build Coastguard Worker let path = env::var("PATH").unwrap();
194*bb4ee6a4SAndroid Build Coastguard Worker let path = [&path, "/sbin", "/usr/sbin"].join(":");
195*bb4ee6a4SAndroid Build Coastguard Worker
196*bb4ee6a4SAndroid Build Coastguard Worker // TODO(b/243127910): Use `mkfs.ext4 -d` to include test data.
197*bb4ee6a4SAndroid Build Coastguard Worker Command::new("mkfs.ext4")
198*bb4ee6a4SAndroid Build Coastguard Worker .arg(disk.path().to_str().unwrap())
199*bb4ee6a4SAndroid Build Coastguard Worker .env("PATH", path)
200*bb4ee6a4SAndroid Build Coastguard Worker .output()
201*bb4ee6a4SAndroid Build Coastguard Worker .expect("failed to execute process");
202*bb4ee6a4SAndroid Build Coastguard Worker disk
203*bb4ee6a4SAndroid Build Coastguard Worker }
204*bb4ee6a4SAndroid Build Coastguard Worker
create_vu_block_config(cmd_type: CmdType, socket: &Path, disk: &Path) -> VuConfig205*bb4ee6a4SAndroid Build Coastguard Worker pub fn create_vu_block_config(cmd_type: CmdType, socket: &Path, disk: &Path) -> VuConfig {
206*bb4ee6a4SAndroid Build Coastguard Worker let socket_path = socket.to_str().unwrap();
207*bb4ee6a4SAndroid Build Coastguard Worker let disk_path = disk.to_str().unwrap();
208*bb4ee6a4SAndroid Build Coastguard Worker println!("disk={disk_path}, socket={socket_path}");
209*bb4ee6a4SAndroid Build Coastguard Worker match cmd_type {
210*bb4ee6a4SAndroid Build Coastguard Worker CmdType::Device => VuConfig::new(cmd_type, "block").extra_args(vec![
211*bb4ee6a4SAndroid Build Coastguard Worker "block".to_string(),
212*bb4ee6a4SAndroid Build Coastguard Worker "--socket-path".to_string(),
213*bb4ee6a4SAndroid Build Coastguard Worker socket_path.to_string(),
214*bb4ee6a4SAndroid Build Coastguard Worker "--file".to_string(),
215*bb4ee6a4SAndroid Build Coastguard Worker disk_path.to_string(),
216*bb4ee6a4SAndroid Build Coastguard Worker ]),
217*bb4ee6a4SAndroid Build Coastguard Worker CmdType::Devices => VuConfig::new(cmd_type, "block").extra_args(vec![
218*bb4ee6a4SAndroid Build Coastguard Worker "--block".to_string(),
219*bb4ee6a4SAndroid Build Coastguard Worker format!("vhost={},path={}", socket_path, disk_path),
220*bb4ee6a4SAndroid Build Coastguard Worker ]),
221*bb4ee6a4SAndroid Build Coastguard Worker }
222*bb4ee6a4SAndroid Build Coastguard Worker }
223*bb4ee6a4SAndroid Build Coastguard Worker
create_vu_console_multiport_config( socket: &Path, file_path: Vec<(PathBuf, PathBuf)>, ) -> VuConfig224*bb4ee6a4SAndroid Build Coastguard Worker pub fn create_vu_console_multiport_config(
225*bb4ee6a4SAndroid Build Coastguard Worker socket: &Path,
226*bb4ee6a4SAndroid Build Coastguard Worker file_path: Vec<(PathBuf, PathBuf)>,
227*bb4ee6a4SAndroid Build Coastguard Worker ) -> VuConfig {
228*bb4ee6a4SAndroid Build Coastguard Worker let socket_path = socket.to_str().unwrap();
229*bb4ee6a4SAndroid Build Coastguard Worker
230*bb4ee6a4SAndroid Build Coastguard Worker let mut args = vec![
231*bb4ee6a4SAndroid Build Coastguard Worker "console".to_string(),
232*bb4ee6a4SAndroid Build Coastguard Worker "--socket-path".to_string(),
233*bb4ee6a4SAndroid Build Coastguard Worker socket_path.to_string(),
234*bb4ee6a4SAndroid Build Coastguard Worker ];
235*bb4ee6a4SAndroid Build Coastguard Worker
236*bb4ee6a4SAndroid Build Coastguard Worker for (i, (output_file, input_file)) in file_path.iter().enumerate() {
237*bb4ee6a4SAndroid Build Coastguard Worker args.push("--port".to_string());
238*bb4ee6a4SAndroid Build Coastguard Worker match input_file.file_name().is_some() {
239*bb4ee6a4SAndroid Build Coastguard Worker true => {
240*bb4ee6a4SAndroid Build Coastguard Worker args.push(format!(
241*bb4ee6a4SAndroid Build Coastguard Worker "type=file,hardware=virtio-console,name=port{},path={},input={}",
242*bb4ee6a4SAndroid Build Coastguard Worker i,
243*bb4ee6a4SAndroid Build Coastguard Worker output_file.to_str().unwrap(),
244*bb4ee6a4SAndroid Build Coastguard Worker input_file.to_str().unwrap(),
245*bb4ee6a4SAndroid Build Coastguard Worker ));
246*bb4ee6a4SAndroid Build Coastguard Worker }
247*bb4ee6a4SAndroid Build Coastguard Worker false => {
248*bb4ee6a4SAndroid Build Coastguard Worker args.push(format!(
249*bb4ee6a4SAndroid Build Coastguard Worker "type=file,hardware=virtio-console,name=port{},path={}",
250*bb4ee6a4SAndroid Build Coastguard Worker i,
251*bb4ee6a4SAndroid Build Coastguard Worker output_file.to_str().unwrap(),
252*bb4ee6a4SAndroid Build Coastguard Worker ));
253*bb4ee6a4SAndroid Build Coastguard Worker }
254*bb4ee6a4SAndroid Build Coastguard Worker };
255*bb4ee6a4SAndroid Build Coastguard Worker }
256*bb4ee6a4SAndroid Build Coastguard Worker VuConfig::new(CmdType::Device, "console").extra_args(args)
257*bb4ee6a4SAndroid Build Coastguard Worker }
258