From b8e675454caad333025a28e5b7784f7a70e2171a Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Sun, 3 May 2026 18:50:58 -0700 Subject: [PATCH 1/7] cleanup vmm-tests-run --- .config/nextest.toml | 7 + Cargo.lock | 4 + flowey/flowey_hvlite/Cargo.toml | 3 + .../src/pipelines/vmm_tests_run.rs | 783 +++++++++++------- .../local_build_and_run_nextest_vmm_tests.rs | 28 +- .../src/artifact_to_build_mapping.rs | 403 --------- .../download_openvmm_vmm_tests_artifacts.rs | 5 +- flowey/flowey_lib_hvlite/src/lib.rs | 1 - petri/petri_artifacts_core/Cargo.toml | 1 + petri/petri_artifacts_core/src/lib.rs | 16 + petri/src/test.rs | 18 +- .../src/lib.rs | 40 +- vmm_tests/petri_artifacts_vmm_test/src/lib.rs | 5 + vmm_tests/vmm_test_images/src/lib.rs | 3 + xtask/src/tasks/guest_test/download_image.rs | 5 +- 15 files changed, 543 insertions(+), 779 deletions(-) delete mode 100644 flowey/flowey_lib_hvlite/src/artifact_to_build_mapping.rs diff --git a/.config/nextest.toml b/.config/nextest.toml index 834b2fdeff..f36ebb84a8 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -86,3 +86,10 @@ filter = 'package(~vmm_tests)' # VMM tests contain their own watchdog timer, but keep an extra long timer # here as a backup. slow-timeout = { period = "10m", terminate-after = 2 } + +[[profile.default.overrides]] +# use fuzzy-matching for the package() to allow out-of-tree tests to use the +# same profile +filter = 'package(~vmm_tests)' +# Use the same slow timeout as ci, but never terminate +slow-timeout = { period = "10m" } diff --git a/Cargo.lock b/Cargo.lock index cc95e2e8e3..3c7d6de1b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2131,6 +2131,9 @@ dependencies = [ "flowey_lib_common", "flowey_lib_hvlite", "log", + "petri_artifacts_common", + "petri_artifacts_core", + "petri_artifacts_vmm_test", "serde", "serde_json", "target-lexicon", @@ -6055,6 +6058,7 @@ version = "0.0.0" dependencies = [ "anyhow", "paste", + "serde", ] [[package]] diff --git a/flowey/flowey_hvlite/Cargo.toml b/flowey/flowey_hvlite/Cargo.toml index dd1a95b26c..c39b86589c 100644 --- a/flowey/flowey_hvlite/Cargo.toml +++ b/flowey/flowey_hvlite/Cargo.toml @@ -12,6 +12,9 @@ flowey_lib_common.workspace = true flowey_lib_hvlite.workspace = true flowey.workspace = true +petri_artifacts_common.workspace = true +petri_artifacts_core.workspace = true +petri_artifacts_vmm_test.workspace = true vmm_test_images = { workspace = true, features = ["serde", "clap"] } anyhow.workspace = true diff --git a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs index 50a58a06fd..df927b372e 100644 --- a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs +++ b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs @@ -12,16 +12,20 @@ use anyhow::Context as _; use flowey::node::prelude::ReadVar; use flowey::pipeline::prelude::*; +use flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::BuildSelections; use flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::VmmTestSelections; -use flowey_lib_hvlite::artifact_to_build_mapping::ResolvedArtifactSelections; -use flowey_lib_hvlite::common::CommonArch; use flowey_lib_hvlite::common::CommonTriple; use flowey_lib_hvlite::install_vmm_tests_deps::VmmTestsDepSelections; +use petri_artifacts_core::ArtifactId; +use petri_artifacts_core::ArtifactListOutput; +use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::io::Write as _; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; +use vmm_test_images::KnownTestArtifacts; /// Build and run VMM tests with automatic artifact discovery #[derive(clap::Args)] @@ -34,7 +38,7 @@ pub struct VmmTestsRunCli { /// Directory for the output artifacts #[clap(long)] - dir: PathBuf, + dir: Option, /// Test filter (nextest filter expression) /// @@ -88,6 +92,43 @@ pub struct VmmTestsRunCli { /// downloaded release. Path to a locally-built MSVM.fd file. #[clap(long)] custom_uefi_firmware: Option, + + /// use the nextest CI profile rather than the default one + #[clap(long)] + ci_profile: bool, +} + +struct CargoNextestListRequest<'a> { + repo_root: &'a Path, + target: &'a str, + filter: &'a str, + release: bool, + include_ignored: bool, +} + +struct RustSuite { + binary_path: PathBuf, + testcases: Vec, +} + +/// Result of resolving artifact requirements to build/download selections +struct ResolvedArtifactSelections { + /// What to build + build: BuildSelections, + /// What to download + downloads: BTreeSet, + /// Whether any tests need release IGVM files from GitHub + needs_release_igvm: bool, +} + +impl Default for ResolvedArtifactSelections { + fn default() -> Self { + Self { + build: BuildSelections::none(), + downloads: BTreeSet::new(), + needs_release_igvm: false, + } + } } impl IntoPipeline for VmmTestsRunCli { @@ -111,80 +152,107 @@ impl IntoPipeline for VmmTestsRunCli { custom_kernel_modules, custom_kernel, custom_uefi_firmware, + ci_profile, } = self; - // 1. Resolve target let target = resolve_target(target, backend_hint)?; let target_os = target.as_triple().operating_system; - let target_architecture = target.as_triple().architecture; + let target_arch = target.common_arch()?; let target_str = target.as_triple().to_string(); - // 2. Validate output directory for WSL - validate_output_dir(&dir, target_os)?; - std::fs::create_dir_all(&dir).context("failed to create output directory")?; - - // 3. Run artifact discovery inline at pipeline construction time - log::info!("Step 1: Discovering required artifacts..."); let repo_root = crate::repo_root(); - let (artifacts_json, test_names, test_binary) = - discover_artifacts(&repo_root, &target_str, &filter, release, build_only) - .context("during artifact discovery")?; - - // 4. Resolve to build selections - let mut resolved = ResolvedArtifactSelections::from_artifact_list_json( - &artifacts_json, - target_architecture, - target_os, - ) - .context("failed to parse discovered artifacts")?; - - if !resolved.unknown.is_empty() { - anyhow::bail!( - "Unknown artifacts found (mapping needs to be updated):\n {}", - resolved.unknown.join("\n ") - ); + + // Validate output directory for WSL + validate_output_dir(dir.as_deref(), target_os)?; + let test_content_dir = dir.unwrap_or_else(|| repo_root.join("target").join("vmm_tests")); + std::fs::create_dir_all(&test_content_dir).context("failed to create output directory")?; + + // Run artifact discovery inline at pipeline construction time since + // flowey doesn't support conditional requests yet + log::info!( + "Discovering artifacts for filter: {} (target: {})", + filter, + target + ); + + // Determine which tests match the filter + let suites = run_cargo_nextest_list(CargoNextestListRequest { + repo_root: &repo_root, + target: &target_str, + filter: &filter, + release, + // When using build-only mode, we need to enumerate tests that could be + // run on any system so that we build all necessary dependencies. By default + // petri marks incompatible tests as ignored. + include_ignored: build_only, + })?; + + if suites.is_empty() { + anyhow::bail!("No tests found for the given filter"); + } + + // Query for the required artifacts + let mut artifacts = Vec::new(); + for suite in suites.values() { + artifacts.append(&mut query_test_binary_artifacts(suite)?); } - // 5. Determine lazy fetch mode. + // Resolve to build selections + let mut resolved = ResolvedArtifactSelections::default(); + for artifact in artifacts { + resolved.resolve_artifact(&artifact)?; + } + + // Determine lazy fetch mode. // // By default, VHD/ISO downloads are skipped and disk images are // streamed on demand via HTTP (with local SQLite caching). This // avoids multi-GB upfront downloads for dev-inner-loop scenarios. // - // Lazy fetch is disabled when: - // - The user passes --no-lazy-fetch - // - Any Hyper-V test is selected (Hyper-V requires local files) + // Lazy fetch is disabled for all downloads when the user passes + // --no-lazy-fetch and for any downloads that are used by a selected + // Hyper-V test. // // When both Hyper-V and non-Hyper-V tests are selected, only the // artifacts required by Hyper-V tests are downloaded upfront; the // rest are lazy-fetched. if no_lazy_fetch { - log::info!("Lazy fetch disabled by --no-lazy-fetch"); + log::info!("Lazy fetch disabled"); } else { - let hyperv_names: Vec<_> = test_names - .iter() - .filter(|name| name.contains("hyperv_")) - .cloned() - .collect(); + let mut hyperv_tests: usize = 0; + let mut hyperv_artifacts = Vec::new(); + for (_, suite) in suites.iter() { + let hyperv_testcases: Vec<_> = suite + .testcases + .iter() + .filter(|name| name.contains("hyperv_")) + .cloned() + .collect(); + + if !hyperv_testcases.is_empty() { + hyperv_tests += hyperv_testcases.len(); + hyperv_artifacts.append(&mut query_test_binary_artifacts(&RustSuite { + binary_path: suite.binary_path.clone(), + testcases: hyperv_testcases, + })?); + } + } + + resolved.downloads.clear(); - if hyperv_names.is_empty() { + if hyperv_tests == 0 { log::info!("Lazy fetch enabled: disk images will be streamed on demand via HTTP"); - resolved.downloads.clear(); } else { log::info!( "Downloading disk images required by {} Hyper-V tests", - hyperv_names.len() + hyperv_tests ); - let hyperv_json = - query_test_binary_artifacts(&test_binary, &hyperv_names, &target_str) - .context("during Hyper-V artifact discovery")?; - let hyperv_resolved = ResolvedArtifactSelections::from_artifact_list_json( - &hyperv_json, - target_architecture, - target_os, - ) - .context("failed to parse Hyper-V artifacts")?; - resolved.downloads = hyperv_resolved.downloads; + } + + // Re-add only the downloads needed for hyper-v. Other selections should + // remain the same since resolve_artifact can only add selections + for artifact in hyperv_artifacts { + resolved.resolve_artifact(&artifact)?; } } @@ -194,44 +262,102 @@ impl IntoPipeline for VmmTestsRunCli { resolved.downloads.iter().collect::>() ); - let selections = selections_from_resolved(filter, resolved, target_os); + let openvmm_repo = flowey_lib_common::git_checkout::RepoSource::ExistingClone( + ReadVar::from_static(repo_root), + ); - // 6. Construct and return the pipeline - log::info!("Step 2: Building and running tests..."); - build_vmm_tests_pipeline( - backend_hint, - target, - selections, - dir, - VmmTestsPipelineOptions { - verbose, - install_missing_deps, - unstable_whp, - release, - build_only, - copy_extras, - skip_vhd_prompt, - custom_kernel_modules, - custom_kernel, - custom_uefi_firmware, - }, - ) + let mut pipeline = Pipeline::new(); + + let mut job = pipeline.new_job( + FlowPlatform::host(backend_hint), + FlowArch::host(backend_hint), + "build all dependencies and run vmm tests", + ); + + job = job.dep_on(|_| flowey_lib_hvlite::_jobs::cfg_versions::Request::Init); + + // Override kernel with local paths if both kernel and modules are specified + if let (Some(kernel_path), Some(modules_path)) = + (custom_kernel.clone(), custom_kernel_modules.clone()) + { + job = + job.dep_on( + move |_| flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalKernel { + arch: target_arch, + kernel: ReadVar::from_static(kernel_path), + modules: ReadVar::from_static(modules_path), + }, + ); + } + + // Override UEFI firmware with a local MSVM.fd path + if let Some(fw_path) = custom_uefi_firmware { + job = job.dep_on(move |_| { + flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalUefi( + target_arch, + ReadVar::from_static(fw_path), + ) + }); + } + + job = job + .dep_on( + |_| flowey_lib_hvlite::_jobs::cfg_hvlite_reposource::Params { + hvlite_repo_source: openvmm_repo.clone(), + }, + ) + .dep_on(|_| flowey_lib_hvlite::_jobs::cfg_common::Params { + local_only: Some(flowey_lib_hvlite::_jobs::cfg_common::LocalOnlyParams { + interactive: true, + auto_install: install_missing_deps, + ignore_rust_version: true, + }), + verbose: ReadVar::from_static(verbose), + locked: false, + deny_warnings: false, + no_incremental: false, + }) + .dep_on(|ctx| { + flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::Params { + target, + test_content_dir, + selections: selections_from_resolved(filter, resolved, target_os), + unstable_whp, + release, + build_only, + copy_extras, + custom_kernel_modules, + custom_kernel, + skip_vhd_prompt, + nextest_profile: if ci_profile { + flowey_lib_hvlite::run_cargo_nextest_run::NextestProfile::Ci + } else { + flowey_lib_hvlite::run_cargo_nextest_run::NextestProfile::Default + }, + done: ctx.new_done_handle(), + } + }); + + job.finish(); + + Ok(pipeline) } } -/// Run artifact discovery by invoking `cargo nextest list` and the test -/// binary's `--list-required-artifacts` flag. -/// -/// Returns the raw JSON string describing required/optional artifacts, the -/// list of matching test names (used for backend detection), and the path -/// to the test binary (for follow-up queries). -fn discover_artifacts( - repo_root: &Path, - target: &str, - filter: &str, - release: bool, - ignore_host_requirements: bool, -) -> anyhow::Result<(String, Vec, PathBuf)> { +/// Get test binaries and associated matching tests for a given nextest filter. +// TODO: this function should really be a flowey node without automatic +// dependency installation, but that would require conditional requests. +fn run_cargo_nextest_list<'a>( + req: CargoNextestListRequest<'a>, +) -> anyhow::Result> { + let CargoNextestListRequest { + repo_root, + target, + filter, + release, + include_ignored, + } = req; + // Check that cargo-nextest is available let nextest_check = Command::new("cargo") .args(["nextest", "--version"]) @@ -240,15 +366,11 @@ fn discover_artifacts( .status(); match nextest_check { Ok(status) if status.success() => {} - _ => anyhow::bail!("cargo-nextest not found. Run 'cargo xflowey restore-packages' first."), + _ => anyhow::bail!( + "cargo-nextest not found. Run 'cargo install --locked cargo-nextest' first." + ), } - log::info!( - "Discovering artifacts for filter: {} (target: {})", - filter, - target - ); - // Step 1: Use nextest to resolve the filter expression to test names and // get the binary path let mut cmd = Command::new("cargo"); @@ -268,59 +390,81 @@ fn discover_artifacts( if release { cmd.arg("--release"); } - // When using build-only mode, we need to enumerate tests that could be - // run on any system so that we build all necessary dependencies. By default - // petri marks incompatible tests as ignored. - if ignore_host_requirements { + if include_ignored { cmd.args(["--run-ignored", "all"]); } let nextest_output = cmd.output().context("failed to run cargo nextest list")?; anyhow::ensure!(nextest_output.status.success(), "cargo nextest list failed",); let nextest_stdout = String::from_utf8(nextest_output.stdout) .map_err(|e| anyhow::anyhow!("nextest output is not valid UTF-8: {}", e))?; - let (test_binary, test_names) = parse_nextest_output(&nextest_stdout)?; - - if test_names.is_empty() { - log::warn!("No tests match the filter: {}", filter); - let empty_output = serde_json::json!({ - "target": target, - "required": [], - "optional": [] - }); - return Ok(( - serde_json::to_string_pretty(&empty_output)?, - Vec::new(), - test_binary, - )); - } - log::info!("Found {} matching tests", test_names.len()); - for name in &test_names { - log::debug!(" - {}", name); + parse_nextest_output(&nextest_stdout) +} + +/// Parse `cargo nextest list --message-format json` output to extract test +/// names and binary path. +fn parse_nextest_output(stdout: &str) -> anyhow::Result> { + let json: serde_json::Value = serde_json::from_str(stdout) + .map_err(|e| anyhow::anyhow!("failed to parse nextest JSON output: {}", e))?; + + let mut suites = BTreeMap::new(); + + for (name, suite) in json + .get("rust-suites") + .and_then(|s| s.as_object()) + .context("no rust-suites object")? + { + let binary_path = PathBuf::from( + suite + .get("binary-path") + .and_then(|v| v.as_str()) + .context("no binary-path str")?, + ); + + let testcases: Vec<_> = suite + .get("testcases") + .and_then(|t| t.as_object()) + .context("no testcases object")? + .iter() + .filter_map(|(test_name, test_info)| { + test_info + .get("filter-match") + .and_then(|fm| fm.get("status")) + .and_then(|s| s.as_str()) + .is_some_and(|s| s == "matches") + .then(|| test_name.to_owned()) + }) + .collect(); + + if !testcases.is_empty() { + suites.insert( + name.to_owned(), + RustSuite { + binary_path, + testcases, + }, + ); + } } - // Step 2: Query petri for artifacts of each matching test - let json = query_test_binary_artifacts(&test_binary, &test_names, target)?; - Ok((json, test_names, test_binary)) + Ok(suites) } -/// Query the test binary for required artifacts given a list of test names. +/// Query the test binary for required and optional artifacts given a list of +/// test names. /// /// This invokes the binary with `--list-required-artifacts --tests-from-stdin` /// and returns the resulting JSON as a string after processing it to inject /// the `target` field. -fn query_test_binary_artifacts( - test_binary: &Path, - test_names: &[String], - target: &str, -) -> anyhow::Result { - log::info!("Using test binary: {}", test_binary.display()); - log::info!("Querying artifacts for {} tests", test_names.len()); - let stdin_data = test_names +fn query_test_binary_artifacts(suite: &RustSuite) -> anyhow::Result> { + log::info!("Using test binary: {}", suite.binary_path.display()); + log::info!("Querying artifacts for {} tests", suite.testcases.len()); + let stdin_data = suite + .testcases .iter() .map(|n| format!("{n}\n")) .collect::(); - let mut child = Command::new(test_binary) + let mut child = Command::new(&suite.binary_path) .args(["--list-required-artifacts", "--tests-from-stdin"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -346,87 +490,18 @@ fn query_test_binary_artifacts( let artifact_stdout = String::from_utf8(artifact_output.stdout) .map_err(|e| anyhow::anyhow!("test output is not valid UTF-8: {}", e))?; - parse_artifacts_output(&artifact_stdout, target) -} - -/// Parse `cargo nextest list --message-format json` output to extract test -/// names and binary path. -fn parse_nextest_output(stdout: &str) -> anyhow::Result<(PathBuf, Vec)> { - let json: serde_json::Value = serde_json::from_str(stdout) - .map_err(|e| anyhow::anyhow!("failed to parse nextest JSON output: {}", e))?; - - let mut test_names = Vec::new(); - let mut binary_path = None; - - // Navigate to rust-suites -> vmm_tests::tests -> testcases - if let Some(vmm_tests) = json - .get("rust-suites") - .and_then(|s| s.get("vmm_tests::tests")) - { - if let Some(path) = vmm_tests.get("binary-path").and_then(|v| v.as_str()) { - binary_path = Some(PathBuf::from(path)); - } - - if let Some(testcases_obj) = vmm_tests.get("testcases").and_then(|t| t.as_object()) { - for (test_name, test_info) in testcases_obj { - let matches = test_info - .get("filter-match") - .and_then(|fm| fm.get("status")) - .and_then(|s| s.as_str()) - == Some("matches"); - - if matches { - test_names.push(test_name.clone()); - } - } - } - } - - let binary_path = binary_path - .ok_or_else(|| anyhow::anyhow!("Could not find test binary path in nextest output"))?; - - Ok((binary_path, test_names)) -} - -/// Parse test binary `--list-required-artifacts` JSON output and add target -/// info. -fn parse_artifacts_output(stdout: &str, target: &str) -> anyhow::Result { - let json: serde_json::Value = serde_json::from_str(stdout) + let ArtifactListOutput { + mut required, + mut optional, + } = serde_json::from_str(&artifact_stdout) .map_err(|e| anyhow::anyhow!("failed to parse test output JSON: {}", e))?; - let required = json - .get("required") - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|v| v.as_str()) - .map(String::from) - .collect::>() - }) - .unwrap_or_default(); - - let optional = json - .get("optional") - .and_then(|v| v.as_array()) - .map(|arr| { - arr.iter() - .filter_map(|v| v.as_str()) - .map(String::from) - .collect::>() - }) - .unwrap_or_default(); - - let output = serde_json::json!({ - "target": target, - "required": required, - "optional": optional, - }); - - Ok(serde_json::to_string_pretty(&output)?) + let mut artifacts = Vec::new(); + artifacts.append(&mut required); + artifacts.append(&mut optional); + Ok(artifacts) } -// Target resolution and pipeline construction helpers - #[derive(clap::ValueEnum, Copy, Clone)] enum VmmTestTargetCli { /// Windows Aarch64 @@ -470,19 +545,26 @@ fn resolve_target( /// requires VHDs to reside on a Windows filesystem. On native Windows or Linux /// this check is a no-op. fn validate_output_dir( - dir: &Path, + dir: Option<&Path>, target_os: target_lexicon::OperatingSystem, ) -> anyhow::Result<()> { - if flowey_cli::running_in_wsl() - && matches!(target_os, target_lexicon::OperatingSystem::Windows) - && !flowey_cli::is_wsl_windows_path(dir) + if flowey_cli::running_in_wsl() && matches!(target_os, target_lexicon::OperatingSystem::Windows) { - anyhow::bail!( - "When targeting Windows from WSL, --dir must be a path on Windows \ - (i.e., on a DrvFs mount like /mnt/c/vmm_tests). \ - Got: {}", - dir.display() - ); + if let Some(dir) = dir { + if !flowey_cli::is_wsl_windows_path(dir) { + anyhow::bail!( + "When targeting Windows from WSL, --dir must be a path on Windows \ + (i.e., on a DrvFs mount like /mnt/c/vmm_tests). \ + Got: {}", + dir.display() + ); + } + } else { + anyhow::bail!( + "An output directory on the Windows filesystem \ + must be specified when targeting Windows from WSL." + ) + } } Ok(()) } @@ -510,105 +592,190 @@ fn selections_from_resolved( } } -struct VmmTestsPipelineOptions { - verbose: bool, - install_missing_deps: bool, - unstable_whp: bool, - release: bool, - build_only: bool, - copy_extras: bool, - skip_vhd_prompt: bool, - custom_kernel_modules: Option, - custom_kernel: Option, - custom_uefi_firmware: Option, -} +impl ResolvedArtifactSelections { + /// Resolve a single artifact ID and update selections. Returns true if the + /// artifact was recognized. + fn resolve_artifact(&mut self, id: &str) -> anyhow::Result<()> { + match id { + // OpenVMM binary + petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::OPENVMM_MACOS_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.openvmm = true; + } -/// Construct the pipeline job for building and running VMM tests. -fn build_vmm_tests_pipeline( - backend_hint: PipelineBackendHint, - target: CommonTriple, - selections: VmmTestSelections, - dir: PathBuf, - opts: VmmTestsPipelineOptions, -) -> anyhow::Result { - let target_architecture = target.as_triple().architecture; - let recipe_arch = match target_architecture { - target_lexicon::Architecture::X86_64 => CommonArch::X86_64, - target_lexicon::Architecture::Aarch64(_) => CommonArch::Aarch64, - _ => anyhow::bail!("Unsupported architecture: {:?}", target_architecture), - }; + // OpenVMM vhost binary (Linux only) + petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_AARCH64 ::GLOBAL_UNIQUE_ID => { + self.build.openvmm_vhost = true; + } - let openvmm_repo = flowey_lib_common::git_checkout::RepoSource::ExistingClone( - ReadVar::from_static(crate::repo_root()), - ); + // OpenHCL IGVM files + petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_CVM_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_AARCH64::GLOBAL_UNIQUE_ID => + { + self.build.openhcl = true; + } - let mut pipeline = Pipeline::new(); + // Release IGVM files (downloaded, not built) + petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_LINUX_DIRECT_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_AARCH64::GLOBAL_UNIQUE_ID => + { + // These are downloaded from GitHub releases, not built + self.needs_release_igvm = true; + } - let mut job = pipeline.new_job( - FlowPlatform::host(backend_hint), - FlowArch::host(backend_hint), - "build vmm test dependencies", - ); + // Guest test UEFI + petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_AARCH64 ::GLOBAL_UNIQUE_ID => { + self.build.guest_test_uefi = true; + } - job = job.dep_on(|_| flowey_lib_hvlite::_jobs::cfg_versions::Request::Init); - - if let (Some(kernel_path), Some(modules_path)) = ( - opts.custom_kernel.clone(), - opts.custom_kernel_modules.clone(), - ) { - job = job.dep_on( - move |_| flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalKernel { - arch: recipe_arch, - kernel: ReadVar::from_static(kernel_path), - modules: ReadVar::from_static(modules_path), - }, - ); - } + // TMKs + petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_AARCH64 ::GLOBAL_UNIQUE_ID => { + self.build.tmks = true; + } - // Override UEFI firmware with a local MSVM.fd path - if let Some(fw_path) = opts.custom_uefi_firmware { - job = job.dep_on(move |_| { - flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalUefi( - recipe_arch, - ReadVar::from_static(fw_path), - ) - }); - } + // TMK VMM + petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.tmk_vmm_windows = true; + } + petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64_MUSL::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64_MUSL::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_MACOS_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.tmk_vmm_linux = true; + } - job = job - .dep_on( - |_| flowey_lib_hvlite::_jobs::cfg_hvlite_reposource::Params { - hvlite_repo_source: openvmm_repo.clone(), - }, - ) - .dep_on(|_| flowey_lib_hvlite::_jobs::cfg_common::Params { - local_only: Some(flowey_lib_hvlite::_jobs::cfg_common::LocalOnlyParams { - interactive: true, - auto_install: opts.install_missing_deps, - ignore_rust_version: true, - }), - verbose: ReadVar::from_static(opts.verbose), - locked: false, - deny_warnings: false, - no_incremental: false, - }) - .side_effect(|done| { - flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::Params { - target, - test_content_dir: dir, - selections, - unstable_whp: opts.unstable_whp, - release: opts.release, - build_only: opts.build_only, - copy_extras: opts.copy_extras, - custom_kernel_modules: opts.custom_kernel_modules, - custom_kernel: opts.custom_kernel, - skip_vhd_prompt: opts.skip_vhd_prompt, - done, - } - }); - - job.finish(); - - Ok(pipeline) + // VmgsTool + petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.vmgstool = true; + } + petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::VMGSTOOL_MACOS_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.vmgstool = true; + } + + // TPM guest tests + petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_WINDOWS_X64::GLOBAL_UNIQUE_ID => { + self.build.tpm_guest_tests_windows = true; + } + petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_LINUX_X64::GLOBAL_UNIQUE_ID => { + self.build.tpm_guest_tests_linux = true; + } + + // Host tools + petri_artifacts_vmm_test::artifacts::host_tools::TEST_IGVM_AGENT_RPC_SERVER_WINDOWS_X64::GLOBAL_UNIQUE_ID => + { + self.build.test_igvm_agent_rpc_server = true; + } + + // Loadable firmware artifacts (these come from deps, not built) + petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_AARCH64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::PCAT_FIRMWARE_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::SVGA_FIRMWARE_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_AARCH64::GLOBAL_UNIQUE_ID => { + // These are resolved from OpenVMM deps, always available + } + + // Test VHDs + petri_artifacts_vmm_test::artifacts::test_vhd::GEN1_WINDOWS_DATA_CENTER_CORE2022_X64::GLOBAL_UNIQUE_ID => + { + self.downloads + .insert(KnownTestArtifacts::Gen1WindowsDataCenterCore2022X64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64::GLOBAL_UNIQUE_ID => + { + self.downloads + .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2022X64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64::GLOBAL_UNIQUE_ID => + { + self.downloads + .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2025X64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED::GLOBAL_UNIQUE_ID => + { + self.build.prep_steps = true; + } + petri_artifacts_vmm_test::artifacts::test_vhd::FREE_BSD_13_2_X64::GLOBAL_UNIQUE_ID => { + self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_X64::GLOBAL_UNIQUE_ID => { + self.downloads.insert(KnownTestArtifacts::Alpine323X64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_AARCH64::GLOBAL_UNIQUE_ID => { + self.downloads + .insert(KnownTestArtifacts::Alpine323Aarch64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_X64::GLOBAL_UNIQUE_ID => { + self.downloads + .insert(KnownTestArtifacts::Ubuntu2404ServerX64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2504_SERVER_X64::GLOBAL_UNIQUE_ID => { + self.downloads + .insert(KnownTestArtifacts::Ubuntu2504ServerX64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_AARCH64::GLOBAL_UNIQUE_ID => { + self.downloads + .insert(KnownTestArtifacts::Ubuntu2404ServerAarch64Vhd); + } + petri_artifacts_vmm_test::artifacts::test_vhd::WINDOWS_11_ENTERPRISE_AARCH64::GLOBAL_UNIQUE_ID => { + self.downloads + .insert(KnownTestArtifacts::Windows11EnterpriseAarch64Vhdx); + } + + // Test ISOs (downloaded) + petri_artifacts_vmm_test::artifacts::test_iso::FREE_BSD_13_2_X64::GLOBAL_UNIQUE_ID => { + self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Iso); + } + + // Test VMGS files + petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_BOOT_ENTRY::GLOBAL_UNIQUE_ID => { + self.downloads.insert(KnownTestArtifacts::VmgsWithBootEntry); + } + petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_16K_TPM::GLOBAL_UNIQUE_ID => { + self.downloads.insert(KnownTestArtifacts::VmgsWith16kTpm); + } + + // OpenHCL usermode binaries (built as part of IGVM) + petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_bin::LATEST_LINUX_DIRECT_TEST_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_dbg::LATEST_LINUX_DIRECT_TEST_X64::GLOBAL_UNIQUE_ID => + { + self.build.openhcl = true; + } + + // Common artifacts (always available, no build needed) + petri_artifacts_common::artifacts::TEST_LOG_DIRECTORY::GLOBAL_UNIQUE_ID => {} + + // Pipette binaries (from petri_artifacts_common) + petri_artifacts_common::artifacts::PIPETTE_LINUX_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_common::artifacts::PIPETTE_LINUX_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.pipette_linux = true; + } + petri_artifacts_common::artifacts::PIPETTE_WINDOWS_X64::GLOBAL_UNIQUE_ID + | petri_artifacts_common::artifacts::PIPETTE_WINDOWS_AARCH64::GLOBAL_UNIQUE_ID => { + self.build.pipette_windows = true; + } + + _ => anyhow::bail!("unknown artifact type"), + }; + Ok(()) + } } diff --git a/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs b/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs index e00f7e93c3..cced6557b7 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs @@ -53,30 +53,7 @@ pub struct BuildSelections { pub test_igvm_agent_rpc_server: bool, } -// Build everything we can by default -impl Default for BuildSelections { - fn default() -> Self { - Self { - prep_steps: true, - openhcl: true, - openvmm: true, - openvmm_vhost: true, - pipette_windows: true, - pipette_linux: true, - guest_test_uefi: true, - tmks: true, - tmk_vmm_windows: true, - tmk_vmm_linux: true, - vmgstool: true, - tpm_guest_tests_windows: true, - tpm_guest_tests_linux: true, - test_igvm_agent_rpc_server: true, - } - } -} - impl BuildSelections { - /// No selections (build nothing) pub fn none() -> Self { Self { prep_steps: false, @@ -123,6 +100,8 @@ flowey_request! { /// Skip the interactive VHD download prompt pub skip_vhd_prompt: bool, + pub nextest_profile: crate::run_cargo_nextest_run::NextestProfile, + pub done: WriteVar, } } @@ -171,6 +150,7 @@ impl SimpleFlowNode for Node { custom_kernel_modules, custom_kernel, skip_vhd_prompt, + nextest_profile, done, } = request; @@ -782,8 +762,6 @@ impl SimpleFlowNode for Node { } })); - let nextest_profile = crate::run_cargo_nextest_run::NextestProfile::Default; - let nextest_run_cmd = ctx.reqv(|v| flowey_lib_common::gen_cargo_nextest_run_cmd::Request { run_kind_deps: RunKindDeps::RunFromArchive { archive_file: ReadVar::from_static(nextest_archive_file.clone()), diff --git a/flowey/flowey_lib_hvlite/src/artifact_to_build_mapping.rs b/flowey/flowey_lib_hvlite/src/artifact_to_build_mapping.rs deleted file mode 100644 index 1ea2e7c715..0000000000 --- a/flowey/flowey_lib_hvlite/src/artifact_to_build_mapping.rs +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Mapping from petri artifact IDs to flowey build/download selections. -//! -//! This module defines a lookup table that maps the string representation of -//! petri `ArtifactHandle` IDs (as output by `--list-required-artifacts`) to -//! their corresponding build selections and download artifacts. -//! -//! FUTURE: This is currently a manual lookup table that requires fixups each -//! time a new artifact is added, but this requires a rearchitecture of petri's -//! artifact system and type erasure. Live with this for now since this is only -//! used in the local vmm-test-run workflow, and is straightforward to fix. - -use crate::_jobs::local_build_and_run_nextest_vmm_tests::BuildSelections; -use std::collections::BTreeSet; -use vmm_test_images::KnownTestArtifacts; - -/// Result of resolving artifact requirements to build/download selections. -#[derive(Debug)] -pub struct ResolvedArtifactSelections { - /// What to build - pub build: BuildSelections, - /// What to download - pub downloads: BTreeSet, - /// Any unknown artifacts that couldn't be mapped - pub unknown: Vec, - /// Target triple from the artifacts file (if present) - pub target_from_file: Option, - /// Whether any tests need release IGVM files from GitHub - pub needs_release_igvm: bool, -} - -impl Default for ResolvedArtifactSelections { - fn default() -> Self { - Self { - build: BuildSelections::none(), - downloads: BTreeSet::new(), - unknown: Vec::new(), - target_from_file: None, - needs_release_igvm: false, - } - } -} - -impl ResolvedArtifactSelections { - /// Parse the JSON output from `--list-required-artifacts` and resolve to - /// build/download selections. - /// - /// The `target_arch` and `target_os` parameters specify the target to - /// validate against. If the JSON contains a `target` field, it will be - /// checked to ensure it matches. - pub fn from_artifact_list_json( - json: &str, - target_arch: target_lexicon::Architecture, - target_os: target_lexicon::OperatingSystem, - ) -> anyhow::Result { - let parsed: ArtifactListOutput = serde_json::from_str(json)?; - - // Validate target if present in the JSON - if let Some(ref file_target) = parsed.target { - let expected_target = format!( - "{}-{}", - match target_arch { - target_lexicon::Architecture::X86_64 => "x86_64", - target_lexicon::Architecture::Aarch64(_) => "aarch64", - _ => "unknown", - }, - match target_os { - target_lexicon::OperatingSystem::Windows => "pc-windows-msvc", - target_lexicon::OperatingSystem::Linux => "unknown-linux-gnu", - _ => "unknown", - } - ); - - // Check if the target in the file is compatible with what we're building for - if !file_target.contains(expected_target.split('-').next().unwrap_or("")) - || (target_os == target_lexicon::OperatingSystem::Windows - && !file_target.contains("windows")) - || (target_os == target_lexicon::OperatingSystem::Linux - && !file_target.contains("linux")) - { - anyhow::bail!( - "Target mismatch: artifacts file was generated for '{}', but building for '{}'", - file_target, - expected_target - ); - } - } - - let mut result = Self { - target_from_file: parsed.target, - ..Default::default() - }; - - // Process both required and optional artifacts - for artifact in parsed.required.iter().chain(parsed.optional.iter()) { - if !result.resolve_artifact(artifact, target_arch, target_os) { - result.unknown.push(artifact.clone()); - } - } - - Ok(result) - } - - /// Resolve a single artifact ID and update selections. Returns true if the - /// artifact was recognized. - fn resolve_artifact( - &mut self, - artifact_id: &str, - target_arch: target_lexicon::Architecture, - target_os: target_lexicon::OperatingSystem, - ) -> bool { - // Artifact IDs are in the format: - // "petri_artifacts_vmm_test::artifacts::ARTIFACT_NAME" - // or nested like: - // "petri_artifacts_vmm_test::artifacts::test_vhd::ARTIFACT_NAME" - - let is_windows = matches!(target_os, target_lexicon::OperatingSystem::Windows); - let is_x64 = matches!(target_arch, target_lexicon::Architecture::X86_64); - - match artifact_id { - // OpenVMM binary - "petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_X64" - | "petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_X64" - | "petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_AARCH64" - | "petri_artifacts_vmm_test::artifacts::OPENVMM_LINUX_AARCH64" - | "petri_artifacts_vmm_test::artifacts::OPENVMM_MACOS_AARCH64" => { - self.build.openvmm = true; - true - } - - // OpenVMM vhost binary (Linux only) - "petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_X64" - | "petri_artifacts_vmm_test::artifacts::OPENVMM_VHOST_LINUX_AARCH64" => { - self.build.openvmm_vhost = true; - true - } - - // OpenHCL IGVM files - "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_CVM_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_LINUX_DIRECT_TEST_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_AARCH64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_STANDARD_DEV_KERNEL_AARCH64" => - { - self.build.openhcl = true; - true - } - - // Release IGVM files (downloaded, not built) - "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_LINUX_DIRECT_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::LATEST_RELEASE_STANDARD_AARCH64" => - { - // These are downloaded from GitHub releases, not built - self.needs_release_igvm = true; - true - } - - // Guest test UEFI - "petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_X64" - | "petri_artifacts_vmm_test::artifacts::test_vhd::GUEST_TEST_UEFI_AARCH64" => { - self.build.guest_test_uefi = true; - true - } - - // TMKs - "petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_X64" - | "petri_artifacts_vmm_test::artifacts::tmks::SIMPLE_TMK_AARCH64" => { - self.build.tmks = true; - true - } - - // TMK VMM - "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_X64" - | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_WIN_AARCH64" => { - self.build.tmk_vmm_windows = true; - true - } - "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64" - | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64" - | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_X64_MUSL" - | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_LINUX_AARCH64_MUSL" - | "petri_artifacts_vmm_test::artifacts::tmks::TMK_VMM_MACOS_AARCH64" => { - self.build.tmk_vmm_linux = true; - true - } - - // VMGSTool - "petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_X64" - | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_WIN_AARCH64" => { - self.build.vmgstool = true; - true - } - "petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_X64" - | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_LINUX_AARCH64" - | "petri_artifacts_vmm_test::artifacts::VMGSTOOL_MACOS_AARCH64" => { - self.build.vmgstool = true; - true - } - - // TPM guest tests - "petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_WINDOWS_X64" => { - self.build.tpm_guest_tests_windows = true; - true - } - "petri_artifacts_vmm_test::artifacts::guest_tools::TPM_GUEST_TESTS_LINUX_X64" => { - self.build.tpm_guest_tests_linux = true; - true - } - - // Host tools - "petri_artifacts_vmm_test::artifacts::host_tools::TEST_IGVM_AGENT_RPC_SERVER_WINDOWS_X64" => - { - self.build.test_igvm_agent_rpc_server = true; - true - } - - // Loadable firmware artifacts (these come from deps, not built) - "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_X64" - | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_X64" - | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_KERNEL_AARCH64" - | "petri_artifacts_vmm_test::artifacts::loadable::LINUX_DIRECT_TEST_INITRD_AARCH64" - | "petri_artifacts_vmm_test::artifacts::loadable::PCAT_FIRMWARE_X64" - | "petri_artifacts_vmm_test::artifacts::loadable::SVGA_FIRMWARE_X64" - | "petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_X64" - | "petri_artifacts_vmm_test::artifacts::loadable::UEFI_FIRMWARE_AARCH64" => { - // These are resolved from OpenVMM deps, always available - true - } - - // Test VHDs (downloaded) - "petri_artifacts_vmm_test::artifacts::test_vhd::GEN1_WINDOWS_DATA_CENTER_CORE2022_X64" => - { - self.downloads - .insert(KnownTestArtifacts::Gen1WindowsDataCenterCore2022X64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2022_X64" => - { - self.downloads - .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2022X64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64" => - { - self.downloads - .insert(KnownTestArtifacts::Gen2WindowsDataCenterCore2025X64Vhd); - // Requires prep_steps for CVM tests - self.build.prep_steps = is_windows && is_x64; - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED" => - { - // This is created by prep_steps, not downloaded - self.build.prep_steps = is_windows && is_x64; - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::FREE_BSD_13_2_X64" => { - self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_X64" => { - self.downloads.insert(KnownTestArtifacts::Alpine323X64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::ALPINE_3_23_AARCH64" => { - self.downloads - .insert(KnownTestArtifacts::Alpine323Aarch64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_X64" => { - self.downloads - .insert(KnownTestArtifacts::Ubuntu2404ServerX64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2504_SERVER_X64" => { - self.downloads - .insert(KnownTestArtifacts::Ubuntu2504ServerX64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_AARCH64" => { - self.downloads - .insert(KnownTestArtifacts::Ubuntu2404ServerAarch64Vhd); - true - } - "petri_artifacts_vmm_test::artifacts::test_vhd::WINDOWS_11_ENTERPRISE_AARCH64" => { - self.downloads - .insert(KnownTestArtifacts::Windows11EnterpriseAarch64Vhdx); - true - } - - // Test ISOs (downloaded) - "petri_artifacts_vmm_test::artifacts::test_iso::FREE_BSD_13_2_X64" => { - self.downloads.insert(KnownTestArtifacts::FreeBsd13_2X64Iso); - true - } - - // Test VMGS files (downloaded) - "petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_BOOT_ENTRY" => { - self.downloads.insert(KnownTestArtifacts::VmgsWithBootEntry); - true - } - - // OpenHCL usermode binaries (built as part of IGVM) - "petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_bin::LATEST_LINUX_DIRECT_TEST_X64" - | "petri_artifacts_vmm_test::artifacts::openhcl_igvm::um_dbg::LATEST_LINUX_DIRECT_TEST_X64" => - { - self.build.openhcl = true; - true - } - - // Common artifacts (always available, no build needed) - "petri_artifacts_common::artifacts::TEST_LOG_DIRECTORY" => true, - - // Pipette binaries (from petri_artifacts_common) - "petri_artifacts_common::artifacts::PIPETTE_LINUX_X64" - | "petri_artifacts_common::artifacts::PIPETTE_LINUX_AARCH64" => { - self.build.pipette_linux = true; - true - } - "petri_artifacts_common::artifacts::PIPETTE_WINDOWS_X64" - | "petri_artifacts_common::artifacts::PIPETTE_WINDOWS_AARCH64" => { - self.build.pipette_windows = true; - true - } - - "petri_artifacts_vmm_test::artifacts::test_vmgs::VMGS_WITH_16K_TPM" => { - self.downloads.insert(KnownTestArtifacts::VmgsWith16kTpm); - true - } - - _ => { - log::warn!("unknown artifact ID with no build mapping: {artifact_id}"); - false - } - } - } -} - -/// JSON structure matching the output of `--list-required-artifacts` -#[derive(serde::Deserialize)] -struct ArtifactListOutput { - /// Target triple the artifacts were discovered for (if present) - #[serde(default)] - target: Option, - required: Vec, - optional: Vec, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_resolve_openvmm() { - let json = r#"{"required":["petri_artifacts_vmm_test::artifacts::OPENVMM_WIN_X64"],"optional":[]}"#; - let result = ResolvedArtifactSelections::from_artifact_list_json( - json, - target_lexicon::Architecture::X86_64, - target_lexicon::OperatingSystem::Windows, - ) - .unwrap(); - - assert!(result.build.openvmm); - assert!(!result.build.openhcl); - assert!(result.downloads.is_empty()); - assert!(result.unknown.is_empty()); - } - - #[test] - fn test_resolve_with_downloads() { - let json = r#"{"required":["petri_artifacts_vmm_test::artifacts::test_vhd::UBUNTU_2404_SERVER_X64","petri_artifacts_common::artifacts::PIPETTE_LINUX_X64"],"optional":[]}"#; - let result = ResolvedArtifactSelections::from_artifact_list_json( - json, - target_lexicon::Architecture::X86_64, - target_lexicon::OperatingSystem::Linux, - ) - .unwrap(); - - assert!(result.build.pipette_linux); - assert!( - result - .downloads - .contains(&KnownTestArtifacts::Ubuntu2404ServerX64Vhd) - ); - } - - #[test] - fn test_unknown_artifact() { - let json = r#"{"required":["some::unknown::artifact"],"optional":[]}"#; - let result = ResolvedArtifactSelections::from_artifact_list_json( - json, - target_lexicon::Architecture::X86_64, - target_lexicon::OperatingSystem::Linux, - ) - .unwrap(); - - assert_eq!(result.unknown, vec!["some::unknown::artifact"]); - } -} diff --git a/flowey/flowey_lib_hvlite/src/download_openvmm_vmm_tests_artifacts.rs b/flowey/flowey_lib_hvlite/src/download_openvmm_vmm_tests_artifacts.rs index 2572cdea71..e1afb27bdf 100644 --- a/flowey/flowey_lib_hvlite/src/download_openvmm_vmm_tests_artifacts.rs +++ b/flowey/flowey_lib_hvlite/src/download_openvmm_vmm_tests_artifacts.rs @@ -8,10 +8,9 @@ use flowey::node::prelude::*; use std::collections::BTreeSet; use std::io::IsTerminal; +use vmm_test_images::CONTAINER; use vmm_test_images::KnownTestArtifacts; - -const STORAGE_ACCOUNT: &str = "hvlitetestvhds"; -const CONTAINER: &str = "vhds"; +use vmm_test_images::STORAGE_ACCOUNT; #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum CustomDiskPolicy { diff --git a/flowey/flowey_lib_hvlite/src/lib.rs b/flowey/flowey_lib_hvlite/src/lib.rs index 7ff2355423..3976df85d4 100644 --- a/flowey/flowey_lib_hvlite/src/lib.rs +++ b/flowey/flowey_lib_hvlite/src/lib.rs @@ -10,7 +10,6 @@ pub mod _jobs; pub mod artifact_openhcl_igvm_from_recipe; pub mod artifact_openhcl_igvm_from_recipe_extras; pub mod artifact_openvmm_hcl_sizecheck; -pub mod artifact_to_build_mapping; pub mod build_and_test_vmgs_lib; pub mod build_guest_test_uefi; pub mod build_guide; diff --git a/petri/petri_artifacts_core/Cargo.toml b/petri/petri_artifacts_core/Cargo.toml index dc7b21db14..0d66542135 100644 --- a/petri/petri_artifacts_core/Cargo.toml +++ b/petri/petri_artifacts_core/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] anyhow.workspace = true paste.workspace = true +serde = { workspace = true, features = ["std", "derive"] } [lints] workspace = true diff --git a/petri/petri_artifacts_core/src/lib.rs b/petri/petri_artifacts_core/src/lib.rs index 2ea355d2db..3d41ca5c93 100644 --- a/petri/petri_artifacts_core/src/lib.rs +++ b/petri/petri_artifacts_core/src/lib.rs @@ -300,6 +300,13 @@ pub struct ErasedArtifactHandle { artifact_id_str: &'static str, } +// used to serialize the artifact handle for vmm-tests-run +impl ToString for ErasedArtifactHandle { + fn to_string(&self) -> String { + self.artifact_id_str.to_string() + } +} + impl std::fmt::Debug for ErasedArtifactHandle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // the `declare_artifacts!` macro uses `module_path!` under-the-hood to @@ -594,3 +601,12 @@ impl TestArtifacts { .unwrap_or_else(|| panic!("Artifact not initially required: {:?}", artifact.erase())) } } + +/// JSON output format for `--list-required-artifacts`. +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ArtifactListOutput { + /// List of unique required artifact IDs across all matching tests. + pub required: Vec, + /// List of unique optional artifact IDs across all matching tests. + pub optional: Vec, +} diff --git a/petri/src/test.rs b/petri/src/test.rs index 6d705df123..a83cd8f8f1 100644 --- a/petri/src/test.rs +++ b/petri/src/test.rs @@ -380,15 +380,6 @@ struct Options { inner: libtest_mimic::Arguments, } -/// JSON output format for `--list-required-artifacts`. -#[derive(serde::Serialize)] -struct ArtifactListOutput { - /// List of unique required artifact IDs across all matching tests. - required: Vec, - /// List of unique optional artifact IDs across all matching tests. - optional: Vec, -} - /// Entry point for test binaries. pub fn test_main( resolve: fn(&str, TestArtifactRequirements) -> anyhow::Result, @@ -430,19 +421,18 @@ pub fn test_main( if matches { for artifact in test.artifact_requirements.required_artifacts() { - required_set.insert(format!("{artifact:?}")); + required_set.insert(artifact.to_string()); } for artifact in test.artifact_requirements.optional_artifacts() { - optional_set.insert(format!("{artifact:?}")); + optional_set.insert(artifact.to_string()); } } } // Remove from optional any artifacts that are required - let optional_set: BTreeSet = - optional_set.difference(&required_set).cloned().collect(); + let optional_set: BTreeSet<_> = optional_set.difference(&required_set).cloned().collect(); - let output = ArtifactListOutput { + let output = petri_artifacts_core::ArtifactListOutput { required: required_set.into_iter().collect(), optional: optional_set.into_iter().collect(), }; diff --git a/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs b/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs index 1ff30ad5f3..4047de887d 100644 --- a/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs +++ b/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs @@ -12,6 +12,9 @@ use petri_artifacts_core::ErasedArtifactHandle; use std::env::consts::EXE_EXTENSION; use std::path::Path; use std::path::PathBuf; +use vmm_test_images::CONTAINER; +use vmm_test_images::KnownTestArtifacts; +use vmm_test_images::STORAGE_ACCOUNT; /// Returns the Cargo build profile directory name for cross-compiled /// artifacts (e.g., pipette). @@ -91,6 +94,7 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact _ if id == test_vhd::GUEST_TEST_UEFI_X64 => guest_test_uefi_disk_path(MachineArch::X86_64), _ if id == test_vhd::GUEST_TEST_UEFI_AARCH64 => guest_test_uefi_disk_path(MachineArch::Aarch64), + _ if id == test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64_PREPPED => { let base_filename = test_vhd::GEN2_WINDOWS_DATA_CENTER_CORE2025_X64::FILENAME; let prepped_filename = base_filename.replace(".vhd", "-prepped.vhd"); @@ -106,13 +110,6 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact ) } - // Blob-hosted artifacts: resolved via blob_artifact_info. - _ => { - if let Some(artifact) = blob_artifact_info(id) { - return get_test_artifact_path(artifact.filename(), artifact.name()); - } - - match id { _ if id == tmks::TMK_VMM_NATIVE => tmk_vmm_native_executable_path(), _ if id == tmks::TMK_VMM_LINUX_X64_MUSL => tmk_vmm_paravisor_path(MachineArch::X86_64), _ if id == tmks::TMK_VMM_LINUX_AARCH64_MUSL => tmk_vmm_paravisor_path(MachineArch::Aarch64), @@ -132,9 +129,12 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact test_igvm_agent_rpc_server_windows_path(MachineArch::X86_64) } - _ => anyhow::bail!("no support for given artifact type"), - } + // Blob-hosted artifacts: resolved via blob_artifact_info. + _ if let Some(artifact) = KnownTestArtifacts::from_handle(id) => { + get_test_artifact_path(artifact) } + + _ => anyhow::bail!("no support for given artifact type"), } } @@ -147,7 +147,7 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact // Fall back to remote URL for artifacts hosted on Azure Blob Storage, // but only for formats the blob disk backend supports (fixed VHD1 and flat). - if let Some(artifact) = blob_artifact_info(id) { + if let Some(artifact) = KnownTestArtifacts::from_handle(id) { let filename = artifact.filename(); if filename.ends_with(".vhd") || filename.ends_with(".iso") { let url = format!( @@ -163,15 +163,6 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact } } -const STORAGE_ACCOUNT: &str = "hvlitetestvhds"; -const CONTAINER: &str = "vhds"; - -/// Returns blob-hosted artifact info (filename, download name) for the given -/// artifact handle, if it is a known blob-hosted artifact. -fn blob_artifact_info(id: ErasedArtifactHandle) -> Option { - vmm_test_images::KnownTestArtifacts::from_handle(id) -} - /// Returns the bundle-relative file name for the given artifact. /// /// This is the `file_name` argument that [`get_path`] would use when @@ -258,15 +249,20 @@ fn windows_msvc_target(arch: MachineArch) -> &'static str { } } -fn get_test_artifact_path(filename: &str, download_name: &str) -> Result { +fn get_test_artifact_path(artifact: KnownTestArtifacts) -> Result { let images_dir = std::env::var("VMM_TEST_IMAGES"); let full_path = Path::new(images_dir.as_deref().unwrap_or("images")); get_path( full_path, - filename, + artifact.filename(), MissingCommand::Xtask { - xtask_args: &["guest-test", "download-image", "--artifacts", download_name], + xtask_args: &[ + "guest-test", + "download-image", + "--artifacts", + &artifact.name(), + ], description: "test artifact", }, ) diff --git a/vmm_tests/petri_artifacts_vmm_test/src/lib.rs b/vmm_tests/petri_artifacts_vmm_test/src/lib.rs index 555d859c4e..577603589e 100644 --- a/vmm_tests/petri_artifacts_vmm_test/src/lib.rs +++ b/vmm_tests/petri_artifacts_vmm_test/src/lib.rs @@ -273,6 +273,11 @@ pub mod artifacts { } } + /// Azure storage account where test VHDs, ISOs, and VMGS files are stored + pub const STORAGE_ACCOUNT: &str = "hvlitetestvhds"; + /// Azure container where test VHDs, ISOs, and VMGS files are stored + pub const CONTAINER: &str = "vhds"; + /// Test VHD artifacts pub mod test_vhd { use crate::tags::IsHostedOnHvliteAzureBlobStore; diff --git a/vmm_tests/vmm_test_images/src/lib.rs b/vmm_tests/vmm_test_images/src/lib.rs index 0e31bb9759..19b1aa9144 100644 --- a/vmm_tests/vmm_test_images/src/lib.rs +++ b/vmm_tests/vmm_test_images/src/lib.rs @@ -19,6 +19,9 @@ use petri_artifacts_core::AsArtifactHandle; use petri_artifacts_core::ErasedArtifactHandle; use petri_artifacts_vmm_test::tags::IsHostedOnHvliteAzureBlobStore; +pub use petri_artifacts_vmm_test::artifacts::CONTAINER; +pub use petri_artifacts_vmm_test::artifacts::STORAGE_ACCOUNT; + /// The VHDs currently stored in Azure Blob Storage. #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[cfg_attr(feature = "clap", clap(rename_all = "verbatim"))] diff --git a/xtask/src/tasks/guest_test/download_image.rs b/xtask/src/tasks/guest_test/download_image.rs index 2b7e94e0e6..c5da006acc 100644 --- a/xtask/src/tasks/guest_test/download_image.rs +++ b/xtask/src/tasks/guest_test/download_image.rs @@ -7,7 +7,9 @@ use clap::Parser; use clap::ValueEnum; use std::path::PathBuf; use std::process::Command; +use vmm_test_images::CONTAINER; use vmm_test_images::KnownTestArtifacts; +use vmm_test_images::STORAGE_ACCOUNT; /// Download an image from Azure Blob Storage. /// @@ -25,9 +27,6 @@ pub struct DownloadImageTask { force: bool, } -const STORAGE_ACCOUNT: &str = "hvlitetestvhds"; -const CONTAINER: &str = "vhds"; - impl Xtask for DownloadImageTask { fn run(mut self, _ctx: crate::XtaskCtx) -> anyhow::Result<()> { if self.artifacts.is_empty() { From df341dc1f82c792ca2dcde79891cdf6c476babeb Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Sun, 3 May 2026 19:13:16 -0700 Subject: [PATCH 2/7] skip prep steps by default --- flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs | 7 +++++++ .../consume_and_test_nextest_vmm_tests_archive.rs | 1 + .../src/_jobs/local_build_and_run_nextest_vmm_tests.rs | 4 ++++ flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs | 7 +++++++ vmm_tests/prep_steps/src/main.rs | 10 +++++++++- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs index df927b372e..e4dc30499d 100644 --- a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs +++ b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs @@ -96,6 +96,11 @@ pub struct VmmTestsRunCli { /// use the nextest CI profile rather than the default one #[clap(long)] ci_profile: bool, + + /// Don't reuse prepped vhds, even if they already exist. + /// Use when making changes to prep_steps + #[clap(long)] + no_reuse_prepped_vhds: bool, } struct CargoNextestListRequest<'a> { @@ -153,6 +158,7 @@ impl IntoPipeline for VmmTestsRunCli { custom_kernel, custom_uefi_firmware, ci_profile, + no_reuse_prepped_vhds, } = self; let target = resolve_target(target, backend_hint)?; @@ -334,6 +340,7 @@ impl IntoPipeline for VmmTestsRunCli { } else { flowey_lib_hvlite::run_cargo_nextest_run::NextestProfile::Default }, + reuse_prepped_vhds: !no_reuse_prepped_vhds, done: ctx.new_done_handle(), } }); diff --git a/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_vmm_tests_archive.rs b/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_vmm_tests_archive.rs index 6cf9e63697..134495353a 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_vmm_tests_archive.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_vmm_tests_archive.rs @@ -192,6 +192,7 @@ impl SimpleFlowNode for Node { release_igvm_files, use_relative_paths: false, disable_remote_artifacts: true, + reuse_prepped_vhds: false, }); // Start the test_igvm_agent_rpc_server before running tests (Windows only). diff --git a/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs b/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs index cced6557b7..49dff96e97 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs @@ -102,6 +102,8 @@ flowey_request! { pub nextest_profile: crate::run_cargo_nextest_run::NextestProfile, + pub reuse_prepped_vhds: bool, + pub done: WriteVar, } } @@ -151,6 +153,7 @@ impl SimpleFlowNode for Node { custom_kernel, skip_vhd_prompt, nextest_profile, + reuse_prepped_vhds, done, } = request; @@ -706,6 +709,7 @@ impl SimpleFlowNode for Node { release_igvm_files, use_relative_paths: build_only, disable_remote_artifacts: false, + reuse_prepped_vhds, }); let mut side_effects = Vec::new(); diff --git a/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs b/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs index 88073c6e16..aa4b8741f4 100644 --- a/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs +++ b/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs @@ -70,6 +70,8 @@ flowey_request! { /// Disable lazy remote artifact fetching (set PETRI_REMOTE_ARTIFACTS=0). /// Should be true in CI where all images are pre-downloaded. pub disable_remote_artifacts: bool, + /// Whether to reuse VHDs created with prep_steps + pub reuse_prepped_vhds: bool, } } @@ -107,6 +109,7 @@ impl SimpleFlowNode for Node { release_igvm_files, use_relative_paths, disable_remote_artifacts, + reuse_prepped_vhds, } = request; let arch = CommonArch::from_architecture(vmm_tests_target.architecture)?; @@ -254,6 +257,10 @@ impl SimpleFlowNode for Node { env.insert("PETRI_REMOTE_ARTIFACTS".into(), "0".into()); } + if reuse_prepped_vhds { + env.insert("PETRI_REUSE_PREPPED_VHDS".into(), "1".into()); + } + if let Some(openvmm) = openvmm { // TODO OSS: update filenames to use openvmm naming (requires petri updates) match rt.read(openvmm) { diff --git a/vmm_tests/prep_steps/src/main.rs b/vmm_tests/prep_steps/src/main.rs index fb1bd0d2d5..0b02012930 100644 --- a/vmm_tests/prep_steps/src/main.rs +++ b/vmm_tests/prep_steps/src/main.rs @@ -92,7 +92,15 @@ fn run( .replace(".vhd", "-prepped.vhd"), ); if result_disk.exists() { - tracing::warn!("Result disk already exists, recreating it."); + if std::env::var("PETRI_REUSE_PREPPED_VHDS") + .ok() + .is_some_and(|v| v.eq_ignore_ascii_case("true") || v == "1") + { + tracing::info!("Result disk already exists, skipping..."); + return Ok(()); + } else { + tracing::warn!("Result disk already exists, recreating it."); + } } else { tracing::info!("Copying source disk to result disk."); } From 1b5e95af6e0735d153cc3ff87f387031e2ad147b Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Mon, 4 May 2026 11:29:58 -0700 Subject: [PATCH 3/7] fixups --- .../src/pipelines/vmm_tests_run.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs index e4dc30499d..95f67676e5 100644 --- a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs +++ b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs @@ -36,7 +36,11 @@ pub struct VmmTestsRunCli { #[clap(long)] target: Option, - /// Directory for the output artifacts + /// Directory for the output artifacts. + /// + /// If not specified, defaults to `target/vmm_tests`. + /// WSL-to-Windows runs still require explicitly overriding this to a + /// Windows-accessible output directory. #[clap(long)] dir: Option, @@ -163,7 +167,7 @@ impl IntoPipeline for VmmTestsRunCli { let target = resolve_target(target, backend_hint)?; let target_os = target.as_triple().operating_system; - let target_arch = target.common_arch()?; + let target_architecture = target.common_arch()?; let target_str = target.as_triple().to_string(); let repo_root = crate::repo_root(); @@ -289,7 +293,7 @@ impl IntoPipeline for VmmTestsRunCli { job = job.dep_on( move |_| flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalKernel { - arch: target_arch, + arch: target_architecture, kernel: ReadVar::from_static(kernel_path), modules: ReadVar::from_static(modules_path), }, @@ -300,7 +304,7 @@ impl IntoPipeline for VmmTestsRunCli { if let Some(fw_path) = custom_uefi_firmware { job = job.dep_on(move |_| { flowey_lib_hvlite::_jobs::cfg_versions::Request::LocalUefi( - target_arch, + target_architecture, ReadVar::from_static(fw_path), ) }); @@ -457,12 +461,9 @@ fn parse_nextest_output(stdout: &str) -> anyhow::Result anyhow::Result> { log::info!("Using test binary: {}", suite.binary_path.display()); log::info!("Querying artifacts for {} tests", suite.testcases.len()); @@ -600,8 +601,7 @@ fn selections_from_resolved( } impl ResolvedArtifactSelections { - /// Resolve a single artifact ID and update selections. Returns true if the - /// artifact was recognized. + /// Resolve a single artifact ID and update selections. fn resolve_artifact(&mut self, id: &str) -> anyhow::Result<()> { match id { // OpenVMM binary @@ -781,7 +781,7 @@ impl ResolvedArtifactSelections { self.build.pipette_windows = true; } - _ => anyhow::bail!("unknown artifact type"), + _ => anyhow::bail!("unknown artifact: {id}"), }; Ok(()) } From 9ef5b1fe7245dde35d7c786be578068834ad0d6d Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Mon, 4 May 2026 12:25:02 -0700 Subject: [PATCH 4/7] fool fmt --- flowey/flowey_lib_hvlite/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flowey/flowey_lib_hvlite/src/lib.rs b/flowey/flowey_lib_hvlite/src/lib.rs index 3976df85d4..348cb2ac84 100644 --- a/flowey/flowey_lib_hvlite/src/lib.rs +++ b/flowey/flowey_lib_hvlite/src/lib.rs @@ -6,6 +6,11 @@ #![expect(missing_docs)] #![forbid(unsafe_code)] +// Satisfy xtask fmt by using serde here +// It is included in flowey's prelude module, but that is not detected. +#[expect(unused_imports)] +use serde::Serialize; + pub mod _jobs; pub mod artifact_openhcl_igvm_from_recipe; pub mod artifact_openhcl_igvm_from_recipe_extras; From f48fe79f44bf0d118fd7780bba1bcdff34362efe Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Mon, 4 May 2026 13:33:52 -0700 Subject: [PATCH 5/7] fix --- petri/petri_artifacts_core/src/lib.rs | 6 +++--- petri/src/test.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/petri/petri_artifacts_core/src/lib.rs b/petri/petri_artifacts_core/src/lib.rs index 3d41ca5c93..16e667c2c5 100644 --- a/petri/petri_artifacts_core/src/lib.rs +++ b/petri/petri_artifacts_core/src/lib.rs @@ -300,9 +300,9 @@ pub struct ErasedArtifactHandle { artifact_id_str: &'static str, } -// used to serialize the artifact handle for vmm-tests-run -impl ToString for ErasedArtifactHandle { - fn to_string(&self) -> String { +impl ErasedArtifactHandle { + /// used to serialize the artifact handle when querying petri for test requirements + pub fn to_global_unique_id(&self) -> String { self.artifact_id_str.to_string() } } diff --git a/petri/src/test.rs b/petri/src/test.rs index a83cd8f8f1..a298d4b3dd 100644 --- a/petri/src/test.rs +++ b/petri/src/test.rs @@ -421,10 +421,10 @@ pub fn test_main( if matches { for artifact in test.artifact_requirements.required_artifacts() { - required_set.insert(artifact.to_string()); + required_set.insert(artifact.to_global_unique_id()); } for artifact in test.artifact_requirements.optional_artifacts() { - optional_set.insert(artifact.to_string()); + optional_set.insert(artifact.to_global_unique_id()); } } } From b9082226f1bdddab8b1009b970c3d50ca88e4774 Mon Sep 17 00:00:00 2001 From: Trevor Jones Date: Mon, 4 May 2026 17:35:29 -0700 Subject: [PATCH 6/7] feedback --- .config/nextest.toml | 37 +++--- .../src/pipelines/vmm_tests_run.rs | 6 +- flowey/flowey_lib_hvlite/Cargo.toml | 4 + flowey/flowey_lib_hvlite/src/lib.rs | 5 - petri/petri_artifacts_core/src/lib.rs | 121 ++++++++++++------ petri/src/test.rs | 46 +++++-- .../src/lib.rs | 7 +- vmm_tests/petri_artifacts_vmm_test/src/lib.rs | 27 ++-- vmm_tests/vmm_test_images/src/lib.rs | 10 +- vmm_tests/vmm_test_macros/src/lib.rs | 9 +- 10 files changed, 169 insertions(+), 103 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index f36ebb84a8..ef238deecd 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -23,6 +23,14 @@ # https://nexte.st/docs/configuration/per-test-overrides/#override-precedence # https://nexte.st/docs/configuration/?h=hierar#hierarchical-configuration +[[profile.ci.overrides]] +# use fuzzy-matching for the package() to allow out-of-tree tests to use the +# same profile +filter = 'package(~vmm_tests)' +# VMM tests contain their own watchdog timer, but keep an extra long timer +# here as a backup. +slow-timeout = { period = "10m", terminate-after = 2 } + [[profile.default.overrides]] filter = 'package(~vmm_tests) and test(very_heavy)' # Extra heavy tests have even more VPs. Internal runners fail when 32vp tests @@ -46,6 +54,13 @@ filter = 'package(~vmm_tests)' # For local dev runs, you may need to manually restrict the number of # threads running via the -j cli arg. threads-required = 2 +# Use the same slow timeout as ci, but never terminate +slow-timeout = { period = "10m" } + +[[profile.ci.overrides]] +# allow loom based tests more time, as they take a while +filter = 'test(loom)' +slow-timeout = { period = "30s", terminate-after = 2 } # Profile for AI agent runs. Minimizes output noise: only prints slow, failing, # and flaky tests. Agents should use: cargo nextest run -p --profile agent @@ -72,24 +87,4 @@ fail-fast = false [profile.ci.junit] path = "junit.xml" -store-success-output = "true" - -[[profile.ci.overrides]] -# allow loom based tests more time, as they take a while -filter = 'test(loom)' -slow-timeout = { period = "30s", terminate-after = 2 } - -[[profile.ci.overrides]] -# use fuzzy-matching for the package() to allow out-of-tree tests to use the -# same profile -filter = 'package(~vmm_tests)' -# VMM tests contain their own watchdog timer, but keep an extra long timer -# here as a backup. -slow-timeout = { period = "10m", terminate-after = 2 } - -[[profile.default.overrides]] -# use fuzzy-matching for the package() to allow out-of-tree tests to use the -# same profile -filter = 'package(~vmm_tests)' -# Use the same slow timeout as ci, but never terminate -slow-timeout = { period = "10m" } +store-success-output = "true" \ No newline at end of file diff --git a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs index 95f67676e5..de4b016d05 100644 --- a/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs +++ b/flowey/flowey_hvlite/src/pipelines/vmm_tests_run.rs @@ -248,7 +248,7 @@ impl IntoPipeline for VmmTestsRunCli { } } - resolved.downloads.clear(); + resolved.downloads.retain(|a| !a.supports_blob_disk()); if hyperv_tests == 0 { log::info!("Lazy fetch enabled: disk images will be streamed on demand via HTTP"); @@ -437,14 +437,14 @@ fn parse_nextest_output(stdout: &str) -> anyhow::Result ResolvedArtifactSource { /// resolve them to paths. pub struct ArtifactResolver<'a> { inner: ArtifactResolverInner<'a>, - remote_policy: RemoteAccess, + remote_policy: Option, } impl<'a> ArtifactResolver<'a> { - /// Returns the default remote access policy, checking the + /// Returns the remote access policy, checking the /// `PETRI_REMOTE_ARTIFACTS` environment variable. /// /// Set `PETRI_REMOTE_ARTIFACTS=0` to force all artifacts to be resolved /// locally, even if `RemoteAccess::Allow` is specified per-call. - fn default_remote_policy() -> RemoteAccess { - match std::env::var("PETRI_REMOTE_ARTIFACTS").as_deref() { - Ok("0") | Ok("false") => RemoteAccess::LocalOnly, - _ => RemoteAccess::Allow, - } + pub fn set_remote_policy(&mut self, test_policy: RemoteAccess) { + self.remote_policy = Some( + if matches!(test_policy, RemoteAccess::LocalOnly) + || matches!( + std::env::var("PETRI_REMOTE_ARTIFACTS").as_deref(), + Ok("0") | Ok("false") + ) + { + RemoteAccess::LocalOnly + } else { + RemoteAccess::Allow + }, + ) } /// Returns a resolver to collect requirements; the artifact objects returned by @@ -204,7 +216,7 @@ impl<'a> ArtifactResolver<'a> { pub fn collector(requirements: &'a mut TestArtifactRequirements) -> Self { ArtifactResolver { inner: ArtifactResolverInner::Collecting(RefCell::new(requirements)), - remote_policy: Self::default_remote_policy(), + remote_policy: None, } } @@ -212,14 +224,18 @@ impl<'a> ArtifactResolver<'a> { pub fn resolver(artifacts: &'a TestArtifacts) -> Self { ArtifactResolver { inner: ArtifactResolverInner::Resolving(artifacts), - remote_policy: Self::default_remote_policy(), + remote_policy: None, } } /// Returns the effective remote access for a given per-call policy, /// respecting the resolver-wide policy. - fn effective_remote(&self, per_call: RemoteAccess) -> RemoteAccess { - if matches!(self.remote_policy, RemoteAccess::LocalOnly) { + fn effective_remote(&self, per_call: RemoteAccess) -> RemoteAccess { + if matches!( + self.remote_policy.expect("remote_policy not populated"), + RemoteAccess::LocalOnly + ) || !A::SUPPORTS_BLOB_DISK + { RemoteAccess::LocalOnly } else { per_call @@ -248,7 +264,9 @@ impl<'a> ArtifactResolver<'a> { ) -> ResolvedOptionalArtifact { match &self.inner { ArtifactResolverInner::Collecting(requirements) => { - requirements.borrow_mut().try_require(handle.erase()); + requirements + .borrow_mut() + .require(handle.erase(), RemoteAccess::LocalOnly, true); ResolvedOptionalArtifact(OptionalArtifactState::Collecting, PhantomData) } ArtifactResolverInner::Resolving(artifacts) => ResolvedOptionalArtifact( @@ -273,12 +291,12 @@ impl<'a> ArtifactResolver<'a> { handle: ArtifactHandle, remote: RemoteAccess, ) -> ResolvedArtifactSource { - let effective = self.effective_remote(remote); + let effective = self.effective_remote::(remote); match &self.inner { ArtifactResolverInner::Collecting(requirements) => { requirements .borrow_mut() - .require_source(handle.erase(), effective); + .require(handle.erase(), effective, false); ResolvedArtifactSource(None, PhantomData) } ArtifactResolverInner::Resolving(artifacts) => { @@ -302,7 +320,7 @@ pub struct ErasedArtifactHandle { impl ErasedArtifactHandle { /// used to serialize the artifact handle when querying petri for test requirements - pub fn to_global_unique_id(&self) -> String { + pub fn global_unique_id(&self) -> String { self.artifact_id_str.to_string() } } @@ -364,7 +382,7 @@ impl AsArtifactHandle for ArtifactHandle { } } -/// Declare one or more type-safe artifacts. +/// Declare one or more type-safe artifacts that do not support blob disk. #[macro_export] macro_rules! declare_artifacts { ( @@ -373,6 +391,45 @@ macro_rules! declare_artifacts { $name:ident ),* $(,)? + ) => { + $crate::declare_artifacts_inner!( + $( + $(#[$doc])* + $name(false), + )* + ); + }; +} + +/// Declare one or more type-safe artifacts that support blob disk. +#[macro_export] +macro_rules! declare_blob_artifacts { + ( + $( + $(#[$doc:meta])* + $name:ident + ),* + $(,)? + ) => { + $crate::declare_artifacts_inner!( + $( + $(#[$doc])* + $name(true), + )* + ); + }; +} + +/// Declare one or more type-safe artifacts, specifying whether each supports +/// blob disk. +#[macro_export] +macro_rules! declare_artifacts_inner { + ( + $( + $(#[$doc:meta])* + $name:ident($supports_blob_disk:literal) + ),* + $(,)? ) => { $( $crate::paste::paste! { @@ -389,6 +446,7 @@ macro_rules! declare_artifacts { mod [< $name __ty >] { impl $crate::ArtifactId for super::$name { const GLOBAL_UNIQUE_ID: &'static str = module_path!(); + const SUPPORTS_BLOB_DISK: bool = $supports_blob_disk; fn i_know_what_im_doing_with_this_manual_impl_instead_of_using_the_declare_artifacts_macro() {} } } @@ -451,36 +509,15 @@ impl TestArtifactRequirements { } } - /// Add a dependency to the set of required artifacts (must be local). - pub fn require(&mut self, dependency: impl AsArtifactHandle) -> &mut Self { - self.require_source(dependency, RemoteAccess::LocalOnly) - } - - /// Add an optional dependency to the set of artifacts. - pub fn try_require(&mut self, dependency: impl AsArtifactHandle) -> &mut Self { - self.artifacts.push(( - dependency.erase(), - ArtifactRequirement { - optional: true, - remote: RemoteAccess::LocalOnly, - }, - )); - self - } - - /// Add a dependency that may resolve to a remote URL. - pub fn require_source( + /// Add a dependency that may be optional or resolve to a remote URL. + pub fn require( &mut self, dependency: impl AsArtifactHandle, remote: RemoteAccess, + optional: bool, ) -> &mut Self { - self.artifacts.push(( - dependency.erase(), - ArtifactRequirement { - optional: false, - remote, - }, - )); + self.artifacts + .push((dependency.erase(), ArtifactRequirement { optional, remote })); self } diff --git a/petri/src/test.rs b/petri/src/test.rs index a298d4b3dd..9641dcd556 100644 --- a/petri/src/test.rs +++ b/petri/src/test.rs @@ -30,6 +30,7 @@ use crate::requirements::can_run_test_with_context; use crate::tracing::try_init_tracing; use anyhow::Context as _; use petri_artifacts_core::ArtifactResolver; +use petri_artifacts_core::RemoteAccess; use std::panic::AssertUnwindSafe; use std::panic::catch_unwind; use test_macro_support::TESTS; @@ -39,7 +40,15 @@ use test_macro_support::TESTS; macro_rules! test { ($f:ident, $req:expr) => { $crate::multitest!(vec![ - $crate::SimpleTest::new(stringify!($f), $req, $f, None, false).into() + $crate::SimpleTest::new( + stringify!($f), + $req, + $f, + None, + false, + ::petri::RemoteAccess::LocalOnly + ) + .into() ]); }; } @@ -49,7 +58,15 @@ macro_rules! test { macro_rules! unstable_test { ($f:ident, $req:expr) => { $crate::multitest!(vec![ - $crate::SimpleTest::new(stringify!($f), $req, $f, None, true).into() + $crate::SimpleTest::new( + stringify!($f), + $req, + $f, + None, + true, + ::petri::RemoteAccess::LocalOnly + ) + .into() ]); }; } @@ -99,8 +116,11 @@ impl Test { tests.into_iter().filter_map(move |test| { let mut artifact_requirements = test.0.artifact_requirements()?; // All tests require the log directory. - artifact_requirements - .require(petri_artifacts_common::artifacts::TEST_LOG_DIRECTORY); + artifact_requirements.require( + petri_artifacts_common::artifacts::TEST_LOG_DIRECTORY, + RemoteAccess::LocalOnly, + false, + ); Some(Self { module, artifact_requirements, @@ -214,7 +234,7 @@ pub trait RunTest: Send { /// Returns `None` if this test makes no sense for this host environment /// (e.g., an x86_64 test on an aarch64 host) and should be left out of the /// test list. - fn resolve(&self, resolver: &ArtifactResolver<'_>) -> Option; + fn resolve(&self, resolver: ArtifactResolver<'_>) -> Option; /// Runs the test, which has been assigned `name`, with the given /// `artifacts`. fn run(&self, params: PetriTestParams<'_>, artifacts: Self::Artifacts) -> anyhow::Result<()>; @@ -239,13 +259,13 @@ impl DynRunTest for T { fn artifact_requirements(&self) -> Option { let mut requirements = TestArtifactRequirements::new(); - self.resolve(&ArtifactResolver::collector(&mut requirements))?; + self.resolve(ArtifactResolver::collector(&mut requirements))?; Some(requirements) } fn run(&self, params: PetriTestParams<'_>, artifacts: &TestArtifacts) -> anyhow::Result<()> { let artifacts = self - .resolve(&ArtifactResolver::resolver(artifacts)) + .resolve(ArtifactResolver::resolver(artifacts)) .context("test should have been skipped")?; self.run(params, artifacts) } @@ -303,6 +323,7 @@ pub struct SimpleTest { /// Optional test requirements pub host_requirements: Option, unstable: bool, + remote_policy: RemoteAccess, } impl SimpleTest @@ -319,6 +340,7 @@ where run: F, host_requirements: Option, unstable: bool, + remote_policy: RemoteAccess, ) -> Self { SimpleTest { leaf_name, @@ -326,6 +348,7 @@ where run, host_requirements, unstable, + remote_policy, } } } @@ -342,8 +365,9 @@ where self.leaf_name } - fn resolve(&self, resolver: &ArtifactResolver<'_>) -> Option { - (self.resolve)(resolver) + fn resolve(&self, mut resolver: ArtifactResolver<'_>) -> Option { + resolver.set_remote_policy(self.remote_policy); + (self.resolve)(&resolver) } fn run(&self, params: PetriTestParams<'_>, artifacts: Self::Artifacts) -> anyhow::Result<()> { @@ -421,10 +445,10 @@ pub fn test_main( if matches { for artifact in test.artifact_requirements.required_artifacts() { - required_set.insert(artifact.to_global_unique_id()); + required_set.insert(artifact.global_unique_id()); } for artifact in test.artifact_requirements.optional_artifacts() { - optional_set.insert(artifact.to_global_unique_id()); + optional_set.insert(artifact.global_unique_id()); } } } diff --git a/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs b/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs index 4047de887d..a1b6147ff8 100644 --- a/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs +++ b/vmm_tests/petri_artifact_resolver_openvmm_known_paths/src/lib.rs @@ -148,11 +148,10 @@ impl petri_artifacts_core::ResolveTestArtifact for OpenvmmKnownPathsTestArtifact // Fall back to remote URL for artifacts hosted on Azure Blob Storage, // but only for formats the blob disk backend supports (fixed VHD1 and flat). if let Some(artifact) = KnownTestArtifacts::from_handle(id) { - let filename = artifact.filename(); - if filename.ends_with(".vhd") || filename.ends_with(".iso") { + if artifact.supports_blob_disk() { let url = format!( "https://{STORAGE_ACCOUNT}.blob.core.windows.net/{CONTAINER}/{}", - filename + artifact.filename() ); return Ok(ArtifactSource::Remote { url }); } @@ -261,7 +260,7 @@ fn get_test_artifact_path(artifact: KnownTestArtifacts) -> Result( +const fn meta( variant: KnownTestArtifacts, ) -> KnownTestArtifactMeta { KnownTestArtifactMeta { @@ -91,6 +93,7 @@ const fn meta( filename: T::FILENAME, size: T::SIZE, download_name: T::DOWNLOAD_NAME, + supports_blob_disk: T::SUPPORTS_BLOB_DISK, } } @@ -139,4 +142,9 @@ impl KnownTestArtifacts { .find(|m| (m.handle_fn)() == id) .map(|m| m.variant) } + + /// Whether this artifact supports blob disk + pub fn supports_blob_disk(&self) -> bool { + self.meta().supports_blob_disk + } } diff --git a/vmm_tests/vmm_test_macros/src/lib.rs b/vmm_tests/vmm_test_macros/src/lib.rs index 99f3de5eee..cbfaa632a2 100644 --- a/vmm_tests/vmm_test_macros/src/lib.rs +++ b/vmm_tests/vmm_test_macros/src/lib.rs @@ -174,11 +174,11 @@ impl ToTokens for PcatGuest { tokens.extend(match self { PcatGuest::Vhd(known_vhd) => { let vhd = known_vhd.image_artifact.clone(); - quote!(::petri::PcatGuest::Vhd(petri::BootImageConfig::from_vhd(resolver.require_source(#vhd, remote_access)))) + quote!(::petri::PcatGuest::Vhd(petri::BootImageConfig::from_vhd(resolver.require_source(#vhd, ::petri::RemoteAccess::Allow)))) } PcatGuest::Iso(known_iso) => { let iso = known_iso.image_artifact.clone(); - quote!(::petri::PcatGuest::Iso(petri::BootImageConfig::from_iso(resolver.require_source(#iso, remote_access)))) + quote!(::petri::PcatGuest::Iso(petri::BootImageConfig::from_iso(resolver.require_source(#iso, ::petri::RemoteAccess::Allow)))) } }); } @@ -199,7 +199,7 @@ impl ToTokens for UefiGuest { tokens.extend(match self { UefiGuest::Vhd(known_vhd) => { let v = known_vhd.image_artifact.clone(); - quote!(::petri::UefiGuest::Vhd(petri::BootImageConfig::from_vhd(resolver.require_source(#v, remote_access)))) + quote!(::petri::UefiGuest::Vhd(petri::BootImageConfig::from_vhd(resolver.require_source(#v, ::petri::RemoteAccess::Allow)))) } UefiGuest::GuestTestUefi(arch) => { let arch_tokens = arch_to_tokens(*arch); @@ -865,12 +865,12 @@ fn make_vmm_test(args: ArgsWithOverrides, item: ItemFn) -> syn::Result syn::Result Date: Mon, 4 May 2026 18:31:42 -0700 Subject: [PATCH 7/7] fix --- petri/petri_artifacts_core/src/lib.rs | 35 ++++++++----------- vmm_tests/petri_artifacts_vmm_test/src/lib.rs | 3 +- vmm_tests/vmm_test_macros/src/lib.rs | 1 - 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/petri/petri_artifacts_core/src/lib.rs b/petri/petri_artifacts_core/src/lib.rs index 07d07c4e26..53223d9b24 100644 --- a/petri/petri_artifacts_core/src/lib.rs +++ b/petri/petri_artifacts_core/src/lib.rs @@ -187,7 +187,7 @@ impl ResolvedArtifactSource { /// resolve them to paths. pub struct ArtifactResolver<'a> { inner: ArtifactResolverInner<'a>, - remote_policy: Option, + remote_policy: RemoteAccess, } impl<'a> ArtifactResolver<'a> { @@ -195,20 +195,17 @@ impl<'a> ArtifactResolver<'a> { /// `PETRI_REMOTE_ARTIFACTS` environment variable. /// /// Set `PETRI_REMOTE_ARTIFACTS=0` to force all artifacts to be resolved - /// locally, even if `RemoteAccess::Allow` is specified per-call. + /// locally, even if `RemoteAccess::Allow` is specified per-call or per-test. pub fn set_remote_policy(&mut self, test_policy: RemoteAccess) { - self.remote_policy = Some( - if matches!(test_policy, RemoteAccess::LocalOnly) - || matches!( - std::env::var("PETRI_REMOTE_ARTIFACTS").as_deref(), - Ok("0") | Ok("false") - ) - { - RemoteAccess::LocalOnly - } else { - RemoteAccess::Allow - }, - ) + self.remote_policy = if matches!(test_policy, RemoteAccess::LocalOnly) + || matches!( + std::env::var("PETRI_REMOTE_ARTIFACTS").as_deref(), + Ok("0") | Ok("false") + ) { + RemoteAccess::LocalOnly + } else { + RemoteAccess::Allow + }; } /// Returns a resolver to collect requirements; the artifact objects returned by @@ -216,7 +213,7 @@ impl<'a> ArtifactResolver<'a> { pub fn collector(requirements: &'a mut TestArtifactRequirements) -> Self { ArtifactResolver { inner: ArtifactResolverInner::Collecting(RefCell::new(requirements)), - remote_policy: None, + remote_policy: RemoteAccess::LocalOnly, } } @@ -224,18 +221,14 @@ impl<'a> ArtifactResolver<'a> { pub fn resolver(artifacts: &'a TestArtifacts) -> Self { ArtifactResolver { inner: ArtifactResolverInner::Resolving(artifacts), - remote_policy: None, + remote_policy: RemoteAccess::LocalOnly, } } /// Returns the effective remote access for a given per-call policy, /// respecting the resolver-wide policy. fn effective_remote(&self, per_call: RemoteAccess) -> RemoteAccess { - if matches!( - self.remote_policy.expect("remote_policy not populated"), - RemoteAccess::LocalOnly - ) || !A::SUPPORTS_BLOB_DISK - { + if matches!(self.remote_policy, RemoteAccess::LocalOnly) || !A::SUPPORTS_BLOB_DISK { RemoteAccess::LocalOnly } else { per_call diff --git a/vmm_tests/petri_artifacts_vmm_test/src/lib.rs b/vmm_tests/petri_artifacts_vmm_test/src/lib.rs index ce9c52da0c..83ea00a7b7 100644 --- a/vmm_tests/petri_artifacts_vmm_test/src/lib.rs +++ b/vmm_tests/petri_artifacts_vmm_test/src/lib.rs @@ -504,7 +504,8 @@ pub mod artifacts { const DOWNLOAD_NAME: &'static str = "Ubuntu2404ServerAarch64Vhd"; } - declare_blob_artifacts! { + // blob disk does not support VHDX files + declare_artifacts! { /// Windows 11 Enterprise ARM64 24H2 WINDOWS_11_ENTERPRISE_AARCH64 } diff --git a/vmm_tests/vmm_test_macros/src/lib.rs b/vmm_tests/vmm_test_macros/src/lib.rs index cbfaa632a2..b33517b3a0 100644 --- a/vmm_tests/vmm_test_macros/src/lib.rs +++ b/vmm_tests/vmm_test_macros/src/lib.rs @@ -865,7 +865,6 @@ fn make_vmm_test(args: ArgsWithOverrides, item: ItemFn) -> syn::Result