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