Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b7da574

Browse files
Unrolled build for #146119
Rollup merge of #146119 - Zalathar:capture, r=jieyouxu compiletest: Implement an experimental `--new-output-capture` mode Thanks to the efforts on #140192, compiletest no longer has an unstable dependency on libtest, but it still has an unstable dependency on `#![feature(internal_output_capture)]`. That makes building compiletest more complicated than for most other bootstrap tools. This PR therefore adds opt-in support for an experimental compiletest mode that avoids the use of `internal_output_capture` APIs, and instead uses more mundane means to capture the output of individual test runners. Each `TestCx` now contains `&dyn ConsoleOut` references for stdout and stderr. All print statements in `compiletests::runtest` have been replaced with `write!` or `writeln!` calls that explicitly write to one of those trait objects. The underlying implementation then forwards to `print!` or `eprint!` (for `--no-capture` or old-output-capture mode), or writes to a separate buffer (in new-output-capture mode). --- Currently, new-output-capture is disabled by default. It can be explicitly enabled in one of two ways: - When running `x test`, pass `--new-output-capture=on` as a *compiletest* argument (after `--`). - E.g. `x test ui -- --new-output-capture=on`. - The short form is `-Non` or `-Ny`. - Set environment variable `COMPILETEST_NEW_OUTPUT_CAPTURE=on`. After some amount of opt-in testing, new-output-capture will become the default (with a temporary opt-out). Eventually, old-output-capture and `#![feature(internal_output_capture)]` will be completely removed from compiletest. r? jieyouxu
2 parents 79bdc62 + c2ae2e0 commit b7da574

File tree

13 files changed

+275
-75
lines changed

13 files changed

+275
-75
lines changed

‎src/tools/compiletest/src/common.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,10 @@ pub struct Config {
667667
/// to avoid `!nocapture` double-negatives.
668668
pub nocapture: bool,
669669

670+
/// True if the experimental new output-capture implementation should be
671+
/// used, avoiding the need for `#![feature(internal_output_capture)]`.
672+
pub new_output_capture: bool,
673+
670674
/// Needed both to construct [`build_helper::git::GitConfig`].
671675
pub nightly_branch: String,
672676
pub git_merge_commit_email: String,
@@ -784,6 +788,7 @@ impl Config {
784788
builtin_cfg_names: Default::default(),
785789
supported_crate_types: Default::default(),
786790
nocapture: Default::default(),
791+
new_output_capture: Default::default(),
787792
nightly_branch: Default::default(),
788793
git_merge_commit_email: Default::default(),
789794
profiler_runtime: Default::default(),

‎src/tools/compiletest/src/executor.rs

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex, mpsc};
1313
use std::{env, hint, io, mem, panic, thread};
1414

1515
use crate::common::{Config, TestPaths};
16+
use crate::output_capture::{self, ConsoleOut};
1617
use crate::panic_hook;
1718

1819
mod deadline;
@@ -120,28 +121,28 @@ fn run_test_inner(
120121
runnable_test: RunnableTest,
121122
completion_sender: mpsc::Sender<TestCompletion>,
122123
) {
123-
let is_capture = !runnable_test.config.nocapture;
124+
let capture = CaptureKind::for_config(&runnable_test.config);
124125

125126
// Install a panic-capture buffer for use by the custom panic hook.
126-
if is_capture {
127+
if capture.should_set_panic_hook() {
127128
panic_hook::set_capture_buf(Default::default());
128129
}
129-
let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![])));
130130

131-
if let Some(capture_buf) = &capture_buf {
132-
io::set_output_capture(Some(Arc::clone(capture_buf)));
131+
if let CaptureKind::Old{ref buf } = capture {
132+
io::set_output_capture(Some(Arc::clone(buf)));
133133
}
134134

135-
let panic_payload = panic::catch_unwind(move || runnable_test.run()).err();
135+
let stdout = capture.stdout();
136+
let stderr = capture.stderr();
137+
138+
let panic_payload = panic::catch_unwind(move || runnable_test.run(stdout, stderr)).err();
136139

137140
if let Some(panic_buf) = panic_hook::take_capture_buf() {
138141
let panic_buf = panic_buf.lock().unwrap_or_else(|e| e.into_inner());
139-
// For now, forward any captured panic message to (captured) stderr.
140-
// FIXME(Zalathar): Once we have our own output-capture buffer for
141-
// non-panic output, append the panic message to that buffer instead.
142-
eprint!("{panic_buf}");
142+
// Forward any captured panic message to (captured) stderr.
143+
write!(stderr, "{panic_buf}");
143144
}
144-
if is_capture {
145+
if matches!(capture,CaptureKind::Old{ .. }) {
145146
io::set_output_capture(None);
146147
}
147148

@@ -152,11 +153,70 @@ fn run_test_inner(
152153
TestOutcome::Failed { message: Some("test did not panic as expected") }
153154
}
154155
};
155-
let stdout = capture_buf.map(|mutex| mutex.lock().unwrap_or_else(|e| e.into_inner()).to_vec());
156156

157+
let stdout = capture.into_inner();
157158
completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap();
158159
}
159160

161+
enum CaptureKind {
162+
/// Do not capture test-runner output, for `--no-capture`.
163+
///
164+
/// (This does not affect `rustc` and other subprocesses spawned by test
165+
/// runners, whose output is always captured.)
166+
None,
167+
168+
/// Use the old output-capture implementation, which relies on the unstable
169+
/// library feature `#![feature(internal_output_capture)]`.
170+
Old { buf: Arc<Mutex<Vec<u8>>> },
171+
172+
/// Use the new output-capture implementation, which only uses stable Rust.
173+
New { buf: output_capture::CaptureBuf },
174+
}
175+
176+
impl CaptureKind {
177+
fn for_config(config: &Config) -> Self {
178+
if config.nocapture {
179+
Self::None
180+
} else if config.new_output_capture {
181+
Self::New { buf: output_capture::CaptureBuf::new() }
182+
} else {
183+
// Create a capure buffer for `io::set_output_capture`.
184+
Self::Old { buf: Default::default() }
185+
}
186+
}
187+
188+
fn should_set_panic_hook(&self) -> bool {
189+
match self {
190+
Self::None => false,
191+
Self::Old { .. } => true,
192+
Self::New { .. } => true,
193+
}
194+
}
195+
196+
fn stdout(&self) -> &dyn ConsoleOut {
197+
self.capture_buf_or(&output_capture::Stdout)
198+
}
199+
200+
fn stderr(&self) -> &dyn ConsoleOut {
201+
self.capture_buf_or(&output_capture::Stderr)
202+
}
203+
204+
fn capture_buf_or<'a>(&'a self, fallback: &'a dyn ConsoleOut) -> &'a dyn ConsoleOut {
205+
match self {
206+
Self::None | Self::Old { .. } => fallback,
207+
Self::New { buf } => buf,
208+
}
209+
}
210+
211+
fn into_inner(self) -> Option<Vec<u8>> {
212+
match self {
213+
Self::None => None,
214+
Self::Old { buf } => Some(buf.lock().unwrap_or_else(|e| e.into_inner()).to_vec()),
215+
Self::New { buf } => Some(buf.into_inner().into()),
216+
}
217+
}
218+
}
219+
160220
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161221
struct TestId(usize);
162222

@@ -174,10 +234,12 @@ impl RunnableTest {
174234
Self { config, testpaths, revision }
175235
}
176236

177-
fn run(&self) {
237+
fn run(&self,stdout:&dynConsoleOut,stderr:&dynConsoleOut) {
178238
__rust_begin_short_backtrace(|| {
179239
crate::runtest::run(
180240
Arc::clone(&self.config),
241+
stdout,
242+
stderr,
181243
&self.testpaths,
182244
self.revision.as_deref(),
183245
);

‎src/tools/compiletest/src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub mod directives;
1515
pub mod errors;
1616
mod executor;
1717
mod json;
18+
mod output_capture;
1819
mod panic_hook;
1920
mod raise_fd_limit;
2021
mod read2;
@@ -177,6 +178,12 @@ pub fn parse_config(args: Vec<String>) -> Config {
177178
// FIXME: Temporarily retained so we can point users to `--no-capture`
178179
.optflag("", "nocapture", "")
179180
.optflag("", "no-capture", "don't capture stdout/stderr of tests")
181+
.optopt(
182+
"N",
183+
"new-output-capture",
184+
"enables or disables the new output-capture implementation",
185+
"off|on",
186+
)
180187
.optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
181188
.optflag("h", "help", "show this message")
182189
.reqopt("", "channel", "current Rust channel", "CHANNEL")
@@ -461,6 +468,14 @@ pub fn parse_config(args: Vec<String>) -> Config {
461468
supported_crate_types: OnceLock::new(),
462469

463470
nocapture: matches.opt_present("no-capture"),
471+
new_output_capture: {
472+
let value = matches
473+
.opt_str("new-output-capture")
474+
.or_else(|| env::var("COMPILETEST_NEW_OUTPUT_CAPTURE").ok())
475+
.unwrap_or_else(|| "off".to_owned());
476+
parse_bool_option(&value)
477+
.unwrap_or_else(|| panic!("unknown `--new-output-capture` value `{value}` given"))
478+
},
464479

465480
nightly_branch: matches.opt_str("nightly-branch").unwrap(),
466481
git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
@@ -476,6 +491,19 @@ pub fn parse_config(args: Vec<String>) -> Config {
476491
}
477492
}
478493

494+
/// Parses the same set of boolean values accepted by rustc command-line arguments.
495+
///
496+
/// Accepting all of these values is more complicated than just picking one
497+
/// pair, but has the advantage that contributors who are used to rustc
498+
/// shouldn't have to think about which values are legal.
499+
fn parse_bool_option(value: &str) -> Option<bool> {
500+
match value {
501+
"off" | "no" | "n" | "false" => Some(false),
502+
"on" | "yes" | "y" | "true" => Some(true),
503+
_ => None,
504+
}
505+
}
506+
479507
pub fn opt_str(maybestr: &Option<String>) -> &str {
480508
match *maybestr {
481509
None => "(none)",
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::fmt;
2+
use std::panic::RefUnwindSafe;
3+
use std::sync::Mutex;
4+
5+
pub trait ConsoleOut: fmt::Debug + RefUnwindSafe {
6+
fn write_fmt(&self, args: fmt::Arguments<'_>);
7+
}
8+
9+
#[derive(Debug)]
10+
pub(crate) struct Stdout;
11+
12+
impl ConsoleOut for Stdout {
13+
fn write_fmt(&self, args: fmt::Arguments<'_>) {
14+
print!("{args}");
15+
}
16+
}
17+
18+
#[derive(Debug)]
19+
pub(crate) struct Stderr;
20+
21+
impl ConsoleOut for Stderr {
22+
fn write_fmt(&self, args: fmt::Arguments<'_>) {
23+
eprint!("{args}");
24+
}
25+
}
26+
27+
pub(crate) struct CaptureBuf {
28+
inner: Mutex<String>,
29+
}
30+
31+
impl CaptureBuf {
32+
pub(crate) fn new() -> Self {
33+
Self { inner: Mutex::new(String::new()) }
34+
}
35+
36+
pub(crate) fn into_inner(self) -> String {
37+
self.inner.into_inner().unwrap_or_else(|e| e.into_inner())
38+
}
39+
}
40+
41+
impl fmt::Debug for CaptureBuf {
42+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43+
f.debug_struct("CaptureBuf").finish_non_exhaustive()
44+
}
45+
}
46+
47+
impl ConsoleOut for CaptureBuf {
48+
fn write_fmt(&self, args: fmt::Arguments<'_>) {
49+
let mut s = self.inner.lock().unwrap_or_else(|e| e.into_inner());
50+
<String as fmt::Write>::write_fmt(&mut s, args).unwrap();
51+
}
52+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /