Skip to content

Commit d289816

Browse files
committed
[nextest-runner] support OSC 9 terminal status reporting
Piggyback on Cargo 1.87's support for the same: rust-lang/cargo#14615 It would be nice to have a standard Rust crate to detect this in the future, but using the same logic as Cargo will do for now.
1 parent 4267090 commit d289816

File tree

7 files changed

+324
-55
lines changed

7 files changed

+324
-55
lines changed

cargo-nextest/src/dispatch.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,8 +1861,13 @@ impl App {
18611861
)?;
18621862

18631863
// Make the reporter.
1864-
let mut reporter =
1865-
reporter_builder.build(&test_list, &profile, output, structured_reporter);
1864+
let mut reporter = reporter_builder.build(
1865+
&test_list,
1866+
&profile,
1867+
&self.base.cargo_configs,
1868+
output,
1869+
structured_reporter,
1870+
);
18661871

18671872
configure_handle_inheritance(no_capture)?;
18681873
let run_stats = runner.try_execute(|event| {

nextest-runner/src/cargo_config/discovery.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ pub(crate) struct CargoConfig {
425425
pub(crate) target: Option<BTreeMap<String, CargoConfigRunner>>,
426426
#[serde(default)]
427427
pub(crate) env: BTreeMap<String, CargoConfigEnv>,
428+
#[serde(default)]
429+
pub(crate) term: CargoConfigTerm,
428430
}
429431

430432
#[derive(Deserialize, Default, Debug)]
@@ -445,6 +447,20 @@ pub(crate) enum Runner {
445447
List(Vec<String>),
446448
}
447449

450+
#[derive(Deserialize, Debug, Default)]
451+
#[serde(rename_all = "kebab-case")]
452+
pub(crate) struct CargoConfigTerm {
453+
#[serde(default)]
454+
pub(crate) progress: CargoConfigTermProgress,
455+
}
456+
457+
#[derive(Deserialize, Debug, Default)]
458+
#[serde(rename_all = "kebab-case")]
459+
pub(crate) struct CargoConfigTermProgress {
460+
#[serde(default)]
461+
pub(crate) term_integration: Option<bool>,
462+
}
463+
448464
#[cfg(test)]
449465
mod tests {
450466
use super::*;

nextest-runner/src/reporter/displayer/imp.rs

Lines changed: 97 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ use super::{
1616
unit_output::TestOutputDisplay,
1717
};
1818
use crate::{
19+
cargo_config::CargoConfigs,
1920
config::{CompiledDefaultFilter, LeakTimeoutResult, ScriptId},
2021
errors::WriteEventError,
2122
helpers::{DisplayScriptInstance, DisplayTestInstance, plural},
2223
list::{TestInstance, TestInstanceId},
23-
reporter::{events::*, helpers::Styles, imp::ReporterStderr},
24+
reporter::{
25+
displayer::progress::TerminalProgress, events::*, helpers::Styles, imp::ReporterStderr,
26+
},
2427
};
2528
use debug_ignore::DebugIgnore;
2629
use indent_write::io::IndentWriter;
@@ -46,7 +49,11 @@ pub(crate) struct DisplayReporterBuilder {
4649
}
4750

4851
impl DisplayReporterBuilder {
49-
pub(crate) fn build(self, output: ReporterStderr<'_>) -> DisplayReporter<'_> {
52+
pub(crate) fn build<'a>(
53+
self,
54+
configs: &CargoConfigs,
55+
output: ReporterStderr<'a>,
56+
) -> DisplayReporter<'a> {
5057
let mut styles: Box<Styles> = Box::default();
5158
if self.should_colorize {
5259
styles.colorize();
@@ -72,31 +79,37 @@ impl DisplayReporterBuilder {
7279
}
7380

7481
let stderr = match output {
75-
ReporterStderr::Terminal if self.no_capture => {
76-
// Do not use a progress bar if --no-capture is passed in. This is required since we
77-
// pass down stderr to the child process.
78-
//
79-
// In the future, we could potentially switch to using a pty, in which case we could
80-
// still potentially use the progress bar as a status bar. However, that brings
81-
// about its own complications: what if a test's output doesn't include a newline?
82-
// We might have to use a curses-like UI which would be a lot of work for not much
83-
// gain.
84-
ReporterStderrImpl::TerminalWithoutBar
85-
}
86-
ReporterStderr::Terminal if is_ci::uncached() => {
87-
// Some CI environments appear to pretend to be a terminal. Disable the progress bar
88-
// in these environments.
89-
ReporterStderrImpl::TerminalWithoutBar
90-
}
91-
ReporterStderr::Terminal if self.hide_progress_bar => {
92-
ReporterStderrImpl::TerminalWithoutBar
93-
}
94-
9582
ReporterStderr::Terminal => {
96-
let state = ProgressBarState::new(self.test_count, theme_characters.progress_chars);
97-
// Note: even if we create a progress bar here, if stderr is
98-
// piped, indicatif will not show it.
99-
ReporterStderrImpl::TerminalWithBar { state }
83+
let progress_bar = if self.no_capture {
84+
// Do not use a progress bar if --no-capture is passed in.
85+
// This is required since we pass down stderr to the child
86+
// process.
87+
//
88+
// In the future, we could potentially switch to using a
89+
// pty, in which case we could still potentially use the
90+
// progress bar as a status bar. However, that brings about
91+
// its own complications: what if a test's output doesn't
92+
// include a newline? We might have to use a curses-like UI
93+
// which would be a lot of work for not much gain.
94+
None
95+
} else if is_ci::uncached() {
96+
// Some CI environments appear to pretend to be a terminal.
97+
// Disable the progress bar in these environments.
98+
None
99+
} else if self.hide_progress_bar {
100+
None
101+
} else {
102+
let state =
103+
ProgressBarState::new(self.test_count, theme_characters.progress_chars);
104+
// Note: even if we create a progress bar here, if stderr is
105+
// piped, indicatif will not show it.
106+
Some(state)
107+
};
108+
let term_progress = TerminalProgress::new(configs, &io::stderr());
109+
ReporterStderrImpl::Terminal {
110+
progress_bar,
111+
term_progress,
112+
}
100113
}
101114
ReporterStderr::Buffer(buf) => ReporterStderrImpl::Buffer(buf),
102115
};
@@ -142,23 +155,37 @@ pub(crate) struct DisplayReporter<'a> {
142155
impl<'a> DisplayReporter<'a> {
143156
pub(crate) fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
144157
match &mut self.stderr {
145-
ReporterStderrImpl::TerminalWithBar { state } => {
146-
// Write to a string that will be printed as a log line.
147-
let mut buf: Vec<u8> = Vec::new();
148-
self.inner
149-
.write_event_impl(event, &mut buf)
150-
.map_err(WriteEventError::Io)?;
151-
152-
state.update_progress_bar(event, &self.inner.styles);
153-
state.write_buf(&buf).map_err(WriteEventError::Io)
154-
}
155-
ReporterStderrImpl::TerminalWithoutBar => {
156-
// Write to a buffered stderr.
157-
let mut writer = BufWriter::new(std::io::stderr());
158-
self.inner
159-
.write_event_impl(event, &mut writer)
160-
.map_err(WriteEventError::Io)?;
161-
writer.flush().map_err(WriteEventError::Io)
158+
ReporterStderrImpl::Terminal {
159+
progress_bar,
160+
term_progress,
161+
} => {
162+
if let Some(state) = progress_bar {
163+
// Write to a string that will be printed as a log line.
164+
let mut buf: Vec<u8> = Vec::new();
165+
self.inner
166+
.write_event_impl(event, &mut buf)
167+
.map_err(WriteEventError::Io)?;
168+
169+
state.update_progress_bar(event, &self.inner.styles);
170+
if let Some(term_progress) = term_progress {
171+
term_progress
172+
.update_progress(event, &mut buf)
173+
.map_err(WriteEventError::Io)?;
174+
}
175+
state.write_buf(&buf).map_err(WriteEventError::Io)
176+
} else {
177+
// Write to a buffered stderr.
178+
let mut writer = BufWriter::new(std::io::stderr());
179+
self.inner
180+
.write_event_impl(event, &mut writer)
181+
.map_err(WriteEventError::Io)?;
182+
if let Some(state) = term_progress {
183+
state
184+
.update_progress(event, &mut writer)
185+
.map_err(WriteEventError::Io)?;
186+
}
187+
writer.flush().map_err(WriteEventError::Io)
188+
}
162189
}
163190
ReporterStderrImpl::Buffer(buf) => self
164191
.inner
@@ -173,21 +200,26 @@ impl<'a> DisplayReporter<'a> {
173200
}
174201

175202
enum ReporterStderrImpl<'a> {
176-
TerminalWithBar {
177-
// Reporter-specific progress bar state.
178-
state: ProgressBarState,
203+
Terminal {
204+
// Reporter-specific progress bar state. None if the progress bar is not
205+
// enabled.
206+
progress_bar: Option<ProgressBarState>,
207+
// OSC 9 code progress reporting.
208+
term_progress: Option<TerminalProgress>,
179209
},
180-
TerminalWithoutBar,
181210
Buffer(&'a mut Vec<u8>),
182211
}
183212

184213
impl ReporterStderrImpl<'_> {
185214
fn finish_and_clear_bar(&self) {
186215
match self {
187-
ReporterStderrImpl::TerminalWithBar { state } => {
216+
ReporterStderrImpl::Terminal {
217+
progress_bar: Some(state),
218+
..
219+
} => {
188220
state.finish_and_clear();
189221
}
190-
ReporterStderrImpl::TerminalWithoutBar | ReporterStderrImpl::Buffer(_) => {}
222+
ReporterStderrImpl::Terminal { .. } | ReporterStderrImpl::Buffer(_) => {}
191223
}
192224
}
193225

@@ -512,6 +544,7 @@ impl<'a> DisplayReporterImpl<'a> {
512544
}
513545
TestEventKind::RunBeginCancel {
514546
setup_scripts_running,
547+
current_stats: _,
515548
running,
516549
reason,
517550
} => {
@@ -544,6 +577,7 @@ impl<'a> DisplayReporterImpl<'a> {
544577
}
545578
TestEventKind::RunBeginKill {
546579
setup_scripts_running,
580+
current_stats: _,
547581
running,
548582
reason,
549583
} => {
@@ -1794,6 +1828,7 @@ mod tests {
17941828
test_output::{ChildExecutionOutput, ChildOutput, ChildSplitOutput},
17951829
};
17961830
use bytes::Bytes;
1831+
use camino::Utf8PathBuf;
17971832
use chrono::Local;
17981833
use nextest_metadata::RustBinaryId;
17991834
use smol_str::SmolStr;
@@ -1806,6 +1841,18 @@ mod tests {
18061841
where
18071842
F: FnOnce(DisplayReporter<'a>),
18081843
{
1844+
// Start and end the search at the cwd -- we expect this to not match
1845+
// any results since it'll be the nextest-runner directory.
1846+
let current_dir = Utf8PathBuf::try_from(std::env::current_dir().expect("obtained cwd"))
1847+
.expect("cwd is valid UTF_8");
1848+
let configs = CargoConfigs::new_with_isolation(
1849+
Vec::<String>::new(),
1850+
&current_dir,
1851+
&current_dir,
1852+
Vec::new(),
1853+
)
1854+
.unwrap();
1855+
18091856
let builder = DisplayReporterBuilder {
18101857
default_filter: CompiledDefaultFilter::for_default_config(),
18111858
status_levels: StatusLevels {
@@ -1821,7 +1868,7 @@ mod tests {
18211868
no_output_indent: false,
18221869
};
18231870
let output = ReporterStderr::Buffer(out);
1824-
let reporter = builder.build(output);
1871+
let reporter = builder.build(&configs, output);
18251872
f(reporter);
18261873
}
18271874

0 commit comments

Comments
 (0)