tests: Move run_swf to util/runner
This commit is contained in:
parent
4418431bfe
commit
818897b74e
|
@ -5,20 +5,17 @@
|
|||
use regex::Regex;
|
||||
use ruffle_core::backend::{
|
||||
log::LogBackend,
|
||||
navigator::{NullExecutor, NullNavigatorBackend},
|
||||
storage::{MemoryStorageBackend, StorageBackend},
|
||||
};
|
||||
use ruffle_core::context::UpdateContext;
|
||||
use ruffle_core::events::MouseButton as RuffleMouseButton;
|
||||
use ruffle_core::external::Value as ExternalValue;
|
||||
use ruffle_core::external::{ExternalInterfaceMethod, ExternalInterfaceProvider};
|
||||
use ruffle_core::limits::ExecutionLimit;
|
||||
use ruffle_core::tag_utils::SwfMovie;
|
||||
use ruffle_core::{Player, PlayerBuilder, PlayerEvent};
|
||||
use ruffle_input_format::{AutomatedEvent, InputInjector, MouseButton as InputMouseButton};
|
||||
use ruffle_core::Player;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use libtest_mimic::{Arguments, Trial};
|
||||
use ruffle_input_format::InputInjector;
|
||||
#[cfg(feature = "imgtests")]
|
||||
use ruffle_render_wgpu::backend::WgpuRenderBackend;
|
||||
#[cfg(feature = "imgtests")]
|
||||
|
@ -28,7 +25,7 @@ use std::collections::BTreeMap;
|
|||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use util::runner::run_swf;
|
||||
use util::test::Test;
|
||||
|
||||
mod util;
|
||||
|
@ -42,9 +39,7 @@ fn set_logger() {
|
|||
.try_init();
|
||||
}
|
||||
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
|
||||
fn external_interface_avm1() -> Result<(), Error> {
|
||||
fn external_interface_avm1() -> Result<()> {
|
||||
set_logger();
|
||||
test_swf_with_hooks(
|
||||
Path::new("tests/swfs/avm1/external_interface/test.swf"),
|
||||
|
@ -98,7 +93,7 @@ fn external_interface_avm1() -> Result<(), Error> {
|
|||
)
|
||||
}
|
||||
|
||||
fn external_interface_avm2() -> Result<(), Error> {
|
||||
fn external_interface_avm2() -> Result<()> {
|
||||
set_logger();
|
||||
test_swf_with_hooks(
|
||||
Path::new("tests/swfs/avm2/external_interface/test.swf"),
|
||||
|
@ -143,7 +138,7 @@ fn external_interface_avm2() -> Result<(), Error> {
|
|||
)
|
||||
}
|
||||
|
||||
fn shared_object_avm1() -> Result<(), Error> {
|
||||
fn shared_object_avm1() -> Result<()> {
|
||||
set_logger();
|
||||
// Test SharedObject persistence. Run an SWF that saves data
|
||||
// to a shared object twice and verify that the data is saved.
|
||||
|
@ -196,7 +191,7 @@ fn shared_object_avm1() -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn shared_object_avm2() -> Result<(), Error> {
|
||||
fn shared_object_avm2() -> Result<()> {
|
||||
set_logger();
|
||||
// Test SharedObject persistence. Run an SWF that saves data
|
||||
// to a shared object twice and verify that the data is saved.
|
||||
|
@ -257,11 +252,11 @@ fn test_swf_with_hooks(
|
|||
num_frames: u32,
|
||||
simulated_input_path: &Path,
|
||||
expected_output_path: &Path,
|
||||
before_start: impl FnOnce(Arc<Mutex<Player>>) -> Result<(), Error>,
|
||||
before_end: impl FnOnce(Arc<Mutex<Player>>) -> Result<(), Error>,
|
||||
before_start: impl FnOnce(Arc<Mutex<Player>>) -> Result<()>,
|
||||
before_end: impl FnOnce(Arc<Mutex<Player>>) -> Result<()>,
|
||||
check_img: bool,
|
||||
frame_time_sleep: bool,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
let injector =
|
||||
InputInjector::from_file(simulated_input_path).unwrap_or_else(|_| InputInjector::empty());
|
||||
let mut expected_output = std::fs::read_to_string(expected_output_path)?.replace("\r\n", "\n");
|
||||
|
@ -299,7 +294,7 @@ fn test_swf_approx(
|
|||
num_patterns: &[Regex],
|
||||
check_img: bool,
|
||||
approx_assert_fn: impl Fn(f64, f64),
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<()> {
|
||||
let injector =
|
||||
InputInjector::from_file(simulated_input_path).unwrap_or_else(|_| InputInjector::empty());
|
||||
let trace_log = run_swf(
|
||||
|
@ -391,180 +386,6 @@ fn test_swf_approx(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads an SWF and runs it through the Ruffle core for a number of frames.
|
||||
/// Tests that the trace output matches the given expected output.
|
||||
fn run_swf(
|
||||
swf_path: &Path,
|
||||
num_frames: u32,
|
||||
before_start: impl FnOnce(Arc<Mutex<Player>>) -> Result<(), Error>,
|
||||
mut injector: InputInjector,
|
||||
before_end: impl FnOnce(Arc<Mutex<Player>>) -> Result<(), Error>,
|
||||
#[allow(unused)] mut check_img: bool,
|
||||
frame_time_sleep: bool,
|
||||
) -> Result<String, Error> {
|
||||
#[allow(unused_assignments)]
|
||||
{
|
||||
check_img &= RUN_IMG_TESTS;
|
||||
}
|
||||
|
||||
let base_path = Path::new(swf_path).parent().unwrap();
|
||||
let mut executor = NullExecutor::new();
|
||||
let movie = SwfMovie::from_path(swf_path, None)?;
|
||||
let frame_time = 1000.0 / movie.frame_rate().to_f64();
|
||||
let frame_time_duration = Duration::from_millis(frame_time as u64);
|
||||
let trace_output = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = PlayerBuilder::new();
|
||||
|
||||
#[cfg(feature = "imgtests")]
|
||||
if check_img {
|
||||
const BACKEND: wgpu::Backends = wgpu::Backends::PRIMARY;
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: BACKEND,
|
||||
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
||||
});
|
||||
|
||||
let descriptors =
|
||||
futures::executor::block_on(WgpuRenderBackend::<TextureTarget>::build_descriptors(
|
||||
BACKEND,
|
||||
instance,
|
||||
None,
|
||||
Default::default(),
|
||||
None,
|
||||
))?;
|
||||
|
||||
let width = movie.width().to_pixels() as u32;
|
||||
let height = movie.height().to_pixels() as u32;
|
||||
|
||||
let target = TextureTarget::new(&descriptors.device, (width, height))?;
|
||||
|
||||
builder = builder
|
||||
.with_renderer(WgpuRenderBackend::new(Arc::new(descriptors), target, 4)?)
|
||||
.with_viewport_dimensions(width, height, 1.0);
|
||||
};
|
||||
|
||||
let player = builder
|
||||
.with_log(TestLogBackend::new(trace_output.clone()))
|
||||
.with_navigator(NullNavigatorBackend::with_base_path(base_path, &executor)?)
|
||||
.with_max_execution_duration(Duration::from_secs(300))
|
||||
.with_viewport_dimensions(
|
||||
movie.width().to_pixels() as u32,
|
||||
movie.height().to_pixels() as u32,
|
||||
1.0,
|
||||
)
|
||||
.with_movie(movie)
|
||||
.build();
|
||||
|
||||
before_start(player.clone())?;
|
||||
|
||||
for _ in 0..num_frames {
|
||||
// If requested, ensure that the 'expected' amount of
|
||||
// time actually elapses between frames. This is useful for
|
||||
// tests that call 'flash.utils.getTimer()' and use
|
||||
// 'setInterval'/'flash.utils.Timer'
|
||||
//
|
||||
// Note that when Ruffle actually runs frames, we can
|
||||
// execute frames faster than this in order to 'catch up'
|
||||
// if we've fallen behind. However, in order to make regression
|
||||
// tests deterministic, we always call 'update_timers' with
|
||||
// an elapsed time of 'frame_time'. By sleeping for 'frame_time_duration',
|
||||
// we ensure that the result of 'flash.utils.getTimer()' is consistent
|
||||
// with timer execution (timers will see an elapsed time of *at least*
|
||||
// the requested timer interval).
|
||||
if frame_time_sleep {
|
||||
std::thread::sleep(frame_time_duration);
|
||||
}
|
||||
|
||||
while !player
|
||||
.lock()
|
||||
.unwrap()
|
||||
.preload(&mut ExecutionLimit::exhausted())
|
||||
{}
|
||||
|
||||
player.lock().unwrap().run_frame();
|
||||
player.lock().unwrap().update_timers(frame_time);
|
||||
executor.run();
|
||||
|
||||
injector.next(|evt, _btns_down| {
|
||||
player.lock().unwrap().handle_event(match evt {
|
||||
AutomatedEvent::MouseDown { pos, btn } => PlayerEvent::MouseDown {
|
||||
x: pos.0,
|
||||
y: pos.1,
|
||||
button: match btn {
|
||||
InputMouseButton::Left => RuffleMouseButton::Left,
|
||||
InputMouseButton::Middle => RuffleMouseButton::Middle,
|
||||
InputMouseButton::Right => RuffleMouseButton::Right,
|
||||
},
|
||||
},
|
||||
AutomatedEvent::MouseMove { pos } => PlayerEvent::MouseMove { x: pos.0, y: pos.1 },
|
||||
AutomatedEvent::MouseUp { pos, btn } => PlayerEvent::MouseUp {
|
||||
x: pos.0,
|
||||
y: pos.1,
|
||||
button: match btn {
|
||||
InputMouseButton::Left => RuffleMouseButton::Left,
|
||||
InputMouseButton::Middle => RuffleMouseButton::Middle,
|
||||
InputMouseButton::Right => RuffleMouseButton::Right,
|
||||
},
|
||||
},
|
||||
AutomatedEvent::Wait => unreachable!(),
|
||||
});
|
||||
});
|
||||
// Rendering has side-effects (such as processing 'DisplayObject.scrollRect' updates)
|
||||
player.lock().unwrap().render();
|
||||
}
|
||||
|
||||
// Render the image to disk
|
||||
// FIXME: Determine how we want to compare against on on-disk image
|
||||
#[cfg(feature = "imgtests")]
|
||||
if check_img {
|
||||
let mut player_lock = player.lock().unwrap();
|
||||
player_lock.render();
|
||||
let renderer = player_lock
|
||||
.renderer_mut()
|
||||
.downcast_mut::<WgpuRenderBackend<TextureTarget>>()
|
||||
.unwrap();
|
||||
|
||||
// Use straight alpha, since we want to save this as a PNG
|
||||
let actual_image = renderer
|
||||
.capture_frame(false)
|
||||
.expect("Failed to capture image");
|
||||
|
||||
let info = renderer.descriptors().adapter.get_info();
|
||||
let suffix = format!("{}-{:?}", std::env::consts::OS, info.backend);
|
||||
|
||||
let expected_image_path = base_path.join(format!("expected-{}.png", &suffix));
|
||||
let expected_image = image::open(&expected_image_path);
|
||||
|
||||
let matches = match expected_image {
|
||||
Ok(img) => {
|
||||
img.as_rgba8().expect("Expected 8-bit RGBA image").as_raw() == actual_image.as_raw()
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Failed to open expected image {:?}: {e:?}",
|
||||
&expected_image_path
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !matches {
|
||||
let actual_image_path = base_path.join(format!("actual-{suffix}.png"));
|
||||
actual_image.save_with_format(&actual_image_path, image::ImageFormat::Png)?;
|
||||
panic!("Test output does not match expected image - saved actual image to {actual_image_path:?}");
|
||||
}
|
||||
}
|
||||
|
||||
before_end(player)?;
|
||||
|
||||
executor.run();
|
||||
|
||||
let trace = trace_output.borrow().join("\n");
|
||||
Ok(trace)
|
||||
}
|
||||
|
||||
struct TestLogBackend {
|
||||
trace_output: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// https://doc.rust-lang.org/book/ch11-03-test-organization.html
|
||||
|
||||
pub mod options;
|
||||
pub mod runner;
|
||||
pub mod test;
|
||||
|
||||
/// Wrapper around string slice that makes debug output `{:?}` to print string same way as `{}`.
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
use crate::{TestLogBackend, RUN_IMG_TESTS};
|
||||
use anyhow::{anyhow, Result};
|
||||
use ruffle_core::backend::navigator::{NullExecutor, NullNavigatorBackend};
|
||||
use ruffle_core::events::MouseButton as RuffleMouseButton;
|
||||
use ruffle_core::limits::ExecutionLimit;
|
||||
use ruffle_core::tag_utils::SwfMovie;
|
||||
use ruffle_core::{Player, PlayerBuilder, PlayerEvent};
|
||||
use ruffle_input_format::{AutomatedEvent, InputInjector, MouseButton as InputMouseButton};
|
||||
#[cfg(feature = "imgtests")]
|
||||
use ruffle_render_wgpu::backend::WgpuRenderBackend;
|
||||
#[cfg(feature = "imgtests")]
|
||||
use ruffle_render_wgpu::{target::TextureTarget, wgpu};
|
||||
use std::cell::RefCell;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Loads an SWF and runs it through the Ruffle core for a number of frames.
|
||||
/// Tests that the trace output matches the given expected output.
|
||||
pub fn run_swf(
|
||||
swf_path: &Path,
|
||||
num_frames: u32,
|
||||
before_start: impl FnOnce(Arc<Mutex<Player>>) -> Result<()>,
|
||||
mut injector: InputInjector,
|
||||
before_end: impl FnOnce(Arc<Mutex<Player>>) -> Result<()>,
|
||||
#[allow(unused)] mut check_img: bool,
|
||||
frame_time_sleep: bool,
|
||||
) -> Result<String> {
|
||||
#[allow(unused_assignments)]
|
||||
{
|
||||
check_img &= RUN_IMG_TESTS;
|
||||
}
|
||||
|
||||
let base_path = Path::new(swf_path).parent().unwrap();
|
||||
let mut executor = NullExecutor::new();
|
||||
let movie = SwfMovie::from_path(swf_path, None).map_err(|e| anyhow!(e.to_string()))?;
|
||||
let frame_time = 1000.0 / movie.frame_rate().to_f64();
|
||||
let frame_time_duration = Duration::from_millis(frame_time as u64);
|
||||
let trace_output = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = PlayerBuilder::new();
|
||||
|
||||
#[cfg(feature = "imgtests")]
|
||||
if check_img {
|
||||
const BACKEND: wgpu::Backends = wgpu::Backends::PRIMARY;
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: BACKEND,
|
||||
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
||||
});
|
||||
|
||||
let descriptors =
|
||||
futures::executor::block_on(WgpuRenderBackend::<TextureTarget>::build_descriptors(
|
||||
BACKEND,
|
||||
instance,
|
||||
None,
|
||||
Default::default(),
|
||||
None,
|
||||
))?;
|
||||
|
||||
let width = movie.width().to_pixels() as u32;
|
||||
let height = movie.height().to_pixels() as u32;
|
||||
|
||||
let target = TextureTarget::new(&descriptors.device, (width, height))?;
|
||||
|
||||
builder = builder
|
||||
.with_renderer(WgpuRenderBackend::new(Arc::new(descriptors), target, 4)?)
|
||||
.with_viewport_dimensions(width, height, 1.0);
|
||||
};
|
||||
|
||||
let player = builder
|
||||
.with_log(TestLogBackend::new(trace_output.clone()))
|
||||
.with_navigator(NullNavigatorBackend::with_base_path(base_path, &executor)?)
|
||||
.with_max_execution_duration(Duration::from_secs(300))
|
||||
.with_viewport_dimensions(
|
||||
movie.width().to_pixels() as u32,
|
||||
movie.height().to_pixels() as u32,
|
||||
1.0,
|
||||
)
|
||||
.with_movie(movie)
|
||||
.build();
|
||||
|
||||
before_start(player.clone())?;
|
||||
|
||||
for _ in 0..num_frames {
|
||||
// If requested, ensure that the 'expected' amount of
|
||||
// time actually elapses between frames. This is useful for
|
||||
// tests that call 'flash.utils.getTimer()' and use
|
||||
// 'setInterval'/'flash.utils.Timer'
|
||||
//
|
||||
// Note that when Ruffle actually runs frames, we can
|
||||
// execute frames faster than this in order to 'catch up'
|
||||
// if we've fallen behind. However, in order to make regression
|
||||
// tests deterministic, we always call 'update_timers' with
|
||||
// an elapsed time of 'frame_time'. By sleeping for 'frame_time_duration',
|
||||
// we ensure that the result of 'flash.utils.getTimer()' is consistent
|
||||
// with timer execution (timers will see an elapsed time of *at least*
|
||||
// the requested timer interval).
|
||||
if frame_time_sleep {
|
||||
std::thread::sleep(frame_time_duration);
|
||||
}
|
||||
|
||||
while !player
|
||||
.lock()
|
||||
.unwrap()
|
||||
.preload(&mut ExecutionLimit::exhausted())
|
||||
{}
|
||||
|
||||
player.lock().unwrap().run_frame();
|
||||
player.lock().unwrap().update_timers(frame_time);
|
||||
executor.run();
|
||||
|
||||
injector.next(|evt, _btns_down| {
|
||||
player.lock().unwrap().handle_event(match evt {
|
||||
AutomatedEvent::MouseDown { pos, btn } => PlayerEvent::MouseDown {
|
||||
x: pos.0,
|
||||
y: pos.1,
|
||||
button: match btn {
|
||||
InputMouseButton::Left => RuffleMouseButton::Left,
|
||||
InputMouseButton::Middle => RuffleMouseButton::Middle,
|
||||
InputMouseButton::Right => RuffleMouseButton::Right,
|
||||
},
|
||||
},
|
||||
AutomatedEvent::MouseMove { pos } => PlayerEvent::MouseMove { x: pos.0, y: pos.1 },
|
||||
AutomatedEvent::MouseUp { pos, btn } => PlayerEvent::MouseUp {
|
||||
x: pos.0,
|
||||
y: pos.1,
|
||||
button: match btn {
|
||||
InputMouseButton::Left => RuffleMouseButton::Left,
|
||||
InputMouseButton::Middle => RuffleMouseButton::Middle,
|
||||
InputMouseButton::Right => RuffleMouseButton::Right,
|
||||
},
|
||||
},
|
||||
AutomatedEvent::Wait => unreachable!(),
|
||||
});
|
||||
});
|
||||
// Rendering has side-effects (such as processing 'DisplayObject.scrollRect' updates)
|
||||
player.lock().unwrap().render();
|
||||
}
|
||||
|
||||
// Render the image to disk
|
||||
// FIXME: Determine how we want to compare against on on-disk image
|
||||
#[cfg(feature = "imgtests")]
|
||||
if check_img {
|
||||
let mut player_lock = player.lock().unwrap();
|
||||
player_lock.render();
|
||||
let renderer = player_lock
|
||||
.renderer_mut()
|
||||
.downcast_mut::<WgpuRenderBackend<TextureTarget>>()
|
||||
.unwrap();
|
||||
|
||||
// Use straight alpha, since we want to save this as a PNG
|
||||
let actual_image = renderer
|
||||
.capture_frame(false)
|
||||
.expect("Failed to capture image");
|
||||
|
||||
let info = renderer.descriptors().adapter.get_info();
|
||||
let suffix = format!("{}-{:?}", std::env::consts::OS, info.backend);
|
||||
|
||||
let expected_image_path = base_path.join(format!("expected-{}.png", &suffix));
|
||||
let expected_image = image::open(&expected_image_path);
|
||||
|
||||
let matches = match expected_image {
|
||||
Ok(img) => {
|
||||
img.as_rgba8().expect("Expected 8-bit RGBA image").as_raw() == actual_image.as_raw()
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Failed to open expected image {:?}: {e:?}",
|
||||
&expected_image_path
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !matches {
|
||||
let actual_image_path = base_path.join(format!("actual-{suffix}.png"));
|
||||
actual_image.save_with_format(&actual_image_path, image::ImageFormat::Png)?;
|
||||
panic!("Test output does not match expected image - saved actual image to {actual_image_path:?}");
|
||||
}
|
||||
}
|
||||
|
||||
before_end(player)?;
|
||||
|
||||
executor.run();
|
||||
|
||||
let trace = trace_output.borrow().join("\n");
|
||||
Ok(trace)
|
||||
}
|
Loading…
Reference in New Issue