lenso-native-adapter 0.3.21

Native Rust Execution Adapter for Lenso.
Documentation
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());
}