1#![forbid(unsafe_code)]
7
8use petri_artifacts_common::tags::MachineArch;
9use petri_artifacts_core::ErasedArtifactHandle;
10use std::env::consts::EXE_EXTENSION;
11use std::path::Path;
12use std::path::PathBuf;
13use vmm_test_images::KnownTestArtifacts;
14
15pub struct OpenvmmKnownPathsTestArtifactResolver<'a>(&'a str);
19
20impl<'a> OpenvmmKnownPathsTestArtifactResolver<'a> {
21 pub fn new(test_name: &'a str) -> Self {
23 Self(test_name)
24 }
25}
26
27impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifactResolver<'_> {
28 #[rustfmt::skip]
29 fn resolve(&self, id: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
30 use petri_artifacts_common::artifacts as common;
31 use petri_artifacts_vmm_test::artifacts::*;
32
33 match id {
34 _ if id == common::PIPETTE_WINDOWS_X64 => pipette_path(MachineArch::X86_64, PipetteFlavor::Windows),
35 _ if id == common::PIPETTE_LINUX_X64 => pipette_path(MachineArch::X86_64, PipetteFlavor::Linux),
36 _ if id == common::PIPETTE_WINDOWS_AARCH64 => pipette_path(MachineArch::Aarch64, PipetteFlavor::Windows),
37 _ if id == common::PIPETTE_LINUX_AARCH64 => pipette_path(MachineArch::Aarch64, PipetteFlavor::Linux),
38
39 _ if id == common::TEST_LOG_DIRECTORY => test_log_directory_path(self.0),
40
41 _ if id == OPENVMM_NATIVE => openvmm_native_executable_path(),
42
43 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_X64 => linux_direct_x64_test_kernel_path(),
44 _ if id == loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64 => linux_direct_arm_image_path(),
45 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_X64 => linux_direct_test_initrd_path(MachineArch::X86_64),
46 _ if id == loadable::LINUX_DIRECT_TEST_INITRD_AARCH64 => linux_direct_test_initrd_path(MachineArch::Aarch64),
47
48 _ if id == loadable::PCAT_FIRMWARE_X64 => pcat_firmware_path(),
49 _ if id == loadable::SVGA_FIRMWARE_X64 => svga_firmware_path(),
50 _ if id == loadable::UEFI_FIRMWARE_X64 => uefi_firmware_path(MachineArch::X86_64),
51 _ if id == loadable::UEFI_FIRMWARE_AARCH64 => uefi_firmware_path(MachineArch::Aarch64),
52
53 _ if id == openhcl_igvm::LATEST_STANDARD_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Standard),
54 _ if id == openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel),
55 _ if id == openhcl_igvm::LATEST_CVM_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Cvm),
56 _ if id == openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_bin_path(MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::LinuxDirect),
57 _ if id == openhcl_igvm::LATEST_STANDARD_AARCH64 => openhcl_bin_path(MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::Standard),
58 _ if id == openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_AARCH64 => openhcl_bin_path(MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel),
59
60 _ if id == openhcl_igvm::um_bin::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_extras_path(OpenhclVersion::Latest,OpenhclFlavor::LinuxDirect,OpenhclExtras::UmBin),
61 _ if id == openhcl_igvm::um_dbg::LATEST_LINUX_DIRECT_TEST_X64 => openhcl_extras_path(OpenhclVersion::Latest,OpenhclFlavor::LinuxDirect,OpenhclExtras::UmDbg),
62
63 _ if id == test_vhd::GUEST_TEST_UEFI_X64 => guest_test_uefi_disk_path(MachineArch::X86_64),
64 _ if id == test_vhd::GUEST_TEST_UEFI_AARCH64 => guest_test_uefi_disk_path(MachineArch::Aarch64),
65 _ if id == test_vhd::GEN1_WINDOWS_DATA_CENTER_CORE2022_X64 => get_test_artifact_path(KnownTestArtifacts::Gen1WindowsDataCenterCore2022X64Vhd),
66 _ if id == test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64 => get_test_artifact_path(KnownTestArtifacts::Gen2WindowsDataCenterCore2022X64Vhd),
67 _ if id == test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64 => get_test_artifact_path(KnownTestArtifacts::Gen2WindowsDataCenterCore2025X64Vhd),
68 _ if id == test_vhd::FREE_BSD_13_2_X64 => get_test_artifact_path(KnownTestArtifacts::FreeBsd13_2X64Vhd),
69 _ if id == test_vhd::UBUNTU_2204_SERVER_X64 => get_test_artifact_path(KnownTestArtifacts::Ubuntu2204ServerX64Vhd),
70 _ if id == test_vhd::UBUNTU_2404_SERVER_AARCH64 => get_test_artifact_path(KnownTestArtifacts::Ubuntu2404ServerAarch64Vhd),
71 _ if id == test_vhd::WINDOWS_11_ENTERPRISE_AARCH64 => get_test_artifact_path(KnownTestArtifacts::Windows11EnterpriseAarch64Vhdx),
72
73 _ if id == test_iso::FREE_BSD_13_2_X64 => get_test_artifact_path(KnownTestArtifacts::FreeBsd13_2X64Iso),
74
75 _ if id == test_vmgs::VMGS_WITH_BOOT_ENTRY => get_test_artifact_path(KnownTestArtifacts::VmgsWithBootEntry),
76
77 _ if id == tmks::TMK_VMM_NATIVE => tmk_vmm_native_executable_path(),
78 _ if id == tmks::TMK_VMM_LINUX_X64_MUSL => tmk_vmm_paravisor_path(MachineArch::X86_64),
79 _ if id == tmks::TMK_VMM_LINUX_AARCH64_MUSL => tmk_vmm_paravisor_path(MachineArch::Aarch64),
80 _ if id == tmks::SIMPLE_TMK_X64 => simple_tmk_path(MachineArch::X86_64),
81 _ if id == tmks::SIMPLE_TMK_AARCH64 => simple_tmk_path(MachineArch::Aarch64),
82
83 _ => anyhow::bail!("no support for given artifact type"),
84 }
85 }
86}
87
88#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
89enum PipetteFlavor {
90 Windows,
91 Linux,
92}
93
94#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
95enum OpenhclVersion {
96 Latest,
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
100enum OpenhclFlavor {
101 Standard,
102 StandardDevKernel,
103 Cvm,
104 LinuxDirect,
105}
106
107#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
108enum OpenhclExtras {
109 UmBin,
110 UmDbg,
111}
112
113fn target_arch_path(arch: MachineArch) -> &'static str {
115 match arch {
116 MachineArch::X86_64 => "x86_64",
117 MachineArch::Aarch64 => "aarch64",
118 }
119}
120
121fn get_test_artifact_path(artifact: KnownTestArtifacts) -> Result<PathBuf, anyhow::Error> {
122 let images_dir = std::env::var("VMM_TEST_IMAGES");
123 let full_path = Path::new(images_dir.as_deref().unwrap_or("images"));
124
125 get_path(
126 full_path,
127 artifact.filename(),
128 MissingCommand::Xtask {
129 xtask_args: &[
130 "guest-test",
131 "download-image",
132 "--artifacts",
133 &artifact.name(),
134 ],
135 description: "test artifact",
136 },
137 )
138}
139
140fn guest_test_uefi_disk_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
142 get_path(
144 format!("target/{}-unknown-uefi/debug", target_arch_path(arch)),
145 "guest_test_uefi.img",
146 MissingCommand::Xtask {
147 xtask_args: &[
148 "guest-test",
149 "uefi",
150 &format!(
151 "--boot{}",
152 match arch {
153 MachineArch::X86_64 => "x64",
154 MachineArch::Aarch64 => "aa64",
155 }
156 ),
157 ],
158 description: "guest_test_uefi image",
159 },
160 )
161}
162
163fn pipette_path(arch: MachineArch, os_flavor: PipetteFlavor) -> anyhow::Result<PathBuf> {
165 let (target_suffixes, binary) = match os_flavor {
168 PipetteFlavor::Windows => (vec!["pc-windows-msvc", "pc-windows-gnu"], "pipette.exe"),
169 PipetteFlavor::Linux => (vec!["unknown-linux-musl"], "pipette"),
170 };
171 for (index, target_suffix) in target_suffixes.iter().enumerate() {
172 let target = format!("{}-{}", target_arch_path(arch), target_suffix);
173 match get_path(
174 format!("target/{target}/debug"),
175 binary,
176 MissingCommand::Build {
177 package: "pipette",
178 target: Some(&target),
179 },
180 ) {
181 Ok(path) => return Ok(path),
182 Err(err) => {
183 if index < target_suffixes.len() - 1 {
184 continue;
185 } else {
186 anyhow::bail!(
187 "None of the suffixes {:?} had `pipette` built, {err:?}",
188 target_suffixes
189 );
190 }
191 }
192 }
193 }
194
195 unreachable!()
196}
197
198fn openvmm_native_executable_path() -> anyhow::Result<PathBuf> {
200 get_output_executable_path("openvmm")
201}
202
203fn tmk_vmm_native_executable_path() -> anyhow::Result<PathBuf> {
205 get_output_executable_path("tmk_vmm")
206}
207
208fn tmk_vmm_paravisor_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
209 let target = match arch {
210 MachineArch::X86_64 => "x86_64-unknown-linux-musl",
211 MachineArch::Aarch64 => "aarch64-unknown-linux-musl",
212 };
213 get_path(
214 format!("target/{target}/debug"),
215 "tmk_vmm",
216 MissingCommand::Build {
217 package: "tmk_vmm",
218 target: Some(target),
219 },
220 )
221}
222
223fn simple_tmk_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
225 let arch_str = match arch {
226 MachineArch::X86_64 => "x86_64",
227 MachineArch::Aarch64 => "aarch64",
228 };
229 let target = match arch {
230 MachineArch::X86_64 => "x86_64-unknown-none",
231 MachineArch::Aarch64 => "aarch64-minimal_rt-none",
232 };
233 get_path(
234 format!("target/{target}/debug"),
235 "simple_tmk",
236 MissingCommand::Custom {
237 description: "simple_tmk",
238 cmd: &format!(
239 "RUSTC_BOOTSTRAP=1 cargo build -p simple_tmk --config openhcl/minimal_rt/{arch_str}-config.toml"
240 ),
241 },
242 )
243}
244
245fn linux_direct_x64_test_kernel_path() -> anyhow::Result<PathBuf> {
247 get_path(
248 ".packages/underhill-deps-private",
249 "x64/vmlinux",
250 MissingCommand::Restore {
251 description: "linux direct test kernel",
252 },
253 )
254}
255
256fn linux_direct_test_initrd_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
258 get_path(
259 ".packages/underhill-deps-private",
260 format!(
261 "{}/initrd",
262 match arch {
263 MachineArch::X86_64 => "x64",
264 MachineArch::Aarch64 => "aarch64",
265 }
266 ),
267 MissingCommand::Restore {
268 description: "linux direct test initrd",
269 },
270 )
271}
272
273fn linux_direct_arm_image_path() -> anyhow::Result<PathBuf> {
275 get_path(
276 ".packages/underhill-deps-private",
277 "aarch64/Image",
278 MissingCommand::Restore {
279 description: "linux direct test kernel",
280 },
281 )
282}
283
284fn pcat_firmware_path() -> anyhow::Result<PathBuf> {
286 get_path(
287 ".packages",
288 "Microsoft.Windows.VmFirmware.Pcat.amd64fre/content/vmfirmwarepcat.dll",
289 MissingCommand::Restore {
290 description: "PCAT firmware binary",
291 },
292 )
293}
294
295fn svga_firmware_path() -> anyhow::Result<PathBuf> {
297 get_path(
298 ".packages",
299 "Microsoft.Windows.VmEmulatedDevices.amd64fre/content/VmEmulatedDevices.dll",
300 MissingCommand::Restore {
301 description: "SVGA firmware binary",
302 },
303 )
304}
305
306fn uefi_firmware_path(arch: MachineArch) -> anyhow::Result<PathBuf> {
308 get_path(
309 ".packages",
310 match arch {
311 MachineArch::X86_64 => {
312 "hyperv.uefi.mscoreuefi.x64.RELEASE/MsvmX64/RELEASE_VS2022/FV/MSVM.fd"
313 }
314 MachineArch::Aarch64 => {
315 "hyperv.uefi.mscoreuefi.AARCH64.RELEASE/MsvmAARCH64/RELEASE_VS2022/FV/MSVM.fd"
316 }
317 },
318 MissingCommand::Restore {
319 description: "UEFI firmware binary",
320 },
321 )
322}
323
324fn openhcl_bin_path(
326 arch: MachineArch,
327 version: OpenhclVersion,
328 flavor: OpenhclFlavor,
329) -> anyhow::Result<PathBuf> {
330 let (path, name, cmd) = match (arch, version, flavor) {
331 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Standard) => (
332 "flowey-out/artifacts/build-igvm/debug/x64",
333 "openhcl-x64.bin",
334 MissingCommand::XFlowey {
335 description: "OpenHCL IGVM file",
336 xflowey_args: &["build-igvm", "x64"],
337 },
338 ),
339 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel) => (
340 "flowey-out/artifacts/build-igvm/debug/x64-devkern",
341 "openhcl-x64-devkern.bin",
342 MissingCommand::XFlowey {
343 description: "OpenHCL IGVM file",
344 xflowey_args: &["build-igvm", "x64-devkern"],
345 },
346 ),
347 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::Cvm) => (
348 "flowey-out/artifacts/build-igvm/debug/x64-cvm",
349 "openhcl-x64-cvm.bin",
350 MissingCommand::XFlowey {
351 description: "OpenHCL IGVM file",
352 xflowey_args: &["build-igvm", "x64-cvm"],
353 },
354 ),
355 (MachineArch::X86_64, OpenhclVersion::Latest, OpenhclFlavor::LinuxDirect) => (
356 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
357 "openhcl-x64-test-linux-direct.bin",
358 MissingCommand::XFlowey {
359 description: "OpenHCL IGVM file",
360 xflowey_args: &["build-igvm", "x64-test-linux-direct"],
361 },
362 ),
363 (MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::Standard) => (
364 "flowey-out/artifacts/build-igvm/debug/aarch64",
365 "openhcl-aarch64.bin",
366 MissingCommand::XFlowey {
367 description: "OpenHCL IGVM file",
368 xflowey_args: &["build-igvm", "aarch64"],
369 },
370 ),
371 (MachineArch::Aarch64, OpenhclVersion::Latest, OpenhclFlavor::StandardDevKernel) => (
372 "flowey-out/artifacts/build-igvm/debug/aarch64-devkern",
373 "openhcl-aarch64-devkern.bin",
374 MissingCommand::XFlowey {
375 description: "OpenHCL IGVM file",
376 xflowey_args: &["build-igvm", "aarch64-devkern"],
377 },
378 ),
379 _ => anyhow::bail!("no openhcl bin with given arch, version, and flavor"),
380 };
381
382 get_path(path, name, cmd)
383}
384
385fn openhcl_extras_path(
387 version: OpenhclVersion,
388 flavor: OpenhclFlavor,
389 item: OpenhclExtras,
390) -> anyhow::Result<PathBuf> {
391 if !matches!(version, OpenhclVersion::Latest) || !matches!(flavor, OpenhclFlavor::LinuxDirect) {
392 anyhow::bail!("Debug symbol path currently only available for LATEST_LINUX_DIRECT_TEST")
393 }
394
395 let (path, name) = match item {
396 OpenhclExtras::UmBin => (
397 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
398 "openvmm_hcl_msft",
399 ),
400 OpenhclExtras::UmDbg => (
401 "flowey-out/artifacts/build-igvm/debug/x64-test-linux-direct",
402 "openvmm_hcl_msft.dbg",
403 ),
404 };
405
406 get_path(
407 path,
408 name,
409 MissingCommand::XFlowey {
410 description: "OpenHCL IGVM file",
411 xflowey_args: &["build-igvm", "x64-test-linux-direct"],
412 },
413 )
414}
415
416fn test_log_directory_path(test_name: &str) -> anyhow::Result<PathBuf> {
418 let root = if let Some(path) = std::env::var_os("TEST_OUTPUT_PATH") {
419 PathBuf::from(path)
420 } else {
421 get_repo_root()?.join("vmm_test_results")
422 };
423 let path = root.join(test_name.replace("::", "__"));
426 fs_err::create_dir_all(&path)?;
427 Ok(path)
428}
429
430const VMM_TESTS_DIR_ENV_VAR: &str = "VMM_TESTS_CONTENT_DIR";
431
432pub fn get_repo_root() -> anyhow::Result<PathBuf> {
434 Ok(Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."))
435}
436
437pub fn get_path(
451 search_path: impl AsRef<Path>,
452 file_name: impl AsRef<Path>,
453 missing_cmd: MissingCommand<'_>,
454) -> anyhow::Result<PathBuf> {
455 let search_path = search_path.as_ref();
456 let file_name = file_name.as_ref();
457 if file_name.is_absolute() {
458 anyhow::bail!("{} should be a relative path", file_name.display());
459 }
460
461 if let Ok(env_dir) = std::env::var(VMM_TESTS_DIR_ENV_VAR) {
462 let full_path = Path::new(&env_dir).join(file_name);
463 if full_path.try_exists()? {
464 return Ok(full_path);
465 }
466 }
467
468 let file_path = if search_path.is_absolute() {
469 search_path.to_owned()
470 } else {
471 get_repo_root()?.join(search_path)
472 };
473
474 let full_path = file_path.join(file_name);
475 if !full_path.exists() {
476 eprintln!("Failed to find {:?}.", full_path);
477 missing_cmd.to_error()?;
478 }
479
480 Ok(full_path)
481}
482
483pub fn get_output_executable_path(name: &str) -> anyhow::Result<PathBuf> {
487 let mut path: PathBuf = std::env::current_exe()?;
488 if path.parent().and_then(|x| x.file_name()).unwrap() == "deps" {
491 path.pop();
492 }
493
494 get_path(
495 path.parent().unwrap(),
496 Path::new(name).with_extension(EXE_EXTENSION),
497 MissingCommand::Build {
498 package: name,
499 target: None,
500 },
501 )
502}
503
504#[derive(Copy, Clone)]
507#[expect(missing_docs)] pub enum MissingCommand<'a> {
509 Build {
511 package: &'a str,
512 target: Option<&'a str>,
513 },
514 Xtask {
516 description: &'a str,
517 xtask_args: &'a [&'a str],
518 },
519 XFlowey {
521 description: &'a str,
522 xflowey_args: &'a [&'a str],
523 },
524 Restore { description: &'a str },
526 Custom { description: &'a str, cmd: &'a str },
528}
529
530impl MissingCommand<'_> {
531 fn to_error(self) -> anyhow::Result<()> {
532 match self {
533 MissingCommand::Build { package, target } => anyhow::bail!(
534 "Failed to find {package} binary. Run `cargo build {target_args}-p {package}` to build it.",
535 target_args =
536 target.map_or(String::new(), |target| format!("--target {} ", target)),
537 ),
538 MissingCommand::Xtask {
539 description,
540 xtask_args: args,
541 } => {
542 anyhow::bail!(
543 "Failed to find {}. Run `cargo xtask {}` to create it.",
544 description,
545 args.join(" ")
546 )
547 }
548 MissingCommand::XFlowey {
549 description,
550 xflowey_args: args,
551 } => anyhow::bail!(
552 "Failed to find {}. Run `cargo xflowey {}` to create it.",
553 description,
554 args.join(" ")
555 ),
556 MissingCommand::Restore { description } => {
557 anyhow::bail!(
558 "Failed to find {}. Run `cargo xflowey restore-packages`.",
559 description
560 )
561 }
562 MissingCommand::Custom { description, cmd } => {
563 anyhow::bail!(
564 "Failed to find {}. Run `{}` to create it.",
565 description,
566 cmd
567 )
568 }
569 }
570 }
571}