Skip to content

Commit 4633be8

Browse files
committed
[nextest-runner] improve data model for child error management
I realized that we need a central way to identify and manage all the different ways in which a test or script can error out. Add a `UnitErrorDescription` struct which is responsible for all this management.
1 parent 866587b commit 4633be8

File tree

13 files changed

+755
-636
lines changed

13 files changed

+755
-636
lines changed

cargo-nextest/src/dispatch.rs

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@ use nextest_runner::{
2929
platform::{BuildPlatforms, HostPlatform, PlatformLibdir, TargetPlatform},
3030
redact::Redactor,
3131
reporter::{
32-
heuristic_extract_description, highlight_end, structured, DescriptionKind,
33-
FinalStatusLevel, StatusLevel, TestOutputDisplay, TestReporterBuilder,
32+
highlight_end, structured, FinalStatusLevel, StatusLevel, TestOutputDisplay,
33+
TestOutputErrorSlice, TestReporterBuilder,
3434
},
3535
reuse_build::{archive_to_file, ArchiveReporter, PathMapper, ReuseBuildInfo},
36-
runner::{
37-
configure_handle_inheritance, ExecutionResult, FinalRunStats, RunStatsFailureKind,
38-
TestRunnerBuilder,
39-
},
36+
runner::{configure_handle_inheritance, FinalRunStats, RunStatsFailureKind, TestRunnerBuilder},
4037
show_config::{ShowNextestVersion, ShowTestGroupSettings, ShowTestGroups, ShowTestGroupsMode},
4138
signal::SignalHandlerKind,
4239
target_runner::{PlatformRunner, TargetRunner},
@@ -2083,8 +2080,8 @@ impl DebugCommand {
20832080
}
20842081
})?;
20852082

2086-
let description_kind = extract_description(&combined, &combined);
2087-
display_description_kind(description_kind, output_format)?;
2083+
let description_kind = extract_slice_from_output(&combined, &combined);
2084+
display_output_slice(description_kind, output_format)?;
20882085
} else {
20892086
let stdout = stdout
20902087
.map(|path| {
@@ -2111,8 +2108,8 @@ impl DebugCommand {
21112108
.transpose()?
21122109
.unwrap_or_default();
21132110

2114-
let description_kind = extract_description(&stdout, &stderr);
2115-
display_description_kind(description_kind, output_format)?;
2111+
let output_slice = extract_slice_from_output(&stdout, &stderr);
2112+
display_output_slice(output_slice, output_format)?;
21162113
}
21172114
}
21182115
}
@@ -2121,25 +2118,20 @@ impl DebugCommand {
21212118
}
21222119
}
21232120

2124-
fn extract_description<'a>(stdout: &'a [u8], stderr: &'a [u8]) -> Option<DescriptionKind<'a>> {
2125-
// The execution result is a generic one.
2126-
heuristic_extract_description(
2127-
ExecutionResult::Fail {
2128-
abort_status: None,
2129-
leaked: false,
2130-
},
2131-
Some(stdout),
2132-
Some(stderr),
2133-
)
2121+
fn extract_slice_from_output<'a>(
2122+
stdout: &'a [u8],
2123+
stderr: &'a [u8],
2124+
) -> Option<TestOutputErrorSlice<'a>> {
2125+
TestOutputErrorSlice::heuristic_extract(Some(stdout), Some(stderr))
21342126
}
21352127

2136-
fn display_description_kind(
2137-
kind: Option<DescriptionKind<'_>>,
2128+
fn display_output_slice(
2129+
output_slice: Option<TestOutputErrorSlice<'_>>,
21382130
output_format: ExtractOutputFormat,
21392131
) -> Result<()> {
21402132
match output_format {
21412133
ExtractOutputFormat::Raw => {
2142-
if let Some(kind) = kind {
2134+
if let Some(kind) = output_slice {
21432135
if let Some(out) = kind.combined_subslice() {
21442136
return std::io::stdout().write_all(out.slice).map_err(|err| {
21452137
ExpectedError::DebugExtractWriteError {
@@ -2151,15 +2143,12 @@ fn display_description_kind(
21512143
}
21522144
}
21532145
ExtractOutputFormat::JunitDescription => {
2154-
if let Some(kind) = kind {
2155-
println!(
2156-
"{}",
2157-
XmlString::new(kind.display_human().to_string()).as_str()
2158-
);
2146+
if let Some(kind) = output_slice {
2147+
println!("{}", XmlString::new(kind.to_string()).as_str());
21592148
}
21602149
}
21612150
ExtractOutputFormat::Highlight => {
2162-
if let Some(kind) = kind {
2151+
if let Some(kind) = output_slice {
21632152
if let Some(out) = kind.combined_subslice() {
21642153
let end = highlight_end(out.slice);
21652154
return std::io::stdout()

nextest-runner/src/errors.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,24 @@ impl<T: std::error::Error> ErrorList<T> {
355355
}
356356
}
357357

358-
/// Returns a 1 line summary of the error list.
359-
pub(crate) fn as_one_line_summary(&self) -> String {
358+
/// Returns a short summary of the error list.
359+
pub(crate) fn short_message(&self) -> String {
360360
if self.inner.len() == 1 {
361+
// This assumes that the error's `std::error::Error` implementation
362+
// provides a short message as its `Display` implementation. That
363+
// isn't always true -- in fact, `ErrorList`'s own `Display`
364+
// implementation is multi-line -- but it is true in practice for
365+
// all the errors we have in place right now. We may want to make
366+
// this better in the future.
361367
format!("{}", self.inner[0])
362368
} else {
363369
format!("{} errors occurred {}", self.inner.len(), self.description)
364370
}
365371
}
372+
373+
pub(crate) fn iter(&self) -> impl Iterator<Item = &T> {
374+
self.inner.iter()
375+
}
366376
}
367377

368378
impl<T: std::error::Error> fmt::Display for ErrorList<T> {

nextest-runner/src/reporter/aggregator.rs

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33

44
//! Metadata management.
55
6-
use super::TestEvent;
6+
use super::{TestEvent, UnitErrorDescription, UnitKind};
77
use crate::{
88
config::{EvaluatableProfile, NextestJunitConfig},
99
errors::{DisplayErrorChain, WriteEventError},
1010
list::TestInstance,
1111
reporter::TestEventKind,
1212
runner::{ExecuteStatus, ExecutionDescription, ExecutionResult},
13-
test_output::{ChildExecutionResult, ChildOutput},
13+
test_output::{ChildExecutionOutput, ChildOutput},
1414
};
1515
use camino::Utf8PathBuf;
1616
use debug_ignore::DebugIgnore;
@@ -150,7 +150,7 @@ impl<'cfg> MetadataJunit<'cfg> {
150150
.set_type(ty);
151151

152152
set_execute_status_props(
153-
rerun,
153+
&rerun.output,
154154
// Reruns are always failures.
155155
false,
156156
junit_store_failure_output,
@@ -176,7 +176,7 @@ impl<'cfg> MetadataJunit<'cfg> {
176176
|| (junit_store_failure_output && !is_success);
177177

178178
set_execute_status_props(
179-
main_status,
179+
&main_status.output,
180180
is_success,
181181
store_stdout_stderr,
182182
TestcaseOrRerun::Testcase(&mut testcase),
@@ -297,45 +297,34 @@ impl TestcaseOrRerun<'_> {
297297
}
298298

299299
fn set_execute_status_props(
300-
execute_status: &ExecuteStatus,
300+
exec_output: &ChildExecutionOutput,
301301
is_success: bool,
302302
store_stdout_stderr: bool,
303303
mut out: TestcaseOrRerun<'_>,
304304
) {
305-
match &execute_status.output {
306-
ChildExecutionResult::Output { output, errors } => {
307-
if !is_success {
308-
if let Some(errors) = errors {
309-
// Use the child errors as the message and description.
310-
out.set_message(errors.as_one_line_summary());
311-
out.set_description(DisplayErrorChain::new(errors).to_string());
312-
};
313-
let description = output.heuristic_extract_description(execute_status.result);
314-
if let Some(description) = description {
315-
out.set_description(description.display_human().to_string());
316-
}
317-
}
305+
// Currently we only aggregate test results, so always specify UnitKind::Test.
306+
let description = UnitErrorDescription::new(UnitKind::Test, exec_output);
307+
if let Some(errors) = description.all_error_list() {
308+
out.set_message(errors.short_message());
309+
out.set_description(DisplayErrorChain::new(errors).to_string());
310+
}
318311

319-
if store_stdout_stderr {
320-
match output {
321-
ChildOutput::Split(split) => {
322-
if let Some(stdout) = &split.stdout {
323-
out.set_system_out(stdout.as_str_lossy());
324-
}
325-
if let Some(stderr) = &split.stderr {
326-
out.set_system_err(stderr.as_str_lossy());
327-
}
312+
if !is_success && store_stdout_stderr {
313+
if let ChildExecutionOutput::Output { output, .. } = exec_output {
314+
match output {
315+
ChildOutput::Split(split) => {
316+
if let Some(stdout) = &split.stdout {
317+
out.set_system_out(stdout.as_str_lossy());
328318
}
329-
ChildOutput::Combined { output } => {
330-
out.set_system_out(output.as_str_lossy())
331-
.set_system_err("(stdout and stderr are combined)");
319+
if let Some(stderr) = &split.stderr {
320+
out.set_system_err(stderr.as_str_lossy());
332321
}
333322
}
323+
ChildOutput::Combined { output } => {
324+
out.set_system_out(output.as_str_lossy())
325+
.set_system_err("(stdout and stderr are combined)");
326+
}
334327
}
335328
}
336-
ChildExecutionResult::StartError(error) => {
337-
out.set_message(format!("Test execution failed: {error}"));
338-
out.set_description(DisplayErrorChain::new(error).to_string());
339-
}
340329
}
341330
}

nextest-runner/src/reporter/displayer.rs

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66
//! The main structure in this module is [`TestReporter`].
77
88
use super::{
9-
helpers::ByteSubslice, structured::StructuredReporter, CancelReason, DescriptionKind,
10-
TestEvent, TestEventKind,
9+
structured::StructuredReporter, ByteSubslice, CancelReason, TestEvent, TestEventKind,
10+
TestOutputErrorSlice, UnitKind,
1111
};
1212
use crate::{
1313
config::{CompiledDefaultFilter, EvaluatableProfile, ScriptId},
1414
errors::{DisplayErrorChain, WriteEventError},
1515
helpers::{plural, DisplayScriptInstance, DisplayTestInstance},
1616
list::{SkipCounts, TestInstance, TestList},
17-
reporter::{aggregator::EventAggregator, helpers::highlight_end},
17+
reporter::{aggregator::EventAggregator, helpers::highlight_end, UnitErrorDescription},
1818
runner::{
1919
AbortStatus, ExecuteStatus, ExecutionDescription, ExecutionResult, ExecutionStatuses,
2020
FinalRunStats, RetryData, RunStats, RunStatsFailureKind, SetupScriptExecuteStatus,
2121
},
22-
test_output::{ChildExecutionResult, ChildOutput, ChildSingleOutput},
22+
test_output::{ChildExecutionOutput, ChildOutput, ChildSingleOutput},
2323
};
2424
use bstr::ByteSlice;
2525
use debug_ignore::DebugIgnore;
@@ -1375,26 +1375,7 @@ impl<'a> TestReporterImpl<'a> {
13751375
writer: &mut dyn Write,
13761376
) -> io::Result<()> {
13771377
let spec = self.output_spec_for_script(script_id, command, args, run_status);
1378-
1379-
match &run_status.output {
1380-
ChildExecutionResult::Output { output, errors } => {
1381-
// Show execution failures first so that they show up
1382-
// immediately after the failure notification.
1383-
if let Some(errors) = errors {
1384-
let error_chain = DisplayErrorChain::new(errors);
1385-
writeln!(writer, "{}\n{error_chain}", spec.exec_fail_header)?;
1386-
}
1387-
1388-
self.write_child_output(&spec, output, None, writer)?;
1389-
}
1390-
1391-
ChildExecutionResult::StartError(error) => {
1392-
let error_chain = DisplayErrorChain::new(error);
1393-
writeln!(writer, "{}\n{error_chain}", spec.exec_fail_header)?;
1394-
}
1395-
}
1396-
1397-
writeln!(writer)
1378+
self.write_child_execution_output(&spec, &run_status.output, writer)
13981379
}
13991380

14001381
fn write_test_execute_status(
@@ -1405,26 +1386,40 @@ impl<'a> TestReporterImpl<'a> {
14051386
writer: &mut dyn Write,
14061387
) -> io::Result<()> {
14071388
let spec = self.output_spec_for_test(test_instance, run_status, is_retry);
1389+
self.write_child_execution_output(&spec, &run_status.output, writer)
1390+
}
1391+
1392+
fn write_child_execution_output(
1393+
&self,
1394+
spec: &ChildOutputSpec,
1395+
exec_output: &ChildExecutionOutput,
1396+
writer: &mut dyn Write,
1397+
) -> io::Result<()> {
1398+
match exec_output {
1399+
ChildExecutionOutput::Output {
1400+
output,
1401+
// result and errors are captured by desc.
1402+
result: _,
1403+
errors: _,
1404+
} => {
1405+
let desc = UnitErrorDescription::new(spec.kind, exec_output);
14081406

1409-
match &run_status.output {
1410-
ChildExecutionResult::Output { output, errors } => {
14111407
// Show execution failures first so that they show up
14121408
// immediately after the failure notification.
1413-
if let Some(errors) = errors {
1409+
if let Some(errors) = desc.exec_fail_error_list() {
14141410
let error_chain = DisplayErrorChain::new(errors);
14151411
writeln!(writer, "{}\n{error_chain}", spec.exec_fail_header)?;
14161412
}
14171413

1418-
let description = if self.styles.is_colorized {
1419-
output.heuristic_extract_description(run_status.result)
1414+
let highlight_slice = if self.styles.is_colorized {
1415+
desc.output_slice()
14201416
} else {
14211417
None
14221418
};
1423-
let spec = self.output_spec_for_test(test_instance, run_status, is_retry);
1424-
self.write_child_output(&spec, output, description, writer)?;
1419+
self.write_child_output(spec, output, highlight_slice, writer)?;
14251420
}
14261421

1427-
ChildExecutionResult::StartError(error) => {
1422+
ChildExecutionOutput::StartError(error) => {
14281423
let error_chain = DisplayErrorChain::new(error);
14291424
writeln!(writer, "{}\n{error_chain}", spec.exec_fail_header)?;
14301425
}
@@ -1437,7 +1432,7 @@ impl<'a> TestReporterImpl<'a> {
14371432
&self,
14381433
spec: &ChildOutputSpec,
14391434
output: &ChildOutput,
1440-
description: Option<DescriptionKind<'_>>,
1435+
highlight_slice: Option<TestOutputErrorSlice<'_>>,
14411436
mut writer: &mut dyn Write,
14421437
) -> io::Result<()> {
14431438
match output {
@@ -1453,7 +1448,7 @@ impl<'a> TestReporterImpl<'a> {
14531448
let mut indent_writer = IndentWriter::new(spec.output_indent, writer);
14541449
self.write_test_single_output_with_description(
14551450
stdout,
1456-
description.and_then(|d| d.stdout_subslice()),
1451+
highlight_slice.and_then(|d| d.stdout_subslice()),
14571452
&mut indent_writer,
14581453
)?;
14591454
indent_writer.flush()?;
@@ -1468,7 +1463,7 @@ impl<'a> TestReporterImpl<'a> {
14681463
let mut indent_writer = IndentWriter::new(spec.output_indent, writer);
14691464
self.write_test_single_output_with_description(
14701465
stderr,
1471-
description.and_then(|d| d.stderr_subslice()),
1466+
highlight_slice.and_then(|d| d.stderr_subslice()),
14721467
&mut indent_writer,
14731468
)?;
14741469
indent_writer.flush()?;
@@ -1482,7 +1477,7 @@ impl<'a> TestReporterImpl<'a> {
14821477
let mut indent_writer = IndentWriter::new(spec.output_indent, writer);
14831478
self.write_test_single_output_with_description(
14841479
output,
1485-
description.and_then(|d| d.combined_subslice()),
1480+
highlight_slice.and_then(|d| d.combined_subslice()),
14861481
&mut indent_writer,
14871482
)?;
14881483
indent_writer.flush()?;
@@ -1618,6 +1613,7 @@ impl<'a> TestReporterImpl<'a> {
16181613
};
16191614

16201615
ChildOutputSpec {
1616+
kind: UnitKind::Test,
16211617
stdout_header,
16221618
stderr_header,
16231619
combined_header,
@@ -1680,6 +1676,7 @@ impl<'a> TestReporterImpl<'a> {
16801676
};
16811677

16821678
ChildOutputSpec {
1679+
kind: UnitKind::Script,
16831680
stdout_header,
16841681
stderr_header,
16851682
combined_header,
@@ -1706,6 +1703,7 @@ const RESET_COLOR: &[u8] = b"\x1b[0m";
17061703
/// measurably slow.
17071704
#[derive(Debug)]
17081705
struct ChildOutputSpec {
1706+
kind: UnitKind,
17091707
stdout_header: String,
17101708
stderr_header: String,
17111709
combined_header: String,

0 commit comments

Comments
 (0)