Skip to content

Commit

Permalink
Rollup 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
Dylan-DPC authored Oct 26, 2020
2 parents c27b862 + db15596 commit 489c4b0
Show file tree
Hide file tree
Showing 14 changed files with 182 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 @@ -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
30 changes: 30 additions & 0 deletions src/test/ui/test-thread-capture.rs
Original file line number Diff line number Diff line change
@@ -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!();
}
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: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

30 changes: 30 additions & 0 deletions src/test/ui/test-thread-nocapture.rs
Original file line number Diff line number Diff line change
@@ -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!();
}
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:29: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 489c4b0

Please sign in to comment.