1 // Copyright 2020 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 use std::time::Duration;
6
7 use fixture::vm::Config;
8 use fixture::vm::TestVm;
9
10 #[test]
boot_test_vm() -> anyhow::Result<()>11 fn boot_test_vm() -> anyhow::Result<()> {
12 let mut vm = TestVm::new(Config::new()).unwrap();
13 assert_eq!(vm.exec_in_guest("echo 42")?.stdout.trim(), "42");
14 Ok(())
15 }
16
17 #[test]
boot_custom_vm_kernel_initrd() -> anyhow::Result<()>18 fn boot_custom_vm_kernel_initrd() -> anyhow::Result<()> {
19 let cfg = Config::new()
20 .with_kernel("https://storage.googleapis.com/crosvm/integration_tests/benchmarks/custom-guest-bzimage-x86_64-r0001")
21 .with_initrd("https://storage.googleapis.com/crosvm/integration_tests/benchmarks/custom-initramfs.cpio.gz-r0005")
22 // Use a non-sense file as rootfs to prove delegate correctly function in initrd
23 .with_rootfs("https://storage.googleapis.com/crosvm/integration_tests/guest-bzimage-aarch64-r0007")
24 .with_stdout_hardware("serial").extra_args(vec!["--mem".to_owned(), "512".to_owned()]);
25 let mut vm = TestVm::new(cfg).unwrap();
26 assert_eq!(
27 vm.exec_in_guest_async("echo 42")?
28 .with_timeout(Duration::from_secs(500))
29 .wait_ok(&mut vm)?
30 .stdout
31 .trim(),
32 "42"
33 );
34 Ok(())
35 }
36
37 #[test]
boot_test_vm_uring() -> anyhow::Result<()>38 fn boot_test_vm_uring() -> anyhow::Result<()> {
39 let mut vm = TestVm::new(
40 Config::new().extra_args(vec!["--async-executor".to_string(), "uring".to_string()]),
41 )
42 .unwrap();
43 assert_eq!(vm.exec_in_guest("echo 42")?.stdout.trim(), "42");
44 Ok(())
45 }
46
47 #[cfg(any(target_os = "android", target_os = "linux"))]
48 #[test]
boot_test_vm_odirect()49 fn boot_test_vm_odirect() {
50 let mut vm = TestVm::new(Config::new().o_direct()).unwrap();
51 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
52 }
53
54 #[cfg(any(target_os = "android", target_os = "linux"))]
55 #[test]
boot_test_vm_config_file()56 fn boot_test_vm_config_file() {
57 let mut vm = TestVm::new_with_config_file(Config::new()).unwrap();
58 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
59 }
60
61 /*
62 * VCPU-level suspend/resume tests (which does NOT suspend the devices)
63 */
64
65 #[cfg(any(target_os = "android", target_os = "linux"))]
66 #[test]
vcpu_suspend_resume_succeeds()67 fn vcpu_suspend_resume_succeeds() {
68 // There is no easy way for us to check if the VM is actually suspended. But at
69 // least exercise the code-path.
70 let mut vm = TestVm::new(Config::new()).unwrap();
71 vm.suspend().unwrap();
72 vm.resume().unwrap();
73 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
74 }
75
76 #[cfg(any(target_os = "android", target_os = "linux"))]
77 #[test]
vcpu_suspend_resume_succeeds_with_pvclock()78 fn vcpu_suspend_resume_succeeds_with_pvclock() {
79 // There is no easy way for us to check if the VM is actually suspended. But at
80 // least exercise the code-path.
81 let mut config = Config::new();
82 config = config.extra_args(vec!["--pvclock".to_string()]);
83 let mut vm = TestVm::new(config).unwrap();
84 vm.suspend().unwrap();
85 vm.resume().unwrap();
86 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
87 }
88
89 /*
90 * Full suspend/resume tests (which suspend the devices and vcpus)
91 */
92
93 #[cfg(any(target_os = "android", target_os = "linux"))]
94 #[test]
full_suspend_resume_test_suspend_resume_full()95 fn full_suspend_resume_test_suspend_resume_full() {
96 // There is no easy way for us to check if the VM is actually suspended. But at
97 // least exercise the code-path.
98 let mut config = Config::new();
99 // Why this test is called "full"? Can anyone explain...?
100 config = config.extra_args(vec![
101 "--no-usb".to_string(),
102 "--no-balloon".to_string(),
103 "--no-rng".to_string(),
104 ]);
105 let mut vm = TestVm::new(config).unwrap();
106 vm.suspend_full().unwrap();
107 vm.resume_full().unwrap();
108 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
109 }
110
111 #[cfg(any(target_os = "android", target_os = "linux"))]
112 #[test]
full_suspend_resume_with_pvclock()113 fn full_suspend_resume_with_pvclock() {
114 // There is no easy way for us to check if the VM is actually suspended. But at
115 // least exercise the code-path.
116 let mut config = Config::new();
117 config = config.extra_args(vec![
118 "--no-usb".to_string(),
119 "--no-balloon".to_string(),
120 "--no-rng".to_string(),
121 "--pvclock".to_string(),
122 ]);
123 let mut vm = TestVm::new(config).unwrap();
124 vm.suspend_full().unwrap();
125 vm.resume_full().unwrap();
126 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
127 }
128
129 #[cfg(any(target_os = "android", target_os = "linux"))]
130 #[test]
vcpu_suspend_resume_with_pvclock_adjusts_guest_clocks()131 fn vcpu_suspend_resume_with_pvclock_adjusts_guest_clocks() {
132 use readclock::ClockValues;
133
134 // SUSPEND_DURATION defines how long the VM should be suspended
135 const SUSPEND_DURATION: Duration = Duration::from_secs(2);
136 const ALLOWANCE: Duration = Duration::from_secs(1);
137
138 // Launch a VM with pvclock option
139 let mut config = Config::new();
140 config = config.extra_args(vec![
141 "--no-usb".to_string(),
142 "--no-balloon".to_string(),
143 "--no-rng".to_string(),
144 "--pvclock".to_string(),
145 ]);
146 let mut vm = TestVm::new(config).unwrap();
147
148 // Mount the proc fs
149 vm.exec_in_guest("mount proc /proc -t proc").unwrap();
150 // Ensure that the kernel has virtio-pvclock
151 assert_eq!(
152 vm.exec_in_guest("cat /proc/config.gz | gunzip | grep '^CONFIG_VIRTIO_PVCLOCK'")
153 .unwrap()
154 .stdout
155 .trim(),
156 "CONFIG_VIRTIO_PVCLOCK=y"
157 );
158
159 let guest_clocks_before = vm.guest_clock_values().unwrap();
160 let host_clocks_before = ClockValues::now();
161 vm.suspend().unwrap();
162 println!("Sleeping {SUSPEND_DURATION:?}...");
163 std::thread::sleep(SUSPEND_DURATION);
164 vm.resume().unwrap();
165 // Sleep a bit, to give the guest a chance to move the CLOCK_BOOTTIME value forward.
166 std::thread::sleep(SUSPEND_DURATION);
167 let guest_clocks_after = vm.guest_clock_values().unwrap();
168 let host_clocks_after = ClockValues::now();
169 // Calculating in f64 since the result may be negative
170 let guest_mono_diff = guest_clocks_after.clock_monotonic().as_secs_f64()
171 - guest_clocks_before.clock_monotonic().as_secs_f64();
172 let guest_boot_diff = guest_clocks_after.clock_boottime().as_secs_f64()
173 - guest_clocks_before.clock_boottime().as_secs_f64();
174 let host_boot_diff = host_clocks_after.clock_boottime().as_secs_f64()
175 - host_clocks_before.clock_boottime().as_secs_f64();
176
177 assert!(host_boot_diff > SUSPEND_DURATION.as_secs_f64());
178 // Although the BOOTTIME and MONOTONIC behavior varies in general for some real-world factors
179 // like the implementation of the kernel, the virtualization platforms and hardware issues,
180 // when virtio-pvclock is in use, crosvm does its best effort to maintain the following
181 // invariants to make the guest's userland peaceful:
182
183 // Invariants 1: Guest's MONOTONIC behaves as if they are stopped during the VM is suspended in
184 // terms of crosvm's VM instance running state. In other words, the guest's monotonic
185 // difference is smaller than the "real" time experienced by the host by SUSPEND_DURATION.
186 let monotonic_error = guest_mono_diff + SUSPEND_DURATION.as_secs_f64() - host_boot_diff;
187 assert!(monotonic_error < ALLOWANCE.as_secs_f64());
188
189 // Invariants 2: Subtracting Guest's MONOTONIC from the Guest's BOOTTIME should be
190 // equal to the total duration that the VM was in the "suspended" state as noted
191 // in the Invariants 1.
192 let guest_suspend_duration = guest_boot_diff - guest_mono_diff;
193 let boottime_error = (guest_suspend_duration - SUSPEND_DURATION.as_secs_f64()).abs();
194 assert!(boottime_error < ALLOWANCE.as_secs_f64());
195 }
196
197 #[cfg(any(target_os = "android", target_os = "linux"))]
198 #[test]
boot_test_vm_disable_sandbox()199 fn boot_test_vm_disable_sandbox() {
200 let mut vm = TestVm::new(Config::new().disable_sandbox()).unwrap();
201 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
202 }
203
204 #[cfg(any(target_os = "android", target_os = "linux"))]
205 #[test]
boot_test_vm_disable_sandbox_odirect()206 fn boot_test_vm_disable_sandbox_odirect() {
207 let mut vm = TestVm::new(Config::new().disable_sandbox().o_direct()).unwrap();
208 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
209 }
210
211 #[cfg(any(target_os = "android", target_os = "linux"))]
212 #[test]
boot_test_vm_disable_sandbox_config_file()213 fn boot_test_vm_disable_sandbox_config_file() {
214 let mut vm = TestVm::new_with_config_file(Config::new().disable_sandbox()).unwrap();
215 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
216 }
217
218 #[cfg(any(target_os = "android", target_os = "linux"))]
219 #[test]
boot_test_disable_sandbox_suspend_resume()220 fn boot_test_disable_sandbox_suspend_resume() {
221 // There is no easy way for us to check if the VM is actually suspended. But at
222 // least exercise the code-path.
223 let mut vm = TestVm::new(Config::new().disable_sandbox()).unwrap();
224 vm.suspend().unwrap();
225 vm.resume().unwrap();
226 assert_eq!(vm.exec_in_guest("echo 42").unwrap().stdout.trim(), "42");
227 }
228