use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{self};
use crate::affinity::{Affinity, pinned_affinities};
use crate::{ThreadAware, Unaware};
type PerCore<T> = crate::Arc<T, crate::PerCore>;
#[derive(Clone, Debug)]
struct Counter {
value: sync::Arc<AtomicI32>,
}
impl Counter {
fn new() -> Self {
Self {
value: sync::Arc::new(AtomicI32::new(0)),
}
}
fn increment_by(&self, v: i32) {
self.value.fetch_add(v, Ordering::AcqRel);
}
fn value(&self) -> i32 {
self.value.load(Ordering::Acquire)
}
}
impl ThreadAware for Counter {
fn relocate(&mut self, _source: Option<Affinity>, _destination: Affinity) {
self.value = sync::Arc::new(AtomicI32::new(0));
}
}
#[test]
fn transfer_creates_new_value() {
let affinities = pinned_affinities(&[2]);
let source = Some(affinities[0]);
let destination = affinities[1];
let pmr = PerCore::new(Counter::new);
pmr.increment_by(10);
let mut pmr2 = pmr.clone();
pmr2.relocate(source, destination);
assert_eq!(pmr.value(), 10);
assert_eq!(pmr2.value(), 0);
}
#[test]
fn new_with_works() {
let pmr = PerCore::new_with((), |()| Counter::new());
pmr.increment_by(3);
assert_eq!(pmr.value(), 3);
}
#[test]
fn new_with_relocate_forwards_to_data() {
#[derive(Clone)]
struct Seed(bool);
impl ThreadAware for Seed {
fn relocate(&mut self, _source: Option<Affinity>, _destination: Affinity) {
self.0 = true;
}
}
let affinities = pinned_affinities(&[2]);
let source = Some(affinities[0]);
let destination = affinities[1];
let mut pmr = PerCore::new_with(Seed(false), |seed| {
let c = Counter::new();
if seed.0 {
c.increment_by(999);
}
c
});
assert_eq!(pmr.value(), 0, "initial factory should see un-relocated seed");
pmr.relocate(source, destination);
assert_eq!(
pmr.value(),
999,
"factory must see relocated seed (BoxedRelocate must forward relocate)"
);
}
#[test]
fn test_from_unaware() {
let mut per_core = PerCore::from_unaware(42);
assert_eq!(*per_core, 42);
let affinities = pinned_affinities(&[2]);
per_core.relocate(Some(affinities[0]), affinities[1]);
assert_eq!(*per_core, 42);
}
#[test]
fn test_partialeq() {
let value1 = PerCore::with_value(42);
let value2 = PerCore::with_value(42);
let value3 = PerCore::with_value(43);
assert_eq!(value1, value2);
assert_ne!(value1, value3);
}
#[test]
fn test_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let value1 = PerCore::with_value(42);
let value2 = PerCore::with_value(42);
let value3 = PerCore::with_value(43);
let mut hasher1 = DefaultHasher::new();
value1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
value2.hash(&mut hasher2);
let hash2 = hasher2.finish();
let mut hasher3 = DefaultHasher::new();
value3.hash(&mut hasher3);
let hash3 = hasher3.finish();
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
}
#[test]
fn test_partialord() {
let value1 = PerCore::with_value(42);
let value2 = PerCore::with_value(43);
assert!(value1 < value2);
assert!(value2 > value1);
}
#[test]
fn test_ord() {
let value1 = PerCore::with_value(42);
let value2 = PerCore::with_value(43);
let value3 = PerCore::with_value(42);
assert_eq!(value1.cmp(&value2), std::cmp::Ordering::Less);
assert_eq!(value2.cmp(&value1), std::cmp::Ordering::Greater);
assert_eq!(value1.cmp(&value3), std::cmp::Ordering::Equal);
}
#[test]
fn test_trc_clone() {
let value = PerCore::with_value(42);
let cloned_value = value.clone();
assert_eq!(*value, *cloned_value);
}
#[test]
fn test_into_arc() {
let trc = PerCore::new(|| 42);
let _arc = trc.into_arc();
let trc = PerCore::with_value(42);
let _arc = trc.into_arc();
let trc = PerCore::with_value(Unaware(42));
let _arc = trc.into_arc();
}
#[test]
fn test_from() {
let trc = PerCore::new(|| 42);
let _arc = trc.into_arc();
let trc = PerCore::with_value(42);
let _arc = trc.into_arc();
let trc = PerCore::with_value(Unaware(42));
let _arc = trc.into_arc().into_arc();
}
#[test]
fn test_trc_relocated_with_factory_data() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let trc_affinity1 = PerCore::with_value(42);
assert_eq!(*trc_affinity1, 42);
let mut trc_affinity2 = trc_affinity1;
trc_affinity2.relocate(affinity1, affinity2);
assert_eq!(*trc_affinity2, 42);
}
#[test]
fn test_trc_relocated_reuses_existing_value() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let trc1 = PerCore::with_value(42);
let trc2 = trc1.clone();
let mut trc1_relocated = trc1;
trc1_relocated.relocate(affinity1, affinity2);
assert_eq!(*trc1_relocated, 42);
let mut trc2_relocated = trc2;
trc2_relocated.relocate(affinity1, affinity2);
assert_eq!(*trc2_relocated, 42);
assert!(std::sync::Arc::ptr_eq(&trc1_relocated.into_arc(), &trc2_relocated.into_arc()));
}
#[test]
fn test_from_storage() {
use std::sync::{Arc, RwLock};
let affinities = pinned_affinities(&[2]);
let affinity1 = affinities[0];
let mut storage = super::storage::Storage::new();
let value = Arc::new(100);
storage.replace(affinity1, Arc::clone(&value));
let storage_arc = Arc::new(RwLock::new(storage));
let trc = PerCore::from_storage(Arc::clone(&storage_arc), affinity1);
assert_eq!(*trc, 100);
assert!(Arc::ptr_eq(&trc.into_arc(), &value));
}
#[test]
fn test_factory_clone_with_data() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let trc1 = PerCore::with_value(42);
let trc2 = trc1.clone();
assert_eq!(*trc1, 42);
assert_eq!(*trc2, 42);
let mut trc1_relocated = trc1;
trc1_relocated.relocate(affinity1, affinity2);
let mut trc2_relocated = trc2;
trc2_relocated.relocate(affinity1, affinity2);
assert_eq!(*trc1_relocated, 42);
assert_eq!(*trc2_relocated, 42);
}
#[test]
fn test_factory_clone_with_closure_boxed() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let trc1 = PerCore::new(|| 100);
let trc2 = trc1.clone();
assert_eq!(*trc1, 100);
assert_eq!(*trc2, 100);
let mut trc1_relocated = trc1;
trc1_relocated.relocate(affinity1, affinity2);
let mut trc2_relocated = trc2;
trc2_relocated.relocate(affinity1, affinity2);
assert_eq!(*trc1_relocated, 100);
assert_eq!(*trc2_relocated, 100);
assert!(std::sync::Arc::ptr_eq(&trc1_relocated.into_arc(), &trc2_relocated.into_arc()));
}
#[test]
fn test_factory_clone_with_manual() {
use std::sync::{Arc, RwLock};
let affinities = pinned_affinities(&[2]);
let affinity1 = affinities[0];
let mut storage = super::storage::Storage::new();
let value = Arc::new(200);
storage.replace(affinity1, Arc::clone(&value));
let storage_arc = Arc::new(RwLock::new(storage));
let trc1 = PerCore::from_storage(Arc::clone(&storage_arc), affinity1);
let trc2 = trc1.clone();
assert_eq!(*trc1, 200);
assert_eq!(*trc2, 200);
assert!(Arc::ptr_eq(&trc1.into_arc(), &trc2.into_arc()));
}
#[test]
fn test_factory_manual_relocated() {
use std::sync::{Arc, RwLock};
let affinities = pinned_affinities(&[2]);
let affinity1 = affinities[0];
let affinity2 = affinities[1];
let mut storage = super::storage::Storage::new();
let value = Arc::new(100);
storage.replace(affinity1, Arc::clone(&value));
let storage_arc = Arc::new(RwLock::new(storage));
let trc = PerCore::from_storage(Arc::clone(&storage_arc), affinity1);
assert_eq!(*trc, 100);
let mut trc_relocated = trc;
trc_relocated.relocate(Some(affinity1), affinity2);
assert_eq!(*trc_relocated, 100);
assert!(Arc::ptr_eq(&trc_relocated.into_arc(), &value));
}
#[test]
fn test_relocated_unknown_source() {
let affinities = pinned_affinities(&[2]);
let source = None;
let destination = affinities[1];
let mut trc = PerCore::with_value(42);
trc.relocate(source, destination);
assert_eq!(*trc, 42);
}
#[test]
fn test_strong_count() {
let arc = PerCore::new(Counter::new);
assert_eq!(PerCore::strong_count(&arc), 1);
let arc2 = arc.clone();
assert_eq!(PerCore::strong_count(&arc), 2);
assert_eq!(PerCore::strong_count(&arc2), 2);
let arc3 = arc.clone();
assert_eq!(PerCore::strong_count(&arc), 3);
assert_eq!(PerCore::strong_count(&arc2), 3);
assert_eq!(PerCore::strong_count(&arc3), 3);
drop(arc2);
assert_eq!(PerCore::strong_count(&arc), 2);
assert_eq!(PerCore::strong_count(&arc3), 2);
drop(arc3);
assert_eq!(PerCore::strong_count(&arc), 1);
}
#[test]
fn test_strong_count_after_relocation() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let arc1 = PerCore::new(Counter::new);
let arc2 = arc1.clone();
assert_eq!(PerCore::strong_count(&arc1), 2);
let mut arc1_relocated = arc1;
arc1_relocated.relocate(affinity1, affinity2);
assert_eq!(PerCore::strong_count(&arc1_relocated), 1);
assert_eq!(PerCore::strong_count(&arc2), 1);
}
#[test]
fn test_strong_count_with_deduplication() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let arc1 = PerCore::new(Counter::new);
let arc2 = arc1.clone();
let mut arc1_relocated = arc1;
arc1_relocated.relocate(affinity1, affinity2);
let mut arc2_relocated = arc2;
arc2_relocated.relocate(affinity1, affinity2);
assert_eq!(PerCore::strong_count(&arc1_relocated), 2);
assert_eq!(PerCore::strong_count(&arc2_relocated), 2);
}
#[test]
fn test_strong_count_independent_across_affinities() {
let affinities = pinned_affinities(&[2]);
let affinity1 = Some(affinities[0]);
let affinity2 = affinities[1];
let arc_a = PerCore::new(Counter::new);
assert_eq!(PerCore::strong_count(&arc_a), 1);
let mut arc_b = arc_a.clone();
arc_b.relocate(affinity1, affinity2);
assert_eq!(PerCore::strong_count(&arc_b), 1);
let arc_a2 = arc_a.clone();
assert_eq!(PerCore::strong_count(&arc_a), 2);
assert_eq!(PerCore::strong_count(&arc_a2), 2);
assert_eq!(PerCore::strong_count(&arc_b), 1); }
#[test]
fn test_relocated_source_equals_destination_does_not_corrupt_storage() {
let affinities = pinned_affinities(&[2]);
let affinity = affinities[0];
let arc = PerCore::new(Counter::new);
arc.increment_by(42);
assert_eq!(arc.value(), 42);
let mut arc = arc;
arc.relocate(Some(affinity), affinity);
assert_eq!(arc.value(), 0, "relocated value should come from factory");
arc.relocate(Some(affinity), affinity);
assert_eq!(
arc.value(),
0,
"subsequent relocation must not see stale pre-relocation value from storage"
);
}
#[test]
fn with_clone_fn_relocates_clone() {
let affinities = pinned_affinities(&[2]);
let source = Some(affinities[0]);
let destination = affinities[1];
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
arc.increment_by(42);
assert_eq!(arc.value(), 42);
let mut arc = arc;
arc.relocate(source, destination);
assert_eq!(arc.value(), 0, "relocated() must be called on the clone");
}
#[test]
fn with_clone_fn_dyn_trait_relocates_correctly() {
trait Plugin: ThreadAware + Send + Sync {
fn name(&self) -> &str;
}
#[derive(Clone)]
struct MyPlugin(String);
impl Plugin for MyPlugin {
fn name(&self) -> &str {
&self.0
}
}
impl ThreadAware for MyPlugin {
fn relocate(&mut self, _source: Option<Affinity>, _destination: Affinity) {
self.0 = format!("{}-relocated", self.0);
}
}
let affinities = pinned_affinities(&[2]);
let source = Some(affinities[0]);
let destination = affinities[1];
let arc = super::Arc::<dyn Plugin, crate::PerCore>::with_clone_fn(MyPlugin("orig".into()), |p: &MyPlugin| Box::new(p.clone()));
assert_eq!(arc.name(), "orig");
let mut arc = arc;
arc.relocate(source, destination);
assert_eq!(arc.name(), "orig-relocated");
}
#[test]
fn with_clone_fn_clone_and_relocate_independently() {
let affinities = pinned_affinities(&[3]);
let source = Some(affinities[0]);
let dest1 = affinities[1];
let dest2 = affinities[2];
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
arc.increment_by(10);
let mut clone1 = arc.clone();
#[expect(clippy::redundant_clone, reason = "testing independent clones")]
let mut clone2 = arc.clone();
clone1.relocate(source, dest1);
clone2.relocate(source, dest2);
assert_eq!(clone1.value(), 0);
assert_eq!(clone2.value(), 0);
}
#[test]
fn with_clone_fn_repeated_relocations() {
let affinities = pinned_affinities(&[4]);
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
arc.increment_by(99);
let mut current = arc;
for i in 0..3 {
let source = Some(affinities[i]);
let dest = affinities[i + 1];
current.relocate(source, dest);
assert_eq!(current.value(), 0, "relocation {i} should reset counter");
current.increment_by(i32::try_from(i + 1).expect("loop index fits in i32"));
}
assert_eq!(current.value(), 3);
}
#[test]
fn with_clone_fn_debug_format() {
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
let debug = format!("{arc:?}");
assert!(!debug.is_empty());
}
#[test]
fn with_clone_fn_deduplication_across_clones() {
let affinities = pinned_affinities(&[2]);
let source = Some(affinities[0]);
let dest = affinities[1];
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
let clone1 = arc.clone();
#[expect(clippy::redundant_clone, reason = "testing independent clones")]
let clone2 = arc.clone();
let mut r1 = clone1;
r1.relocate(source, dest);
let mut r2 = clone2;
r2.relocate(source, dest);
assert!(sync::Arc::ptr_eq(&r1.clone().into_arc(), &r2.clone().into_arc()));
}
#[test]
fn with_clone_fn_debug_includes_erased() {
let arc = super::Arc::<Counter, crate::PerCore>::with_clone_fn(Counter::new(), |c: &Counter| Box::new(c.clone()));
let dbg = format!("{arc:?}");
assert!(
dbg.contains("factory: Clone"),
"Debug output should mention factory Clone variant: {dbg}"
);
}
#[test]
fn factory_data_debug() {
let arc = PerCore::from_unaware(42);
let dbg = format!("{arc:?}");
assert!(dbg.contains("Data"), "Debug output should mention Data variant: {dbg}");
}
#[test]
fn factory_manual_debug() {
use std::sync::{self, RwLock};
let affinities = pinned_affinities(&[1]);
let storage = sync::Arc::new(RwLock::new(super::storage::Storage::new()));
storage
.write()
.expect("lock should not be poisoned")
.replace(affinities[0], sync::Arc::new(42));
let arc = super::Arc::<i32, crate::PerCore>::from_storage(storage, affinities[0]);
let dbg = format!("{arc:?}");
assert!(dbg.contains("Manual"), "Debug output should mention Manual variant: {dbg}");
}
#[test]
fn factory_closure_debug() {
let arc = PerCore::new(Counter::new);
let dbg = format!("{arc:?}");
assert!(dbg.contains("Closure"), "Debug output should mention Closure variant: {dbg}");
}
#[test]
fn new_boxed_relocate() {
let affinities = pinned_affinities(&[2]);
let mut arc = super::Arc::<Counter, crate::PerCore>::new_boxed(|| Box::new(Counter::new()));
arc.relocate(Some(affinities[0]), affinities[1]);
assert_eq!(arc.value(), 0, "new_boxed relocate should create a fresh counter");
}