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

At test discovery, write to logfile in the same format as to stdout #123365

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5515,6 +5515,7 @@ dependencies = [
"libc",
"panic_abort",
"panic_unwind",
"rand",
"std",
]

Expand Down
3 changes: 3 additions & 0 deletions library/test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ core = { path = "../core" }
panic_unwind = { path = "../panic_unwind" }
panic_abort = { path = "../panic_abort" }
libc = { version = "0.2.150", default-features = false }

[dev-dependencies]
rand = { version = "0.8.5" }
140 changes: 98 additions & 42 deletions library/test/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use std::fs::File;
use std::io;
use std::io::prelude::Write;
use std::path::PathBuf;
use std::time::Instant;
use std::vec;

use super::{
bench::fmt_bench_samples,
Expand All @@ -19,6 +21,11 @@ use super::{
types::{NamePadding, TestDesc, TestDescAndFn},
};

pub trait Output {
fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()>;
fn write_plain(&mut self, word: &str) -> io::Result<()>;
}

/// Generic wrapper over stdout.
pub enum OutputLocation<T> {
Pretty(Box<term::StdoutTerminal>),
Expand All @@ -29,48 +36,98 @@ impl<T: Write> Write for OutputLocation<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
OutputLocation::Pretty(ref mut term) => term.write(buf),
OutputLocation::Raw(ref mut stdout) => stdout.write(buf),
OutputLocation::Raw(ref mut stdout_or_file) => stdout_or_file.write(buf),
}
}

fn flush(&mut self) -> io::Result<()> {
match *self {
OutputLocation::Pretty(ref mut term) => term.flush(),
OutputLocation::Raw(ref mut stdout) => stdout.flush(),
OutputLocation::Raw(ref mut stdout_or_file) => stdout_or_file.flush(),
}
}
}

pub struct ConsoleTestDiscoveryState {
pub log_out: Option<File>,
pub tests: usize,
pub benchmarks: usize,
pub ignored: usize,
impl<T: Write> Output for OutputLocation<T> {
fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> {
match self {
OutputLocation::Pretty(ref mut term) => {
term.fg(color)?;
term.write_all(word.as_bytes())?;
term.reset()?;
}
OutputLocation::Raw(ref mut stdout) => {
stdout.write_all(word.as_bytes())?;
}
}

self.flush()
}

fn write_plain(&mut self, word: &str) -> io::Result<()> {
self.write_all(word.as_bytes())?;
self.flush()
}
}

impl ConsoleTestDiscoveryState {
pub fn new(opts: &TestOpts) -> io::Result<ConsoleTestDiscoveryState> {
let log_out = match opts.logfile {
Some(ref path) => Some(File::create(path)?),
None => None,
struct OutputMultiplexer {
pub outputs: Vec<Box<dyn Output>>,
}

impl OutputMultiplexer {
pub fn new(lock_stdout: bool, logfile: &Option<PathBuf>) -> io::Result<Self> {
let mut outputs: Vec<Box<dyn Output>> = vec![];

if lock_stdout {
let output = match term::stdout() {
None => OutputLocation::Raw(io::stdout().lock()),
Some(t) => OutputLocation::Pretty(t),
};
outputs.push(Box::new(output))
} else {
let output = match term::stdout() {
None => OutputLocation::Raw(io::stdout()),
Some(t) => OutputLocation::Pretty(t),
};
outputs.push(Box::new(output))
}

match logfile {
Some(ref path) => outputs.push(Box::new(OutputLocation::Raw(File::create(path)?))),
None => (),
};

Ok(ConsoleTestDiscoveryState { log_out, tests: 0, benchmarks: 0, ignored: 0 })
Ok(Self { outputs })
}
}

pub fn write_log<F, S>(&mut self, msg: F) -> io::Result<()>
where
S: AsRef<str>,
F: FnOnce() -> S,
{
match self.log_out {
None => Ok(()),
Some(ref mut o) => {
let msg = msg();
let msg = msg.as_ref();
o.write_all(msg.as_bytes())
}
impl Output for OutputMultiplexer {
fn write_pretty(&mut self, word: &str, color: term::color::Color) -> io::Result<()> {
for output in &mut self.outputs {
output.write_pretty(word, color)?;
}

Ok(())
}

fn write_plain(&mut self, word: &str) -> io::Result<()> {
for output in &mut self.outputs {
output.write_plain(word)?;
}

Ok(())
}
}

pub struct ConsoleTestDiscoveryState {
pub tests: usize,
pub benchmarks: usize,
pub ignored: usize,
}

impl ConsoleTestDiscoveryState {
pub fn new() -> io::Result<ConsoleTestDiscoveryState> {
Ok(ConsoleTestDiscoveryState { tests: 0, benchmarks: 0, ignored: 0 })
}
}

Expand Down Expand Up @@ -171,21 +228,18 @@ impl ConsoleTestState {

// List the tests to console, and optionally to logfile. Filters are honored.
pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<()> {
let output = match term::stdout() {
None => OutputLocation::Raw(io::stdout().lock()),
Some(t) => OutputLocation::Pretty(t),
};

let mut multiplexer = OutputMultiplexer::new(true, &opts.logfile)?;
let mut out: Box<dyn OutputFormatter> = match opts.format {
OutputFormat::Pretty | OutputFormat::Junit => {
Box::new(PrettyFormatter::new(output, false, 0, false, None))
Box::new(PrettyFormatter::new(&mut multiplexer, false, 0, false, None))
}
OutputFormat::Terse => Box::new(TerseFormatter::new(output, false, 0, false)),
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
OutputFormat::Terse => Box::new(TerseFormatter::new(&mut multiplexer, false, 0, false)),
OutputFormat::Json => Box::new(JsonFormatter::new(&mut multiplexer)),
};
let mut st = ConsoleTestDiscoveryState::new(opts)?;

out.write_discovery_start()?;

let mut st = ConsoleTestDiscoveryState::new()?;
for test in filter_tests(opts, tests).into_iter() {
use crate::TestFn::*;

Expand All @@ -205,7 +259,6 @@ pub fn list_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Res
st.ignored += if desc.ignore { 1 } else { 0 };

out.write_test_discovered(&desc, fntype)?;
st.write_log(|| format!("{fntype} {}\n", desc.name))?;
}

out.write_discovery_finish(&st)
Expand Down Expand Up @@ -284,7 +337,7 @@ fn on_test_event(
/// A simple console test runner.
/// Runs provided tests reporting process and results to the stdout.
pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Result<bool> {
let output = match term::stdout() {
let mut output = match term::stdout() {
None => OutputLocation::Raw(io::stdout()),
Some(t) => OutputLocation::Pretty(t),
};
Expand All @@ -299,17 +352,20 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec<TestDescAndFn>) -> io::Resu

let mut out: Box<dyn OutputFormatter> = match opts.format {
OutputFormat::Pretty => Box::new(PrettyFormatter::new(
output,
&mut output,
opts.use_color(),
max_name_len,
is_multithreaded,
opts.time_options,
)),
OutputFormat::Terse => {
Box::new(TerseFormatter::new(output, opts.use_color(), max_name_len, is_multithreaded))
}
OutputFormat::Json => Box::new(JsonFormatter::new(output)),
OutputFormat::Junit => Box::new(JunitFormatter::new(output)),
OutputFormat::Terse => Box::new(TerseFormatter::new(
&mut output,
opts.use_color(),
max_name_len,
is_multithreaded,
)),
OutputFormat::Json => Box::new(JsonFormatter::new(&mut output)),
OutputFormat::Junit => Box::new(JunitFormatter::new(&mut output)),
};
let mut st = ConsoleTestState::new(opts)?;

Expand Down
16 changes: 8 additions & 8 deletions library/test/src/formatters/json.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use std::{borrow::Cow, io, io::prelude::Write};
use std::{borrow::Cow, io};

use super::OutputFormatter;
use crate::{
console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
console::{ConsoleTestDiscoveryState, ConsoleTestState, Output},
test_result::TestResult,
time,
types::TestDesc,
};

pub(crate) struct JsonFormatter<T> {
out: OutputLocation<T>,
pub(crate) struct JsonFormatter<'a> {
out: &'a mut dyn Output,
}

impl<T: Write> JsonFormatter<T> {
pub fn new(out: OutputLocation<T>) -> Self {
impl<'a> JsonFormatter<'a> {
pub fn new(out: &'a mut dyn Output) -> Self {
Self { out }
}

Expand All @@ -23,7 +23,7 @@ impl<T: Write> JsonFormatter<T> {
// by issuing `write_all` calls line-by-line.
assert_eq!(s.chars().last(), Some('\n'));

self.out.write_all(s.as_ref())
self.out.write_plain(s)
}

fn write_event(
Expand Down Expand Up @@ -56,7 +56,7 @@ impl<T: Write> JsonFormatter<T> {
}
}

impl<T: Write> OutputFormatter for JsonFormatter<T> {
impl OutputFormatter for JsonFormatter<'_> {
fn write_discovery_start(&mut self) -> io::Result<()> {
self.writeln_message(concat!(r#"{ "type": "suite", "event": "discovery" }"#, "\n"))
}
Expand Down
18 changes: 9 additions & 9 deletions library/test/src/formatters/junit.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
use std::io::{self, prelude::Write};
use std::io;
use std::time::Duration;

use super::OutputFormatter;
use crate::{
console::{ConsoleTestDiscoveryState, ConsoleTestState, OutputLocation},
console::{ConsoleTestDiscoveryState, ConsoleTestState, Output},
test_result::TestResult,
time,
types::{TestDesc, TestType},
};

pub struct JunitFormatter<T> {
out: OutputLocation<T>,
pub struct JunitFormatter<'a> {
out: &'a mut dyn Output,
results: Vec<(TestDesc, TestResult, Duration, Vec<u8>)>,
}

impl<T: Write> JunitFormatter<T> {
pub fn new(out: OutputLocation<T>) -> Self {
impl<'a> JunitFormatter<'a> {
pub fn new(out: &'a mut dyn Output) -> Self {
Self { out, results: Vec::new() }
}

fn write_message(&mut self, s: &str) -> io::Result<()> {
assert!(!s.contains('\n'));

self.out.write_all(s.as_ref())
self.out.write_plain(s)
}
}

Expand All @@ -38,7 +38,7 @@ fn str_to_cdata(s: &str) -> String {
format!("<![CDATA[{}]]>", escaped_output)
}

impl<T: Write> OutputFormatter for JunitFormatter<T> {
impl OutputFormatter for JunitFormatter<'_> {
fn write_discovery_start(&mut self) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::NotFound, "Not yet implemented!"))
}
Expand Down Expand Up @@ -179,7 +179,7 @@ impl<T: Write> OutputFormatter for JunitFormatter<T> {
self.write_message("</testsuite>")?;
self.write_message("</testsuites>")?;

self.out.write_all(b"\n")?;
self.out.write_plain("\n")?;

Ok(state.failed == 0)
}
Expand Down
Loading
Loading