use super::*;
use futures::{FutureExt, executor::block_on, future, task::noop_waker};
use std::task::Context;
#[derive(Debug)]
struct Authority {
revocations: Rc<Cell<usize>>,
gate: Rc<Cell<bool>>,
}
impl NativeGenerationResource for Authority {
fn revoke(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {
self.revocations.set(self.revocations.get() + 1);
let gate = self.gate.clone();
Box::pin(poll_fn(move |_| {
if gate.get() {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}))
}
}
fn resource(revocations: &Rc<Cell<usize>>, gate: &Rc<Cell<bool>>) -> Authority {
Authority {
revocations: revocations.clone(),
gate: gate.clone(),
}
}
fn poll_once<F: std::future::Future>(future: std::pin::Pin<&mut F>) -> Poll<F::Output> {
future.poll(&mut Context::from_waker(&noop_waker()))
}
#[test]
fn three_factories_share_typed_authority_only_in_one_generation() {
let old = NativeGenerationScope::new();
let new = NativeGenerationScope::new();
let revocations = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let created = Cell::new(0);
let context = old.context();
let make = || {
created.set(created.get() + 1);
Ok(resource(&revocations, &gate))
};
let a = context
.resolve("test.authority", "effects", &Value::Null, make)
.unwrap();
let b = context
.resolve("test.authority", "effects", &Value::Null, make)
.unwrap();
let c = context
.resolve("test.authority", "effects", &Value::Null, make)
.unwrap();
let d = new
.context()
.resolve("test.authority", "effects", &Value::Null, make)
.unwrap();
assert!(Rc::ptr_eq(&a.value, &b.value) && Rc::ptr_eq(&b.value, &c.value));
assert!(!Rc::ptr_eq(&a.value, &d.value));
assert_eq!(created.get(), 2);
block_on(old.retire()).unwrap();
assert!(a.acquire().is_err());
assert!(d.acquire().is_ok());
block_on(new.retire()).unwrap();
assert_eq!(revocations.get(), 2);
}
#[test]
fn retirement_rejects_new_grants_and_waits_for_physical_lease_drain() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let handle = scope
.context()
.resolve("owner", "effects", &Value::Null, || {
Ok(resource(&count, &gate))
})
.unwrap();
let lease = handle.acquire().unwrap();
let mut retirement = Box::pin(scope.retire());
assert!(poll_once(retirement.as_mut()).is_pending());
assert!(handle.acquire().is_err());
assert!(
scope
.context()
.resolve("owner", "new", &Value::Null, || Ok(resource(&count, &gate)))
.is_err()
);
assert_eq!(count.get(), 0);
assert_eq!(lease.revocations.get(), 0);
drop(lease);
assert!(poll_once(retirement.as_mut()).is_ready());
drop(retirement);
block_on(scope.retire()).unwrap();
assert_eq!(count.get(), 1);
}
#[test]
fn cancelled_retirement_retains_and_resumes_the_same_revoke_future() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(false));
scope
.context()
.resolve("owner", "effects", &Value::Null, || {
Ok(resource(&count, &gate))
})
.unwrap();
let mut first = Box::pin(scope.retire());
assert!(poll_once(first.as_mut()).is_pending());
assert_eq!(count.get(), 1);
drop(first);
let mut second = Box::pin(scope.retire());
assert!(poll_once(second.as_mut()).is_pending());
assert_eq!(count.get(), 1);
gate.set(true);
assert!(poll_once(second.as_mut()).is_ready());
assert_eq!(count.get(), 1);
}
#[derive(Debug)]
struct Other;
impl NativeGenerationResource for Other {
fn revoke(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {
future::ready(Ok(())).boxed_local()
}
}
#[test]
fn conflicting_type_or_owner_configuration_fails_without_another_constructor() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let context = scope.context();
context
.resolve(
"owner",
"effects",
&serde_json::json!({"public":"fixture"}),
|| Ok(resource(&count, &gate)),
)
.unwrap();
assert!(
context
.resolve(
"owner",
"effects",
&serde_json::json!({"public":"other"}),
|| -> Result<Authority, _> {
panic!("conflicting configuration must not construct")
}
)
.is_err()
);
assert!(
context
.resolve(
"owner",
"effects",
&serde_json::json!({"public":"fixture"}),
|| Ok(Other)
)
.is_err()
);
block_on(scope.retire()).unwrap();
assert_eq!(count.get(), 1);
}
#[test]
fn failed_preparation_revokes_previously_constructed_authority() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let context = scope.context();
context
.resolve("owner", "good", &Value::Null, || {
Ok(resource(&count, &gate))
})
.unwrap();
assert!(
context
.resolve("owner", "bad", &Value::Null, || -> Result<Other, _> {
Err(invalid("owner preparation failed"))
})
.is_err()
);
block_on(scope.retire()).unwrap();
assert_eq!(count.get(), 1);
}
#[test]
fn abandoned_scope_closes_admission_and_retains_uncertain_authority() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let handle = scope
.context()
.resolve("owner", "effects", &Value::Null, || {
Ok(resource(&count, &gate))
})
.unwrap();
let weak = Rc::downgrade(&handle.value);
drop(scope);
assert!(handle.acquire().is_err());
drop(handle);
assert!(weak.upgrade().is_some());
assert_eq!(count.get(), 0);
}
#[test]
fn reentrant_constructor_cannot_overwrite_or_double_revoke_an_authority() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
let context = scope.context();
context
.resolve("owner", "effects", &Value::Null, || {
assert!(
context
.resolve("owner", "effects", &Value::Null, || Ok(resource(
&count, &gate
)))
.is_err()
);
Ok(resource(&count, &gate))
})
.unwrap();
block_on(scope.retire()).unwrap();
assert_eq!(count.get(), 1);
}
#[test]
fn closed_during_constructor_is_retained_for_rollback_without_a_grant() {
let scope = NativeGenerationScope::new();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
assert!(
scope
.context()
.resolve("owner", "effects", &Value::Null, || {
scope.close();
assert!(Box::pin(scope.retire()).now_or_never().is_none());
Ok(resource(&count, &gate))
})
.is_err()
);
block_on(scope.retire()).unwrap();
assert_eq!(count.get(), 1);
}
#[test]
fn dropped_scope_during_first_constructor_keeps_uncertain_resource_owned() {
let scope = NativeGenerationScope::new();
let context = scope.context();
let count = Rc::new(Cell::new(0));
let gate = Rc::new(Cell::new(true));
assert!(
context
.resolve("owner", "effects", &Value::Null, || {
drop(scope);
Ok(resource(&count, &gate))
})
.is_err()
);
let value = context
.state
.entries
.borrow()
.values()
.next()
.unwrap()
.value
.clone();
let weak = Rc::downgrade(&value);
drop(value);
drop(context);
assert!(weak.upgrade().is_some());
assert_eq!(count.get(), 0);
}
#[derive(Debug)]
struct RecordedAuthority {
events: Rc<RefCell<Vec<&'static str>>>,
name: &'static str,
fail: bool,
}
impl NativeGenerationResource for RecordedAuthority {
fn revoke(&self) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>> {
self.events.borrow_mut().push(self.name);
future::ready(if self.fail {
Err(invalid("revocation failed"))
} else {
Ok(())
})
.boxed_local()
}
}
#[test]
fn revocation_failure_is_sticky_and_remaining_resources_release_in_preparation_reverse_order() {
let scope = NativeGenerationScope::new();
let events = Rc::new(RefCell::new(Vec::new()));
let context = scope.context();
let first = context
.resolve("owner", "z", &Value::Null, || {
Ok(RecordedAuthority {
events: events.clone(),
name: "first",
fail: false,
})
})
.unwrap();
context
.resolve("owner", "a", &Value::Null, || {
Ok(RecordedAuthority {
events: events.clone(),
name: "second",
fail: true,
})
})
.unwrap();
let weak = Rc::downgrade(&first.value);
assert!(block_on(scope.retire()).is_err());
assert_eq!(*events.borrow(), ["second", "first"]);
assert!(block_on(scope.retire()).is_err());
assert_eq!(events.borrow().len(), 2);
assert!(first.acquire().is_err());
drop(first);
drop(context);
drop(scope);
assert!(weak.upgrade().is_some());
}