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 "inspect_derive",
115 "mesh_derive",
116 "save_restore_derive",
117 "test_with_tracing_macro",
118 "pal_async_test",
119 "vmm_test_macros",
120 ]
121 .map(|x| x.to_string())
122 .to_vec();
123
124 {
127 let xtask_bin = match xtask {
128 crate::build_xtask::XtaskOutput::LinuxBin { bin, dbg: _ } => bin,
129 crate::build_xtask::XtaskOutput::WindowsBin { exe, pdb: _ } => exe,
130 };
131
132 rt.sh.change_dir(openvmm_repo_path);
133 let output =
134 flowey::shell_cmd!(rt, "{xtask_bin} fuzz list --crates").output()?;
135 let output = String::from_utf8(output.stdout)?;
136
137 let fuzz_crates = output.trim().split('\n').map(|s| s.to_owned());
138 exclude.extend(fuzz_crates);
139 }
140
141 Ok(TestPackages::Workspace { exclude })
142 }
143 });
144
145 for Request {
146 target,
147 profile,
148 build_mode,
149 } in requests
150 {
151 let mut pre_run_deps = ambient_deps.clone();
152
153 let sysroot_arch = CommonArch::from_architecture(target.architecture)?;
154
155 if matches!(target.environment, target_lexicon::Environment::Musl) {
159 pre_run_deps.push(
160 ctx.reqv(|v| crate::init_openvmm_magicpath_openhcl_sysroot::Request {
161 arch: sysroot_arch,
162 path: v,
163 })
164 .into_side_effect(),
165 );
166 }
167
168 let features = if matches!(
174 target.operating_system,
175 target_lexicon::OperatingSystem::Windows
176 ) {
177 CargoFeatureSet::Specific(vec!["ci".into()])
178 } else {
179 CargoFeatureSet::All
180 };
181
182 let injected_env = ctx.reqv(|v| crate::init_cross_build::Request {
183 target: target.clone(),
184 injected_env: v,
185 });
186
187 let base_build_params = NextestBuildParams {
188 packages: test_packages.clone(),
189 features,
190 no_default_features: false,
191 target: target.clone(),
192 profile: match profile {
193 CommonProfile::Release => CargoBuildProfile::Release,
194 CommonProfile::Debug => CargoBuildProfile::Debug,
195 },
196 extra_env: injected_env,
197 };
198
199 let mut runs: Vec<(String, NextestBuildParams)> =
201 vec![("base".into(), base_build_params.clone())];
202
203 let mut crypto_feature_sets = vec![
212 ("native", CargoFeatureSet::Specific(vec!["native".into()])),
213 ("rust", CargoFeatureSet::Specific(vec!["rust".into()])),
214 ];
215 if matches!(
216 target.operating_system,
217 target_lexicon::OperatingSystem::Linux
218 ) {
219 crypto_feature_sets
220 .push(("openssl", CargoFeatureSet::Specific(vec!["openssl".into()])));
221 if matches!(target.environment, target_lexicon::Environment::Musl) {
223 crypto_feature_sets.push((
224 "symcrypt",
225 CargoFeatureSet::Specific(vec!["symcrypt".into()]),
226 ));
227 }
228 crypto_feature_sets.push(("all", CargoFeatureSet::All));
229 }
230 for (name, features) in crypto_feature_sets {
231 runs.push((
232 format!("crypto-{}", name),
233 NextestBuildParams {
234 packages: ReadVar::from_static(TestPackages::Crates {
235 crates: vec!["crypto".into()],
236 }),
237 features,
238 ..base_build_params.clone()
239 },
240 ));
241 }
242
243 match build_mode {
244 BuildNextestUnitTestMode::ImmediatelyRun {
245 nextest_profile,
246 junit_test_label,
247 artifact_dir,
248 results,
249 publish_done,
250 } => {
251 let test_results: Vec<_> = runs
252 .into_iter()
253 .map(|(friendly_name, build_params)| {
254 let test_label = format!("{junit_test_label}-{friendly_name}");
255 let r = ctx.reqv(|v| crate::run_cargo_nextest_run::Request {
256 friendly_name: test_label.clone(),
257 run_kind:
258 flowey_lib_common::run_cargo_nextest_run::NextestRunKind::BuildAndRun(
259 build_params,
260 ),
261 nextest_profile,
262 nextest_filter_expr: None,
263 nextest_working_dir: None,
264 nextest_config_file: None,
265 run_ignored: false,
266 extra_env: None,
267 pre_run_deps: pre_run_deps.clone(),
268 results: v,
269 });
270 (test_label, r)
271 })
272 .collect();
273
274 let publish_dones: Vec<_> = test_results
277 .iter()
278 .map(|(test_label, r)| {
279 let junit_xml = r.clone().map(ctx, |t| t.junit_xml);
280 ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
281 junit_xml,
282 test_label: test_label.clone(),
283 attachments: BTreeMap::new(),
284 output_dir: artifact_dir.clone(),
285 done: v,
286 })
287 })
288 .collect();
289
290 ctx.emit_minor_rust_step("merge unit test results", |ctx| {
291 let test_results = test_results
292 .into_iter()
293 .map(|(_, r)| r.claim(ctx))
294 .collect::<Vec<_>>();
295 let results = results.claim(ctx);
296 move |rt| {
297 let flattened = test_results.into_iter().map(|t| rt.read(t)).collect();
298 rt.write(results, &flattened);
299 }
300 });
301
302 ctx.emit_side_effect_step(publish_dones, [publish_done]);
303 }
304 BuildNextestUnitTestMode::Archive(unit_tests_archive) => {
305 let archive_files: Vec<_> = runs
306 .into_iter()
307 .map(|(friendly_name, build_params)| {
308 ctx.reqv(|v| flowey_lib_common::run_cargo_nextest_archive::Request {
309 friendly_label: friendly_name,
310 working_dir: openvmm_repo_path.clone(),
311 build_params,
312 pre_run_deps: pre_run_deps.clone(),
313 archive_file: v,
314 })
315 })
316 .collect();
317
318 ctx.emit_minor_rust_step("report built unit tests", |ctx| {
319 let archive_files = archive_files.claim(ctx);
320 let unit_tests = unit_tests_archive.claim(ctx);
321 |rt| {
322 let flattened = archive_files
323 .into_iter()
324 .map(|t| NextestUnitTestArchive {
325 archive_file: rt.read(t),
326 })
327 .collect::<Vec<_>>();
328 rt.write(unit_tests, &flattened);
329 }
330 });
331 }
332 }
333 }
334
335 Ok(())
336 }
337}