assertions.rs

  1use serde::{Deserialize, Serialize};
  2use std::fmt::Write;
  3use std::fmt::{self};
  4
  5#[derive(Default, Debug, Serialize, Deserialize, Clone)]
  6pub struct AssertionsReport {
  7    pub ran: Vec<RanAssertion>,
  8    pub max: Option<usize>,
  9}
 10
 11#[derive(Debug, Serialize, Deserialize, Clone)]
 12pub struct RanAssertion {
 13    pub id: String,
 14    pub result: Result<RanAssertionResult, String>,
 15}
 16
 17#[derive(Debug, Serialize, Deserialize, Clone)]
 18pub struct RanAssertionResult {
 19    pub analysis: Option<String>,
 20    pub passed: bool,
 21}
 22
 23impl AssertionsReport {
 24    pub fn new(max: Option<usize>) -> Self {
 25        AssertionsReport {
 26            ran: Vec::new(),
 27            max,
 28        }
 29    }
 30
 31    pub fn error(msg: String) -> Self {
 32        let assert = RanAssertion {
 33            id: "no-unhandled-errors".into(),
 34            result: Err(msg),
 35        };
 36        AssertionsReport {
 37            ran: vec![assert],
 38            max: Some(1),
 39        }
 40    }
 41
 42    pub fn is_empty(&self) -> bool {
 43        self.ran.is_empty()
 44    }
 45
 46    pub fn total_count(&self) -> usize {
 47        self.run_count().max(self.max.unwrap_or(0))
 48    }
 49
 50    pub fn run_count(&self) -> usize {
 51        self.ran.len()
 52    }
 53
 54    pub fn passed_count(&self) -> usize {
 55        self.ran
 56            .iter()
 57            .filter(|a| a.result.as_ref().is_ok_and(|result| result.passed))
 58            .count()
 59    }
 60
 61    pub fn passed_percentage(&self) -> f32 {
 62        if self.total_count() == 0 {
 63            0.0
 64        } else {
 65            (self.passed_count() as f32 / self.total_count() as f32) * 100.0
 66        }
 67    }
 68}
 69
 70const ROUND_WIDTH: usize = "Round".len();
 71const ASSERTIONS_WIDTH: usize = 42;
 72const RESULTS_WIDTH: usize = 8;
 73
 74pub fn print_table_header() {
 75    println!(
 76        "┌─{}─┬─{}─┬─{}─┐",
 77        "".repeat(ROUND_WIDTH),
 78        "".repeat(ASSERTIONS_WIDTH),
 79        "".repeat(RESULTS_WIDTH)
 80    );
 81
 82    println!(
 83        "{:^ROUND_WIDTH$}{:^ASSERTIONS_WIDTH$}{:^RESULTS_WIDTH$}",
 84        "Round", "Assertion", "Result"
 85    );
 86
 87    println!(
 88        "├─{}─┼─{}─┼─{}─┤",
 89        "".repeat(ROUND_WIDTH),
 90        "".repeat(ASSERTIONS_WIDTH),
 91        "".repeat(RESULTS_WIDTH)
 92    )
 93}
 94
 95pub fn display_error_row(f: &mut String, round: usize, error: String) -> fmt::Result {
 96    let last_two_columns = ASSERTIONS_WIDTH + RESULTS_WIDTH;
 97    writeln!(
 98        f,
 99        "│ {:^ROUND_WIDTH$} │ {:<last_two_columns$} |",
100        round,
101        truncate(&error, last_two_columns)
102    )
103}
104
105pub fn display_table_row(f: &mut String, round: usize, assertion: &RanAssertion) -> fmt::Result {
106    let result = match &assertion.result {
107        Ok(result) if result.passed => "\x1b[32m✔︎ Passed\x1b[0m",
108        Ok(_) => "\x1b[31m✗ Failed\x1b[0m",
109        Err(_) => "\x1b[31m💥 Judge Error\x1b[0m",
110    };
111
112    writeln!(
113        f,
114        "│ {:^ROUND_WIDTH$} │ {:<ASSERTIONS_WIDTH$} │ {:>RESULTS_WIDTH$} │",
115        round,
116        truncate(&assertion.id, ASSERTIONS_WIDTH),
117        result
118    )
119}
120
121pub fn print_table_round_summary<'a>(
122    round: &str,
123    reports: impl Iterator<Item = &'a AssertionsReport>,
124) {
125    let mut passed = 0;
126    let mut total = 0;
127    for report in reports {
128        passed += report.passed_count();
129        total += report.total_count();
130    }
131
132    println!(
133        "{:^ROUND_WIDTH$}{:<ASSERTIONS_WIDTH$}{:>RESULTS_WIDTH$}",
134        round,
135        "total",
136        format!("{}%", (passed as f32 / total as f32 * 100.0).floor())
137    )
138}
139
140pub fn print_table_footer() {
141    println!(
142        "└─{}─┴─{}─┴─{}─┘",
143        "".repeat(ROUND_WIDTH),
144        "".repeat(ASSERTIONS_WIDTH),
145        "".repeat(RESULTS_WIDTH)
146    )
147}
148
149pub fn print_table_divider() {
150    println!(
151        "├─{}─┼─{}─┼─{}─┤",
152        "".repeat(ROUND_WIDTH),
153        "".repeat(ASSERTIONS_WIDTH),
154        "".repeat(RESULTS_WIDTH)
155    )
156}
157
158fn truncate(assertion: &str, max_width: usize) -> String {
159    let is_verbose = std::env::var("VERBOSE").is_ok_and(|v| !v.is_empty());
160
161    if assertion.len() <= max_width || is_verbose {
162        assertion.to_string()
163    } else {
164        let mut end_ix = max_width - 1;
165        while !assertion.is_char_boundary(end_ix) {
166            end_ix -= 1;
167        }
168        format!("{}", &assertion[..end_ix])
169    }
170}