xref: /aosp_15_r20/external/crosvm/e2e_tests/fixture/src/vm.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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