Skip to content

Commit 38b920d

Browse files
committed
Capture output from threads spawned in tests
Fixes #42474.
1 parent f9d422e commit 38b920d

14 files changed

+177
-12
lines changed

library/std/src/io/impls.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,13 @@ impl<B: BufRead + ?Sized> BufRead for Box<B> {
210210
#[cfg(test)]
211211
/// This impl is only used by printing logic, so any error returned is always
212212
/// of kind `Other`, and should be ignored.
213-
impl Write for Box<dyn (::realstd::io::Write) + Send> {
213+
impl Write for dyn ::realstd::io::LocalOutput {
214214
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
215-
(**self).write(buf).map_err(|_| ErrorKind::Other.into())
215+
(*self).write(buf).map_err(|_| ErrorKind::Other.into())
216216
}
217217

218218
fn flush(&mut self) -> io::Result<()> {
219-
(**self).flush().map_err(|_| ErrorKind::Other.into())
219+
(*self).flush().map_err(|_| ErrorKind::Other.into())
220220
}
221221
}
222222

library/std/src/io/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,12 @@ pub use self::stdio::{StderrLock, StdinLock, StdoutLock};
274274
pub use self::stdio::{_eprint, _print};
275275
#[unstable(feature = "libstd_io_internals", issue = "42788")]
276276
#[doc(no_inline, hidden)]
277-
pub use self::stdio::{set_panic, set_print};
277+
pub use self::stdio::{set_panic, set_print, LocalOutput};
278278
#[stable(feature = "rust1", since = "1.0.0")]
279279
pub use self::util::{copy, empty, repeat, sink, Empty, Repeat, Sink};
280280

281+
pub(crate) use self::stdio::clone_io;
282+
281283
mod buffered;
282284
mod cursor;
283285
mod error;

library/std/src/io/stdio.rs

+28-5
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ use crate::thread::LocalKey;
1313

1414
thread_local! {
1515
/// Stdout used by print! and println! macros
16-
static LOCAL_STDOUT: RefCell<Option<Box<dyn Write + Send>>> = {
16+
static LOCAL_STDOUT: RefCell<Option<Box<dyn LocalOutput>>> = {
1717
RefCell::new(None)
1818
}
1919
}
2020

2121
thread_local! {
2222
/// Stderr used by eprint! and eprintln! macros, and panics
23-
static LOCAL_STDERR: RefCell<Option<Box<dyn Write + Send>>> = {
23+
static LOCAL_STDERR: RefCell<Option<Box<dyn LocalOutput>>> = {
2424
RefCell::new(None)
2525
}
2626
}
@@ -903,6 +903,18 @@ impl fmt::Debug for StderrLock<'_> {
903903
}
904904
}
905905

906+
/// A writer than can be cloned to new threads.
907+
#[unstable(
908+
feature = "set_stdio",
909+
reason = "this trait may disappear completely or be replaced \
910+
with a more general mechanism",
911+
issue = "none"
912+
)]
913+
#[doc(hidden)]
914+
pub trait LocalOutput: Write + Send {
915+
fn clone_box(&self) -> Box<dyn LocalOutput>;
916+
}
917+
906918
/// Resets the thread-local stderr handle to the specified writer
907919
///
908920
/// This will replace the current thread's stderr handle, returning the old
@@ -918,7 +930,7 @@ impl fmt::Debug for StderrLock<'_> {
918930
issue = "none"
919931
)]
920932
#[doc(hidden)]
921-
pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
933+
pub fn set_panic(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
922934
use crate::mem;
923935
LOCAL_STDERR.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
924936
let _ = s.flush();
@@ -941,14 +953,25 @@ pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
941953
issue = "none"
942954
)]
943955
#[doc(hidden)]
944-
pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
956+
pub fn set_print(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
945957
use crate::mem;
946958
LOCAL_STDOUT.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
947959
let _ = s.flush();
948960
Some(s)
949961
})
950962
}
951963

964+
pub(crate) fn clone_io() -> (Option<Box<dyn LocalOutput>>, Option<Box<dyn LocalOutput>>) {
965+
LOCAL_STDOUT.with(|stdout| {
966+
LOCAL_STDERR.with(|stderr| {
967+
(
968+
stdout.borrow().as_ref().map(|o| o.clone_box()),
969+
stderr.borrow().as_ref().map(|o| o.clone_box()),
970+
)
971+
})
972+
})
973+
}
974+
952975
/// Write `args` to output stream `local_s` if possible, `global_s`
953976
/// otherwise. `label` identifies the stream in a panic message.
954977
///
@@ -961,7 +984,7 @@ pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
961984
/// However, if the actual I/O causes an error, this function does panic.
962985
fn print_to<T>(
963986
args: fmt::Arguments<'_>,
964-
local_s: &'static LocalKey<RefCell<Option<Box<dyn Write + Send>>>>,
987+
local_s: &'static LocalKey<RefCell<Option<Box<dyn LocalOutput>>>>,
965988
global_s: fn() -> T,
966989
label: &str,
967990
) where

library/std/src/panicking.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ fn default_hook(info: &PanicInfo<'_>) {
210210

211211
if let Some(mut local) = set_panic(None) {
212212
// NB. In `cfg(test)` this uses the forwarding impl
213-
// for `Box<dyn (::realstd::io::Write) + Send>`.
213+
// for `dyn ::realstd::io::LocalOutput`.
214214
write(&mut local);
215215
set_panic(Some(local));
216216
} else if let Some(mut out) = panic_output() {

library/std/src/thread/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,16 @@ impl Builder {
465465
let my_packet: Arc<UnsafeCell<Option<Result<T>>>> = Arc::new(UnsafeCell::new(None));
466466
let their_packet = my_packet.clone();
467467

468+
let (stdout, stderr) = crate::io::clone_io();
469+
468470
let main = move || {
469471
if let Some(name) = their_thread.cname() {
470472
imp::Thread::set_name(name);
471473
}
472474

475+
crate::io::set_print(stdout);
476+
crate::io::set_panic(stderr);
477+
473478
thread_info::set(imp::guard::current(), their_thread);
474479
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
475480
crate::sys_common::backtrace::__rust_begin_short_backtrace(f)

library/test/src/helpers/sink.rs

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
sync::{Arc, Mutex},
77
};
88

9+
#[derive(Clone)]
910
pub struct Sink(Arc<Mutex<Vec<u8>>>);
1011

1112
impl Sink {
@@ -14,6 +15,12 @@ impl Sink {
1415
}
1516
}
1617

18+
impl io::LocalOutput for Sink {
19+
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
20+
Box::new(Self(self.0.clone()))
21+
}
22+
}
23+
1724
impl Write for Sink {
1825
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
1926
Write::write(&mut *self.0.lock().unwrap(), data)

src/librustc_interface/util.rs

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ impl Write for Sink {
100100
Ok(())
101101
}
102102
}
103+
impl io::LocalOutput for Sink {
104+
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
105+
Box::new(Self(self.0.clone()))
106+
}
107+
}
103108

104109
/// Like a `thread::Builder::spawn` followed by a `join()`, but avoids the need
105110
/// for `'static` bounds.

src/test/ui/panic-while-printing.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
use std::fmt;
77
use std::fmt::{Display, Formatter};
8-
use std::io::set_panic;
8+
use std::io::{self, set_panic, LocalOutput, Write};
99

1010
pub struct A;
1111

@@ -15,8 +15,23 @@ impl Display for A {
1515
}
1616
}
1717

18+
struct Sink;
19+
impl Write for Sink {
20+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
21+
Ok(buf.len())
22+
}
23+
fn flush(&mut self) -> io::Result<()> {
24+
Ok(())
25+
}
26+
}
27+
impl LocalOutput for Sink {
28+
fn clone_box(&self) -> Box<dyn LocalOutput> {
29+
Box::new(Sink)
30+
}
31+
}
32+
1833
fn main() {
19-
set_panic(Some(Box::new(Vec::new())));
34+
set_panic(Some(Box::new(Sink)));
2035
assert!(std::panic::catch_unwind(|| {
2136
eprintln!("{}", A);
2237
})

src/test/ui/test-thread-capture.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// compile-flags: --test
2+
// run-fail
3+
// run-flags: --test-threads=1
4+
// check-run-results
5+
// exec-env:RUST_BACKTRACE=0
6+
7+
#[test]
8+
fn thready_pass() {
9+
println!("fee");
10+
std::thread::spawn(|| {
11+
println!("fie");
12+
println!("foe");
13+
})
14+
.join()
15+
.unwrap();
16+
println!("fum");
17+
}
18+
19+
#[test]
20+
fn thready_fail() {
21+
println!("fee");
22+
std::thread::spawn(|| {
23+
println!("fie");
24+
println!("foe");
25+
})
26+
.join()
27+
.unwrap();
28+
println!("fum");
29+
panic!();
30+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
running 2 tests
3+
test thready_fail ... FAILED
4+
test thready_pass ... ok
5+
6+
failures:
7+
8+
---- thready_fail stdout ----
9+
fee
10+
fie
11+
foe
12+
fum
13+
thread 'main' panicked at 'explicit panic', $DIR/test-thread-capture.rs:29:5
14+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
15+
16+
17+
failures:
18+
thready_fail
19+
20+
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
21+

src/test/ui/test-thread-nocapture.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// compile-flags: --test
2+
// run-fail
3+
// run-flags: --test-threads=1 --nocapture
4+
// check-run-results
5+
// exec-env:RUST_BACKTRACE=0
6+
7+
#[test]
8+
fn thready_pass() {
9+
println!("fee");
10+
std::thread::spawn(|| {
11+
println!("fie");
12+
println!("foe");
13+
})
14+
.join()
15+
.unwrap();
16+
println!("fum");
17+
}
18+
19+
#[test]
20+
fn thready_fail() {
21+
println!("fee");
22+
std::thread::spawn(|| {
23+
println!("fie");
24+
println!("foe");
25+
})
26+
.join()
27+
.unwrap();
28+
println!("fum");
29+
panic!();
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
thread 'main' panicked at 'explicit panic', $DIR/test-thread-nocapture.rs:29:5
2+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
running 2 tests
3+
test thready_fail ... fee
4+
fie
5+
foe
6+
fum
7+
FAILED
8+
test thready_pass ... fee
9+
fie
10+
foe
11+
fum
12+
ok
13+
14+
failures:
15+
16+
failures:
17+
thready_fail
18+
19+
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
20+

src/test/ui/threads-sendsync/task-stderr.rs

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ impl Write for Sink {
1616
}
1717
fn flush(&mut self) -> io::Result<()> { Ok(()) }
1818
}
19+
impl io::LocalOutput for Sink {
20+
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
21+
Box::new(Sink(self.0.clone()))
22+
}
23+
}
1924

2025
fn main() {
2126
let data = Arc::new(Mutex::new(Vec::new()));

0 commit comments

Comments
 (0)