1use crate::common::CommonArch;
11use crate::common::CommonProfile;
12use crate::common::CommonTriple;
13use crate::run_cargo_nextest_run::NextestProfile;
14use flowey::node::prelude::*;
15use flowey_lib_common::run_cargo_build::CargoFeatureSet;
16use flowey_lib_common::run_cargo_nextest_run::TestResults;
17use flowey_lib_common::run_cargo_nextest_run::build_params::NextestBuildParams;
18use flowey_lib_common::run_cargo_nextest_run::build_params::TestPackages;
19use std::collections::BTreeMap;
20
21#[derive(Serialize, Deserialize)]
23pub struct NextestUnitTestArchive {
24 #[serde(rename = "unit_tests.tar.zst")]
25 pub archive_file: PathBuf,
26}
27
28#[derive(Serialize, Deserialize)]
30pub enum BuildNextestUnitTestMode {
31 ImmediatelyRun {
34 nextest_profile: NextestProfile,
35 junit_test_label: String,
39 artifact_dir: Option<ReadVar<PathBuf>>,
42 results: WriteVar<Vec<TestResults>>,
44 publish_done: WriteVar<SideEffect>,
46 },
47 Archive(WriteVar<Vec<NextestUnitTestArchive>>),
50 Coverage {
52 packages: Vec<String>,
53 options: crate::collect_unit_test_coverage::Options,
54 done: WriteVar<SideEffect>,
55 },
56}
57
58flowey_request! {
59 pub struct Request {
60 pub target: target_lexicon::Triple,
62 pub profile: CommonProfile,
64 pub build_mode: BuildNextestUnitTestMode,
66 }
67}
68
69new_flow_node!(struct Node);
70
71impl FlowNode for Node {
72 type Request = Request;
73
74 fn imports(ctx: &mut ImportCtx<'_>) {
75 ctx.import::<crate::build_xtask::Node>();
76 ctx.import::<crate::git_checkout_openvmm_repo::Node>();
77 ctx.import::<crate::init_openvmm_magicpath_openhcl_sysroot::Node>();
78 ctx.import::<crate::install_openvmm_rust_build_essential::Node>();
79 ctx.import::<crate::run_cargo_nextest_run::Node>();
80 ctx.import::<crate::init_cross_build::Node>();
81 ctx.import::<crate::collect_unit_test_coverage::Node>();
82 ctx.import::<flowey_lib_common::run_cargo_nextest_archive::Node>();
83 ctx.import::<flowey_lib_common::publish_test_results::Node>();
84 }
85
86 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
87 let xtask_target = CommonTriple::Common {
88 arch: ctx.arch().try_into()?,
89 platform: ctx.platform().try_into()?,
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 let ambient_deps = vec![ctx.reqv(crate::install_openvmm_rust_build_essential::Request)];
101
102 for Request {
103 target,
104 profile,
105 build_mode,
106 } in requests
107 {
108 let test_packages = ctx.emit_rust_stepv("determine unit test exclusions", |ctx| {
109 let xtask = xtask.clone().claim(ctx);
110 let openvmm_repo_path = openvmm_repo_path.clone().claim(ctx);
111 move |rt| {
112 let xtask = rt.read(xtask);
113 let openvmm_repo_path = rt.read(openvmm_repo_path);
114
115 let mut exclude = [
116 "vmm_tests",
118 "cca_tests",
120 "guest_test_uefi",
122 "inspect_derive",
129 "mesh_derive",
130 "save_restore_derive",
131 "test_with_tracing_macro",
132 "pal_async_test",
133 "vmm_test_macros",
134 ]
135 .map(|x| x.to_string())
136 .to_vec();
137
138 {
141 let xtask_bin = match xtask {
142 crate::build_xtask::XtaskOutput::LinuxBin { bin, dbg: _ } => bin,
143 crate::build_xtask::XtaskOutput::WindowsBin { exe, pdb: _ } => exe,
144 };
145
146 rt.sh.change_dir(openvmm_repo_path);
147 let output =
148 flowey::shell_cmd!(rt, "{xtask_bin} fuzz list --crates").output()?;
149 let output = String::from_utf8(output.stdout)?;
150
151 let fuzz_crates = output.trim().split('\n').map(|s| s.to_owned());
152 exclude.extend(fuzz_crates);
153 }
154
155 if matches!(
157 target.operating_system,
158 target_lexicon::OperatingSystem::Windows
159 | target_lexicon::OperatingSystem::Darwin(_)
160 ) {
161 exclude.push("vtpm_util".into());
162 }
163
164 Ok(TestPackages::Workspace { exclude })
165 }
166 });
167
168 let mut pre_run_deps = ambient_deps.clone();
169
170 let packages = if let BuildNextestUnitTestMode::Coverage { packages, .. } = &build_mode
171 {
172 let packages = packages.clone();
173 ctx.emit_rust_stepv("select unit test coverage packages", |ctx| {
174 let test_packages = test_packages.clone().claim(ctx);
175 move |rt| {
176 let TestPackages::Workspace { exclude } = rt.read(test_packages) else {
177 unreachable!()
178 };
179 for package in &packages {
180 anyhow::ensure!(
181 !exclude.contains(package),
182 "{package} is excluded from the unit test suite"
183 );
184 }
185 Ok(TestPackages::Crates { crates: packages })
186 }
187 })
188 } else {
189 test_packages.clone()
190 };
191
192 let sysroot_arch = CommonArch::from_architecture(target.architecture)?;
193
194 if matches!(target.environment, target_lexicon::Environment::Musl) {
198 pre_run_deps.push(
199 ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
200 arch: sysroot_arch,
201 path: v,
202 })
203 .into_side_effect(),
204 );
205 }
206
207 let features = if matches!(
213 target.operating_system,
214 target_lexicon::OperatingSystem::Windows
215 ) {
216 CargoFeatureSet::Specific(vec!["ci".into()])
217 } else {
218 CargoFeatureSet::All
219 };
220
221 let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
222 target: target.clone(),
223 injected_env: v,
224 });
225
226 let base_build_params = NextestBuildParams {
227 packages,
228 features,
229 no_default_features: false,
230 target: target.clone(),
231 profile: profile.into(),
232 extra_env: injected_env,
233 };
234
235 let mut runs: Vec<(String, NextestBuildParams)> =
237 vec![("base".into(), base_build_params.clone())];
238
239 let mut crypto_feature_sets = vec![
248 ("native", CargoFeatureSet::Specific(vec!["native".into()])),
249 ("rust", CargoFeatureSet::Specific(vec!["rust".into()])),
250 ];
251 if matches!(
252 target.operating_system,
253 target_lexicon::OperatingSystem::Linux
254 ) {
255 crypto_feature_sets
256 .push(("openssl", CargoFeatureSet::Specific(vec!["openssl".into()])));
257 if matches!(target.environment, target_lexicon::Environment::Musl) {
259 crypto_feature_sets.push((
260 "symcrypt",
261 CargoFeatureSet::Specific(vec!["symcrypt".into()]),
262 ));
263 }
264 crypto_feature_sets.push(("all", CargoFeatureSet::All));
265 }
266 for (name, features) in crypto_feature_sets {
267 if matches!(
268 &build_mode,
269 BuildNextestUnitTestMode::Coverage { packages, .. }
270 if !packages.iter().any(|p| p == "crypto")
271 ) {
272 continue;
273 }
274 runs.push((
275 format!("crypto-{}", name),
276 NextestBuildParams {
277 packages: ReadVar::from_static(TestPackages::Crates {
278 crates: vec!["crypto".into()],
279 }),
280 features,
281 ..base_build_params.clone()
282 },
283 ));
284 }
285
286 match build_mode {
287 BuildNextestUnitTestMode::Coverage { options, done, .. } => {
288 ctx.req(crate::collect_unit_test_coverage::Request::Collect {
289 xtask: xtask.clone(),
290 runs,
291 options,
292 working_dir: openvmm_repo_path.clone(),
293 pre_run_deps,
294 done,
295 });
296 }
297 BuildNextestUnitTestMode::ImmediatelyRun {
298 nextest_profile,
299 junit_test_label,
300 artifact_dir,
301 results,
302 publish_done,
303 } => {
304 let test_results: Vec<_> = runs
305 .into_iter()
306 .map(|(friendly_name, build_params)| {
307 let test_label = format!("{junit_test_label}-{friendly_name}");
308 let r = ctx.reqv(|v| crate::run_cargo_nextest_run::Request {
309 friendly_name: test_label.clone(),
310 run_kind:
311 flowey_lib_common::run_cargo_nextest_run::NextestRunKind::BuildAndRun(
312 build_params,
313 ),
314 nextest_profile,
315 nextest_filter_expr: None,
316 nextest_working_dir: None,
317 nextest_config_file: None,
318 run_ignored: false,
319 extra_env: None,
320 pre_run_deps: pre_run_deps.clone(),
321 results: v,
322 });
323 (test_label, r)
324 })
325 .collect();
326
327 let publish_dones: Vec<_> = test_results
330 .iter()
331 .map(|(test_label, r)| {
332 ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
333 test_results: r.clone(),
334 test_label: test_label.clone(),
335 attachments: BTreeMap::new(),
336 output_dir: artifact_dir.clone(),
337 upload_logs_on_success: true,
338 done: v,
339 })
340 })
341 .collect();
342
343 ctx.emit_minor_rust_step("merge unit test results", |ctx| {
344 let test_results = test_results
345 .into_iter()
346 .map(|(_, r)| r.claim(ctx))
347 .collect::<Vec<_>>();
348 let results = results.claim(ctx);
349 move |rt| {
350 let flattened = test_results.into_iter().map(|t| rt.read(t)).collect();
351 rt.write(results, &flattened);
352 }
353 });
354
355 ctx.emit_side_effect_step(publish_dones, [publish_done]);
356 }
357 BuildNextestUnitTestMode::Archive(unit_tests_archive) => {
358 let archive_files: Vec<_> = runs
359 .into_iter()
360 .map(|(friendly_name, build_params)| {
361 ctx.reqv(|v| flowey_lib_common::run_cargo_nextest_archive::Request {
362 friendly_label: friendly_name,
363 working_dir: openvmm_repo_path.clone(),
364 build_params,
365 pre_run_deps: pre_run_deps.clone(),
366 archive_file: v,
367 })
368 })
369 .collect();
370
371 ctx.emit_minor_rust_step("report built unit tests", |ctx| {
372 let archive_files = archive_files.claim(ctx);
373 let unit_tests = unit_tests_archive.claim(ctx);
374 |rt| {
375 let flattened = archive_files
376 .into_iter()
377 .map(|t| NextestUnitTestArchive {
378 archive_file: rt.read(t),
379 })
380 .collect::<Vec<_>>();
381 rt.write(unit_tests, &flattened);
382 }
383 });
384 }
385 }
386 }
387
388 Ok(())
389 }
390}