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
88 changes: 60 additions & 28 deletions crates/fe/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ struct SingleRunResult {
suite_key: String,
symbol_name: String,
result: TestResult,
output: String,
measurement: Option<GasMeasurement>,
elapsed: Duration,
}
Expand Down Expand Up @@ -888,7 +889,15 @@ impl OutcomeCollectorState {
}
JobOutcome::SingleFinished(single) => {
let single = *single;
let suite_key = single.suite_key.clone();
let suite_key = single.suite_key;
let kind = if single.result.passed {
StreamStatusKind::Pass
} else {
StreamStatusKind::Fail
};
let elapsed = single.elapsed;
let name = single.result.name.clone();
let output = single.output;
let suite_is_complete = {
let state = self.pending_suites.get_mut(&suite_key).ok_or_else(|| {
format!("received single test outcome for unknown suite `{suite_key}`")
Expand All @@ -902,6 +911,7 @@ impl OutcomeCollectorState {
}
state.completed_singles == state.expected_singles
};
self.emit_single_output(&suite_key, kind, elapsed, &name, &output, ctx)?;
if suite_is_complete {
self.finalize_pending_suite(&suite_key, ctx)?;
}
Expand All @@ -910,6 +920,47 @@ impl OutcomeCollectorState {
Ok(())
}

fn emit_single_output(
&mut self,
suite_key: &str,
kind: StreamStatusKind,
elapsed: Duration,
name: &str,
output: &str,
ctx: &OutcomeContext<'_>,
) -> Result<(), String> {
if self.buffer_grouped_output {
let suite_idx = *self.suite_index_by_key.get(suite_key).ok_or_else(|| {
format!("received single test output for unknown suite `{suite_key}`")
})?;
self.grouped_lines[suite_idx].push(format_streamed_status_line(
true,
ctx.suite_label_width,
suite_key,
kind,
Some(elapsed),
name,
));
append_streamed_multi_output_lines(
&mut self.grouped_lines[suite_idx],
ctx.suite_label_width,
suite_key,
output,
);
} else {
print_streamed_status(
ctx.multi,
ctx.suite_label_width,
suite_key,
kind,
Some(elapsed),
name,
);
print_streamed_output(ctx.multi, ctx.suite_label_width, suite_key, output);
}
Ok(())
}

fn flush_grouped_suite_lines(&mut self, suite_idx: usize) -> Result<(), String> {
let lines = self
.grouped_lines
Expand Down Expand Up @@ -1479,23 +1530,7 @@ fn emit_single_outcome(
outcome_tx: &Sender<JobOutcome>,
shared: &WorkerSharedConfig,
) {
let (output, single) = run_single_test_job(job, shared);
let _ = outcome_tx.send(JobOutcome::Status {
suite_key: single.suite_key.clone(),
kind: if single.result.passed {
StreamStatusKind::Pass
} else {
StreamStatusKind::Fail
},
elapsed: Some(single.elapsed),
message: single.result.name.clone(),
});
if !output.is_empty() {
let _ = outcome_tx.send(JobOutcome::Text {
suite_key: single.suite_key.clone(),
text: output,
});
}
let single = run_single_test_job(job, shared);
let _ = outcome_tx.send(JobOutcome::SingleFinished(Box::new(single)));
}

Expand Down Expand Up @@ -1563,7 +1598,7 @@ fn emit_grouped_suite_outcome(
aggregate_suite_staging: prepared.aggregate_suite_staging,
};
for single_job in prepared.single_jobs {
let (output, single) = run_single_test_job(single_job, cfg.shared.as_ref());
let single = run_single_test_job(single_job, cfg.shared.as_ref());
let _ = outcome_tx.send(JobOutcome::Status {
suite_key: single.suite_key.clone(),
kind: if single.result.passed {
Expand All @@ -1574,10 +1609,10 @@ fn emit_grouped_suite_outcome(
elapsed: Some(single.elapsed),
message: single.result.name.clone(),
});
if !output.is_empty() {
if !single.output.is_empty() {
let _ = outcome_tx.send(JobOutcome::Text {
suite_key: single.suite_key.clone(),
text: output,
text: single.output,
});
}
state.completed_singles += 1;
Expand Down Expand Up @@ -1850,10 +1885,7 @@ fn prepare_suite_job(
)
}

fn run_single_test_job(
job: SingleTestJob,
shared: &WorkerSharedConfig,
) -> (String, SingleRunResult) {
fn run_single_test_job(job: SingleTestJob, shared: &WorkerSharedConfig) -> SingleRunResult {
let report_ctx = job.report_root.as_ref().map(|root| ReportContext {
root_dir: root.clone(),
});
Expand All @@ -1880,14 +1912,14 @@ fn run_single_test_job(
&shared.backend,
&shared.debug,
);
let result = SingleRunResult {
SingleRunResult {
suite_key: job.suite_key,
symbol_name: case.symbol_name,
result: outcome.result,
output,
measurement: Some(measurement),
elapsed,
};
(output, result)
}
}

fn finalize_pending_suite(
Expand Down
37 changes: 37 additions & 0 deletions crates/fe/tests/cli_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,43 @@ fn test_fe_test_runner(fixture: Fixture<&str>) {
snap_test!(output, fixture.path());
}

#[test]
fn test_fe_test_parallel_failure_details_follow_status_lines() {
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/fe_test_runner/checked_arithmetic_reverts.fe");
let fixture = fixture
.to_str()
.unwrap_or_else(|| panic!("fixture path is not utf-8: {}", fixture.display()));
let out = run_fe_main_impl(&["test", "--jobs", "8", fixture], None, &[]);

assert_ne!(
out.exit_code,
0,
"expected failing tests:\n{}",
out.combined()
);
let lines = out.stdout.lines().collect::<Vec<_>>();
let fail_lines = lines
.iter()
.enumerate()
.filter(|(_, line)| line.starts_with("FAIL ["))
.collect::<Vec<_>>();
assert_eq!(
fail_lines.len(),
8,
"expected all fixture tests to fail:\n{}",
out.stdout
);
for (line_idx, line) in fail_lines {
let next = lines.get(line_idx + 1).copied().unwrap_or_default();
assert!(
next.trim_start().starts_with("Test reverted:"),
"expected failure detail immediately after status `{line}`, got `{next}`:\n{}",
out.stdout
);
}
}

#[dir_test(
dir: "$CARGO_MANIFEST_DIR/tests/fixtures/cli_output/ingots/library",
glob: "**/app/fe.toml",
Expand Down
1 change: 1 addition & 0 deletions newsfragments/1420.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `fe test` output so failure details are printed immediately after the corresponding failed test status when tests run in parallel.
Loading