Implement iced_test::run entrypoint for ice testing
This commit is contained in:
parent
e136e14b7c
commit
6a6a2ac8c5
6 changed files with 149 additions and 22 deletions
|
|
@ -221,10 +221,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
self.confirm();
|
self.confirm();
|
||||||
|
|
||||||
let ice = Ice {
|
let ice = Ice {
|
||||||
viewport: Size::new(
|
viewport: self.viewport,
|
||||||
self.viewport.width as u32,
|
|
||||||
self.viewport.height as u32,
|
|
||||||
),
|
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
preset: self.preset.clone(),
|
preset: self.preset.clone(),
|
||||||
instructions: self.instructions.clone(),
|
instructions: self.instructions.clone(),
|
||||||
|
|
@ -246,10 +243,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.discard()
|
.discard()
|
||||||
}
|
}
|
||||||
Message::Imported(Ok(ice)) => {
|
Message::Imported(Ok(ice)) => {
|
||||||
self.viewport = Size::new(
|
self.viewport = ice.viewport;
|
||||||
ice.viewport.width as f32,
|
|
||||||
ice.viewport.height as f32,
|
|
||||||
);
|
|
||||||
self.mode = ice.mode;
|
self.mode = ice.mode;
|
||||||
self.preset = ice.preset;
|
self.preset = ice.preset;
|
||||||
self.instructions = ice.instructions;
|
self.instructions = ice.instructions;
|
||||||
|
|
@ -347,7 +341,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
emulator::Event::Action(action) => {
|
emulator::Event::Action(action) => {
|
||||||
emulator.perform(program, action);
|
emulator.perform(program, action);
|
||||||
}
|
}
|
||||||
emulator::Event::Failed => {
|
emulator::Event::Failed(_instruction) => {
|
||||||
*outcome = Outcome::Failed;
|
*outcome = Outcome::Failed;
|
||||||
}
|
}
|
||||||
emulator::Event::Ready => {
|
emulator::Event::Ready => {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ use iced::widget::{
|
||||||
};
|
};
|
||||||
use iced::window;
|
use iced::window;
|
||||||
use iced::{
|
use iced::{
|
||||||
Center, Element, Fill, Font, Function, Preset, Subscription,
|
Application, Center, Element, Fill, Font, Function, Preset, Program,
|
||||||
Task as Command,
|
Subscription, Task as Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -16,14 +16,16 @@ pub fn main() -> iced::Result {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
tracing_subscriber::fmt::init();
|
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)
|
.subscription(Todos::subscription)
|
||||||
.title(Todos::title)
|
.title(Todos::title)
|
||||||
.font(Todos::ICON_FONT)
|
.font(Todos::ICON_FONT)
|
||||||
.window_size((500.0, 800.0))
|
.window_size((500.0, 800.0))
|
||||||
.presets(presets());
|
.presets(presets())
|
||||||
|
|
||||||
todos.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -648,4 +650,13 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn it_passes_the_ice_tests() -> Result<(), Error> {
|
||||||
|
iced_test::run(
|
||||||
|
application(),
|
||||||
|
format!("{}/tests", env!("CARGO_MANIFEST_DIR")),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ pub struct Emulator<P: Program> {
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub enum Event<P: Program> {
|
pub enum Event<P: Program> {
|
||||||
Action(Action<P::Message>),
|
Action(Action<P::Message>),
|
||||||
Failed,
|
Failed(Instruction),
|
||||||
Ready,
|
Ready,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +211,7 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
|
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
|
|
||||||
match instruction {
|
match &instruction {
|
||||||
Instruction::Interact(interaction) => {
|
Instruction::Interact(interaction) => {
|
||||||
let Some(events) = interaction.events(|target| match target {
|
let Some(events) = interaction.events(|target| match target {
|
||||||
instruction::Target::Point(position) => Some(*position),
|
instruction::Target::Point(position) => Some(*position),
|
||||||
|
|
@ -234,7 +234,7 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) else {
|
}) else {
|
||||||
self.runtime.send(Event::Failed);
|
self.runtime.send(Event::Failed(instruction));
|
||||||
self.cache = Some(user_interface.into_cache());
|
self.cache = Some(user_interface.into_cache());
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
@ -282,7 +282,7 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
self.runtime.send(Event::Ready);
|
self.runtime.send(Event::Ready);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.runtime.send(Event::Failed);
|
self.runtime.send(Event::Failed(instruction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
|
use crate::Instruction;
|
||||||
|
use crate::ice;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A test error.
|
/// A test error.
|
||||||
|
|
@ -20,6 +24,23 @@ pub enum Error {
|
||||||
/// The encoding of some PNG image failed.
|
/// The encoding of some PNG image failed.
|
||||||
#[error("the encoding of some PNG image failed: {0}")]
|
#[error("the encoding of some PNG image failed: {0}")]
|
||||||
PngEncodingFailed(Arc<png::EncodingError>),
|
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 {
|
impl From<io::Error> for Error {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::instruction;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Ice {
|
pub struct Ice {
|
||||||
pub viewport: Size<u32>,
|
pub viewport: Size,
|
||||||
pub mode: emulator::Mode,
|
pub mode: emulator::Mode,
|
||||||
pub preset: Option<String>,
|
pub preset: Option<String>,
|
||||||
pub instructions: Vec<Instruction>,
|
pub instructions: Vec<Instruction>,
|
||||||
|
|
@ -110,8 +110,8 @@ impl std::fmt::Display for Ice {
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"viewport: {width}x{height}",
|
"viewport: {width}x{height}",
|
||||||
width = self.viewport.width,
|
width = self.viewport.width as u32, // TODO
|
||||||
height = self.viewport.height
|
height = self.viewport.height as u32, // TODO
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
|
||||||
101
test/src/lib.rs
101
test/src/lib.rs
|
|
@ -104,3 +104,104 @@ pub use ice::Ice;
|
||||||
pub use instruction::Instruction;
|
pub use instruction::Instruction;
|
||||||
pub use selector::Selector;
|
pub use selector::Selector;
|
||||||
pub use simulator::{Simulator, simulator};
|
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(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue