Skip to content

Commit da9a070

Browse files
authored
port old ambiguity tests over (#9617)
# Objective - Some of the old ambiguity tests didn't get ported over during schedule v3. ## Solution - Port over tests from https://github.com/bevyengine/bevy/blob/15ee98db8d1c6705111e0f11a8fc240ceaf9f2db/crates/bevy_ecs/src/schedule/ambiguity_detection.rs#L279-L612 with minimal changes - Make a method to convert the ambiguity conflicts to a string for easier verification of correct results.
1 parent 2b2abce commit da9a070

File tree

2 files changed

+356
-13
lines changed

2 files changed

+356
-13
lines changed

crates/bevy_ecs/src/schedule/mod.rs

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,4 +716,334 @@ mod tests {
716716
assert!(matches!(result, Err(ScheduleBuildError::Ambiguity(_))));
717717
}
718718
}
719+
720+
mod system_ambiguity {
721+
// Required to make the derive macro behave
722+
use crate as bevy_ecs;
723+
use crate::event::Events;
724+
use crate::prelude::*;
725+
726+
#[derive(Resource)]
727+
struct R;
728+
729+
#[derive(Component)]
730+
struct A;
731+
732+
#[derive(Component)]
733+
struct B;
734+
735+
// An event type
736+
#[derive(Event)]
737+
struct E;
738+
739+
fn empty_system() {}
740+
fn res_system(_res: Res<R>) {}
741+
fn resmut_system(_res: ResMut<R>) {}
742+
fn nonsend_system(_ns: NonSend<R>) {}
743+
fn nonsendmut_system(_ns: NonSendMut<R>) {}
744+
fn read_component_system(_query: Query<&A>) {}
745+
fn write_component_system(_query: Query<&mut A>) {}
746+
fn with_filtered_component_system(_query: Query<&mut A, With<B>>) {}
747+
fn without_filtered_component_system(_query: Query<&mut A, Without<B>>) {}
748+
fn event_reader_system(_reader: EventReader<E>) {}
749+
fn event_writer_system(_writer: EventWriter<E>) {}
750+
fn event_resource_system(_events: ResMut<Events<E>>) {}
751+
fn read_world_system(_world: &World) {}
752+
fn write_world_system(_world: &mut World) {}
753+
754+
// Tests for conflict detection
755+
756+
#[test]
757+
fn one_of_everything() {
758+
let mut world = World::new();
759+
world.insert_resource(R);
760+
world.spawn(A);
761+
world.init_resource::<Events<E>>();
762+
763+
let mut schedule = Schedule::default();
764+
schedule
765+
// nonsendmut system deliberately conflicts with resmut system
766+
.add_systems((resmut_system, write_component_system, event_writer_system));
767+
768+
let _ = schedule.initialize(&mut world);
769+
770+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
771+
}
772+
773+
#[test]
774+
fn read_only() {
775+
let mut world = World::new();
776+
world.insert_resource(R);
777+
world.spawn(A);
778+
world.init_resource::<Events<E>>();
779+
780+
let mut schedule = Schedule::default();
781+
schedule.add_systems((
782+
empty_system,
783+
empty_system,
784+
res_system,
785+
res_system,
786+
nonsend_system,
787+
nonsend_system,
788+
read_component_system,
789+
read_component_system,
790+
event_reader_system,
791+
event_reader_system,
792+
read_world_system,
793+
read_world_system,
794+
));
795+
796+
let _ = schedule.initialize(&mut world);
797+
798+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
799+
}
800+
801+
#[test]
802+
fn read_world() {
803+
let mut world = World::new();
804+
world.insert_resource(R);
805+
world.spawn(A);
806+
world.init_resource::<Events<E>>();
807+
808+
let mut schedule = Schedule::default();
809+
schedule.add_systems((
810+
resmut_system,
811+
write_component_system,
812+
event_writer_system,
813+
read_world_system,
814+
));
815+
816+
let _ = schedule.initialize(&mut world);
817+
818+
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
819+
}
820+
821+
#[test]
822+
fn resources() {
823+
let mut world = World::new();
824+
world.insert_resource(R);
825+
826+
let mut schedule = Schedule::default();
827+
schedule.add_systems((resmut_system, res_system));
828+
829+
let _ = schedule.initialize(&mut world);
830+
831+
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
832+
}
833+
834+
#[test]
835+
fn nonsend() {
836+
let mut world = World::new();
837+
world.insert_resource(R);
838+
839+
let mut schedule = Schedule::default();
840+
schedule.add_systems((nonsendmut_system, nonsend_system));
841+
842+
let _ = schedule.initialize(&mut world);
843+
844+
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
845+
}
846+
847+
#[test]
848+
fn components() {
849+
let mut world = World::new();
850+
world.spawn(A);
851+
852+
let mut schedule = Schedule::default();
853+
schedule.add_systems((read_component_system, write_component_system));
854+
855+
let _ = schedule.initialize(&mut world);
856+
857+
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
858+
}
859+
860+
#[test]
861+
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
862+
fn filtered_components() {
863+
let mut world = World::new();
864+
world.spawn(A);
865+
866+
let mut schedule = Schedule::default();
867+
schedule.add_systems((
868+
with_filtered_component_system,
869+
without_filtered_component_system,
870+
));
871+
872+
let _ = schedule.initialize(&mut world);
873+
874+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
875+
}
876+
877+
#[test]
878+
fn events() {
879+
let mut world = World::new();
880+
world.init_resource::<Events<E>>();
881+
882+
let mut schedule = Schedule::default();
883+
schedule.add_systems((
884+
// All of these systems clash
885+
event_reader_system,
886+
event_writer_system,
887+
event_resource_system,
888+
));
889+
890+
let _ = schedule.initialize(&mut world);
891+
892+
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
893+
}
894+
895+
#[test]
896+
fn exclusive() {
897+
let mut world = World::new();
898+
world.insert_resource(R);
899+
world.spawn(A);
900+
world.init_resource::<Events<E>>();
901+
902+
let mut schedule = Schedule::default();
903+
schedule.add_systems((
904+
// All 3 of these conflict with each other
905+
write_world_system,
906+
write_world_system,
907+
res_system,
908+
));
909+
910+
let _ = schedule.initialize(&mut world);
911+
912+
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
913+
}
914+
915+
// Tests for silencing and resolving ambiguities
916+
#[test]
917+
fn before_and_after() {
918+
let mut world = World::new();
919+
world.init_resource::<Events<E>>();
920+
921+
let mut schedule = Schedule::default();
922+
schedule.add_systems((
923+
event_reader_system.before(event_writer_system),
924+
event_writer_system,
925+
event_resource_system.after(event_writer_system),
926+
));
927+
928+
let _ = schedule.initialize(&mut world);
929+
930+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
931+
}
932+
933+
#[test]
934+
fn ignore_all_ambiguities() {
935+
let mut world = World::new();
936+
world.insert_resource(R);
937+
938+
let mut schedule = Schedule::default();
939+
schedule.add_systems((
940+
resmut_system.ambiguous_with_all(),
941+
res_system,
942+
nonsend_system,
943+
));
944+
945+
let _ = schedule.initialize(&mut world);
946+
947+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
948+
}
949+
950+
#[test]
951+
fn ambiguous_with_label() {
952+
let mut world = World::new();
953+
world.insert_resource(R);
954+
955+
#[derive(SystemSet, Hash, PartialEq, Eq, Debug, Clone)]
956+
struct IgnoreMe;
957+
958+
let mut schedule = Schedule::default();
959+
schedule.add_systems((
960+
resmut_system.ambiguous_with(IgnoreMe),
961+
res_system.in_set(IgnoreMe),
962+
nonsend_system.in_set(IgnoreMe),
963+
));
964+
965+
let _ = schedule.initialize(&mut world);
966+
967+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
968+
}
969+
970+
#[test]
971+
fn ambiguous_with_system() {
972+
let mut world = World::new();
973+
974+
let mut schedule = Schedule::default();
975+
schedule.add_systems((
976+
write_component_system.ambiguous_with(read_component_system),
977+
read_component_system,
978+
));
979+
let _ = schedule.initialize(&mut world);
980+
981+
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
982+
}
983+
984+
// Tests that the correct ambiguities were reported in the correct order.
985+
#[test]
986+
fn correct_ambiguities() {
987+
use super::*;
988+
989+
#[derive(ScheduleLabel, Hash, PartialEq, Eq, Debug, Clone)]
990+
struct TestSchedule;
991+
992+
fn system_a(_res: ResMut<R>) {}
993+
fn system_b(_res: ResMut<R>) {}
994+
fn system_c(_res: ResMut<R>) {}
995+
fn system_d(_res: ResMut<R>) {}
996+
fn system_e(_res: ResMut<R>) {}
997+
998+
let mut world = World::new();
999+
world.insert_resource(R);
1000+
1001+
let mut schedule = Schedule::new(TestSchedule);
1002+
schedule.add_systems((
1003+
system_a,
1004+
system_b,
1005+
system_c.ambiguous_with_all(),
1006+
system_d.ambiguous_with(system_b),
1007+
system_e.after(system_a),
1008+
));
1009+
1010+
schedule.graph_mut().initialize(&mut world);
1011+
let _ = schedule
1012+
.graph_mut()
1013+
.build_schedule(world.components(), &TestSchedule.dyn_clone());
1014+
1015+
let ambiguities: Vec<_> = schedule
1016+
.graph()
1017+
.conflicts_to_string(world.components())
1018+
.collect();
1019+
1020+
let expected = &[
1021+
(
1022+
"system_d".to_string(),
1023+
"system_a".to_string(),
1024+
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
1025+
),
1026+
(
1027+
"system_d".to_string(),
1028+
"system_e".to_string(),
1029+
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
1030+
),
1031+
(
1032+
"system_b".to_string(),
1033+
"system_a".to_string(),
1034+
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
1035+
),
1036+
(
1037+
"system_b".to_string(),
1038+
"system_e".to_string(),
1039+
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"],
1040+
),
1041+
];
1042+
1043+
// ordering isn't stable so do this
1044+
for entry in expected {
1045+
assert!(ambiguities.contains(entry));
1046+
}
1047+
}
1048+
}
7191049
}

crates/bevy_ecs/src/schedule/schedule.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,21 +1553,11 @@ impl ScheduleGraph {
15531553
Consider adding `before`, `after`, or `ambiguous_with` relationships between these:\n",
15541554
);
15551555

1556-
for (system_a, system_b, conflicts) in ambiguities {
1557-
let name_a = self.get_node_name(system_a);
1558-
let name_b = self.get_node_name(system_b);
1559-
1560-
debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1561-
debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1562-
1556+
for (name_a, name_b, conflicts) in self.conflicts_to_string(components) {
15631557
writeln!(message, " -- {name_a} and {name_b}").unwrap();
1564-
if !conflicts.is_empty() {
1565-
let conflict_names: Vec<_> = conflicts
1566-
.iter()
1567-
.map(|id| components.get_name(*id).unwrap())
1568-
.collect();
15691558

1570-
writeln!(message, " conflict on: {conflict_names:?}").unwrap();
1559+
if !conflicts.is_empty() {
1560+
writeln!(message, " conflict on: {conflicts:?}").unwrap();
15711561
} else {
15721562
// one or both systems must be exclusive
15731563
let world = std::any::type_name::<World>();
@@ -1578,6 +1568,29 @@ impl ScheduleGraph {
15781568
message
15791569
}
15801570

1571+
/// convert conflics to human readable format
1572+
pub fn conflicts_to_string<'a>(
1573+
&'a self,
1574+
components: &'a Components,
1575+
) -> impl Iterator<Item = (String, String, Vec<&str>)> + 'a {
1576+
self.conflicting_systems
1577+
.iter()
1578+
.map(move |(system_a, system_b, conflicts)| {
1579+
let name_a = self.get_node_name(system_a);
1580+
let name_b = self.get_node_name(system_b);
1581+
1582+
debug_assert!(system_a.is_system(), "{name_a} is not a system.");
1583+
debug_assert!(system_b.is_system(), "{name_b} is not a system.");
1584+
1585+
let conflict_names: Vec<_> = conflicts
1586+
.iter()
1587+
.map(|id| components.get_name(*id).unwrap())
1588+
.collect();
1589+
1590+
(name_a, name_b, conflict_names)
1591+
})
1592+
}
1593+
15811594
fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
15821595
for (set_id, _, _) in self.hierarchy.graph.edges_directed(id, Direction::Incoming) {
15831596
if f(set_id) {

0 commit comments

Comments
 (0)