mod app_ext;
mod commands_ext;
mod data;
mod log;
mod util;
pub mod prelude {
pub use super::app_ext::*;
pub use super::commands_ext::*;
pub use super::data::*;
pub use super::log::*;
pub use super::*;
pub(crate) use bevy::prelude::*;
}
use bevy::{
app::ScheduleRunnerPlugin,
diagnostic::FrameCountPlugin,
log::{DEFAULT_FILTER, LogPlugin},
state::app::StatesPlugin,
time::TimePlugin,
};
use crate::prelude::*;
#[derive(Resource, Deref, PartialEq)]
pub struct TestRunnerTimeout(pub f32);
impl Default for TestRunnerTimeout {
fn default() -> Self {
Self(5.)
}
}
#[derive(Resource, Deref, PartialEq, Eq)]
pub struct LogTestSteps(pub bool);
impl Default for LogTestSteps {
fn default() -> Self {
Self(true)
}
}
#[derive(Debug)]
pub struct TestRunnerPlugin {
pub log_level: bevy::log::Level,
pub log_filter: String,
}
impl Default for TestRunnerPlugin {
fn default() -> Self {
Self {
log_level: bevy::log::Level::TRACE,
log_filter: DEFAULT_FILTER.to_string(),
}
}
}
impl Plugin for TestRunnerPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((
TaskPoolPlugin::default(),
FrameCountPlugin,
TimePlugin,
ScheduleRunnerPlugin::default(),
LogPlugin {
level: self.log_level,
filter: self.log_filter.clone(),
custom_layer: crate::log::custom_layer,
..Default::default()
},
AssetPlugin::default(),
ImagePlugin::default(),
StatesPlugin,
));
app.init_resource::<LogTestSteps>();
app.init_resource::<TestRunnerTimeout>();
app.add_systems(
Update,
move |time: Res<Time<Real>>,
timeout: Res<TestRunnerTimeout>,
mut events: MessageWriter<AppExit>| {
let elapsed = time.elapsed_secs();
if elapsed > **timeout {
error!("Timeout after {elapsed}s");
events.write(AppExit::error());
}
},
);
app.add_systems(
PostUpdate,
(
log_step.run_if(resource_exists_and_equals(LogTestSteps(true))),
check_exit,
)
.chain(),
);
app.init_state::<Step>();
}
}
fn log_step(step: Res<State<Step>>, mut local_step: Local<u32>) {
info_once!("Step = {}", ***step); if ***step != *local_step {
*local_step = ***step;
info!("Step = {}", ***step);
}
}
fn check_exit(mut reader: MessageReader<AppExit>) {
for msg in reader.read() {
error!("Obtained exit message {msg:?}");
}
}
#[test]
fn timeout() {
let mut app = App::new();
app.add_plugins(TestRunnerPlugin::default());
app.insert_resource(TestRunnerTimeout(0.5));
assert!(app.run().is_error());
}
#[test]
fn explicit_failure() {
let mut app = App::new();
app.add_plugins(TestRunnerPlugin::default());
app.add_systems(First, |mut commands: Commands| {
commands.write_message(AppExit::error());
});
assert!(app.run().is_error());
}
#[test]
fn explicit_success() {
let mut app = App::new();
app.add_plugins(TestRunnerPlugin::default());
app.add_systems(First, |mut commands: Commands| {
commands.write_message(AppExit::Success);
});
assert!(app.run().is_success());
}