use crate::internal_prelude::*;
#[cfg(test)]
use std::sync::atomic::{AtomicBool, Ordering};
define_derive_deftly! {
BombImpls:
impl $ttype {
pub(crate) const fn new_armed() -> Self {
let status = Status::ARMED_IN_TESTS;
$ttype { status }
}
}
#[cfg(test)]
impl $ttype {
pub(crate) fn new_simulated() -> (Self, SimulationHandle) {
let handle = SimulationHandle::new();
let status = S::ArmedSimulated(handle.clone());
($ttype { status }, handle)
}
pub(crate) fn make_simulated(&mut self) -> SimulationHandle {
let handle = SimulationHandle::new();
let new_status = S::ArmedSimulated(handle.clone());
let old_status = mem::replace(&mut self.status, new_status);
assert!(matches!(old_status, S::Armed));
handle
}
fn drop_impl(&mut self) {
let status = mem::replace(&mut self.status, S::Disarmed);
<$ttype as DropStatus>::drop_status(status);
}
}
#[cfg(test)]
impl Drop for $ttype {
fn drop(&mut self) {
self.drop_impl();
}
}
}
#[allow(unused)]
trait DropStatus {
fn drop_status(status: Status);
}
#[derive(Deftly, Debug)]
#[derive_deftly(BombImpls)]
pub(crate) struct DropBomb {
status: Status,
}
#[derive(Deftly, Debug)]
#[derive_deftly(BombImpls)]
pub(crate) struct DropBombCondition {
#[allow(dead_code)] status: Status,
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) struct SimulationHandle {
exploded: Arc<AtomicBool>,
}
#[cfg(test)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) struct SimulationExploded;
#[derive(Debug)]
enum Status {
Disarmed,
#[cfg(test)]
Armed,
#[cfg(test)]
ArmedSimulated(SimulationHandle),
}
use Status as S;
impl DropBomb {
pub(crate) fn disarm(&mut self) {
self.status = S::Disarmed;
}
}
#[cfg(test)]
impl DropStatus for DropBomb {
fn drop_status(status: Status) {
match status {
S::Disarmed => {}
S::Armed => panic!("DropBomb dropped without a previous call to .disarm()"),
S::ArmedSimulated(handle) => handle.set_exploded(),
}
}
}
#[cfg(test)] macro_rules! drop_bomb_disarm_assert {
{ $bomb:expr, $condition:expr $(,)? } => {
$bomb.disarm_assert(
|| $condition,
format_args!(concat!("condition = ", stringify!($condition))),
)
};
{ $bomb:expr, $condition:expr, $fmt:literal $($rest:tt)* } => {
$bomb.disarm_assert(
|| $condition,
format_args!(concat!("condition = ", stringify!($condition), ": ", $fmt),
$($rest)*),
)
};
}
impl DropBombCondition {
#[inline]
#[cfg(test)] pub(crate) fn disarm_assert(&mut self, call: impl FnOnce() -> bool, msg: fmt::Arguments) {
match mem::replace(&mut self.status, S::Disarmed) {
S::Disarmed => {
let _ = call;
let _ = msg;
#[cfg(test)]
panic!("disarm_assert called more than once!");
}
#[cfg(test)]
S::Armed => {
if !call() {
panic!("drop condition violated: dropped, but condition is false: {msg}");
}
}
#[cfg(test)]
#[allow(clippy::print_stderr)]
S::ArmedSimulated(handle) => {
if !call() {
eprintln!("drop condition violated in simulation: {msg}");
handle.set_exploded();
}
}
}
}
}
impl Default for DropBombCondition {
fn default() -> DropBombCondition {
Self::new_armed()
}
}
#[cfg(test)]
impl DropStatus for DropBombCondition {
fn drop_status(status: Status) {
assert!(matches!(status, S::Disarmed));
}
}
#[cfg(test)]
impl SimulationHandle {
pub(crate) fn outcome(mut self) -> Result<(), SimulationExploded> {
let panicked = Arc::into_inner(mem::take(&mut self.exploded))
.expect("bomb has not yet been dropped")
.into_inner();
if panicked {
Err(SimulationExploded)
} else {
Ok(())
}
}
pub(crate) fn expect_ok(self) {
let () = self.outcome().expect("bomb unexpectedly exploded");
}
pub(crate) fn expect_exploded(self) {
let SimulationExploded = self
.outcome()
.expect_err("bomb unexpectedly didn't explode");
}
fn new() -> Self {
SimulationHandle {
exploded: Default::default(),
}
}
fn clone(&self) -> Self {
SimulationHandle {
exploded: self.exploded.clone(),
}
}
fn set_exploded(&self) {
self.exploded.store(true, Ordering::Release);
}
}
impl Status {
#[cfg(test)]
const ARMED_IN_TESTS: Status = S::Armed;
#[cfg(not(test))]
const ARMED_IN_TESTS: Status = S::Disarmed;
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::string_slice)] #![allow(clippy::let_and_return)]
use super::*;
use std::any::Any;
use std::panic::catch_unwind;
#[test]
fn bomb_disarmed() {
let mut b = DropBomb::new_armed();
b.disarm();
drop(b);
}
#[test]
fn bomb_panic() {
let mut b = DropBomb::new_armed();
let _: Box<dyn Any> = catch_unwind(AssertUnwindSafe(|| b.drop_impl())).unwrap_err();
}
#[test]
fn bomb_sim_disarmed() {
let (mut b, h) = DropBomb::new_simulated();
b.disarm();
drop(b);
h.expect_ok();
}
#[test]
fn bomb_sim_explosion() {
let (b, h) = DropBomb::new_simulated();
drop(b);
h.expect_exploded();
}
#[test]
fn bomb_make_sim_explosion() {
let mut b = DropBomb::new_armed();
let h = b.make_simulated();
drop(b);
h.expect_exploded();
}
struct HasBomb {
on_drop: Result<(), ()>,
bomb: DropBombCondition,
}
impl Drop for HasBomb {
fn drop(&mut self) {
drop_bomb_disarm_assert!(self.bomb, self.on_drop.is_ok());
}
}
#[test]
fn cond_ok() {
let hb = HasBomb {
on_drop: Ok(()),
bomb: DropBombCondition::new_armed(),
};
drop(hb);
}
#[test]
fn cond_sim_explosion() {
let (bomb, h) = DropBombCondition::new_simulated();
let hb = HasBomb {
on_drop: Err(()),
bomb,
};
drop(hb);
h.expect_exploded();
}
#[test]
fn cond_explosion_panic() {
let mut bomb = DropBombCondition::new_armed();
let _: Box<dyn Any> = catch_unwind(AssertUnwindSafe(|| {
bomb.disarm_assert(|| false, format_args!("testing"));
}))
.unwrap_err();
}
#[test]
fn cond_forgot_drop_impl() {
struct ForgotDropImpl {
bomb: DropBombCondition,
}
let fdi = ForgotDropImpl {
bomb: DropBombCondition::new_armed(),
};
let mut bomb = fdi.bomb;
let _: Box<dyn Any> = catch_unwind(AssertUnwindSafe(|| bomb.drop_impl())).unwrap_err();
}
}