1use core::fmt;
2
3#[derive(Default, Clone, PartialEq)]
40pub struct FailureCase {
41 pub backends: Option<wgpu::Backends>,
46
47 pub vendor: Option<u32>,
55
56 pub adapter: Option<&'static str>,
64
65 pub driver: Option<&'static str>,
73
74 pub reasons: Vec<FailureReason>,
80
81 pub behavior: FailureBehavior,
83}
84
85impl FailureCase {
86 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn always() -> Self {
93 FailureCase::default()
94 }
95
96 pub fn never() -> Self {
98 FailureCase {
99 backends: Some(wgpu::Backends::empty()),
100 ..FailureCase::default()
101 }
102 }
103
104 pub fn backend(backends: wgpu::Backends) -> Self {
106 FailureCase {
107 backends: Some(backends),
108 ..FailureCase::default()
109 }
110 }
111
112 pub fn adapter(adapter: &'static str) -> Self {
120 FailureCase {
121 adapter: Some(adapter),
122 ..FailureCase::default()
123 }
124 }
125
126 pub fn backend_adapter(backends: wgpu::Backends, adapter: &'static str) -> Self {
135 FailureCase {
136 backends: Some(backends),
137 adapter: Some(adapter),
138 ..FailureCase::default()
139 }
140 }
141
142 pub fn webgl2() -> Self {
144 #[cfg(target_arch = "wasm32")]
145 let case = FailureCase::backend(wgpu::Backends::GL);
146 #[cfg(not(target_arch = "wasm32"))]
147 let case = FailureCase::never();
148 case
149 }
150
151 pub fn molten_vk() -> Self {
153 FailureCase {
154 backends: Some(wgpu::Backends::VULKAN),
155 driver: Some("MoltenVK"),
156 ..FailureCase::default()
157 }
158 }
159
160 pub fn kosmic_krisp() -> Self {
162 FailureCase {
163 backends: Some(wgpu::Backends::VULKAN),
164 driver: Some("KosmicKrisp"),
165 ..FailureCase::default()
166 }
167 }
168
169 pub fn mac_vulkan(f: impl Fn(FailureCase) -> FailureCase) -> Vec<Self> {
171 vec![f(FailureCase::molten_vk()), f(FailureCase::kosmic_krisp())]
172 }
173
174 pub fn reasons(&self) -> &[FailureReason] {
176 if self.reasons.is_empty() {
177 std::array::from_ref(&FailureReason::ANY)
178 } else {
179 &self.reasons
180 }
181 }
182
183 pub fn validation_error(mut self, msg: &'static str) -> Self {
189 self.reasons
190 .push(FailureReason::validation_error().with_message(msg));
191 self
192 }
193
194 pub fn panic(mut self, msg: &'static str) -> Self {
200 self.reasons.push(FailureReason::panic().with_message(msg));
201 self
202 }
203
204 pub fn flaky(self) -> Self {
208 FailureCase {
209 behavior: FailureBehavior::Ignore,
210 ..self
211 }
212 }
213
214 pub(crate) fn applies_to_adapter(
223 &self,
224 info: &wgpu::AdapterInfo,
225 ) -> Option<FailureApplicationReasons> {
226 let mut reasons = FailureApplicationReasons::empty();
227
228 if let Some(backends) = self.backends {
229 if !backends.contains(wgpu::Backends::from(info.backend)) {
230 return None;
231 }
232 reasons.set(FailureApplicationReasons::BACKEND, true);
233 }
234 if let Some(vendor) = self.vendor {
235 if vendor != info.vendor {
236 return None;
237 }
238 reasons.set(FailureApplicationReasons::VENDOR, true);
239 }
240 if let Some(adapter) = self.adapter {
241 let adapter = adapter.to_lowercase();
242 if !info.name.contains(&adapter) {
243 return None;
244 }
245 reasons.set(FailureApplicationReasons::ADAPTER, true);
246 }
247 if let Some(driver) = self.driver {
248 let driver = driver.to_lowercase();
249 if !info.driver.contains(&driver) {
250 return None;
251 }
252 reasons.set(FailureApplicationReasons::DRIVER, true);
253 }
254
255 if reasons.is_empty() {
258 Some(FailureApplicationReasons::ALWAYS)
259 } else {
260 Some(reasons)
261 }
262 }
263
264 pub(crate) fn matches_failure(&self, failure: &FailureResult) -> bool {
266 for reason in self.reasons() {
267 let kind_matched = reason.kind.is_none_or(|kind| kind == failure.kind);
268
269 let message_matched =
270 reason
271 .message
272 .is_none_or(|message| matches!(&failure.message, Some(actual) if actual.to_lowercase().contains(&message.to_lowercase())));
273
274 if kind_matched && message_matched {
275 let message = failure.message.as_deref().unwrap_or("*no message*");
276 log::error!("Matched {} {message}", failure.kind);
277 return true;
278 }
279 }
280
281 false
282 }
283}
284
285bitflags::bitflags! {
286 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
288 pub struct FailureApplicationReasons: u8 {
289 const BACKEND = 1 << 0;
290 const VENDOR = 1 << 1;
291 const ADAPTER = 1 << 2;
292 const DRIVER = 1 << 3;
293 const ALWAYS = 1 << 4;
294 }
295}
296
297#[derive(Default, Debug, Clone, PartialEq)]
301pub struct FailureReason {
302 kind: Option<FailureResultKind>,
306 message: Option<&'static str>,
312}
313
314impl FailureReason {
315 const ANY: Self = Self {
317 kind: None,
318 message: None,
319 };
320
321 #[allow(dead_code, reason = "Not constructed on wasm")]
323 pub fn validation_error() -> Self {
324 Self {
325 kind: Some(FailureResultKind::ValidationError),
326 message: None,
327 }
328 }
329
330 pub fn panic() -> Self {
332 Self {
333 kind: Some(FailureResultKind::Panic),
334 message: None,
335 }
336 }
337
338 pub fn with_message(self, message: &'static str) -> Self {
344 Self {
345 message: Some(message),
346 ..self
347 }
348 }
349}
350
351#[derive(Default, Clone, PartialEq)]
352pub enum FailureBehavior {
353 #[default]
357 AssertFailure,
358 Ignore,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq)]
366pub(crate) enum FailureResultKind {
367 #[allow(dead_code, reason = "Not constructed on wasm")]
368 ValidationError,
369 Panic,
370}
371
372impl fmt::Display for FailureResultKind {
373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374 match self {
375 FailureResultKind::ValidationError => write!(f, "Validation Error"),
376 FailureResultKind::Panic => write!(f, "Panic"),
377 }
378 }
379}
380
381#[derive(Debug)]
382pub(crate) struct FailureResult {
383 kind: FailureResultKind,
384 message: Option<String>,
385}
386
387impl FailureResult {
388 pub(super) fn panic() -> Self {
390 Self {
391 kind: FailureResultKind::Panic,
392 message: None,
393 }
394 }
395
396 #[allow(dead_code, reason = "Not constructed on wasm")]
398 pub(super) fn validation_error() -> Self {
399 Self {
400 kind: FailureResultKind::ValidationError,
401 message: None,
402 }
403 }
404
405 pub(super) fn with_message(self, message: impl fmt::Display) -> Self {
407 Self {
408 kind: self.kind,
409 message: Some(message.to_string()),
410 }
411 }
412}
413
414#[derive(PartialEq, Clone, Copy, Debug)]
415pub(crate) enum ExpectationMatchResult {
416 Panic,
417 Complete,
418}
419
420pub(crate) fn expectations_match_failures(
422 expectations: &[FailureCase],
423 mut actual: Vec<FailureResult>,
424) -> ExpectationMatchResult {
425 let mut result = ExpectationMatchResult::Complete;
427
428 for expected_failure in expectations {
430 let mut matched = false;
432
433 actual.retain(|failure| {
437 let matches = expected_failure.matches_failure(failure);
439
440 if matches {
441 matched = true;
442 }
443
444 !matches
446 });
447
448 if !matched && matches!(expected_failure.behavior, FailureBehavior::AssertFailure) {
451 result = ExpectationMatchResult::Panic;
452 log::error!(
453 "Expected to fail due to {:?}, but did not fail",
454 expected_failure.reasons()
455 );
456 }
457 }
458
459 if !actual.is_empty() {
462 result = ExpectationMatchResult::Panic;
463 for failure in actual {
464 let message = failure.message.as_deref().unwrap_or("*no message*");
465 log::error!("{}: {message}", failure.kind);
466 }
467 }
468
469 result
470}
471
472#[cfg(test)]
473mod test {
474 use crate::{
475 expectations::{ExpectationMatchResult, FailureResult},
476 init::init_logger,
477 FailureCase,
478 };
479
480 fn validation_err(msg: &'static str) -> FailureResult {
481 FailureResult::validation_error().with_message(msg)
482 }
483
484 fn panic(msg: &'static str) -> FailureResult {
485 FailureResult::panic().with_message(msg)
486 }
487
488 #[test]
489 fn simple_match() {
490 init_logger();
491
492 let expectation = vec![];
495 let actual = vec![FailureResult::validation_error()];
496
497 assert_eq!(
498 super::expectations_match_failures(&expectation, actual),
499 ExpectationMatchResult::Panic
500 );
501
502 let expectation = vec![FailureCase::always()];
505 let actual = vec![];
506
507 assert_eq!(
508 super::expectations_match_failures(&expectation, actual),
509 ExpectationMatchResult::Panic
510 );
511
512 let expectation = vec![FailureCase::always()];
515 let actual = vec![FailureResult::validation_error()];
516
517 assert_eq!(
518 super::expectations_match_failures(&expectation, actual),
519 ExpectationMatchResult::Complete
520 );
521
522 let expectation = vec![FailureCase::always()];
525 let actual = vec![FailureResult::panic()];
526
527 assert_eq!(
528 super::expectations_match_failures(&expectation, actual),
529 ExpectationMatchResult::Complete
530 );
531 }
532
533 #[test]
534 fn substring_match() {
535 init_logger();
536
537 let expectation: Vec<FailureCase> =
540 vec![FailureCase::always().validation_error("Some StrIng")];
541 let actual = vec![FailureResult::validation_error().with_message(
542 "a very long string that contains sOmE sTrInG of different capitalization",
543 )];
544
545 assert_eq!(
546 super::expectations_match_failures(&expectation, actual),
547 ExpectationMatchResult::Complete
548 );
549
550 let expectation = vec![FailureCase::always().validation_error("Some String")];
553 let actual = vec![validation_err("a very long string that doesn't contain it")];
554
555 assert_eq!(
556 super::expectations_match_failures(&expectation, actual),
557 ExpectationMatchResult::Panic
558 );
559 }
560
561 #[test]
562 fn ignore_flaky() {
563 init_logger();
564
565 let expectation = vec![FailureCase::always().validation_error("blah").flaky()];
566 let actual = vec![validation_err("some blah")];
567
568 assert_eq!(
569 super::expectations_match_failures(&expectation, actual),
570 ExpectationMatchResult::Complete
571 );
572
573 let expectation = vec![FailureCase::always().validation_error("blah").flaky()];
574 let actual = vec![];
575
576 assert_eq!(
577 super::expectations_match_failures(&expectation, actual),
578 ExpectationMatchResult::Complete
579 );
580 }
581
582 #[test]
583 fn matches_multiple_errors() {
584 init_logger();
585
586 let expectation = vec![FailureCase::always().validation_error("blah")];
589 let actual = vec![
590 validation_err("some blah"),
591 validation_err("some other blah"),
592 ];
593
594 assert_eq!(
595 super::expectations_match_failures(&expectation, actual),
596 ExpectationMatchResult::Complete
597 );
598
599 let expectation = vec![FailureCase::always().validation_error("blah")];
602 let actual = vec![
603 validation_err("some blah"),
604 validation_err("some other blah"),
605 validation_err("something else"),
606 ];
607
608 assert_eq!(
609 super::expectations_match_failures(&expectation, actual),
610 ExpectationMatchResult::Panic
611 );
612 }
613
614 #[test]
615 fn multi_reason_error() {
616 init_logger();
617
618 let expectation = vec![FailureCase::default()
619 .validation_error("blah")
620 .panic("panik")];
621 let actual = vec![
622 validation_err("my blah blah validation error"),
623 panic("my panik"),
624 ];
625
626 assert_eq!(
627 super::expectations_match_failures(&expectation, actual),
628 ExpectationMatchResult::Complete
629 );
630 }
631}