1#![forbid(unsafe_code)]
7
8use anyhow::Context;
9use fs_err::PathExt;
10use petri_artifacts_common::tags::MachineArch;
11use petri_artifacts_core::ArtifactSource;
12use petri_artifacts_core::AsArtifactHandle;
13use petri_artifacts_core::ErasedArtifactHandle;
14use std::env::consts::EXE_EXTENSION;
15use std::path::Path;
16use std::path::PathBuf;
17use vmm_test_images::CONTAINER;
18use vmm_test_images::KnownTestArtifacts;
19use vmm_test_images::STORAGE_ACCOUNT;
20
21pub fn cargo_build_profile() -> &'static str {
28 static PROFILE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
29 PROFILE.get_or_init(|| {
30 if let Ok(exe) = std::env::current_exe() {
31 if exe.components().any(|c| c.as_os_str() == "release") {
32 return "release".to_string();
33 }
34 }
35 "debug".to_string()
36 })
37}
38
39pub struct OpenvmmKnownPathsTestArtifactResolver<'a>(&'a str);
43
44impl<'a> OpenvmmKnownPathsTestArtifactResolver<'a> {
45 pub fn new(test_name: &'a str) -> Self {
47 Self(test_name)
48 }
49}
50
51impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifactResolver<'_> {
52 #[rustfmt::skip]
53 fn resolve(&self, id: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
54 use petri_artifacts_common::artifacts as common;
55 use petri_artifacts_vmm_test::artifacts::*;
56 use petri_artifacts_vmm_test::tags::IsHostedOnHvliteAzureBlobStore;
57
58 match id {
59 _ if id == common::PIPETTE_WINDOWS_X64 => pipette_path(MachineArch::X86_64, PipetteFlavor::Windows),
60 _ if id == common::PIPETTE_LINUX_X64 => pipette_path(MachineArch::X86_64, PipetteFlavor::Linux),
61 _ if id == common::PIPETTE_WINDOWS_AARCH64 => pipette_path(MachineArch::Aarch64, PipetteFlavor::Windows),
62 _ if id == common::PIPETTE_LINUX_AARCH64 => pipette_path(MachineArch::Aarch64, PipetteFlavor::Linux),
63
64 _ if id == common::TEST_LOG_DIRECTORY => test_log_directory_path(self.0),
65
66 _ if id == OPENVMM_NATIVE => openvmm_native_executable_path(),
67 #[cfg(target_os = "linux")]
68 _ if id == OPENVMM_VHOST_NATIVE => openvmm_vhost_native_executable_path(),
69
70 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_X64 => linux_direct_x64_test_kernel_path(),
71 _ if id == loadable::LINUX_DIRECT_TEST_BZIMAGE_X64 => linux_direct_x64_test_bzimage_path(),
72 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64 => linux_direct_arm_image_path(),
73 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_X64 => linux_direct_test_initrd_path(MachineArch::X86_64),
74 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_AARCH64 => linux_direct_test_initrd_path(MachineArch::Aarch64),
75
76 _ if id == petritools::PETRITOOLS_EROFS_X64 => petritools_erofs_path(MachineArch::X86_64),
77 _ if id == petritools::PETRITOOLS_EROFS_AARCH64 => petritools_erofs_path(MachineArch::Aarch64),
78
79 _ if id == loadable::PCAT_FIRMWARE_X64 => pcat_firmware_path(),
80 _ if id == loadable::SVGA_FIRMWARE_X64 => svga_firmware_path(),
81 _ if id == loadable::UEFI_FIRMWARE_X64 => uefi_firmware_path(MachineArch::X86_64),
82 _ if id == loadable::UEFI_FIRMWARE_AARCH64 => uefi_firmware_path(MachineArch::Aarch64),
83
84 _ if id == openhcl_igvm::LATEST_STANDARD_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Standard),
85 _ if id == openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel),
86 _ if id == openhcl_igvm::LATEST_CVM_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Cvm),
87 _ if id == openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::LinuxDirect),
88 _ if id == openhcl_igvm::LATEST_STANDARD_AARCH64 => openhcl_bin_path(MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::Standard),
89 _ if id == openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_AARCH64 => openhcl_bin_path(MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel),
90
91 _ if id == openhcl_igvm::LATEST_RELEASE_STANDARD_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Release2511, OpenhclFlavor::Standard),
92 _ if id == openhcl_igvm::LATEST_RELEASE_LINUX_DIRECT_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Release2511, OpenhclFlavor::LinuxDirect),
93 _ if id == openhcl_igvm::LATEST_RELEASE_STANDARD_AARCH64 => openhcl_bin_path(MachineArch::Aarch64, OpenhclVersion::Release2511, OpenhclFlavor::Standard),
94
95 _ if id == openhcl_igvm::um_bin::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_extras_path(OpenhclVersion::Latest,OpenhclFlavor::LinuxDirect,OpenhclExtras::UmBin),
96 _ if id == openhcl_igvm::um_dbg::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_extras_path(OpenhclVersion::Latest,OpenhclFlavor::LinuxDirect,OpenhclExtras::UmDbg),
97
98 _ if id == test_vhd::GUEST_TEST_UEFI_X64 => guest_test_uefi_disk_path(MachineArch::X86_64),
99 _ if id == test_vhd::GUEST_TEST_UEFI_AARCH64 => guest_test_uefi_disk_path(MachineArch::Aarch64),
100
101 _ if id == test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED => {
102 let base_filename = test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64::FILENAME;
103 let prepped_filename = base_filename.replace(".vhd", "-prepped.vhd");
104 let images_dir = std::env::var("VMM_TEST_IMAGES");
105 let full_path = Path::new(images_dir.as_deref().unwrap_or("images"));
106 get_path(
107 full_path,
108 prepped_filename,
109 MissingCommand::Run {
110 description: "prepped test image",
111 package: "prep_steps",
112 },
113 )
114 }
115
116 _ if id == test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64_NO_VMBUS_PREPPED => {
117 let base_filename = test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64::FILENAME;
118 let prepped_filename = base_filename.replace(".vhd", "-no-vmbus-prepped.vhd");
119 let images_dir = std::env::var("VMM_TEST_IMAGES");
120 let full_path = Path::new(images_dir.as_deref().unwrap_or("images"));
121 get_path(
122 full_path,
123 prepped_filename,
124 MissingCommand::Custom {
125 description: "no-vmbus prepped test image",
126 cmd: "cargo run -p prep_steps -- no-vmbus",
127 },
128 )
129 }
130
131 _ if id == tmks::TMK_VMM_NATIVE => tmk_vmm_native_executable_path(),
132 _ if id == tmks::TMK_VMM_LINUX_X64 => tmk_vmm_linux_path(MachineArch::X86_64),
133 _ if id == tmks::TMK_VMM_LINUX_AARCH64 =>
134 env_path_or(OPENVMM_CCA_TMK_VMM_ENV_VAR, || {
135 tmk_vmm_linux_path(MachineArch::Aarch64)
136 }),
137 _ if id == tmks::TMK_VMM_LINUX_X64_MUSL => tmk_vmm_paravisor_path(MachineArch::X86_64),
138 _ if id == tmks::TMK_VMM_LINUX_AARCH64_MUSL => tmk_vmm_paravisor_path(MachineArch::Aarch64),
139 _ if id == tmks::SIMPLE_TMK_X64 => simple_tmk_path(MachineArch::X86_64),
140 _ if id == tmks::SIMPLE_TMK_AARCH64 =>
141 env_path_or(OPENVMM_CCA_SIMPLE_TMK_ENV_VAR, || {
142 simple_tmk_path(MachineArch::Aarch64)
143 }),
144
145 _ if id == cca::SHRINKWRAP => cca_shrinkwrap_path(),
146 _ if id == cca::VENV => cca_venv_path(),
147 _ if id == cca::ROOTFS => cca_package_path("rootfs.ext2", "CCA emulation rootfs"),
148 _ if id == cca::E2FSCK => cca_buildroot_host_sbin_path("e2fsck", "CCA buildroot host e2fsck"),
149 _ if id == cca::RESIZE2FS => cca_buildroot_host_sbin_path("resize2fs", "CCA buildroot host resize2fs"),
150 _ if id == cca::GUEST_DISK => cca_package_path("guest-disk.img", "CCA guest disk"),
151 _ if id == cca::PLANE0_LINUX_IMAGE => cca_plane0_linux_image_path(),
152 _ if id == cca::KVMTOOL_EFI => cca_package_path("KVMTOOL_EFI.fd", "CCA kvmtool EFI firmware"),
153 _ if id == cca::LKVM => cca_package_path("lkvm", "CCA lkvm"),
154
155 _ if id == vmgstool::VMGSTOOL_NATIVE => vmgstool_native_executable_path(),
156 _ if id == vmgstool::VMGSTOOL_DEV_NATIVE => vmgstool_dev_native_executable_path(),
157
158 _ if id == guest_tools::TPM_GUEST_TESTS_WINDOWS_X64 => {
159 tpm_guest_tests_windows_path(MachineArch::X86_64)
160 }
161 _ if id == guest_tools::TPM_GUEST_TESTS_LINUX_X64 => {
162 tpm_guest_tests_linux_path(MachineArch::X86_64)
163 }
164
165 _ if id == virtio_win::VIRTIO_WIN_DRIVERS => {
166 virtio_win_path()
167 }
168
169 _ if id == host_tools::TEST_IGVM_AGENT_RPC_SERVER_WINDOWS_X64 => {
170 test_igvm_agent_rpc_server_windows_path(MachineArch::X86_64)
171 }
172
173 _ if let Some(artifact) = KnownTestArtifacts::from_handle(id) => {
175 get_test_artifact_path(artifact)
176 }
177
178 _ => anyhow::bail!("no support for given artifact type"),
179 }
180 }
181
182 fn resolve_source(&self, id: ErasedArtifactHandle) -> anyhow::Result<ArtifactSource> {
183 let local_err = match self.resolve(id) {
185 Ok(path) => return Ok(ArtifactSource::Local(path)),
186 Err(e) => e,
187 };
188
189 if let Some(artifact) = KnownTestArtifacts::from_handle(id) {
192 if artifact.supports_blob_disk() {
193 let url = format!(
194 "https://{STORAGE_ACCOUNT}.blob.core.windows.net/{CONTAINER}/{}",
195 artifact.filename()
196 );
197 return Ok(ArtifactSource::Remote { url });
198 }
199 }
200
201 Err(local_err)
203 }
204}
205
206pub fn resolve_bundle_name(id: ErasedArtifactHandle) -> Option<&'static str> {
217 use petri_artifacts_common::artifacts as common;
218 use petri_artifacts_vmm_test::artifacts::*;
219
220 match id {
221 _ if id == common::PIPETTE_LINUX_X64 => Some("pipette"),
222 _ if id == common::PIPETTE_LINUX_AARCH64 => Some("pipette"),
223 _ if id == common::PIPETTE_WINDOWS_X64 => Some("pipette.exe"),
224 _ if id == common::PIPETTE_WINDOWS_AARCH64 => Some("pipette.exe"),
225 _ if id == OPENVMM_NATIVE => Some(if cfg!(windows) {
226 "openvmm.exe"
227 } else {
228 "openvmm"
229 }),
230 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_X64 => Some("x64/vmlinux"),
231 _ if id == loadable::LINUX_DIRECT_TEST_BZIMAGE_X64 => Some("x64/bzImage"),
232 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64 => Some("aarch64/Image"),
233 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_X64 => Some("x64/initrd"),
234 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_AARCH64 => Some("aarch64/initrd"),
235 _ if id == petritools::PETRITOOLS_EROFS_X64 => Some("x64/petritools.erofs"),
236 _ if id == petritools::PETRITOOLS_EROFS_AARCH64 => Some("aarch64/petritools.erofs"),
237 _ if id == loadable::UEFI_FIRMWARE_X64 => {
238 Some("hyperv.uefi.mscoreuefi.x64.RELEASE/MsvmX64/RELEASE_VS2022/FV/MSVM.fd")
239 }
240 _ if id == loadable::UEFI_FIRMWARE_AARCH64 => {
241 Some("hyperv.uefi.mscoreuefi.AARCH64.RELEASE/MsvmAARCH64/RELEASE_CLANGPDB/FV/MSVM.fd")
242 }
243 _ => {
244 None
248 }
249 }
250}
251
252#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
253enum PipetteFlavor {
254 Windows,
255 Linux,
256}
257
258#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
259enum OpenhclVersion {
260 Latest,
261 Release2511,
262}
263
264#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
265enum OpenhclFlavor {
266 Standard,
267 StandardDevKernel,
268 Cvm,
269 LinuxDirect,
270}
271
272#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
273enum OpenhclExtras {
274 UmBin,
275 UmDbg,
276}
277
278fn target_arch_path(arch: MachineArch) -> &'static str {
280 match arch {
281 MachineArch::X86_64 => "x86_64",
282 MachineArch::Aarch64 => "aarch64",
283 }
284}
285
286fn windows_msvc_target(arch: MachineArch) -> &'static str {
287 match arch {
288 MachineArch::X86_64 => "x86_64-pc-windows-msvc",
289 MachineArch::Aarch64 => "aarch64-pc-windows-msvc",
290 }
291}
292
293fn get_test_artifact_path(artifact: KnownTestArtifacts) -> Result<PathBuf, anyhow::Error> {
294 let images_dir = std::env::var("VMM_TEST_IMAGES");
295 let full_path = Path::new(images_dir.as_deref().unwrap_or("images"));
296
297 get_path(
298 full_path,
299 artifact.filename(),
300 MissingCommand::Xtask {
301 xtask_args: &[
302 "guest-test",
303 "download-image",
304 "--artifacts",
305 artifact.name(),
306 ],
307 description: "test artifact",
308 },
309 )
310}
311
312fn guest_test_uefi_disk_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
314 get_path(
316 format!("target/{}-unknown-uefi/debug", target_arch_path(arch)),
317 "guest_test_uefi.img",
318 MissingCommand::Xtask {
319 xtask_args: &[
320 "guest-test",
321 "uefi",
322 &format!(
323 "--boot{}",
324 match arch {
325 MachineArch::X86_64 => "x64",
326 MachineArch::Aarch64 => "aa64",
327 }
328 ),
329 ],
330 description: "guest_test_uefi image",
331 },
332 )
333}
334
335fn pipette_path(arch: MachineArch, os_flavor: PipetteFlavor) -> anyhow::Result<PathBuf> {
337 let (target_suffixes, binary) = match os_flavor {
340 PipetteFlavor::Windows => (vec!["pc-windows-msvc", "pc-windows-gnu"], "pipette.exe"),
341 PipetteFlavor::Linux => (vec!["unknown-linux-musl"], "pipette"),
342 };
343 for (index, target_suffix) in target_suffixes.iter().enumerate() {
344 let target = format!("{}-{}", target_arch_path(arch), target_suffix);
345 match get_path(
346 format!("target/{target}/{}", cargo_build_profile()),
347 binary,
348 MissingCommand::Build {
349 package: "pipette",
350 target: Some(&target),
351 },
352 ) {
353 Ok(path) => return Ok(path),
354 Err(err) => {
355 if index < target_suffixes.len() - 1 {
356 continue;
357 } else {
358 anyhow::bail!(
359 "None of the suffixes {:?} had `pipette` built, {err:?}",
360 target_suffixes
361 );
362 }
363 }
364 }
365 }
366
367 unreachable!()
368}
369
370fn openvmm_native_executable_path() -> anyhow::Result<PathBuf> {
372 get_output_executable_path("openvmm")
373}
374
375#[cfg(target_os = "linux")]
377fn openvmm_vhost_native_executable_path() -> anyhow::Result<PathBuf> {
378 get_output_executable_path("openvmm_vhost")
379}
380
381fn tmk_vmm_native_executable_path() -> anyhow::Result<PathBuf> {
383 get_output_executable_path("tmk_vmm")
384}
385
386fn vmgstool_native_executable_path() -> anyhow::Result<PathBuf> {
388 get_output_executable_path("vmgstool")
389}
390
391fn vmgstool_dev_native_executable_path() -> anyhow::Result<PathBuf> {
393 get_path(
394 "target/debug",
395 Path::new("vmgstool-dev").with_extension(EXE_EXTENSION),
396 MissingCommand::Custom {
397 description: "vmgstool-dev (Cargo build output must be renamed to match)",
398 cmd: "cargo build -p vmgstool --features encryption,test_helpers",
399 },
400 )
401}
402
403fn tpm_guest_tests_windows_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
405 let target = windows_msvc_target(arch);
406 get_path(
407 format!("target/{target}/debug"),
408 "tpm_guest_tests.exe",
409 MissingCommand::Build {
410 package: "tpm_guest_tests",
411 target: Some(target),
412 },
413 )
414}
415
416fn tpm_guest_tests_linux_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
417 let target = match arch {
418 MachineArch::X86_64 => "x86_64-unknown-linux-gnu",
419 MachineArch::Aarch64 => "aarch64-unknown-linux-gnu",
420 };
421
422 get_path(
423 format!("target/{target}/debug"),
424 "tpm_guest_tests",
425 MissingCommand::Build {
426 package: "tpm_guest_tests",
427 target: Some(target),
428 },
429 )
430}
431
432fn test_igvm_agent_rpc_server_windows_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
434 let target = windows_msvc_target(arch);
435 get_path(
436 format!("target/{target}/debug"),
437 "test_igvm_agent_rpc_server.exe",
438 MissingCommand::Build {
439 package: "test_igvm_agent_rpc_server",
440 target: Some(target),
441 },
442 )
443}
444
445fn virtio_win_path() -> anyhow::Result<PathBuf> {
447 get_path(
448 ".packages",
449 "virtio-win",
450 MissingCommand::Restore {
451 description: "virtio-win drivers",
452 },
453 )
454}
455
456const OPENVMM_CCA_TEST_ROOT_ENV_VAR: &str = "OPENVMM_CCA_TEST_ROOT";
457const OPENVMM_CCA_TMK_VMM_ENV_VAR: &str = "OPENVMM_CCA_TMK_VMM";
458const OPENVMM_CCA_SIMPLE_TMK_ENV_VAR: &str = "OPENVMM_CCA_SIMPLE_TMK";
459
460fn cca_missing_command(description: &'static str) -> MissingCommand<'static> {
461 MissingCommand::XFlowey {
462 description,
463 xflowey_args: &["cca-tests", "--install-emu"],
464 }
465}
466
467fn env_path_or(
468 name: &str,
469 fallback: impl FnOnce() -> anyhow::Result<PathBuf>,
470) -> anyhow::Result<PathBuf> {
471 std::env::var_os(name)
472 .map(PathBuf::from)
473 .map(Ok)
474 .unwrap_or_else(fallback)
475}
476
477fn cca_test_root() -> PathBuf {
478 std::env::var_os(OPENVMM_CCA_TEST_ROOT_ENV_VAR)
479 .map(PathBuf::from)
480 .unwrap_or_else(|| PathBuf::from("target/cca-test"))
481}
482
483fn cca_home_dir() -> anyhow::Result<PathBuf> {
484 std::env::var_os("HOME")
485 .map(PathBuf::from)
486 .ok_or_else(|| anyhow::anyhow!("HOME is not set"))
487}
488
489fn cca_shrinkwrap_path() -> anyhow::Result<PathBuf> {
490 get_path(
491 cca_test_root().join("shrinkwrap"),
492 "shrinkwrap/shrinkwrap",
493 cca_missing_command("CCA shrinkwrap executable"),
494 )
495}
496
497fn cca_venv_path() -> anyhow::Result<PathBuf> {
498 get_path(
499 cca_test_root().join("shrinkwrap"),
500 "venv",
501 cca_missing_command("CCA shrinkwrap virtual environment"),
502 )
503}
504
505fn cca_plane0_linux_image_path() -> anyhow::Result<PathBuf> {
506 get_path(
507 cca_test_root().join("plane0-linux/arch/arm64/boot"),
508 "Image",
509 cca_missing_command("CCA Plane0 Linux image"),
510 )
511}
512
513fn cca_package_path(file_name: &'static str, description: &'static str) -> anyhow::Result<PathBuf> {
514 get_path(
515 cca_home_dir()?.join(".shrinkwrap/package/cca-3world"),
516 file_name,
517 cca_missing_command(description),
518 )
519}
520
521fn cca_buildroot_host_sbin_path(
522 file_name: &'static str,
523 description: &'static str,
524) -> anyhow::Result<PathBuf> {
525 get_path(
526 cca_home_dir()?.join(".shrinkwrap/build/build/cca-3world/buildroot/host/sbin"),
527 file_name,
528 cca_missing_command(description),
529 )
530}
531
532fn tmk_vmm_linux_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
534 let target = match arch {
535 MachineArch::X86_64 => "x86_64-unknown-linux-gnu",
536 MachineArch::Aarch64 => "aarch64-unknown-linux-gnu",
537 };
538
539 if let Some(path) = try_get_path(format!("target/{target}/debug"), "tmk_vmm")? {
540 return Ok(path);
541 }
542
543 if let Some(path) =
544 flowey_built_executable_path(format!("target/tmk_vmm/{target}/debug/deps"), "tmk_vmm")?
545 {
546 return Ok(path);
547 }
548
549 get_path(
550 format!("target/{target}/debug"),
551 "tmk_vmm",
552 MissingCommand::Build {
553 package: "tmk_vmm",
554 target: Some(target),
555 },
556 )
557}
558
559fn tmk_vmm_paravisor_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
561 let target = match arch {
562 MachineArch::X86_64 => "x86_64-unknown-linux-musl",
563 MachineArch::Aarch64 => "aarch64-unknown-linux-musl",
564 };
565 get_path(
566 format!("target/{target}/debug"),
567 "tmk_vmm",
568 MissingCommand::Build {
569 package: "tmk_vmm",
570 target: Some(target),
571 },
572 )
573}
574
575fn simple_tmk_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
577 let arch_str = match arch {
578 MachineArch::X86_64 => "x86_64",
579 MachineArch::Aarch64 => "aarch64",
580 };
581 let target = match arch {
582 MachineArch::X86_64 => "x86_64-unknown-none",
583 MachineArch::Aarch64 => "aarch64-minimal_rt-none",
584 };
585
586 if let Some(path) = try_get_path(format!("target/{target}/debug"), "simple_tmk")? {
587 return Ok(path);
588 }
589
590 if let Some(path) = flowey_built_executable_path(
591 format!("target/simple_tmk/{target}/debug/deps"),
592 "simple_tmk",
593 )? {
594 return Ok(path);
595 }
596
597 get_path(
598 format!("target/{target}/debug"),
599 "simple_tmk",
600 MissingCommand::Custom {
601 description: "simple_tmk",
602 cmd: &format!(
603 "RUSTC_BOOTSTRAP=1 cargo build -p simple_tmk --config openhcl/minimal_rt/{arch_str}-config.toml"
604 ),
605 },
606 )
607}
608
609fn linux_direct_x64_test_kernel_path() -> anyhow::Result<PathBuf> {
611 use petri_artifacts_vmm_test::artifacts::loadable;
612 get_path(
613 ".packages/underhill-deps-private",
614 resolve_bundle_name(loadable::LINUX_DIRECT_TEST_KERNEL_X64.erase()).unwrap(),
615 MissingCommand::Restore {
616 description: "linux direct test kernel",
617 },
618 )
619}
620
621fn linux_direct_x64_test_bzimage_path() -> anyhow::Result<PathBuf> {
623 use petri_artifacts_vmm_test::artifacts::loadable;
624 get_path(
625 ".packages/underhill-deps-private",
626 resolve_bundle_name(loadable::LINUX_DIRECT_TEST_BZIMAGE_X64.erase()).unwrap(),
627 MissingCommand::Restore {
628 description: "linux direct test bzImage",
629 },
630 )
631}
632
633fn linux_direct_test_initrd_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
635 use petri_artifacts_vmm_test::artifacts::loadable;
636 let id = match arch {
637 MachineArch::X86_64 => loadable::LINUX_DIRECT_TEST_INITRD_X64.erase(),
638 MachineArch::Aarch64 => loadable::LINUX_DIRECT_TEST_INITRD_AARCH64.erase(),
639 };
640 get_path(
641 ".packages/underhill-deps-private",
642 resolve_bundle_name(id).unwrap(),
643 MissingCommand::Restore {
644 description: "linux direct test initrd",
645 },
646 )
647}
648
649fn petritools_erofs_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
651 use petri_artifacts_vmm_test::artifacts::petritools;
652 let id = match arch {
653 MachineArch::X86_64 => petritools::PETRITOOLS_EROFS_X64.erase(),
654 MachineArch::Aarch64 => petritools::PETRITOOLS_EROFS_AARCH64.erase(),
655 };
656 get_path(
657 ".packages/underhill-deps-private",
658 resolve_bundle_name(id).unwrap(),
659 MissingCommand::Restore {
660 description: "petritools erofs image",
661 },
662 )
663}
664
665fn linux_direct_arm_image_path() -> anyhow::Result<PathBuf> {
667 use petri_artifacts_vmm_test::artifacts::loadable;
668 get_path(
669 ".packages/underhill-deps-private",
670 resolve_bundle_name(loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64.erase()).unwrap(),
671 MissingCommand::Restore {
672 description: "linux direct test kernel",
673 },
674 )
675}
676
677fn pcat_firmware_path() -> anyhow::Result<PathBuf> {
679 get_path(
680 ".packages",
681 "Microsoft.Windows.VmFirmware.Pcat.amd64fre/content/vmfirmwarepcat.dll",
682 MissingCommand::Restore {
683 description: "PCAT firmware binary",
684 },
685 )
686}
687
688fn svga_firmware_path() -> anyhow::Result<PathBuf> {
690 get_path(
691 ".packages",
692 "Microsoft.Windows.VmEmulatedDevices.amd64fre/content/VmEmulatedDevices.dll",
693 MissingCommand::Restore {
694 description: "SVGA firmware binary",
695 },
696 )
697}
698
699fn uefi_firmware_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
701 use petri_artifacts_vmm_test::artifacts::loadable;
702 let id = match arch {
703 MachineArch::X86_64 => loadable::UEFI_FIRMWARE_X64.erase(),
704 MachineArch::Aarch64 => loadable::UEFI_FIRMWARE_AARCH64.erase(),
705 };
706 get_path(
707 ".packages",
708 resolve_bundle_name(id).unwrap(),
709 MissingCommand::Restore {
710 description: "UEFI firmware binary",
711 },
712 )
713}
714
715fn openhcl_bin_path(
717 arch: MachineArch,
718 version: OpenhclVersion,
719 flavor: OpenhclFlavor,
720) -> anyhow::Result<PathBuf> {
721 let (path, name, cmd) = match (arch, version, flavor) {
722 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Standard) => (
723 "flowey-out/artifacts/build-igvm/debug/x64",
724 "openhcl-x64.bin",
725 MissingCommand::XFlowey {
726 description: "OpenHCL IGVM file",
727 xflowey_args: &["build-igvm", "x64"],
728 },
729 ),
730 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel) => (
731 "flowey-out/artifacts/build-igvm/debug/x64-devkern",
732 "openhcl-x64-devkern.bin",
733 MissingCommand::XFlowey {
734 description: "OpenHCL IGVM file",
735 xflowey_args: &["build-igvm", "x64-devkern"],
736 },
737 ),
738 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Cvm) => (
739 "flowey-out/artifacts/build-igvm/debug/x64-cvm",
740 "openhcl-x64-cvm.bin",
741 MissingCommand::XFlowey {
742 description: "OpenHCL IGVM file",
743 xflowey_args: &["build-igvm", "x64-cvm"],
744 },
745 ),
746 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::LinuxDirect) => (
747 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
748 "openhcl-x64-test-linux-direct.bin",
749 MissingCommand::XFlowey {
750 description: "OpenHCL IGVM file",
751 xflowey_args: &["build-igvm", "x64-test-linux-direct"],
752 },
753 ),
754 (MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::Standard) => (
755 "flowey-out/artifacts/build-igvm/debug/aarch64",
756 "openhcl-aarch64.bin",
757 MissingCommand::XFlowey {
758 description: "OpenHCL IGVM file",
759 xflowey_args: &["build-igvm", "aarch64"],
760 },
761 ),
762 (MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel) => (
763 "flowey-out/artifacts/build-igvm/debug/aarch64-devkern",
764 "openhcl-aarch64-devkern.bin",
765 MissingCommand::XFlowey {
766 description: "OpenHCL IGVM file",
767 xflowey_args: &["build-igvm", "aarch64-devkern"],
768 },
769 ),
770 (MachineArch::X86_64, OpenhclVersion::Release2511, OpenhclFlavor::LinuxDirect) => (
771 "flowey-out/artifacts/last-release-igvm-files",
772 "release-2511-x64-direct-openhcl.bin",
773 MissingCommand::XFlowey {
774 description: "Previous OpenHCL release IGVM file",
775 xflowey_args: &["restore-packages"],
776 },
777 ),
778 (MachineArch::X86_64, OpenhclVersion::Release2511, OpenhclFlavor::Standard) => (
779 "flowey-out/artifacts/last-release-igvm-files",
780 "release-2511-x64-openhcl.bin",
781 MissingCommand::XFlowey {
782 description: "Previous OpenHCL release IGVM file",
783 xflowey_args: &["restore-packages"],
784 },
785 ),
786 (MachineArch::Aarch64, OpenhclVersion::Release2511, OpenhclFlavor::Standard) => (
787 "flowey-out/artifacts/last-release-igvm-files",
788 "release-2511-aarch64-openhcl.bin",
789 MissingCommand::XFlowey {
790 description: "Previous OpenHCL release IGVM file",
791 xflowey_args: &["restore-packages"],
792 },
793 ),
794 _ => anyhow::bail!("no openhcl bin with given arch, version, and flavor"),
795 };
796
797 get_path(path, name, cmd)
798}
799
800fn openhcl_extras_path(
802 version: OpenhclVersion,
803 flavor: OpenhclFlavor,
804 item: OpenhclExtras,
805) -> anyhow::Result<PathBuf> {
806 if !matches!(version, OpenhclVersion::Latest) || !matches!(flavor, OpenhclFlavor::LinuxDirect) {
807 anyhow::bail!("Debug symbol path currently only available for LATEST_LINUX_DIRECT_TEST")
808 }
809
810 let (path, name) = match item {
811 OpenhclExtras::UmBin => (
812 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
813 "openvmm_hcl_msft",
814 ),
815 OpenhclExtras::UmDbg => (
816 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
817 "openvmm_hcl_msft.dbg",
818 ),
819 };
820
821 get_path(
822 path,
823 name,
824 MissingCommand::XFlowey {
825 description: "OpenHCL IGVM file",
826 xflowey_args: &["build-igvm", "x64-test-linux-direct"],
827 },
828 )
829}
830
831fn test_log_directory_path(test_name: &str) -> anyhow::Result<PathBuf> {
833 let root = if let Some(path) = std::env::var_os("TEST_OUTPUT_PATH") {
834 PathBuf::from(path)
835 } else {
836 get_repo_root()?.join("vmm_test_results")
837 };
838 let path = root.join(test_name.replace("::", "__"));
841 fs_err::create_dir_all(&path)?;
842 Ok(path)
843}
844
845const VMM_TESTS_DIR_ENV_VAR: &str = "VMM_TESTS_CONTENT_DIR";
846
847pub fn get_repo_root() -> anyhow::Result<PathBuf> {
849 Ok(Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."))
850}
851
852fn try_get_path(
853 search_path: impl AsRef<Path>,
854 file_name: impl AsRef<Path>,
855) -> anyhow::Result<Option<PathBuf>> {
856 let search_path = search_path.as_ref();
857 let file_name = file_name.as_ref();
858 if file_name.is_absolute() {
859 anyhow::bail!("{} should be a relative path", file_name.display());
860 }
861
862 if let Ok(env_dir) = std::env::var(VMM_TESTS_DIR_ENV_VAR) {
863 let full_path = Path::new(&env_dir).join(file_name);
864 if full_path.try_exists()? {
865 return Ok(Some(full_path));
866 }
867 }
868
869 let file_path = if search_path.is_absolute() {
870 search_path.to_owned()
871 } else {
872 get_repo_root()?.join(search_path)
873 };
874
875 let full_path = file_path.join(file_name);
876 Ok(full_path.try_exists()?.then_some(full_path))
877}
878
879fn flowey_built_executable_path(
880 search_path: impl AsRef<Path>,
881 binary_prefix: &str,
882) -> anyhow::Result<Option<PathBuf>> {
883 let search_path = search_path.as_ref();
884 let dir = if search_path.is_absolute() {
885 search_path.to_owned()
886 } else {
887 get_repo_root()?.join(search_path)
888 };
889
890 if !dir.is_dir() {
891 return Ok(None);
892 }
893
894 let mut candidates = Vec::new();
895 for entry in std::fs::read_dir(&dir)? {
896 let entry = entry?;
897 let path = entry.path();
898 if !path.is_file() {
899 continue;
900 }
901
902 if path.extension().is_some() {
903 continue;
904 }
905
906 let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
907 continue;
908 };
909
910 if file_name == binary_prefix || file_name.starts_with(&format!("{binary_prefix}-")) {
911 let modified = entry.metadata()?.modified().ok();
912 candidates.push((modified, path));
913 }
914 }
915
916 candidates.sort_by_key(|(modified, _)| *modified);
917 Ok(candidates.pop().map(|(_, path)| path))
918}
919
920pub fn get_path(
934 search_path: impl AsRef<Path>,
935 file_name: impl AsRef<Path>,
936 missing_cmd: MissingCommand<'_>,
937) -> anyhow::Result<PathBuf> {
938 let search_path = search_path.as_ref();
939 let file_name = file_name.as_ref();
940 if file_name.is_absolute() {
941 anyhow::bail!("{} should be a relative path", file_name.display());
942 }
943
944 if let Ok(env_dir) = std::env::var(VMM_TESTS_DIR_ENV_VAR) {
945 let full_path = Path::new(&env_dir).join(file_name);
946 if full_path.fs_err_try_exists()? {
947 return Ok(full_path);
948 }
949 }
950
951 let file_path = if search_path.is_absolute() {
952 search_path.to_owned()
953 } else {
954 get_repo_root()?.join(search_path)
955 };
956
957 let full_path = file_path.join(file_name);
958 if !full_path.fs_err_try_exists()? {
959 missing_cmd
960 .to_error()
961 .with_context(|| format!("failed to find {}", full_path.display()))?;
962 }
963
964 Ok(full_path)
965}
966
967pub fn get_output_executable_path(name: &str) -> anyhow::Result<PathBuf> {
971 let mut path: PathBuf = std::env::current_exe()?;
972 if path.parent().and_then(|x| x.file_name()).unwrap() == "deps" {
975 path.pop();
976 }
977
978 get_path(
979 path.parent().unwrap(),
980 Path::new(name).with_extension(EXE_EXTENSION),
981 MissingCommand::Build {
982 package: name,
983 target: None,
984 },
985 )
986}
987
988#[derive(Copy, Clone)]
991#[expect(missing_docs)] pub enum MissingCommand<'a> {
993 Build {
995 package: &'a str,
996 target: Option<&'a str>,
997 },
998 Run {
1000 description: &'a str,
1001 package: &'a str,
1002 },
1003 Xtask {
1005 description: &'a str,
1006 xtask_args: &'a [&'a str],
1007 },
1008 XFlowey {
1010 description: &'a str,
1011 xflowey_args: &'a [&'a str],
1012 },
1013 Restore { description: &'a str },
1015 Custom { description: &'a str, cmd: &'a str },
1017}
1018
1019impl MissingCommand<'_> {
1020 fn to_error(self) -> anyhow::Result<()> {
1021 match self {
1022 MissingCommand::Build { package, target } => anyhow::bail!(
1023 "Failed to find {package} binary. Run `cargo build {target_args}-p {package}` to build it.",
1024 target_args =
1025 target.map_or(String::new(), |target| format!("--target {} ", target)),
1026 ),
1027 MissingCommand::Run {
1028 description,
1029 package,
1030 } => anyhow::bail!(
1031 "Failed to find {}. Run `cargo run -p {}` to create it.",
1032 description,
1033 package
1034 ),
1035 MissingCommand::Xtask {
1036 description,
1037 xtask_args: args,
1038 } => {
1039 anyhow::bail!(
1040 "Failed to find {}. Run `cargo xtask {}` to create it.",
1041 description,
1042 args.join(" ")
1043 )
1044 }
1045 MissingCommand::XFlowey {
1046 description,
1047 xflowey_args: args,
1048 } => anyhow::bail!(
1049 "Failed to find {}. Run `cargo xflowey {}` to create it.",
1050 description,
1051 args.join(" ")
1052 ),
1053 MissingCommand::Restore { description } => {
1054 anyhow::bail!(
1055 "Failed to find {}. Run `cargo xflowey restore-packages`.",
1056 description
1057 )
1058 }
1059 MissingCommand::Custom { description, cmd } => {
1060 anyhow::bail!(
1061 "Failed to find {}. Run `{}` to create it.",
1062 description,
1063 cmd
1064 )
1065 }
1066 }
1067 }
1068}