Skip to content

Commit 487e961

Browse files
committed
Auto merge of #52181 - QuietMisdreavus:panicked-tester, r=GuillaumeGomez
rustdoc: set panic output before starting compiler thread pool When the compiler was updated to run on a thread pool, rustdoc's processing of compiler/doctest stderr/stdout was moved into each compiler thread. However, this caused output of the test to be lost if the test failed at *runtime* instead of compile time. This change sets up the `set_panic` call and output bomb before starting the compiler thread pool, so that the `Drop` call that writes back to the test's stdout happens after the test runs, not just after it compiles. Fixes #51162
2 parents f498e4e + 76e33b4 commit 487e961

File tree

5 files changed

+132
-55
lines changed

5 files changed

+132
-55
lines changed

src/librustc_driver/lib.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -1497,10 +1497,12 @@ fn parse_crate_attrs<'a>(sess: &'a Session, input: &Input) -> PResult<'a, Vec<as
14971497
}
14981498
}
14991499

1500-
/// Runs `f` in a suitable thread for running `rustc`; returns a
1501-
/// `Result` with either the return value of `f` or -- if a panic
1502-
/// occurs -- the panic value.
1503-
pub fn in_rustc_thread<F, R>(f: F) -> Result<R, Box<dyn Any + Send>>
1500+
/// Runs `f` in a suitable thread for running `rustc`; returns a `Result` with either the return
1501+
/// value of `f` or -- if a panic occurs -- the panic value.
1502+
///
1503+
/// This version applies the given name to the thread. This is used by rustdoc to ensure consistent
1504+
/// doctest output across platforms and executions.
1505+
pub fn in_named_rustc_thread<F, R>(name: String, f: F) -> Result<R, Box<dyn Any + Send>>
15041506
where F: FnOnce() -> R + Send + 'static,
15051507
R: Send + 'static,
15061508
{
@@ -1564,7 +1566,7 @@ pub fn in_rustc_thread<F, R>(f: F) -> Result<R, Box<dyn Any + Send>>
15641566

15651567
// The or condition is added from backward compatibility.
15661568
if spawn_thread || env::var_os("RUST_MIN_STACK").is_some() {
1567-
let mut cfg = thread::Builder::new().name("rustc".to_string());
1569+
let mut cfg = thread::Builder::new().name(name);
15681570

15691571
// FIXME: Hacks on hacks. If the env is trying to override the stack size
15701572
// then *don't* set it explicitly.
@@ -1580,6 +1582,16 @@ pub fn in_rustc_thread<F, R>(f: F) -> Result<R, Box<dyn Any + Send>>
15801582
}
15811583
}
15821584

1585+
/// Runs `f` in a suitable thread for running `rustc`; returns a
1586+
/// `Result` with either the return value of `f` or -- if a panic
1587+
/// occurs -- the panic value.
1588+
pub fn in_rustc_thread<F, R>(f: F) -> Result<R, Box<dyn Any + Send>>
1589+
where F: FnOnce() -> R + Send + 'static,
1590+
R: Send + 'static,
1591+
{
1592+
in_named_rustc_thread("rustc".to_string(), f)
1593+
}
1594+
15831595
/// Get a list of extra command-line flags provided by the user, as strings.
15841596
///
15851597
/// This function is used during ICEs to show more information useful for

src/librustdoc/test.rs

+47-45
Original file line numberDiff line numberDiff line change
@@ -232,40 +232,42 @@ fn run_test(test: &str, cratename: &str, filename: &FileName, line: usize,
232232
..config::basic_options().clone()
233233
};
234234

235-
let (libdir, outdir) = driver::spawn_thread_pool(sessopts, |sessopts| {
236-
// Shuffle around a few input and output handles here. We're going to pass
237-
// an explicit handle into rustc to collect output messages, but we also
238-
// want to catch the error message that rustc prints when it fails.
239-
//
240-
// We take our thread-local stderr (likely set by the test runner) and replace
241-
// it with a sink that is also passed to rustc itself. When this function
242-
// returns the output of the sink is copied onto the output of our own thread.
243-
//
244-
// The basic idea is to not use a default Handler for rustc, and then also
245-
// not print things by default to the actual stderr.
246-
struct Sink(Arc<Mutex<Vec<u8>>>);
247-
impl Write for Sink {
248-
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
249-
Write::write(&mut *self.0.lock().unwrap(), data)
250-
}
251-
fn flush(&mut self) -> io::Result<()> { Ok(()) }
235+
// Shuffle around a few input and output handles here. We're going to pass
236+
// an explicit handle into rustc to collect output messages, but we also
237+
// want to catch the error message that rustc prints when it fails.
238+
//
239+
// We take our thread-local stderr (likely set by the test runner) and replace
240+
// it with a sink that is also passed to rustc itself. When this function
241+
// returns the output of the sink is copied onto the output of our own thread.
242+
//
243+
// The basic idea is to not use a default Handler for rustc, and then also
244+
// not print things by default to the actual stderr.
245+
struct Sink(Arc<Mutex<Vec<u8>>>);
246+
impl Write for Sink {
247+
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
248+
Write::write(&mut *self.0.lock().unwrap(), data)
252249
}
253-
struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>);
254-
impl Drop for Bomb {
255-
fn drop(&mut self) {
256-
let _ = self.1.write_all(&self.0.lock().unwrap());
257-
}
250+
fn flush(&mut self) -> io::Result<()> { Ok(()) }
251+
}
252+
struct Bomb(Arc<Mutex<Vec<u8>>>, Box<Write+Send>);
253+
impl Drop for Bomb {
254+
fn drop(&mut self) {
255+
let _ = self.1.write_all(&self.0.lock().unwrap());
258256
}
259-
let data = Arc::new(Mutex::new(Vec::new()));
257+
}
258+
let data = Arc::new(Mutex::new(Vec::new()));
259+
260+
let old = io::set_panic(Some(box Sink(data.clone())));
261+
let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
262+
263+
let (libdir, outdir, compile_result) = driver::spawn_thread_pool(sessopts, |sessopts| {
260264
let codemap = Lrc::new(CodeMap::new_doctest(
261265
sessopts.file_path_mapping(), filename.clone(), line as isize - line_offset as isize
262266
));
263267
let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
264268
Some(codemap.clone()),
265269
false,
266270
false);
267-
let old = io::set_panic(Some(box Sink(data.clone())));
268-
let _bomb = Bomb(data.clone(), old.unwrap_or(box io::stdout()));
269271

270272
// Compile the code
271273
let diagnostic_handler = errors::Handler::with_emitter(true, false, box emitter);
@@ -312,28 +314,28 @@ fn run_test(test: &str, cratename: &str, filename: &FileName, line: usize,
312314
Err(_) | Ok(Err(CompileIncomplete::Errored(_))) => Err(())
313315
};
314316

315-
match (compile_result, compile_fail) {
316-
(Ok(()), true) => {
317-
panic!("test compiled while it wasn't supposed to")
318-
}
319-
(Ok(()), false) => {}
320-
(Err(()), true) => {
321-
if error_codes.len() > 0 {
322-
let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
323-
error_codes.retain(|err| !out.contains(err));
324-
}
325-
}
326-
(Err(()), false) => {
327-
panic!("couldn't compile the test")
317+
(libdir, outdir, compile_result)
318+
});
319+
320+
match (compile_result, compile_fail) {
321+
(Ok(()), true) => {
322+
panic!("test compiled while it wasn't supposed to")
323+
}
324+
(Ok(()), false) => {}
325+
(Err(()), true) => {
326+
if error_codes.len() > 0 {
327+
let out = String::from_utf8(data.lock().unwrap().to_vec()).unwrap();
328+
error_codes.retain(|err| !out.contains(err));
328329
}
329330
}
330-
331-
if error_codes.len() > 0 {
332-
panic!("Some expected error codes were not found: {:?}", error_codes);
331+
(Err(()), false) => {
332+
panic!("couldn't compile the test")
333333
}
334+
}
334335

335-
(libdir, outdir)
336-
});
336+
if error_codes.len() > 0 {
337+
panic!("Some expected error codes were not found: {:?}", error_codes);
338+
}
337339

338340
if no_run { return }
339341

@@ -548,7 +550,7 @@ impl Collector {
548550
debug!("Creating test {}: {}", name, test);
549551
self.tests.push(testing::TestDescAndFn {
550552
desc: testing::TestDesc {
551-
name: testing::DynTestName(name),
553+
name: testing::DynTestName(name.clone()),
552554
ignore: should_ignore,
553555
// compiler failures are test failures
554556
should_panic: testing::ShouldPanic::No,
@@ -558,7 +560,7 @@ impl Collector {
558560
let panic = io::set_panic(None);
559561
let print = io::set_print(None);
560562
match {
561-
rustc_driver::in_rustc_thread(move || with_globals(move || {
563+
rustc_driver::in_named_rustc_thread(name, move || with_globals(move || {
562564
io::set_panic(panic);
563565
io::set_print(print);
564566
hygiene::set_default_edition(edition);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Issue #51162: A failed doctest was not printing its stdout/stderr
12+
// FIXME: if/when the output of the test harness can be tested on its own, this test should be
13+
// adapted to use that, and that normalize line can go away
14+
15+
// compile-flags:--test
16+
// normalize-stdout-test: "src/test/rustdoc-ui" -> "$$DIR"
17+
// failure-status: 101
18+
19+
// doctest fails at runtime
20+
/// ```
21+
/// panic!("oh no");
22+
/// ```
23+
pub struct SomeStruct;
24+
25+
// doctest fails at compile time
26+
/// ```
27+
/// no
28+
/// ```
29+
pub struct OtherStruct;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
running 2 tests
3+
test $DIR/failed-doctest-output.rs - OtherStruct (line 26) ... FAILED
4+
test $DIR/failed-doctest-output.rs - SomeStruct (line 20) ... FAILED
5+
6+
failures:
7+
8+
---- $DIR/failed-doctest-output.rs - OtherStruct (line 26) stdout ----
9+
error[E0425]: cannot find value `no` in this scope
10+
--> $DIR/failed-doctest-output.rs:27:1
11+
|
12+
3 | no
13+
| ^^ not found in this scope
14+
15+
thread '$DIR/failed-doctest-output.rs - OtherStruct (line 26)' panicked at 'couldn't compile the test', librustdoc/test.rs:332:13
16+
note: Run with `RUST_BACKTRACE=1` for a backtrace.
17+
18+
---- $DIR/failed-doctest-output.rs - SomeStruct (line 20) stdout ----
19+
thread '$DIR/failed-doctest-output.rs - SomeStruct (line 20)' panicked at 'test executable failed:
20+
21+
thread 'main' panicked at 'oh no', $DIR/failed-doctest-output.rs:3:1
22+
note: Run with `RUST_BACKTRACE=1` for a backtrace.
23+
24+
', librustdoc/test.rs:367:17
25+
26+
27+
failures:
28+
$DIR/failed-doctest-output.rs - OtherStruct (line 26)
29+
$DIR/failed-doctest-output.rs - SomeStruct (line 20)
30+
31+
test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out
32+

src/tools/compiletest/src/header.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -392,18 +392,20 @@ impl TestProps {
392392

393393
if let Some(code) = config.parse_failure_status(ln) {
394394
self.failure_status = code;
395-
} else {
396-
self.failure_status = match config.mode {
397-
Mode::RunFail => 101,
398-
_ => 1,
399-
};
400395
}
401396

402397
if !self.run_rustfix {
403398
self.run_rustfix = config.parse_run_rustfix(ln);
404399
}
405400
});
406401

402+
if self.failure_status == -1 {
403+
self.failure_status = match config.mode {
404+
Mode::RunFail => 101,
405+
_ => 1,
406+
};
407+
}
408+
407409
for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
408410
if let Ok(val) = env::var(key) {
409411
if self.exec_env.iter().find(|&&(ref x, _)| x == key).is_none() {

0 commit comments

Comments
 (0)