Skip to content

Commit b499744

Browse files
committed
Add a test for components that panic while being dropped
1 parent 5a95716 commit b499744

File tree

1 file changed

+133
-0
lines changed
  • crates/bevy_ecs/src/world

1 file changed

+133
-0
lines changed

crates/bevy_ecs/src/world/mod.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,3 +1191,136 @@ impl Default for MainThreadValidator {
11911191
}
11921192
}
11931193
}
1194+
1195+
#[cfg(test)]
1196+
mod tests {
1197+
use super::World;
1198+
use bevy_ecs_macros::Component;
1199+
use std::{
1200+
panic,
1201+
sync::{
1202+
atomic::{AtomicBool, Ordering},
1203+
Arc, Mutex,
1204+
},
1205+
};
1206+
1207+
// For bevy_ecs_macros
1208+
use crate as bevy_ecs;
1209+
1210+
type ID = u8;
1211+
1212+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1213+
enum DropLogItem {
1214+
Create(ID),
1215+
Drop(ID),
1216+
}
1217+
1218+
#[derive(Component)]
1219+
struct MayPanicInDrop {
1220+
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
1221+
expected_panic_flag: Arc<AtomicBool>,
1222+
should_panic: bool,
1223+
id: u8,
1224+
}
1225+
1226+
impl MayPanicInDrop {
1227+
fn new(
1228+
drop_log: &Arc<Mutex<Vec<DropLogItem>>>,
1229+
expected_panic_flag: &Arc<AtomicBool>,
1230+
should_panic: bool,
1231+
id: u8,
1232+
) -> Self {
1233+
println!("creating component with id {}", id);
1234+
drop_log.lock().unwrap().push(DropLogItem::Create(id));
1235+
1236+
Self {
1237+
drop_log: Arc::clone(drop_log),
1238+
expected_panic_flag: Arc::clone(expected_panic_flag),
1239+
should_panic,
1240+
id,
1241+
}
1242+
}
1243+
}
1244+
1245+
impl Drop for MayPanicInDrop {
1246+
fn drop(&mut self) {
1247+
println!("dropping component with id {}", self.id);
1248+
1249+
{
1250+
let mut drop_log = self.drop_log.lock().unwrap();
1251+
drop_log.push(DropLogItem::Drop(self.id));
1252+
// Don't keep the mutex while panicking, or we'll poison it.
1253+
drop(drop_log);
1254+
}
1255+
1256+
if self.should_panic {
1257+
self.expected_panic_flag.store(true, Ordering::SeqCst);
1258+
panic!("testing what happens on panic inside drop");
1259+
}
1260+
}
1261+
}
1262+
1263+
struct DropTestHelper {
1264+
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
1265+
/// Set to `true` right before we intentionally panic, so that if we get
1266+
/// a panic, we know if it was intended or not.
1267+
expected_panic_flag: Arc<AtomicBool>,
1268+
}
1269+
1270+
impl DropTestHelper {
1271+
pub fn new() -> Self {
1272+
Self {
1273+
drop_log: Arc::new(Mutex::new(Vec::<DropLogItem>::new())),
1274+
expected_panic_flag: Arc::new(AtomicBool::new(false)),
1275+
}
1276+
}
1277+
1278+
pub fn make_component(&self, should_panic: bool, id: ID) -> MayPanicInDrop {
1279+
MayPanicInDrop::new(&self.drop_log, &self.expected_panic_flag, should_panic, id)
1280+
}
1281+
1282+
pub fn finish(self, panic_res: std::thread::Result<()>) -> Vec<DropLogItem> {
1283+
let drop_log = Arc::try_unwrap(self.drop_log)
1284+
.unwrap()
1285+
.into_inner()
1286+
.unwrap();
1287+
let expected_panic_flag = self.expected_panic_flag.load(Ordering::SeqCst);
1288+
1289+
if !expected_panic_flag {
1290+
match panic_res {
1291+
Ok(()) => panic!("Expected a panic but it didn't happen"),
1292+
Err(e) => panic::resume_unwind(e),
1293+
}
1294+
}
1295+
1296+
drop_log
1297+
}
1298+
}
1299+
1300+
#[test]
1301+
fn panic_while_overwriting_component() {
1302+
let helper = DropTestHelper::new();
1303+
1304+
let res = panic::catch_unwind(|| {
1305+
let mut world = World::new();
1306+
world
1307+
.spawn()
1308+
.insert(helper.make_component(true, 0))
1309+
.insert(helper.make_component(false, 1));
1310+
1311+
println!("Done inserting! Dropping world...");
1312+
});
1313+
1314+
let drop_log = helper.finish(res);
1315+
1316+
assert_eq!(
1317+
&*drop_log,
1318+
[
1319+
DropLogItem::Create(0),
1320+
DropLogItem::Create(1),
1321+
DropLogItem::Drop(0),
1322+
DropLogItem::Drop(1)
1323+
]
1324+
);
1325+
}
1326+
}

0 commit comments

Comments
 (0)