Skip to content

Commit 6b28db3

Browse files
authored
Rollup merge of rust-lang#78227 - SergioBenitez:test-stdout-threading, r=m-ou-se
Capture output from threads spawned in tests This is revival of rust-lang#75172. Original text: > Fixes rust-lang#42474. > > r? `@dtolnay` since you expressed interest in this, but feel free to redirect if you aren't the right person anymore. --- Closes rust-lang#75172.
2 parents bfb4439 + db15596 commit 6b28db3

14 files changed

+182
-12
lines changed

compiler/rustc_interface/src/util.rs

+5
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ impl Write for Sink {
113113
Ok(())
114114
}
115115
}
116+
impl io::LocalOutput for Sink {
117+
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
118+
Box::new(Self(self.0.clone()))
119+
}
120+
}
116121

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

library/std/src/io/impls.rs

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

221221
fn flush(&mut self) -> io::Result<()> {
222-
(**self).flush().map_err(|_| ErrorKind::Other.into())
222+
(*self).flush().map_err(|_| ErrorKind::Other.into())
223223
}
224224
}
225225

library/std/src/io/mod.rs

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

284+
pub(crate) use self::stdio::clone_io;
285+
284286
mod buffered;
285287
mod cursor;
286288
mod error;

library/std/src/io/stdio.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ use crate::thread::LocalKey;
1818

1919
thread_local! {
2020
/// Used by the test crate to capture the output of the print! and println! macros.
21-
static LOCAL_STDOUT: RefCell<Option<Box<dyn Write + Send>>> = {
21+
static LOCAL_STDOUT: RefCell<Option<Box<dyn LocalOutput>>> = {
2222
RefCell::new(None)
2323
}
2424
}
2525

2626
thread_local! {
2727
/// Used by the test crate to capture the output of the eprint! and eprintln! macros, and panics.
28-
static LOCAL_STDERR: RefCell<Option<Box<dyn Write + Send>>> = {
28+
static LOCAL_STDERR: RefCell<Option<Box<dyn LocalOutput>>> = {
2929
RefCell::new(None)
3030
}
3131
}
@@ -888,6 +888,18 @@ impl fmt::Debug for StderrLock<'_> {
888888
}
889889
}
890890

891+
/// A writer than can be cloned to new threads.
892+
#[unstable(
893+
feature = "set_stdio",
894+
reason = "this trait may disappear completely or be replaced \
895+
with a more general mechanism",
896+
issue = "none"
897+
)]
898+
#[doc(hidden)]
899+
pub trait LocalOutput: Write + Send {
900+
fn clone_box(&self) -> Box<dyn LocalOutput>;
901+
}
902+
891903
/// Resets the thread-local stderr handle to the specified writer
892904
///
893905
/// This will replace the current thread's stderr handle, returning the old
@@ -903,7 +915,7 @@ impl fmt::Debug for StderrLock<'_> {
903915
issue = "none"
904916
)]
905917
#[doc(hidden)]
906-
pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
918+
pub fn set_panic(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
907919
use crate::mem;
908920
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
909921
// LOCAL_STDERR is definitely None since LOCAL_STREAMS is false.
@@ -934,7 +946,7 @@ pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
934946
issue = "none"
935947
)]
936948
#[doc(hidden)]
937-
pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
949+
pub fn set_print(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
938950
use crate::mem;
939951
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
940952
// LOCAL_STDOUT is definitely None since LOCAL_STREAMS is false.
@@ -950,6 +962,22 @@ pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
950962
s
951963
}
952964

965+
pub(crate) fn clone_io() -> (Option<Box<dyn LocalOutput>>, Option<Box<dyn LocalOutput>>) {
966+
// Don't waste time when LOCAL_{STDOUT,STDERR} are definitely None.
967+
if !LOCAL_STREAMS.load(Ordering::Relaxed) {
968+
return (None, None);
969+
}
970+
971+
LOCAL_STDOUT.with(|stdout| {
972+
LOCAL_STDERR.with(|stderr| {
973+
(
974+
stdout.borrow().as_ref().map(|o| o.clone_box()),
975+
stderr.borrow().as_ref().map(|o| o.clone_box()),
976+
)
977+
})
978+
})
979+
}
980+
953981
/// Write `args` to output stream `local_s` if possible, `global_s`
954982
/// otherwise. `label` identifies the stream in a panic message.
955983
///
@@ -962,7 +990,7 @@ pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
962990
/// However, if the actual I/O causes an error, this function does panic.
963991
fn print_to<T>(
964992
args: fmt::Arguments<'_>,
965-
local_s: &'static LocalKey<RefCell<Option<Box<dyn Write + Send>>>>,
993+
local_s: &'static LocalKey<RefCell<Option<Box<dyn LocalOutput>>>>,
966994
global_s: fn() -> T,
967995
label: &str,
968996
) where

library/std/src/panicking.rs

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

221221
if let Some(mut local) = set_panic(None) {
222222
// NB. In `cfg(test)` this uses the forwarding impl
223-
// for `Box<dyn (::realstd::io::Write) + Send>`.
223+
// for `dyn ::realstd::io::LocalOutput`.
224224
write(&mut local);
225225
set_panic(Some(local));
226226
} else if let Some(mut out) = panic_output() {

library/std/src/thread/mod.rs

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

460+
let (stdout, stderr) = crate::io::clone_io();
461+
460462
let main = move || {
461463
if let Some(name) = their_thread.cname() {
462464
imp::Thread::set_name(name);
463465
}
464466

467+
crate::io::set_print(stdout);
468+
crate::io::set_panic(stderr);
469+
465470
// SAFETY: the stack guard passed is the one for the current thread.
466471
// This means the current thread's stack and the new thread's stack
467472
// are properly set and protected from each other.

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.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/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)