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 #75172

Closed
wants to merge 1 commit into from
Closed
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
6 changes: 3 additions & 3 deletions library/std/src/io/impls.rs
Original file line number Diff line number Diff line change
@@ -210,13 +210,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())
}
}

4 changes: 3 additions & 1 deletion library/std/src/io/mod.rs
Original file line number Diff line number Diff line change
@@ -274,10 +274,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;
33 changes: 28 additions & 5 deletions library/std/src/io/stdio.rs
Original file line number Diff line number Diff line change
@@ -13,14 +13,14 @@ use crate::thread::LocalKey;

thread_local! {
/// Stdout used by 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! {
/// Stderr used by 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)
}
}
@@ -903,6 +903,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
@@ -918,7 +930,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;
LOCAL_STDERR.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
let _ = s.flush();
@@ -941,14 +953,25 @@ 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;
LOCAL_STDOUT.with(move |slot| mem::replace(&mut *slot.borrow_mut(), sink)).and_then(|mut s| {
let _ = s.flush();
Some(s)
})
}

pub(crate) fn clone_io() -> (Option<Box<dyn LocalOutput>>, Option<Box<dyn LocalOutput>>) {
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.
///
@@ -961,7 +984,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
2 changes: 1 addition & 1 deletion library/std/src/panicking.rs
Original file line number Diff line number Diff line change
@@ -210,7 +210,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() {
5 changes: 5 additions & 0 deletions library/std/src/thread/mod.rs
Original file line number Diff line number Diff line change
@@ -465,11 +465,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);

thread_info::set(imp::guard::current(), their_thread);
let try_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
crate::sys_common::backtrace::__rust_begin_short_backtrace(f)
7 changes: 7 additions & 0 deletions library/test/src/helpers/sink.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ use std::{
sync::{Arc, Mutex},
};

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

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

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

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