Skip to content

Commit adf1e3b

Browse files
authored
Improve test feedback and implementation (#5068)
1 parent 3716b8a commit adf1e3b

File tree

3 files changed

+132
-71
lines changed

3 files changed

+132
-71
lines changed

tests/src/expectations.rs

Lines changed: 117 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::fmt;
2+
13
/// Conditions under which a test should fail or be skipped.
24
///
35
/// By passing a `FailureCase` to [`TestParameters::expect_fail`][expect_fail], you can
@@ -15,7 +17,7 @@
1517
/// vendor: None,
1618
/// adapter: Some("RTX"),
1719
/// driver: None,
18-
/// reasons: vec![FailureReason::ValidationError(Some("Some error substring"))],
20+
/// reasons: vec![FailureReason::validation_error().with_message("Some error substring")],
1921
/// behavior: FailureBehavior::AssertFailure,
2022
/// }
2123
/// # ;
@@ -158,7 +160,7 @@ impl FailureCase {
158160
/// Return the reasons why this case should fail.
159161
pub fn reasons(&self) -> &[FailureReason] {
160162
if self.reasons.is_empty() {
161-
std::array::from_ref(&FailureReason::Any)
163+
std::array::from_ref(&FailureReason::ANY)
162164
} else {
163165
&self.reasons
164166
}
@@ -170,7 +172,8 @@ impl FailureCase {
170172
///
171173
/// If multiple reasons are pushed, will match any of them.
172174
pub fn validation_error(mut self, msg: &'static str) -> Self {
173-
self.reasons.push(FailureReason::ValidationError(Some(msg)));
175+
self.reasons
176+
.push(FailureReason::validation_error().with_message(msg));
174177
self
175178
}
176179

@@ -180,7 +183,7 @@ impl FailureCase {
180183
///
181184
/// If multiple reasons are pushed, will match any of them.
182185
pub fn panic(mut self, msg: &'static str) -> Self {
183-
self.reasons.push(FailureReason::Panic(Some(msg)));
186+
self.reasons.push(FailureReason::panic().with_message(msg));
184187
self
185188
}
186189

@@ -247,43 +250,16 @@ impl FailureCase {
247250
/// Returns true if the given failure "satisfies" this failure case.
248251
pub(crate) fn matches_failure(&self, failure: &FailureResult) -> bool {
249252
for reason in self.reasons() {
250-
let result = match (reason, failure) {
251-
(FailureReason::Any, _) => {
252-
log::error!("Matched failure case: Wildcard");
253-
true
254-
}
255-
(FailureReason::ValidationError(None), FailureResult::ValidationError(_)) => {
256-
log::error!("Matched failure case: Any Validation Error");
257-
true
258-
}
259-
(
260-
FailureReason::ValidationError(Some(expected)),
261-
FailureResult::ValidationError(Some(actual)),
262-
) => {
263-
let result = actual.to_lowercase().contains(&expected.to_lowercase());
264-
if result {
265-
log::error!(
266-
"Matched failure case: Validation Error containing \"{}\"",
267-
expected
268-
);
269-
}
270-
result
271-
}
272-
(FailureReason::Panic(None), FailureResult::Panic(_)) => {
273-
log::error!("Matched failure case: Any Panic");
274-
true
275-
}
276-
(FailureReason::Panic(Some(expected)), FailureResult::Panic(Some(actual))) => {
277-
let result = actual.to_lowercase().contains(&expected.to_lowercase());
278-
if result {
279-
log::error!("Matched failure case: Panic containing \"{}\"", expected);
280-
}
281-
result
282-
}
283-
_ => false,
284-
};
285-
286-
if result {
253+
let kind_matched = reason.kind.map_or(true, |kind| kind == failure.kind);
254+
255+
let message_matched =
256+
reason
257+
.message
258+
.map_or(true, |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);
287263
return true;
288264
}
289265
}
@@ -308,18 +284,54 @@ bitflags::bitflags! {
308284
///
309285
/// If the test fails for a different reason, the given FailureCase will be ignored.
310286
#[derive(Default, Debug, Clone, PartialEq)]
311-
pub enum FailureReason {
312-
/// Matches any failure.
313-
#[default]
314-
Any,
315-
/// Matches validation errors raised from the backend validation.
287+
pub struct FailureReason {
288+
/// Match a particular kind of failure result.
316289
///
317-
/// If a string is provided, matches only validation errors that contain the string.
318-
ValidationError(Option<&'static str>),
319-
/// A panic was raised.
290+
/// If `None`, match any result kind.
291+
kind: Option<FailureResultKind>,
292+
/// Match a particular message of a failure result.
320293
///
321-
/// If a string is provided, matches only panics that contain the string.
322-
Panic(Option<&'static str>),
294+
/// If `None`, matches any message. If `Some`, a case-insensitive sub-string
295+
/// test is performed. Allowing `"error occured"` to match a message like
296+
/// `"An unexpected Error occured!"`.
297+
message: Option<&'static str>,
298+
}
299+
300+
impl FailureReason {
301+
/// Match any failure reason.
302+
const ANY: Self = Self {
303+
kind: None,
304+
message: None,
305+
};
306+
307+
/// Match a validation error.
308+
#[allow(dead_code)] // Not constructed on wasm
309+
pub fn validation_error() -> Self {
310+
Self {
311+
kind: Some(FailureResultKind::ValidationError),
312+
message: None,
313+
}
314+
}
315+
316+
/// Match a panic.
317+
pub fn panic() -> Self {
318+
Self {
319+
kind: Some(FailureResultKind::Panic),
320+
message: None,
321+
}
322+
}
323+
324+
/// Match an error with a message.
325+
///
326+
/// If specified, a case-insensitive sub-string test is performed. Allowing
327+
/// `"error occured"` to match a message like `"An unexpected Error
328+
/// occured!"`.
329+
pub fn with_message(self, message: &'static str) -> Self {
330+
Self {
331+
message: Some(message),
332+
..self
333+
}
334+
}
323335
}
324336

325337
#[derive(Default, Clone)]
@@ -336,11 +348,53 @@ pub enum FailureBehavior {
336348
Ignore,
337349
}
338350

351+
#[derive(Debug, Clone, Copy, PartialEq)]
352+
pub(crate) enum FailureResultKind {
353+
#[allow(dead_code)] // Not constructed on wasm
354+
ValidationError,
355+
Panic,
356+
}
357+
358+
impl 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+
339367
#[derive(Debug)]
340-
pub(crate) enum FailureResult {
368+
pub(crate) struct FailureResult {
369+
kind: FailureResultKind,
370+
message: Option<String>,
371+
}
372+
373+
impl FailureResult {
374+
/// Failure result is a panic.
375+
pub(super) fn panic() -> Self {
376+
Self {
377+
kind: FailureResultKind::Panic,
378+
message: None,
379+
}
380+
}
381+
382+
/// Failure result is a validation error.
341383
#[allow(dead_code)] // Not constructed on wasm
342-
ValidationError(Option<String>),
343-
Panic(Option<String>),
384+
pub(super) fn validation_error() -> Self {
385+
Self {
386+
kind: FailureResultKind::ValidationError,
387+
message: None,
388+
}
389+
}
390+
391+
/// Message associated with a failure result.
392+
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+
}
344398
}
345399

346400
#[derive(PartialEq, Clone, Copy, Debug)]
@@ -393,7 +447,8 @@ pub(crate) fn expectations_match_failures(
393447
if !actual.is_empty() {
394448
result = ExpectationMatchResult::Panic;
395449
for failure in actual {
396-
log::error!("Unexpected failure due to: {:?}", failure);
450+
let message = failure.message.as_deref().unwrap_or("*no message*");
451+
log::error!("{}: {message}", failure.kind);
397452
}
398453
}
399454

@@ -409,11 +464,11 @@ mod test {
409464
};
410465

411466
fn validation_err(msg: &'static str) -> FailureResult {
412-
FailureResult::ValidationError(Some(String::from(msg)))
467+
FailureResult::validation_error().with_message(msg)
413468
}
414469

415470
fn panic(msg: &'static str) -> FailureResult {
416-
FailureResult::Panic(Some(String::from(msg)))
471+
FailureResult::panic().with_message(msg)
417472
}
418473

419474
#[test]
@@ -423,7 +478,7 @@ mod test {
423478
// -- Unexpected failure --
424479

425480
let expectation = vec![];
426-
let actual = vec![FailureResult::ValidationError(None)];
481+
let actual = vec![FailureResult::validation_error()];
427482

428483
assert_eq!(
429484
super::expectations_match_failures(&expectation, actual),
@@ -443,7 +498,7 @@ mod test {
443498
// -- Expected failure (validation) --
444499

445500
let expectation = vec![FailureCase::always()];
446-
let actual = vec![FailureResult::ValidationError(None)];
501+
let actual = vec![FailureResult::validation_error()];
447502

448503
assert_eq!(
449504
super::expectations_match_failures(&expectation, actual),
@@ -453,7 +508,7 @@ mod test {
453508
// -- Expected failure (panic) --
454509

455510
let expectation = vec![FailureCase::always()];
456-
let actual = vec![FailureResult::Panic(None)];
511+
let actual = vec![FailureResult::panic()];
457512

458513
assert_eq!(
459514
super::expectations_match_failures(&expectation, actual),
@@ -469,9 +524,9 @@ mod test {
469524

470525
let expectation: Vec<FailureCase> =
471526
vec![FailureCase::always().validation_error("Some StrIng")];
472-
let actual = vec![FailureResult::ValidationError(Some(String::from(
527+
let actual = vec![FailureResult::validation_error().with_message(
473528
"a very long string that contains sOmE sTrInG of different capitalization",
474-
)))];
529+
)];
475530

476531
assert_eq!(
477532
super::expectations_match_failures(&expectation, actual),

tests/src/image.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ pub async fn compare_image_output(
238238
)
239239
.await;
240240
write_png(
241-
difference_path,
241+
&difference_path,
242242
width,
243243
height,
244244
&magma_image_with_alpha,
@@ -247,7 +247,7 @@ pub async fn compare_image_output(
247247
.await;
248248

249249
if !all_passed {
250-
panic!("Image data mismatch!")
250+
panic!("Image data mismatch: {}", difference_path.display())
251251
}
252252
}
253253

tests/src/run.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,29 @@ pub async fn execute_test(
8686
.await;
8787

8888
if let Err(panic) = panic_res {
89-
let panic_str = panic.downcast_ref::<&'static str>();
90-
let panic_string = if let Some(&panic_str) = panic_str {
91-
Some(panic_str.to_string())
89+
let message = panic
90+
.downcast_ref::<&str>()
91+
.copied()
92+
.or_else(|| panic.downcast_ref::<String>().map(String::as_str));
93+
94+
let result = FailureResult::panic();
95+
96+
let result = if let Some(panic_str) = message {
97+
result.with_message(panic_str)
9298
} else {
93-
panic.downcast_ref::<String>().cloned()
99+
result
94100
};
95101

96-
failures.push(FailureResult::Panic(panic_string))
102+
failures.push(result)
97103
}
98104

99105
// Check whether any validation errors were reported during the test run.
100106
cfg_if::cfg_if!(
101107
if #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))] {
102-
failures.extend(wgpu::hal::VALIDATION_CANARY.get_and_reset().into_iter().map(|msg| FailureResult::ValidationError(Some(msg))));
108+
failures.extend(wgpu::hal::VALIDATION_CANARY.get_and_reset().into_iter().map(|msg| FailureResult::validation_error().with_message(msg)));
103109
} else if #[cfg(all(target_arch = "wasm32", feature = "webgl"))] {
104110
if _surface_guard.unwrap().check_for_unreported_errors() {
105-
failures.push(FailureResult::ValidationError(None));
111+
failures.push(FailureResult::validation_error());
106112
}
107113
} else {
108114
}

0 commit comments

Comments
 (0)