diff --git a/.github/workflows/openvmm-ci.yaml b/.github/workflows/openvmm-ci.yaml index d6ee9dcb39..abf0d9a4f7 100644 --- a/.github/workflows/openvmm-ci.yaml +++ b/.github/workflows/openvmm-ci.yaml @@ -528,7 +528,11 @@ jobs: run: flowey.exe e 10 flowey_lib_common::run_cargo_clippy 1 shell: bash - name: create cargo-nextest cache dir - run: flowey.exe e 10 flowey_lib_common::download_cargo_nextest 0 + run: |- + flowey.exe e 10 flowey_lib_common::download_cargo_nextest 0 + flowey.exe e 10 flowey_lib_common::download_cargo_nextest 1 + flowey.exe e 10 flowey_lib_common::download_cargo_nextest 2 + flowey.exe e 10 flowey_lib_common::download_cargo_nextest 3 shell: bash - name: Pre-processing cache vars run: |- @@ -542,18 +546,20 @@ jobs: key: ${{ env.floweyvar2 }} path: ${{ env.floweyvar3 }} name: 'Restore cache: cargo-nextest' - - name: report $CARGO_HOME + - name: downloading cargo-nextest run: |- flowey.exe v 10 'flowey_lib_common::cache:4:flowey_lib_common/src/cache.rs:462:70' --is-raw-string update --env-source steps.flowey_lib_common__cache__1.outputs.cache-hit < cmd.into_pipeline(pipeline_hint), OpenvmmPipelines::CustomVmfirmwareigvmDll(cmd) => cmd.into_pipeline(pipeline_hint), - OpenvmmPipelines::Ci(cmd) => match cmd { OpenvmmPipelinesCi::CheckinGates(cmd) => cmd.into_pipeline(pipeline_hint), OpenvmmPipelinesCi::BuildDocs(cmd) => cmd.into_pipeline(pipeline_hint), }, OpenvmmPipelines::RestorePackages(cmd) => cmd.into_pipeline(pipeline_hint), + OpenvmmPipelines::VmmTests(cmd) => cmd.into_pipeline(pipeline_hint), } } } diff --git a/flowey/flowey_hvlite/src/pipelines/vmm_tests.rs b/flowey/flowey_hvlite/src/pipelines/vmm_tests.rs new file mode 100644 index 0000000000..d2b0c2457e --- /dev/null +++ b/flowey/flowey_hvlite/src/pipelines/vmm_tests.rs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +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::VmmTestSelectionFlags; +use flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::VmmTestSelections; +use flowey_lib_hvlite::run_cargo_build::common::CommonTriple; +use std::path::PathBuf; +use vmm_test_images::KnownTestArtifacts; + +#[derive(clap::ValueEnum, Copy, Clone)] +pub enum VmmTestTargetCli { + /// Windows Aarch64 + WindowsAarch64, + /// Windows X64 + WindowsX64, + /// Linux X64 + LinuxX64, +} + +/// Build everything needed and run the VMM tests +#[derive(clap::Args)] +pub struct VmmTestsCli { + /// Specify what target to build the VMM tests for + /// + /// If not specified, defaults to the current host target. + #[clap(long)] + target: Option, + + /// Directory for the output artifacts + #[clap(long)] + dir: Option, + + /// Custom test filter + #[clap(long, conflicts_with("flags"))] + filter: Option, + /// Custom list of artifacts to download + #[clap(long, conflicts_with("flags"))] + artifacts: Vec, + /// Flags used to generate the VMM test filter + /// + /// Syntax: `--flags=<+|->,..` + /// + /// Available flags with default values: + /// + /// `-tdx,-hyperv_vbs,+windows,+ubuntu,+freebsd,+openhcl,+openvmm,+hyperv,+uefi,+pcat,+tmk,+guest_test_uefi` + // TODO: Automatically generate the list of possible flags + #[clap(long)] + flags: Option, + + /// pass `--verbose` to cargo + #[clap(long)] + verbose: bool, + /// Automatically install any missing required dependencies. + #[clap(long)] + install_missing_deps: bool, + + /// Use unstable WHP interfaces + #[clap(long)] + unstable_whp: bool, + /// Release build instead of debug build + #[clap(long)] + release: bool, + + /// Build only, do not run + #[clap(long)] + build_only: bool, + /// Copy extras to output dir (symbols, etc) + #[clap(long)] + copy_extras: bool, +} + +impl IntoPipeline for VmmTestsCli { + fn into_pipeline(self, backend_hint: PipelineBackendHint) -> anyhow::Result { + if !matches!(backend_hint, PipelineBackendHint::Local) { + anyhow::bail!("vmm-tests is for local use only") + } + + let Self { + target, + dir, + filter, + artifacts, + flags, + verbose, + install_missing_deps, + unstable_whp, + release, + build_only, + copy_extras, + } = self; + + let openvmm_repo = flowey_lib_common::git_checkout::RepoSource::ExistingClone( + ReadVar::from_static(crate::repo_root()), + ); + + let mut pipeline = Pipeline::new(); + + let host_target = match ( + FlowArch::host(backend_hint), + FlowPlatform::host(backend_hint), + ) { + (FlowArch::Aarch64, FlowPlatform::Windows) => VmmTestTargetCli::WindowsAarch64, + (FlowArch::X86_64, FlowPlatform::Windows) => VmmTestTargetCli::WindowsX64, + (FlowArch::X86_64, FlowPlatform::Linux(_)) => VmmTestTargetCli::LinuxX64, + _ => anyhow::bail!("unsupported host"), + }; + + let target = match target.unwrap_or(host_target) { + VmmTestTargetCli::WindowsAarch64 => CommonTriple::AARCH64_WINDOWS_MSVC, + VmmTestTargetCli::WindowsX64 => CommonTriple::X86_64_WINDOWS_MSVC, + VmmTestTargetCli::LinuxX64 => CommonTriple::X86_64_LINUX_GNU, + }; + + pipeline + .new_job( + FlowPlatform::host(backend_hint), + FlowArch::host(backend_hint), + "build vmm test dependencies", + ) + .dep_on(|_| flowey_lib_hvlite::_jobs::cfg_versions::Request {}) + .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, + force_nuget_mono: false, + external_nuget_auth: false, + ignore_rust_version: true, + }), + verbose: ReadVar::from_static(verbose), + locked: false, + deny_warnings: false, + }) + .dep_on( + |ctx| flowey_lib_hvlite::_jobs::local_build_and_run_nextest_vmm_tests::Params { + target, + test_content_dir: dir, + selections: if let Some(filter) = filter { + VmmTestSelections::Custom { + filter, + artifacts, + build: BuildSelections::default(), + } + } else { + VmmTestSelections::Flags(flags.unwrap()) + }, + unstable_whp, + release, + build_only, + copy_extras, + done: ctx.new_done_handle(), + }, + ) + .finish(); + + Ok(pipeline) + } +} diff --git a/flowey/flowey_lib_common/src/download_cargo_nextest.rs b/flowey/flowey_lib_common/src/download_cargo_nextest.rs index 369499aea6..7c19cdd13b 100644 --- a/flowey/flowey_lib_common/src/download_cargo_nextest.rs +++ b/flowey/flowey_lib_common/src/download_cargo_nextest.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Download (and optionally, install) a copy of `cargo-nextest`. +//! Download a copy of `cargo-nextest`. use crate::cache::CacheHit; use flowey::node::prelude::*; @@ -10,14 +10,11 @@ flowey_request! { pub enum Request { /// Version of `cargo nextest` to install (e.g: "0.9.57") Version(String), - /// Install `cargo-nextest` as a `cargo` extension (invoked via `cargo - /// nextest`). - InstallWithCargo(WriteVar), - /// Install `cargo-nextest` as a standalone binary, without requiring Rust + /// Download `cargo-nextest` as a standalone binary, without requiring Rust /// to be installed. /// /// Useful when running archived nextest tests in a separate job. - InstallStandalone(WriteVar), + Get(ReadVar, WriteVar), } } @@ -28,137 +25,93 @@ impl FlowNode for Node { fn imports(ctx: &mut ImportCtx<'_>) { ctx.import::(); - ctx.import::(); - ctx.import::(); } fn emit(requests: Vec, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { let mut version = None; - let mut install_with_cargo = Vec::new(); - let mut install_standalone = Vec::new(); + let mut reqs = Vec::new(); for req in requests { match req { Request::Version(v) => same_across_all_reqs("Version", &mut version, v)?, - Request::InstallWithCargo(v) => install_with_cargo.push(v), - Request::InstallStandalone(v) => install_standalone.push(v), + Request::Get(target, path) => reqs.push((target, path)), } } let version = version.ok_or(anyhow::anyhow!("Missing essential request: Version"))?; - let install_with_cargo = install_with_cargo; - let install_standalone = install_standalone; + let reqs = reqs; // -- end of req processing -- // - if install_standalone.is_empty() && install_with_cargo.is_empty() { + if reqs.is_empty() { return Ok(()); } - let cargo_nextest_bin = ctx.platform().binary("cargo-nextest"); - let cache_dir = ctx.emit_rust_stepv("create cargo-nextest cache dir", |_| { |_| Ok(std::env::current_dir()?.absolute()?) }); - let cache_key = ReadVar::from_static(format!("cargo-nextest-{version}")); - let hitvar = ctx.reqv(|v| { - crate::cache::Request { - label: "cargo-nextest".into(), - dir: cache_dir.clone(), - key: cache_key, - restore_keys: None, // we want an exact hit - hitvar: v, - } - }); - - // rust deps in case we end up doing a cargo-install - // TODO: only install rust if we can't find nextest - let cargo_install_persistent_dir = - ctx.reqv(crate::cfg_persistent_dir_cargo_install::Request); - let rust_toolchain = ctx.reqv(crate::install_rust::Request::GetRustupToolchain); - let cargo_home = ctx.reqv(crate::install_rust::Request::GetCargoHome); - let rust_installed = ctx.reqv(crate::install_rust::Request::EnsureInstalled); - - ctx.emit_rust_step("installing cargo-nextest", |ctx| { - install_with_cargo.claim(ctx); - - let install_standalone = install_standalone.claim(ctx); - let cache_dir = cache_dir.claim(ctx); - let hitvar = hitvar.claim(ctx); - let cargo_install_persistent_dir = cargo_install_persistent_dir.claim(ctx); - let rust_toolchain = rust_toolchain.claim(ctx); - let cargo_home = cargo_home.claim(ctx); - rust_installed.claim(ctx); - - move |rt| { - let cache_dir = rt.read(cache_dir); - let cargo_install_persistent_dir = rt.read(cargo_install_persistent_dir); - let rust_toolchain = rt.read(rust_toolchain); - let cargo_home = rt.read(cargo_home); - - let cached_bin_path = cache_dir.join(&cargo_nextest_bin); - let cached = if matches!(rt.read(hitvar), CacheHit::Hit) { - assert!(cached_bin_path.exists()); - Some(cached_bin_path.clone()) - } else { - None - }; - - let (cargo_home, path_to_cargo_nextest) = if let Some(cached) = cached { - (cargo_home, cached) - } else { - let root = cargo_install_persistent_dir.unwrap_or("./".into()); - - let sh = xshell::Shell::new()?; - let run = |offline| { - let rust_toolchain = rust_toolchain.as_ref(); - let rust_toolchain = rust_toolchain.map(|s| format!("+{s}")); - - xshell::cmd!( - sh, - "cargo {rust_toolchain...} - install - --locked - {offline...} - --root {root} - --target-dir {root} - --version {version} - cargo-nextest - " - ) - .run() + for (target, path) in reqs { + let (cache_key, cache_dir) = { + let version = version.clone(); + let cache_key = target.map(ctx, move |target| { + format!("cargo-nextest-{version}-{target}") + }); + let cache_dir = cache_dir + .zip(ctx, cache_key.clone()) + .map(ctx, |(p, k)| p.join(k)); + (cache_key, cache_dir) + }; + + let hitvar = ctx.reqv(|v| { + crate::cache::Request { + label: "cargo-nextest".into(), + dir: cache_dir.clone(), + key: cache_key, + restore_keys: None, // we want an exact hit + hitvar: v, + } + }); + + let version = version.clone(); + ctx.emit_rust_step("downloading cargo-nextest", |ctx| { + let path = path.claim(ctx); + let cache_dir = cache_dir.claim(ctx); + let hitvar = hitvar.claim(ctx); + let target = target.claim(ctx); + + move |rt| { + let cache_dir = rt.read(cache_dir); + let target = rt.read(target); + + let cargo_nextest_bin = match target.operating_system { + target_lexicon::OperatingSystem::Windows => "cargo-nextest.exe", + _ => "cargo-nextest", }; + let cached_bin_path = cache_dir.join(cargo_nextest_bin); + let target = target.to_string(); - // Try --offline to avoid an unnecessary git fetch on rerun. - if run(Some("--offline")).is_err() { - // Try again without --offline. - run(None)?; - } - - let out_bin = root.absolute()?.join("bin").join(&cargo_nextest_bin); + if !matches!(rt.read(hitvar), CacheHit::Hit) { + let sh = xshell::Shell::new()?; - // move the compiled bin into the cache dir - fs_err::rename(out_bin, &cached_bin_path)?; - let final_bin = cached_bin_path.absolute()?; + let nextest_archive = "nextest.tar.gz"; + xshell::cmd!(sh, "curl --fail -L https://get.nexte.st/{version}/{target}.tar.gz -o {nextest_archive}").run()?; + xshell::cmd!(sh, "tar -xf {nextest_archive}").run()?; - (cargo_home, final_bin) - }; + // move the downloaded bin into the cache dir + fs_err::create_dir_all(&cache_dir)?; + fs_err::rename(cargo_nextest_bin, &cached_bin_path)?; + } - // is installing with cargo, make sure the bin we built / - // downloaded is accessible via cargo nextest - fs_err::copy( - &path_to_cargo_nextest, - cargo_home.join("bin").join(&cargo_nextest_bin), - )?; + let cached_bin_path = cached_bin_path.absolute()?; + log::info!("downloaded to {}", cached_bin_path.to_string_lossy()); + assert!(cached_bin_path.exists()); + rt.write(path, &cached_bin_path); - for var in install_standalone { - rt.write(var, &path_to_cargo_nextest) + Ok(()) } - - Ok(()) - } - }); + }); + } Ok(()) } diff --git a/flowey/flowey_lib_common/src/install_cargo_nextest.rs b/flowey/flowey_lib_common/src/install_cargo_nextest.rs new file mode 100644 index 0000000000..7a4355a379 --- /dev/null +++ b/flowey/flowey_lib_common/src/install_cargo_nextest.rs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Install `cargo-nextest`. + +use flowey::node::prelude::*; + +flowey_request! { + pub struct Request(pub WriteVar); +} + +new_flow_node!(struct Node); + +impl FlowNode for Node { + type Request = Request; + + fn imports(ctx: &mut ImportCtx<'_>) { + ctx.import::(); + ctx.import::(); + } + + fn emit(requests: Vec, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { + let mut done = Vec::new(); + + for req in requests { + done.push(req.0); + } + + let done = done; + + // -- end of req processing -- // + + if done.is_empty() { + return Ok(()); + } + + let cargo_nextest_bin = ctx.platform().binary("cargo-nextest"); + + let nextest_path = ctx.reqv(|v| { + crate::download_cargo_nextest::Request::Get( + ReadVar::from_static(target_lexicon::Triple::host()), + v, + ) + }); + let cargo_home = ctx.reqv(crate::install_rust::Request::GetCargoHome); + let rust_installed = ctx.reqv(crate::install_rust::Request::EnsureInstalled); + + ctx.emit_rust_step("installing cargo-nextest", |ctx| { + let nextest_path = nextest_path.claim(ctx); + let cargo_home = cargo_home.claim(ctx); + rust_installed.claim(ctx); + done.claim(ctx); + + move |rt| { + let nextest_path = rt.read(nextest_path); + let cargo_home = rt.read(cargo_home); + + // copy to cargo home bin folder so that nextest + // is accessible via `cargo nextest`` + fs_err::copy( + &nextest_path, + cargo_home.join("bin").join(&cargo_nextest_bin), + )?; + + Ok(()) + } + }); + + Ok(()) + } +} diff --git a/flowey/flowey_lib_common/src/lib.rs b/flowey/flowey_lib_common/src/lib.rs index 591459afb2..0c2b6ab081 100644 --- a/flowey/flowey_lib_common/src/lib.rs +++ b/flowey/flowey_lib_common/src/lib.rs @@ -37,6 +37,7 @@ pub mod gh_workflow_id; pub mod git_checkout; pub mod git_merge_commit; pub mod install_azure_cli; +pub mod install_cargo_nextest; pub mod install_dist_pkg; pub mod install_git; pub mod install_nodejs; diff --git a/flowey/flowey_lib_common/src/publish_test_results.rs b/flowey/flowey_lib_common/src/publish_test_results.rs index f0e96233f9..5407168be3 100644 --- a/flowey/flowey_lib_common/src/publish_test_results.rs +++ b/flowey/flowey_lib_common/src/publish_test_results.rs @@ -110,7 +110,7 @@ impl FlowNode for Node { } FlowBackend::Local => { if let Some(output_dir) = output_dir.clone() { - ctx.emit_rust_step(step_name, |ctx| { + use_side_effects.push(ctx.emit_rust_step(step_name, |ctx| { let output_dir = output_dir.claim(ctx); let has_junit_xml = has_junit_xml.claim(ctx); let junit_xml = junit_xml.claim(ctx); @@ -129,13 +129,16 @@ impl FlowNode for Node { Ok(()) } - }); + })); + } else { + use_side_effects.push(has_junit_xml.into_side_effect()); + use_side_effects.push(junit_xml.into_side_effect()); } } } for (attachment_label, (attachment_path, publish_on_ado)) in attachments { - let step_name = format!("publish test results: {attachment_label} ({label})"); + let step_name = format!("publish test results: {label} ({attachment_label})"); let artifact_name = format!("{label}-{attachment_label}"); let attachment_exists = attachment_path.map(ctx, |p| { @@ -199,7 +202,7 @@ impl FlowNode for Node { } FlowBackend::Local => { if let Some(output_dir) = output_dir.clone() { - ctx.emit_rust_step(step_name, |ctx| { + use_side_effects.push(ctx.emit_rust_step(step_name, |ctx| { let output_dir = output_dir.claim(ctx); let attachment_exists = attachment_exists.claim(ctx); let attachment_path = attachment_path.claim(ctx); @@ -218,8 +221,11 @@ impl FlowNode for Node { Ok(()) } - }); + })); + } else { + use_side_effects.push(attachment_exists.into_side_effect()); } + use_side_effects.push(attachment_path_string.into_side_effect()); } } } diff --git a/flowey/flowey_lib_common/src/run_cargo_nextest_archive.rs b/flowey/flowey_lib_common/src/run_cargo_nextest_archive.rs index f9c915f34f..94ac20d901 100644 --- a/flowey/flowey_lib_common/src/run_cargo_nextest_archive.rs +++ b/flowey/flowey_lib_common/src/run_cargo_nextest_archive.rs @@ -32,14 +32,14 @@ impl FlowNode for Node { fn imports(ctx: &mut ImportCtx<'_>) { ctx.import::(); - ctx.import::(); + ctx.import::(); ctx.import::(); } fn emit(requests: Vec, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { let cargo_flags = ctx.reqv(crate::cfg_cargo_common_flags::Request::GetFlags); - let nextest_installed = ctx.reqv(crate::download_cargo_nextest::Request::InstallWithCargo); + let nextest_installed = ctx.reqv(crate::install_cargo_nextest::Request); let rust_toolchain = ctx.reqv(crate::install_rust::Request::GetRustupToolchain); diff --git a/flowey/flowey_lib_common/src/run_cargo_nextest_run.rs b/flowey/flowey_lib_common/src/run_cargo_nextest_run.rs index 1ae33c6046..954fb60f27 100644 --- a/flowey/flowey_lib_common/src/run_cargo_nextest_run.rs +++ b/flowey/flowey_lib_common/src/run_cargo_nextest_run.rs @@ -62,9 +62,9 @@ pub mod build_params { pub no_default_features: bool, /// Whether to build tests with unstable `-Zpanic-abort-tests` flag pub unstable_panic_abort_tests: Option, - /// Build unit tests for the specified target + /// Build tests for the specified target pub target: target_lexicon::Triple, - /// Build unit tests with the specified cargo profile + /// Build tests with the specified cargo profile pub profile: CargoBuildProfile, /// Additional env vars set when building the tests pub extra_env: ReadVar, C>, @@ -77,7 +77,11 @@ pub enum NextestRunKind { /// Build and run tests in a single step. BuildAndRun(build_params::NextestBuildParams), /// Run tests from pre-built nextest archive file. - RunFromArchive(ReadVar), + RunFromArchive { + archive_file: ReadVar, + target: Option>, + nextest_bin: Option>, + }, } #[derive(Serialize, Deserialize)] @@ -133,6 +137,7 @@ enum RunKindDeps { RunFromArchive { archive_file: ReadVar, nextest_bin: ReadVar, + target: ReadVar, }, } @@ -144,6 +149,7 @@ impl FlowNode for Node { fn imports(ctx: &mut ImportCtx<'_>) { ctx.import::(); ctx.import::(); + ctx.import::(); ctx.import::(); } @@ -185,8 +191,7 @@ impl FlowNode for Node { NextestRunKind::BuildAndRun(params) => { let cargo_flags = ctx.reqv(crate::cfg_cargo_common_flags::Request::GetFlags); - let nextest_installed = - ctx.reqv(crate::download_cargo_nextest::Request::InstallWithCargo); + let nextest_installed = ctx.reqv(crate::install_cargo_nextest::Request); let rust_toolchain = ctx.reqv(crate::install_rust::Request::GetRustupToolchain); @@ -201,13 +206,22 @@ impl FlowNode for Node { cargo_flags, } } - NextestRunKind::RunFromArchive(archive_file) => { - let nextest_bin = - ctx.reqv(crate::download_cargo_nextest::Request::InstallStandalone); + NextestRunKind::RunFromArchive { + archive_file, + target, + nextest_bin, + } => { + let target = + target.unwrap_or(ReadVar::from_static(target_lexicon::Triple::host())); + + let nextest_bin = nextest_bin.unwrap_or_else(|| { + ctx.reqv(|v| crate::download_cargo_nextest::Request::Get(target.clone(), v)) + }); RunKindDeps::RunFromArchive { archive_file, nextest_bin, + target, } } }; @@ -233,6 +247,28 @@ impl FlowNode for Node { let config_file = rt.read(config_file); let mut with_env = rt.read(extra_env).unwrap_or_default(); + let target = match &run_kind_deps { + RunKindDeps::BuildAndRun { + params: build_params::NextestBuildParams { target, .. }, + .. + } => target.clone(), + RunKindDeps::RunFromArchive { target, .. } => rt.read(target.clone()), + }; + + let windows_via_wsl2 = crate::_util::running_in_wsl(rt) + && matches!( + target.operating_system, + target_lexicon::OperatingSystem::Windows + ); + + let maybe_convert_path = |path: PathBuf| -> PathBuf { + if windows_via_wsl2 { + crate::_util::wslpath::linux_to_win(path) + } else { + path + } + }; + // first things first - determine if junit is supported by // the profile, and if so, where the output if going to be. let junit_path = { @@ -320,10 +356,13 @@ impl FlowNode for Node { RunKindDeps::RunFromArchive { archive_file, nextest_bin, + target: _, } => { let build_args = vec![ "--archive-file".into(), - rt.read(archive_file).display().to_string(), + maybe_convert_path(rt.read(archive_file)) + .display() + .to_string(), ]; let nextest_invocation = NextestInvocation::Standalone { @@ -354,15 +393,20 @@ impl FlowNode for Node { "--profile".into(), (&nextest_profile).into(), "--config-file".into(), - config_file.into(), + maybe_convert_path(config_file).into(), "--workspace-remap".into(), - (&working_dir).into(), + maybe_convert_path(working_dir.clone()).into(), ]); for (tool, config_file) in tool_config_files { args.extend([ "--tool-config-file".into(), - format!("{}:{}", tool, rt.read(config_file).display()).into(), + format!( + "{}:{}", + tool, + maybe_convert_path(rt.read(config_file)).display() + ) + .into(), ]); } @@ -454,16 +498,30 @@ impl FlowNode for Node { let arg_string = || { args.iter() - .map(|v| v.to_string_lossy().to_string()) + .map(|v| format!("'{}'", v.to_string_lossy())) .collect::>() .join(" ") }; - let env_string = with_env - .iter() - .map(|(k, v)| format!("{k}='{v}'")) - .collect::>() - .join(" "); + let env_string = match target.operating_system { + target_lexicon::OperatingSystem::Windows => with_env + .iter() + .map(|(k, v)| format!("$env:{k}='{v}'")) + .collect::>() + .join("; "), + _ => with_env + .iter() + .map(|(k, v)| format!("{k}='{v}'")) + .collect::>() + .join(" "), + }; + + log::info!( + "{} {} {}", + env_string, + argv0.to_string_lossy(), + arg_string() + ); // nextest has meaningful exit codes that we want to parse. // @@ -473,12 +531,6 @@ impl FlowNode for Node { // exit code of the process. // // So we have to use the raw process API instead. - log::info!( - "$ {} {} {}", - env_string, - argv0.to_string_lossy(), - arg_string() - ); let mut command = std::process::Command::new(&argv0); command.args(&args).envs(with_env).current_dir(&working_dir); @@ -704,9 +756,11 @@ impl RunKindDeps { RunKindDeps::RunFromArchive { archive_file, nextest_bin, + target, } => RunKindDeps::RunFromArchive { archive_file: archive_file.claim(ctx), nextest_bin: nextest_bin.claim(ctx), + target: target.claim(ctx), }, } } diff --git a/flowey/flowey_lib_hvlite/src/_jobs/cfg_versions.rs b/flowey/flowey_lib_hvlite/src/_jobs/cfg_versions.rs index a3980d8d14..73df13e2f3 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/cfg_versions.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/cfg_versions.rs @@ -24,7 +24,7 @@ pub const MDBOOK_ADMONISH: &str = "1.18.0"; pub const MDBOOK_MERMAID: &str = "0.14.0"; pub const RUSTUP_TOOLCHAIN: &str = "1.87.0"; pub const MU_MSVM: &str = "24.0.4"; -pub const NEXTEST: &str = "0.9.74"; +pub const NEXTEST: &str = "0.9.96"; pub const NODEJS: &str = "18.x"; // N.B. Kernel version numbers for dev and stable branches are not directly // comparable. They originate from separate branches, and the fourth digit diff --git a/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_unit_tests_archive.rs b/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_unit_tests_archive.rs index 0f98908489..00d642e555 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_unit_tests_archive.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/consume_and_test_nextest_unit_tests_archive.rs @@ -48,6 +48,8 @@ impl SimpleFlowNode for Node { let results = ctx.reqv(|v| crate::test_nextest_unit_tests_archive::Request { nextest_archive_file: nextest_unit_test_archive, nextest_profile, + nextest_bin: None, + target: None, results: v, }); 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 195a8c911e..5bec46c689 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 @@ -111,24 +111,7 @@ impl SimpleFlowNode for Node { let disk_images_dir = ctx.reqv(crate::download_openvmm_vmm_tests_artifacts::Request::GetDownloadFolder); - // FUTURE: once we move away from the known_paths resolver, this will no - // longer be an ambient pre-run dependency. - let mu_msvm_arch = match target.architecture { - target_lexicon::Architecture::X86_64 => { - crate::download_uefi_mu_msvm::MuMsvmArch::X86_64 - } - target_lexicon::Architecture::Aarch64(_) => { - crate::download_uefi_mu_msvm::MuMsvmArch::Aarch64 - } - arch => anyhow::bail!("unsupported arch {arch}"), - }; - let pre_run_deps = vec![ - ctx.reqv(|v| crate::init_openvmm_magicpath_uefi_mu_msvm::Request { - arch: mu_msvm_arch, - done: v, - }), - ctx.reqv(crate::init_hyperv_tests::Request), - ]; + let pre_run_deps = vec![ctx.reqv(crate::init_hyperv_tests::Request)]; let (test_log_path, get_test_log_path) = ctx.new_var(); @@ -152,6 +135,10 @@ impl SimpleFlowNode for Node { nextest_archive_file: nextest_vmm_tests_archive, nextest_profile, nextest_filter_expr, + nextest_working_dir: None, + nextest_config_file: None, + nextest_bin: None, + target: None, extra_env, pre_run_deps, results: v, 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 new file mode 100644 index 0000000000..1e74577c75 --- /dev/null +++ b/flowey/flowey_lib_hvlite/src/_jobs/local_build_and_run_nextest_vmm_tests.rs @@ -0,0 +1,741 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! A local-only job that builds everything needed and runs the VMM tests + +use crate::_jobs::local_build_igvm::non_production_build_igvm_tool_out_name; +use crate::build_nextest_vmm_tests::NextestVmmTestsArchive; +use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe; +use crate::build_openvmm_hcl::OpenvmmHclBuildProfile; +use crate::run_cargo_build::common::CommonArch; +use crate::run_cargo_build::common::CommonPlatform; +use crate::run_cargo_build::common::CommonProfile; +use crate::run_cargo_build::common::CommonTriple; +use flowey::node::prelude::*; +use std::collections::BTreeMap; +use std::str::FromStr; +use vmm_test_images::KnownTestArtifacts; + +#[derive(Serialize, Deserialize)] +pub enum VmmTestSelections { + Custom { + /// Custom test filter + filter: String, + /// Custom list of artifacts to download + artifacts: Vec, + /// Custom list of artifacts to build + build: BuildSelections, + }, + Flags(VmmTestSelectionFlags), +} + +/// Define VMM test selection flags +macro_rules! define_vmm_test_selection_flags { + { + $( + $name:ident: $default_value:literal, + )* + } => { + #[derive(Serialize, Deserialize, Clone)] + pub struct VmmTestSelectionFlags { + $( + pub $name: bool, + )* + } + + impl Default for VmmTestSelectionFlags { + fn default() -> Self { + Self { + $( + $name: $default_value, + )* + } + } + } + + impl FromStr for VmmTestSelectionFlags { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let mut flags = Self::default(); + for flag in s.split(',') { + let (sign, flag) = flag.split_at_checked(1).context("get sign")?; + let val = match sign { + "+" => true, + "-" => false, + s => anyhow::bail!("invalid sign: {s}"), + }; + match flag { + $( + stringify!($name) => flags.$name = val, + )* + f => anyhow::bail!("invalid flag: {f}"), + } + } + Ok(flags) + } + } + }; +} + +define_vmm_test_selection_flags! { + tdx: false, + hyperv_vbs: false, + windows: true, + ubuntu: true, + freebsd: true, + openhcl: true, + openvmm: true, + hyperv: true, + uefi: true, + pcat: true, + tmk: true, + guest_test_uefi: true, +} + +#[derive(Serialize, Deserialize)] +pub struct BuildSelections { + pub openhcl: bool, + pub openvmm: bool, + pub pipette_windows: bool, + pub pipette_linux: bool, + pub guest_test_uefi: bool, + pub tmks: bool, + pub tmk_vmm_windows: bool, + pub tmk_vmm_linux: bool, +} + +// Build everything we can by default +impl Default for BuildSelections { + fn default() -> Self { + Self { + openhcl: true, + openvmm: true, + pipette_windows: true, + pipette_linux: true, + guest_test_uefi: true, + tmks: true, + tmk_vmm_windows: true, + tmk_vmm_linux: true, + } + } +} + +flowey_request! { + pub struct Params { + pub target: CommonTriple, + + pub test_content_dir: Option, + + pub selections: VmmTestSelections, + + /// Use unstable WHP interfaces + pub unstable_whp: bool, + /// Release build instead of debug build + pub release: bool, + + /// Whether to run the tests or just build and archive + pub build_only: bool, + /// Copy extras to output dir (symbols, etc) + pub copy_extras: bool, + + pub done: WriteVar, + } +} + +new_simple_flow_node!(struct Node); + +impl SimpleFlowNode for Node { + type Request = Params; + + fn imports(ctx: &mut ImportCtx<'_>) { + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + ctx.import::(); + } + + fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { + let Params { + target, + test_content_dir, + selections, + unstable_whp, + release, + build_only, + copy_extras, + done, + } = request; + + let arch = target.common_arch().unwrap(); + let arch_tag = match arch { + CommonArch::X86_64 => "x64", + CommonArch::Aarch64 => "aarch64", + }; + let platform_tag = match target.as_triple().operating_system { + target_lexicon::OperatingSystem::Windows => "windows", + target_lexicon::OperatingSystem::Linux => "linux", + _ => unreachable!(), + }; + let test_label = format!("{arch_tag}-{platform_tag}-vmm-tests"); + + // Some things can only be built on linux + let linux_host = matches!(ctx.platform(), FlowPlatform::Linux(_)); + + let mut copy_to_dir = Vec::new(); + let extras_dir = Path::new("extras"); + + let (nextest_filter_expr, test_artifacts, mut build) = match selections { + VmmTestSelections::Custom { + filter, + artifacts, + build, + } => (filter, artifacts, build), + VmmTestSelections::Flags(VmmTestSelectionFlags { + tdx, + hyperv_vbs, + windows, + mut ubuntu, + freebsd, + mut openhcl, + openvmm, + hyperv, + uefi, + pcat, + tmk, + guest_test_uefi, + }) => { + let mut build = BuildSelections::default(); + + if !linux_host { + log::warn!( + "Cannot build for linux on windows. Skipping all tests that rely on linux artifacts." + ); + ubuntu = false; + openhcl = false; + } + + // VTL2 not supported on Linux + if !matches!( + target.as_triple().operating_system, + target_lexicon::OperatingSystem::Windows + ) { + openhcl = false; + } + + let mut filter = "all()".to_string(); + if !tdx { + filter.push_str(" & !test(tdx)"); + } + if !hyperv_vbs { + filter.push_str(" & !(test(vbs) & test(hyperv))"); + } + if !ubuntu { + filter.push_str(" & !test(ubuntu)"); + build.pipette_linux = false; + } + if !windows { + filter.push_str(" & !test(windows)"); + build.pipette_windows = false; + } + if !freebsd { + filter.push_str(" & !test(freebsd)"); + } + if !openhcl { + filter.push_str(" & !test(openhcl)"); + build.openhcl = false; + } + if !openvmm { + filter.push_str(" & !test(openvmm)"); + build.openvmm = false; + } + if !hyperv { + filter.push_str(" & !test(hyperv)"); + } + if !uefi { + filter.push_str(" & !test(uefi)"); + } + if !pcat { + filter.push_str(" & !test(pcat)"); + } + if !tmk { + filter.push_str(" & !test(tmk)"); + build.tmks = false; + build.tmk_vmm_linux = false; + build.tmk_vmm_windows = false; + } + if !guest_test_uefi { + filter.push_str(" & !test(guest_test_uefi)"); + build.guest_test_uefi = false; + } + + let artifacts = match arch { + CommonArch::X86_64 => { + let mut artifacts = Vec::new(); + + if windows && (tdx || hyperv_vbs) { + artifacts.push(KnownTestArtifacts::Gen2WindowsDataCenterCore2025X64Vhd); + } + if ubuntu { + artifacts.push(KnownTestArtifacts::Ubuntu2204ServerX64Vhd); + } + if windows { + artifacts.extend_from_slice(&[ + KnownTestArtifacts::Gen1WindowsDataCenterCore2022X64Vhd, + KnownTestArtifacts::Gen2WindowsDataCenterCore2022X64Vhd, + ]); + } + if freebsd { + artifacts.extend_from_slice(&[ + KnownTestArtifacts::FreeBsd13_2X64Vhd, + KnownTestArtifacts::FreeBsd13_2X64Iso, + ]); + } + if windows || ubuntu { + artifacts.push(KnownTestArtifacts::VmgsWithBootEntry); + } + + artifacts + } + CommonArch::Aarch64 => { + let mut artifacts = Vec::new(); + + if ubuntu { + artifacts.push(KnownTestArtifacts::Ubuntu2404ServerAarch64Vhd); + } + if windows { + artifacts.push(KnownTestArtifacts::Windows11EnterpriseAarch64Vhdx); + } + if windows || ubuntu { + artifacts.push(KnownTestArtifacts::VmgsWithBootEntry); + } + + artifacts + } + }; + + (filter, artifacts, build) + } + }; + + if !linux_host { + build.openhcl = false; + build.pipette_linux = false; + build.tmk_vmm_linux = false; + } + + let register_openhcl_igvm_files = build.openhcl.then(|| { + let openvmm_hcl_profile = if release { + OpenvmmHclBuildProfile::OpenvmmHclShip + } else { + OpenvmmHclBuildProfile::Debug + }; + let openhcl_recipies = match arch { + CommonArch::X86_64 => vec![ + OpenhclIgvmRecipe::X64, + OpenhclIgvmRecipe::X64Devkern, + OpenhclIgvmRecipe::X64TestLinuxDirect, + OpenhclIgvmRecipe::X64Cvm, + ], + CommonArch::Aarch64 => { + vec![ + OpenhclIgvmRecipe::Aarch64, + OpenhclIgvmRecipe::Aarch64Devkern, + ] + } + }; + let openhcl_extras_dir = extras_dir.join("openhcl"); + + let mut register_openhcl_igvm_files = Vec::new(); + for recipe in openhcl_recipies { + let (read_built_openvmm_hcl, built_openvmm_hcl) = ctx.new_var(); + let (read_built_openhcl_igvm, built_openhcl_igvm) = ctx.new_var(); + let (read_built_openhcl_boot, built_openhcl_boot) = ctx.new_var(); + let (read_built_sidecar, built_sidecar) = ctx.new_var(); + ctx.req(crate::build_openhcl_igvm_from_recipe::Request { + profile: openvmm_hcl_profile, + recipe: recipe.clone(), + custom_target: None, + built_openvmm_hcl, + built_openhcl_boot, + built_openhcl_igvm, + built_sidecar, + }); + + register_openhcl_igvm_files.push(read_built_openhcl_igvm.map(ctx, { + let recipe = recipe.clone(); + |x| (recipe, x) + })); + + if copy_extras { + let dir = + openhcl_extras_dir.join(non_production_build_igvm_tool_out_name(&recipe)); + copy_to_dir.extend_from_slice(&[ + ( + dir.clone(), + read_built_openvmm_hcl.map(ctx, |x| Some(x.bin)), + ), + (dir.clone(), read_built_openvmm_hcl.map(ctx, |x| x.dbg)), + ( + dir.clone(), + read_built_openhcl_boot.map(ctx, |x| Some(x.bin)), + ), + ( + dir.clone(), + read_built_openhcl_boot.map(ctx, |x| Some(x.dbg)), + ), + ( + dir.clone(), + read_built_sidecar.map(ctx, |x| x.map(|y| y.bin)), + ), + ( + dir.clone(), + read_built_sidecar.map(ctx, |x| x.map(|y| y.dbg)), + ), + ]); + } + } + let register_openhcl_igvm_files: ReadVar< + Vec<(OpenhclIgvmRecipe, crate::run_igvmfilegen::IgvmOutput)>, + > = ReadVar::transpose_vec(ctx, register_openhcl_igvm_files); + + register_openhcl_igvm_files + }); + + let register_openvmm = build.openvmm.then(|| { + let output = ctx.reqv(|v| crate::build_openvmm::Request { + params: crate::build_openvmm::OpenvmmBuildParams { + target: target.clone(), + profile: CommonProfile::from_release(release), + // FIXME: this relies on openvmm default features + features: if unstable_whp { + [crate::build_openvmm::OpenvmmFeature::UnstableWhp].into() + } else { + [].into() + }, + }, + openvmm: v, + }); + if copy_extras { + copy_to_dir.push(( + extras_dir.to_owned(), + output.map(ctx, |x| { + Some(match x { + crate::build_openvmm::OpenvmmOutput::WindowsBin { exe: _, pdb } => pdb, + crate::build_openvmm::OpenvmmOutput::LinuxBin { bin: _, dbg } => dbg, + }) + }), + )); + } + output + }); + + let register_pipette_windows = build.pipette_windows.then(|| { + let output = ctx.reqv(|v| crate::build_pipette::Request { + target: CommonTriple::Common { + arch, + platform: CommonPlatform::WindowsMsvc, + }, + profile: CommonProfile::from_release(release), + pipette: v, + }); + if copy_extras { + copy_to_dir.push(( + extras_dir.to_owned(), + output.map(ctx, |x| { + Some(match x { + crate::build_pipette::PipetteOutput::WindowsBin { exe: _, pdb } => pdb, + _ => unreachable!(), + }) + }), + )); + } + output + }); + + let register_pipette_linux_musl = build.pipette_linux.then(|| { + let output = ctx.reqv(|v| crate::build_pipette::Request { + target: CommonTriple::Common { + arch, + platform: CommonPlatform::LinuxMusl, + }, + profile: CommonProfile::from_release(release), + pipette: v, + }); + if copy_extras { + copy_to_dir.push(( + extras_dir.to_owned(), + output.map(ctx, |x| { + Some(match x { + crate::build_pipette::PipetteOutput::LinuxBin { bin: _, dbg } => dbg, + _ => unreachable!(), + }) + }), + )); + } + output + }); + + let register_guest_test_uefi = build.guest_test_uefi.then(|| { + let output = ctx.reqv(|v| crate::build_guest_test_uefi::Request { + arch, + profile: CommonProfile::from_release(release), + guest_test_uefi: v, + }); + if copy_extras { + copy_to_dir.push((extras_dir.to_owned(), output.map(ctx, |x| Some(x.efi)))); + copy_to_dir.push((extras_dir.to_owned(), output.map(ctx, |x| Some(x.pdb)))); + } + output + }); + + let register_tmks = build.tmks.then(|| { + let output = ctx.reqv(|v| crate::build_tmks::Request { + arch, + profile: CommonProfile::from_release(release), + tmks: v, + }); + if copy_extras { + copy_to_dir.push((extras_dir.to_owned(), output.map(ctx, |x| Some(x.dbg)))); + } + output + }); + + let register_tmk_vmm = build.tmk_vmm_windows.then(|| { + let output = ctx.reqv(|v| crate::build_tmk_vmm::Request { + target: CommonTriple::Common { + arch, + platform: CommonPlatform::WindowsMsvc, + }, + unstable_whp, + profile: CommonProfile::from_release(release), + tmk_vmm: v, + }); + if copy_extras { + copy_to_dir.push(( + extras_dir.to_owned(), + output.map(ctx, |x| { + Some(match x { + crate::build_tmk_vmm::TmkVmmOutput::WindowsBin { exe: _, pdb } => pdb, + _ => unreachable!(), + }) + }), + )); + } + output + }); + + let register_tmk_vmm_linux_musl = build.tmk_vmm_linux.then(|| { + let output = ctx.reqv(|v| crate::build_tmk_vmm::Request { + target: CommonTriple::Common { + arch, + platform: CommonPlatform::LinuxMusl, + }, + unstable_whp, + profile: CommonProfile::from_release(release), + tmk_vmm: v, + }); + if copy_extras { + copy_to_dir.push(( + extras_dir.to_owned(), + output.map(ctx, |x| { + Some(match x { + crate::build_tmk_vmm::TmkVmmOutput::LinuxBin { bin: _, dbg } => dbg, + _ => unreachable!(), + }) + }), + )); + } + output + }); + + let nextest_archive_file = ctx.reqv(|v| crate::build_nextest_vmm_tests::Request { + target: target.as_triple(), + profile: CommonProfile::from_release(release), + build_mode: crate::build_nextest_vmm_tests::BuildNextestVmmTestsMode::Archive(v), + }); + let nextest_archive_path = Path::new("vmm-tests-archive.tar.zst"); + copy_to_dir.push(( + nextest_archive_path.to_owned(), + nextest_archive_file.map(ctx, |x| Some(x.archive_file)), + )); + + if let Some(dir) = &test_content_dir { + let vmm_test_artifacts_dir = dir.join("images"); + fs_err::create_dir_all(&vmm_test_artifacts_dir)?; + ctx.req( + crate::download_openvmm_vmm_tests_artifacts::Request::CustomCacheDir( + vmm_test_artifacts_dir, + ), + ); + } + ctx.req(crate::download_openvmm_vmm_tests_artifacts::Request::Download(test_artifacts)); + let test_artifacts_dir = + ctx.reqv(crate::download_openvmm_vmm_tests_artifacts::Request::GetDownloadFolder); + + let test_content_dir = test_content_dir + .map(|x| ReadVar::from_static(x)) + .unwrap_or_else(|| { + ctx.emit_rust_stepv("creating new test content dir", |_| { + |_| Ok(std::env::current_dir()?.absolute()?) + }) + }); + + // use the copied archive file + let nextest_archive_path = nextest_archive_path.to_owned(); + let nextest_archive_file = test_content_dir.map(ctx, |dir| NextestVmmTestsArchive { + archive_file: dir.join(nextest_archive_path), + }); + + let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir); + + let nextest_config_file = Path::new("nextest.toml"); + let nextest_config_file_src = openvmm_repo_path.map(ctx, move |p| { + Some(p.join(".config").join(nextest_config_file)) + }); + copy_to_dir.push((nextest_config_file.to_owned(), nextest_config_file_src)); + let nextest_config_file = + test_content_dir.map(ctx, move |dir| dir.join(nextest_config_file)); + + let cargo_toml_file = Path::new("Cargo.toml"); + let repo_cargo_toml_file_src = + openvmm_repo_path.map(ctx, move |p| Some(p.join(cargo_toml_file))); + let crate_cargo_toml_file = PathBuf::new() + .join("vmm_tests") + .join("vmm_tests") + .join(cargo_toml_file); + let crate_cargo_toml_file_src = crate_cargo_toml_file.clone(); + let crate_cargo_toml_file_src = + openvmm_repo_path.map(ctx, move |p| Some(p.join(crate_cargo_toml_file_src))); + copy_to_dir.push((cargo_toml_file.to_owned(), repo_cargo_toml_file_src)); + copy_to_dir.push((crate_cargo_toml_file, crate_cargo_toml_file_src)); + + let target = target.as_triple(); + let nextest_bin = Path::new(match target.operating_system { + target_lexicon::OperatingSystem::Windows => "cargo-nextest.exe", + _ => "cargo-nextest", + }); + let nextest_bin_src = ctx + .reqv(|v| { + flowey_lib_common::download_cargo_nextest::Request::Get( + ReadVar::from_static(target.clone()), + v, + ) + }) + .map(ctx, Some); + copy_to_dir.push((nextest_bin.to_owned(), nextest_bin_src)); + let nextest_bin = test_content_dir.map(ctx, move |dir| dir.join(nextest_bin)); + + let extra_env = ctx.reqv(|v| crate::init_vmm_tests_env::Request { + test_content_dir: test_content_dir.clone(), + vmm_tests_target: target.clone(), + register_openvmm, + register_pipette_windows, + register_pipette_linux_musl, + register_guest_test_uefi, + register_tmks, + register_tmk_vmm, + register_tmk_vmm_linux_musl, + disk_images_dir: Some(test_artifacts_dir), + register_openhcl_igvm_files, + get_test_log_path: None, + get_env: v, + }); + + let copied_files = ctx.emit_rust_step("copy additional files to test content dir", |ctx| { + let copy_to_dir = copy_to_dir + .into_iter() + .map(|(dst, src)| (dst, src.claim(ctx))) + .collect::>(); + let test_content_dir = test_content_dir.clone().claim(ctx); + + move |rt| { + let test_content_dir = rt.read(test_content_dir); + + for (dst, src) in copy_to_dir { + let src = rt.read(src); + + if let Some(src) = src { + // TODO: specify files names for everything + let dst = if dst.starts_with("extras") { + test_content_dir + .join(dst) + .join(src.file_name().context("no file name")?) + } else { + test_content_dir.join(dst) + }; + + fs_err::create_dir_all(dst.parent().context("no parent")?)?; + fs_err::copy(src, dst)?; + } + } + + Ok(()) + } + }); + + if build_only { + ctx.emit_side_effect_step( + [ + nextest_archive_file.into_side_effect(), + nextest_config_file.into_side_effect(), + nextest_bin.into_side_effect(), + extra_env.into_side_effect(), + copied_files.into_side_effect(), + ], + [done], + ); + } else { + let results = ctx.reqv(|v| crate::test_nextest_vmm_tests_archive::Request { + nextest_archive_file, + nextest_profile: crate::run_cargo_nextest_run::NextestProfile::Default, + nextest_filter_expr: Some(nextest_filter_expr), + nextest_working_dir: Some(test_content_dir.clone()), + nextest_config_file: Some(nextest_config_file), + nextest_bin: Some(nextest_bin), + target: Some(ReadVar::from_static(target)), + extra_env, + pre_run_deps: vec![copied_files], + results: v, + }); + + let junit_xml = results.map(ctx, |r| r.junit_xml); + let published_results = + ctx.reqv(|v| flowey_lib_common::publish_test_results::Request { + junit_xml, + test_label, + attachments: BTreeMap::new(), // the logs are already there + output_dir: Some(test_content_dir), + done: v, + }); + + ctx.emit_rust_step("report test results", |ctx| { + published_results.claim(ctx); + done.claim(ctx); + + let results = results.clone().claim(ctx); + move |rt| { + let results = rt.read(results); + if results.all_tests_passed { + log::info!("all tests passed!"); + } else { + log::error!("encountered test failures."); + } + + Ok(()) + } + }); + } + + Ok(()) + } +} diff --git a/flowey/flowey_lib_hvlite/src/_jobs/mod.rs b/flowey/flowey_lib_hvlite/src/_jobs/mod.rs index a72b0d5306..46d7ca31ed 100644 --- a/flowey/flowey_lib_hvlite/src/_jobs/mod.rs +++ b/flowey/flowey_lib_hvlite/src/_jobs/mod.rs @@ -20,6 +20,7 @@ pub mod check_xtask_fmt; pub mod consolidate_and_publish_gh_pages; pub mod consume_and_test_nextest_unit_tests_archive; pub mod consume_and_test_nextest_vmm_tests_archive; +pub mod local_build_and_run_nextest_vmm_tests; pub mod local_build_igvm; pub mod local_custom_vmfirmwareigvm_dll; pub mod local_restore_packages; diff --git a/flowey/flowey_lib_hvlite/src/build_nextest_unit_tests.rs b/flowey/flowey_lib_hvlite/src/build_nextest_unit_tests.rs index 30bfa50edb..8864c05088 100644 --- a/flowey/flowey_lib_hvlite/src/build_nextest_unit_tests.rs +++ b/flowey/flowey_lib_hvlite/src/build_nextest_unit_tests.rs @@ -223,7 +223,7 @@ impl FlowNode for Node { features, no_default_features: false, unstable_panic_abort_tests, - target, + target: target.clone(), profile: match profile { CommonProfile::Release => CargoBuildProfile::Release, CommonProfile::Debug => CargoBuildProfile::Debug, @@ -242,6 +242,8 @@ impl FlowNode for Node { ), nextest_profile, nextest_filter_expr: None, + nextest_working_dir: None, + nextest_config_file: None, run_ignored: false, extra_env: None, pre_run_deps, diff --git a/flowey/flowey_lib_hvlite/src/build_nextest_vmm_tests.rs b/flowey/flowey_lib_hvlite/src/build_nextest_vmm_tests.rs index 866780593d..cfb927e3be 100644 --- a/flowey/flowey_lib_hvlite/src/build_nextest_vmm_tests.rs +++ b/flowey/flowey_lib_hvlite/src/build_nextest_vmm_tests.rs @@ -148,6 +148,8 @@ impl FlowNode for Node { ), nextest_profile, nextest_filter_expr, + nextest_working_dir: None, + nextest_config_file: None, run_ignored: false, extra_env: Some(extra_env), pre_run_deps: ambient_deps, 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 9d6acf5ea7..8bf9a036f7 100644 --- a/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs +++ b/flowey/flowey_lib_hvlite/src/init_vmm_tests_env.rs @@ -6,6 +6,7 @@ use crate::build_openhcl_igvm_from_recipe::OpenhclIgvmRecipe; use crate::download_openvmm_deps::OpenvmmDepsArch; +use crate::download_uefi_mu_msvm::MuMsvmArch; use flowey::node::prelude::*; use std::collections::BTreeMap; @@ -62,6 +63,7 @@ impl SimpleFlowNode for Node { fn imports(ctx: &mut ImportCtx<'_>) { ctx.import::(); ctx.import::(); + ctx.import::(); } fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { @@ -94,7 +96,15 @@ impl SimpleFlowNode for Node { crate::download_openvmm_deps::Request::GetLinuxTestKernel(openvmm_deps_arch, v) }); - let openvmm_repo_root = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir); + let mu_msvm_arch = match vmm_tests_target.architecture { + target_lexicon::Architecture::X86_64 => MuMsvmArch::X86_64, + target_lexicon::Architecture::Aarch64(_) => MuMsvmArch::Aarch64, + arch => anyhow::bail!("unsupported arch {arch}"), + }; + let uefi = ctx.reqv(|v| crate::download_uefi_mu_msvm::Request::GetMsvmFd { + arch: mu_msvm_arch, + msvm_fd: v, + }); ctx.emit_rust_step("setting up vmm_tests env", |ctx| { let test_content_dir = test_content_dir.claim(ctx); @@ -111,13 +121,13 @@ impl SimpleFlowNode for Node { let openhcl_igvm_files = register_openhcl_igvm_files.claim(ctx); let test_linux_initrd = test_linux_initrd.claim(ctx); let test_linux_kernel = test_linux_kernel.claim(ctx); - let openvmm_repo_root = openvmm_repo_root.claim(ctx); + let uefi = uefi.claim(ctx); move |rt| { let test_linux_initrd = rt.read(test_linux_initrd); let test_linux_kernel = rt.read(test_linux_kernel); + let uefi = rt.read(uefi); let test_content_dir = rt.read(test_content_dir); - let openvmm_repo_root = rt.read(openvmm_repo_root); let mut env = BTreeMap::new(); @@ -145,11 +155,6 @@ impl SimpleFlowNode for Node { path_as_string(&test_content_dir)?, ); - env.insert( - "VMM_TESTS_REPO_ROOT".into(), - path_as_string(&openvmm_repo_root)?, - ); - // use a subdir for test logs let test_log_dir = test_content_dir.join("test_results"); if !test_log_dir.exists() { @@ -292,6 +297,26 @@ impl SimpleFlowNode for Node { test_content_dir.join(arch_dir).join(kernel_file_name), )?; + let uefi_dir = test_content_dir + .join(format!( + "hyperv.uefi.mscoreuefi.{}.RELEASE", + match mu_msvm_arch { + MuMsvmArch::Aarch64 => "AARCH64", + MuMsvmArch::X86_64 => "x64", + } + )) + .join(format!( + "Msvm{}", + match mu_msvm_arch { + MuMsvmArch::Aarch64 => "AARCH64", + MuMsvmArch::X86_64 => "X64", + } + )) + .join("RELEASE_VS2022") + .join("FV"); + fs_err::create_dir_all(&uefi_dir)?; + fs_err::copy(uefi, uefi_dir.join("MSVM.fd"))?; + // debug log the current contents of the dir log::debug!("final folder content: {}", test_content_dir.display()); for entry in test_content_dir.read_dir()? { diff --git a/flowey/flowey_lib_hvlite/src/run_cargo_nextest_run.rs b/flowey/flowey_lib_hvlite/src/run_cargo_nextest_run.rs index 0fb110a874..299657d6a6 100644 --- a/flowey/flowey_lib_hvlite/src/run_cargo_nextest_run.rs +++ b/flowey/flowey_lib_hvlite/src/run_cargo_nextest_run.rs @@ -28,6 +28,10 @@ flowey_request! { pub nextest_profile: NextestProfile, /// Nextest test filter expression pub nextest_filter_expr: Option, + /// Nextest working directory (defaults to repo root) + pub nextest_working_dir: Option>, + /// Nextest configuration file (defaults to config in repo) + pub nextest_config_file: Option>, /// Whether to run ignored test pub run_ignored: bool, /// Additional env vars set when executing the tests. @@ -54,7 +58,7 @@ impl FlowNode for Node { fn emit(requests: Vec, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> { let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir); - let nextest_config_file = + let default_nextest_config_file = openvmm_repo_path.map(ctx, |p| p.join(".config").join("nextest.toml")); let base_env = [ @@ -72,8 +76,10 @@ impl FlowNode for Node { run_kind, nextest_profile, nextest_filter_expr, + nextest_working_dir, + nextest_config_file, run_ignored, - pre_run_deps, + mut pre_run_deps, results, extra_env, } in requests @@ -88,12 +94,26 @@ impl FlowNode for Node { ReadVar::from_static(base_env.clone()) }; + let working_dir = if let Some(nextest_working_dir) = nextest_working_dir { + pre_run_deps.push(openvmm_repo_path.clone().into_side_effect()); + nextest_working_dir + } else { + openvmm_repo_path.clone() + }; + + let config_file = if let Some(nextest_config_file) = nextest_config_file { + pre_run_deps.push(default_nextest_config_file.clone().into_side_effect()); + nextest_config_file + } else { + default_nextest_config_file.clone() + }; + ctx.req(flowey_lib_common::run_cargo_nextest_run::Request::Run( flowey_lib_common::run_cargo_nextest_run::Run { friendly_name, run_kind, - working_dir: openvmm_repo_path.clone(), - config_file: nextest_config_file.clone(), + working_dir, + config_file, tool_config_files: Vec::new(), nextest_profile: match nextest_profile { NextestProfile::Default => "default".into(), diff --git a/flowey/flowey_lib_hvlite/src/test_nextest_unit_tests_archive.rs b/flowey/flowey_lib_hvlite/src/test_nextest_unit_tests_archive.rs index 2975ba69ca..2eca837b3d 100644 --- a/flowey/flowey_lib_hvlite/src/test_nextest_unit_tests_archive.rs +++ b/flowey/flowey_lib_hvlite/src/test_nextest_unit_tests_archive.rs @@ -18,6 +18,10 @@ flowey_request! { pub nextest_archive_file: ReadVar, /// Nextest profile to use when running the source code pub nextest_profile: NextestProfile, + /// Optionally provide the nextest bin to use + pub nextest_bin: Option>, + /// Target for the tests to run on + pub target: Option>, /// Results of running the tests pub results: WriteVar, } @@ -36,6 +40,8 @@ impl FlowNode for Node { for Request { nextest_archive_file, nextest_profile, + nextest_bin, + target, results, } in requests { @@ -43,11 +49,16 @@ impl FlowNode for Node { ctx.req(crate::run_cargo_nextest_run::Request { friendly_name: "unit-tests".into(), - run_kind: flowey_lib_common::run_cargo_nextest_run::NextestRunKind::RunFromArchive( - nextest_archive, - ), + run_kind: + flowey_lib_common::run_cargo_nextest_run::NextestRunKind::RunFromArchive { + archive_file: nextest_archive, + target, + nextest_bin, + }, nextest_profile, nextest_filter_expr: None, + nextest_working_dir: None, + nextest_config_file: None, run_ignored: false, extra_env: None, pre_run_deps: Vec::new(), // FIXME: ensure all deps are installed diff --git a/flowey/flowey_lib_hvlite/src/test_nextest_vmm_tests_archive.rs b/flowey/flowey_lib_hvlite/src/test_nextest_vmm_tests_archive.rs index 18b9e7eacb..b57d0ec14a 100644 --- a/flowey/flowey_lib_hvlite/src/test_nextest_vmm_tests_archive.rs +++ b/flowey/flowey_lib_hvlite/src/test_nextest_vmm_tests_archive.rs @@ -21,6 +21,14 @@ flowey_request! { pub nextest_filter_expr: Option, /// Nextest profile to use when running the source code pub nextest_profile: NextestProfile, + /// Nextest working directory (defaults to repo root) + pub nextest_working_dir: Option>, + /// Nextest configuration file (defaults to config in repo) + pub nextest_config_file: Option>, + /// Optionally provide the nextest bin to use + pub nextest_bin: Option>, + /// Target for the tests to run on + pub target: Option>, /// Additional env vars set when executing the tests. pub extra_env: ReadVar>, /// Wait for specified side-effects to resolve before building / running @@ -46,6 +54,10 @@ impl SimpleFlowNode for Node { nextest_archive_file, nextest_filter_expr, nextest_profile, + nextest_working_dir, + nextest_config_file, + nextest_bin, + target, extra_env, mut pre_run_deps, results, @@ -69,11 +81,15 @@ impl SimpleFlowNode for Node { ctx.req(crate::run_cargo_nextest_run::Request { friendly_name: "vmm_tests".into(), - run_kind: flowey_lib_common::run_cargo_nextest_run::NextestRunKind::RunFromArchive( - nextest_archive, - ), + run_kind: flowey_lib_common::run_cargo_nextest_run::NextestRunKind::RunFromArchive { + archive_file: nextest_archive, + target, + nextest_bin, + }, nextest_profile, nextest_filter_expr, + nextest_working_dir, + nextest_config_file, run_ignored: false, extra_env: Some(extra_env), pre_run_deps, 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 d43219cb18..326224b131 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 @@ -419,25 +419,10 @@ fn test_log_directory_path(test_name: &str) -> anyhow::Result { } const VMM_TESTS_DIR_ENV_VAR: &str = "VMM_TESTS_CONTENT_DIR"; -const VMM_TESTS_REPO_ROOT_ENV_VAR: &str = "VMM_TESTS_REPO_ROOT"; /// Gets a path to the root of the repo. pub fn get_repo_root() -> anyhow::Result { - if let Ok(env_dir) = std::env::var(VMM_TESTS_REPO_ROOT_ENV_VAR) { - let repo_root = PathBuf::from(&env_dir); - - if repo_root.exists() { - Ok(repo_root) - } else { - anyhow::bail!( - "{} from {} does not exist", - repo_root.display(), - VMM_TESTS_REPO_ROOT_ENV_VAR - ) - } - } else { - Ok(Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")) - } + Ok(Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")) } /// Attempts to find the given file, first checking for it relative to the test