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 is_empty(&self) -> bool {
 32        self.ran.is_empty()
 33    }
 34
 35    pub fn total_count(&self) -> usize {
 36        self.run_count().max(self.max.unwrap_or(0))
 37    }
 38
 39    pub fn run_count(&self) -> usize {
 40        self.ran.len()
 41    }
 42
 43    pub fn passed_count(&self) -> usize {
 44        self.ran
 45            .iter()
 46            .filter(|a| a.result.as_ref().map_or(false, |result| result.passed))
 47            .count()
 48    }
 49
 50    pub fn passed_percentage(&self) -> f32 {
 51        if self.total_count() == 0 {
 52            0.0
 53        } else {
 54            (self.passed_count() as f32 / self.total_count() as f32) * 100.0
 55        }
 56    }
 57}
 58
 59const ROUND_WIDTH: usize = "Round".len();
 60const ASSERTIONS_WIDTH: usize = 42;
 61const RESULTS_WIDTH: usize = 8;
 62
 63pub fn print_table_header() {
 64    println!(
 65        "┌─{}─┬─{}─┬─{}─┐",
 66        "".repeat(ROUND_WIDTH),
 67        "".repeat(ASSERTIONS_WIDTH),
 68        "".repeat(RESULTS_WIDTH)
 69    );
 70
 71    println!(
 72        "{:^ROUND_WIDTH$}{:^ASSERTIONS_WIDTH$}{:^RESULTS_WIDTH$}",
 73        "Round", "Assertion", "Result"
 74    );
 75
 76    println!(
 77        "├─{}─┼─{}─┼─{}─┤",
 78        "".repeat(ROUND_WIDTH),
 79        "".repeat(ASSERTIONS_WIDTH),
 80        "".repeat(RESULTS_WIDTH)
 81    )
 82}
 83
 84pub fn display_error_row(f: &mut String, round: usize, error: String) -> fmt::Result {
 85    let last_two_columns = ASSERTIONS_WIDTH + RESULTS_WIDTH;
 86    writeln!(
 87        f,
 88        "│ {:^ROUND_WIDTH$} │ {:<last_two_columns$} |",
 89        round,
 90        truncate(&error, last_two_columns)
 91    )
 92}
 93
 94pub fn display_table_row(f: &mut String, round: usize, assertion: &RanAssertion) -> fmt::Result {
 95    let result = match &assertion.result {
 96        Ok(result) if result.passed => "\x1b[32m✔︎ Passed\x1b[0m",
 97        Ok(_) => "\x1b[31m✗ Failed\x1b[0m",
 98        Err(_) => "\x1b[31m💥 Judge Error\x1b[0m",
 99    };
100
101    writeln!(
102        f,
103        "│ {:^ROUND_WIDTH$} │ {:<ASSERTIONS_WIDTH$} │ {:>RESULTS_WIDTH$} │",
104        round,
105        truncate(&assertion.id, ASSERTIONS_WIDTH),
106        result
107    )
108}
109
110pub fn print_table_round_summary<'a>(
111    round: &str,
112    reports: impl Iterator<Item = &'a AssertionsReport>,
113) {
114    let mut passed = 0;
115    let mut total = 0;
116    for report in reports {
117        passed += report.passed_count();
118        total += report.total_count();
119    }
120
121    println!(
122        "{:^ROUND_WIDTH$}{:<ASSERTIONS_WIDTH$}{:>RESULTS_WIDTH$}",
123        round,
124        "total",
125        format!("{}%", (passed as f32 / total as f32 * 100.0).floor())
126    )
127}
128
129pub fn print_table_footer() {
130    println!(
131        "└─{}─┴─{}─┴─{}─┘",
132        "".repeat(ROUND_WIDTH),
133        "".repeat(ASSERTIONS_WIDTH),
134        "".repeat(RESULTS_WIDTH)
135    )
136}
137
138pub fn print_table_divider() {
139    println!(
140        "├─{}─┼─{}─┼─{}─┤",
141        "".repeat(ROUND_WIDTH),
142        "".repeat(ASSERTIONS_WIDTH),
143        "".repeat(RESULTS_WIDTH)
144    )
145}
146
147fn truncate(assertion: &str, max_width: usize) -> String {
148    if assertion.len() <= max_width {
149        assertion.to_string()
150    } else {
151        let mut end_ix = max_width - 1;
152        while !assertion.is_char_boundary(end_ix) {
153            end_ix -= 1;
154        }
155        format!("{}", &assertion[..end_ix])
156    }
157}