diff --git a/crates/anstream/Cargo.toml b/crates/anstream/Cargo.toml index c8c5f214..1511b973 100644 --- a/crates/anstream/Cargo.toml +++ b/crates/anstream/Cargo.toml @@ -28,6 +28,8 @@ pre-release-replacements = [ default = ["auto", "wincon"] auto = ["dep:anstyle-query", "dep:colorchoice"] wincon = ["dep:anstyle-wincon"] +# Enable in `dev-dependencies` to make sure output is captured for tests +test = [] [dependencies] anstyle = { version = "1.0.0", path = "../anstyle" } diff --git a/crates/anstream/src/macros.rs b/crates/anstream/src/macros.rs index ea909594..c9d62ae4 100644 --- a/crates/anstream/src/macros.rs +++ b/crates/anstream/src/macros.rs @@ -22,6 +22,10 @@ /// Use `print!` only for the primary output of your program. Use /// [`eprint!`] instead to print error and progress messages. /// +/// **NOTE:** Not all `print!` calls will be captured in tests like [`std::print!`] +/// - Capturing will automatically be activated in test binaries +/// - Otherwise, only when the `test` feature is enabled +/// /// # Panics /// /// Panics if writing to `stdout` fails for any reason **except** broken pipe. @@ -56,14 +60,29 @@ #[macro_export] macro_rules! print { ($($arg:tt)*) => {{ - use std::io::Write as _; + if cfg!(any(feature = "test", test)) { + use std::io::Write as _; + + let stdio = std::io::stdout(); + let choice = $crate::AutoStream::choice(&stdio); + let buffer = $crate::Buffer::new(); + let mut stream = $crate::AutoStream::new(buffer, choice); + // Ignore errors rather than panic + let _ = ::std::write!(&mut stream, $($arg)*); + let buffer = stream.into_inner(); + // Should be UTF-8 but not wanting to panic + let buffer = String::from_utf8_lossy(buffer.as_bytes()); + ::std::print!("{}", buffer) + } else { + use std::io::Write as _; - let mut stream = $crate::stdout(); - match ::std::write!(&mut stream, $($arg)*) { - Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { - ::std::panic!("failed printing to stdout: {e}"); + let mut stream = $crate::stdout(); + match ::std::write!(&mut stream, $($arg)*) { + Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { + ::std::panic!("failed printing to stdout: {e}"); + } + Err(_) | Ok(_) => {} } - Err(_) | Ok(_) => {} } }}; } @@ -91,6 +110,10 @@ macro_rules! print { /// Use `println!` only for the primary output of your program. Use /// [`eprintln!`] instead to print error and progress messages. /// +/// **NOTE:** Not all `println!` calls will be captured in tests like [`std::println!`] +/// - Capturing will automatically be activated in test binaries +/// - Otherwise, only when the `test` feature is enabled +/// /// # Panics /// /// Panics if writing to `stdout` fails for any reason **except** broken pipe. @@ -118,14 +141,29 @@ macro_rules! println { $crate::print!("\n") }; ($($arg:tt)*) => {{ - use std::io::Write as _; + if cfg!(any(feature = "test", test)) { + use std::io::Write as _; + + let stdio = std::io::stdout(); + let choice = $crate::AutoStream::choice(&stdio); + let buffer = $crate::Buffer::new(); + let mut stream = $crate::AutoStream::new(buffer, choice); + // Ignore errors rather than panic + let _ = ::std::write!(&mut stream, $($arg)*); + let buffer = stream.into_inner(); + // Should be UTF-8 but not wanting to panic + let buffer = String::from_utf8_lossy(buffer.as_bytes()); + ::std::println!("{}", buffer) + } else { + use std::io::Write as _; - let mut stream = $crate::stdout(); - match ::std::writeln!(&mut stream, $($arg)*) { - Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { - ::std::panic!("failed printing to stdout: {e}"); + let mut stream = $crate::stdout(); + match ::std::writeln!(&mut stream, $($arg)*) { + Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { + ::std::panic!("failed printing to stdout: {e}"); + } + Err(_) | Ok(_) => {} } - Err(_) | Ok(_) => {} } }}; } @@ -139,6 +177,10 @@ macro_rules! println { /// Use `eprint!` only for error and progress messages. Use `print!` /// instead for the primary output of your program. /// +/// **NOTE:** Not all `eprint!` calls will be captured in tests like [`std::eprint!`] +/// - Capturing will automatically be activated in test binaries +/// - Otherwise, only when the `test` feature is enabled +/// /// # Panics /// /// Panics if writing to `stderr` fails for any reason **except** broken pipe. @@ -159,14 +201,29 @@ macro_rules! println { #[macro_export] macro_rules! eprint { ($($arg:tt)*) => {{ - use std::io::Write as _; + if cfg!(any(feature = "test", test)) { + use std::io::Write as _; + + let stdio = std::io::stderr(); + let choice = $crate::AutoStream::choice(&stdio); + let buffer = $crate::Buffer::new(); + let mut stream = $crate::AutoStream::new(buffer, choice); + // Ignore errors rather than panic + let _ = ::std::write!(&mut stream, $($arg)*); + let buffer = stream.into_inner(); + // Should be UTF-8 but not wanting to panic + let buffer = String::from_utf8_lossy(buffer.as_bytes()); + ::std::eprint!("{}", buffer) + } else { + use std::io::Write as _; - let mut stream = $crate::stderr(); - match ::std::write!(&mut stream, $($arg)*) { - Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { - ::std::panic!("failed printing to stdout: {e}"); + let mut stream = $crate::stderr(); + match ::std::write!(&mut stream, $($arg)*) { + Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { + ::std::panic!("failed printing to stdout: {e}"); + } + Err(_) | Ok(_) => {} } - Err(_) | Ok(_) => {} } }}; } @@ -180,6 +237,10 @@ macro_rules! eprint { /// Use `eprintln!` only for error and progress messages. Use `println!` /// instead for the primary output of your program. /// +/// **NOTE:** Not all `eprintln!` calls will be captured in tests like [`std::eprintln!`] +/// - Capturing will automatically be activated in test binaries +/// - Otherwise, only when the `test` feature is enabled +/// /// # Panics /// /// Panics if writing to `stderr` fails for any reason **except** broken pipe. @@ -203,14 +264,29 @@ macro_rules! eprintln { $crate::eprint!("\n") }; ($($arg:tt)*) => {{ - use std::io::Write as _; + if cfg!(any(feature = "test", test)) { + use std::io::Write as _; + + let stdio = std::io::stderr(); + let choice = $crate::AutoStream::choice(&stdio); + let buffer = $crate::Buffer::new(); + let mut stream = $crate::AutoStream::new(buffer, choice); + // Ignore errors rather than panic + let _ = ::std::write!(&mut stream, $($arg)*); + let buffer = stream.into_inner(); + // Should be UTF-8 but not wanting to panic + let buffer = String::from_utf8_lossy(buffer.as_bytes()); + ::std::eprintln!("{}", buffer) + } else { + use std::io::Write as _; - let mut stream = $crate::stderr(); - match ::std::writeln!(&mut stream, $($arg)*) { - Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { - ::std::panic!("failed printing to stdout: {e}"); + let mut stream = $crate::stderr(); + match ::std::writeln!(&mut stream, $($arg)*) { + Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { + ::std::panic!("failed printing to stdout: {e}"); + } + Err(_) | Ok(_) => {} } - Err(_) | Ok(_) => {} } }}; } diff --git a/crates/anstream/tests/macros.rs b/crates/anstream/tests/macros.rs new file mode 100644 index 00000000..f9df09c7 --- /dev/null +++ b/crates/anstream/tests/macros.rs @@ -0,0 +1,50 @@ +#[test] +#[cfg(feature = "auto")] +fn print() { + anstream::print!( + "{}This should be captured{}", + anstyle::AnsiColor::Red.on_default().render(), + anstyle::Reset.render() + ); +} + +#[test] +#[cfg(feature = "auto")] +fn println() { + anstream::println!( + "{}This should be captured{}", + anstyle::AnsiColor::Red.on_default().render(), + anstyle::Reset.render() + ); +} + +#[test] +#[cfg(feature = "auto")] +fn eprint() { + anstream::eprint!( + "{}This should be captured{}", + anstyle::AnsiColor::Red.on_default().render(), + anstyle::Reset.render() + ); +} + +#[test] +#[cfg(feature = "auto")] +fn eprintln() { + anstream::eprintln!( + "{}This should be captured{}", + anstyle::AnsiColor::Red.on_default().render(), + anstyle::Reset.render() + ); +} + +#[test] +#[cfg(feature = "auto")] +#[should_panic] +fn panic() { + anstream::panic!( + "{}This should be captured{}", + anstyle::AnsiColor::Red.on_default().render(), + anstyle::Reset.render() + ); +}