Skip to content

Commit f50890d

Browse files
committed
tests: distinguish between panicky and non-panicky known_failure tests
Tests that are expected to panic are now required to explicitely say so in their `test.toml`, by using `known_failure.panic = "expected message"`. Additionally, only panics raised during player ticking are caught, and not those happenning elsewhere (e.g. in rendering, or in test framework code).
1 parent 401101f commit f50890d

File tree

10 files changed

+112
-31
lines changed

10 files changed

+112
-31
lines changed

tests/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ ignore = false
3737

3838
# If true, this test is known to fail and the test runner will expect it to fail.
3939
# When the test passes in the future, it'll fail and alert that it now passes.
40+
# This will not catch Ruffle panics; if the test is expected to panic, use
41+
# `known_failure.panic = "panic message"`
42+
# instead.
4043
known_failure = false
4144

4245
# Path (relative to the directory containing test.toml) to the expected output

tests/framework/src/options.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ pub mod approximations;
22
pub mod expression;
33
pub mod font;
44
pub mod image_comparison;
5+
pub mod known_failure;
56
pub mod player;
67

78
use crate::image_trigger::ImageTrigger;
89
use crate::options::approximations::Approximations;
910
use crate::options::font::{DefaultFontsOptions, FontOptions, FontSortOptions};
1011
use crate::options::image_comparison::ImageComparison;
12+
use crate::options::known_failure::KnownFailure;
1113
use crate::options::player::PlayerOptions;
1214
use anyhow::{Result, bail};
1315
use serde::Deserialize;
@@ -68,7 +70,7 @@ pub struct TestOptions {
6870
pub sleep_to_meet_frame_rate: bool,
6971
pub image_comparisons: HashMap<String, ImageComparison>,
7072
pub ignore: bool,
71-
pub known_failure: bool,
73+
pub known_failure: KnownFailure,
7274
pub approximations: Option<Approximations>,
7375
pub player_options: PlayerOptions,
7476
pub log_fetch: bool,
@@ -89,7 +91,7 @@ impl Default for TestOptions {
8991
sleep_to_meet_frame_rate: false,
9092
image_comparisons: Default::default(),
9193
ignore: false,
92-
known_failure: false,
94+
known_failure: KnownFailure::None,
9395
approximations: None,
9496
player_options: PlayerOptions::default(),
9597
log_fetch: false,
@@ -178,6 +180,10 @@ impl TestOptions {
178180
Ok(())
179181
}
180182

183+
pub fn has_known_failure(&self) -> bool {
184+
!matches!(self.known_failure, KnownFailure::None)
185+
}
186+
181187
pub fn output_path(&self, test_directory: &VfsPath) -> Result<VfsPath> {
182188
Ok(test_directory.join(&self.output_path)?)
183189
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::fmt;
2+
3+
use serde::{
4+
Deserialize, Deserializer,
5+
de::{self, value::MapAccessDeserializer},
6+
};
7+
8+
#[derive(Clone, Debug, Default)]
9+
pub enum KnownFailure {
10+
#[default]
11+
None,
12+
AnyCheck,
13+
Panic {
14+
message: String,
15+
},
16+
}
17+
18+
impl<'de> Deserialize<'de> for KnownFailure {
19+
fn deserialize<D: Deserializer<'de>>(deser: D) -> Result<Self, D::Error> {
20+
deser.deserialize_any(KnownFailureVisitor)
21+
}
22+
}
23+
24+
struct KnownFailureVisitor;
25+
26+
impl<'de> de::Visitor<'de> for KnownFailureVisitor {
27+
type Value = KnownFailure;
28+
29+
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
30+
f.write_str("a boolean, or `.panic = 'message'`")
31+
}
32+
33+
fn visit_bool<E: de::Error>(self, v: bool) -> Result<Self::Value, E> {
34+
if v {
35+
Ok(KnownFailure::AnyCheck)
36+
} else {
37+
Ok(KnownFailure::None)
38+
}
39+
}
40+
41+
fn visit_map<A: de::MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
42+
#[derive(Deserialize)]
43+
#[serde(deny_unknown_fields)]
44+
enum Raw {
45+
#[serde(rename = "panic")]
46+
Panic(String),
47+
}
48+
49+
match Raw::deserialize(MapAccessDeserializer::new(map))? {
50+
Raw::Panic(message) => Ok(KnownFailure::Panic { message }),
51+
}
52+
}
53+
}

tests/framework/src/runner.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::fs_commands::{FsCommand, TestFsCommandProvider};
88
use crate::image_trigger::ImageTrigger;
99
use crate::options::TestOptions;
1010
use crate::options::image_comparison::ImageComparison;
11+
use crate::options::known_failure::KnownFailure;
1112
use crate::runner::automation::perform_automated_event;
1213
use crate::runner::image_test::capture_and_compare_image;
1314
use crate::runner::trace::compare_trace_output;
@@ -20,6 +21,8 @@ use ruffle_core::{Player, PlayerBuilder};
2021
use ruffle_input_format::InputInjector;
2122
use ruffle_render::backend::{RenderBackend, ViewportDimensions};
2223
use ruffle_socket_format::SocketEvent;
24+
use std::any::Any;
25+
use std::borrow::Cow;
2326
use std::collections::HashMap;
2427
use std::sync::{Arc, Mutex, mpsc};
2528
use std::time::Duration;
@@ -163,33 +166,39 @@ impl TestRunner {
163166
pub fn tick(&mut self) -> Result<TestStatus> {
164167
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
165168

166-
let unwind_result = catch_unwind(AssertUnwindSafe(|| self.tick_inner()));
167-
168-
match (unwind_result, self.options.known_failure) {
169-
(Ok(ret), _) => ret,
170-
// known_failure tests may pass by panicking.
171-
(Err(_), true) => Ok(TestStatus::Finished),
172-
(Err(panic), false) => resume_unwind(panic),
169+
let unwind_result = catch_unwind(AssertUnwindSafe(|| self.do_tick()));
170+
match (unwind_result, &self.options.known_failure) {
171+
(Ok(()), _) => (),
172+
(Err(panic), KnownFailure::Panic { message }) => {
173+
let actual = panic_payload_as_string(panic);
174+
if actual.contains(message) {
175+
return Ok(TestStatus::Finished);
176+
}
177+
178+
let mut actual = actual.into_owned();
179+
actual.push_str("\n\nnote: expected panic message to contain: ");
180+
actual.push_str(message);
181+
resume_unwind(Box::new(actual))
182+
}
183+
(Err(panic), _) => resume_unwind(panic),
173184
}
174-
}
175-
176-
fn tick_inner(&mut self) -> Result<TestStatus> {
177-
self.do_tick();
178185

179-
match (self.test(), self.options.known_failure) {
186+
match (self.test(), &self.options.known_failure) {
180187
(Ok(()), _) => (),
181-
(Err(_), true) => return Ok(TestStatus::Finished),
182-
(Err(err), false) => return Err(err),
188+
(Err(_), KnownFailure::AnyCheck) => return Ok(TestStatus::Finished),
189+
(Err(err), _) => return Err(err),
183190
}
184191

185-
match self.remaining_iterations {
186-
0 => match (self.last_test(), self.options.known_failure) {
187-
(Ok(()), true) => Err(anyhow!(
192+
match (self.remaining_iterations, &self.options.known_failure) {
193+
(0, KnownFailure::None) => self.last_test().map(|_| TestStatus::Finished),
194+
(0, KnownFailure::Panic { .. }) => Err(anyhow!(
195+
"Test was known to be panicking, but now finishes successfully. Please update it and remove `known_failure.panic = '...'`!",
196+
)),
197+
(0, KnownFailure::AnyCheck) => match self.last_test() {
198+
Ok(()) => Err(anyhow!(
188199
"Test was known to be failing, but now passes successfully. Please update it and remove `known_failure = true`!",
189200
)),
190-
(Ok(()), false) => Ok(TestStatus::Finished),
191-
(Err(_), true) => Ok(TestStatus::Finished),
192-
(Err(err), false) => Err(err),
201+
Err(_) => Ok(TestStatus::Finished),
193202
},
194203
_ if self.options.sleep_to_meet_frame_rate => {
195204
// If requested, ensure that the 'expected' amount of
@@ -258,7 +267,7 @@ impl TestRunner {
258267
&self.player,
259268
&name,
260269
image_comparison,
261-
self.options.known_failure,
270+
matches!(self.options.known_failure, KnownFailure::AnyCheck),
262271
self.render_interface.as_deref(),
263272
)?;
264273
} else {
@@ -284,7 +293,7 @@ impl TestRunner {
284293
&self.player,
285294
&name,
286295
comp,
287-
self.options.known_failure,
296+
matches!(self.options.known_failure, KnownFailure::AnyCheck),
288297
self.render_interface.as_deref(),
289298
)?;
290299
}
@@ -302,7 +311,7 @@ impl TestRunner {
302311
&self.player,
303312
&name,
304313
comp,
305-
self.options.known_failure,
314+
matches!(self.options.known_failure, KnownFailure::AnyCheck),
306315
self.render_interface.as_deref(),
307316
)?;
308317
}
@@ -332,3 +341,13 @@ impl TestRunner {
332341
self.images.extract_if(|_k, v| v.trigger == trigger).next()
333342
}
334343
}
344+
345+
fn panic_payload_as_string(panic: Box<dyn Any + Send + 'static>) -> Cow<'static, str> {
346+
if let Some(s) = panic.downcast_ref::<&str>() {
347+
(*s).into()
348+
} else if let Ok(s) = panic.downcast::<String>() {
349+
(*s).into()
350+
} else {
351+
"<opaque payload>".into()
352+
}
353+
}

tests/framework/src/test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ impl Test {
142142
if self.options.ignore {
143143
return false;
144144
}
145-
if ignore_known_failures && self.options.known_failure {
145+
if ignore_known_failures && self.options.has_known_failure() {
146146
return false;
147147
}
148148
self.options.required_features.can_run()

tests/tests/regression_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fn trial_for_test(opts: &RuffleTestOpts, test: Test, list_only: bool) -> Trial {
106106
// Put extra info into the test 'kind' instead of appending it to the test name,
107107
// to not break `cargo test some/test -- --exact` and `cargo test -- --list`.
108108
let mut test_kind = String::new();
109-
if test.options.known_failure {
109+
if test.options.has_known_failure() {
110110
test_kind.push('!');
111111
}
112112
if let Some(name) = &test.options.subtest_name {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
num_frames = 1
2-
known_failure = true
2+
known_failure.panic = "attempt to add with overflow"

tests/tests/swfs/from_gnash/misc-ming.all/action_order/PlaceAndRemove/test.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
num_frames = 30
2-
known_failure = true
2+
known_failure.panic = "Gotos must start from the correct tag position for frame 1"
33

44
[subtests.fp9]
55
output_path = "output.fp9.txt"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
num_frames = 30
2-
known_failure = true
2+
known_failure.panic = "attempt to add with overflow"

tests/tests/swfs/from_gnash/misc-swfc.all/matrix_accuracy_test1/test.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
num_frames = 30
2-
known_failure = true
2+
known_failure.panic = "attempt to subtract with overflow"
33

44
[subtests.fp9]
55
output_path = "output.fp9.txt"

0 commit comments

Comments
 (0)