use std::env;
use std::fs;
use std::mem::{discriminant, Discriminant};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::mpsc::Receiver;
use anyhow::{anyhow, Error};
use rand::Rng;
use tempfile::{tempdir, TempDir};
use vmtest::output::Output;
use vmtest::vmtest::Vmtest;
use vmtest::Config;
pub fn asset(name: &str) -> PathBuf {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
root.join("tests/.assets").join(name)
}
pub fn setup(config: Config, fixtures: &[&str]) -> (Vmtest, TempDir) {
let dir = tempdir().expect("Failed to create tempdir");
for fixture in fixtures {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let file = root.join("tests/fixtures").join(fixture);
fs::copy(file, dir.path().join(fixture)).expect("Failed to copy fixture");
}
let vmtest = vmtest::Vmtest::new(dir.path(), config).expect("Failed to construct vmtest");
(vmtest, dir)
}
#[doc(hidden)]
pub fn get_error(recv: Receiver<Output>, disc: Option<Discriminant<Output>>) -> Option<Error> {
loop {
let msg = match recv.recv() {
Ok(m) => m,
Err(_) => break,
};
let msg_disc = discriminant(&msg);
match msg {
Output::BootEnd(Err(e)) | Output::SetupEnd(Err(e)) | Output::CommandEnd(Err(e)) => {
if let Some(d) = disc {
if msg_disc == d {
return Some(e);
}
} else {
return Some(e);
}
}
Output::Command(s) => println!("Command output={s}"),
Output::CommandEnd(Ok(rc)) => {
if let Some(d) = disc {
if msg_disc == d && rc != 0 {
return Some(anyhow!("Command failed with {}", rc));
}
} else if rc != 0 {
return Some(anyhow!("Command failed with {}", rc));
}
}
_ => (),
};
}
None
}
#[macro_export]
macro_rules! assert_err {
($recv:expr, $variant:path) => {
use std::mem::discriminant;
let d = discriminant(&$variant(Ok(())));
assert!(dbg!(get_error($recv, Some(d))).is_some());
};
($recv:expr, $variant:path, $ty:ty) => {
use std::mem::discriminant;
let d = discriminant(&$variant(Ok(<$ty>::default())));
assert!(dbg!(get_error($recv, Some(d))).is_some());
};
}
#[macro_export]
macro_rules! assert_get_err {
($recv:expr, $variant:path) => {{
use std::mem::discriminant;
let d = discriminant(&$variant(Ok(())));
get_error($recv, Some(d)).expect("Failed to find error")
}};
}
#[macro_export]
macro_rules! assert_no_err {
($recv:expr) => {
assert!(dbg!(get_error($recv, None)).is_none());
};
}
fn gen_image_name() -> PathBuf {
let mut path = PathBuf::new();
path.push("/tmp");
let id = rand::thread_rng().gen_range(100_000..1_000_000);
let image = format!("/tmp/image-{id}.qcow2");
path.push(image);
path
}
pub struct CowImage {
path: PathBuf,
}
impl CowImage {
fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn as_pathbuf(&self) -> PathBuf {
self.path.clone()
}
}
impl Drop for CowImage {
fn drop(&mut self) {
fs::remove_file(&self.path).expect("Failed to remove CoW'd image");
}
}
pub fn create_new_image(image: PathBuf) -> CowImage {
let out_image = gen_image_name();
let out = Command::new("qemu-img")
.arg("create")
.arg("-F")
.arg("raw")
.arg("-b")
.arg(image)
.arg("-f")
.arg("qcow2")
.arg(out_image.clone())
.output()
.expect("error creating image file");
if !out.status.success() {
panic!(
"error creating image file: out={} err={} status={}",
std::str::from_utf8(&out.stdout).unwrap(),
std::str::from_utf8(&out.stderr).unwrap(),
out.status
);
}
CowImage::new(out_image)
}