4

I have a function foo that can't be modified and contains println! and eprintln! code in it.

fn foo() {
    println!("hello");
}

After I call the function, I have to test what it printed so I want to capture the stdout/stderr into a variable.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Brad
  • 43
  • 4

3 Answers3

3

I strongly recommend against doing this, but if you are using nightly and don't mind using a feature that seems unlikely to ever be stabilized, you can directly capture stdout and stderr using hidden functionality of the standard library:

#![feature(internal_output_capture)]

use std::sync::Arc;

fn foo() {
    println!("hello");
    eprintln!("world");
}

fn main() {
    std::io::set_output_capture(Some(Default::default()));
    foo();
    let captured = std::io::set_output_capture(None);

    let captured = captured.unwrap();
    let captured = Arc::try_unwrap(captured).unwrap();
    let captured = captured.into_inner().unwrap();
    let captured = String::from_utf8(captured).unwrap();

    assert_eq!(captured, "hello\nworld\n");
}

It's very rare that a function "cannot be changed", so I'd encourage you to do so and use dependency injection instead. For example, if you are able to edit foo but do not want to change its signature, move all the code to a new function with generics which you can test directly:

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

fn foo() {
    foo_inner(io::stdout(), io::stderr()).unwrap()
}

fn foo_inner(mut out: impl Write, mut err: impl Write) -> io::Result<()> {
    writeln!(out, "hello")?;
    writeln!(err, "world")?;
    Ok(())
}

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

Not sure if this would work on windows, but should work on unix like systems. You should replace the file descriptor to something you can read later. I don't think it is really easy.

I would suggest to use stdio_override which already does that for you using files. You can redirect it, then execute the function and the read the file content.

From the example:

use stdio_override::StdoutOverride;
use std::fs;
let file_name = "./test.txt";
let guard = StdoutOverride::override_file(file_name)?;
println!("Isan to Stdout!");

let contents = fs::read_to_string(file_name)?;
assert_eq!("Isan to Stdout!\n", contents);
drop(guard);
println!("Outside!");

The library also support anything that implements AsRawFd, through the override_raw call. Confirming that it will probably just work on unix.

Otherwise, you can check on the implementation on how it is done internally, and maybe you could bypass a writer instead of a file somehow.

Netwave
  • 40,134
  • 6
  • 50
  • 93
1

Shadow println!:

use std::{fs::File, io::Write, mem::MaybeUninit, sync::Mutex};

static mut FILE: MaybeUninit<Mutex<File>> = MaybeUninit::uninit();

macro_rules! println {
    ($($tt:tt)*) => {{
        unsafe { writeln!(&mut FILE.assume_init_mut().lock().unwrap(), $($tt)*).unwrap(); }
    }}
}

fn foo() {
    println!("hello");
}

fn main() {
    unsafe {
        FILE.write(Mutex::new(File::create("out").unwrap()));
    }
    foo();
}
Acorn
  • 24,970
  • 5
  • 40
  • 69
  • This doesn't make sense. If `foo` is inside of the user's code, then they could change it. If it's not inside of their code, then shadowing `println` will have no effect. – Shepmaster May 10 '22 at 14:44
  • "*they could change it*" That contradicts what OP says: "*can't be modified*". OP writes "*I have*" -> hints at being their code but being unable to change it for a foreign reason. For example it could be homework. – Acorn May 10 '22 at 15:04
  • 1
    It's very interesting vision to solve the problem. Thank you very much. – Brad May 11 '22 at 04:11
  • My pleasure @Brad – Acorn May 11 '22 at 07:28