Implement iced_test::run entrypoint for ice testing

This commit is contained in:
Héctor Ramón Jiménez 2025-08-27 06:02:02 +02:00
parent e136e14b7c
commit 6a6a2ac8c5
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
6 changed files with 149 additions and 22 deletions

View file

@ -221,10 +221,7 @@ impl<P: Program + 'static> Tester<P> {
self.confirm();
let ice = Ice {
viewport: Size::new(
self.viewport.width as u32,
self.viewport.height as u32,
),
viewport: self.viewport,
mode: self.mode,
preset: self.preset.clone(),
instructions: self.instructions.clone(),
@ -246,10 +243,7 @@ impl<P: Program + 'static> Tester<P> {
.discard()
}
Message::Imported(Ok(ice)) => {
self.viewport = Size::new(
ice.viewport.width as f32,
ice.viewport.height as f32,
);
self.viewport = ice.viewport;
self.mode = ice.mode;
self.preset = ice.preset;
self.instructions = ice.instructions;
@ -347,7 +341,7 @@ impl<P: Program + 'static> Tester<P> {
emulator::Event::Action(action) => {
emulator.perform(program, action);
}
emulator::Event::Failed => {
emulator::Event::Failed(_instruction) => {
*outcome = Outcome::Failed;
}
emulator::Event::Ready => {

View file

@ -5,8 +5,8 @@ use iced::widget::{
};
use iced::window;
use iced::{
Center, Element, Fill, Font, Function, Preset, Subscription,
Task as Command,
Application, Center, Element, Fill, Font, Function, Preset, Program,
Subscription, Task as Command,
};
use serde::{Deserialize, Serialize};
@ -16,14 +16,16 @@ pub fn main() -> iced::Result {
#[cfg(not(target_arch = "wasm32"))]
tracing_subscriber::fmt::init();
let todos = iced::application(Todos::new, Todos::update, Todos::view)
application().run()
}
fn application() -> Application<impl Program> {
iced::application(Todos::new, Todos::update, Todos::view)
.subscription(Todos::subscription)
.title(Todos::title)
.font(Todos::ICON_FONT)
.window_size((500.0, 800.0))
.presets(presets());
todos.run()
.presets(presets())
}
#[derive(Debug)]
@ -648,4 +650,13 @@ mod tests {
Ok(())
}
#[test]
#[ignore]
fn it_passes_the_ice_tests() -> Result<(), Error> {
iced_test::run(
application(),
format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
)
}
}

View file

@ -36,7 +36,7 @@ pub struct Emulator<P: Program> {
#[allow(missing_debug_implementations)]
pub enum Event<P: Program> {
Action(Action<P::Message>),
Failed,
Failed(Instruction),
Ready,
}
@ -211,7 +211,7 @@ impl<P: Program + 'static> Emulator<P> {
let mut messages = Vec::new();
match instruction {
match &instruction {
Instruction::Interact(interaction) => {
let Some(events) = interaction.events(|target| match target {
instruction::Target::Point(position) => Some(*position),
@ -234,7 +234,7 @@ impl<P: Program + 'static> Emulator<P> {
}
}
}) else {
self.runtime.send(Event::Failed);
self.runtime.send(Event::Failed(instruction));
self.cache = Some(user_interface.into_cache());
return;
};
@ -282,7 +282,7 @@ impl<P: Program + 'static> Emulator<P> {
self.runtime.send(Event::Ready);
}
_ => {
self.runtime.send(Event::Failed);
self.runtime.send(Event::Failed(instruction));
}
}

View file

@ -1,4 +1,8 @@
use crate::Instruction;
use crate::ice;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;
/// A test error.
@ -20,6 +24,23 @@ pub enum Error {
/// The encoding of some PNG image failed.
#[error("the encoding of some PNG image failed: {0}")]
PngEncodingFailed(Arc<png::EncodingError>),
#[error("the ice test ({file}) is invalid: {error}")]
IceParsingFailed {
file: PathBuf,
error: ice::ParseError,
},
#[error("the ice test ({file}) failed")]
IceFailed {
file: PathBuf,
instruction: Instruction,
},
#[error(
"the preset \"{name}\" does not exist (available presets: {available:?})"
)]
PresetNotFound {
name: String,
available: Vec<String>,
},
}
impl From<io::Error> for Error {

View file

@ -5,7 +5,7 @@ use crate::instruction;
#[derive(Debug, Clone, PartialEq)]
pub struct Ice {
pub viewport: Size<u32>,
pub viewport: Size,
pub mode: emulator::Mode,
pub preset: Option<String>,
pub instructions: Vec<Instruction>,
@ -110,8 +110,8 @@ impl std::fmt::Display for Ice {
writeln!(
f,
"viewport: {width}x{height}",
width = self.viewport.width,
height = self.viewport.height
width = self.viewport.width as u32, // TODO
height = self.viewport.height as u32, // TODO
)?;
writeln!(

View file

@ -104,3 +104,104 @@ pub use ice::Ice;
pub use instruction::Instruction;
pub use selector::Selector;
pub use simulator::{Simulator, simulator};
use std::path::Path;
pub fn run(
program: impl program::Program + 'static,
tests_dir: impl AsRef<Path>,
) -> Result<(), Error> {
use crate::runtime::futures::futures::StreamExt;
use crate::runtime::futures::futures::channel::mpsc;
use crate::runtime::futures::futures::executor;
use std::ffi::OsStr;
use std::fs;
let tests = fs::read_dir(tests_dir)?;
// TODO: Concurrent runtimes
for file in tests {
let file = file?;
if file.path().extension().and_then(OsStr::to_str) != Some("ice") {
continue;
}
let ice = {
let content = fs::read_to_string(file.path())?;
match Ice::parse(&content) {
Ok(ice) => ice,
Err(error) => {
return Err(Error::IceParsingFailed {
file: file.path().to_path_buf(),
error,
});
}
}
};
let (sender, mut receiver) = mpsc::channel(1);
let preset = if let Some(preset) = ice.preset {
let Some(preset) = program
.presets()
.iter()
.find(|candidate| candidate.name() == preset)
else {
return Err(Error::PresetNotFound {
name: preset.to_owned(),
available: program
.presets()
.iter()
.map(program::Preset::name)
.map(str::to_owned)
.collect(),
});
};
Some(preset)
} else {
None
};
let mut emulator = Emulator::with_preset(
sender,
&program,
ice.mode,
ice.viewport,
preset,
);
let mut instructions: Vec<_> =
ice.instructions.into_iter().rev().collect();
loop {
let Some(event) = executor::block_on(receiver.next()) else {
panic!("emulator runtime stopped unexpectedly");
};
match event {
emulator::Event::Action(action) => {
emulator.perform(&program, action);
}
emulator::Event::Failed(instruction) => {
return Err(Error::IceFailed {
file: file.path().to_path_buf(),
instruction,
});
}
emulator::Event::Ready => {
let Some(instruction) = instructions.pop() else {
break;
};
emulator.run(&program, instruction);
}
}
}
}
Ok(())
}