petri_artifact_resolver_openvmm_known_paths/
lib.rs1#![forbid(unsafe_code)]
7
8use anyhow::Context;
9use petri_artifacts_common::artifacts::*;
10use petri_artifacts_core::ArtifactId;
11use petri_artifacts_core::ArtifactSource;
12use petri_artifacts_core::ErasedArtifactHandle;
13use petri_artifacts_vmm_test::artifacts::*;
14use petri_artifacts_vmm_test::vmm_test_image_from_id;
15use std::env::consts::EXE_EXTENSION;
16use std::path::Path;
17use std::path::PathBuf;
18
19pub struct OpenvmmKnownPathsTestArtifactResolver<'a>(&'a str);
23
24impl<'a> OpenvmmKnownPathsTestArtifactResolver<'a> {
25 pub fn new(test_name: &'a str) -> Self {
27 Self(test_name)
28 }
29}
30
31impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifactResolver<'_> {
32 fn resolve(&self, handle: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
33 match handle.global_unique_id() {
34 TEST_LOG_DIRECTORY::GLOBAL_UNIQUE_ID => test_log_directory_path(self.0),
35
36 test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED::GLOBAL_UNIQUE_ID
37 | test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64_NO_VMBUS_PREPPED::GLOBAL_UNIQUE_ID => {
38 get_vmm_test_image_path(handle.filename(), handle.global_unique_id())
39 }
40
41 id if let Some(artifact) = vmm_test_image_from_id(id) => {
42 get_vmm_test_image_path(artifact.filename(), artifact.name())
43 }
44
45 _ => resolve_artifact(handle),
46 }
47 }
48
49 fn resolve_source(&self, handle: ErasedArtifactHandle) -> anyhow::Result<ArtifactSource> {
50 let local_err = match self.resolve(handle) {
52 Ok(path) => return Ok(ArtifactSource::Local(path)),
53 Err(e) => e,
54 };
55
56 if let Some(url) = vmm_test_image_from_id(handle.global_unique_id()).and_then(|i| i.url()) {
59 return Ok(ArtifactSource::Remote { url });
60 }
61
62 Err(local_err)
64 }
65}
66
67const VMM_TESTS_CONTENT_DIR_ENV_VAR: &str = "VMM_TESTS_CONTENT_DIR";
68const TEST_OUTPUT_PATH_ENV_VAR: &str = "TEST_OUTPUT_PATH";
69const VMM_TEST_IMAGES_ENV_VAR: &str = "VMM_TEST_IMAGES";
70
71pub fn resolve_artifact(handle: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
73 let test_content_dir_path = test_content_dir_artifact_path(handle.relative_path());
74
75 if test_content_dir_path.is_ok() {
76 return test_content_dir_path;
77 }
78
79 let magic_path = resolve_magic_path_artifact(handle);
80
81 if magic_path.is_ok() {
82 return magic_path;
83 }
84
85 let target = handle.target_triple();
86 let exe_artifact_path = target
87 .as_ref()
88 .context("no associated triple for artifact")
89 .and_then(|t| get_executable_path_artifact(t, None, handle.filename()));
90
91 if exe_artifact_path.is_ok() {
92 return exe_artifact_path;
93 }
94
95 let relative_path = get_executable_path_relative(handle.filename());
96
97 if relative_path.is_ok() {
98 return relative_path;
99 }
100
101 let fallback_target = handle.target_triple().and_then(|mut target| {
103 matches!(
104 target,
105 target_lexicon::Triple {
106 operating_system: target_lexicon::OperatingSystem::Linux,
107 environment: target_lexicon::Environment::Gnu | target_lexicon::Environment::Musl,
108 ..
109 }
110 )
111 .then(|| {
112 if target.environment == target_lexicon::Environment::Gnu {
113 target.environment = target_lexicon::Environment::Musl;
114 } else {
115 target.environment = target_lexicon::Environment::Gnu;
116 }
117 target
118 })
119 });
120
121 let fallback_test_content_dir_path = fallback_target
122 .as_ref()
123 .context("no fallback target")
124 .and_then(|t| {
125 test_content_dir_artifact_path(PathBuf::from(t.to_string()).join(handle.filename()))
126 });
127
128 if fallback_test_content_dir_path.is_ok() {
129 return fallback_test_content_dir_path;
130 }
131
132 let fallback_exe_artifact_path = fallback_target
133 .as_ref()
134 .context("no associated triple for artifact")
135 .and_then(|t| get_executable_path_artifact(t, None, handle.filename()));
136
137 if fallback_exe_artifact_path.is_ok() {
138 return fallback_exe_artifact_path;
139 }
140
141 Err(anyhow::anyhow!(
142 "unable to locate {}:\n\t{}\n\t{}\n\t{}\n\t{}\n\t{}\n\t{}",
143 handle.global_unique_id(),
144 test_content_dir_path.unwrap_err(),
145 magic_path.unwrap_err(),
146 exe_artifact_path.unwrap_err(),
147 relative_path.unwrap_err(),
148 fallback_test_content_dir_path.unwrap_err(),
149 fallback_exe_artifact_path.unwrap_err(),
150 ))
151}
152
153fn test_content_dir_artifact_path(relative_path: impl AsRef<Path>) -> anyhow::Result<PathBuf> {
154 let test_content_dir =
155 std::env::var(VMM_TESTS_CONTENT_DIR_ENV_VAR).context("test content dir env var not set")?;
156 let path = PathBuf::from(test_content_dir).join(relative_path.as_ref());
157 if !path.exists() {
158 anyhow::bail!("{} not found", path.display())
159 }
160 Ok(path)
161}
162
163fn test_log_directory_path(test_name: &str) -> anyhow::Result<PathBuf> {
165 let root = std::env::var_os(TEST_OUTPUT_PATH_ENV_VAR)
166 .map_or_else(|| get_repo_root().join("vmm_test_results"), PathBuf::from);
167 let path = root.join(test_name.replace("::", "__"));
170 fs_err::create_dir_all(&path)?;
171 Ok(path)
172}
173
174pub fn get_repo_root() -> PathBuf {
176 Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
177}
178
179pub fn cargo_build_profile() -> &'static str {
186 static PROFILE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
187 PROFILE.get_or_init(|| {
188 if let Ok(exe) = std::env::current_exe() {
189 if exe.components().any(|c| c.as_os_str() == "release") {
190 return "release".to_string();
191 }
192 }
193 "debug".to_string()
194 })
195}
196
197pub fn get_executable_path_relative(name: &str) -> anyhow::Result<PathBuf> {
200 let mut current_exe = std::env::current_exe().context("unable to get current exe")?;
201 if current_exe.parent().and_then(|x| x.file_name()).unwrap() == "deps" {
204 current_exe.pop();
205 }
206
207 let exe_path = current_exe
208 .parent()
209 .context("current exe has no parent")?
210 .join(Path::new(name).with_extension(EXE_EXTENSION));
211 if !exe_path.exists() {
212 anyhow::bail!("{} not found", exe_path.display());
213 }
214 Ok(exe_path)
215}
216
217pub fn get_executable_path_artifact(
221 target: &target_lexicon::Triple,
222 build_profile: Option<&str>,
223 filename: &str,
224) -> anyhow::Result<PathBuf> {
225 let exe_path = get_repo_root()
226 .join("target")
227 .join(target.to_string())
228 .join(build_profile.unwrap_or_else(|| cargo_build_profile()))
229 .join(filename);
230 if !exe_path.exists() {
231 anyhow::bail!("{} not found", exe_path.display());
232 }
233 Ok(exe_path)
234}
235
236fn get_vmm_test_image_path(filename: &str, name: &str) -> Result<PathBuf, anyhow::Error> {
237 let test_images_dir =
238 std::env::var(VMM_TEST_IMAGES_ENV_VAR).context("test images dir env var not set")?;
239 let path = PathBuf::from(test_images_dir).join(filename);
240 if !path.exists() {
241 anyhow::bail!("missing {} at {}", name, path.display())
242 }
243 Ok(path)
244}
245
246fn resolve_magic_path_artifact(handle: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
249 let package_dir = match handle.global_unique_id() {
250 loadable::LINUX_DIRECT_TEST_KERNEL_X64::GLOBAL_UNIQUE_ID
251 | loadable::LINUX_DIRECT_TEST_BZIMAGE_X64::GLOBAL_UNIQUE_ID
252 | loadable::LINUX_DIRECT_TEST_INITRD_X64::GLOBAL_UNIQUE_ID
253 | petritools::PETRITOOLS_EROFS_X64::GLOBAL_UNIQUE_ID => "underhill-deps-private/x64",
254 loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64::GLOBAL_UNIQUE_ID
255 | loadable::LINUX_DIRECT_TEST_INITRD_AARCH64::GLOBAL_UNIQUE_ID
256 | petritools::PETRITOOLS_EROFS_AARCH64::GLOBAL_UNIQUE_ID => {
257 "underhill-deps-private/aarch64"
258 }
259 loadable::UEFI_FIRMWARE_X64::GLOBAL_UNIQUE_ID => {
260 "hyperv.uefi.mscoreuefi.x64.RELEASE/MsvmX64/RELEASE_VS2022/FV"
261 }
262 loadable::UEFI_FIRMWARE_AARCH64::GLOBAL_UNIQUE_ID => {
263 "hyperv.uefi.mscoreuefi.AARCH64.RELEASE/MsvmAARCH64/RELEASE_CLANGPDB/FV"
264 }
265 loadable::PCAT_FIRMWARE_X64::GLOBAL_UNIQUE_ID => {
266 "Microsoft.Windows.VmFirmware.Pcat.amd64fre/content"
267 }
268 loadable::SVGA_FIRMWARE_X64::GLOBAL_UNIQUE_ID => {
269 "Microsoft.Windows.VmEmulatedDevices.amd64fre/content"
270 }
271 virtio_win::VIRTIO_WINDOWS_DRIVERS::GLOBAL_UNIQUE_ID => "",
272
273 _ => anyhow::bail!("not a magic path artifact"),
274 };
275
276 let magic_path = get_repo_root()
277 .join(".packages")
278 .join(package_dir)
279 .join(handle.filename());
280 if !magic_path.exists() {
281 anyhow::bail!("{} not found", magic_path.display());
282 }
283 Ok(magic_path)
284}