use std::{fmt::Display, io, marker::PhantomData, time::Duration};
use crate::{
capture::OutputCapture,
formatter::{
common::{
CommonFormatter,
color::{ColorSetting, SupportsColor, colors::*},
fto,
fto::TestName,
label::{FromGroupCtx, FromGroupKey, GroupLabel},
},
*,
},
outcome::{TestFailure, TestStatus},
panic::PanicExpectation,
};
#[derive(Debug, Clone)]
pub struct PrettyFormatter<'t, W: io::Write, L, Extra> {
common: CommonFormatter<'t, W, L, Extra>,
}
impl<'t, W: io::Write, L, Extra> PrettyFormatter<'t, W, L, Extra> {
pub fn new() -> PrettyFormatter<'t, io::Stdout, GroupLabel<FromGroupKey>, Extra> {
PrettyFormatter::default()
}
pub fn with_target<WithTarget: io::Write>(
self,
target: WithTarget,
) -> PrettyFormatter<'t, WithTarget, L, Extra> {
PrettyFormatter {
common: CommonFormatter {
target,
color_setting: self.common.color_setting,
tests: self.common.tests,
_label_marker: PhantomData,
},
}
}
pub fn with_color_setting(self, color_setting: impl Into<ColorSetting>) -> Self {
Self {
common: CommonFormatter {
color_setting: color_setting.into(),
..self.common
},
}
}
pub fn with_group_label_from_key(
self,
) -> PrettyFormatter<'t, W, GroupLabel<FromGroupKey>, Extra> {
PrettyFormatter {
common: CommonFormatter {
target: self.common.target,
color_setting: self.common.color_setting,
tests: self.common.tests,
_label_marker: PhantomData,
},
}
}
pub fn with_group_label_from_ctx(
self,
) -> PrettyFormatter<'t, W, GroupLabel<FromGroupCtx>, Extra> {
PrettyFormatter {
common: CommonFormatter {
target: self.common.target,
color_setting: self.common.color_setting,
tests: self.common.tests,
_label_marker: PhantomData,
},
}
}
}
impl<'t, Extra> Default for PrettyFormatter<'t, io::Stdout, GroupLabel<FromGroupKey>, Extra> {
fn default() -> Self {
Self {
common: Default::default(),
}
}
}
impl<'t, W: io::Write + SupportsColor, L, Extra> PrettyFormatter<'t, W, L, Extra> {
pub fn use_color(&self) -> bool {
self.common.use_color()
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct PrettyTestOutcome<'t> {
pub name: &'t str,
pub status: TestStatus,
pub should_panic: PanicExpectation,
}
impl<'t, 'o, Extra> From<FmtTestOutcome<'t, 'o, Extra>> for PrettyTestOutcome<'t> {
fn from(value: FmtTestOutcome<'t, 'o, Extra>) -> Self {
Self {
name: value.meta.name.as_ref(),
status: value.outcome.status.clone(),
should_panic: value.meta.should_panic.clone(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PrettyRunOutcomes<'t> {
pub passed: usize,
pub failed: usize,
pub ignored: usize,
pub filtered_out: usize,
pub duration: Duration,
pub failures: Vec<PrettyFailure<'t>>,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PrettyFailure<'t> {
pub name: &'t str,
pub failure: TestFailure,
pub output: OutputCapture,
}
impl<'t, 'o> From<FmtRunOutcomes<'t, 'o>> for PrettyRunOutcomes<'t> {
fn from(value: FmtRunOutcomes<'t, 'o>) -> Self {
Self {
passed: value
.outcomes
.iter()
.map(|(_, outcome)| outcome)
.filter(|outcome| outcome.passed())
.count(),
failed: value
.outcomes
.iter()
.map(|(_, outcome)| outcome)
.filter(|outcome| outcome.failed())
.count(),
ignored: value
.outcomes
.iter()
.map(|(_, outcome)| outcome)
.filter(|outcome| outcome.ignored())
.count(),
filtered_out: value.filtered_out,
duration: value.duration,
failures: value
.outcomes
.iter()
.filter_map(|(name, outcome)| {
let TestStatus::Failed(failure) = &outcome.status else {
return None;
};
Some(PrettyFailure {
name,
failure: failure.clone(),
output: outcome.output.clone(),
})
})
.collect(),
}
}
}
impl<'t, Extra: 't + Sync, W: io::Write + SupportsColor + Send, L: Send> TestFormatter<'t, Extra>
for PrettyFormatter<'t, W, L, Extra>
{
type Error = io::Error;
type RunInit = fto::Tests<'t, Extra>;
fn fmt_run_init(&mut self, data: Self::RunInit) -> Result<(), Self::Error> {
self.common.fmt_run_init(data)
}
type RunStart = fto::TestCount;
fn fmt_run_start(&mut self, data: Self::RunStart) -> Result<(), Self::Error> {
self.common.fmt_run_start(data)
}
type TestOutcome = PrettyTestOutcome<'t>;
fn fmt_test_outcome(&mut self, data: Self::TestOutcome) -> Result<(), Self::Error> {
let use_color = self.use_color();
let target = &mut self.common.target;
write!(target, "test {}", data.name)?;
if let PanicExpectation::ShouldPanic | PanicExpectation::ShouldPanicWithExpected(..) =
data.should_panic
{
write!(target, " - should panic")?;
}
write!(target, " ... ")?;
match (data.status, use_color) {
(TestStatus::Passed, true) => write!(target, "{GREEN}ok{RESET}")?,
(TestStatus::Passed, false) => write!(target, "ok")?,
(
TestStatus::Ignored {
reason: Some(reason),
},
true,
) => write!(target, "{YELLOW}ignored, {reason}{RESET}")?,
(
TestStatus::Ignored {
reason: Some(reason),
},
false,
) => write!(target, "ignored, {reason}")?,
(TestStatus::Ignored { reason: None }, true) => {
write!(target, "{YELLOW}ignored{RESET}")?
}
(TestStatus::Ignored { reason: None }, false) => write!(target, "ignored")?,
(TestStatus::TimedOut, true) => write!(target, "{RED}timed out{RESET}")?,
(TestStatus::TimedOut, false) => write!(target, "timed out")?,
(TestStatus::Failed(_test_failure), true) => write!(target, "{RED}FAILED{RESET}")?,
(TestStatus::Failed(_test_failure), false) => write!(target, "FAILED")?,
(TestStatus::Other(_), true) => write!(target, "{CYAN}other{RESET}")?,
(TestStatus::Other(_), false) => write!(target, "other")?,
};
writeln!(target)
}
type RunOutcomes = fto::RunOutcomes<'t>;
fn fmt_run_outcomes(&mut self, data: Self::RunOutcomes) -> Result<(), Self::Error> {
self.common.fmt_run_outcomes(data)
}
type TestIgnored = ();
type TestStart = ();
}
impl<'t, W, L, Extra, GroupKey, GroupCtx> GroupedTestFormatter<'t, Extra, GroupKey, GroupCtx>
for PrettyFormatter<'t, W, L, Extra>
where
W: io::Write + SupportsColor + Send,
L: Send + Display,
Extra: 't + Sync,
GroupKey: 't,
GroupCtx: 't,
for<'b, 'g> L: From<&'b FmtGroupStart<'g, GroupKey, GroupCtx>>,
for<'o> L: From<(&'o GroupKey, Option<&'o GroupCtx>)>,
{
type GroupedRunStart = fto::TestCount;
fn fmt_grouped_run_start(&mut self, data: Self::GroupedRunStart) -> Result<(), Self::Error> {
self.common.fmt_grouped_run_start(data)
}
type GroupStart = fto::GroupStart<L>;
fn fmt_group_start(&mut self, data: Self::GroupStart) -> Result<(), Self::Error> {
self.common.fmt_group_start(data)
}
type GroupedRunOutcomes = fto::GroupedRunOutcomes<'t, L>;
fn fmt_grouped_run_outcomes(
&mut self,
data: Self::GroupedRunOutcomes,
) -> Result<(), Self::Error> {
self.common.fmt_grouped_run_outcomes(data)
}
type GroupOutcomes = ();
}
impl<'t, Extra: 't, W: io::Write, L> TestListFormatter<'t, Extra>
for PrettyFormatter<'t, W, L, Extra>
{
type Error = io::Error;
type ListTest = TestName<'t>;
fn fmt_list_test(&mut self, data: Self::ListTest) -> Result<(), Self::Error> {
writeln!(self.common.target, "{}: test", data.0)
}
type EndListing = fto::TestCount;
fn fmt_end_listing(&mut self, data: Self::EndListing) -> Result<(), Self::Error> {
match data.0 {
0 => writeln!(self.common.target, "0 tests"),
1 => writeln!(self.common.target, "\n1 test"),
n => writeln!(self.common.target, "\n{n} tests"),
}
}
type InitListing = ();
type BeginListing = ();
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PrettyListGroupStart<L> {
pub group_label: String,
pub _label_marker: PhantomData<L>,
}
impl<'g, L, GroupKey, GroupCtx> From<FmtListGroupStart<'g, GroupKey, GroupCtx>>
for PrettyListGroupStart<L>
where
for<'b> L: From<&'b FmtListGroupStart<'g, GroupKey, GroupCtx>> + Display,
{
fn from(value: FmtListGroupStart<'g, GroupKey, GroupCtx>) -> Self {
PrettyListGroupStart {
group_label: L::from(&value).to_string(),
_label_marker: PhantomData,
}
}
}
impl<'t, W, L, Extra, GroupKey, GroupCtx> GroupedTestListFormatter<'t, Extra, GroupKey, GroupCtx>
for PrettyFormatter<'t, W, L, Extra>
where
W: io::Write + SupportsColor + Send,
L: Send + Display,
GroupKey: 't,
GroupCtx: 't,
for<'b, 'g> L: From<&'b FmtListGroupStart<'g, GroupKey, GroupCtx>>,
{
type ListGroupStart = PrettyListGroupStart<L>;
fn fmt_list_group_start(&mut self, data: Self::ListGroupStart) -> Result<(), Self::Error> {
writeln!(self.common.target, "\ngroup {}", data.group_label)
}
type ListGroups = ();
type ListGroupEnd = ();
}