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}