Skip to content

Commit

Permalink
Auto merge of rust-lang#78227 - SergioBenitez:test-stdout-threading, …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
bors committed Oct 27, 2020
2 parents 20b1e05 7c4fe00 commit 56d288f
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 12 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_interface/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 115,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
// 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

0 comments on commit 56d288f

Please sign in to comment.