Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,100 changes: 813 additions & 287 deletions .github/workflows/openvmm-ci.yaml

Large diffs are not rendered by default.

1,100 changes: 813 additions & 287 deletions .github/workflows/openvmm-pr-release.yaml

Large diffs are not rendered by default.

1,112 changes: 820 additions & 292 deletions .github/workflows/openvmm-pr.yaml

Large diffs are not rendered by default.

584 changes: 558 additions & 26 deletions ci-flowey/openvmm-pr.yaml

Large diffs are not rendered by default.

133 changes: 85 additions & 48 deletions flowey/flowey_hvlite/src/pipelines/checkin_gates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,9 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: String,
test_artifacts: Vec<KnownTestArtifacts>,
needs_prep_run: bool,
/// Number of shards to split test execution across.
/// None means no sharding (single job).
shard_count: Option<usize>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between None and Some(1)? Why not just make this a plain usize?

Copy link
Contributor Author

@justus-camp-microsoft justus-camp-microsoft Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issue I guess, the nextest invocation would just end up having --partition count:1/1 which is harmless. In any case, I'm not sure sharding this way ends up being worth it from testing

}

let standard_filter = {
Expand Down Expand Up @@ -1106,6 +1109,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr,
test_artifacts,
needs_prep_run,
shard_count,
} in [
VmmTestJobParams {
platform: FlowPlatform::Windows,
Expand All @@ -1117,6 +1121,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: standard_filter.clone(),
test_artifacts: standard_x64_test_artifacts.clone(),
needs_prep_run: false,
shard_count: Some(2),
},
VmmTestJobParams {
platform: FlowPlatform::Windows,
Expand All @@ -1128,6 +1133,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: cvm_filter("tdx"),
test_artifacts: cvm_x64_test_artifacts.clone(),
needs_prep_run: true,
shard_count: None,
},
VmmTestJobParams {
platform: FlowPlatform::Windows,
Expand All @@ -1139,6 +1145,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: standard_filter.clone(),
test_artifacts: standard_x64_test_artifacts.clone(),
needs_prep_run: false,
shard_count: Some(2),
},
VmmTestJobParams {
platform: FlowPlatform::Windows,
Expand All @@ -1150,6 +1157,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: cvm_filter("snp"),
test_artifacts: cvm_x64_test_artifacts,
needs_prep_run: true,
shard_count: None,
},
VmmTestJobParams {
platform: FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
Expand All @@ -1162,6 +1170,7 @@ impl IntoPipeline for CheckinGatesCli {
nextest_filter_expr: format!("{standard_filter} & !test(pcat_x64)"),
test_artifacts: standard_x64_test_artifacts,
needs_prep_run: false,
shard_count: None,
},
VmmTestJobParams {
platform: FlowPlatform::Windows,
Expand All @@ -1178,6 +1187,7 @@ impl IntoPipeline for CheckinGatesCli {
KnownTestArtifacts::VmgsWithBootEntry,
],
needs_prep_run: false,
shard_count: None,
},
] {
// Skip ARM64/CVM jobs entirely for ADO backend (no native ARM64/CVM pools in ADO)
Expand All @@ -1189,65 +1199,92 @@ impl IntoPipeline for CheckinGatesCli {
continue;
}
}
let test_label = format!("{label}-vmm-tests");

let pub_vmm_tests_results = if matches!(backend_hint, PipelineBackendHint::Local) {
Some(pipeline.new_artifact(&test_label).0)
} else {
None
let shard_range: Vec<Option<(usize, usize)>> = match shard_count {
Some(total) => (1..=total).map(|shard| Some((shard, total))).collect(),
None => vec![None],
};

let use_vmm_tests_archive = match target {
CommonTriple::X86_64_WINDOWS_MSVC => &use_vmm_tests_archive_windows_x86,
CommonTriple::X86_64_LINUX_GNU => &use_vmm_tests_archive_linux_x86,
CommonTriple::AARCH64_WINDOWS_MSVC => &use_vmm_tests_archive_windows_aarch64,
_ => unreachable!(),
};
for shard in shard_range {
let shard_suffix = match shard {
Some((s, t)) => format!(" ({s}/{t})"),
None => String::new(),
};
let job_label = format!("run vmm-tests [{label}]{shard_suffix}");
let test_label = match shard {
Some((s, t)) => format!("{label}-{s}of{t}-vmm-tests"),
None => format!("{label}-vmm-tests"),
};

let mut vmm_tests_run_job = pipeline
.new_job(platform, arch, format!("run vmm-tests [{label}]"))
.gh_set_pool(gh_pool);
let nextest_partition = shard.map(|(shard, total)| {
flowey_lib_common::gen_cargo_nextest_run_cmd::NextestPartition { shard, total }
});

// Only add ADO pool for x86_64 jobs (ARM not supported in ADO org)
if matches!(arch, FlowArch::X86_64) {
vmm_tests_run_job = vmm_tests_run_job.ado_set_pool(match platform {
FlowPlatform::Windows => {
crate::pipelines_shared::ado_pools::default_x86_pool(FlowPlatform::Windows)
}
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu) => {
crate::pipelines_shared::ado_pools::default_x86_pool(FlowPlatform::Linux(
FlowPlatformLinuxDistro::Ubuntu,
))
let pub_vmm_tests_results = if matches!(backend_hint, PipelineBackendHint::Local) {
Some(pipeline.new_artifact(&test_label).0)
} else {
None
};

let use_vmm_tests_archive = match target {
CommonTriple::X86_64_WINDOWS_MSVC => &use_vmm_tests_archive_windows_x86,
CommonTriple::X86_64_LINUX_GNU => &use_vmm_tests_archive_linux_x86,
CommonTriple::AARCH64_WINDOWS_MSVC => &use_vmm_tests_archive_windows_aarch64,
_ => unreachable!(),
};

let nextest_filter_expr = nextest_filter_expr.clone();
let test_artifacts = test_artifacts.clone();

let mut vmm_tests_run_job = pipeline
.new_job(platform, arch, job_label)
.gh_set_pool(gh_pool.clone());

// Only add ADO pool for x86_64 jobs (ARM not supported in ADO org)
if matches!(arch, FlowArch::X86_64) {
vmm_tests_run_job = vmm_tests_run_job.ado_set_pool(match platform {
FlowPlatform::Windows => {
crate::pipelines_shared::ado_pools::default_x86_pool(
FlowPlatform::Windows,
)
}
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu) => {
crate::pipelines_shared::ado_pools::default_x86_pool(
FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu),
)
}
_ => anyhow::bail!("unsupported platform"),
});
}

vmm_tests_run_job = vmm_tests_run_job.dep_on(|ctx| {
flowey_lib_hvlite::_jobs::consume_and_test_nextest_vmm_tests_archive::Params {
junit_test_label: test_label,
nextest_vmm_tests_archive: ctx.use_typed_artifact(use_vmm_tests_archive),
target: target.as_triple(),
nextest_profile:
flowey_lib_hvlite::run_cargo_nextest_run::NextestProfile::Ci,
nextest_filter_expr: Some(nextest_filter_expr),
nextest_partition,
dep_artifact_dirs: resolve_vmm_tests_artifacts(ctx),
test_artifacts,
fail_job_on_test_fail: true,
artifact_dir: pub_vmm_tests_results.map(|x| ctx.publish_artifact(x)),
needs_prep_run,
done: ctx.new_done_handle(),
}
_ => anyhow::bail!("unsupported platform"),
});
}

vmm_tests_run_job = vmm_tests_run_job.dep_on(|ctx| {
flowey_lib_hvlite::_jobs::consume_and_test_nextest_vmm_tests_archive::Params {
junit_test_label: test_label,
nextest_vmm_tests_archive: ctx.use_typed_artifact(use_vmm_tests_archive),
target: target.as_triple(),
nextest_profile: flowey_lib_hvlite::run_cargo_nextest_run::NextestProfile::Ci,
nextest_filter_expr: Some(nextest_filter_expr),
dep_artifact_dirs: resolve_vmm_tests_artifacts(ctx),
test_artifacts,
fail_job_on_test_fail: true,
artifact_dir: pub_vmm_tests_results.map(|x| ctx.publish_artifact(x)),
needs_prep_run,
done: ctx.new_done_handle(),
if let Some(vmm_tests_disk_cache_dir) = vmm_tests_disk_cache_dir.clone() {
vmm_tests_run_job = vmm_tests_run_job.dep_on(|_| {
flowey_lib_hvlite::download_openvmm_vmm_tests_artifacts::Request::CustomCacheDir(
vmm_tests_disk_cache_dir,
)
})
}
});

if let Some(vmm_tests_disk_cache_dir) = vmm_tests_disk_cache_dir.clone() {
vmm_tests_run_job = vmm_tests_run_job.dep_on(|_| {
flowey_lib_hvlite::download_openvmm_vmm_tests_artifacts::Request::CustomCacheDir(
vmm_tests_disk_cache_dir,
)
})
all_jobs.push(vmm_tests_run_job.finish());
}

all_jobs.push(vmm_tests_run_job.finish());
}

// test the flowey local backend by running cargo xflowey build-igvm on x64
Expand Down
19 changes: 19 additions & 0 deletions flowey/flowey_lib_common/src/gen_cargo_nextest_run_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ use flowey::node::prelude::*;
use std::collections::BTreeMap;
use std::ffi::OsString;

/// Nextest test partitioning configuration for sharding tests across
/// multiple CI jobs.
#[derive(Serialize, Deserialize, Clone)]
pub struct NextestPartition {
/// 1-indexed shard number
pub shard: usize,
/// Total number of shards
pub total: usize,
}

flowey_request! {
pub struct Request {
/// What kind of test run this is (inline build vs. from nextest archive).
Expand All @@ -28,6 +38,9 @@ flowey_request! {
pub run_ignored: bool,
/// Override fail fast setting
pub fail_fast: Option<bool>,
/// Nextest partition for sharding tests across multiple CI jobs.
/// When set, passes `--partition count:shard/total` to nextest.
pub nextest_partition: Option<NextestPartition>,
/// Additional env vars set when executing the tests.
pub extra_env: Option<ReadVar<BTreeMap<String, String>>>,
/// Additional command to run before the tests
Expand Down Expand Up @@ -91,6 +104,7 @@ impl FlowNode for Node {
nextest_filter_expr,
run_ignored,
fail_fast,
nextest_partition,
portable,
command,
} in requests
Expand Down Expand Up @@ -301,6 +315,11 @@ impl FlowNode for Node {
args.push(nextest_filter_expr.into());
}

if let Some(partition) = nextest_partition {
args.push("--partition".into());
args.push(format!("count:{}/{}", partition.shard, partition.total).into());
}

if run_ignored {
args.push("--run-ignored".into());
args.push("all".into());
Expand Down
4 changes: 4 additions & 0 deletions flowey/flowey_lib_common/src/run_cargo_nextest_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ pub struct Run {
pub nextest_filter_expr: Option<String>,
/// Whether to run ignored tests
pub run_ignored: bool,
/// Nextest partition for sharding tests across multiple CI jobs.
pub nextest_partition: Option<crate::gen_cargo_nextest_run_cmd::NextestPartition>,
/// Set rlimits to allow unlimited sized coredump file (if supported)
pub with_rlimit_unlimited_core_size: bool,
/// Additional env vars set when executing the tests.
Expand Down Expand Up @@ -151,6 +153,7 @@ impl FlowNode for Node {
extra_env,
with_rlimit_unlimited_core_size,
nextest_filter_expr,
nextest_partition,
run_ignored,
pre_run_deps,
results,
Expand Down Expand Up @@ -204,6 +207,7 @@ impl FlowNode for Node {
nextest_filter_expr,
run_ignored,
fail_fast,
nextest_partition,
extra_env,
extra_commands: None,
portable: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ flowey_request! {
pub nextest_profile: NextestProfile,
/// Nextest test filter expression.
pub nextest_filter_expr: Option<String>,
/// Nextest partition for sharding tests across multiple CI jobs.
pub nextest_partition: Option<flowey_lib_common::gen_cargo_nextest_run_cmd::NextestPartition>,
/// Artifacts corresponding to required test dependencies
pub dep_artifact_dirs: VmmTestsDepArtifacts,
/// Test artifacts to download
Expand Down Expand Up @@ -90,6 +92,7 @@ impl SimpleFlowNode for Node {
target,
nextest_profile,
nextest_filter_expr,
nextest_partition,
dep_artifact_dirs,
test_artifacts,
fail_job_on_test_fail,
Expand Down Expand Up @@ -222,6 +225,7 @@ impl SimpleFlowNode for Node {
nextest_working_dir: None,
nextest_config_file: None,
nextest_bin: None,
nextest_partition,
target: None,
extra_env,
pre_run_deps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ impl SimpleFlowNode for Node {
nextest_filter_expr: Some(nextest_filter_expr.clone()),
run_ignored: false,
fail_fast: None,
nextest_partition: None,
extra_env: Some(extra_env.clone()),
extra_commands: register_prep_steps
.clone()
Expand Down Expand Up @@ -1053,6 +1054,7 @@ impl SimpleFlowNode for Node {
nextest_working_dir: Some(ReadVar::from_static(test_content_dir.clone())),
nextest_config_file: Some(ReadVar::from_static(nextest_config_file)),
nextest_bin: Some(ReadVar::from_static(nextest_bin)),
nextest_partition: None,
target: Some(ReadVar::from_static(target_triple.clone())),
extra_env,
pre_run_deps: side_effects,
Expand Down
1 change: 1 addition & 0 deletions flowey/flowey_lib_hvlite/src/build_nextest_unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ impl FlowNode for Node {
nextest_filter_expr: None,
nextest_working_dir: None,
nextest_config_file: None,
nextest_partition: None,
run_ignored: false,
extra_env: None,
pre_run_deps,
Expand Down
1 change: 1 addition & 0 deletions flowey/flowey_lib_hvlite/src/build_nextest_vmm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ impl FlowNode for Node {
nextest_filter_expr,
nextest_working_dir: None,
nextest_config_file: None,
nextest_partition: None,
run_ignored: false,
extra_env: Some(extra_env),
pre_run_deps: ambient_deps,
Expand Down
4 changes: 4 additions & 0 deletions flowey/flowey_lib_hvlite/src/run_cargo_nextest_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ flowey_request! {
pub nextest_working_dir: Option<ReadVar<PathBuf>>,
/// Nextest configuration file (defaults to config in repo)
pub nextest_config_file: Option<ReadVar<PathBuf>>,
/// Nextest partition for sharding tests across multiple CI jobs.
pub nextest_partition: Option<flowey_lib_common::gen_cargo_nextest_run_cmd::NextestPartition>,
/// Whether to run ignored test
pub run_ignored: bool,
/// Additional env vars set when executing the tests.
Expand Down Expand Up @@ -85,6 +87,7 @@ impl FlowNode for Node {
nextest_filter_expr,
nextest_working_dir,
nextest_config_file,
nextest_partition,
run_ignored,
mut pre_run_deps,
results,
Expand Down Expand Up @@ -126,6 +129,7 @@ impl FlowNode for Node {
extra_env: Some(extra_env),
with_rlimit_unlimited_core_size: true,
nextest_filter_expr,
nextest_partition,
run_ignored,
pre_run_deps,
results,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ impl FlowNode for Node {
nextest_filter_expr: None,
nextest_working_dir: None,
nextest_config_file: None,
nextest_partition: None,
run_ignored: false,
extra_env: None,
pre_run_deps: Vec::new(), // FIXME: ensure all deps are installed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ flowey_request! {
pub nextest_config_file: Option<ReadVar<PathBuf>>,
/// Optionally provide the nextest bin to use
pub nextest_bin: Option<ReadVar<PathBuf>>,
/// Nextest partition for sharding tests across multiple CI jobs.
pub nextest_partition: Option<flowey_lib_common::gen_cargo_nextest_run_cmd::NextestPartition>,
/// Target for the tests to run on
pub target: Option<ReadVar<target_lexicon::Triple>>,
/// Additional env vars set when executing the tests.
Expand Down Expand Up @@ -57,6 +59,7 @@ impl SimpleFlowNode for Node {
nextest_working_dir,
nextest_config_file,
nextest_bin,
nextest_partition,
target,
extra_env,
mut pre_run_deps,
Expand Down Expand Up @@ -89,6 +92,7 @@ impl SimpleFlowNode for Node {
nextest_filter_expr,
nextest_working_dir,
nextest_config_file,
nextest_partition,
run_ignored: false,
extra_env: Some(extra_env),
pre_run_deps,
Expand Down