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 use std::env;
6 use std::io::Write;
7 use std::path::Path;
8 use std::path::PathBuf;
9 use std::process::Command;
10 use std::sync::Once;
11 use std::time::Duration;
12
13 use anyhow::anyhow;
14 use anyhow::bail;
15 use anyhow::Context;
16 use anyhow::Result;
17 use base::syslog;
18 use base::test_utils::check_can_sudo;
19 use crc32fast::hash;
20 use delegate::wire_format::DelegateMessage;
21 use delegate::wire_format::ExitStatus;
22 use delegate::wire_format::GuestToHostMessage;
23 use delegate::wire_format::HostToGuestMessage;
24 use delegate::wire_format::ProgramExit;
25 use log::info;
26 use log::Level;
27 use prebuilts::download_file;
28 use readclock::ClockValues;
29 use url::Url;
30
31 use crate::sys::SerialArgs;
32 use crate::sys::TestVmSys;
33 use crate::utils::run_with_timeout;
34
35 const PREBUILT_URL: &str = "https://storage.googleapis.com/crosvm/integration_tests";
36
37 #[cfg(target_arch = "x86_64")]
38 const ARCH: &str = "x86_64";
39 #[cfg(target_arch = "arm")]
40 const ARCH: &str = "arm";
41 #[cfg(target_arch = "aarch64")]
42 const ARCH: &str = "aarch64";
43 #[cfg(target_arch = "riscv64")]
44 const ARCH: &str = "riscv64";
45
46 /// Timeout when waiting for pipes that are expected to be ready.
47 const COMMUNICATION_TIMEOUT: Duration = Duration::from_secs(5);
48
49 /// Timeout for the VM to boot and the delegate to report that it's ready.
50 const BOOT_TIMEOUT: Duration = Duration::from_secs(60);
51
52 /// Default timeout when waiting for guest commands to execute
53 const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(10);
54
prebuilt_version() -> &'static str55 fn prebuilt_version() -> &'static str {
56 include_str!("../../guest_under_test/PREBUILT_VERSION").trim()
57 }
58
kernel_prebuilt_url_string() -> Url59 fn kernel_prebuilt_url_string() -> Url {
60 Url::parse(&format!(
61 "{}/guest-bzimage-{}-{}",
62 PREBUILT_URL,
63 ARCH,
64 prebuilt_version()
65 ))
66 .unwrap()
67 }
68
rootfs_prebuilt_url_string() -> Url69 fn rootfs_prebuilt_url_string() -> Url {
70 Url::parse(&format!(
71 "{}/guest-rootfs-{}-{}",
72 PREBUILT_URL,
73 ARCH,
74 prebuilt_version()
75 ))
76 .unwrap()
77 }
78
local_path_from_url(url: &Url) -> PathBuf79 pub(super) fn local_path_from_url(url: &Url) -> PathBuf {
80 if url.scheme() == "file" {
81 return url.to_file_path().unwrap();
82 }
83 if url.scheme() != "http" && url.scheme() != "https" {
84 panic!("Only file, http, https URLs are supported for artifacts")
85 }
86 env::current_exe().unwrap().parent().unwrap().join(format!(
87 "e2e_prebuilt-{:x}-{:x}",
88 hash(url.as_str().as_bytes()),
89 hash(url.path().as_bytes())
90 ))
91 }
92
93 /// Represents a command running in the guest. See `TestVm::exec_in_guest_async()`
94 #[must_use]
95 pub struct GuestProcess {
96 command: String,
97 timeout: Duration,
98 }
99
100 impl GuestProcess {
with_timeout(self, duration: Duration) -> Self101 pub fn with_timeout(self, duration: Duration) -> Self {
102 Self {
103 timeout: duration,
104 ..self
105 }
106 }
107
108 /// Waits for the process to finish execution and return ExitStatus.
109 /// Will fail on a non-zero exit code.
wait_ok(self, vm: &mut TestVm) -> Result<ProgramExit>110 pub fn wait_ok(self, vm: &mut TestVm) -> Result<ProgramExit> {
111 let command = self.command.clone();
112 let result = self.wait_result(vm)?;
113
114 match &result.exit_status {
115 ExitStatus::Code(0) => Ok(result),
116 ExitStatus::Code(code) => {
117 bail!("Command `{}` terminated with exit code {}", command, code)
118 }
119 ExitStatus::Signal(code) => bail!("Command `{}` stopped with signal {}", command, code),
120 ExitStatus::None => bail!("Command `{}` stopped for unknown reason", command),
121 }
122 }
123
124 /// Same as `wait_ok` but will return a ExitStatus instead of failing on a non-zero exit code,
125 /// will only fail when cannot receive output from guest.
wait_result(self, vm: &mut TestVm) -> Result<ProgramExit>126 pub fn wait_result(self, vm: &mut TestVm) -> Result<ProgramExit> {
127 let message = vm.read_message_from_guest(self.timeout).with_context(|| {
128 format!(
129 "Command `{}`: Failed to read response from guest",
130 self.command
131 )
132 })?;
133 // VM is ready when receiving any message (as for current protocol)
134 match message {
135 GuestToHostMessage::ProgramExit(exit_info) => Ok(exit_info),
136 _ => bail!("Receive other message when anticipating ProgramExit"),
137 }
138 }
139 }
140
141 /// Configuration to start `TestVm`.
142 pub struct Config {
143 /// Extra arguments for the `run` subcommand.
144 pub(super) extra_args: Vec<String>,
145
146 /// Use `O_DIRECT` for the rootfs.
147 pub(super) o_direct: bool,
148
149 /// Log level of `TestVm`
150 pub(super) log_level: Level,
151
152 /// File to save crosvm log to
153 pub(super) log_file: Option<String>,
154
155 /// Wrapper command line for executing `TestVM`
156 pub(super) wrapper_cmd: Option<String>,
157
158 /// Url to kernel image
159 pub(super) kernel_url: Url,
160
161 /// Url to initrd image
162 pub(super) initrd_url: Option<Url>,
163
164 /// Url to rootfs image
165 pub(super) rootfs_url: Option<Url>,
166
167 /// If rootfs image is writable
168 pub(super) rootfs_rw: bool,
169
170 /// If rootfs image is zstd compressed
171 pub(super) rootfs_compressed: bool,
172
173 /// Console hardware type
174 pub(super) console_hardware: String,
175 }
176
177 impl Default for Config {
default() -> Self178 fn default() -> Self {
179 Self {
180 log_level: Level::Info,
181 extra_args: Default::default(),
182 o_direct: Default::default(),
183 log_file: None,
184 wrapper_cmd: None,
185 kernel_url: kernel_prebuilt_url_string(),
186 initrd_url: None,
187 rootfs_url: Some(rootfs_prebuilt_url_string()),
188 rootfs_rw: false,
189 rootfs_compressed: false,
190 console_hardware: "virtio-console".to_owned(),
191 }
192 }
193 }
194
195 impl Config {
196 /// Creates a new `run` command with `extra_args`.
new() -> Self197 pub fn new() -> Self {
198 Self::from_env()
199 }
200
201 /// Uses extra arguments for `crosvm run`.
extra_args(mut self, args: Vec<String>) -> Self202 pub fn extra_args(mut self, args: Vec<String>) -> Self {
203 let mut args = args;
204 self.extra_args.append(&mut args);
205 self
206 }
207
208 /// Uses `O_DIRECT` for the rootfs.
o_direct(mut self) -> Self209 pub fn o_direct(mut self) -> Self {
210 self.o_direct = true;
211 self
212 }
213
214 /// Uses `disable-sandbox` argument for `crosvm run`.
disable_sandbox(mut self) -> Self215 pub fn disable_sandbox(mut self) -> Self {
216 self.extra_args.push("--disable-sandbox".to_string());
217 self
218 }
219
from_env() -> Self220 pub fn from_env() -> Self {
221 let mut cfg: Config = Default::default();
222 if let Ok(wrapper_cmd) = env::var("CROSVM_CARGO_TEST_E2E_WRAPPER_CMD") {
223 cfg.wrapper_cmd = Some(wrapper_cmd);
224 }
225 if let Ok(log_file) = env::var("CROSVM_CARGO_TEST_LOG_FILE") {
226 cfg.log_file = Some(log_file);
227 }
228 if env::var("CROSVM_CARGO_TEST_LOG_LEVEL_DEBUG").is_ok() {
229 cfg.log_level = Level::Debug;
230 }
231 if let Ok(kernel_url) = env::var("CROSVM_CARGO_TEST_KERNEL_IMAGE") {
232 info!("Using overrided kernel from env CROSVM_CARGO_TEST_KERNEL_IMAGE={kernel_url}");
233 cfg.kernel_url = Url::from_file_path(kernel_url).unwrap();
234 }
235 if let Ok(initrd_url) = env::var("CROSVM_CARGO_TEST_INITRD_IMAGE") {
236 info!("Using overrided kernel from env CROSVM_CARGO_TEST_INITRD_IMAGE={initrd_url}");
237 cfg.initrd_url = Some(Url::from_file_path(initrd_url).unwrap());
238 }
239 if let Ok(rootfs_url) = env::var("CROSVM_CARGO_TEST_ROOTFS_IMAGE") {
240 info!("Using overrided kernel from env CROSVM_CARGO_TEST_ROOTFS_IMAGE={rootfs_url}");
241 cfg.rootfs_url = Some(Url::from_file_path(rootfs_url).unwrap());
242 }
243 cfg
244 }
245
with_kernel(mut self, url: &str) -> Self246 pub fn with_kernel(mut self, url: &str) -> Self {
247 self.kernel_url = Url::parse(url).unwrap();
248 self
249 }
250
with_initrd(mut self, url: &str) -> Self251 pub fn with_initrd(mut self, url: &str) -> Self {
252 self.initrd_url = Some(Url::parse(url).unwrap());
253 self
254 }
255
with_rootfs(mut self, url: &str) -> Self256 pub fn with_rootfs(mut self, url: &str) -> Self {
257 self.rootfs_url = Some(Url::parse(url).unwrap());
258 self
259 }
260
rootfs_is_rw(mut self) -> Self261 pub fn rootfs_is_rw(mut self) -> Self {
262 self.rootfs_rw = true;
263 self
264 }
265
rootfs_is_compressed(mut self) -> Self266 pub fn rootfs_is_compressed(mut self) -> Self {
267 self.rootfs_compressed = true;
268 self
269 }
270
with_stdout_hardware(mut self, hw_type: &str) -> Self271 pub fn with_stdout_hardware(mut self, hw_type: &str) -> Self {
272 self.console_hardware = hw_type.into();
273 self
274 }
275
with_vhost_user(mut self, device_type: &str, socket_path: &Path) -> Self276 pub fn with_vhost_user(mut self, device_type: &str, socket_path: &Path) -> Self {
277 self.extra_args.push("--vhost-user".to_string());
278 self.extra_args.push(format!(
279 "{},socket={}",
280 device_type,
281 socket_path.to_str().unwrap()
282 ));
283 self
284 }
285
286 #[cfg(any(target_os = "android", target_os = "linux"))]
with_vhost_user_fs(mut self, socket_path: &Path, tag: &str) -> Self287 pub fn with_vhost_user_fs(mut self, socket_path: &Path, tag: &str) -> Self {
288 self.extra_args.push("--vhost-user-fs".to_string());
289 self.extra_args
290 .push(format!("{},tag={}", socket_path.to_str().unwrap(), tag));
291 self
292 }
293 }
294
295 static PREP_ONCE: Once = Once::new();
296
297 /// Test fixture to spin up a VM running a guest that can be communicated with.
298 ///
299 /// After creation, commands can be sent via exec_in_guest. The VM is stopped
300 /// when this instance is dropped.
301 pub struct TestVm {
302 // Platform-dependent bits
303 sys: TestVmSys,
304 // The guest is ready to receive a command.
305 ready: bool,
306 // True if commands should be ran with `sudo`.
307 sudo: bool,
308 }
309
310 impl TestVm {
311 /// Downloads prebuilts if needed.
initialize_once()312 fn initialize_once() {
313 if let Err(e) = syslog::init() {
314 panic!("failed to initiailize syslog: {}", e);
315 }
316
317 // It's possible the prebuilts downloaded by crosvm-9999.ebuild differ
318 // from the version that crosvm was compiled for.
319 info!("Prebuilt version to be used: {}", prebuilt_version());
320 if let Ok(value) = env::var("CROSVM_CARGO_TEST_PREBUILT_VERSION") {
321 if value != prebuilt_version() {
322 panic!(
323 "Environment provided prebuilts are version {}, but crosvm was compiled \
324 for prebuilt version {}. Did you update PREBUILT_VERSION everywhere?",
325 value,
326 prebuilt_version()
327 );
328 }
329 }
330 }
331
initiailize_artifacts(cfg: &Config)332 fn initiailize_artifacts(cfg: &Config) {
333 let kernel_path = local_path_from_url(&cfg.kernel_url);
334 if !kernel_path.exists() && cfg.kernel_url.scheme() != "file" {
335 download_file(cfg.kernel_url.as_str(), &kernel_path).unwrap();
336 }
337 assert!(kernel_path.exists(), "{:?} does not exist", kernel_path);
338
339 if let Some(initrd_url) = &cfg.initrd_url {
340 let initrd_path = local_path_from_url(initrd_url);
341 if !initrd_path.exists() && initrd_url.scheme() != "file" {
342 download_file(initrd_url.as_str(), &initrd_path).unwrap();
343 }
344 assert!(initrd_path.exists(), "{:?} does not exist", initrd_path);
345 }
346
347 if let Some(rootfs_url) = &cfg.rootfs_url {
348 let rootfs_download_path = local_path_from_url(rootfs_url);
349 if !rootfs_download_path.exists() && rootfs_url.scheme() != "file" {
350 download_file(rootfs_url.as_str(), &rootfs_download_path).unwrap();
351 }
352 assert!(
353 rootfs_download_path.exists(),
354 "{:?} does not exist",
355 rootfs_download_path
356 );
357
358 if cfg.rootfs_compressed {
359 let rootfs_raw_path = rootfs_download_path.with_extension("raw");
360 Command::new("zstd")
361 .arg("-d")
362 .arg(&rootfs_download_path)
363 .arg("-o")
364 .arg(&rootfs_raw_path)
365 .arg("-f")
366 .output()
367 .expect("Failed to decompress rootfs");
368 TestVmSys::check_rootfs_file(&rootfs_raw_path);
369 } else {
370 TestVmSys::check_rootfs_file(&rootfs_download_path);
371 }
372 }
373 }
374
375 /// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt
376 /// files if necessary.
377 ///
378 /// This generic method takes a `FnOnce` argument which is in charge of completing the `Command`
379 /// with all the relevant options needed to boot the VM.
new_generic<F>(f: F, cfg: Config, sudo: bool) -> Result<TestVm> where F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>,380 pub fn new_generic<F>(f: F, cfg: Config, sudo: bool) -> Result<TestVm>
381 where
382 F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>,
383 {
384 PREP_ONCE.call_once(TestVm::initialize_once);
385
386 TestVm::initiailize_artifacts(&cfg);
387
388 let mut vm = TestVm {
389 sys: TestVmSys::new_generic(f, cfg, sudo).with_context(|| "Could not start crosvm")?,
390 ready: false,
391 sudo,
392 };
393 vm.wait_for_guest_ready(BOOT_TIMEOUT)
394 .with_context(|| "Guest did not become ready after boot")?;
395 Ok(vm)
396 }
397
new_generic_restore<F>(f: F, cfg: Config, sudo: bool) -> Result<TestVm> where F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>,398 pub fn new_generic_restore<F>(f: F, cfg: Config, sudo: bool) -> Result<TestVm>
399 where
400 F: FnOnce(&mut Command, &SerialArgs, &Config) -> Result<()>,
401 {
402 PREP_ONCE.call_once(TestVm::initialize_once);
403 let mut vm = TestVm {
404 sys: TestVmSys::new_generic(f, cfg, sudo).with_context(|| "Could not start crosvm")?,
405 ready: false,
406 sudo,
407 };
408 vm.ready = true;
409 // TODO(b/280607404): A cold restored VM cannot respond to cmds from `exec_in_guest_async`.
410 Ok(vm)
411 }
412
new(cfg: Config) -> Result<TestVm>413 pub fn new(cfg: Config) -> Result<TestVm> {
414 TestVm::new_generic(TestVmSys::append_config_args, cfg, false)
415 }
416
417 /// Create `TestVm` from a snapshot, using `--restore` but NOT `--suspended`.
new_restore(cfg: Config) -> Result<TestVm>418 pub fn new_restore(cfg: Config) -> Result<TestVm> {
419 let mut vm = TestVm::new_generic_restore(TestVmSys::append_config_args, cfg, false)?;
420 // Send a resume request to wait for the restore to finish.
421 // We don't want to return from this function until the restore is complete, otherwise it
422 // will be difficult to differentiate between a slow restore and a slow response from the
423 // guest.
424 let vm = run_with_timeout(
425 move || {
426 vm.resume_full().expect("failed to resume after VM restore");
427 vm
428 },
429 Duration::from_secs(60),
430 )
431 .expect("VM restore timeout");
432
433 Ok(vm)
434 }
435
436 /// Create `TestVm` from a snapshot, using `--restore` AND `--suspended`.
new_restore_suspended(cfg: Config) -> Result<TestVm>437 pub fn new_restore_suspended(cfg: Config) -> Result<TestVm> {
438 TestVm::new_generic_restore(TestVmSys::append_config_args, cfg, false)
439 }
440
new_sudo(cfg: Config) -> Result<TestVm>441 pub fn new_sudo(cfg: Config) -> Result<TestVm> {
442 check_can_sudo();
443
444 TestVm::new_generic(TestVmSys::append_config_args, cfg, true)
445 }
446
447 /// Instanciate a new crosvm instance using a configuration file. The first call will trigger
448 /// the download of prebuilt files if necessary.
new_with_config_file(cfg: Config) -> Result<TestVm>449 pub fn new_with_config_file(cfg: Config) -> Result<TestVm> {
450 TestVm::new_generic(TestVmSys::append_config_file_arg, cfg, false)
451 }
452
453 /// Executes the provided command in the guest.
454 /// Returns command output as Ok(ProgramExit), or an Error if the program did not exit with 0.
exec_in_guest(&mut self, command: &str) -> Result<ProgramExit>455 pub fn exec_in_guest(&mut self, command: &str) -> Result<ProgramExit> {
456 self.exec_in_guest_async(command)?.wait_ok(self)
457 }
458
459 /// Same as `exec_in_guest` but will return Ok(ProgramExit) instead of failing on a
460 /// non-zero exit code.
exec_in_guest_unchecked(&mut self, command: &str) -> Result<ProgramExit>461 pub fn exec_in_guest_unchecked(&mut self, command: &str) -> Result<ProgramExit> {
462 self.exec_in_guest_async(command)?.wait_result(self)
463 }
464
465 /// Executes the provided command in the guest asynchronously.
466 /// The command will be run in the guest, but output will not be read until
467 /// GuestProcess::wait_ok() or GuestProcess::wait_result() is called.
exec_in_guest_async(&mut self, command: &str) -> Result<GuestProcess>468 pub fn exec_in_guest_async(&mut self, command: &str) -> Result<GuestProcess> {
469 assert!(self.ready);
470 self.ready = false;
471
472 // Send command to guest
473 self.write_message_to_guest(
474 &HostToGuestMessage::RunCommand {
475 command: command.to_owned(),
476 },
477 COMMUNICATION_TIMEOUT,
478 )
479 .with_context(|| format!("Command `{}`: Failed to write to guest pipe", command))?;
480
481 Ok(GuestProcess {
482 command: command.to_owned(),
483 timeout: DEFAULT_COMMAND_TIMEOUT,
484 })
485 }
486
487 // Waits for the guest to be ready to receive commands
wait_for_guest_ready(&mut self, timeout: Duration) -> Result<()>488 fn wait_for_guest_ready(&mut self, timeout: Duration) -> Result<()> {
489 assert!(!self.ready);
490 let message: GuestToHostMessage = self.read_message_from_guest(timeout)?;
491 match message {
492 GuestToHostMessage::Ready => {
493 self.ready = true;
494 Ok(())
495 }
496 _ => Err(anyhow!("Recevied unexpected data from delegate")),
497 }
498 }
499
500 /// Reads one line via the `from_guest` pipe from the guest delegate.
read_message_from_guest(&mut self, timeout: Duration) -> Result<GuestToHostMessage>501 fn read_message_from_guest(&mut self, timeout: Duration) -> Result<GuestToHostMessage> {
502 let reader = self.sys.from_guest_reader.clone();
503
504 let result = run_with_timeout(
505 move || loop {
506 let message = { reader.lock().unwrap().next() };
507
508 if let Some(message_result) = message {
509 if let Ok(msg) = message_result {
510 match msg {
511 DelegateMessage::GuestToHost(guest_to_host) => {
512 return Ok(guest_to_host);
513 }
514 // Guest will send an echo of the message sent from host, ignore it
515 DelegateMessage::HostToGuest(_) => {
516 continue;
517 }
518 }
519 } else {
520 bail!(format!(
521 "Failed to receive message from guest: {:?}",
522 message_result.unwrap_err()
523 ))
524 };
525 };
526 },
527 timeout,
528 );
529 match result {
530 Ok(x) => {
531 self.ready = true;
532 x
533 }
534 Err(x) => Err(x),
535 }
536 }
537
538 /// Send one line via the `to_guest` pipe to the guest delegate.
write_message_to_guest( &mut self, data: &HostToGuestMessage, timeout: Duration, ) -> Result<()>539 fn write_message_to_guest(
540 &mut self,
541 data: &HostToGuestMessage,
542 timeout: Duration,
543 ) -> Result<()> {
544 let writer = self.sys.to_guest.clone();
545 let data_str = serde_json::to_string_pretty(&DelegateMessage::HostToGuest(data.clone()))?;
546 run_with_timeout(
547 move || -> Result<()> {
548 println!("-> {}", &data_str);
549 {
550 writeln!(writer.lock().unwrap(), "{}", &data_str)?;
551 }
552 Ok(())
553 },
554 timeout,
555 )?
556 }
557
558 /// Hotplug a tap device.
hotplug_tap(&mut self, tap_name: &str) -> Result<()>559 pub fn hotplug_tap(&mut self, tap_name: &str) -> Result<()> {
560 self.sys
561 .crosvm_command(
562 "virtio-net",
563 vec!["add".to_owned(), tap_name.to_owned()],
564 self.sudo,
565 )
566 .map(|_| ())
567 }
568
569 /// Remove hotplugged device on bus.
remove_pci_device(&mut self, bus_num: u8) -> Result<()>570 pub fn remove_pci_device(&mut self, bus_num: u8) -> Result<()> {
571 self.sys
572 .crosvm_command(
573 "virtio-net",
574 vec!["remove".to_owned(), bus_num.to_string()],
575 self.sudo,
576 )
577 .map(|_| ())
578 }
579
stop(&mut self) -> Result<()>580 pub fn stop(&mut self) -> Result<()> {
581 self.sys
582 .crosvm_command("stop", vec![], self.sudo)
583 .map(|_| ())
584 }
585
suspend(&mut self) -> Result<()>586 pub fn suspend(&mut self) -> Result<()> {
587 self.sys
588 .crosvm_command("suspend", vec![], self.sudo)
589 .map(|_| ())
590 }
591
suspend_full(&mut self) -> Result<()>592 pub fn suspend_full(&mut self) -> Result<()> {
593 self.sys
594 .crosvm_command("suspend", vec!["--full".to_string()], self.sudo)
595 .map(|_| ())
596 }
597
resume(&mut self) -> Result<()>598 pub fn resume(&mut self) -> Result<()> {
599 self.sys
600 .crosvm_command("resume", vec![], self.sudo)
601 .map(|_| ())
602 }
603
resume_full(&mut self) -> Result<()>604 pub fn resume_full(&mut self) -> Result<()> {
605 self.sys
606 .crosvm_command("resume", vec!["--full".to_string()], self.sudo)
607 .map(|_| ())
608 }
609
disk(&mut self, args: Vec<String>) -> Result<()>610 pub fn disk(&mut self, args: Vec<String>) -> Result<()> {
611 self.sys.crosvm_command("disk", args, self.sudo).map(|_| ())
612 }
613
snapshot(&mut self, filename: &std::path::Path) -> Result<()>614 pub fn snapshot(&mut self, filename: &std::path::Path) -> Result<()> {
615 self.sys
616 .crosvm_command(
617 "snapshot",
618 vec!["take".to_string(), String::from(filename.to_str().unwrap())],
619 self.sudo,
620 )
621 .map(|_| ())
622 }
623
624 // No argument is passed in restore as we will always restore snapshot.bkp for testing.
restore(&mut self, filename: &std::path::Path) -> Result<()>625 pub fn restore(&mut self, filename: &std::path::Path) -> Result<()> {
626 self.sys
627 .crosvm_command(
628 "snapshot",
629 vec![
630 "restore".to_string(),
631 String::from(filename.to_str().unwrap()),
632 ],
633 self.sudo,
634 )
635 .map(|_| ())
636 }
637
swap_command(&mut self, command: &str) -> Result<Vec<u8>>638 pub fn swap_command(&mut self, command: &str) -> Result<Vec<u8>> {
639 self.sys
640 .crosvm_command("swap", vec![command.to_string()], self.sudo)
641 }
642
guest_clock_values(&mut self) -> Result<ClockValues>643 pub fn guest_clock_values(&mut self) -> Result<ClockValues> {
644 let output = self
645 .exec_in_guest("readclock")
646 .context("Failed to execute readclock binary")?;
647 serde_json::from_str(&output.stdout).context("Failed to parse result")
648 }
649 }
650
651 impl Drop for TestVm {
drop(&mut self)652 fn drop(&mut self) {
653 self.stop().unwrap();
654 let status = self.sys.process.take().unwrap().wait().unwrap();
655 if !status.success() {
656 panic!("VM exited illegally: {}", status);
657 }
658 }
659 }
660