Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c7386cc
Minimal setup to use nextest API without CLI (uses bon::builder)
TobiasvdVen Sep 30, 2025
3fb5062
Remove bon
TobiasvdVen Oct 1, 2025
6264dd1
Add some documentation comments to things that are now public
TobiasvdVen Oct 1, 2025
b855116
Add CargoOptions::new()
TobiasvdVen Oct 1, 2025
1af82e1
Remove unnecessary whitespace change
TobiasvdVen Oct 1, 2025
c3e9be0
Fix a comment
TobiasvdVen Oct 1, 2025
20dbfcb
Added a newline, as I think that may be tripping up the lint step
TobiasvdVen Oct 1, 2025
23a8bc6
Revert changes to cargo-nextest lib.rs, pub changes must be achieve i…
TobiasvdVen Oct 2, 2025
9ffbc75
Remove apparent unnecessary dependencies on OutputContext
TobiasvdVen Oct 2, 2025
098839a
Remove unnecessary 'use OutputContext'
TobiasvdVen Oct 2, 2025
facbc2e
First draft moving cargo_cli.rs and updating ExpectedError accordingly
TobiasvdVen Oct 2, 2025
e314ba7
The same for 'acquire_graph_data'
TobiasvdVen Oct 2, 2025
4c0cc44
Extract duplication of TargetTripleError stderr writing
TobiasvdVen Oct 3, 2025
a063be8
Fix casing of CargoMetadataError
TobiasvdVen Oct 3, 2025
e8099e3
Reduce diff
TobiasvdVen Oct 3, 2025
ba7fde4
Reduce diff in dispatch.rs due to auto formatting
TobiasvdVen Oct 3, 2025
de04cf4
Bit more cleanup
TobiasvdVen Oct 3, 2025
a69d4fc
Fix some new warnings due to missing documentation
TobiasvdVen Oct 3, 2025
08e7d93
Some minor cleanup
TobiasvdVen Oct 3, 2025
023003d
Bit of extra whitespace
TobiasvdVen Oct 3, 2025
55d8015
Remove unnecessary clap optional features
TobiasvdVen Oct 3, 2025
6e50ae2
Fix typo in: nextest-runner/src/errors.rs
TobiasvdVen Oct 13, 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 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 3 additions & 84 deletions cargo-nextest/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

use crate::{
ExpectedError, Result, ReuseBuildKind,
cargo_cli::{CargoCli, CargoOptions},
output::{OutputContext, OutputOpts, OutputWriter, StderrStyles, should_redact},
reuse_build::{ArchiveFormatOpt, ReuseBuildOpts, make_path_mapper},
version,
Expand All @@ -16,6 +15,7 @@ use nextest_filtering::{Filterset, FiltersetKind, ParseContext};
use nextest_metadata::BuildPlatform;
use nextest_runner::{
RustcCli,
cargo_cli::{acquire_graph_data, CargoCli, CargoOptions},
cargo_config::{CargoConfigs, EnvironmentMap, TargetTriple},
config::{
core::{
Expand Down Expand Up @@ -56,7 +56,7 @@ use std::{
collections::BTreeSet,
env::VarError,
fmt,
io::{Cursor, Write},
io::Write,
sync::{Arc, OnceLock},
time::Duration,
};
Expand Down Expand Up @@ -780,41 +780,6 @@ impl From<RunIgnoredOpt> for RunIgnored {
}
}

impl CargoOptions {
fn compute_binary_list(
&self,
graph: &PackageGraph,
manifest_path: Option<&Utf8Path>,
output: OutputContext,
build_platforms: BuildPlatforms,
) -> Result<BinaryList> {
// Don't use the manifest path from the graph to ensure that if the user cd's into a
// particular crate and runs cargo nextest, then it behaves identically to cargo test.
let mut cargo_cli = CargoCli::new("test", manifest_path, output);

// Only build tests in the cargo test invocation, do not run them.
cargo_cli.add_args(["--no-run", "--message-format", "json-render-diagnostics"]);
cargo_cli.add_options(self);

let expression = cargo_cli.to_expression();
let output = expression
.stdout_capture()
.unchecked()
.run()
.map_err(|err| ExpectedError::build_exec_failed(cargo_cli.all_args(), err))?;
if !output.status.success() {
return Err(ExpectedError::build_failed(
cargo_cli.all_args(),
output.status.code(),
));
}

let test_binaries =
BinaryList::from_messages(Cursor::new(output.stdout), graph, build_platforms)?;
Ok(test_binaries)
}
}

/// Test runner options.
#[derive(Debug, Default, Args)]
#[command(next_help_heading = "Runner options")]
Expand Down Expand Up @@ -1276,7 +1241,6 @@ impl BaseApp {
cargo_opts.target_dir.as_deref(),
&cargo_opts,
&build_platforms,
output,
)?;
let graph = PackageGraph::from_json(&json)
.map_err(|err| ExpectedError::cargo_metadata_parse_error(None, err))?;
Expand Down Expand Up @@ -1588,7 +1552,6 @@ impl BaseApp {
None => Arc::new(self.cargo_opts.compute_binary_list(
self.graph(),
self.manifest_path.as_deref(),
self.output,
self.build_platforms.clone(),
)?),
};
Expand Down Expand Up @@ -2000,7 +1963,7 @@ impl ShowConfigCommand {
match self {
Self::Version {} => {
let mut cargo_cli =
CargoCli::new("locate-project", manifest_path.as_deref(), output);
CargoCli::new("locate-project", manifest_path.as_deref());
Copy link
Author

@TobiasvdVen TobiasvdVen Oct 4, 2025

Choose a reason for hiding this comment

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

Removal of OutputContext, was passed into CargoCli::new, where its only usage was in CargoCli::to_expression to add the --color argument.

Copy link
Member

Choose a reason for hiding this comment

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

Where is the --color argument passed in now?

Copy link
Author

@TobiasvdVen TobiasvdVen Oct 13, 2025

Choose a reason for hiding this comment

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

Well, it isn't 😄

The way I went about this is noticing that the --color argument seemed unnecessary, as all usages of to_expression (the only place --color was added) are in places that don't display their output - it is captured and only used internally (specifically in compute_binary_list and acquire_graph_data).

There is this comment in CargoCli, which may or may not be relevant: // --color is handled by runner.

I'm not entirely sure what it's intended to communicate, but I think it's explaining why there is no color field on the CargoCli struct. Maybe if we're no longer "internally" adding the --color argument it should become a field of CargoCli, so that users can set it? I'm not sure if CargoCli is intended for use beyond what it is currently used for, though, in which case it could be superfluous.

My concern would be that I missed some way in which it was relevant. Presumably it was there for a reason, at least at one point, but I was unable to identify one.

Copy link
Member

@sunshowers sunshowers Oct 13, 2025

Choose a reason for hiding this comment

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

Well -- when the user runs cargo nextest run, we run cargo test --no-run to build all the tests -- we want to forward the --color argument to that underlying cargo test execution. Totally fine with it being factored out to be easier to do, but we need to maintain this property.

Copy link
Author

Choose a reason for hiding this comment

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

Ah, I went and had another look, and I misinterpreted things before.

I thought the output of cargo test --no-run (in compute_binary_list) was being captured, in the sense that it was only used for parsing to a list of binaries. But its output is also sent to stdout, so --color is relevant.

My bad, I'll have to take a different approach.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks -- stdout (machine-readable output) is captured, but stderr (human-readable output) is passed through.

cargo_cli.add_args(["--workspace", "--message-format=plain"]);
let locate_project_output = cargo_cli
.to_expression()
Expand Down Expand Up @@ -2405,50 +2368,6 @@ pub enum BuildPlatformsOutputFormat {
Triple,
}

fn acquire_graph_data(
manifest_path: Option<&Utf8Path>,
target_dir: Option<&Utf8Path>,
cargo_opts: &CargoOptions,
build_platforms: &BuildPlatforms,
output: OutputContext,
) -> Result<String> {
let cargo_target_arg = build_platforms.to_cargo_target_arg()?;
let cargo_target_arg_str = cargo_target_arg.to_string();

let mut cargo_cli = CargoCli::new("metadata", manifest_path, output);
cargo_cli
.add_args(["--format-version=1", "--all-features"])
.add_args(["--filter-platform", &cargo_target_arg_str])
.add_generic_cargo_options(cargo_opts);

// We used to be able to pass in --no-deps in common cases, but that was (a) error-prone and (b)
// a bit harder to do given that some nextest config options depend on the graph. Maybe we could
// reintroduce it some day.

let mut expression = cargo_cli.to_expression().stdout_capture().unchecked();
// cargo metadata doesn't support "--target-dir" but setting the environment
// variable works.
if let Some(target_dir) = target_dir {
expression = expression.env("CARGO_TARGET_DIR", target_dir);
}
// Capture stdout but not stderr.
let output = expression
.run()
.map_err(|err| ExpectedError::cargo_metadata_exec_failed(cargo_cli.all_args(), err))?;
if !output.status.success() {
return Err(ExpectedError::cargo_metadata_failed(
cargo_cli.all_args(),
output.status,
));
}

let json = String::from_utf8(output.stdout).map_err(|error| {
let io_error = std::io::Error::new(std::io::ErrorKind::InvalidData, error);
ExpectedError::cargo_metadata_exec_failed(cargo_cli.all_args(), io_error)
})?;
Ok(json)
}

fn detect_build_platforms(
cargo_configs: &CargoConfigs,
target_cli_option: Option<&str>,
Expand Down
161 changes: 64 additions & 97 deletions cargo-nextest/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,10 @@ pub enum ExpectedError {
#[source]
err: std::io::Error,
},
#[error("cargo metadata exec failed")]
CargoMetadataExecFailed {
command: String,
err: std::io::Error,
},
#[error("cargo metadata failed")]
CargoMetadataFailed {
command: String,
exit_status: ExitStatus,
#[from]
err: CargoMetadataError
},
#[error("cargo locate-project exec failed")]
CargoLocateProjectExecFailed {
Expand Down Expand Up @@ -165,16 +160,10 @@ pub enum ExpectedError {
#[source]
err: CreateTestListError,
},
#[error("failed to execute build command")]
BuildExecFailed {
command: String,
#[source]
err: std::io::Error,
},
#[error("build failed")]
BuildFailed {
command: String,
exit_code: Option<i32>,
#[from]
err: CreateBinaryListError
},
#[error("building test runner failed")]
TestRunnerBuildError {
Expand Down Expand Up @@ -292,26 +281,6 @@ pub enum ExpectedError {
}

impl ExpectedError {
pub(crate) fn cargo_metadata_exec_failed(
command: impl IntoIterator<Item = impl AsRef<str>>,
err: std::io::Error,
) -> Self {
Self::CargoMetadataExecFailed {
command: shell_words::join(command),
err,
}
}

pub(crate) fn cargo_metadata_failed(
command: impl IntoIterator<Item = impl AsRef<str>>,
exit_status: ExitStatus,
) -> Self {
Self::CargoMetadataFailed {
command: shell_words::join(command),
exit_status,
}
}

pub(crate) fn cargo_locate_project_exec_failed(
command: impl IntoIterator<Item = impl AsRef<str>>,
err: std::io::Error,
Expand Down Expand Up @@ -365,26 +334,6 @@ impl ExpectedError {
Self::ExperimentalFeatureNotEnabled { name, var_name }
}

pub(crate) fn build_exec_failed(
command: impl IntoIterator<Item = impl AsRef<str>>,
err: std::io::Error,
) -> Self {
Self::BuildExecFailed {
command: shell_words::join(command),
err,
}
}

pub(crate) fn build_failed(
command: impl IntoIterator<Item = impl AsRef<str>>,
exit_code: Option<i32>,
) -> Self {
Self::BuildFailed {
command: shell_words::join(command),
exit_code,
}
}

pub(crate) fn filter_expression_parse_error(all_errors: Vec<FiltersetParseErrors>) -> Self {
Self::FiltersetParseError { all_errors }
}
Expand All @@ -404,8 +353,7 @@ impl ExpectedError {
/// Returns the exit code for the process.
pub fn process_exit_code(&self) -> i32 {
match self {
Self::CargoMetadataExecFailed { .. }
| Self::CargoMetadataFailed { .. }
Self::CargoMetadataFailed { .. }
| Self::CargoLocateProjectExecFailed { .. }
| Self::CargoLocateProjectFailed { .. } => NextestExitCode::CARGO_METADATA_FAILED,
Self::WorkspaceRootInvalidUtf8 { .. }
Expand Down Expand Up @@ -452,7 +400,7 @@ impl ExpectedError {
Self::FromMessagesError { .. } | Self::CreateTestListError { .. } => {
NextestExitCode::TEST_LIST_CREATION_FAILED
}
Self::BuildExecFailed { .. } | Self::BuildFailed { .. } => {
Self::BuildFailed { .. } => {
NextestExitCode::BUILD_FAILED
}
Self::SetupScriptFailed => NextestExitCode::SETUP_SCRIPT_FAILED,
Expand Down Expand Up @@ -485,20 +433,27 @@ impl ExpectedError {
error!("failed to get current executable");
Some(err as &dyn Error)
}
Self::CargoMetadataExecFailed { command, err } => {
error!("failed to execute `{}`", command.style(styles.bold));
Some(err as &dyn Error)
}
Self::CargoMetadataFailed {
command,
exit_status,
} => {
error!(
"command `{}` failed with {}",
command.style(styles.bold),
exit_status
);
None
Self::CargoMetadataFailed { err } => {
match err {
CargoMetadataError::CommandExecFail { command, err } => {
error!("failed to execute `{}`", command.style(styles.bold));
Some(err as &dyn Error)
}
CargoMetadataError::CommandFail {
command,
exit_status
} => {
error!(
"command `{}` failed with {}",
command.style(styles.bold),
exit_status
);
None
}
CargoMetadataError::TargetTriple { err } => {
display_target_triple_error_to_stderr(err)
}
}
}
Self::CargoLocateProjectExecFailed { command, err } => {
error!("failed to execute `{}`", command.style(styles.bold));
Expand Down Expand Up @@ -741,14 +696,7 @@ impl ExpectedError {
Some(err as &dyn Error)
}
Self::TargetTripleError { err } => {
if let Some(report) = err.source_report() {
// Display the miette report if available.
error!(target: "cargo_nextest::no_heading", "{report:?}");
None
} else {
error!("{err}");
err.source()
}
display_target_triple_error_to_stderr(err)
Copy link
Author

Choose a reason for hiding this comment

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

The original printing of TargetTripleError to stderr.

}
Self::RemapAbsoluteError {
arg_name,
Expand Down Expand Up @@ -822,25 +770,33 @@ impl ExpectedError {
error!("creating test list failed");
Some(err as &dyn Error)
}
Self::BuildExecFailed { command, err } => {
error!("failed to execute `{}`", command.style(styles.bold));
Some(err as &dyn Error)
}
Self::BuildFailed { command, exit_code } => {
let with_code_str = match exit_code {
Some(code) => {
format!(" with code {}", code.style(styles.bold))
}
None => "".to_owned(),
};
Self::BuildFailed { err } => {
match err {
CreateBinaryListError::CommandExecFail { command, error: _ } => {
error!("failed to execute `{}`", command.style(styles.bold));
Some(err as &dyn Error)
},
CreateBinaryListError::CommandFail { command, exit_code } => {
let with_code_str = match exit_code {
Some(code) => {
format!(" with code {}", code.style(styles.bold))
}
None => "".to_owned(),
};

error!(
"command `{}` exited{}",
command.style(styles.bold),
with_code_str,
);
error!(
"command `{}` exited{}",
command.style(styles.bold),
with_code_str,
);

None
None
},
CreateBinaryListError::FromMessages { error: _ } => {
error!("failed to parse messages generated by Cargo");
Some(err as &dyn Error)
}
}
}
Self::TestRunnerBuildError { err } => {
error!("failed to build test runner");
Expand Down Expand Up @@ -997,3 +953,14 @@ impl ExpectedError {
}
}
}

fn display_target_triple_error_to_stderr(err: &TargetTripleError) -> Option<&(dyn Error + 'static)> {
if let Some(report) = err.source_report() {
// Display the miette report if available.
error!(target: "cargo_nextest::no_heading", "{report:?}");
None
} else {
error!("{err}");
err.source()
}
}
1 change: 0 additions & 1 deletion cargo-nextest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

#![warn(missing_docs)]

mod cargo_cli;
mod dispatch;
#[cfg(unix)]
mod double_spawn;
Expand Down
Loading
Loading