Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3a064d5
initial work
Oct 6, 2025
b215a0c
fixes
Oct 6, 2025
92d4904
Add support for force published artifacts
Oct 6, 2025
233acfa
test out new force publish artifact step
Oct 6, 2025
ccbe79d
regen yaml
Oct 6, 2025
33381cf
Try new job
Oct 6, 2025
c46f87f
condense
Oct 6, 2025
e7826ba
Merge branch 'main' into add-verify-tests-step
Oct 6, 2025
693630a
Try a successful vmm_tests run
Oct 6, 2025
2c2795d
update pipeline files
Oct 7, 2025
6be7aa0
.
Oct 7, 2025
689dd30
fix typo
Oct 7, 2025
922d587
Prepare for review
Oct 7, 2025
51f8bb8
print debug logs
Oct 7, 2025
cb736c0
Handle both dirs and files gracefully
Oct 7, 2025
d1348e7
Remove logging statements
Oct 7, 2025
4c0d383
.
Oct 7, 2025
2962333
Proper refactor of publish_test_result.rs
Oct 7, 2025
3fa2e2e
More cleanup
Oct 7, 2025
044179e
Merge branch 'main' into add-verify-tests-step
Oct 8, 2025
e944546
Add file extension
Oct 8, 2025
2cd9f9d
Fix local vmm_tests
Oct 8, 2025
fcb1ed5
Scope changes in YAML
Oct 9, 2025
d522230
Address feedback
Oct 10, 2025
b4595ac
.
Oct 10, 2025
717272c
.
Oct 13, 2025
de175cb
Trim unnecessary optionals
Oct 13, 2025
2185870
Merge branch 'main' into add-verify-tests-step
Oct 14, 2025
4b8f415
Feedback
Oct 15, 2025
130e31e
Update comments and make the run_cargo_nextest_list command configurable
Oct 15, 2025
eea6cc1
Revert this change
Oct 15, 2025
2e1d8e2
Revert "Revert this change"
Oct 15, 2025
20e6604
.
Oct 15, 2025
98bfa23
Revert "."
Oct 17, 2025
2c1eccb
Merge branch 'main' into add-verify-tests-step
Oct 17, 2025
7c3499f
Merge branch 'main' into add-verify-tests-step
Oct 27, 2025
3a58241
address feedback
Oct 27, 2025
076177c
update base env according to main
Oct 27, 2025
6157e8c
Remove unused step
Oct 27, 2025
ccad775
Merge branch 'main' into add-verify-tests-step
Nov 3, 2025
e6d8b07
Allow skipping artifact publishing for closed source pipelines
Nov 3, 2025
553c0d6
Missing conversions for closed source fixup
Nov 3, 2025
3ab3b8b
Remove warnings from GH
Nov 4, 2025
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,082 changes: 607 additions & 475 deletions .github/workflows/openvmm-ci.yaml

Large diffs are not rendered by default.

1,082 changes: 607 additions & 475 deletions .github/workflows/openvmm-pr-release.yaml

Large diffs are not rendered by default.

1,086 changes: 609 additions & 477 deletions .github/workflows/openvmm-pr.yaml

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions flowey/flowey_lib_common/src/gen_cargo_nextest_list_cmd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Run cargo-nextest list subcommand.
use crate::gen_cargo_nextest_run_cmd;
use flowey::node::prelude::*;
use std::collections::BTreeMap;

flowey_request! {
pub struct Request {
/// What kind of test run this is (inline build vs. from nextest archive).
pub run_kind_deps: gen_cargo_nextest_run_cmd::RunKindDeps,
/// Working directory the test archive was created from.
pub working_dir: ReadVar<PathBuf>,
/// Path to `.config/nextest.toml`
pub config_file: ReadVar<PathBuf>,
/// Nextest profile to use when running the source code
pub nextest_profile: String,
/// Nextest test filter expression
pub nextest_filter_expr: Option<String>,
/// Additional env vars set when executing the tests.
pub extra_env: Option<ReadVar<BTreeMap<String, String>>>,
/// Generated cargo-nextest list command
pub command: WriteVar<gen_cargo_nextest_run_cmd::Command>,
}
}

new_flow_node!(struct Node);

impl FlowNode for Node {
type Request = Request;

fn imports(ctx: &mut ImportCtx<'_>) {
ctx.import::<gen_cargo_nextest_run_cmd::Node>();
}

fn emit(requests: Vec<Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
for Request {
run_kind_deps,
working_dir,
config_file,
nextest_profile,
nextest_filter_expr,
extra_env,
command: list_cmd,
} in requests
{
if let gen_cargo_nextest_run_cmd::RunKindDeps::BuildAndRun {
params: _,
nextest_installed: _,
rust_toolchain: _,
cargo_flags: _,
} = run_kind_deps
{
anyhow::bail!("BuildAndRun is not supported.")
}

let run_cmd = ctx.reqv(|v| gen_cargo_nextest_run_cmd::Request {
run_kind_deps,
working_dir,
config_file,
tool_config_files: Vec::new(), // Ignored
nextest_profile,
extra_env,
nextest_filter_expr,
run_ignored: true,
fail_fast: None,
portable: false,
command: v,
});

ctx.emit_rust_step("generate nextest list command", |ctx| {
let run_cmd = run_cmd.claim(ctx);
let list_cmd = list_cmd.claim(ctx);
move |rt| {
let mut cmd = rt.read(run_cmd);
cmd.args = cmd
.args
.into_iter()
.map(|arg| if arg == "run" { "list".into() } else { arg })
.collect();
cmd.args.extend(["--message-format".into(), "json".into()]);

rt.write(list_cmd, &cmd);
log::info!("Generated command: {}", cmd);

Ok(())
}
});
}
Ok(())
}
}
1 change: 1 addition & 0 deletions flowey/flowey_lib_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod download_mdbook_admonish;
pub mod download_mdbook_mermaid;
pub mod download_nuget_exe;
pub mod download_protoc;
pub mod gen_cargo_nextest_list_cmd;
pub mod gen_cargo_nextest_run_cmd;
pub mod gh_download_azure_key_vault_secret;
pub mod gh_latest_completed_workflow_id;
Expand Down
29 changes: 29 additions & 0 deletions flowey/flowey_lib_common/src/publish_test_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ flowey_request! {
/// To keep making forward progress, I've tweaked this node to accept an
/// optional... but this ain't great.
pub junit_xml: ReadVar<Option<PathBuf>>,
/// Path to the JSON output from `nextest list --json`.
pub nextest_list_json: Option<ReadVar<Option<PathBuf>>>,
/// Brief string used when publishing the test.
/// Must be unique to the pipeline.
pub test_label: String,
Expand Down Expand Up @@ -57,6 +59,7 @@ impl FlowNode for Node {

for Request {
junit_xml,
nextest_list_json,
test_label: label,
attachments,
output_dir,
Expand All @@ -77,6 +80,13 @@ impl FlowNode for Node {
let has_junit_xml = junit_xml.map(ctx, |p| p.is_some());
let junit_xml = junit_xml.map(ctx, |p| p.unwrap_or_default());

let has_nextest_list = nextest_list_json
.as_ref()
.map(|v| v.map(ctx, |p| p.is_some()));
let nextest_list_json = nextest_list_json
.as_ref()
.map(|v| v.map(ctx, |p| p.unwrap_or_default()));

match ctx.backend() {
FlowBackend::Ado => {
use_side_effects.push(ctx.reqv(|v| {
Expand All @@ -96,6 +106,25 @@ impl FlowNode for Node {
p.absolute().expect("invalid path").display().to_string()
});

if let Some(nextest_list_json) = nextest_list_json {
let nextest_list_json = nextest_list_json.map(ctx, |p| {
p.absolute().expect("invalid path").display().to_string()
});

let has_nextest_list = has_nextest_list.unwrap();

use_side_effects.push(
ctx.emit_gh_step(
format!("publish test results: {label} (nextest list JSON)"),
"actions/upload-artifact@v4",
)
.condition(has_nextest_list)
.with("name", format!("{label}-nextest-list-json"))
.with("path", nextest_list_json)
.finish(ctx),
);
}

// Note: usually flowey's built-in artifact publishing API
// should be used instead of this, but here we need to
// manually upload the artifact now so that it is still
Expand Down
114 changes: 103 additions & 11 deletions flowey/flowey_lib_common/src/run_cargo_nextest_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
use crate::gen_cargo_nextest_run_cmd::RunKindDeps;
use flowey::node::prelude::*;
use std::collections::BTreeMap;
use std::io::Write;
use std::process::ExitStatus;
use std::process::Stdio;
#[derive(Serialize, Deserialize)]
pub struct TestResults {
pub all_tests_passed: bool,
/// Path to JUnit XML output (if enabled by the nextest profile)
pub junit_xml: Option<PathBuf>,
/// Path to JSON file containing output of `cargo nextest list` command (if running in ci)
pub nextest_list_output: Option<PathBuf>,
}

/// Parameters related to building nextest tests
Expand Down Expand Up @@ -135,6 +140,7 @@ impl FlowNode for Node {
ctx.import::<crate::download_cargo_nextest::Node>();
ctx.import::<crate::install_cargo_nextest::Node>();
ctx.import::<crate::install_rust::Node>();
ctx.import::<crate::gen_cargo_nextest_list_cmd::Node>();
ctx.import::<crate::gen_cargo_nextest_run_cmd::Node>();
}

Expand Down Expand Up @@ -211,6 +217,30 @@ impl FlowNode for Node {
}
};

let list_cmd = match &run_kind_deps {
RunKindDeps::BuildAndRun { .. } => None,
RunKindDeps::RunFromArchive {
archive_file,
nextest_bin,
target,
} => {
let list_cmd = ctx.reqv(|v| crate::gen_cargo_nextest_list_cmd::Request {
run_kind_deps: RunKindDeps::RunFromArchive {
archive_file: archive_file.clone(),
nextest_bin: nextest_bin.clone(),
target: target.clone(),
},
working_dir: working_dir.clone(),
config_file: config_file.clone(),
nextest_profile: nextest_profile.clone(),
nextest_filter_expr: None, // Ignored
extra_env: extra_env.clone(),
command: v,
});
Some(list_cmd)
}
};

let cmd = ctx.reqv(|v| crate::gen_cargo_nextest_run_cmd::Request {
run_kind_deps,
working_dir: working_dir.clone(),
Expand All @@ -227,6 +257,7 @@ impl FlowNode for Node {

let (all_tests_passed_read, all_tests_passed_write) = ctx.new_var();
let (junit_xml_read, junit_xml_write) = ctx.new_var();
let (nextest_list_output_file_read, nextest_list_output_file_write) = ctx.new_var();

ctx.emit_rust_step(format!("run '{friendly_name}' nextest tests"), |ctx| {
pre_run_deps.claim(ctx);
Expand All @@ -235,12 +266,15 @@ impl FlowNode for Node {
let config_file = config_file.claim(ctx);
let all_tests_passed_var = all_tests_passed_write.claim(ctx);
let junit_xml_write = junit_xml_write.claim(ctx);
let nextest_list_output_file_write = nextest_list_output_file_write.claim(ctx);
let list_cmd = list_cmd.claim(ctx);
let cmd = cmd.claim(ctx);

move |rt| {
let working_dir = rt.read(working_dir);
let config_file = rt.read(config_file);
let cmd = rt.read(cmd);
let list_cmd = rt.read(list_cmd);

// first things first - determine if junit is supported by
// the profile, and if so, where the output if going to be.
Expand Down Expand Up @@ -310,17 +344,7 @@ impl FlowNode for Node {
// exit code of the process.
//
// So we have to use the raw process API instead.
let mut command = std::process::Command::new(&cmd.argv0);
command
.args(&cmd.args)
.envs(&cmd.env)
.current_dir(&working_dir);

let mut child = command.spawn().with_context(|| {
format!("failed to spawn '{}'", cmd.argv0.to_string_lossy())
})?;

let status = child.wait()?;
let (status, _stdout) = run_command(&cmd, &working_dir, false)?;

#[cfg(unix)]
if let Some((soft, hard)) = old_core_rlimits {
Expand Down Expand Up @@ -369,24 +393,52 @@ impl FlowNode for Node {

rt.write(junit_xml_write, &junit_xml);

// run the list command to get all tests in the executable
if let Some(list_cmd) = list_cmd {
let (status, stdout_opt) = run_command(&list_cmd, &working_dir, true)?;
anyhow::ensure!(status.success(), "failed to list tests in executable");
let stdout =
stdout_opt.context("missing stdout from nextest list command")?;
let nextest_list_json = get_nextest_list_output_from_stdout(&stdout)?;
if let Some(ref junit_xml_path) = junit_xml {
anyhow::ensure!(
junit_xml_path.is_file(),
"expected junit xml to exist at {:?}",
junit_xml_path
);
let containing_dir = junit_xml_path
.parent()
.context("junit xml has no parent")?
.to_path_buf();
let output_path = containing_dir.join("nextest_list.json");
let mut file = fs_err::File::create_new(output_path.clone())?;
file.write_all(nextest_list_json.to_string().as_bytes())?;

rt.write(nextest_list_output_file_write, &output_path.absolute()?);
}
}

Ok(())
}
});

ctx.emit_minor_rust_step("write results", |ctx| {
let all_tests_passed = all_tests_passed_read.claim(ctx);
let junit_xml = junit_xml_read.claim(ctx);
let nextest_list_output = nextest_list_output_file_read.claim(ctx);
let results = results.claim(ctx);

move |rt| {
let all_tests_passed = rt.read(all_tests_passed);
let junit_xml = rt.read(junit_xml);
let nextest_list_output = rt.read(nextest_list_output);

rt.write(
results,
&TestResults {
all_tests_passed,
junit_xml,
nextest_list_output: Some(nextest_list_output),
},
);
}
Expand Down Expand Up @@ -421,3 +473,43 @@ impl build_params::NextestBuildParams {
}
}
}

fn run_command(
cmd: &crate::gen_cargo_nextest_run_cmd::Command,
working_dir: &PathBuf,
capture_stdout: bool,
) -> anyhow::Result<(ExitStatus, Option<String>)> {
let mut command = std::process::Command::new(&cmd.argv0);
command
.args(&cmd.args)
.envs(&cmd.env)
.current_dir(working_dir);

if capture_stdout {
command.stdout(Stdio::piped());
} else {
command.stdout(Stdio::inherit());
}

let mut child = command
.spawn()
.with_context(|| format!("failed to spawn '{}'", cmd.argv0.to_string_lossy()))?;

if capture_stdout {
let output = child.wait_with_output()?;
let stdout_str = String::from_utf8_lossy(&output.stdout).into_owned();
Ok((output.status, Some(stdout_str)))
} else {
let status = child.wait()?;
Ok((status, None))
}
}

fn get_nextest_list_output_from_stdout(output: &str) -> anyhow::Result<serde_json::Value> {
for line in output.lines() {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line) {
return Ok(json_value);
}
}
anyhow::bail!("failed to find JSON output in nextest list command output");
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl SimpleFlowNode for Node {
let junit_xml = results.map(ctx, |r| r.junit_xml);
let reported_results = ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
junit_xml,
nextest_list_json: None,
test_label: junit_test_label,
attachments: BTreeMap::new(),
output_dir: artifact_dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ impl SimpleFlowNode for Node {
let junit_xml = results.map(ctx, |r| r.junit_xml);
let reported_results = ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
junit_xml,
nextest_list_json: None,
test_label: junit_test_label,
attachments: BTreeMap::new(),
output_dir: artifact_dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,10 @@ impl SimpleFlowNode for Node {
let test_log_path = test_log_path.depending_on(ctx, &results);

let junit_xml = results.map(ctx, |r| r.junit_xml);
let nextest_list_json = results.map(ctx, |r| r.nextest_list_output);
let reported_results = ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
junit_xml,
nextest_list_json: Some(nextest_list_json),
test_label: junit_test_label,
attachments: BTreeMap::from([("logs".to_string(), (test_log_path, false))]),
output_dir: artifact_dir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,7 @@ impl SimpleFlowNode for Node {
let published_results =
ctx.reqv(|v| flowey_lib_common::publish_test_results::Request {
junit_xml,
nextest_list_json: None,
test_label,
attachments: BTreeMap::new(), // the logs are already there
output_dir: Some(ReadVar::from_static(test_content_dir)),
Expand Down
Loading
Loading