use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use tempfile::{tempdir, tempdir_in};
use test_log::test;
use vmtest::output::Output;
use vmtest::ui::Ui;
use vmtest::Mount;
use vmtest::{Config, Target, VMConfig};
mod helpers;
use helpers::*;
#[test]
fn test_run() {
let config = Config {
target: vec![
Target {
name: "uefi image boots with uefi flag".to_string(),
image: Some(asset("image-uefi.raw-efi")),
uefi: true,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
Target {
name: "not uefi image boots without uefi flag".to_string(),
image: Some(asset("image-not-uefi.raw")),
uefi: false,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let ui = Ui::new(vmtest);
let failed = ui.run(false);
assert_eq!(failed, 0);
}
#[test]
fn test_run_multiple_return_number_failures() {
let config = Config {
target: vec![
Target {
name: "uefi image boots with uefi flag".to_string(),
image: Some(asset("image-uefi.raw-efi")),
uefi: true,
command: "exit 1".to_string(),
..Default::default()
},
Target {
name: "uefi image boots with uefi flag 2".to_string(),
image: Some(asset("image-uefi.raw-efi")),
uefi: true,
command: "exit 1".to_string(),
..Default::default()
},
Target {
name: "not uefi image boots without uefi flag".to_string(),
image: Some(asset("image-not-uefi.raw")),
uefi: false,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let ui = Ui::new(vmtest);
let failed = ui.run(false);
assert_eq!(failed, 2);
}
#[test]
fn test_run_single_return_number_return_code() {
let config = Config {
target: vec![Target {
name: "not uefi image boots without uefi flag".to_string(),
image: Some(asset("image-not-uefi.raw")),
uefi: false,
command: "exit 12".to_string(),
..Default::default()
}],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let ui = Ui::new(vmtest);
let failed = ui.run(false);
assert_eq!(failed, 12);
}
#[test]
fn test_vmtest_infra_error() {
let config = Config {
target: vec![Target {
name: "not an actual image, should return EX_UNAVAILABLE".to_string(),
image: Some(asset("not_an_actual_image")),
uefi: false,
command: "exit 12".to_string(),
..Default::default()
}],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let ui = Ui::new(vmtest);
let failed = ui.run(false);
assert_eq!(failed, 69);
}
#[test]
fn test_run_one() {
let uefi_image = create_new_image(asset("image-uefi.raw-efi"));
let non_uefi_image = create_new_image(asset("image-not-uefi.raw"));
let config = Config {
target: vec![
Target {
name: "uefi image boots with uefi flag".to_string(),
image: Some(uefi_image.as_pathbuf()),
uefi: true,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
Target {
name: "not uefi image boots without uefi flag".to_string(),
image: Some(non_uefi_image.as_pathbuf()),
uefi: false,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
for i in 0..2 {
let (send, recv) = channel();
vmtest.run_one(i, send);
assert_no_err!(recv);
}
}
#[test]
fn test_run_out_of_bounds() {
let uefi_image = create_new_image(asset("image-uefi.raw-efi"));
let non_uefi_image = create_new_image(asset("image-not-uefi.raw"));
let config = Config {
target: vec![
Target {
name: "uefi image boots with uefi flag".to_string(),
image: Some(uefi_image.as_pathbuf()),
uefi: true,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
Target {
name: "not uefi image boots without uefi flag".to_string(),
image: Some(non_uefi_image.as_pathbuf()),
uefi: false,
command: "/mnt/vmtest/main.sh nixos".to_string(),
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let (send, recv) = channel();
vmtest.run_one(2, send);
assert_err!(recv, Output::BootEnd);
}
#[test]
fn test_not_uefi() {
let uefi_image = create_new_image(asset("image-uefi.raw-efi"));
let config = Config {
target: vec![Target {
name: "uefi image does not boot without uefi flag".to_string(),
image: Some(uefi_image.as_pathbuf()),
uefi: false,
command: "echo unreachable".to_string(),
..Default::default()
}],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_err!(recv, Output::BootEnd);
}
#[test]
fn test_command_runs_in_shell() {
let config = Config {
target: vec![Target {
name: "command is run in shell".to_string(),
kernel: Some(asset("bzImage-v5.15-default")),
command: "if true; then echo -n $0 > /mnt/vmtest/result; fi".to_string(),
..Default::default()
}],
};
let (vmtest, dir) = setup(config, &[]);
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_no_err!(recv);
let result_path = dir.path().join("result");
let result = fs::read_to_string(result_path).expect("Failed to read result");
assert_eq!(result, "bash");
}
#[test]
fn test_interactive_shell() {
let dir = tempdir().expect("Failed to create tempdir");
assert!(env::set_current_dir(dir.path()).is_ok());
let vmtest_bin_path = Path::new(env!("CARGO_BIN_EXE_vmtest"));
let command = format!(
"{} --kernel {} -",
vmtest_bin_path
.to_str()
.expect("Failed to convert vmtest path to str"),
asset("bzImage-v5.15-default").to_str().unwrap(),
);
let mut p = rexpect::spawn(&command, Some(30000)).expect("Failed to spawn vmtest");
p.exp_regex(".*root@.*#.*")
.expect("Did not get shell prompt");
p.send_line("sleep 10")
.expect("Failed to send sleep command");
p.send_line("echo \"hello world\" > /mnt/vmtest/result")
.expect("Failed to send echo command");
p.exp_regex(".*root@.*#.*")
.expect("Did not get shell prompt after sending command.");
p.send_line("exit").expect("Failed to send exit command.");
p.exp_eof().expect("vmtest did not return EOF");
let result_path = dir.path().join("result");
let result = fs::read_to_string(result_path).expect("Failed to read result");
assert_eq!(result, "hello world\n");
}
#[test]
fn test_kernel_target_env_var_propagation() {
let config = Config {
target: vec![Target {
name: "host env vars are propagated into guest".to_string(),
kernel: Some(asset("bzImage-v5.15-default")),
command: "echo -n $TEST_ENV_VAR > /mnt/vmtest/result".to_string(),
..Default::default()
}],
};
env::set_var("TEST_ENV_VAR", "test value");
let (vmtest, dir) = setup(config, &[]);
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_no_err!(recv);
let result_path = dir.path().join("result");
let result = fs::read_to_string(result_path).expect("Failed to read result");
assert_eq!(result, "test value");
}
#[test]
fn test_kernel_target_cwd_preserved() {
let config = Config {
target: vec![Target {
name: "host cwd preserved in guest".to_string(),
kernel: Some(asset("bzImage-v5.15-default")),
command: "cat text_file.txt".to_string(),
..Default::default()
}],
};
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let fixtures = root.join("tests/fixtures");
let vmtest = vmtest::Vmtest::new(fixtures, config).expect("Failed to construct vmtest");
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_no_err!(recv);
}
#[test]
fn test_command_process_substitution() {
let config = Config {
target: vec![Target {
name: "command can run process substitution".to_string(),
kernel: Some(asset("bzImage-v5.15-default")),
command: "cat <(echo -n $0) > /mnt/vmtest/result".to_string(),
..Default::default()
}],
};
let (vmtest, dir) = setup(config, &[]);
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_no_err!(recv);
let result_path = dir.path().join("result");
let result = fs::read_to_string(result_path).expect("Failed to read result");
assert_eq!(result, "bash");
}
#[test]
fn test_qemu_error_shown() {
let config = Config {
target: vec![Target {
name: "invalid kernel path".to_string(),
kernel: Some(asset("doesn't exist")),
command: "true".to_string(),
..Default::default()
}],
};
let (vmtest, _dir) = setup(config, &[]);
let (send, recv) = channel();
vmtest.run_one(0, send);
let err = assert_get_err!(recv, Output::BootEnd);
let msg = err.to_string();
assert!(msg.contains("qemu: could not open kernel file"));
}
#[test]
fn test_kernel_ro_flag() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let touch_dir = tempdir_in(root).expect("Failed to create tempdir");
let config = Config {
target: vec![Target {
name: "cannot touch host rootfs with ro".to_string(),
kernel: Some(asset("bzImage-v5.15-default")),
kernel_args: Some("ro".to_string()),
command: format!("touch {}/file", touch_dir.path().display()),
..Default::default()
}],
};
let (vmtest, _dir) = setup(config, &[]);
let (send, recv) = channel();
vmtest.run_one(0, send);
assert_err!(recv, Output::CommandEnd, i64);
}
#[test]
fn test_run_custom_resources() {
let uefi_image_t1 = create_new_image(asset("image-uefi.raw-efi"));
let uefi_image_t2 = create_new_image(asset("image-uefi.raw-efi"));
let config = Config {
target: vec![
Target {
name: "Custom number of CPUs".to_string(),
image: Some(uefi_image_t1.as_pathbuf()),
uefi: true,
command: r#"bash -xc "[[ "$(nproc)" == "1" ]]""#.into(),
vm: VMConfig {
num_cpus: 1,
..Default::default()
},
..Default::default()
},
Target {
name: "Custom amount of RAM".to_string(),
image: Some(uefi_image_t2.as_pathbuf()),
uefi: true,
command: r#"bash -xc "cat /proc/meminfo | grep 'MemTotal: 2..... kB'""#
.into(),
vm: VMConfig {
memory: "256M".into(),
..Default::default()
},
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
for i in 0..2 {
let (send, recv) = channel();
vmtest.run_one(i, send);
assert_no_err!(recv);
}
}
#[test]
fn test_run_custom_mounts() {
let uefi_image = create_new_image(asset("image-uefi.raw-efi"));
let config = Config {
target: vec![
Target {
name: "mount".to_string(),
image: Some(uefi_image.as_pathbuf()),
uefi: true,
command: r#"bash -xc "[[ -e /tmp/mount/README.md ]]""#.into(),
vm: VMConfig {
mounts: HashMap::from([(
"/tmp/mount".into(),
Mount {
host_path: Path::new(env!("CARGO_MANIFEST_DIR")).into(),
writable: true,
},
)]),
..Default::default()
},
..Default::default()
},
Target {
name: "RO mount".to_string(),
image: Some(uefi_image.as_pathbuf()),
uefi: true,
command: r#"bash -xc "(touch /tmp/ro/hi && exit -1) || true""#.into(),
vm: VMConfig {
mounts: HashMap::from([(
"/tmp/ro".into(),
Mount {
host_path: Path::new(env!("CARGO_MANIFEST_DIR")).into(),
writable: false,
},
)]),
..Default::default()
},
..Default::default()
},
],
};
let (vmtest, _dir) = setup(config, &["main.sh"]);
for i in 0..2 {
let (send, recv) = channel();
vmtest.run_one(i, send);
assert_no_err!(recv);
}
}