use crate::{
config::CompiledDefaultFilter,
helpers::plural,
list::SkipCounts,
reporter::{
events::{CancelReason, FinalRunStats, RunStatsFailureKind},
helpers::Styles,
},
};
use owo_colors::OwoColorize;
use std::{
fmt,
io::{self, Write},
time::Duration,
};
pub(super) struct DisplayBracketedHhMmSs(pub(super) Duration);
impl fmt::Display for DisplayBracketedHhMmSs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total_secs = self.0.as_secs();
let secs = total_secs % 60;
let total_mins = total_secs / 60;
let mins = total_mins % 60;
let hours = total_mins / 60;
let out = format!("{hours:02}:{mins:02}:{secs:02}");
write!(f, "[{:>9}] ", out)
}
}
pub(super) struct DisplayBracketedDuration(pub(super) Duration);
impl fmt::Display for DisplayBracketedDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{:>8.3?}s] ", self.0.as_secs_f64())
}
}
pub(super) struct DisplayDurationBy(pub(super) Duration);
impl fmt::Display for DisplayDurationBy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "by {:>7.3?}s ", self.0.as_secs_f64())
}
}
pub(super) struct DisplaySlowDuration(pub(super) Duration);
impl fmt::Display for DisplaySlowDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[>{:>7.3?}s] ", self.0.as_secs_f64())
}
}
pub(super) fn write_skip_counts(
skip_counts: &SkipCounts,
default_filter: &CompiledDefaultFilter,
styles: &Styles,
writer: &mut dyn Write,
) -> io::Result<()> {
if skip_counts.skipped_tests > 0 || skip_counts.skipped_binaries > 0 {
write!(writer, " (")?;
write_skip_counts_impl(
skip_counts.skipped_tests,
skip_counts.skipped_binaries,
styles,
writer,
)?;
if skip_counts.skipped_tests == skip_counts.skipped_tests_default_filter
&& skip_counts.skipped_binaries == skip_counts.skipped_binaries_default_filter
{
write!(
writer,
" {} via {}",
"skipped".style(styles.skip),
default_filter.display_config(styles.count)
)?;
} else {
write!(writer, " {}", "skipped".style(styles.skip))?;
if skip_counts.skipped_binaries_default_filter > 0
|| skip_counts.skipped_tests_default_filter > 0
{
write!(writer, ", including ")?;
write_skip_counts_impl(
skip_counts.skipped_tests_default_filter,
skip_counts.skipped_binaries_default_filter,
styles,
writer,
)?;
write!(
writer,
" via {}",
default_filter.display_config(styles.count)
)?;
}
}
write!(writer, ")")?;
}
Ok(())
}
fn write_skip_counts_impl(
skipped_tests: usize,
skipped_binaries: usize,
styles: &Styles,
writer: &mut dyn Write,
) -> io::Result<()> {
if skipped_tests > 0 && skipped_binaries > 0 {
write!(
writer,
"{} {} and {} {}",
skipped_tests.style(styles.count),
plural::tests_str(skipped_tests),
skipped_binaries.style(styles.count),
plural::binaries_str(skipped_binaries),
)?;
} else if skipped_tests > 0 {
write!(
writer,
"{} {}",
skipped_tests.style(styles.count),
plural::tests_str(skipped_tests),
)?;
} else if skipped_binaries > 0 {
write!(
writer,
"{} {}",
skipped_binaries.style(styles.count),
plural::binaries_str(skipped_binaries),
)?;
}
Ok(())
}
pub(super) fn write_final_warnings(
final_stats: FinalRunStats,
cancel_status: Option<CancelReason>,
styles: &Styles,
writer: &mut dyn Write,
) -> io::Result<()> {
match final_stats {
FinalRunStats::Failed(RunStatsFailureKind::Test {
initial_run_count,
not_run,
})
| FinalRunStats::Cancelled(RunStatsFailureKind::Test {
initial_run_count,
not_run,
}) if not_run > 0 => {
if cancel_status == Some(CancelReason::TestFailure) {
writeln!(
writer,
"{}: {}/{} {} {} not run due to {} (run with {} to run all tests, or run with {})",
"warning".style(styles.skip),
not_run.style(styles.count),
initial_run_count.style(styles.count),
plural::tests_plural_if(initial_run_count != 1 || not_run != 1),
plural::were_plural_if(initial_run_count != 1 || not_run != 1),
CancelReason::TestFailure.to_static_str().style(styles.skip),
"--no-fail-fast".style(styles.count),
"--max-fail".style(styles.count),
)?;
} else {
let due_to_reason = match cancel_status {
Some(reason) => {
format!(" due to {}", reason.to_static_str().style(styles.skip))
}
None => "".to_string(),
};
writeln!(
writer,
"{}: {}/{} {} {} not run{}",
"warning".style(styles.skip),
not_run.style(styles.count),
initial_run_count.style(styles.count),
plural::tests_plural_if(initial_run_count != 1 || not_run != 1),
plural::were_plural_if(initial_run_count != 1 || not_run != 1),
due_to_reason,
)?;
}
}
_ => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::CompiledDefaultFilterSection;
use nextest_filtering::CompiledExpr;
#[test]
fn test_write_skip_counts() {
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 1,
skipped_tests_default_filter: 1,
skipped_binaries: 0,
skipped_binaries_default_filter: 0,
}, false), @" (1 test skipped via profile.my-profile.default-filter)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 2,
skipped_tests_default_filter: 2,
skipped_binaries: 0,
skipped_binaries_default_filter: 0,
}, false), @" (2 tests skipped via profile.my-profile.default-filter)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 1,
skipped_tests_default_filter: 0,
skipped_binaries: 0,
skipped_binaries_default_filter: 0,
}, false), @" (1 test skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 2,
skipped_tests_default_filter: 0,
skipped_binaries: 0,
skipped_binaries_default_filter: 0,
}, false), @" (2 tests skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 0,
skipped_tests_default_filter: 0,
skipped_binaries: 1,
skipped_binaries_default_filter: 1,
}, false), @" (1 binary skipped via profile.my-profile.default-filter)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 0,
skipped_tests_default_filter: 0,
skipped_binaries: 2,
skipped_binaries_default_filter: 2,
}, true), @" (2 binaries skipped via default-filter in profile.my-profile.overrides)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 0,
skipped_tests_default_filter: 0,
skipped_binaries: 1,
skipped_binaries_default_filter: 0,
}, false), @" (1 binary skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 0,
skipped_tests_default_filter: 0,
skipped_binaries: 2,
skipped_binaries_default_filter: 0,
}, false), @" (2 binaries skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 1,
skipped_tests_default_filter: 1,
skipped_binaries: 1,
skipped_binaries_default_filter: 1,
}, true), @" (1 test and 1 binary skipped via default-filter in profile.my-profile.overrides)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 2,
skipped_tests_default_filter: 2,
skipped_binaries: 3,
skipped_binaries_default_filter: 3,
}, false), @" (2 tests and 3 binaries skipped via profile.my-profile.default-filter)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 1,
skipped_tests_default_filter: 0,
skipped_binaries: 1,
skipped_binaries_default_filter: 0,
}, false), @" (1 test and 1 binary skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 2,
skipped_tests_default_filter: 0,
skipped_binaries: 3,
skipped_binaries_default_filter: 0,
}, false), @" (2 tests and 3 binaries skipped)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 1,
skipped_tests_default_filter: 0,
skipped_binaries: 1,
skipped_binaries_default_filter: 1,
}, true), @" (1 test and 1 binary skipped, including 1 binary via default-filter in profile.my-profile.overrides)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 3,
skipped_tests_default_filter: 2,
skipped_binaries: 1,
skipped_binaries_default_filter: 0,
}, false), @" (3 tests and 1 binary skipped, including 2 tests via profile.my-profile.default-filter)");
insta::assert_snapshot!(skip_counts_str(&SkipCounts {
skipped_tests: 0,
skipped_tests_default_filter: 0,
skipped_binaries: 0,
skipped_binaries_default_filter: 0,
}, false), @"");
}
fn skip_counts_str(skip_counts: &SkipCounts, override_section: bool) -> String {
let mut buf = Vec::new();
write_skip_counts(
skip_counts,
&CompiledDefaultFilter {
expr: CompiledExpr::ALL,
profile: "my-profile".to_owned(),
section: if override_section {
CompiledDefaultFilterSection::Override(0)
} else {
CompiledDefaultFilterSection::Profile
},
},
&Styles::default(),
&mut buf,
)
.unwrap();
String::from_utf8(buf).unwrap()
}
#[test]
fn test_final_warnings() {
let warnings = final_warnings_for(
FinalRunStats::Failed(RunStatsFailureKind::Test {
initial_run_count: 3,
not_run: 1,
}),
Some(CancelReason::TestFailure),
);
assert_eq!(
warnings,
"warning: 1/3 tests were not run due to test failure \
(run with --no-fail-fast to run all tests, or run with --max-fail)\n"
);
let warnings = final_warnings_for(
FinalRunStats::Failed(RunStatsFailureKind::Test {
initial_run_count: 8,
not_run: 5,
}),
Some(CancelReason::Signal),
);
assert_eq!(warnings, "warning: 5/8 tests were not run due to signal\n");
let warnings = final_warnings_for(
FinalRunStats::Cancelled(RunStatsFailureKind::Test {
initial_run_count: 1,
not_run: 1,
}),
Some(CancelReason::Interrupt),
);
assert_eq!(warnings, "warning: 1/1 test was not run due to interrupt\n");
let warnings = final_warnings_for(FinalRunStats::NoTestsRun, None);
assert_eq!(warnings, "");
let warnings = final_warnings_for(FinalRunStats::NoTestsRun, Some(CancelReason::Signal));
assert_eq!(warnings, "");
let warnings = final_warnings_for(FinalRunStats::Success, None);
assert_eq!(warnings, "");
let warnings = final_warnings_for(
FinalRunStats::Failed(RunStatsFailureKind::SetupScript),
Some(CancelReason::SetupScriptFailure),
);
assert_eq!(warnings, "");
let warnings = final_warnings_for(
FinalRunStats::Cancelled(RunStatsFailureKind::SetupScript),
Some(CancelReason::Interrupt),
);
assert_eq!(warnings, "");
}
fn final_warnings_for(stats: FinalRunStats, cancel_status: Option<CancelReason>) -> String {
let mut buf: Vec<u8> = Vec::new();
let styles = Styles::default();
write_final_warnings(stats, cancel_status, &styles, &mut buf).unwrap();
String::from_utf8(buf).unwrap()
}
}