#[allow(unused_imports)]
pub use super::*;
#[cfg(test)]
#[allow(
clippy::items_after_statements,
clippy::redundant_clone,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
trivial_casts,
clippy::borrow_as_ptr,
clippy::cast_ptr_alignment,
clippy::unused_self,
unused_qualifications,
unreachable_pub,
private_interfaces
)] mod audit_tests {
use super::*;
use core::sync::atomic::{AtomicUsize, Ordering};
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
static DROP_COUNT_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serialize_drop_count() -> std::sync::MutexGuard<'static, ()> {
DROP_COUNT_SERIAL
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct DropCounter(#[allow(dead_code)] u32);
impl Drop for DropCounter {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn borrow_exclusion_and_recovery() {
let mut a = RefAny::new(7i32);
let mut b = a.clone();
{
let r = a.downcast_ref::<i32>().unwrap();
assert_eq!(*r, 7);
assert!(b.downcast_mut::<i32>().is_none());
assert!(b.downcast_ref::<i32>().is_some());
}
{
let mut m = a.downcast_mut::<i32>().unwrap();
*m = 42;
assert!(b.downcast_ref::<i32>().is_none());
}
assert_eq!(*a.downcast_ref::<i32>().unwrap(), 42);
}
#[test]
fn type_id_guard() {
let mut a = RefAny::new(1u64);
assert!(a.downcast_ref::<i32>().is_none());
assert!(a.downcast_ref::<u64>().is_some());
assert_eq!(
RefAny::get_type_id_static::<u64>(),
RefAny::get_type_id_static::<u64>()
);
assert_ne!(
RefAny::get_type_id_static::<u64>(),
RefAny::get_type_id_static::<i64>()
);
}
#[test]
fn replace_contents_drops_exactly_once() {
let _serial = serialize_drop_count();
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(1));
let b = RefAny::new(DropCounter(2));
assert!(a.replace_contents(b));
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
}
#[test]
fn replace_contents_denied_while_borrowed() {
let mut a = RefAny::new(1i32);
let mut a2 = a.clone();
let r = a.downcast_ref::<i32>().unwrap();
assert!(!a2.replace_contents(RefAny::new(2i32)));
drop(r);
assert!(a2.replace_contents(RefAny::new(2i32)));
}
#[test]
fn miri_new_downcast_drop_roundtrip() {
let _serial = serialize_drop_count();
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(9));
assert!(a.downcast_ref::<DropCounter>().is_some());
assert!(a.downcast_ref::<u8>().is_none());
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_alignment_preserved() {
#[repr(align(16))]
#[derive(Debug)]
struct Over(u64);
let mut a = RefAny::new(Over(0xABCD));
let r = a.downcast_ref::<Over>().unwrap();
assert_eq!(r.0, 0xABCD);
assert_eq!((&raw const *r) as usize % 16, 0);
}
#[test]
fn miri_clone_refcount_increment_decrement() {
let _serial = serialize_drop_count();
DROP_COUNT.store(0, Ordering::SeqCst);
{
let a = RefAny::new(DropCounter(1));
assert_eq!(a.get_ref_count(), 1);
let b = a.clone();
assert_eq!(a.get_ref_count(), 2);
assert_eq!(b.get_ref_count(), 2);
{
let c = b.clone();
assert_eq!(c.get_ref_count(), 3);
}
assert_eq!(a.get_ref_count(), 2);
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_downcast_mut_mutation_visible_across_clones() {
let mut a = RefAny::new(10u32);
let mut b = a.clone();
{
let mut m = a.downcast_mut::<u32>().unwrap();
*m += 5;
}
assert_eq!(*b.downcast_ref::<u32>().unwrap(), 15);
}
#[test]
fn miri_borrow_counter_transitions_and_underflow_guard() {
let a = RefAny::new(0i32);
let rc = &a.sharing_info;
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
rc.increase_ref();
assert!(rc.can_be_shared()); assert!(!rc.can_be_shared_mut()); rc.decrease_ref();
assert!(rc.can_be_shared_mut());
rc.increase_refmut();
assert!(!rc.can_be_shared()); assert!(!rc.can_be_shared_mut());
rc.decrease_refmut();
assert!(rc.can_be_shared_mut());
rc.decrease_ref();
rc.decrease_refmut();
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
}
#[test]
fn miri_type_id_static_stable_and_distinct() {
assert_eq!(
RefAny::get_type_id_static::<(u8, u64)>(),
RefAny::get_type_id_static::<(u8, u64)>()
);
assert_ne!(
RefAny::get_type_id_static::<u32>(),
RefAny::get_type_id_static::<[u32; 2]>()
);
}
#[test]
fn miri_zst_roundtrip_and_destructor() {
let _serial = serialize_drop_count();
DROP_COUNT.store(0, Ordering::SeqCst);
struct ZstDrop;
impl Drop for ZstDrop {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
{
let mut a = RefAny::new(ZstDrop);
assert_eq!(a.get_data_len(), 0);
assert!(a.downcast_ref::<ZstDrop>().is_some());
let _b = a.clone();
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_replace_contents_alloc_paths() {
let _serial = serialize_drop_count();
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(1));
assert!(a.replace_contents(RefAny::new(DropCounter(2))));
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); assert_eq!(a.downcast_ref::<DropCounter>().unwrap().0, 2u32);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
}
#[test]
fn miri_replace_contents_changes_layout() {
let mut a = RefAny::new(7u8);
assert!(a.replace_contents(RefAny::new(0x1122_3344_5566_7788u64)));
{
let r = a.downcast_ref::<u64>().unwrap();
assert_eq!(*r, 0x1122_3344_5566_7788u64);
assert_eq!((&raw const *r) as usize % core::mem::align_of::<u64>(), 0);
}
assert!(a.downcast_ref::<u8>().is_none());
}
#[test]
fn miri_refcount_clone_keeps_inner_alive() {
let a = RefAny::new(5usize);
let rc0 = a.sharing_info.clone(); let rc1 = rc0.clone(); assert_eq!(a.get_ref_count(), 3);
drop(rc1);
drop(rc0);
assert_eq!(a.get_ref_count(), 1);
assert_eq!(*a.clone().downcast_ref::<usize>().unwrap(), 5);
}
}
#[cfg(test)]
#[allow(
clippy::items_after_statements,
clippy::redundant_clone,
clippy::needless_pass_by_value,
clippy::needless_range_loop,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_lossless,
clippy::float_cmp,
clippy::unreadable_literal,
clippy::unusual_byte_groupings,
clippy::many_single_char_names,
clippy::used_underscore_binding,
clippy::borrow_as_ptr,
clippy::cast_ptr_alignment,
clippy::fn_to_numeric_cast_any,
trivial_casts,
unused_qualifications,
unreachable_pub,
private_interfaces,
missing_debug_implementations,
missing_copy_implementations
)] mod autotest_generated {
use alloc::{string::String, vec::Vec};
use core::{
ffi::c_void,
sync::atomic::{AtomicUsize, Ordering},
};
use super::*;
extern "C" fn noop_destructor(_: *mut c_void) {}
fn round_trip<T: 'static + Clone + PartialEq + core::fmt::Debug>(value: T) {
let mut a = RefAny::new(value.clone());
let r = a
.downcast_ref::<T>()
.expect("downcast to the stored type must succeed");
assert_eq!(*r, value);
}
#[test]
#[should_panic(expected = "NULL pointer passed for non-ZST type")]
fn new_c_null_ptr_with_nonzero_len_panics() {
drop(RefAny::new_c(
core::ptr::null(),
4,
4,
RefAny::get_type_id_static::<u32>(),
AzString::from_const_str("autotest::NullPtr"),
noop_destructor,
0,
0,
));
}
#[test]
#[should_panic(expected = "Failed to create layout")]
fn new_c_non_power_of_two_align_panics() {
let value: u32 = 7;
drop(RefAny::new_c(
(&raw const value).cast::<c_void>(),
4,
3, RefAny::get_type_id_static::<u32>(),
AzString::from_const_str("autotest::BadAlign"),
noop_destructor,
0,
0,
));
}
#[test]
#[should_panic(expected = "Failed to create layout")]
fn new_c_huge_len_panics_instead_of_overflowing() {
let value: u8 = 1;
drop(RefAny::new_c(
(&raw const value).cast::<c_void>(),
usize::MAX,
1,
RefAny::get_type_id_static::<u8>(),
AzString::from_const_str("autotest::HugeLen"),
noop_destructor,
0,
0,
));
}
#[test]
fn new_c_zero_len_null_ptr_is_a_clean_zst() {
let mut a = RefAny::new_c(
core::ptr::null(),
0,
0, RefAny::get_type_id_static::<()>(),
AzString::from_const_str("autotest::Zst"),
noop_destructor,
0,
0,
);
assert_eq!(a.get_data_len(), 0);
assert!(a.get_data_ptr().is_null());
assert!(a.is_type(RefAny::get_type_id_static::<()>()));
assert!(a.downcast_ref::<()>().is_some());
assert!(a.downcast_mut::<()>().is_some());
assert!(a.sharing_info.can_be_shared_mut());
}
#[test]
fn new_c_round_trip_matches_rust_constructor() {
let value: u64 = 0xDEAD_BEEF_CAFE_BABE;
let mut a = RefAny::new_c(
(&raw const value).cast::<c_void>(),
core::mem::size_of::<u64>(),
core::mem::align_of::<u64>(),
RefAny::get_type_id_static::<u64>(),
AzString::from_const_str("u64"),
noop_destructor,
7,
9,
);
assert_eq!(a.get_data_len(), core::mem::size_of::<u64>());
assert_eq!(a.get_ref_count(), 1);
assert_eq!(a.get_serialize_fn(), 7);
assert_eq!(a.get_deserialize_fn(), 9);
assert!(a.can_serialize());
assert!(a.can_deserialize());
assert_eq!(*a.downcast_ref::<u64>().unwrap(), value);
}
#[test]
fn new_c_wrong_type_id_rejects_downcast() {
let value: u64 = 0x0102_0304_0506_0708;
let real_id = RefAny::get_type_id_static::<u64>();
let mut a = RefAny::new_c(
(&raw const value).cast::<c_void>(),
core::mem::size_of::<u64>(),
core::mem::align_of::<u64>(),
real_id ^ 1, AzString::from_const_str("u64"),
noop_destructor,
0,
0,
);
assert!(!a.is_type(real_id));
assert!(a.downcast_ref::<u64>().is_none());
assert!(a.downcast_mut::<u64>().is_none());
assert!(a.sharing_info.can_be_shared_mut());
}
#[test]
fn new_c_over_aligned_small_payload() {
let value: u8 = 0x5A;
let mut a = RefAny::new_c(
(&raw const value).cast::<c_void>(),
1,
16,
RefAny::get_type_id_static::<u8>(),
AzString::from_const_str("u8"),
noop_destructor,
0,
0,
);
assert_eq!(a.get_data_ptr() as usize % 16, 0);
assert_eq!(*a.downcast_ref::<u8>().unwrap(), 0x5A);
}
#[test]
fn new_c_preserves_unicode_and_empty_type_names() {
let value: u32 = 0;
let weird = "app::💥Ünïcødé<T>\u{202E}rtl\u{0}nul";
let a = RefAny::new_c(
(&raw const value).cast::<c_void>(),
4,
4,
1,
AzString::from(String::from(weird)),
noop_destructor,
0,
0,
);
assert_eq!(a.get_type_name().as_str(), weird);
let b = RefAny::new_c(
(&raw const value).cast::<c_void>(),
4,
4,
2,
AzString::from_const_str(""),
noop_destructor,
0,
0,
);
assert_eq!(b.get_type_name().as_str(), "");
}
#[test]
fn new_invariants_hold() {
let mut a = RefAny::new(0x1122_3344u32);
assert_eq!(a.get_data_len(), core::mem::size_of::<u32>());
assert!(!a.get_data_ptr().is_null());
assert_eq!(a.get_data_ptr() as usize % core::mem::align_of::<u32>(), 0);
assert_eq!(a.get_type_id(), RefAny::get_type_id_static::<u32>());
assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
assert_eq!(a.get_type_name().as_str(), "u32");
assert_eq!(a.get_ref_count(), 1);
assert!(a.has_no_copies());
assert_eq!(a.get_serialize_fn(), 0);
assert_eq!(a.get_deserialize_fn(), 0);
assert_eq!(a.get_update_fn(), 0);
assert!(!a.can_serialize());
assert!(!a.can_deserialize());
assert!(a.sharing_info.can_be_shared());
assert!(a.sharing_info.can_be_shared_mut());
assert_eq!(a.instance_id, 0);
assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x1122_3344);
}
#[test]
fn new_zero_sized_array_of_aligned_type_is_a_zst() {
let mut a = RefAny::new([0u64; 0]);
assert_eq!(a.get_data_len(), 0);
assert!(a.get_data_ptr().is_null());
assert_eq!(
a.sharing_info
.debug_get_refcount_copied()
._internal_layout_size,
0
);
assert!(a.downcast_ref::<[u64; 0]>().is_some());
assert_eq!(a.get_ref_count(), 1);
}
#[test]
fn new_large_over_aligned_payload_round_trips() {
#[repr(align(64))]
#[derive(Clone)]
struct Big([u8; 4096]);
let mut a = RefAny::new(Big([0xAB; 4096]));
assert_eq!(a.get_data_len(), 4096);
assert_eq!(a.get_data_ptr() as usize % 64, 0);
let r = a.downcast_ref::<Big>().unwrap();
assert_eq!((&raw const *r) as usize % 64, 0);
assert!(r.0.iter().all(|&b| b == 0xAB));
}
#[test]
fn integer_limits_round_trip() {
round_trip(u8::MIN);
round_trip(u8::MAX);
round_trip(i8::MIN);
round_trip(i8::MAX);
round_trip(u16::MAX);
round_trip(i16::MIN);
round_trip(u32::MAX);
round_trip(i32::MIN);
round_trip(u64::MAX);
round_trip(i64::MIN);
round_trip(u128::MAX);
round_trip(i128::MIN);
round_trip(i128::MAX);
round_trip(usize::MAX);
round_trip(isize::MIN);
round_trip(0usize);
}
#[test]
fn float_extremes_round_trip_bit_exact() {
let mut nan = RefAny::new(f64::NAN);
assert!(nan.downcast_ref::<f64>().unwrap().is_nan());
let bits = 0x7FF0_0000_0000_0001u64;
let mut payload_nan = RefAny::new(f64::from_bits(bits));
assert_eq!(payload_nan.downcast_ref::<f64>().unwrap().to_bits(), bits);
let mut neg_zero = RefAny::new(-0.0f64);
let nz = neg_zero.downcast_ref::<f64>().unwrap();
assert!(*nz == 0.0 && nz.is_sign_negative());
drop(nz);
let mut inf = RefAny::new(f32::NEG_INFINITY);
assert_eq!(*inf.downcast_ref::<f32>().unwrap(), f32::NEG_INFINITY);
assert!(inf.downcast_ref::<f64>().is_none());
round_trip(f64::MIN);
round_trip(f64::MAX);
round_trip(f64::MIN_POSITIVE);
round_trip(f32::EPSILON);
round_trip(f32::MAX);
}
#[test]
fn owned_unicode_payloads_round_trip() {
round_trip(String::new());
round_trip(String::from("héllo 🌍 \u{202E}rtl\u{0}nul"));
round_trip('🌍');
let v: Vec<String> = vec![String::from("a"), String::from("🎉"), String::new()];
round_trip(v);
}
#[test]
fn padded_struct_round_trips() {
#[derive(Clone, PartialEq, Debug)]
#[repr(C)]
struct Padded {
a: u8,
b: u64,
c: u8,
}
round_trip(Padded {
a: 0xFF,
b: u64::MAX,
c: 0x01,
});
}
#[test]
fn set_serialize_fn_zero_and_extremes() {
let mut a = RefAny::new(1u32);
assert_eq!(a.get_serialize_fn(), 0);
assert!(!a.can_serialize());
a.set_serialize_fn(usize::MAX);
assert_eq!(a.get_serialize_fn(), usize::MAX);
assert!(a.can_serialize());
a.set_serialize_fn(1);
assert_eq!(a.get_serialize_fn(), 1);
assert!(a.can_serialize());
a.set_serialize_fn(0);
assert_eq!(a.get_serialize_fn(), 0);
assert!(!a.can_serialize());
let mut b = a.clone();
b.set_serialize_fn(42);
assert_eq!(a.get_serialize_fn(), 42);
assert!(a.can_serialize());
b.set_serialize_fn(0);
assert!(!a.can_serialize());
}
#[test]
fn set_deserialize_fn_zero_and_extremes() {
let mut a = RefAny::new(1u32);
assert_eq!(a.get_deserialize_fn(), 0);
assert!(!a.can_deserialize());
a.set_deserialize_fn(usize::MAX);
assert_eq!(a.get_deserialize_fn(), usize::MAX);
assert!(a.can_deserialize());
a.set_deserialize_fn(1);
assert_eq!(a.get_deserialize_fn(), 1);
a.set_deserialize_fn(0);
assert_eq!(a.get_deserialize_fn(), 0);
assert!(!a.can_deserialize());
let mut b = a.clone();
b.set_deserialize_fn(42);
assert_eq!(a.get_deserialize_fn(), 42);
b.set_deserialize_fn(0);
assert!(!a.can_deserialize());
}
#[test]
fn set_update_fn_zero_and_extremes() {
let mut a = RefAny::new(1u32);
assert_eq!(a.get_update_fn(), 0);
a.set_update_fn(usize::MAX);
assert_eq!(a.get_update_fn(), usize::MAX);
a.set_update_fn(0);
assert_eq!(a.get_update_fn(), 0);
assert!(a.downcast_mut::<u32>().is_some());
}
static UPDATE_CALLS: AtomicUsize = AtomicUsize::new(0);
static UPDATE_LEN: AtomicUsize = AtomicUsize::new(0);
static UPDATE_PRE_VALUE: AtomicUsize = AtomicUsize::new(0);
extern "C" fn record_update(ptr: *const c_void, len: usize) {
UPDATE_CALLS.fetch_add(1, Ordering::SeqCst);
UPDATE_LEN.store(len, Ordering::SeqCst);
if !ptr.is_null() && len == core::mem::size_of::<u32>() {
let pre = unsafe { core::ptr::read_unaligned(ptr.cast::<u32>()) };
UPDATE_PRE_VALUE.store(pre as usize, Ordering::SeqCst);
}
}
#[test]
fn update_fn_fires_once_with_pre_mutation_data() {
UPDATE_CALLS.store(0, Ordering::SeqCst);
let mut a = RefAny::new(7u32);
let cb: extern "C" fn(*const c_void, usize) = record_update;
a.set_update_fn(cb as usize);
assert_eq!(a.get_update_fn(), cb as usize);
{
let mut m = a.downcast_mut::<u32>().unwrap();
*m = 9;
}
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(UPDATE_LEN.load(Ordering::SeqCst), 4);
assert_eq!(UPDATE_PRE_VALUE.load(Ordering::SeqCst), 7);
assert!(a.downcast_mut::<u64>().is_none());
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(*a.downcast_ref::<u32>().unwrap(), 9);
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
let mut b = a.clone();
let r = a.downcast_ref::<u32>().unwrap();
assert!(b.downcast_mut::<u32>().is_none());
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
drop(r);
b.set_update_fn(0);
assert!(b.downcast_mut::<u32>().is_some());
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
}
#[test]
fn is_type_true_false_and_extremes() {
let a = RefAny::new(0u32);
let id = a.get_type_id();
assert!(a.is_type(id));
assert!(!a.is_type(!id)); assert!(!a.is_type(id.wrapping_add(1)));
assert!(!a.is_type(RefAny::get_type_id_static::<i32>()));
if id != 0 {
assert!(!a.is_type(0));
}
if id != u64::MAX {
assert!(!a.is_type(u64::MAX));
}
}
#[test]
fn has_no_copies_transitions() {
let mut a = RefAny::new(1u32);
assert!(a.has_no_copies());
{
let b = a.clone();
assert!(!a.has_no_copies()); assert!(!b.has_no_copies());
}
assert!(a.has_no_copies());
{
let mut c = a.clone();
let r = c.downcast_ref::<u32>().unwrap();
assert_eq!(*r, 1);
assert!(!a.has_no_copies());
}
assert!(a.has_no_copies());
{
let mut c = a.clone();
let m = c.downcast_mut::<u32>().unwrap();
assert_eq!(*m, 1);
assert!(!a.has_no_copies());
}
assert!(a.has_no_copies());
}
#[test]
fn can_serialize_and_can_deserialize_track_the_fn_pointers() {
let mut a = RefAny::new(1u32);
assert!(!a.can_serialize());
assert!(!a.can_deserialize());
a.set_serialize_fn(1);
assert!(a.can_serialize());
assert!(!a.can_deserialize());
a.set_deserialize_fn(usize::MAX);
assert!(a.can_serialize());
assert!(a.can_deserialize());
a.set_serialize_fn(0);
a.set_deserialize_fn(0);
assert!(!a.can_serialize());
assert!(!a.can_deserialize());
}
#[test]
fn get_ref_count_tracks_clones_and_borrow_guards() {
let mut a = RefAny::new(5u8);
assert_eq!(a.get_ref_count(), 1);
let mut b = a.clone();
assert_eq!(a.get_ref_count(), 2);
assert_eq!(b.get_ref_count(), 2);
{
let r = b.downcast_ref::<u8>().unwrap();
assert_eq!(*r, 5);
assert_eq!(a.get_ref_count(), 3);
}
assert_eq!(a.get_ref_count(), 2);
{
let m = b.downcast_mut::<u8>().unwrap();
assert_eq!(*m, 5);
assert_eq!(a.get_ref_count(), 3);
}
assert_eq!(a.get_ref_count(), 2);
drop(b);
assert_eq!(a.get_ref_count(), 1);
assert_eq!(*a.downcast_ref::<u8>().unwrap(), 5);
}
#[test]
fn debug_snapshot_matches_the_live_counters() {
let a = RefAny::new(0x1122_3344u32);
let d = a.sharing_info.debug_get_refcount_copied();
assert_eq!(d.num_copies, 1);
assert_eq!(d.num_refs, 0);
assert_eq!(d.num_mutable_refs, 0);
assert_eq!(d._internal_len, 4);
assert_eq!(d._internal_layout_size, 4);
assert_eq!(d._internal_layout_align, core::mem::align_of::<u32>());
assert_eq!(d.type_id, RefAny::get_type_id_static::<u32>());
assert_eq!(d.type_name.as_str(), "u32");
assert_ne!(d.custom_destructor, 0);
assert_eq!(d.serialize_fn, 0);
assert_eq!(d.deserialize_fn, 0);
a.sharing_info.increase_ref();
a.sharing_info.increase_refmut();
let d2 = a.sharing_info.debug_get_refcount_copied();
assert_eq!(d2.num_refs, 1);
assert_eq!(d2.num_mutable_refs, 1);
assert_eq!(d.num_refs, 0);
a.sharing_info.decrease_ref();
a.sharing_info.decrease_refmut();
let d3 = a.sharing_info.debug_get_refcount_copied();
assert_eq!((d3.num_refs, d3.num_mutable_refs), (0, 0));
assert!(!alloc::format!("{:?}", a.sharing_info).is_empty());
}
#[test]
fn get_type_name_reports_the_rust_type() {
#[derive(Clone)]
struct AutotestNamed(#[allow(dead_code)] u8);
let a = RefAny::new(AutotestNamed(1));
let name = a.get_type_name();
assert!(
name.as_str().contains("AutotestNamed"),
"unexpected type name: {}",
name.as_str()
);
let generic = RefAny::new(Vec::<String>::new());
assert!(generic.get_type_name().as_str().contains("Vec"));
assert_eq!(RefAny::new(1u32).get_type_name().as_str(), "u32");
}
#[test]
fn refcount_new_downcast_and_clone_lifecycle() {
let rc = RefCount::new(RefCountInner {
_internal_ptr: core::ptr::null(),
num_copies: AtomicUsize::new(1),
num_refs: AtomicUsize::new(0),
num_mutable_refs: AtomicUsize::new(0),
_internal_len: 0,
_internal_layout_size: 0,
_internal_layout_align: 1,
type_id: 0xDEAD_BEEF,
type_name: AzString::from_const_str("autotest::Synthetic"),
custom_destructor: noop_destructor,
serialize_fn: 0,
deserialize_fn: 0,
update_fn: 0,
});
assert!(!rc.ptr.is_null());
assert!(rc.run_destructor);
let inner = rc.downcast();
assert_eq!(inner.type_id, 0xDEAD_BEEF);
assert_eq!(inner.type_name.as_str(), "autotest::Synthetic");
assert_eq!(inner._internal_len, 0);
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
let c1 = rc.clone();
assert_eq!(rc.debug_get_refcount_copied().num_copies, 2);
let c2 = c1.clone();
assert_eq!(rc.debug_get_refcount_copied().num_copies, 3);
drop(c2);
drop(c1);
assert_eq!(rc.debug_get_refcount_copied().num_copies, 1);
}
#[test]
fn borrow_counters_saturate_at_zero_and_stay_usable() {
let mut a = RefAny::new(3i64);
{
let rc = &a.sharing_info;
for _ in 0..64 {
rc.decrease_ref();
rc.decrease_refmut();
}
let d = rc.debug_get_refcount_copied();
assert_eq!(d.num_refs, 0);
assert_eq!(d.num_mutable_refs, 0);
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
for _ in 0..256 {
rc.increase_ref();
}
assert_eq!(rc.debug_get_refcount_copied().num_refs, 256);
assert!(rc.can_be_shared());
assert!(!rc.can_be_shared_mut());
for _ in 0..256 {
rc.decrease_ref();
}
assert_eq!(rc.debug_get_refcount_copied().num_refs, 0);
assert!(rc.can_be_shared_mut());
rc.increase_refmut();
rc.increase_refmut();
assert!(!rc.can_be_shared());
rc.decrease_refmut();
rc.decrease_refmut();
rc.decrease_refmut();
assert_eq!(rc.debug_get_refcount_copied().num_mutable_refs, 0);
}
assert_eq!(*a.downcast_ref::<i64>().unwrap(), 3);
assert!(a.downcast_mut::<i64>().is_some());
}
#[test]
fn type_id_static_is_stable_and_collision_free() {
let ids = [
RefAny::get_type_id_static::<u8>(),
RefAny::get_type_id_static::<u16>(),
RefAny::get_type_id_static::<u32>(),
RefAny::get_type_id_static::<u64>(),
RefAny::get_type_id_static::<u128>(),
RefAny::get_type_id_static::<usize>(),
RefAny::get_type_id_static::<i8>(),
RefAny::get_type_id_static::<i16>(),
RefAny::get_type_id_static::<i32>(),
RefAny::get_type_id_static::<i64>(),
RefAny::get_type_id_static::<i128>(),
RefAny::get_type_id_static::<isize>(),
RefAny::get_type_id_static::<f32>(),
RefAny::get_type_id_static::<f64>(),
RefAny::get_type_id_static::<bool>(),
RefAny::get_type_id_static::<char>(),
RefAny::get_type_id_static::<()>(),
RefAny::get_type_id_static::<String>(),
RefAny::get_type_id_static::<Vec<u8>>(),
RefAny::get_type_id_static::<Vec<u16>>(),
RefAny::get_type_id_static::<[u8; 1]>(),
RefAny::get_type_id_static::<[u8; 2]>(),
RefAny::get_type_id_static::<(u8, u8)>(),
RefAny::get_type_id_static::<(u8, u16)>(),
RefAny::get_type_id_static::<Option<u8>>(),
RefAny::get_type_id_static::<Option<u16>>(),
];
for i in 0..ids.len() {
for j in (i + 1)..ids.len() {
assert_ne!(ids[i], ids[j], "type id collision between {i} and {j}");
}
}
assert_eq!(RefAny::get_type_id_static::<Vec<u8>>(), ids[18]);
assert_eq!(RefAny::get_type_id_static::<u8>(), ids[0]);
}
#[test]
fn root_instance_id_is_zero_and_clones_are_distinct() {
let a = RefAny::new(0u8);
assert_eq!(a.instance_id, 0);
let b = a.clone();
let c = b.clone();
assert_ne!(b.instance_id, 0);
assert_ne!(c.instance_id, 0);
assert_ne!(b.instance_id, c.instance_id);
assert_eq!(a.get_ref_count(), 3);
}
#[test]
fn replace_contents_zst_and_value_transitions() {
#[derive(Clone)]
struct Zst;
let mut a = RefAny::new(Zst);
assert_eq!(a.get_data_len(), 0);
assert!(a.get_data_ptr().is_null());
assert!(a.replace_contents(RefAny::new(0x4142_4344u32)));
assert_eq!(a.get_data_len(), 4);
assert!(!a.get_data_ptr().is_null());
assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x4142_4344);
assert!(a.replace_contents(RefAny::new(Zst)));
assert_eq!(a.get_data_len(), 0);
assert!(a.get_data_ptr().is_null());
assert!(a.downcast_ref::<Zst>().is_some());
assert!(a.downcast_mut::<Zst>().is_some());
assert!(a.sharing_info.can_be_shared_mut());
}
#[test]
fn replace_contents_is_visible_to_all_clones() {
let mut a = RefAny::new(1u32);
let mut b = a.clone();
assert!(a.replace_contents(RefAny::new(2u32)));
assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
assert!(a.replace_contents(RefAny::new(String::from("swapped"))));
assert!(b.downcast_ref::<u32>().is_none());
assert_eq!(b.downcast_ref::<String>().unwrap().as_str(), "swapped");
assert!(b.get_type_name().as_str().contains("String"));
assert_eq!(b.get_type_id(), RefAny::get_type_id_static::<String>());
}
#[test]
fn replace_contents_denied_while_mutably_borrowed() {
let mut a = RefAny::new(1u32);
let mut b = a.clone();
let m = a.downcast_mut::<u32>().unwrap();
assert!(!b.replace_contents(RefAny::new(2u32)));
drop(m);
assert!(b.replace_contents(RefAny::new(2u32)));
assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
}
#[test]
fn replace_contents_resets_the_fn_pointers_to_the_new_value() {
let mut a = RefAny::new(1u32);
a.set_serialize_fn(3);
a.set_deserialize_fn(4);
assert!(a.can_serialize());
assert!(a.can_deserialize());
assert!(a.replace_contents(RefAny::new(2u32)));
assert_eq!(a.get_serialize_fn(), 0);
assert_eq!(a.get_deserialize_fn(), 0);
assert_eq!(a.get_update_fn(), 0);
assert!(!a.can_serialize());
assert!(!a.can_deserialize());
}
#[test]
fn repeated_replace_contents_stays_consistent() {
let mut a = RefAny::new(String::from("start"));
for i in 0..16u32 {
assert!(a.replace_contents(RefAny::new(i)));
assert_eq!(*a.downcast_ref::<u32>().unwrap(), i);
assert!(a.replace_contents(RefAny::new(u128::from(i) | (1 << 100))));
assert_eq!(
*a.downcast_ref::<u128>().unwrap(),
u128::from(i) | (1 << 100)
);
assert!(a.replace_contents(RefAny::new(String::from("s"))));
}
assert_eq!(a.downcast_ref::<String>().unwrap().as_str(), "s");
}
#[cfg(feature = "std")]
#[test]
fn panicking_payload_drop_is_contained() {
struct PanicOnDrop(#[allow(dead_code)] u64);
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("autotest: payload Drop panicked (expected, must be contained)");
}
}
let a = RefAny::new(PanicOnDrop(1));
drop(a); }
#[cfg(feature = "std")]
#[test]
fn concurrent_clone_and_borrow_keeps_the_refcount_balanced() {
use std::{sync::Arc, thread};
let shared = Arc::new(RefAny::new(11u32));
let mut handles = Vec::new();
for _ in 0..4 {
let s = Arc::clone(&shared);
handles.push(thread::spawn(move || {
for _ in 0..16 {
let mut local = (*s).clone();
let r = local
.downcast_ref::<u32>()
.expect("shared borrow must always succeed here");
assert_eq!(*r, 11);
}
}));
}
for h in handles {
h.join().expect("worker thread panicked");
}
assert_eq!(shared.get_ref_count(), 1);
}
#[test]
fn a_released_refany_answers_instead_of_aborting() {
let mut released = RefAny::new(7u32);
released.sharing_info.ptr = core::ptr::null();
assert_eq!(released.get_type_id(), 0, "a released RefAny has no type");
assert_eq!(released.get_type_name().as_str(), "<released>");
assert!(
released.downcast_ref::<u32>().is_none(),
"there is nothing to borrow",
);
assert!(
released.downcast_mut::<u32>().is_none(),
"there is nothing to borrow mutably",
);
assert!(released.downcast_ref::<u8>().is_none());
}
}