test(engine): ratchet on Bevy system-order ambiguities
Test / test (pull_request) Failing after 16m20s
Test / test (pull_request) Failing after 16m20s
First measurement of schedule hygiene (issue #143): the headless gameplay cluster (Game/Table/Card/Hud/AutoComplete/UiModal/UiFocus/ Settings) carries 302 system pairs with conflicting data access and no ordering edge. Too many to triage in one pass and most are likely benign event/resource writers — but unproven, and nothing stopped the count from growing. New schedule_checks test builds the cluster with ambiguity detection promoted to error, parses the reported pair count, and asserts it never exceeds AMBIGUITY_BASELINE (302). New ambiguous pairs now fail CI at the PR; the legacy backlog can be burned down incrementally by adding .before/.after or .ambiguous_with and lowering the baseline. Refs #143 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ pub mod replay_overlay;
|
|||||||
pub mod replay_playback;
|
pub mod replay_playback;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod safe_area;
|
pub mod safe_area;
|
||||||
|
mod schedule_checks;
|
||||||
pub mod selection_plugin;
|
pub mod selection_plugin;
|
||||||
pub mod settings_plugin;
|
pub mod settings_plugin;
|
||||||
pub mod splash_plugin;
|
pub mod splash_plugin;
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
//! Schedule hygiene checks (issue #143).
|
||||||
|
//!
|
||||||
|
//! Bevy runs systems with conflicting data access in nondeterministic order
|
||||||
|
//! unless an ordering edge exists. Nothing enforced this historically, and
|
||||||
|
//! the headless gameplay cluster carries a large legacy backlog of such
|
||||||
|
//! pairs — most benign (event/resource writers that never observably race),
|
||||||
|
//! but unproven. Rather than a permanently red hard-error test or no test
|
||||||
|
//! at all, this is a RATCHET: the count may only go down. New conflicting
|
||||||
|
//! pairs fail CI immediately; the backlog can be triaged incrementally
|
||||||
|
//! (add `.before`/`.after` where order matters, `.ambiguous_with` where it
|
||||||
|
//! provably doesn't, then lower `AMBIGUITY_BASELINE`).
|
||||||
|
//!
|
||||||
|
//! To see the offending pair names, add the `bevy_debug` / debug feature to
|
||||||
|
//! the bevy dev-dependency (system names are stripped otherwise) and set
|
||||||
|
//! `ambiguity_detection: LogLevel::Error` manually.
|
||||||
|
//!
|
||||||
|
//! Scope note: `CoreGamePlugin` (the full composition) performs real
|
||||||
|
//! storage/platform I/O in `build` and cannot run in a unit test; the
|
||||||
|
//! cluster below is the same one the engine's headless behaviour tests use.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bevy::ecs::schedule::{LogLevel, ScheduleBuildSettings};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::auto_complete_plugin::AutoCompletePlugin;
|
||||||
|
use crate::card_plugin::CardPlugin;
|
||||||
|
use crate::game_plugin::GamePlugin;
|
||||||
|
use crate::hud_plugin::HudPlugin;
|
||||||
|
use crate::settings_plugin::SettingsPlugin;
|
||||||
|
use crate::table_plugin::TablePlugin;
|
||||||
|
use crate::ui_focus::UiFocusPlugin;
|
||||||
|
use crate::ui_modal::UiModalPlugin;
|
||||||
|
|
||||||
|
/// Legacy ambiguity backlog measured 2026-07-06 (issue #143). This
|
||||||
|
/// number may only decrease. If your change trips this assertion you
|
||||||
|
/// have added a pair of systems with conflicting data access and no
|
||||||
|
/// ordering edge — add `.before`/`.after` (order matters) or
|
||||||
|
/// `.ambiguous_with` (provably order-independent) at the registration
|
||||||
|
/// site. When triage lowers the real count, lower this constant too.
|
||||||
|
const AMBIGUITY_BASELINE: usize = 302;
|
||||||
|
|
||||||
|
fn cluster_app() -> App {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(MinimalPlugins)
|
||||||
|
.add_plugins(GamePlugin)
|
||||||
|
.add_plugins(TablePlugin)
|
||||||
|
.add_plugins(CardPlugin)
|
||||||
|
.add_plugins(HudPlugin)
|
||||||
|
.add_plugins(AutoCompletePlugin)
|
||||||
|
.add_plugins(UiModalPlugin)
|
||||||
|
.add_plugins(UiFocusPlugin)
|
||||||
|
.add_plugins(SettingsPlugin::headless());
|
||||||
|
app.init_resource::<ButtonInput<KeyCode>>();
|
||||||
|
app
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_schedule_ambiguities_do_not_grow() {
|
||||||
|
let mut app = cluster_app();
|
||||||
|
app.edit_schedule(Update, |s| {
|
||||||
|
s.set_build_settings(ScheduleBuildSettings {
|
||||||
|
ambiguity_detection: LogLevel::Error,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// With LogLevel::Error the first schedule build panics when any
|
||||||
|
// ambiguity exists, and the panic message begins with the pair
|
||||||
|
// count. Catch it and ratchet on that count. (Parsing a panic
|
||||||
|
// message is brittle by design — if a Bevy upgrade rewords it,
|
||||||
|
// this test fails loudly and the parse below needs one-line
|
||||||
|
// maintenance, which is preferable to losing the ratchet.)
|
||||||
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||||
|
app.update();
|
||||||
|
}));
|
||||||
|
|
||||||
|
let count = match result {
|
||||||
|
Ok(()) => 0,
|
||||||
|
Err(payload) => {
|
||||||
|
let msg = payload
|
||||||
|
.downcast_ref::<String>()
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| payload.downcast_ref::<&str>().map(|s| (*s).to_string()))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let parsed = msg
|
||||||
|
.split(" pairs of systems")
|
||||||
|
.next()
|
||||||
|
.and_then(|prefix| prefix.split_whitespace().last())
|
||||||
|
.and_then(|n| n.parse::<usize>().ok());
|
||||||
|
parsed.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"ambiguity panic message no longer parseable (Bevy upgrade?): {msg}"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
count <= AMBIGUITY_BASELINE,
|
||||||
|
"system-order ambiguities grew: {count} > baseline {AMBIGUITY_BASELINE}. \
|
||||||
|
Add .before/.after or .ambiguous_with at the new registration site.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user