Skip to content

Capturing thread output during tests interacts poorly with global thread pools #113080

Open
@alexcrichton

Description

@alexcrichton

Tests by default capture their output of stdout/stderr using a libstd-private API, and these handles used to capture output are inherited by spawned threads of each test. This inheriting behavior interacts poorly for programs which have something akin to a global thread pool. Any test may spawn a thread added to the global thread pool and then that global thread's output is permanently fused to the original test that spawned it.

This ends up resulting in confusing situations such as rayon-rs/rayon#1066 where test panic messages are entirely swallowed by default and not displayed at all. A smaller (contrived) example of this is:

use std::sync::{Condvar, Mutex};
use std::thread::JoinHandle;

static LOCK: Mutex<Option<JoinHandle<()>>> = Mutex::new(None);
static COND: Condvar = Condvar::new();

#[test]
fn foo() {
    *LOCK.lock().unwrap() = Some(std::thread::spawn(|| {
        panic!("this message will not be seen");
    }));
    COND.notify_one();
}

#[test]
fn bar() {
    let lock = LOCK.lock().unwrap();
    let mut lock = COND.wait_while(lock, |state| state.is_none()).unwrap();
    if let Err(e) = lock.take().unwrap().join() {
        std::panic::resume_unwind(e);
    }
}

where here the foo test spawns some work which is then "resumed" in bar, but the panic message isn't actually displayed anywhere:

$ cargo test
    Finished test [unoptimized + debuginfo] target(s) in 0.01s
     Running unittests src/main.rs (target/debug/deps/wat-fc5370c265a065b8)

running 2 tests
test foo ... ok
test bar ... FAILED

failures:

failures:
    bar

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--bin wat`

I'm not sure that there's really an easy fix for this, so this is probably more of a "shouting into the void" style of issue, but figured I might as well open it after taking the time to investigate it anyway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-libtestArea: `#[test]` / the `test` libraryC-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions