Skip to content

Commit 956f072

Browse files
committed
Add a test for components that panic while being dropped
1 parent 7055c9e commit 956f072

File tree

1 file changed

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

1 file changed

+128
-0
lines changed

crates/bevy_ecs/src/world/mod.rs

+128
Original file line numberDiff line numberDiff line change
@@ -1167,3 +1167,131 @@ impl Default for MainThreadValidator {
11671167
}
11681168
}
11691169
}
1170+
1171+
#[cfg(test)]
1172+
mod tests {
1173+
use super::World;
1174+
use std::{
1175+
panic,
1176+
sync::{
1177+
atomic::{AtomicBool, Ordering},
1178+
Arc, Mutex,
1179+
},
1180+
};
1181+
1182+
type ID = u8;
1183+
1184+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1185+
enum DropLogItem {
1186+
Create(ID),
1187+
Drop(ID),
1188+
}
1189+
1190+
struct MayPanicInDrop {
1191+
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
1192+
expected_panic_flag: Arc<AtomicBool>,
1193+
should_panic: bool,
1194+
id: u8,
1195+
}
1196+
1197+
impl MayPanicInDrop {
1198+
fn new(
1199+
drop_log: &Arc<Mutex<Vec<DropLogItem>>>,
1200+
expected_panic_flag: &Arc<AtomicBool>,
1201+
should_panic: bool,
1202+
id: u8,
1203+
) -> Self {
1204+
println!("creating component with id {}", id);
1205+
drop_log.lock().unwrap().push(DropLogItem::Create(id));
1206+
1207+
Self {
1208+
drop_log: Arc::clone(&drop_log),
1209+
expected_panic_flag: Arc::clone(&expected_panic_flag),
1210+
should_panic,
1211+
id,
1212+
}
1213+
}
1214+
}
1215+
1216+
impl Drop for MayPanicInDrop {
1217+
fn drop(&mut self) {
1218+
println!("dropping component with id {}", self.id);
1219+
1220+
{
1221+
let mut drop_log = self.drop_log.lock().unwrap();
1222+
drop_log.push(DropLogItem::Drop(self.id));
1223+
// Don't keep the mutex while panicking, or we'll poison it.
1224+
drop(drop_log);
1225+
}
1226+
1227+
if self.should_panic {
1228+
self.expected_panic_flag.store(true, Ordering::SeqCst);
1229+
panic!("testing what happens on panic inside drop");
1230+
}
1231+
}
1232+
}
1233+
1234+
struct DropTestHelper {
1235+
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
1236+
/// Set to `true` right before we intentionally panic, so that if we get
1237+
/// a panic, we know if it was intended or not.
1238+
expected_panic_flag: Arc<AtomicBool>,
1239+
}
1240+
1241+
impl DropTestHelper {
1242+
pub fn new() -> Self {
1243+
Self {
1244+
drop_log: Arc::new(Mutex::new(Vec::<DropLogItem>::new())),
1245+
expected_panic_flag: Arc::new(AtomicBool::new(false)),
1246+
}
1247+
}
1248+
1249+
pub fn make_component(&self, should_panic: bool, id: ID) -> MayPanicInDrop {
1250+
MayPanicInDrop::new(&self.drop_log, &self.expected_panic_flag, should_panic, id)
1251+
}
1252+
1253+
pub fn finish(self, panic_res: std::thread::Result<()>) -> Vec<DropLogItem> {
1254+
let drop_log = Arc::try_unwrap(self.drop_log)
1255+
.unwrap()
1256+
.into_inner()
1257+
.unwrap();
1258+
let expected_panic_flag = self.expected_panic_flag.load(Ordering::SeqCst);
1259+
1260+
if !expected_panic_flag {
1261+
match panic_res {
1262+
Ok(()) => panic!("Expected a panic but it didn't happen"),
1263+
Err(e) => panic::resume_unwind(e),
1264+
}
1265+
}
1266+
1267+
drop_log
1268+
}
1269+
}
1270+
1271+
#[test]
1272+
fn panic_while_overwriting_component() {
1273+
let helper = DropTestHelper::new();
1274+
1275+
let res = panic::catch_unwind(|| {
1276+
let mut world = World::new();
1277+
world
1278+
.spawn()
1279+
.insert(helper.make_component(true, 0))
1280+
.insert(helper.make_component(false, 1));
1281+
1282+
println!("Done inserting! Dropping world...");
1283+
});
1284+
1285+
let drop_log = helper.finish(res);
1286+
1287+
assert_eq!(
1288+
&*drop_log,
1289+
[
1290+
DropLogItem::Create(0),
1291+
DropLogItem::Create(1),
1292+
DropLogItem::Drop(0),
1293+
DropLogItem::Drop(1)
1294+
]
1295+
);
1296+
}
1297+
}

0 commit comments

Comments
 (0)