henad_explore/testing/
report.rs1use std::fmt;
4
5use super::ModelCheck;
6
7#[derive(Debug)]
9pub struct ModelReport {
10 model_id: String,
11 failures: Vec<CheckFailure>,
12 skipped: Vec<SkippedCheck>,
13 thread_count_jobs: Option<usize>,
14}
15
16impl ModelReport {
17 pub(super) fn new(model_id: &str) -> Self {
18 Self {
19 model_id: model_id.to_owned(),
20 failures: Vec::new(),
21 skipped: Vec::new(),
22 thread_count_jobs: None,
23 }
24 }
25
26 pub(super) fn fail(&mut self, check: ModelCheck, message: String) {
27 self.failures.push(CheckFailure { check, message });
28 }
29
30 pub(super) fn skip(&mut self, check: ModelCheck, reason: SkipReason) {
31 self.skipped.push(SkippedCheck { check, reason });
32 }
33
34 pub(super) fn set_thread_count_jobs(&mut self, jobs: usize) {
35 self.thread_count_jobs = Some(jobs);
36 }
37
38 pub fn model_id(&self) -> &str {
40 &self.model_id
41 }
42
43 pub fn failures(&self) -> &[CheckFailure] {
45 &self.failures
46 }
47
48 pub fn skipped(&self) -> &[SkippedCheck] {
50 &self.skipped
51 }
52
53 pub fn thread_count_jobs(&self) -> Option<usize> {
55 self.thread_count_jobs
56 }
57
58 pub fn passed(&self) -> bool {
60 self.failures.is_empty()
61 }
62
63 pub fn skip_reason(&self, check: ModelCheck) -> Option<&SkipReason> {
65 self.skipped
66 .iter()
67 .find(|skipped| skipped.check == check)
68 .map(|skipped| &skipped.reason)
69 }
70
71 pub fn assert_passed(&self) {
77 assert!(self.passed(), "{self}");
78 }
79
80 fn summary(&self) -> String {
82 let names = |checks: &mut dyn Iterator<Item = ModelCheck>| {
83 checks.map(|check| check.to_string()).collect::<Vec<_>>().join(", ")
84 };
85 if self.failures.is_empty() {
86 format!(
87 "Model '{}' passed its checks, with {} skipped.",
88 self.model_id,
89 self.skipped.len()
90 )
91 } else {
92 format!(
93 "Model '{}' failed {} of its checks: {}.",
94 self.model_id,
95 self.failures.len(),
96 names(&mut self.failures.iter().map(CheckFailure::check))
97 )
98 }
99 }
100}
101
102impl fmt::Display for ModelReport {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 writeln!(f, "{}", self.summary())?;
106 for failure in &self.failures {
107 writeln!(f, " {failure}")?;
108 }
109 for skipped in &self.skipped {
110 writeln!(f, " {skipped}")?;
111 }
112 Ok(())
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct CheckFailure {
119 check: ModelCheck,
120 message: String,
121}
122
123impl CheckFailure {
124 pub fn check(&self) -> ModelCheck {
126 self.check
127 }
128
129 pub fn message(&self) -> &str {
131 &self.message
132 }
133}
134
135impl fmt::Display for CheckFailure {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(f, "{} failed: {}", self.check, self.message)
138 }
139}
140
141#[derive(Debug, Clone)]
143pub struct SkippedCheck {
144 check: ModelCheck,
145 reason: SkipReason,
146}
147
148impl SkippedCheck {
149 pub fn check(&self) -> ModelCheck {
151 self.check
152 }
153
154 pub fn reason(&self) -> &SkipReason {
156 &self.reason
157 }
158}
159
160impl fmt::Display for SkippedCheck {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 write!(f, "{} skipped: {}", self.check, self.reason)
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
168#[non_exhaustive]
169pub enum SkipReason {
170 OtherBackend,
172 InexactReplay,
174 Exempt(String),
176 NoDevice,
178 NativeOnly,
181 OneJob,
183 OneJobAtOverride(String),
185}
186
187impl fmt::Display for SkipReason {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 match self {
190 Self::OtherBackend => f.write_str("does not apply to this backend"),
191 Self::InexactReplay => f.write_str("the model declares that its runs do not replay exactly"),
192 Self::Exempt(reason) => write!(f, "exempt: {reason}"),
193 Self::NoDevice => f.write_str("no GPU device"),
194 Self::NativeOnly => f.write_str("runs on native targets only"),
195 Self::OneJob => f.write_str("one job at every size within bounds"),
196 Self::OneJobAtOverride(param_id) => write!(f, "one job at the {param_id} an override sets"),
197 }
198 }
199}
200
201#[derive(Debug)]
203pub struct SetReport {
204 reports: Vec<ModelReport>,
205 unknown_models: Vec<String>,
206}
207
208impl SetReport {
209 pub(super) fn new(reports: Vec<ModelReport>, unknown_models: Vec<String>) -> Self {
210 Self {
211 reports,
212 unknown_models,
213 }
214 }
215
216 pub fn reports(&self) -> &[ModelReport] {
218 &self.reports
219 }
220
221 pub fn unknown_models(&self) -> &[String] {
223 &self.unknown_models
224 }
225
226 pub fn passed(&self) -> bool {
228 self.unknown_models.is_empty() && self.reports.iter().all(ModelReport::passed)
229 }
230
231 pub fn assert_passed(&self) {
237 assert!(self.passed(), "{self}");
238 }
239}
240
241impl fmt::Display for SetReport {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 let failed = self.reports.iter().filter(|report| !report.passed()).count();
246 let checked = models(self.reports.len());
247 if failed == 0 {
248 write!(f, "{checked} checked, and none failed.")?;
249 } else {
250 write!(f, "{checked} checked, and {failed} failed.")?;
251 }
252 if !self.unknown_models.is_empty() {
253 write!(
254 f,
255 " The settings name {} the set lacks.",
256 models(self.unknown_models.len())
257 )?;
258 }
259 writeln!(f)?;
260 for id in &self.unknown_models {
261 writeln!(f, "The settings name model '{id}', which the set lacks.")?;
262 }
263 for report in &self.reports {
264 write!(f, "{report}")?;
265 }
266 Ok(())
267 }
268}
269
270fn models(count: usize) -> String {
272 if count == 1 {
273 "1 model".to_owned()
274 } else {
275 format!("{count} models")
276 }
277}