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::CargoBuildProfile;
16use flowey_lib_common::run_cargo_build::CargoFeatureSet;
17use flowey_lib_common::run_cargo_nextest_run::TestResults;
18use flowey_lib_common::run_cargo_nextest_run::build_params::NextestBuildParams;
19use flowey_lib_common::run_cargo_nextest_run::build_params::TestPackages;
20use std::collections::BTreeMap;
21
22#[derive(Serialize, Deserialize)]
24pub struct NextestUnitTestArchive {
25 #[serde(rename = "unit_tests.tar.zst")]
26 pub archive_file: PathBuf,
27}
28
29#[derive(Serialize, Deserialize)]
31pub enum BuildNextestUnitTestMode {
32 ImmediatelyRun {
35 nextest_profile: NextestProfile,
36 junit_test_label: String,
40 artifact_dir: Option<ReadVar<PathBuf>>,
43 results: WriteVar<Vec<TestResults>>,
45 publish_done: WriteVar<SideEffect>,
47 },
48 Archive(WriteVar<Vec<NextestUnitTestArchive>>),
51}
52
53flowey_request! {
54 pub struct Request {
55 pub target: target_lexicon::Triple,
57 pub profile: CommonProfile,
59 pub build_mode: BuildNextestUnitTestMode,
61 }
62}
63
64new_flow_node!(struct Node);
65
66impl FlowNode for Node {
67 type Request = Request;
68
69 fn imports(ctx: &mut ImportCtx<'_>) {
70 ctx.import::<crate::build_xtask::Node>();
71 ctx.import::<crate::git_checkout_openvmm_repo::Node>();
72 ctx.import::<crate::init_openvmm_magicpath_openhcl_sysroot::Node>();
73 ctx.import::<crate::install_openvmm_rust_build_essential::Node>();
74 ctx.import::<crate::run_cargo_nextest_run::Node>();
75 ctx.import::<crate::init_cross_build::Node>();
76 ctx.import::<flowey_lib_common::run_cargo_nextest_archive::Node>();
77 ctx.import::<flowey_lib_common::publish_test_results::Node>();
78 }
79
80 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
81 let xtask_target = CommonTriple::Common {
82 arch: ctx.arch().try_into()?,
83 platform: ctx.platform().try_into()?,
84 };
85 let xtask = ctx.reqv(|v| crate::build_xtask::Request {
86 target: xtask_target,
87 xtask: v,
88 });
89
90 let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
91
92 let ambient_deps = vec![ctx.reqv(crate::install_openvmm_rust_build_essential::Request)];
95
96 let test_packages = ctx.emit_rust_stepv("determine unit test exclusions", |ctx| {
97 let xtask = xtask.claim(ctx);
98 let openvmm_repo_path = openvmm_repo_path.clone().claim(ctx);
99 move |rt| {
100 let xtask = rt.read(xtask);
101 let openvmm_repo_path = rt.read(openvmm_repo_path);
102
103 let mut exclude = [
104 "vmm_tests",
106 "guest_test_uefi",
108 "crypto",
110 "inspect_derive",
117 "mesh_derive",
118 "save_restore_derive",
119 "test_with_tracing_macro",
120 "pal_async_test",
121 "vmm_test_macros",
122 ]
123 .map(|x| x.to_string())
124 .to_vec();
125
126 {
129 let xtask_bin = match xtask {
130 crate::build_xtask::XtaskOutput::LinuxBin { bin, dbg: _ } => bin,
131 crate::build_xtask::XtaskOutput::WindowsBin { exe, pdb: _ } => exe,
132 };
133
134 rt.sh.change_dir(openvmm_repo_path);
135 let output =
136 flowey::shell_cmd!(rt, "{xtask_bin} fuzz list --crates").output()?;
137 let output = String::from_utf8(output.stdout)?;
138
139 let fuzz_crates = output.trim().split('\n').map(|s| s.to_owned());
140 exclude.extend(fuzz_crates);
141 }
142
143 Ok(TestPackages::Workspace { exclude })
144 }
145 });
146
147 for Request {
148 target,
149 profile,
150 build_mode,
151 } in requests
152 {
153 let mut pre_run_deps = ambient_deps.clone();
154
155 let sysroot_arch = CommonArch::from_architecture(target.architecture)?;
156
157 if matches!(target.environment, target_lexicon::Environment::Musl) {
161 pre_run_deps.push(
162 ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
163 arch: sysroot_arch,
164 path: v,
165 })
166 .into_side_effect(),
167 );
168 }
169
170 let features = if matches!(
176 target.operating_system,
177 target_lexicon::OperatingSystem::Windows
178 ) {
179 CargoFeatureSet::Specific(vec!["ci".into()])
180 } else {
181 CargoFeatureSet::All
182 };
183
184 let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
185 target: target.clone(),
186 injected_env: v,
187 });
188
189 let base_build_params = NextestBuildParams {
190 packages: test_packages.clone(),
191 features,
192 no_default_features: false,
193 target: target.clone(),
194 profile: match profile {
195 CommonProfile::Release => CargoBuildProfile::Release,
196 CommonProfile::Debug => CargoBuildProfile::Debug,
197 },
198 extra_env: injected_env,
199 };
200
201 let mut runs: Vec<(String, NextestBuildParams)> =
203 vec![("unit-tests".into(), base_build_params.clone())];
204
205 let mut crypto_feature_sets = vec![
214 ("none", CargoFeatureSet::None),
215 ("rust", CargoFeatureSet::Specific(vec!["rust".into()])),
216 ];
217 if matches!(
218 target.operating_system,
219 target_lexicon::OperatingSystem::Linux
220 ) {
221 crypto_feature_sets
222 .push(("openssl", CargoFeatureSet::Specific(vec!["openssl".into()])));
223 if matches!(target.environment, target_lexicon::Environment::Musl) {
225 crypto_feature_sets.push((
226 "symcrypt",
227 CargoFeatureSet::Specific(vec!["symcrypt".into()]),
228 ));
229 }
230 crypto_feature_sets.push(("all", CargoFeatureSet::All));
231 }
232 for (name, features) in crypto_feature_sets {
233 runs.push((
234 format!("unit-tests crypto ({})", name),
235 NextestBuildParams {
236 packages: ReadVar::from_static(TestPackages::Crates {
237 crates: vec!["crypto".into()],
238 }),
239 features,
240 ..base_build_params.clone()
241 },
242 ));
243 }
244
245 match build_mode {
246 BuildNextestUnitTestMode::ImmediatelyRun {
247 nextest_profile,
248 junit_test_label,
249 artifact_dir,
250 results,
251 publish_done,
252 } => {
253 let test_results: Vec<_> = runs
254 .into_iter()
255 .map(|(friendly_name, build_params)| {
256 let r = ctx.reqv(|v| crate::run_cargo_nextest_run::Request {
257 friendly_name: friendly_name.clone(),
258 run_kind:
259 flowey_lib_common::run_cargo_nextest_run::NextestRunKind::BuildAndRun(
260 build_params,
261 ),
262 nextest_profile,
263 nextest_filter_expr: None,
264 nextest_working_dir: None,
265 nextest_config_file: None,
266 run_ignored: false,
267 extra_env: None,
268 pre_run_deps: pre_run_deps.clone(),
269 results: v,
270 });
271 (friendly_name, r)
272 })
273 .collect();
274
275 let publish_dones: Vec<_> = test_results
278 .iter()
279 .map(|(friendly_name, r)| {
280 let junit_xml = r.clone().map(ctx, |t| t.junit_xml);
281 ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
282 junit_xml,
283 test_label: format!("{junit_test_label}-{friendly_name}"),
284 attachments: BTreeMap::new(),
285 output_dir: artifact_dir.clone(),
286 done: v,
287 })
288 })
289 .collect();
290
291 ctx.emit_minor_rust_step("merge unit test results", |ctx| {
292 let test_results = test_results
293 .into_iter()
294 .map(|(_, r)| r.claim(ctx))
295 .collect::<Vec<_>>();
296 let results = results.claim(ctx);
297 move |rt| {
298 let flattened = test_results.into_iter().map(|t| rt.read(t)).collect();
299 rt.write(results, &flattened);
300 }
301 });
302
303 ctx.emit_side_effect_step(publish_dones, [publish_done]);
304 }
305 BuildNextestUnitTestMode::Archive(unit_tests_archive) => {
306 let archive_files: Vec<_> = runs
307 .into_iter()
308 .map(|(friendly_name, build_params)| {
309 ctx.reqv(|v| flowey_lib_common::run_cargo_nextest_archive::Request {
310 friendly_label: friendly_name,
311 working_dir: openvmm_repo_path.clone(),
312 build_params,
313 pre_run_deps: pre_run_deps.clone(),
314 archive_file: v,
315 })
316 })
317 .collect();
318
319 ctx.emit_minor_rust_step("report built unit tests", |ctx| {
320 let archive_files = archive_files.claim(ctx);
321 let unit_tests = unit_tests_archive.claim(ctx);
322 |rt| {
323 let flattened = archive_files
324 .into_iter()
325 .map(|t| NextestUnitTestArchive {
326 archive_file: rt.read(t),
327 })
328 .collect::<Vec<_>>();
329 rt.write(unit_tests, &flattened);
330 }
331 });
332 }
333 }
334 }
335
336 Ok(())
337 }
338}