use rust_dix::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
struct ServiceA;
struct ServiceB;
struct ServiceC;
#[test]
fn circular_dependency_self_reference() {
let result = ServiceCollection::new()
.singleton(|r| {
let _a = r.get_any(std::any::type_name::<ServiceA>());
Arc::new(ServiceA)
})
.build();
assert!(result.is_err());
match result {
Err(RdiError::CircularDependency(_)) => {}
_ => panic!("Expected CircularDependency"),
}
}
#[test]
fn circular_dependency_indirect() {
let result = ServiceCollection::new()
.singleton(|r| {
let _b = r.get_any(std::any::type_name::<ServiceB>());
Arc::new(ServiceA)
})
.singleton(|r| {
let _a = r.get_any(std::any::type_name::<ServiceA>());
Arc::new(ServiceB)
})
.build();
assert!(result.is_err());
match result {
Err(RdiError::CircularDependency(_)) => {}
_ => panic!("Expected CircularDependency"),
}
}
#[test]
fn no_circular_dependency_valid() {
let result = ServiceCollection::new()
.singleton(|_| Arc::new(ServiceB))
.singleton(|r| {
let _b = r.get_any(std::any::type_name::<ServiceB>());
Arc::new(ServiceA)
})
.build();
assert!(result.is_ok());
}
#[test]
fn scoped_cached_at_root_scope() {
let provider = ServiceCollection::new()
.scoped(|_| Arc::new(ServiceA))
.build()
.unwrap();
let a1: Arc<ServiceA> = provider.get().unwrap();
let a2: Arc<ServiceA> = provider.get().unwrap();
assert!(Arc::ptr_eq(&a1, &a2), "root scope 内 Scoped 应复用同一实例");
}
#[test]
fn scoped_isolated_between_root_and_child_scope() {
let provider = ServiceCollection::new()
.scoped(|_| Arc::new(ServiceA))
.build()
.unwrap();
let root_a: Arc<ServiceA> = provider.get().unwrap();
let scope = provider.scope();
let child_a: Arc<ServiceA> = scope.get().unwrap();
assert!(
!Arc::ptr_eq(&root_a, &child_a),
"根与子 scope 的 Scoped 应相互独立"
);
}
#[test]
fn root_scope_scoped_factory_executed_once() {
static SCOPED_CALLS: AtomicU64 = AtomicU64::new(0);
struct ScopedService(u64);
let provider = ServiceCollection::new()
.scoped(|_| {
let n = SCOPED_CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(ScopedService(n))
})
.build()
.unwrap();
let a1: Arc<ScopedService> = provider.get().unwrap();
assert_eq!(SCOPED_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(a1.0, 0);
let a2: Arc<ScopedService> = provider.get().unwrap();
let a3: Arc<ScopedService> = provider.get().unwrap();
assert_eq!(
SCOPED_CALLS.load(Ordering::SeqCst),
1,
"root scope 缓存应阻止工厂再次执行"
);
assert_eq!(a2.0, 0);
assert_eq!(a3.0, 0);
assert!(Arc::ptr_eq(&a1, &a2));
assert!(Arc::ptr_eq(&a2, &a3));
}
#[test]
fn root_scope_transient_factory_executed_each_time() {
static TRANSIENT_CALLS: AtomicU64 = AtomicU64::new(0);
struct TransientService(u64);
let provider = ServiceCollection::new()
.transient(|_| {
let n = TRANSIENT_CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(TransientService(n))
})
.build()
.unwrap();
let t1: Arc<TransientService> = provider.get().unwrap();
let t2: Arc<TransientService> = provider.get().unwrap();
let t3: Arc<TransientService> = provider.get().unwrap();
assert_eq!(TRANSIENT_CALLS.load(Ordering::SeqCst), 3);
assert_eq!(t1.0, 0);
assert_eq!(t2.0, 1);
assert_eq!(t3.0, 2);
assert!(!Arc::ptr_eq(&t1, &t2));
}
#[test]
fn scoped_factory_not_triggered_at_build() {
static BUILD_CALLS: AtomicU64 = AtomicU64::new(0);
struct S;
let provider = ServiceCollection::new()
.scoped(|_| {
BUILD_CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(S)
})
.build()
.unwrap();
assert_eq!(
BUILD_CALLS.load(Ordering::SeqCst),
0,
"build 阶段不应触发 Scoped 工厂"
);
let _s1: Arc<S> = provider.get().unwrap();
assert_eq!(BUILD_CALLS.load(Ordering::SeqCst), 1);
let _s2: Arc<S> = provider.get().unwrap();
assert_eq!(BUILD_CALLS.load(Ordering::SeqCst), 1);
}
#[test]
fn singleton_depends_on_scoped_rejected() {
let result = ServiceCollection::new()
.scoped(|_| Arc::new(ServiceA))
.singleton(|r| {
let _a = r.get_any(std::any::type_name::<ServiceA>());
Arc::new(ServiceB)
})
.build();
assert!(result.is_err());
match result {
Err(RdiError::SingletonDependsOnScoped { singleton, scoped }) => {
assert!(singleton.contains("ServiceB"));
assert!(scoped.contains("ServiceA"));
}
_ => panic!("Expected SingletonDependsOnScoped error"),
}
}
#[test]
fn singleton_indirect_depends_on_scoped_rejected() {
let result = ServiceCollection::new()
.scoped(|_| Arc::new(ServiceA))
.transient(|r| {
let _a = r.get_any(std::any::type_name::<ServiceA>());
Arc::new(ServiceC)
})
.singleton(|r| {
let _c = r.get_any(std::any::type_name::<ServiceC>());
Arc::new(ServiceB)
})
.build();
assert!(result.is_err());
match result {
Err(RdiError::SingletonDependsOnScoped { .. }) => {}
_ => panic!("Expected SingletonDependsOnScoped error"),
}
}
#[test]
fn scoped_correct_usage() {
let provider = ServiceCollection::new()
.scoped(|_| Arc::new(ServiceA))
.build()
.unwrap();
let scope = provider.scope();
let a1: Arc<ServiceA> = scope.get().unwrap();
let a2: Arc<ServiceA> = scope.get().unwrap();
assert!(Arc::ptr_eq(&a1, &a2));
let scope2 = provider.scope();
let a3: Arc<ServiceA> = scope2.get().unwrap();
assert!(!Arc::ptr_eq(&a1, &a3));
}
#[test]
fn transient_creates_new_instances() {
let p = ServiceCollection::new()
.transient(|_| Arc::new(ServiceA))
.build()
.unwrap();
let a1: Arc<ServiceA> = p.get().unwrap();
let a2: Arc<ServiceA> = p.get().unwrap();
assert!(!Arc::ptr_eq(&a1, &a2));
}
#[test]
fn singleton_shared_globally() {
let p = ServiceCollection::new()
.singleton(|_| Arc::new(ServiceA))
.build()
.unwrap();
let a1: Arc<ServiceA> = p.get().unwrap();
let a2: Arc<ServiceA> = p.get().unwrap();
assert!(Arc::ptr_eq(&a1, &a2));
}
#[test]
fn diamond_dependency_valid() {
struct D;
let p = ServiceCollection::new()
.singleton(|_| Arc::new(D))
.singleton(|r| {
let _d = r.get_any(std::any::type_name::<D>());
Arc::new(ServiceB)
})
.singleton(|r| {
let _d = r.get_any(std::any::type_name::<D>());
Arc::new(ServiceC)
})
.singleton(|r| {
let _b = r.get_any(std::any::type_name::<ServiceB>());
let _c = r.get_any(std::any::type_name::<ServiceC>());
Arc::new(ServiceA)
})
.build();
assert!(p.is_ok());
}
#[test]
fn circular_dependency_error_message() {
let result = ServiceCollection::new()
.singleton(|r| {
let _b = r.get_any(std::any::type_name::<ServiceB>());
Arc::new(ServiceA)
})
.singleton(|r| {
let _a = r.get_any(std::any::type_name::<ServiceA>());
Arc::new(ServiceB)
})
.build();
match result {
Err(RdiError::CircularDependency(msg)) => {
assert!(msg.contains("ServiceA"));
assert!(msg.contains("ServiceB"));
}
_ => panic!("Expected CircularDependency error"),
}
}
#[test]
fn root_scope_scoped_factory_executed_once_under_concurrency() {
static SCOPED_CALLS: AtomicU64 = AtomicU64::new(0);
struct ScopedService(u64);
let provider = ServiceCollection::new()
.scoped(|_| {
let n = SCOPED_CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(ScopedService(n))
})
.build()
.unwrap();
let mut handles = Vec::new();
for _ in 0..8 {
let p = provider.clone();
handles.push(std::thread::spawn(move || {
let s: Arc<ScopedService> = p.get().unwrap();
s
}));
}
let results: Vec<Arc<ScopedService>> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert_eq!(
SCOPED_CALLS.load(Ordering::SeqCst),
1,
"8 线程并发解析应只触发一次工厂(LazyCache 消除 TOCTOU)"
);
assert_eq!(results[0].0, 0, "唯一实例应携带序号 0");
for r in &results {
assert!(Arc::ptr_eq(&results[0], r), "所有线程应拿到同一 Arc 实例");
}
}
#[test]
fn validation_does_not_leak_transient_to_runtime_cache() {
static TRANSIENT_CALLS: AtomicU64 = AtomicU64::new(0);
struct TransientService(u64);
struct SingletonService;
let provider = ServiceCollection::new()
.transient(|_| {
let n = TRANSIENT_CALLS.fetch_add(1, Ordering::SeqCst);
Arc::new(TransientService(n))
})
.singleton(|r| {
let _t = r.get_any(std::any::type_name::<TransientService>());
Arc::new(SingletonService)
})
.build()
.unwrap();
assert_eq!(
TRANSIENT_CALLS.load(Ordering::SeqCst),
1,
"build 阶段 validation 应执行一次 Transient 工厂"
);
let t1: Arc<TransientService> = provider.get().unwrap();
assert_eq!(
TRANSIENT_CALLS.load(Ordering::SeqCst),
2,
"运行时首次解析应执行新工厂,而非复用 validation 缓存"
);
assert_eq!(t1.0, 1, "运行时首次解析应得到序号 1(validation 是 0)");
let t2: Arc<TransientService> = provider.get().unwrap();
assert_eq!(TRANSIENT_CALLS.load(Ordering::SeqCst), 3);
assert_eq!(t2.0, 2);
assert!(
!Arc::ptr_eq(&t1, &t2),
"Transient 每次解析应返回新实例,无缓存泄漏"
);
}