`writeln!(std::io::stdout().lock(), "")` не может быть захвачен тестом груза

Я пытаюсь реализовать регистратор в своей многопоточной программе. Поэтому я попытался использовать std::io::stdout, чтобы получить StdoutLock для обеспечения атомарности. Но позже я обнаружил, что все записи журнала на стандартный вывод не могут быть записаны, когда cargo test.

Я написал демо-версию этого:

use std::io::Write as _;

pub fn print() {
    println!("Hello, world! (print)");
}

pub fn write() {
    let mut handle = std::io::stdout().lock();
    writeln!(&mut handle, "Hello, world! (write)").unwrap();
    handle.flush().unwrap();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_print() {
        print();
    }

    #[test]
    fn test_write() {
        write();
    }
}

При запуске cargo test печатается:

$ cargo test --lib

running 2 tests
Hello, world! (write)
test tests::test_print ... ok
test tests::test_write ... ok

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

Интересно, как избежать печати "Hello, world! (write)" при запуске тестов.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
4
0
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Захват стандартного вывода в libtest действительно не учитывает stdout(), это проблема №90785.

В идеале это будет исправлено в стандартном формате; до тех пор вы можете создать оболочку, которая на основе cfg(test) переключается между println!() и stdout().lock():

use std::io::{self, Write};

pub struct Stdout {
    #[cfg(not(test))]
    inner: std::io::StdoutLock<'static>,
}

impl Stdout {
    pub fn lock() -> Self {
        Self {
            #[cfg(not(test))]
            inner: std::io::stdout().lock(),
        }
    }
}

impl Write for Stdout {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        #[cfg(not(test))]
        {
            self.inner.write(buf)
        }
        #[cfg(test)]
        {
            println!("{}", std::str::from_utf8(buf).expect("non-UTF8 print"));
            Ok(buf.len())
        }
    }
    
    fn flush(&mut self) -> io::Result<()> {
        #[cfg(not(test))]
        {
            self.inner.flush()
        }
        #[cfg(test)]
        {
            Ok(())
        }
    }
}

pub fn write() {
    let mut handle = Stdout::lock();
    writeln!(&mut handle, "Hello, world! (write)").unwrap();
    // println!("Hello, world! (write)");
    handle.flush().unwrap();
}

Однако это не работает в интеграционных тестах, потому что они не устанавливают cfg(test).

Другие вопросы по теме