#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::fmt::Debug;
use pacta_contract::{Pact, Registry, Retainer, Timestamp};
use uuid::Uuid;
pub const LEASE_MILLIS: u64 = 1000;
const DOCKET: &str = "conformance";
fn at(millis: u64) -> Timestamp {
Timestamp::from_millis(millis)
}
fn a_pact_on(docket: &str) -> Pact {
Pact::new(
Uuid::new_v4(),
docket.to_string(),
"conformance".to_string(),
Vec::new(),
)
}
fn a_pact() -> Pact {
a_pact_on(DOCKET)
}
pub fn run<R, F>(make: F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
no_available_pact_returns_none(&make);
unrequested_docket_is_not_claimed(&make);
claim_returns_claim_with_lease(&make);
held_pact_not_reclaimable_before_expiry(&make);
expired_lease_lapses_and_reclaims_with_rotated_retainer(&make);
stale_retainer_settle_rejected_after_reclaim(&make);
late_fulfill_before_reclaim_succeeds(&make);
fulfill_settles_and_pact_not_claimable(&make);
breach_settles_terminally(&make);
released_pact_withheld_until_reclaimable(&make);
released_pact_reclaimable_at_its_instant(&make);
immediate_reclaim_reclaims_like_lapse(&make);
release_rotates_authority_from_prior_holder(&make);
heartbeat_extends_lease_preventing_lapse(&make);
heartbeat_on_lapsed_lease_rejected(&make);
heartbeat_unknown_retainer_rejected(&make);
}
#[cfg(feature = "async")]
mod async_runner {
use core::future::Future;
use pacta_contract::AsyncRegistry;
use pacta_contract::{Claim, Pact, Registry, Retainer, Timestamp, Transition};
fn block_on<F: Future>(future: F) -> F::Output {
use core::task::{Context, Poll};
let mut future = core::pin::pin!(future);
let mut cx = Context::from_waker(core::task::Waker::noop());
loop {
match future.as_mut().poll(&mut cx) {
Poll::Ready(output) => return output,
Poll::Pending => core::hint::spin_loop(),
}
}
}
struct BlockOn<R>(R);
impl<R: AsyncRegistry> Registry for BlockOn<R> {
type Error = R::Error;
fn claim(&self, dockets: &[&str], now: Timestamp) -> Result<Option<Claim>, Self::Error> {
block_on(self.0.claim(dockets, now))
}
fn lease_millis(&self) -> u64 {
self.0.lease_millis()
}
fn apply(
&self,
retainer: &Retainer,
transition: &Transition<'_>,
) -> Result<(), Self::Error> {
block_on(self.0.apply(retainer, transition))
}
}
pub fn run_async<R, F>(make: F)
where
R: AsyncRegistry,
R::Error: core::fmt::Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
crate::run(move |pacts, lease_millis| BlockOn(make(pacts, lease_millis)));
}
pub fn run_async_contention<R, F>(make: F)
where
R: AsyncRegistry + 'static,
R::Error: core::fmt::Debug + Send,
F: Fn(Vec<Pact>, u64) -> R,
{
use std::sync::Arc;
for _ in 0..2000 {
let reg = Arc::new(make(vec![crate::a_pact()], crate::LEASE_MILLIS));
let retainer = block_on(reg.claim(&[crate::DOCKET], crate::at(0)))
.expect("claim should not error")
.expect("a pact should be claimable")
.retainer;
let a = {
let reg = Arc::clone(®);
let retainer = retainer.clone();
std::thread::spawn(move || block_on(reg.fulfill(&retainer)))
};
let b = {
let reg = Arc::clone(®);
let retainer = retainer.clone();
std::thread::spawn(move || block_on(reg.fulfill(&retainer)))
};
let (ra, rb) = (a.join().unwrap(), b.join().unwrap());
let winners = [ra.is_ok(), rb.is_ok()]
.into_iter()
.filter(|&ok| ok)
.count();
assert_eq!(
winners, 1,
"settlement must apply exactly once: a={ra:?} b={rb:?}"
);
assert!(
block_on(reg.claim(&[crate::DOCKET], crate::at(0)))
.expect("claim should not error")
.is_none(),
"a settled pact must not be claimable again"
);
}
}
}
#[cfg(feature = "async")]
pub use async_runner::{run_async, run_async_contention};
fn no_available_pact_returns_none<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(Vec::new(), LEASE_MILLIS);
assert!(
registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.is_none(),
"an empty registry must yield no claim"
);
}
fn unrequested_docket_is_not_claimed<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact_on("other")], LEASE_MILLIS);
assert!(
registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.is_none(),
"a pact on an unrequested docket must not be claimed"
);
assert!(
registry
.claim(&["other"], at(0))
.expect("claim should not error")
.is_some(),
"the same pact must be claimable from its own docket"
);
}
fn claim_returns_claim_with_lease<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(100))
.expect("claim should not error")
.expect("a pact should be claimable");
assert_eq!(
claim.lease_expiry,
at(100 + LEASE_MILLIS),
"lease expiry must be now plus the lease duration"
);
}
fn held_pact_not_reclaimable_before_expiry<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let _first = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
assert!(
registry
.claim(&[DOCKET], at(500))
.expect("claim should not error")
.is_none(),
"a held pact must not be reclaimable before its lease expires"
);
}
fn expired_lease_lapses_and_reclaims_with_rotated_retainer<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let first = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
let second = registry
.claim(&[DOCKET], at(1500))
.expect("claim should not error")
.expect("an expired pact should be reclaimable through the claim path");
assert_ne!(
first.retainer.id(),
second.retainer.id(),
"reclaiming a lapsed pact must rotate the retainer"
);
assert_eq!(
second.lease_expiry,
at(1500 + LEASE_MILLIS),
"the reclaim must set a fresh lease"
);
}
fn stale_retainer_settle_rejected_after_reclaim<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let first = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
let _second = registry
.claim(&[DOCKET], at(1500))
.expect("claim should not error")
.expect("an expired pact should be reclaimable");
assert!(
registry.fulfill(&first.retainer).is_err(),
"the prior holder must not settle after a reclaim (at-least-once safety)"
);
}
fn late_fulfill_before_reclaim_succeeds<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
assert!(
registry.fulfill(&claim.retainer).is_ok(),
"a late fulfill before any reclaim must settle"
);
assert!(
registry
.claim(&[DOCKET], at(9999))
.expect("claim should not error")
.is_none(),
"a settled pact must not be claimable"
);
}
fn fulfill_settles_and_pact_not_claimable<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.fulfill(&claim.retainer)
.expect("fulfill should settle");
assert!(
registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.is_none(),
"a fulfilled pact must not be claimable"
);
}
fn breach_settles_terminally<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.breach(&claim.retainer)
.expect("breach should settle");
assert!(
registry
.claim(&[DOCKET], at(5000))
.expect("claim should not error")
.is_none(),
"a breached pact must not be claimable, even after its lease would have expired"
);
}
fn released_pact_withheld_until_reclaimable<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.release(&claim.retainer, at(5000))
.expect("release should succeed for the current holder");
assert!(
registry
.claim(&[DOCKET], at(3000))
.expect("claim should not error")
.is_none(),
"a released pact must not be claimable before its reclaimable instant"
);
}
fn released_pact_reclaimable_at_its_instant<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let first = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.release(&first.retainer, at(5000))
.expect("release should succeed");
let second = registry
.claim(&[DOCKET], at(5000))
.expect("claim should not error")
.expect("a released pact must be claimable at its reclaimable instant");
assert_ne!(
first.retainer.id(),
second.retainer.id(),
"reclaiming a released pact must rotate the retainer"
);
}
fn immediate_reclaim_reclaims_like_lapse<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.release(&claim.retainer, at(0))
.expect("release with an immediate reclaim should succeed");
assert!(
registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.is_some(),
"an immediate reclaim must make the pact claimable at once, as a voluntary lapse"
);
}
fn release_rotates_authority_from_prior_holder<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.release(&claim.retainer, at(0))
.expect("release should succeed");
assert!(
registry.fulfill(&claim.retainer).is_err(),
"the prior holder must not settle after releasing (release rotates authority)"
);
}
fn heartbeat_extends_lease_preventing_lapse<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
registry
.heartbeat(&claim.retainer, at(800))
.expect("an in-window heartbeat should extend the lease");
assert!(
registry
.claim(&[DOCKET], at(1500))
.expect("claim should not error")
.is_none(),
"a heartbeat within the window must prevent a lapse"
);
}
fn heartbeat_on_lapsed_lease_rejected<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
assert!(
registry.heartbeat(&claim.retainer, at(1200)).is_err(),
"a heartbeat after the lease expired must be rejected, forcing a re-claim"
);
}
fn heartbeat_unknown_retainer_rejected<R, F>(make: &F)
where
R: Registry,
R::Error: Debug,
F: Fn(Vec<Pact>, u64) -> R,
{
let registry = make(vec![a_pact()], LEASE_MILLIS);
let _claim = registry
.claim(&[DOCKET], at(0))
.expect("claim should not error")
.expect("a pact should be claimable");
let unknown = Retainer::new(Uuid::new_v4());
assert!(
registry.heartbeat(&unknown, at(100)).is_err(),
"a heartbeat with an unissued retainer must be rejected"
);
}