flowey_lib_hvlite/
build_nextest_unit_tests.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Build all cargo-nextest based unit-tests in the OpenVMM workspace.
5//!
6//! In the context of OpenVMM, we consider a "unit-test" to be any test which
7//! doesn't require any special dependencies (e.g: additional binaries, disk
8//! images, etc...), and can be run simply by invoking the test bin itself.
9
10use crate::download_lxutil::LxutilArch;
11use crate::init_openvmm_magicpath_openhcl_sysroot::OpenvmmSysrootArch;
12use crate::run_cargo_build::common::CommonArch;
13use crate::run_cargo_build::common::CommonPlatform;
14use crate::run_cargo_build::common::CommonProfile;
15use crate::run_cargo_build::common::CommonTriple;
16use crate::run_cargo_nextest_run::NextestProfile;
17use flowey::node::prelude::*;
18use flowey_lib_common::run_cargo_build::CargoBuildProfile;
19use flowey_lib_common::run_cargo_nextest_run::TestResults;
20use flowey_lib_common::run_cargo_nextest_run::build_params::FeatureSet;
21use flowey_lib_common::run_cargo_nextest_run::build_params::PanicAbortTests;
22use flowey_lib_common::run_cargo_nextest_run::build_params::TestPackages;
23
24/// Type-safe wrapper around a built nextest archive containing unit tests
25#[derive(Serialize, Deserialize)]
26pub struct NextestUnitTestArchive {
27    #[serde(rename = "unit_tests.tar.zst")]
28    pub archive_file: PathBuf,
29}
30
31/// Build mode to use when building the nextest unit tests
32#[derive(Serialize, Deserialize)]
33pub enum BuildNextestUnitTestMode {
34    /// Build and immediate run unit tests, side-stepping any intermediate
35    /// archiving steps.
36    ImmediatelyRun {
37        nextest_profile: NextestProfile,
38        results: WriteVar<TestResults>,
39    },
40    /// Build and archive the tests into a nextest archive file, which can then
41    /// be run via [`crate::test_nextest_unit_tests_archive`].
42    Archive(WriteVar<NextestUnitTestArchive>),
43}
44
45flowey_request! {
46    pub struct Request {
47        /// Build and run unit tests for the specified target
48        pub target: target_lexicon::Triple,
49        /// Build and run unit tests with the specified cargo profile
50        pub profile: CommonProfile,
51        /// Whether to build tests with unstable `-Zpanic-abort-tests` flag
52        pub unstable_panic_abort_tests: Option<PanicAbortTests>,
53        /// Build mode to use when building the nextest unit tests
54        pub build_mode: BuildNextestUnitTestMode,
55    }
56}
57
58new_flow_node!(struct Node);
59
60impl FlowNode for Node {
61    type Request = Request;
62
63    fn imports(ctx: &mut ImportCtx<'_>) {
64        ctx.import::<crate::build_xtask::Node>();
65        ctx.import::<crate::git_checkout_openvmm_repo::Node>();
66        ctx.import::<crate::init_openvmm_magicpath_openhcl_sysroot::Node>();
67        ctx.import::<crate::init_openvmm_magicpath_lxutil::Node>();
68        ctx.import::<crate::install_openvmm_rust_build_essential::Node>();
69        ctx.import::<crate::run_cargo_nextest_run::Node>();
70        ctx.import::<crate::init_cross_build::Node>();
71        ctx.import::<flowey_lib_common::run_cargo_nextest_archive::Node>();
72    }
73
74    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
75        let flowey_platform = ctx.platform();
76        let flowey_arch = ctx.arch();
77
78        let xtask_target = CommonTriple::Common {
79            arch: match flowey_arch {
80                FlowArch::X86_64 => CommonArch::X86_64,
81                FlowArch::Aarch64 => CommonArch::Aarch64,
82                arch => anyhow::bail!("unsupported arch {arch}"),
83            },
84            platform: match flowey_platform {
85                FlowPlatform::Windows => CommonPlatform::WindowsMsvc,
86                FlowPlatform::Linux(_) => CommonPlatform::LinuxGnu,
87                FlowPlatform::MacOs => CommonPlatform::MacOs,
88                platform => anyhow::bail!("unsupported platform {platform}"),
89            },
90        };
91        let xtask = ctx.reqv(|v| crate::build_xtask::Request {
92            target: xtask_target,
93            xtask: v,
94        });
95
96        let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
97
98        // building these packages in the OpenVMM repo requires installing some
99        // additional deps
100        let ambient_deps = vec![ctx.reqv(crate::install_openvmm_rust_build_essential::Request)];
101
102        let test_packages = ctx.emit_rust_stepv("determine unit test exclusions", |ctx| {
103            let xtask = xtask.claim(ctx);
104            let openvmm_repo_path = openvmm_repo_path.clone().claim(ctx);
105            move |rt| {
106                let xtask = rt.read(xtask);
107                let openvmm_repo_path = rt.read(openvmm_repo_path);
108
109                let mut exclude = [
110                    // TODO: document why we're skipping these first few crates
111                    // (I'm just cargo-cult copying these exclusions)
112                    "whp",
113                    "kvm",
114                    "openvmm",
115                    // Skip VMM tests, they get run in a different step.
116                    "vmm_tests",
117                    // Skip guest_test_uefi, as it's a no_std UEFI crate
118                    "guest_test_uefi",
119                    // Exclude various proc_macro crates, since they don't compile successfully
120                    // under --test with panic=abort targets.
121                    // https://github.com/rust-lang/cargo/issues/4336 is tracking this.
122                    //
123                    // In any case though, it's not like these crates should have unit tests
124                    // anyway.
125                    "inspect_derive",
126                    "mesh_derive",
127                    "save_restore_derive",
128                    "test_with_tracing_macro",
129                    "pal_async_test",
130                    "vmm_test_macros",
131                ]
132                .map(|x| x.to_string())
133                .to_vec();
134
135                // Exclude fuzz crates, since there libfuzzer-sys doesn't play
136                // nice with unit tests
137                {
138                    let xtask_bin = match xtask {
139                        crate::build_xtask::XtaskOutput::LinuxBin { bin, dbg: _ } => bin,
140                        crate::build_xtask::XtaskOutput::WindowsBin { exe, pdb: _ } => exe,
141                    };
142
143                    let sh = xshell::Shell::new()?;
144                    sh.change_dir(openvmm_repo_path);
145                    let output = xshell::cmd!(sh, "{xtask_bin} fuzz list --crates").output()?;
146                    let output = String::from_utf8(output.stdout)?;
147
148                    let fuzz_crates = output.trim().split('\n').map(|s| s.to_owned());
149                    exclude.extend(fuzz_crates);
150                }
151
152                Ok(TestPackages::Workspace { exclude })
153            }
154        });
155
156        for Request {
157            target,
158            profile,
159            unstable_panic_abort_tests,
160            build_mode,
161        } in requests
162        {
163            let mut pre_run_deps = ambient_deps.clone();
164
165            let (sysroot_arch, lxutil_arch) = match target.architecture {
166                target_lexicon::Architecture::X86_64 => {
167                    (OpenvmmSysrootArch::X64, LxutilArch::X86_64)
168                }
169                target_lexicon::Architecture::Aarch64(_) => {
170                    (OpenvmmSysrootArch::Aarch64, LxutilArch::Aarch64)
171                }
172                arch => anyhow::bail!("unsupported arch {arch}"),
173            };
174
175            // lxutil is required by certain build.rs scripts.
176            //
177            // FUTURE: should prob have a way to opt-out of this lxutil build
178            // script requirement in non-interactive scenarios?
179            pre_run_deps.push(ctx.reqv(|v| crate::init_openvmm_magicpath_lxutil::Request {
180                arch: lxutil_arch,
181                done: v,
182            }));
183
184            // See comment in `crate::cargo_build` for why this is necessary.
185            //
186            // copied here since this node doesn't actually route through `cargo build`.
187            if matches!(target.environment, target_lexicon::Environment::Musl) {
188                pre_run_deps.push(
189                    ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
190                        arch: sysroot_arch,
191                        path: v,
192                    })
193                    .into_side_effect(),
194                );
195            }
196
197            // HACK: the following behavior has been cargo-culted from our old
198            // CI, and at some point, we should actually improve the testing
199            // story on windows, so that we can run with FeatureSet::All in CI.
200            //
201            // On windows, we can't run with all features, as many crates
202            // require openSSL for crypto, which isn't supported yet.
203            //
204            // Adding the the "ci" feature is also used to skip certain tests
205            // that fail in CI.
206            let features = if matches!(
207                target.operating_system,
208                target_lexicon::OperatingSystem::Windows
209            ) {
210                FeatureSet::Specific(vec!["ci".into()])
211            } else {
212                FeatureSet::All
213            };
214
215            let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
216                target: target.clone(),
217                injected_env: v,
218            });
219
220            let build_params =
221                flowey_lib_common::run_cargo_nextest_run::build_params::NextestBuildParams {
222                    packages: test_packages.clone(),
223                    features,
224                    no_default_features: false,
225                    unstable_panic_abort_tests,
226                    target: target.clone(),
227                    profile: match profile {
228                        CommonProfile::Release => CargoBuildProfile::Release,
229                        CommonProfile::Debug => CargoBuildProfile::Debug,
230                    },
231                    extra_env: injected_env,
232                };
233
234            match build_mode {
235                BuildNextestUnitTestMode::ImmediatelyRun {
236                    nextest_profile,
237                    results,
238                } => ctx.req(crate::run_cargo_nextest_run::Request {
239                    friendly_name: "unit-tests".into(),
240                    run_kind: flowey_lib_common::run_cargo_nextest_run::NextestRunKind::BuildAndRun(
241                        build_params,
242                    ),
243                    nextest_profile,
244                    nextest_filter_expr: None,
245                    nextest_working_dir: None,
246                    nextest_config_file: None,
247                    run_ignored: false,
248                    extra_env: None,
249                    pre_run_deps,
250                    results,
251                }),
252                BuildNextestUnitTestMode::Archive(unit_tests_archive) => {
253                    let archive_file =
254                        ctx.reqv(|v| flowey_lib_common::run_cargo_nextest_archive::Request {
255                            friendly_label: "unit-tests".into(),
256                            working_dir: openvmm_repo_path.clone(),
257                            build_params,
258                            pre_run_deps,
259                            archive_file: v,
260                        });
261
262                    ctx.emit_minor_rust_step("report built unit tests", |ctx| {
263                        let archive_file = archive_file.claim(ctx);
264                        let unit_tests = unit_tests_archive.claim(ctx);
265                        |rt| {
266                            let archive_file = rt.read(archive_file);
267                            rt.write(unit_tests, &NextestUnitTestArchive { archive_file });
268                        }
269                    });
270                }
271            }
272        }
273
274        Ok(())
275    }
276}