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