Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture output from threads spawned in tests #78227

Merged
merged 3 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ impl Write for Sink {
Ok(())
}
}
impl io::LocalOutput for Sink {
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
Box::new(Self(self.0.clone()))
}
}

/// Like a `thread::Builder::spawn` followed by a `join()`, but avoids the need
/// for `'static` bounds.
Expand Down
6 changes: 3 additions & 3 deletions library/std/src/io/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,13 @@ impl<B: BufRead + ?Sized> BufRead for Box<B> {
#[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<dyn (::realstd::io::Write) + Send> {
impl Write for dyn ::realstd::io::LocalOutput {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
(**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())
}
}

Expand Down
4 changes: 3 additions & 1 deletion library/std/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
38 changes: 33 additions & 5 deletions library/std/src/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Box<dyn Write + Send>>> = {
static LOCAL_STDOUT: RefCell<Option<Box<dyn LocalOutput>>> = {
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<Option<Box<dyn Write + Send>>> = {
static LOCAL_STDERR: RefCell<Option<Box<dyn LocalOutput>>> = {
RefCell::new(None)
}
}
Expand Down Expand Up @@ -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<dyn LocalOutput>;
}

/// Resets the thread-local stderr handle to the specified writer
///
/// This will replace the current thread's stderr handle, returning the old
Expand All @@ -903,7 +915,7 @@ impl fmt::Debug for StderrLock<'_> {
issue = "none"
)]
#[doc(hidden)]
pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
pub fn set_panic(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
use crate::mem;
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
// LOCAL_STDERR is definitely None since LOCAL_STREAMS is false.
Expand Down Expand Up @@ -934,7 +946,7 @@ pub fn set_panic(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
issue = "none"
)]
#[doc(hidden)]
pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write + Send>> {
pub fn set_print(sink: Option<Box<dyn LocalOutput>>) -> Option<Box<dyn LocalOutput>> {
use crate::mem;
if sink.is_none() && !LOCAL_STREAMS.load(Ordering::Relaxed) {
// LOCAL_STDOUT is definitely None since LOCAL_STREAMS is false.
Expand All @@ -950,6 +962,22 @@ pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
s
}

pub(crate) fn clone_io() -> (Option<Box<dyn LocalOutput>>, Option<Box<dyn LocalOutput>>) {
// 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| {
(
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.
///
Expand All @@ -962,7 +990,7 @@ pub fn set_print(sink: Option<Box<dyn Write + Send>>) -> Option<Box<dyn Write +
/// However, if the actual I/O causes an error, this function does panic.
fn print_to<T>(
args: fmt::Arguments<'_>,
local_s: &'static LocalKey<RefCell<Option<Box<dyn Write + Send>>>>,
local_s: &'static LocalKey<RefCell<Option<Box<dyn LocalOutput>>>>,
global_s: fn() -> T,
label: &str,
) where
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn (::realstd::io::Write) + Send>`.
// for `dyn ::realstd::io::LocalOutput`.
write(&mut local);
set_panic(Some(local));
} else if let Some(mut out) = panic_output() {
Expand Down
5 changes: 5 additions & 0 deletions library/std/src/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,16 @@ impl Builder {
let my_packet: Arc<UnsafeCell<Option<Result<T>>>> = 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.
Expand Down
7 changes: 7 additions & 0 deletions library/test/src/helpers/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
sync::{Arc, Mutex},
};

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

impl Sink {
Expand All @@ -14,6 +15,12 @@ impl Sink {
}
}

impl io::LocalOutput for Sink {
fn clone_box(&self) -> Box<dyn io::LocalOutput> {
Box::new(self.clone())
}
}

impl Write for Sink {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
Write::write(&mut *self.0.lock().unwrap(), data)
Expand Down
19 changes: 17 additions & 2 deletions src/test/ui/panic-while-printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,8 +15,23 @@ impl Display for A {
}
}

struct Sink;
impl Write for Sink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl LocalOutput for Sink {
fn clone_box(&self) -> Box<dyn LocalOutput> {
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);
})
Expand Down
31 changes: 31 additions & 0 deletions src/test/ui/test-thread-capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// compile-flags: --test
// run-fail
// run-flags: --test-threads=1
// check-run-results
// exec-env:RUST_BACKTRACE=0
SergioBenitez marked this conversation as resolved.
Show resolved Hide resolved
// ignore-emscripten no threads support

#[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!();
}
21 changes: 21 additions & 0 deletions src/test/ui/test-thread-capture.run.stdout
Original file line number Diff line number Diff line change
@@ -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:30: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

31 changes: 31 additions & 0 deletions src/test/ui/test-thread-nocapture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// compile-flags: --test
// run-fail
// run-flags: --test-threads=1 --nocapture
// check-run-results
// exec-env:RUST_BACKTRACE=0
// ignore-emscripten no threads support

#[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!();
}
2 changes: 2 additions & 0 deletions src/test/ui/test-thread-nocapture.run.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
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
20 changes: 20 additions & 0 deletions src/test/ui/test-thread-nocapture.run.stdout
Original file line number Diff line number Diff line change
@@ -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

5 changes: 5 additions & 0 deletions src/test/ui/threads-sendsync/task-stderr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn io::LocalOutput> {
Box::new(Sink(self.0.clone()))
}
}

fn main() {
let data = Arc::new(Mutex::new(Vec::new()));
Expand Down