petri/
requirements.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Test requirements framework for runtime test filtering.
5
6/// Execution environments where tests can run.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ExecutionEnvironment {
9    /// Bare metal execution (not nested virtualization).
10    Baremetal,
11    /// Nested virtualization environment.
12    Nested,
13}
14
15/// CPU vendors.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Vendor {
18    /// AMD processors.
19    Amd,
20    /// Intel processors.
21    Intel,
22    /// ARM processors.
23    Arm,
24}
25
26/// Types of isolation supported.
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub enum IsolationType {
29    /// Virtualization-based Security (VBS)
30    Vbs,
31    /// Secure Nested Paging (SNP)
32    Snp,
33    /// Trusted Domain Extensions (TDX)
34    Tdx,
35}
36
37/// VMM implementation types.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum VmmType {
40    /// OpenVMM.
41    OpenVmm,
42    /// Microsoft Hyper-V.
43    HyperV,
44}
45
46/// Information about the VM host, retrieved via PowerShell on Windows.
47#[derive(Debug, Clone)]
48pub struct VmHostInfo {
49    /// VBS support status
50    pub vbs_supported: bool,
51    /// SNP support status
52    pub snp_status: bool,
53    /// TDX support status
54    pub tdx_status: bool,
55}
56
57/// Platform-specific host context extending the base HostContext
58#[derive(Debug, Clone)]
59pub struct HostContext {
60    /// VmHost information retrieved via PowerShell
61    pub vm_host_info: Option<VmHostInfo>,
62    /// CPU vendor
63    pub vendor: Vendor,
64    /// Execution environment
65    pub execution_environment: ExecutionEnvironment,
66}
67
68impl HostContext {
69    /// Create a new host context by querying host information
70    pub async fn new() -> Self {
71        let is_nested = {
72            // xtask-fmt allow-target-arch cpu-intrinsic
73            #[cfg(target_arch = "x86_64")]
74            {
75                let result = safe_intrinsics::cpuid(
76                    hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION,
77                    0,
78                );
79                hvdef::HvEnlightenmentInformation::from(
80                    result.eax as u128
81                        | (result.ebx as u128) << 32
82                        | (result.ecx as u128) << 64
83                        | (result.edx as u128) << 96,
84                )
85                .nested()
86            }
87            // xtask-fmt allow-target-arch cpu-intrinsic
88            #[cfg(not(target_arch = "x86_64"))]
89            {
90                false
91            }
92        };
93
94        let vendor = {
95            // xtask-fmt allow-target-arch cpu-intrinsic
96            #[cfg(target_arch = "x86_64")]
97            {
98                let result = safe_intrinsics::cpuid(
99                    x86defs::cpuid::CpuidFunction::VendorAndMaxFunction.0,
100                    0,
101                );
102                if x86defs::cpuid::Vendor::from_ebx_ecx_edx(result.ebx, result.ecx, result.edx)
103                    .is_amd_compatible()
104                {
105                    Vendor::Amd
106                } else {
107                    assert!(
108                        x86defs::cpuid::Vendor::from_ebx_ecx_edx(
109                            result.ebx, result.ecx, result.edx
110                        )
111                        .is_intel_compatible()
112                    );
113                    Vendor::Intel
114                }
115            }
116            // xtask-fmt allow-target-arch cpu-intrinsic
117            #[cfg(not(target_arch = "x86_64"))]
118            {
119                Vendor::Arm
120            }
121        };
122
123        let vm_host_info = {
124            #[cfg(windows)]
125            {
126                crate::vm::hyperv::powershell::run_get_vm_host()
127                    .await
128                    .ok()
129                    .map(|info| VmHostInfo {
130                        vbs_supported: info.guest_isolation_types.contains(
131                            &crate::vm::hyperv::powershell::HyperVGuestStateIsolationType::Vbs,
132                        ),
133                        snp_status: info.snp_status,
134                        tdx_status: info.tdx_status,
135                    })
136            }
137            #[cfg(not(windows))]
138            {
139                None
140            }
141        };
142
143        Self {
144            vm_host_info,
145            vendor,
146            execution_environment: if is_nested {
147                ExecutionEnvironment::Nested
148            } else {
149                ExecutionEnvironment::Baremetal
150            },
151        }
152    }
153}
154
155/// A single requirement for a test to run.
156pub enum TestRequirement {
157    /// Execution environment requirement.
158    ExecutionEnvironment(ExecutionEnvironment),
159    /// Vendor requirement.
160    Vendor(Vendor),
161    /// Isolation requirement.
162    Isolation(IsolationType),
163    /// Logical AND of two requirements.
164    And(Box<TestRequirement>, Box<TestRequirement>),
165    /// Logical OR of two requirements.
166    Or(Box<TestRequirement>, Box<TestRequirement>),
167    /// Logical NOT of a requirement.
168    Not(Box<TestRequirement>),
169}
170
171impl TestRequirement {
172    /// Evaluate if this requirement is satisfied with the given host context
173    pub fn is_satisfied(&self, context: &HostContext) -> bool {
174        match self {
175            TestRequirement::ExecutionEnvironment(env) => context.execution_environment == *env,
176            TestRequirement::Vendor(vendor) => context.vendor == *vendor,
177            TestRequirement::Isolation(isolation_type) => {
178                if let Some(vm_host_info) = &context.vm_host_info {
179                    match isolation_type {
180                        IsolationType::Vbs => vm_host_info.vbs_supported,
181                        IsolationType::Snp => vm_host_info.snp_status,
182                        IsolationType::Tdx => vm_host_info.tdx_status,
183                    }
184                } else {
185                    false
186                }
187            }
188            TestRequirement::And(req1, req2) => {
189                req1.is_satisfied(context) && req2.is_satisfied(context)
190            }
191            TestRequirement::Or(req1, req2) => {
192                req1.is_satisfied(context) || req2.is_satisfied(context)
193            }
194            TestRequirement::Not(req) => !req.is_satisfied(context),
195        }
196    }
197}
198
199/// Result of evaluating all requirements for a test
200#[derive(Debug, Clone)]
201pub struct TestEvaluationResult {
202    /// Name of the test being evaluated
203    pub test_name: String,
204    /// Overall result: can the test be run?
205    pub can_run: bool,
206}
207
208impl TestEvaluationResult {
209    /// Create a new result indicating the test can run (no requirements)
210    pub fn new(test_name: &str) -> Self {
211        Self {
212            test_name: test_name.to_string(),
213            can_run: true,
214        }
215    }
216}
217
218/// Container for test requirements that can be evaluated
219pub struct TestCaseRequirements {
220    requirements: TestRequirement,
221}
222
223impl TestCaseRequirements {
224    /// Create a new TestCaseRequirements from a TestRequirement
225    pub fn new(requirements: TestRequirement) -> Self {
226        Self { requirements }
227    }
228}
229
230/// Evaluates if a test case can be run in the current execution environment with context.
231pub fn can_run_test_with_context(
232    config: Option<&TestCaseRequirements>,
233    context: &HostContext,
234) -> bool {
235    if let Some(config) = config {
236        config.requirements.is_satisfied(context)
237    } else {
238        true
239    }
240}