use crate::{
ExpectedError, ExtractOutputFormat, Result,
cargo_cli::{CargoCli, CargoOptions},
output::{OutputContext, StderrStyles},
};
use camino::{Utf8Path, Utf8PathBuf};
use itertools::Itertools;
use nextest_filtering::{Filterset, FiltersetKind, KnownGroups, ParseContext};
use nextest_runner::{
RustcCli,
cargo_config::{CargoConfigs, TargetTriple},
errors::TargetTripleError,
platform::{BuildPlatforms, HostPlatform, Platform, PlatformLibdir, TargetPlatform},
reporter::{
TestOutputErrorSlice,
events::{FinalRunStats, RunStatsFailureKind},
},
run_mode::NextestRunMode,
target_runner::{PlatformRunner, TargetRunner},
user_config::{UserConfig, UserConfigLocation},
};
use owo_colors::OwoColorize;
use std::io::Write;
use swrite::{SWrite, swrite};
use tracing::{debug, warn};
pub(super) 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);
let mut expression = cargo_cli.to_expression().stdout_capture().unchecked();
if let Some(target_dir) = target_dir {
expression = expression.env("CARGO_TARGET_DIR", target_dir);
}
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)
}
pub(super) fn detect_build_platforms(
cargo_configs: &CargoConfigs,
target_cli_option: Option<&str>,
) -> Result<BuildPlatforms, ExpectedError> {
let host = HostPlatform::detect(PlatformLibdir::from_rustc_stdout(
RustcCli::print_host_libdir().read(),
))?;
let triple_info = discover_target_triple(cargo_configs, target_cli_option, &host.platform)?;
let target = triple_info.map(|triple| {
let libdir =
PlatformLibdir::from_rustc_stdout(RustcCli::print_target_libdir(&triple).read());
TargetPlatform::new(triple, libdir)
});
Ok(BuildPlatforms { host, target })
}
pub(super) fn resolve_user_config(
host_platform: &Platform,
location: UserConfigLocation<'_>,
) -> Result<UserConfig, ExpectedError> {
UserConfig::for_host_platform(host_platform, location)
.map_err(|e| ExpectedError::UserConfigError { err: Box::new(e) })
}
pub(super) fn discover_target_triple(
cargo_configs: &CargoConfigs,
target_cli_option: Option<&str>,
host_platform: &Platform,
) -> Result<Option<TargetTriple>, TargetTripleError> {
TargetTriple::find(cargo_configs, target_cli_option, host_platform).inspect(|v| {
if let Some(triple) = v {
debug!(
"using target triple `{}` defined by `{}`; {}",
triple.platform.triple_str(),
triple.source,
triple.location,
);
} else {
debug!("no target triple found, assuming no cross-compilation");
}
})
}
pub(super) fn runner_for_target(
cargo_configs: &CargoConfigs,
build_platforms: &BuildPlatforms,
styles: &StderrStyles,
) -> TargetRunner {
match TargetRunner::new(cargo_configs, build_platforms) {
Ok(runner) => {
if build_platforms.target.is_some() {
if let Some(runner) = runner.target() {
log_platform_runner("for the target platform, ", runner, styles);
}
if let Some(runner) = runner.host() {
log_platform_runner("for the host platform, ", runner, styles);
}
} else {
if let Some(runner) = runner.target() {
log_platform_runner("", runner, styles);
}
}
runner
}
Err(err) => {
warn_on_err("target runner", &err, styles);
TargetRunner::empty()
}
}
}
pub(super) fn log_platform_runner(prefix: &str, runner: &PlatformRunner, styles: &StderrStyles) {
let runner_command = shell_words::join(std::iter::once(runner.binary()).chain(runner.args()));
tracing::info!(
"{prefix}using target runner `{}` defined by {}",
runner_command.style(styles.bold),
runner.source()
)
}
pub(super) fn warn_on_err(thing: &str, err: &dyn std::error::Error, styles: &StderrStyles) {
let mut s = String::with_capacity(256);
swrite!(s, "could not determine {thing}: {err}");
let mut next_error = err.source();
while let Some(err) = next_error {
swrite!(s, "\n {} {}", "caused by:".style(styles.warning_text), err);
next_error = err.source();
}
warn!("{}", s);
}
pub(super) fn build_filtersets(
pcx: &ParseContext<'_>,
filter_set: &[String],
kind: FiltersetKind,
known_groups: &KnownGroups,
) -> Result<Vec<Filterset>> {
let (exprs, all_errors): (Vec<_>, Vec<_>) = filter_set
.iter()
.map(|input| Filterset::parse(input.clone(), pcx, kind, known_groups))
.partition_result();
if !all_errors.is_empty() {
Err(ExpectedError::filter_expression_parse_error(all_errors))
} else {
Ok(exprs)
}
}
pub(super) fn extract_slice_from_output<'a>(
stdout: &'a [u8],
stderr: &'a [u8],
) -> Option<TestOutputErrorSlice<'a>> {
TestOutputErrorSlice::heuristic_extract(Some(stdout), Some(stderr))
}
pub(super) fn display_output_slice(
output_slice: Option<TestOutputErrorSlice<'_>>,
output_format: ExtractOutputFormat,
) -> Result<()> {
use nextest_runner::reporter::highlight_end;
use quick_junit::XmlString;
match output_format {
ExtractOutputFormat::Raw => {
if let Some(kind) = output_slice
&& let Some(out) = kind.combined_subslice()
{
return std::io::stdout().write_all(out.slice).map_err(|err| {
ExpectedError::DebugExtractWriteError {
format: output_format,
err,
}
});
}
}
ExtractOutputFormat::JunitDescription => {
if let Some(kind) = output_slice {
println!("{}", XmlString::new(kind.to_string()).as_str());
}
}
ExtractOutputFormat::Highlight => {
if let Some(kind) = output_slice
&& let Some(out) = kind.combined_subslice()
{
let end = highlight_end(out.slice);
return std::io::stdout()
.write_all(&out.slice[..end])
.map_err(|err| ExpectedError::DebugExtractWriteError {
format: output_format,
err,
});
}
}
}
eprintln!("(no description found)");
Ok(())
}
pub(super) fn locate_workspace_root(
manifest_path: Option<&Utf8Path>,
output: OutputContext,
) -> Result<Utf8PathBuf> {
let mut cargo_cli = CargoCli::new("locate-project", manifest_path, output);
cargo_cli.add_args(["--workspace", "--message-format=plain"]);
let locate_project_output = cargo_cli
.to_expression()
.stdout_capture()
.unchecked()
.run()
.map_err(|error| {
ExpectedError::cargo_locate_project_exec_failed(cargo_cli.all_args(), error)
})?;
if !locate_project_output.status.success() {
return Err(ExpectedError::cargo_locate_project_failed(
cargo_cli.all_args(),
locate_project_output.status,
));
}
let workspace_root = String::from_utf8(locate_project_output.stdout)
.map_err(|err| ExpectedError::WorkspaceRootInvalidUtf8 { err })?;
let workspace_root = Utf8Path::new(workspace_root.trim_end());
workspace_root
.parent()
.map(|p| p.to_owned())
.ok_or_else(|| ExpectedError::WorkspaceRootInvalid {
workspace_root: workspace_root.to_owned(),
})
}
pub(super) fn final_stats_to_error(
stats: FinalRunStats,
mode: NextestRunMode,
rerun_available: bool,
) -> Option<ExpectedError> {
match stats {
FinalRunStats::Success => None,
FinalRunStats::NoTestsRun => Some(ExpectedError::NoTestsRun {
mode,
is_default: true,
}),
FinalRunStats::Cancelled {
kind: RunStatsFailureKind::SetupScript,
..
}
| FinalRunStats::Failed {
kind: RunStatsFailureKind::SetupScript,
} => Some(ExpectedError::setup_script_failed()),
FinalRunStats::Cancelled {
kind: RunStatsFailureKind::Test { .. },
..
}
| FinalRunStats::Failed {
kind: RunStatsFailureKind::Test { .. },
} => Some(ExpectedError::test_run_failed(rerun_available)),
}
}