From d0d0e7820806bbb89a2733bae8f9aaa9c78345d0 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Tue, 4 Aug 2020 18:42:36 -0700 Subject: [PATCH 1/3] Capture output from threads spawned in tests Fixes #42474. --- compiler/rustc_interface/src/util.rs | 5 +++ library/std/src/io/impls.rs | 6 ++-- library/std/src/io/mod.rs | 4 ++- library/std/src/io/stdio.rs | 33 +++++++++++++++++--- library/std/src/panicking.rs | 2 +- library/std/src/thread/mod.rs | 5 +++ library/test/src/helpers/sink.rs | 7 +++++ src/test/ui/panic-while-printing.rs | 19 +++++++++-- src/test/ui/test-thread-capture.rs | 30 ++++++++++++++++++ src/test/ui/test-thread-capture.run.stdout | 21 +++++++++++++ src/test/ui/test-thread-nocapture.rs | 30 ++++++++++++++++++ src/test/ui/test-thread-nocapture.run.stderr | 2 ++ src/test/ui/test-thread-nocapture.run.stdout | 20 ++++++++++++ src/test/ui/threads-sendsync/task-stderr.rs | 5 +++ 14 files changed, 177 insertions(+), 12 deletions(-) create mode 100644 src/test/ui/test-thread-capture.rs create mode 100644 src/test/ui/test-thread-capture.run.stdout create mode 100644 src/test/ui/test-thread-nocapture.rs create mode 100644 src/test/ui/test-thread-nocapture.run.stderr create mode 100644 src/test/ui/test-thread-nocapture.run.stdout diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index c1b359c7d0de5..73003d8ebd6a3 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -113,6 +113,11 @@ impl Write for Sink { Ok(()) } } +impl io::LocalOutput for Sink { + fn clone_box(&self) -> Box { + Box::new(Self(self.0.clone())) + } +} /// Like a `thread::Builder::spawn` followed by a `join()`, but avoids the need /// for `'static` bounds. diff --git a/library/std/src/io/impls.rs b/library/std/src/io/impls.rs index e09e7ba978e86..66426101d278e 100644 --- a/library/std/src/io/impls.rs +++ b/library/std/src/io/impls.rs @@ -213,13 +213,13 @@ impl BufRead for Box { #[cfg(test)] /// This impl is only used by printing logic, so any error returned is always /// of kind `Other`, and should be ignored. -impl Write for Box { +impl Write for dyn ::realstd::io::LocalOutput { fn write(&mut self, buf: &[u8]) -> io::Result { - (**self).write(buf).map_err(|_| ErrorKind::Other.into()) + (*self).write(buf).map_err(|_| ErrorKind::Other.into()) } fn flush(&mut self) -> io::Result<()> { - (**self).flush().map_err(|_| ErrorKind::Other.into()) + (*self).flush().map_err(|_| ErrorKind::Other.into()) } } diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index d9d0380781925..e6efe6ec57eea 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -277,10 +277,12 @@ pub use self::stdio::{StderrLock, StdinLock, StdoutLock}; pub use self::stdio::{_eprint, _print}; #[unstable(feature = "libstd_io_internals", issue = "42788")] #[doc(no_inline, hidden)] -pub use self::stdio::{set_panic, set_print}; +pub use self::stdio::{set_panic, set_print, LocalOutput}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::util::{copy, empty, repeat, sink, Empty, Repeat, Sink}; +pub(crate) use self::stdio::clone_io; + mod buffered; mod cursor; mod error; diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index 36b49401591f5..5b50ce4c2ee8d 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -18,14 +18,14 @@ use crate::thread::LocalKey; thread_local! { /// Used by the test crate to capture the output of the print! and println! macros. - static LOCAL_STDOUT: RefCell>> = { + static LOCAL_STDOUT: RefCell>> = { RefCell::new(None) } } thread_local! { /// Used by the test crate to capture the output of the eprint! and eprintln! macros, and panics. - static LOCAL_STDERR: RefCell>> = { + static LOCAL_STDERR: RefCell>> = { RefCell::new(None) } } @@ -888,6 +888,18 @@ impl fmt::Debug for StderrLock<'_> { } } +/// A writer than can be cloned to new threads. +#[unstable( + feature = "set_stdio", + reason = "this trait may disappear completely or be replaced \ + with a more general mechanism", + issue = "none" +)] +#[doc(hidden)] +pub trait LocalOutput: Write + Send { + fn clone_box(&self) -> Box; +} + /// Resets the thread-local stderr handle to the specified writer /// /// This will replace the current thread's stderr handle, returning the old @@ -903,7 +915,7 @@ impl fmt::Debug for StderrLock<'_> { issue = "none" )] #[doc(hidden)] -pub fn set_panic(sink: Option>) -> Option> { +pub fn set_panic(sink: Option>) -> Option> { use crate::mem; if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) { // LOCAL_STDERR is definitely None since LOCAL_STREAMS is false. @@ -934,7 +946,7 @@ pub fn set_panic(sink: Option>) -> Option>) -> Option> { +pub fn set_print(sink: Option>) -> Option> { use crate::mem; if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) { // LOCAL_STDOUT is definitely None since LOCAL_STREAMS is false. @@ -950,6 +962,17 @@ pub fn set_print(sink: Option>) -> Option (Option>, Option>) { + LOCAL_STDOUT.with(|stdout| { + LOCAL_STDERR.with(|stderr| { + ( + stdout.borrow().as_ref().map(|o| o.clone_box()), + stderr.borrow().as_ref().map(|o| o.clone_box()), + ) + }) + }) +} + /// Write `args` to output stream `local_s` if possible, `global_s` /// otherwise. `label` identifies the stream in a panic message. /// @@ -962,7 +985,7 @@ pub fn set_print(sink: Option>) -> Option( args: fmt::Arguments<'_>, - local_s: &'static LocalKey>>>, + local_s: &'static LocalKey>>>, global_s: fn() -> T, label: &str, ) where diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index 8dceb12de87b8..17d5398b4ef3a 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -220,7 +220,7 @@ fn default_hook(info: &PanicInfo<'_>) { if let Some(mut local) = set_panic(None) { // NB. In `cfg(test)` this uses the forwarding impl - // for `Box`. + // for `dyn ::realstd::io::LocalOutput`. write(&mut local); set_panic(Some(local)); } else if let Some(mut out) = panic_output() { diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 45c10266ba255..bdb8fc7807b3a 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -457,11 +457,16 @@ impl Builder { let my_packet: Arc>>> = Arc::new(UnsafeCell::new(None)); let their_packet = my_packet.clone(); + let (stdout, stderr) = crate::io::clone_io(); + let main = move || { if let Some(name) = their_thread.cname() { imp::Thread::set_name(name); } + crate::io::set_print(stdout); + crate::io::set_panic(stderr); + // SAFETY: the stack guard passed is the one for the current thread. // This means the current thread's stack and the new thread's stack // are properly set and protected from each other. diff --git a/library/test/src/helpers/sink.rs b/library/test/src/helpers/sink.rs index aa7fe2487730e..dfbf0a3b72f54 100644 --- a/library/test/src/helpers/sink.rs +++ b/library/test/src/helpers/sink.rs @@ -6,6 +6,7 @@ use std::{ sync::{Arc, Mutex}, }; +#[derive(Clone)] pub struct Sink(Arc>>); impl Sink { @@ -14,6 +15,12 @@ impl Sink { } } +impl io::LocalOutput for Sink { + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + impl Write for Sink { fn write(&mut self, data: &[u8]) -> io::Result { Write::write(&mut *self.0.lock().unwrap(), data) diff --git a/src/test/ui/panic-while-printing.rs b/src/test/ui/panic-while-printing.rs index 7e9fa16b0847a..21fc12759f87c 100644 --- a/src/test/ui/panic-while-printing.rs +++ b/src/test/ui/panic-while-printing.rs @@ -5,7 +5,7 @@ use std::fmt; use std::fmt::{Display, Formatter}; -use std::io::set_panic; +use std::io::{self, set_panic, LocalOutput, Write}; pub struct A; @@ -15,8 +15,23 @@ impl Display for A { } } +struct Sink; +impl Write for Sink { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} +impl LocalOutput for Sink { + fn clone_box(&self) -> Box { + Box::new(Sink) + } +} + fn main() { - set_panic(Some(Box::new(Vec::new()))); + set_panic(Some(Box::new(Sink))); assert!(std::panic::catch_unwind(|| { eprintln!("{}", A); }) diff --git a/src/test/ui/test-thread-capture.rs b/src/test/ui/test-thread-capture.rs new file mode 100644 index 0000000000000..2f8f3163deb9e --- /dev/null +++ b/src/test/ui/test-thread-capture.rs @@ -0,0 +1,30 @@ +// compile-flags: --test +// run-fail +// run-flags: --test-threads=1 +// check-run-results +// exec-env:RUST_BACKTRACE=0 + +#[test] +fn thready_pass() { + println!("fee"); + std::thread::spawn(|| { + println!("fie"); + println!("foe"); + }) + .join() + .unwrap(); + println!("fum"); +} + +#[test] +fn thready_fail() { + println!("fee"); + std::thread::spawn(|| { + println!("fie"); + println!("foe"); + }) + .join() + .unwrap(); + println!("fum"); + panic!(); +} diff --git a/src/test/ui/test-thread-capture.run.stdout b/src/test/ui/test-thread-capture.run.stdout new file mode 100644 index 0000000000000..6a49de5f4ab9e --- /dev/null +++ b/src/test/ui/test-thread-capture.run.stdout @@ -0,0 +1,21 @@ + +running 2 tests +test thready_fail ... FAILED +test thready_pass ... ok + +failures: + +---- thready_fail stdout ---- +fee +fie +foe +fum +thread 'main' panicked at 'explicit panic', $DIR/test-thread-capture.rs:29:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + thready_fail + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out + diff --git a/src/test/ui/test-thread-nocapture.rs b/src/test/ui/test-thread-nocapture.rs new file mode 100644 index 0000000000000..c107c6c9dd327 --- /dev/null +++ b/src/test/ui/test-thread-nocapture.rs @@ -0,0 +1,30 @@ +// compile-flags: --test +// run-fail +// run-flags: --test-threads=1 --nocapture +// check-run-results +// exec-env:RUST_BACKTRACE=0 + +#[test] +fn thready_pass() { + println!("fee"); + std::thread::spawn(|| { + println!("fie"); + println!("foe"); + }) + .join() + .unwrap(); + println!("fum"); +} + +#[test] +fn thready_fail() { + println!("fee"); + std::thread::spawn(|| { + println!("fie"); + println!("foe"); + }) + .join() + .unwrap(); + println!("fum"); + panic!(); +} diff --git a/src/test/ui/test-thread-nocapture.run.stderr b/src/test/ui/test-thread-nocapture.run.stderr new file mode 100644 index 0000000000000..ce5244c13ab26 --- /dev/null +++ b/src/test/ui/test-thread-nocapture.run.stderr @@ -0,0 +1,2 @@ +thread 'main' panicked at 'explicit panic', $DIR/test-thread-nocapture.rs:29:5 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/test/ui/test-thread-nocapture.run.stdout b/src/test/ui/test-thread-nocapture.run.stdout new file mode 100644 index 0000000000000..77b42ed88d63f --- /dev/null +++ b/src/test/ui/test-thread-nocapture.run.stdout @@ -0,0 +1,20 @@ + +running 2 tests +test thready_fail ... fee +fie +foe +fum +FAILED +test thready_pass ... fee +fie +foe +fum +ok + +failures: + +failures: + thready_fail + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out + diff --git a/src/test/ui/threads-sendsync/task-stderr.rs b/src/test/ui/threads-sendsync/task-stderr.rs index d474084bf20ce..bc4bedac196ec 100644 --- a/src/test/ui/threads-sendsync/task-stderr.rs +++ b/src/test/ui/threads-sendsync/task-stderr.rs @@ -16,6 +16,11 @@ impl Write for Sink { } fn flush(&mut self) -> io::Result<()> { Ok(()) } } +impl io::LocalOutput for Sink { + fn clone_box(&self) -> Box { + Box::new(Sink(self.0.clone())) + } +} fn main() { let data = Arc::new(Mutex::new(Vec::new())); From db15596c5747eed0344f2622a9d85ad9534b23f9 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 22 Oct 2020 03:22:13 -0700 Subject: [PATCH 2/3] Only load LOCAL_STREAMS if they are being used --- library/std/src/io/stdio.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index 5b50ce4c2ee8d..2eb5fb4528620 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -963,6 +963,11 @@ pub fn set_print(sink: Option>) -> Option (Option>, Option>) { + // Don't waste time when LOCAL_{STDOUT,STDERR} are definitely None. + if !LOCAL_STREAMS.load(Ordering::Relaxed) { + return (None, None); + } + LOCAL_STDOUT.with(|stdout| { LOCAL_STDERR.with(|stderr| { ( From 7c4fe002131f1160f2054885ab40c74464951a64 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Mon, 26 Oct 2020 13:36:33 -0700 Subject: [PATCH 3/3] Ignore threaded capture tests on WASM w/o threads --- src/test/ui/test-thread-capture.rs | 1 + src/test/ui/test-thread-capture.run.stdout | 2 +- src/test/ui/test-thread-nocapture.rs | 1 + src/test/ui/test-thread-nocapture.run.stderr | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/ui/test-thread-capture.rs b/src/test/ui/test-thread-capture.rs index 2f8f3163deb9e..6bec48cd81678 100644 --- a/src/test/ui/test-thread-capture.rs +++ b/src/test/ui/test-thread-capture.rs @@ -3,6 +3,7 @@ // run-flags: --test-threads=1 // check-run-results // exec-env:RUST_BACKTRACE=0 +// ignore-emscripten no threads support #[test] fn thready_pass() { diff --git a/src/test/ui/test-thread-capture.run.stdout b/src/test/ui/test-thread-capture.run.stdout index 6a49de5f4ab9e..1102aadab02a9 100644 --- a/src/test/ui/test-thread-capture.run.stdout +++ b/src/test/ui/test-thread-capture.run.stdout @@ -10,7 +10,7 @@ fee fie foe fum -thread 'main' panicked at 'explicit panic', $DIR/test-thread-capture.rs:29:5 +thread 'main' panicked at 'explicit panic', $DIR/test-thread-capture.rs:30:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace diff --git a/src/test/ui/test-thread-nocapture.rs b/src/test/ui/test-thread-nocapture.rs index c107c6c9dd327..82df6e77cb1da 100644 --- a/src/test/ui/test-thread-nocapture.rs +++ b/src/test/ui/test-thread-nocapture.rs @@ -3,6 +3,7 @@ // run-flags: --test-threads=1 --nocapture // check-run-results // exec-env:RUST_BACKTRACE=0 +// ignore-emscripten no threads support #[test] fn thready_pass() { diff --git a/src/test/ui/test-thread-nocapture.run.stderr b/src/test/ui/test-thread-nocapture.run.stderr index ce5244c13ab26..98bd96d0abe83 100644 --- a/src/test/ui/test-thread-nocapture.run.stderr +++ b/src/test/ui/test-thread-nocapture.run.stderr @@ -1,2 +1,2 @@ -thread 'main' panicked at 'explicit panic', $DIR/test-thread-nocapture.rs:29:5 +thread 'main' panicked at 'explicit panic', $DIR/test-thread-nocapture.rs:30:5 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace