Skip to main content

petri/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A Rust-based testing framework for VMMs.
5//!
6//! At this time - `petri` supports testing OpenVMM, OpenHCL,
7//! and Hyper-V based VMs.
8
9// TODO: Remove this dependency by adding a frontend worker that handles
10// hypervisor auto-detection in the spawned openvmm process instead of
11// requiring probes to be registered in the petri process.
12extern crate openvmm_hypervisors as _;
13
14pub mod disk_image;
15mod linux_direct_serial_agent;
16// TODO: Add docs and maybe a trait interface for this, or maybe this can
17// remain crate-local somehow without violating interface privacy.
18#[expect(missing_docs)]
19pub mod openhcl_diag;
20pub mod requirements;
21mod test;
22mod tracing;
23mod vm;
24mod worker;
25
26pub use petri_artifacts_core::ArtifactHandle;
27pub use petri_artifacts_core::ArtifactResolver;
28pub use petri_artifacts_core::ArtifactSource;
29pub use petri_artifacts_core::AsArtifactHandle;
30pub use petri_artifacts_core::ErasedArtifactHandle;
31pub use petri_artifacts_core::RemoteAccess;
32pub use petri_artifacts_core::ResolveTestArtifact;
33pub use petri_artifacts_core::ResolvedArtifact;
34pub use petri_artifacts_core::ResolvedArtifactSource;
35pub use petri_artifacts_core::ResolvedOptionalArtifact;
36pub use petri_artifacts_core::TestArtifactRequirements;
37pub use petri_artifacts_core::TestArtifacts;
38pub use pipette_client as pipette;
39pub use test::PetriTestParams;
40pub use test::RunTest;
41pub use test::SimpleTest;
42pub use test::TestCase;
43pub use test::test_macro_support;
44pub use test::test_main;
45pub use tracing::*;
46pub use vm::*;
47
48use jiff::Timestamp;
49use std::process::Command;
50use std::process::Stdio;
51use thiserror::Error;
52
53/// 1 kibibyte's worth of bytes.
54pub const SIZE_1_KB: u64 = 1024;
55/// 1 mebibyte's worth of bytes.
56pub const SIZE_1_MB: u64 = 1024 * SIZE_1_KB;
57/// 1 gibibyte's worth of bytes.
58pub const SIZE_1_GB: u64 = 1024 * SIZE_1_MB;
59
60/// The kind of shutdown to perform.
61#[expect(missing_docs)] // Self-describing names.
62pub enum ShutdownKind {
63    Shutdown,
64    Reboot,
65    // TODO: Add hibernate?
66}
67
68/// Error running command
69#[derive(Error, Debug)]
70pub enum CommandError {
71    /// failed to launch command
72    #[error("failed to launch command")]
73    Launch(#[from] std::io::Error),
74    /// command exited with non-zero status
75    #[error("command exited with non-zero status ({0}): {1}")]
76    Command(std::process::ExitStatus, String),
77}
78
79/// Run a command on the host and return the output
80pub async fn run_host_cmd(mut cmd: Command) -> Result<String, CommandError> {
81    cmd.stderr(Stdio::piped()).stdin(Stdio::null());
82
83    let cmd_debug = format!("{cmd:?}");
84    ::tracing::debug!(cmd = cmd_debug, "executing command");
85
86    let start = Timestamp::now();
87    let output = blocking::unblock(move || cmd.output()).await?;
88    let time_elapsed = Timestamp::now() - start;
89
90    let stdout_str = String::from_utf8_lossy(&output.stdout).to_string();
91    let stderr_str = String::from_utf8_lossy(&output.stderr).to_string();
92    ::tracing::debug!(
93        cmd = cmd_debug,
94        stdout_str,
95        stderr_str,
96        "command exited in {:.3}s with status {}",
97        time_elapsed.total(jiff::Unit::Second).unwrap(),
98        output.status
99    );
100
101    if !output.status.success() {
102        return Err(CommandError::Command(output.status, stderr_str));
103    }
104
105    Ok(stdout_str.trim().to_owned())
106}