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 reasons(&self) -> &[FailureReason] {
162 if self.reasons.is_empty() {
163 std::array::from_ref(&FailureReason::ANY)
164 } else {
165 &self.reasons
166 }
167 }
168
169 pub fn validation_error(mut self, msg: &'static str) -> Self {
175 self.reasons
176 .push(FailureReason::validation_error().with_message(msg));
177 self
178 }
179
180 pub fn panic(mut self, msg: &'static str) -> Self {
186 self.reasons.push(FailureReason::panic().with_message(msg));
187 self
188 }
189
190 pub fn flaky(self) -> Self {
194 FailureCase {
195 behavior: FailureBehavior::Ignore,
196 ..self
197 }
198 }
199
200 pub(crate) fn applies_to_adapter(
209 &self,
210 info: &wgpu::AdapterInfo,
211 ) -> Option<FailureApplicationReasons> {
212 let mut reasons = FailureApplicationReasons::empty();
213
214 if let Some(backends) = self.backends {
215 if !backends.contains(wgpu::Backends::from(info.backend)) {
216 return None;
217 }
218 reasons.set(FailureApplicationReasons::BACKEND, true);
219 }
220 if let Some(vendor) = self.vendor {
221 if vendor != info.vendor {
222 return None;
223 }
224 reasons.set(FailureApplicationReasons::VENDOR, true);
225 }
226 if let Some(adapter) = self.adapter {
227 let adapter = adapter.to_lowercase();
228 if !info.name.contains(&adapter) {
229 return None;
230 }
231 reasons.set(FailureApplicationReasons::ADAPTER, true);
232 }
233 if let Some(driver) = self.driver {
234 let driver = driver.to_lowercase();
235 if !info.driver.contains(&driver) {
236 return None;
237 }
238 reasons.set(FailureApplicationReasons::DRIVER, true);
239 }
240
241 if reasons.is_empty() {
244 Some(FailureApplicationReasons::ALWAYS)
245 } else {
246 Some(reasons)
247 }
248 }
249
250 pub(crate) fn matches_failure(&self, failure: &FailureResult) -> bool {
252 for reason in self.reasons() {
253 let kind_matched = reason.kind.is_none_or(|kind| kind == failure.kind);
254
255 let message_matched =
256 reason
257 .message
258 .is_none_or(|message| matches!(&failure.message, Some(actual) if actual.to_lowercase().contains(&message.to_lowercase())));
259
260 if kind_matched && message_matched {
261 let message = failure.message.as_deref().unwrap_or("*no message*");
262 log::error!("Matched {} {message}", failure.kind);
263 return true;
264 }
265 }
266
267 false
268 }
269}
270
271bitflags::bitflags! {
272 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
274 pub struct FailureApplicationReasons: u8 {
275 const BACKEND = 1 << 0;
276 const VENDOR = 1 << 1;
277 const ADAPTER = 1 << 2;
278 const DRIVER = 1 << 3;
279 const ALWAYS = 1 << 4;
280 }
281}
282
283#[derive(Default, Debug, Clone, PartialEq)]
287pub struct FailureReason {
288 kind: Option<FailureResultKind>,
292 message: Option<&'static str>,
298}
299
300impl FailureReason {
301 const ANY: Self = Self {
303 kind: None,
304 message: None,
305 };
306
307 #[allow(dead_code)] pub fn validation_error() -> Self {
310 Self {
311 kind: Some(FailureResultKind::ValidationError),
312 message: None,
313 }
314 }
315
316 pub fn panic() -> Self {
318 Self {
319 kind: Some(FailureResultKind::Panic),
320 message: None,
321 }
322 }
323
324 pub fn with_message(self, message: &'static str) -> Self {
330 Self {
331 message: Some(message),
332 ..self
333 }
334 }
335}
336
337#[derive(Default, Clone, PartialEq)]
338pub enum FailureBehavior {
339 #[default]
343 AssertFailure,
344 Ignore,
349}
350
351#[derive(Debug, Clone, Copy, PartialEq)]
352pub(crate) enum FailureResultKind {
353 #[allow(dead_code)] ValidationError,
355 Panic,
356}
357
358impl fmt::Display for FailureResultKind {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self {
361 FailureResultKind::ValidationError => write!(f, "Validation Error"),
362 FailureResultKind::Panic => write!(f, "Panic"),
363 }
364 }
365}
366
367#[derive(Debug)]
368pub(crate) struct FailureResult {
369 kind: FailureResultKind,
370 message: Option<String>,
371}
372
373impl FailureResult {
374 pub(super) fn panic() -> Self {
376 Self {
377 kind: FailureResultKind::Panic,
378 message: None,
379 }
380 }
381
382 #[allow(dead_code)] pub(super) fn validation_error() -> Self {
385 Self {
386 kind: FailureResultKind::ValidationError,
387 message: None,
388 }
389 }
390
391 pub(super) fn with_message(self, message: impl fmt::Display) -> Self {
393 Self {
394 kind: self.kind,
395 message: Some(message.to_string()),
396 }
397 }
398}
399
400#[derive(PartialEq, Clone, Copy, Debug)]
401pub(crate) enum ExpectationMatchResult {
402 Panic,
403 Complete,
404}
405
406pub(crate) fn expectations_match_failures(
408 expectations: &[FailureCase],
409 mut actual: Vec<FailureResult>,
410) -> ExpectationMatchResult {
411 let mut result = ExpectationMatchResult::Complete;
413
414 for expected_failure in expectations {
416 let mut matched = false;
418
419 actual.retain(|failure| {
423 let matches = expected_failure.matches_failure(failure);
425
426 if matches {
427 matched = true;
428 }
429
430 !matches
432 });
433
434 if !matched && matches!(expected_failure.behavior, FailureBehavior::AssertFailure) {
437 result = ExpectationMatchResult::Panic;
438 log::error!(
439 "Expected to fail due to {:?}, but did not fail",
440 expected_failure.reasons()
441 );
442 }
443 }
444
445 if !actual.is_empty() {
448 result = ExpectationMatchResult::Panic;
449 for failure in actual {
450 let message = failure.message.as_deref().unwrap_or("*no message*");
451 log::error!("{}: {message}", failure.kind);
452 }
453 }
454
455 result
456}
457
458#[cfg(test)]
459mod test {
460 use crate::{
461 expectations::{ExpectationMatchResult, FailureResult},
462 init::init_logger,
463 FailureCase,
464 };
465
466 fn validation_err(msg: &'static str) -> FailureResult {
467 FailureResult::validation_error().with_message(msg)
468 }
469
470 fn panic(msg: &'static str) -> FailureResult {
471 FailureResult::panic().with_message(msg)
472 }
473
474 #[test]
475 fn simple_match() {
476 init_logger();
477
478 let expectation = vec![];
481 let actual = vec![FailureResult::validation_error()];
482
483 assert_eq!(
484 super::expectations_match_failures(&expectation, actual),
485 ExpectationMatchResult::Panic
486 );
487
488 let expectation = vec![FailureCase::always()];
491 let actual = vec![];
492
493 assert_eq!(
494 super::expectations_match_failures(&expectation, actual),
495 ExpectationMatchResult::Panic
496 );
497
498 let expectation = vec![FailureCase::always()];
501 let actual = vec![FailureResult::validation_error()];
502
503 assert_eq!(
504 super::expectations_match_failures(&expectation, actual),
505 ExpectationMatchResult::Complete
506 );
507
508 let expectation = vec![FailureCase::always()];
511 let actual = vec![FailureResult::panic()];
512
513 assert_eq!(
514 super::expectations_match_failures(&expectation, actual),
515 ExpectationMatchResult::Complete
516 );
517 }
518
519 #[test]
520 fn substring_match() {
521 init_logger();
522
523 let expectation: Vec<FailureCase> =
526 vec![FailureCase::always().validation_error("Some StrIng")];
527 let actual = vec![FailureResult::validation_error().with_message(
528 "a very long string that contains sOmE sTrInG of different capitalization",
529 )];
530
531 assert_eq!(
532 super::expectations_match_failures(&expectation, actual),
533 ExpectationMatchResult::Complete
534 );
535
536 let expectation = vec![FailureCase::always().validation_error("Some String")];
539 let actual = vec![validation_err("a very long string that doesn't contain it")];
540
541 assert_eq!(
542 super::expectations_match_failures(&expectation, actual),
543 ExpectationMatchResult::Panic
544 );
545 }
546
547 #[test]
548 fn ignore_flaky() {
549 init_logger();
550
551 let expectation = vec![FailureCase::always().validation_error("blah").flaky()];
552 let actual = vec![validation_err("some blah")];
553
554 assert_eq!(
555 super::expectations_match_failures(&expectation, actual),
556 ExpectationMatchResult::Complete
557 );
558
559 let expectation = vec![FailureCase::always().validation_error("blah").flaky()];
560 let actual = vec![];
561
562 assert_eq!(
563 super::expectations_match_failures(&expectation, actual),
564 ExpectationMatchResult::Complete
565 );
566 }
567
568 #[test]
569 fn matches_multiple_errors() {
570 init_logger();
571
572 let expectation = vec![FailureCase::always().validation_error("blah")];
575 let actual = vec![
576 validation_err("some blah"),
577 validation_err("some other blah"),
578 ];
579
580 assert_eq!(
581 super::expectations_match_failures(&expectation, actual),
582 ExpectationMatchResult::Complete
583 );
584
585 let expectation = vec![FailureCase::always().validation_error("blah")];
588 let actual = vec![
589 validation_err("some blah"),
590 validation_err("some other blah"),
591 validation_err("something else"),
592 ];
593
594 assert_eq!(
595 super::expectations_match_failures(&expectation, actual),
596 ExpectationMatchResult::Panic
597 );
598 }
599
600 #[test]
601 fn multi_reason_error() {
602 init_logger();
603
604 let expectation = vec![FailureCase::default()
605 .validation_error("blah")
606 .panic("panik")];
607 let actual = vec![
608 validation_err("my blah blah validation error"),
609 panic("my panik"),
610 ];
611
612 assert_eq!(
613 super::expectations_match_failures(&expectation, actual),
614 ExpectationMatchResult::Complete
615 );
616 }
617}