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