Skip to content

Commit aa94400

Browse files
DolceTriadedanobi
authored andcommitted
tests: Make tests more deterministic.
Run each test in a clean image by using a CoW image to save space. Only blocks that are modified by the test are actually written to disk (ends up being 5 MiB max for some tests). Also, explicitly set the bootindex for the boot devices to ensure that they are booted first by the QEMU UEFI firmware.
1 parent 03f7556 commit aa94400

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
run: |
3737
sudo apt-get update
3838
# Virtualization deps
39-
sudo apt-get install -y qemu-system-x86-64 qemu-guest-agent ovmf
39+
sudo apt-get install -y qemu-system-x86-64 qemu-guest-agent qemu-utils ovmf
4040
4141
- name: Cache test assets
4242
uses: actions/cache@v3

src/qemu.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,19 @@ fn gen_init() -> Result<NamedTempFile> {
103103
/// Generate arguments for inserting a file as a drive into the guest
104104
fn drive_args(file: &Path, index: u32) -> Vec<OsString> {
105105
let mut args: Vec<OsString> = Vec::new();
106-
106+
let disk_id = format!("disk{}", hash(file));
107107
args.push("-drive".into());
108108
args.push(
109109
format!(
110-
"file={},index={},media=disk,if=virtio",
110+
"file={},index={},media=disk,if=none,id={}",
111111
file.display(),
112-
index
112+
index,
113+
disk_id
113114
)
114115
.into(),
115116
);
117+
args.push("-device".into());
118+
args.push(format!("virtio-blk-pci,drive={},bootindex={}", disk_id, index).into());
116119

117120
args
118121
}
@@ -282,7 +285,7 @@ fn kernel_args(kernel: &Path, init: &Path, additional_kargs: Option<&String>) ->
282285
args
283286
}
284287

285-
fn hash(s: &PathBuf) -> u64 {
288+
fn hash<T: Hash + ?Sized>(s: &T) -> u64 {
286289
let mut h = std::collections::hash_map::DefaultHasher::new();
287290
s.hash(&mut h);
288291

tests/test.rs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ use std::collections::HashMap;
22
use std::env;
33
use std::fs;
44
use std::path::Path;
5+
use std::path::PathBuf;
6+
use std::process::Command;
57
use std::sync::mpsc::channel;
68

79
use lazy_static::lazy_static;
10+
use log::error;
11+
use rand::Rng;
812
use regex::Regex;
913
use tempfile::tempdir_in;
1014
use test_log::test;
@@ -21,6 +25,43 @@ lazy_static! {
2125
static ref FILTER_ALL: Regex = Regex::new(".*").unwrap();
2226
}
2327

28+
fn gen_image_name() -> PathBuf {
29+
let mut path = PathBuf::new();
30+
path.push("/tmp");
31+
32+
let id = rand::thread_rng().gen_range(100_000..1_000_000);
33+
let image = format!("/tmp/image-{id}.qcow2");
34+
path.push(image);
35+
36+
path
37+
}
38+
39+
// Create a CoW image to ensure each test runs in a clean image.
40+
fn create_new_image(image: PathBuf) -> Option<PathBuf> {
41+
let out_image = gen_image_name();
42+
let out = Command::new("qemu-img")
43+
.arg("create")
44+
.arg("-F")
45+
.arg("raw")
46+
.arg("-b")
47+
.arg(image)
48+
.arg("-f")
49+
.arg("qcow2")
50+
.arg(out_image.clone())
51+
.output()
52+
.expect("error creating image file");
53+
if !out.status.success() {
54+
error!(
55+
"error creating image file: out={} err={} status={}",
56+
std::str::from_utf8(&out.stdout).unwrap(),
57+
std::str::from_utf8(&out.stderr).unwrap(),
58+
out.status
59+
);
60+
return None;
61+
}
62+
Some(out_image)
63+
}
64+
2465
// Expect that we can run the entire matrix successfully
2566
#[test]
2667
fn test_run() {
@@ -59,7 +100,7 @@ fn test_run_one() {
59100
target: vec![
60101
Target {
61102
name: "uefi image boots with uefi flag".to_string(),
62-
image: Some(asset("image-uefi.raw-efi")),
103+
image: create_new_image(asset("image-uefi.raw-efi")),
63104
uefi: true,
64105
command: "/mnt/vmtest/main.sh nixos".to_string(),
65106
kernel: None,
@@ -68,7 +109,7 @@ fn test_run_one() {
68109
},
69110
Target {
70111
name: "not uefi image boots without uefi flag".to_string(),
71-
image: Some(asset("image-not-uefi.raw")),
112+
image: create_new_image(asset("image-not-uefi.raw")),
72113
uefi: false,
73114
command: "/mnt/vmtest/main.sh nixos".to_string(),
74115
kernel: None,
@@ -92,7 +133,7 @@ fn test_run_out_of_bounds() {
92133
target: vec![
93134
Target {
94135
name: "uefi image boots with uefi flag".to_string(),
95-
image: Some(asset("image-uefi.raw-efi")),
136+
image: create_new_image(asset("image-uefi.raw-efi")),
96137
uefi: true,
97138
command: "/mnt/vmtest/main.sh nixos".to_string(),
98139
kernel: None,
@@ -101,7 +142,7 @@ fn test_run_out_of_bounds() {
101142
},
102143
Target {
103144
name: "not uefi image boots without uefi flag".to_string(),
104-
image: Some(asset("image-not-uefi.raw")),
145+
image: create_new_image(asset("image-not-uefi.raw")),
105146
uefi: false,
106147
command: "/mnt/vmtest/main.sh nixos".to_string(),
107148
kernel: None,
@@ -122,7 +163,7 @@ fn test_not_uefi() {
122163
let config = Config {
123164
target: vec![Target {
124165
name: "uefi image does not boot without uefi flag".to_string(),
125-
image: Some(asset("image-uefi.raw-efi")),
166+
image: create_new_image(asset("image-uefi.raw-efi")),
126167
uefi: false,
127168
command: "echo unreachable".to_string(),
128169
kernel: None,
@@ -274,7 +315,7 @@ fn test_run_custom_resources() {
274315
target: vec![
275316
Target {
276317
name: "Custom number of CPUs".to_string(),
277-
image: Some(asset("image-uefi.raw-efi")),
318+
image: create_new_image(asset("image-uefi.raw-efi")),
278319
uefi: true,
279320
command: r#"bash -xc "[[ "$(nproc)" == "1" ]]""#.into(),
280321
kernel: None,
@@ -286,9 +327,10 @@ fn test_run_custom_resources() {
286327
},
287328
Target {
288329
name: "Custom amount of RAM".to_string(),
289-
image: Some(asset("image-uefi.raw-efi")),
330+
image: create_new_image(asset("image-uefi.raw-efi")),
290331
uefi: true,
291-
command: r#"bash -xc "cat /proc/meminfo | grep 'MemTotal: 222204 kB'""#
332+
// Should be in the 200 thousands, but it's variable.
333+
command: r#"bash -xc "cat /proc/meminfo | grep 'MemTotal: 2..... kB'""#
292334
.into(),
293335
kernel: None,
294336
kernel_args: None,
@@ -313,7 +355,7 @@ fn test_run_custom_mounts() {
313355
target: vec![
314356
Target {
315357
name: "mount".to_string(),
316-
image: Some(asset("image-uefi.raw-efi")),
358+
image: create_new_image(asset("image-uefi.raw-efi")),
317359
uefi: true,
318360
command: r#"bash -xc "[[ -e /tmp/mount/README.md ]]""#.into(),
319361
kernel: None,
@@ -331,7 +373,7 @@ fn test_run_custom_mounts() {
331373
},
332374
Target {
333375
name: "RO mount".to_string(),
334-
image: Some(asset("image-uefi.raw-efi")),
376+
image: create_new_image(asset("image-uefi.raw-efi")),
335377
uefi: true,
336378
command: r#"bash -xc "(touch /tmp/ro/hi && exit -1) || true""#.into(),
337379
kernel: None,

0 commit comments

Comments
 (0)