use crate::record::ObligationKind;
use std::fmt;
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Resolution {
Commit,
Abort,
}
impl fmt::Display for Resolution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Commit => f.write_str("commit"),
Self::Abort => f.write_str("abort"),
}
}
}
#[must_use = "obligations must be resolved (commit or abort); dropping leaks the obligation"]
pub struct GradedObligation {
kind: ObligationKind,
description: String,
resolved: bool,
}
impl GradedObligation {
pub fn reserve(kind: ObligationKind, description: impl Into<String>) -> Self {
Self {
kind,
description: description.into(),
resolved: false,
}
}
#[must_use]
pub fn resolve(mut self, resolution: Resolution) -> ResolvedProof {
self.resolved = true;
ResolvedProof {
kind: self.kind,
resolution,
}
}
#[must_use]
pub fn kind(&self) -> ObligationKind {
self.kind
}
#[must_use]
pub fn description(&self) -> &str {
&self.description
}
#[must_use]
pub fn is_resolved(&self) -> bool {
self.resolved
}
#[must_use]
pub fn into_raw(mut self) -> RawObligation {
self.resolved = true; RawObligation {
kind: self.kind,
description: std::mem::take(&mut self.description),
}
}
}
impl Drop for GradedObligation {
fn drop(&mut self) {
if !self.resolved {
if std::thread::panicking() {
return;
}
panic!(
"OBLIGATION LEAKED: {} obligation '{}' was dropped without being resolved. \
Call .resolve(Resolution::Commit) or .resolve(Resolution::Abort) before scope exit.",
self.kind, self.description,
);
}
}
}
impl fmt::Debug for GradedObligation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GradedObligation")
.field("kind", &self.kind)
.field("description", &self.description)
.field("resolved", &self.resolved)
.finish()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ResolvedProof {
kind: ObligationKind,
resolution: Resolution,
}
impl ResolvedProof {
#[must_use]
pub fn kind(&self) -> ObligationKind {
self.kind
}
#[must_use]
pub fn resolution(&self) -> Resolution {
self.resolution
}
}
impl fmt::Display for ResolvedProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "resolved({}, {})", self.kind, self.resolution)
}
}
#[derive(Debug, Clone)]
pub struct RawObligation {
pub kind: ObligationKind,
pub description: String,
}
pub struct GradedScope {
label: String,
reserved: u32,
resolved: u32,
closed: bool,
}
impl GradedScope {
#[must_use]
pub fn open(label: impl Into<String>) -> Self {
Self {
label: label.into(),
reserved: 0,
resolved: 0,
closed: false,
}
}
pub fn on_reserve(&mut self) {
self.reserved = self
.reserved
.checked_add(1)
.expect("on_reserve overflowed outstanding obligation count");
}
pub fn on_resolve(&mut self) {
assert!(
self.resolved < self.reserved,
"on_resolve called more times than on_reserve ({} >= {})",
self.resolved,
self.reserved,
);
self.resolved = self
.resolved
.checked_add(1)
.expect("on_resolve overflowed resolved obligation count");
}
#[must_use]
pub fn outstanding(&self) -> u32 {
self.reserved.saturating_sub(self.resolved)
}
#[must_use = "close() must be checked; Err indicates leaked obligations"]
pub fn close(mut self) -> Result<ScopeProof, ScopeLeakError> {
let outstanding = self.outstanding();
if outstanding == 0 {
self.closed = true;
Ok(ScopeProof {
label: self.label.clone(),
total_reserved: self.reserved,
total_resolved: self.resolved,
})
} else {
let err = ScopeLeakError {
label: self.label.clone(),
outstanding,
reserved: self.reserved,
resolved: self.resolved,
};
self.closed = true;
Err(err)
}
}
#[must_use]
pub fn label(&self) -> &str {
&self.label
}
}
impl Drop for GradedScope {
fn drop(&mut self) {
if !self.closed && self.outstanding() > 0 {
if std::thread::panicking() {
return;
}
panic!(
"SCOPE LEAKED: scope '{}' dropped with {} outstanding obligation(s) \
({} reserved, {} resolved). Call .close() before scope exit.",
self.label,
self.outstanding(),
self.reserved,
self.resolved,
);
}
}
}
impl fmt::Debug for GradedScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GradedScope")
.field("label", &self.label)
.field("reserved", &self.reserved)
.field("resolved", &self.resolved)
.field("outstanding", &self.outstanding())
.field("closed", &self.closed)
.finish()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct ScopeProof {
label: String,
total_reserved: u32,
total_resolved: u32,
}
impl ScopeProof {
#[must_use]
pub fn label(&self) -> &str {
&self.label
}
#[must_use]
pub fn total_reserved(&self) -> u32 {
self.total_reserved
}
#[must_use]
pub fn total_resolved(&self) -> u32 {
self.total_resolved
}
}
impl fmt::Display for ScopeProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"scope '{}' clean: {}/{} resolved",
self.label, self.total_resolved, self.total_reserved
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScopeLeakError {
pub label: String,
pub outstanding: u32,
pub reserved: u32,
pub resolved: u32,
}
impl fmt::Display for ScopeLeakError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"scope '{}' leaked: {} outstanding ({} reserved, {} resolved)",
self.label, self.outstanding, self.reserved, self.resolved,
)
}
}
impl std::error::Error for ScopeLeakError {}
pub mod toy_api {
use super::{GradedObligation, ObligationKind, Resolution, ResolvedProof};
fn assert_send_permit(permit: &GradedObligation, operation: &str) {
assert_eq!(
permit.kind(),
ObligationKind::SendPermit,
"{operation} requires a SendPermit obligation (got {})",
permit.kind()
);
}
pub struct ToyChannel {
capacity: usize,
messages: Vec<String>,
reserved_permits: usize,
}
impl ToyChannel {
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
capacity,
messages: Vec::new(),
reserved_permits: 0,
}
}
#[cfg(test)]
pub(super) fn from_state(
capacity: usize,
messages: Vec<String>,
reserved_permits: usize,
) -> Self {
Self {
capacity,
messages,
reserved_permits,
}
}
#[must_use]
pub fn reserve_send(&mut self) -> Option<GradedObligation> {
let occupied = self
.messages
.len()
.checked_add(self.reserved_permits)
.expect("toy channel occupancy overflowed");
if occupied < self.capacity {
self.reserved_permits = self
.reserved_permits
.checked_add(1)
.expect("toy channel reserved permit count overflowed");
Some(GradedObligation::reserve(
ObligationKind::SendPermit,
"toy channel send permit",
))
} else {
None
}
}
pub fn commit_send(&mut self, permit: GradedObligation, message: String) -> ResolvedProof {
assert_send_permit(&permit, "commit_send");
self.reserved_permits = self
.reserved_permits
.checked_sub(1)
.expect("commit_send requires an outstanding reservation");
self.messages.push(message);
permit.resolve(Resolution::Commit)
}
#[must_use]
pub fn abort_send(&mut self, permit: GradedObligation) -> ResolvedProof {
assert_send_permit(&permit, "abort_send");
self.reserved_permits = self
.reserved_permits
.checked_sub(1)
.expect("abort_send requires an outstanding reservation");
permit.resolve(Resolution::Abort)
}
#[must_use]
pub fn len(&self) -> usize {
self.messages.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
}
}
mod sealed {
pub trait Sealed {}
}
pub trait TokenKind: sealed::Sealed {
fn obligation_kind() -> ObligationKind;
}
#[derive(Debug)]
pub enum SendPermit {}
impl sealed::Sealed for SendPermit {}
impl TokenKind for SendPermit {
fn obligation_kind() -> ObligationKind {
ObligationKind::SendPermit
}
}
#[derive(Debug)]
pub enum AckKind {}
impl sealed::Sealed for AckKind {}
impl TokenKind for AckKind {
fn obligation_kind() -> ObligationKind {
ObligationKind::Ack
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum LeaseKind {}
impl sealed::Sealed for LeaseKind {}
impl TokenKind for LeaseKind {
fn obligation_kind() -> ObligationKind {
ObligationKind::Lease
}
}
#[derive(Debug)]
pub enum IoOpKind {}
impl sealed::Sealed for IoOpKind {}
impl TokenKind for IoOpKind {
fn obligation_kind() -> ObligationKind {
ObligationKind::IoOp
}
}
#[derive(Debug)]
pub enum SemaphorePermitKind {}
impl sealed::Sealed for SemaphorePermitKind {}
impl TokenKind for SemaphorePermitKind {
fn obligation_kind() -> ObligationKind {
ObligationKind::SemaphorePermit
}
}
#[must_use = "obligation tokens must be consumed via commit() or abort()"]
pub struct ObligationToken<K: TokenKind> {
description: String,
armed: bool,
_kind: PhantomData<K>,
}
impl<K: TokenKind> ObligationToken<K> {
#[allow(clippy::double_must_use)]
#[must_use]
pub fn reserve(description: impl Into<String>) -> Self {
Self {
description: description.into(),
armed: true,
_kind: PhantomData,
}
}
#[must_use]
pub fn commit(mut self) -> CommittedProof<K> {
self.armed = false;
CommittedProof { _kind: PhantomData }
}
#[must_use]
pub fn abort(mut self) -> AbortedProof<K> {
self.armed = false;
AbortedProof { _kind: PhantomData }
}
#[must_use]
pub fn into_raw(mut self) -> RawObligation {
self.armed = false;
let description = std::mem::take(&mut self.description);
RawObligation {
kind: K::obligation_kind(),
description,
}
}
#[must_use]
pub fn description(&self) -> &str {
&self.description
}
}
impl<K: TokenKind> Drop for ObligationToken<K> {
fn drop(&mut self) {
if self.armed {
if std::thread::panicking() {
return;
}
panic!(
"OBLIGATION TOKEN LEAKED: {} token '{}' was dropped without being consumed. \
Call .commit() or .abort() before scope exit.",
K::obligation_kind(),
self.description,
);
}
}
}
impl<K: TokenKind> fmt::Debug for ObligationToken<K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ObligationToken")
.field("kind", &K::obligation_kind())
.field("description", &self.description)
.field("armed", &self.armed)
.finish()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CommittedProof<K: TokenKind> {
_kind: PhantomData<K>,
}
impl<K: TokenKind> CommittedProof<K> {
#[must_use]
pub fn into_resolved_proof(self) -> ResolvedProof {
ResolvedProof {
kind: K::obligation_kind(),
resolution: Resolution::Commit,
}
}
#[must_use]
pub fn kind(&self) -> ObligationKind {
K::obligation_kind()
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct AbortedProof<K: TokenKind> {
_kind: PhantomData<K>,
}
impl<K: TokenKind> AbortedProof<K> {
#[must_use]
pub fn into_resolved_proof(self) -> ResolvedProof {
ResolvedProof {
kind: K::obligation_kind(),
resolution: Resolution::Abort,
}
}
#[must_use]
pub fn kind(&self) -> ObligationKind {
K::obligation_kind()
}
}
pub type SendPermitToken = ObligationToken<SendPermit>;
pub type AckToken = ObligationToken<AckKind>;
pub type LeaseToken = ObligationToken<LeaseKind>;
pub type IoOpToken = ObligationToken<IoOpKind>;
impl GradedScope {
#[allow(clippy::double_must_use)]
#[must_use]
pub fn reserve_token<K: TokenKind>(
&mut self,
description: impl Into<String>,
) -> ObligationToken<K> {
self.on_reserve();
ObligationToken::reserve(description)
}
#[must_use]
pub fn resolve_commit<K: TokenKind>(&mut self, token: ObligationToken<K>) -> CommittedProof<K> {
self.on_resolve();
token.commit()
}
#[must_use]
pub fn resolve_abort<K: TokenKind>(&mut self, token: ObligationToken<K>) -> AbortedProof<K> {
self.on_resolve();
token.abort()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::record::ObligationKind;
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn obligation_commit_clean() {
init_test("obligation_commit_clean");
let ob = GradedObligation::reserve(ObligationKind::SendPermit, "test");
let kind = ob.kind();
crate::assert_with_log!(
kind == ObligationKind::SendPermit,
"kind",
ObligationKind::SendPermit,
kind
);
let is_resolved = ob.is_resolved();
crate::assert_with_log!(!is_resolved, "not yet resolved", false, is_resolved);
let proof = ob.resolve(Resolution::Commit);
let r = proof.resolution();
crate::assert_with_log!(r == Resolution::Commit, "resolution", Resolution::Commit, r);
crate::test_complete!("obligation_commit_clean");
}
#[test]
fn obligation_abort_clean() {
init_test("obligation_abort_clean");
let ob = GradedObligation::reserve(ObligationKind::Ack, "ack-test");
let proof = ob.resolve(Resolution::Abort);
let r = proof.resolution();
crate::assert_with_log!(r == Resolution::Abort, "resolution", Resolution::Abort, r);
crate::test_complete!("obligation_abort_clean");
}
#[test]
fn obligation_into_raw_disarms() {
init_test("obligation_into_raw_disarms");
let ob = GradedObligation::reserve(ObligationKind::Lease, "lease-test");
let raw = ob.into_raw();
let kind = raw.kind;
crate::assert_with_log!(
kind == ObligationKind::Lease,
"raw kind",
ObligationKind::Lease,
kind
);
drop(raw);
crate::test_complete!("obligation_into_raw_disarms");
}
#[test]
#[should_panic(expected = "OBLIGATION LEAKED")]
fn obligation_drop_without_resolve_panics() {
init_test("obligation_drop_without_resolve_panics");
let _ob = GradedObligation::reserve(ObligationKind::IoOp, "leaked-io");
}
#[test]
fn scope_clean_close() {
init_test("scope_clean_close");
let mut scope = GradedScope::open("test-scope");
scope.on_reserve();
scope.on_resolve();
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 0, "outstanding", 0, outstanding);
let proof = scope.close().expect("scope should close cleanly");
let label = proof.label();
crate::assert_with_log!(label == "test-scope", "label", "test-scope", label);
let total = proof.total_reserved();
crate::assert_with_log!(total == 1, "reserved", 1, total);
crate::test_complete!("scope_clean_close");
}
#[test]
fn scope_multiple_obligations() {
init_test("scope_multiple_obligations");
let mut scope = GradedScope::open("multi");
scope.on_reserve();
scope.on_reserve();
scope.on_reserve();
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 3, "outstanding", 3, outstanding);
scope.on_resolve();
scope.on_resolve();
scope.on_resolve();
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 0, "outstanding", 0, outstanding);
let proof = scope.close().expect("clean");
let total = proof.total_reserved();
crate::assert_with_log!(total == 3, "reserved", 3, total);
crate::test_complete!("scope_multiple_obligations");
}
#[test]
fn scope_close_with_leak_returns_error() {
init_test("scope_close_with_leak_returns_error");
let mut scope = GradedScope::open("leaky-scope");
scope.on_reserve();
scope.on_reserve();
scope.on_resolve();
let err = scope.close().expect_err("should fail");
let outstanding = err.outstanding;
crate::assert_with_log!(outstanding == 1, "outstanding", 1, outstanding);
let label = &err.label;
crate::assert_with_log!(label == "leaky-scope", "label", "leaky-scope", label);
let msg = format!("{err}");
let has_leaked = msg.contains("leaked");
crate::assert_with_log!(has_leaked, "display has leaked", true, has_leaked);
crate::test_complete!("scope_close_with_leak_returns_error");
}
#[test]
#[should_panic(expected = "SCOPE LEAKED")]
fn scope_drop_with_outstanding_panics() {
init_test("scope_drop_with_outstanding_panics");
let mut scope = GradedScope::open("drop-leak");
scope.on_reserve();
}
#[test]
fn scope_drop_without_close_ok_when_empty() {
init_test("scope_drop_without_close_ok_when_empty");
let _scope = GradedScope::open("empty-scope");
}
#[test]
fn combined_obligation_and_scope() {
init_test("combined_obligation_and_scope");
let mut scope = GradedScope::open("combined");
let ob1 = GradedObligation::reserve(ObligationKind::SendPermit, "send");
scope.on_reserve();
let ob2 = GradedObligation::reserve(ObligationKind::Ack, "ack");
scope.on_reserve();
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 2, "outstanding", 2, outstanding);
let _proof1 = ob1.resolve(Resolution::Commit);
scope.on_resolve();
let _proof2 = ob2.resolve(Resolution::Abort);
scope.on_resolve();
let proof = scope.close().expect("clean close");
let total = proof.total_reserved();
crate::assert_with_log!(total == 2, "total reserved", 2, total);
crate::test_complete!("combined_obligation_and_scope");
}
#[test]
fn toy_channel_correct_usage() {
init_test("toy_channel_correct_usage");
let mut ch = toy_api::ToyChannel::new(10);
let permit = ch.reserve_send().expect("should get permit");
let proof = ch.commit_send(permit, "hello".to_string());
let resolution = proof.resolution();
crate::assert_with_log!(
resolution == Resolution::Commit,
"commit",
Resolution::Commit,
resolution
);
let len = ch.len();
crate::assert_with_log!(len == 1, "len", 1, len);
crate::test_complete!("toy_channel_correct_usage");
}
#[test]
fn toy_channel_abort_usage() {
init_test("toy_channel_abort_usage");
let mut ch = toy_api::ToyChannel::new(10);
let permit = ch.reserve_send().expect("should get permit");
let proof = ch.abort_send(permit);
let resolution = proof.resolution();
crate::assert_with_log!(
resolution == Resolution::Abort,
"abort",
Resolution::Abort,
resolution
);
let len = ch.len();
crate::assert_with_log!(len == 0, "len", 0, len);
crate::test_complete!("toy_channel_abort_usage");
}
#[test]
#[should_panic(expected = "OBLIGATION LEAKED")]
fn toy_channel_leaked_permit_panics() {
init_test("toy_channel_leaked_permit_panics");
let mut ch = toy_api::ToyChannel::new(10);
let _permit = ch.reserve_send().expect("should get permit");
}
#[test]
fn toy_channel_full_returns_none() {
init_test("toy_channel_full_returns_none");
let mut ch = toy_api::ToyChannel::new(0);
let permit = ch.reserve_send();
let is_none = permit.is_none();
crate::assert_with_log!(is_none, "full", true, is_none);
crate::test_complete!("toy_channel_full_returns_none");
}
#[test]
fn toy_channel_reservation_tracks_outstanding_capacity() {
init_test("toy_channel_reservation_tracks_outstanding_capacity");
let mut ch = toy_api::ToyChannel::new(1);
let first = ch.reserve_send().expect("first permit should succeed");
let second = ch.reserve_send();
crate::assert_with_log!(
second.is_none(),
"second permit blocked by outstanding reservation",
true,
second.is_none()
);
let _proof = ch.abort_send(first);
let retry = ch.reserve_send();
crate::assert_with_log!(
retry.is_some(),
"capacity should reopen after abort",
true,
retry.is_some()
);
crate::test_complete!("toy_channel_reservation_tracks_outstanding_capacity");
}
#[test]
fn toy_channel_commit_rejects_wrong_obligation_kind() {
init_test("toy_channel_commit_rejects_wrong_obligation_kind");
let mut ch = toy_api::ToyChannel::from_state(1, Vec::new(), 1);
let forged = GradedObligation::reserve(ObligationKind::Ack, "forged ack");
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ch.commit_send(forged, "hello".to_string())
}));
crate::assert_with_log!(
result.is_err(),
"wrong kind rejected",
true,
result.is_err()
);
crate::assert_with_log!(ch.is_empty(), "no message enqueued", true, ch.is_empty());
let retry = ch.reserve_send();
crate::assert_with_log!(
retry.is_none(),
"reservation count preserved after rejected commit",
true,
retry.is_none()
);
crate::test_complete!("toy_channel_commit_rejects_wrong_obligation_kind");
}
#[test]
fn toy_channel_abort_rejects_wrong_obligation_kind() {
init_test("toy_channel_abort_rejects_wrong_obligation_kind");
let mut ch = toy_api::ToyChannel::from_state(1, Vec::new(), 1);
let forged = GradedObligation::reserve(ObligationKind::Lease, "forged lease");
let result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ch.abort_send(forged)));
crate::assert_with_log!(
result.is_err(),
"wrong kind rejected",
true,
result.is_err()
);
crate::assert_with_log!(ch.is_empty(), "abort does not enqueue", true, ch.is_empty());
let retry = ch.reserve_send();
crate::assert_with_log!(
retry.is_none(),
"reservation count preserved after rejected abort",
true,
retry.is_none()
);
crate::test_complete!("toy_channel_abort_rejects_wrong_obligation_kind");
}
#[test]
fn display_impls() {
init_test("graded_display_impls");
let proof = ResolvedProof {
kind: ObligationKind::SendPermit,
resolution: Resolution::Commit,
};
let s = format!("{proof}");
let has_resolved = s.contains("resolved");
crate::assert_with_log!(has_resolved, "proof display", true, has_resolved);
let scope_proof = ScopeProof {
label: "test".to_string(),
total_reserved: 3,
total_resolved: 3,
};
let s = format!("{scope_proof}");
let has_clean = s.contains("clean");
crate::assert_with_log!(has_clean, "scope proof display", true, has_clean);
let err = ScopeLeakError {
label: "bad".to_string(),
outstanding: 2,
reserved: 5,
resolved: 3,
};
let s = format!("{err}");
let has_leaked = s.contains("leaked");
crate::assert_with_log!(has_leaked, "scope error display", true, has_leaked);
let resolution = format!("{}", Resolution::Commit);
crate::assert_with_log!(
resolution == "commit",
"resolution display",
"commit",
resolution
);
crate::test_complete!("graded_display_impls");
}
#[test]
fn typing_judgment_demonstration() {
init_test("typing_judgment_demonstration");
let mut scope = GradedScope::open("typing_demo");
let ob = GradedObligation::reserve(ObligationKind::SendPermit, "demo");
scope.on_reserve();
let proof = ob.resolve(Resolution::Commit);
scope.on_resolve();
let scope_proof = scope.close().expect("scope should be clean");
let kind = proof.kind();
crate::assert_with_log!(
kind == ObligationKind::SendPermit,
"proof kind",
ObligationKind::SendPermit,
kind
);
let label = scope_proof.label();
crate::assert_with_log!(label == "typing_demo", "scope label", "typing_demo", label);
crate::test_complete!("typing_judgment_demonstration");
}
#[test]
fn resource_semiring_identity() {
init_test("resource_semiring_identity");
let scope = GradedScope::open("zero");
let proof = scope.close().expect("zero obligations = clean");
let total = proof.total_reserved();
crate::assert_with_log!(total == 0, "zero reserved", 0, total);
crate::test_complete!("resource_semiring_identity");
}
#[test]
fn resource_semiring_additive() {
init_test("resource_semiring_additive");
let mut scope = GradedScope::open("additive");
for _ in 0..3 {
let ob = GradedObligation::reserve(ObligationKind::Lease, "lease");
scope.on_reserve();
let _proof = ob.resolve(Resolution::Commit);
scope.on_resolve();
}
let proof = scope.close().expect("all resolved");
let total = proof.total_reserved();
crate::assert_with_log!(total == 3, "3 reserved", 3, total);
let resolved = proof.total_resolved();
crate::assert_with_log!(resolved == 3, "3 resolved", 3, resolved);
crate::test_complete!("resource_semiring_additive");
}
#[test]
fn token_commit_returns_proof() {
init_test("token_commit_returns_proof");
let token: SendPermitToken = ObligationToken::reserve("commit-test");
let proof = token.commit();
let kind = proof.kind();
crate::assert_with_log!(
kind == ObligationKind::SendPermit,
"proof kind",
ObligationKind::SendPermit,
kind
);
crate::test_complete!("token_commit_returns_proof");
}
#[test]
fn token_abort_returns_proof() {
init_test("token_abort_returns_proof");
let token: AckToken = ObligationToken::reserve("abort-test");
let proof = token.abort();
let kind = proof.kind();
crate::assert_with_log!(
kind == ObligationKind::Ack,
"proof kind",
ObligationKind::Ack,
kind
);
crate::test_complete!("token_abort_returns_proof");
}
#[test]
#[should_panic(expected = "OBLIGATION TOKEN LEAKED")]
fn token_drop_without_consume_panics() {
init_test("token_drop_without_consume_panics");
let _token: SendPermitToken = ObligationToken::reserve("leaked-token");
}
#[test]
fn token_into_raw_disarms() {
init_test("token_into_raw_disarms");
let token: LeaseToken = ObligationToken::reserve("raw-escape");
let raw = token.into_raw();
let kind = raw.kind;
crate::assert_with_log!(
kind == ObligationKind::Lease,
"raw kind",
ObligationKind::Lease,
kind
);
drop(raw);
crate::test_complete!("token_into_raw_disarms");
}
#[test]
fn committed_proof_bridge() {
init_test("committed_proof_bridge");
let token: SendPermitToken = ObligationToken::reserve("bridge-commit");
let committed = token.commit();
let resolved = committed.into_resolved_proof();
let r = resolved.resolution();
crate::assert_with_log!(r == Resolution::Commit, "resolution", Resolution::Commit, r);
let kind = resolved.kind();
crate::assert_with_log!(
kind == ObligationKind::SendPermit,
"kind",
ObligationKind::SendPermit,
kind
);
crate::test_complete!("committed_proof_bridge");
}
#[test]
fn aborted_proof_bridge() {
init_test("aborted_proof_bridge");
let token: AckToken = ObligationToken::reserve("bridge-abort");
let aborted = token.abort();
let resolved = aborted.into_resolved_proof();
let r = resolved.resolution();
crate::assert_with_log!(r == Resolution::Abort, "resolution", Resolution::Abort, r);
let kind = resolved.kind();
crate::assert_with_log!(
kind == ObligationKind::Ack,
"kind",
ObligationKind::Ack,
kind
);
crate::test_complete!("aborted_proof_bridge");
}
#[test]
fn token_kind_mapping() {
init_test("token_kind_mapping");
let sp = SendPermit::obligation_kind();
crate::assert_with_log!(
sp == ObligationKind::SendPermit,
"SendPermit",
ObligationKind::SendPermit,
sp
);
let ack = AckKind::obligation_kind();
crate::assert_with_log!(
ack == ObligationKind::Ack,
"AckKind",
ObligationKind::Ack,
ack
);
let lease = LeaseKind::obligation_kind();
crate::assert_with_log!(
lease == ObligationKind::Lease,
"LeaseKind",
ObligationKind::Lease,
lease
);
let io = IoOpKind::obligation_kind();
crate::assert_with_log!(
io == ObligationKind::IoOp,
"IoOpKind",
ObligationKind::IoOp,
io
);
crate::test_complete!("token_kind_mapping");
}
#[test]
fn scope_reserve_and_commit_token() {
init_test("scope_reserve_and_commit_token");
let mut scope = GradedScope::open("token-scope-commit");
let token: SendPermitToken = scope.reserve_token("scoped-send");
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 1, "outstanding", 1, outstanding);
let proof = scope.resolve_commit(token);
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 0, "outstanding", 0, outstanding);
let kind = proof.kind();
crate::assert_with_log!(
kind == ObligationKind::SendPermit,
"kind",
ObligationKind::SendPermit,
kind
);
let scope_proof = scope.close().expect("scope should close cleanly");
let total = scope_proof.total_reserved();
crate::assert_with_log!(total == 1, "reserved", 1, total);
crate::test_complete!("scope_reserve_and_commit_token");
}
#[test]
fn scope_reserve_and_abort_token() {
init_test("scope_reserve_and_abort_token");
let mut scope = GradedScope::open("token-scope-abort");
let token: AckToken = scope.reserve_token("scoped-ack");
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 1, "outstanding", 1, outstanding);
let proof = scope.resolve_abort(token);
let outstanding = scope.outstanding();
crate::assert_with_log!(outstanding == 0, "outstanding", 0, outstanding);
let kind = proof.kind();
crate::assert_with_log!(
kind == ObligationKind::Ack,
"kind",
ObligationKind::Ack,
kind
);
let scope_proof = scope.close().expect("scope should close cleanly");
let total = scope_proof.total_reserved();
crate::assert_with_log!(total == 1, "reserved", 1, total);
crate::test_complete!("scope_reserve_and_abort_token");
}
#[test]
fn all_four_token_kinds() {
init_test("all_four_token_kinds");
let t1: SendPermitToken = ObligationToken::reserve("sp");
let p1 = t1.commit();
let k1 = p1.kind();
crate::assert_with_log!(
k1 == ObligationKind::SendPermit,
"SendPermit",
ObligationKind::SendPermit,
k1
);
let t2: AckToken = ObligationToken::reserve("ack");
let p2 = t2.abort();
let k2 = p2.kind();
crate::assert_with_log!(k2 == ObligationKind::Ack, "Ack", ObligationKind::Ack, k2);
let t3: LeaseToken = ObligationToken::reserve("lease");
let p3 = t3.commit();
let k3 = p3.kind();
crate::assert_with_log!(
k3 == ObligationKind::Lease,
"Lease",
ObligationKind::Lease,
k3
);
let t4: IoOpToken = ObligationToken::reserve("io");
let p4 = t4.abort();
let k4 = p4.kind();
crate::assert_with_log!(k4 == ObligationKind::IoOp, "IoOp", ObligationKind::IoOp, k4);
crate::test_complete!("all_four_token_kinds");
}
#[test]
fn resolution_debug_clone_copy_eq() {
let r = Resolution::Commit;
let dbg = format!("{r:?}");
assert!(dbg.contains("Commit"), "{dbg}");
let copied = r;
let cloned = r;
assert_eq!(copied, cloned);
assert_ne!(r, Resolution::Abort);
}
#[test]
fn resolved_proof_debug_eq() {
let rp = ResolvedProof {
kind: ObligationKind::SendPermit,
resolution: Resolution::Commit,
};
let dbg = format!("{rp:?}");
assert!(dbg.contains("ResolvedProof"), "{dbg}");
let rp_same = ResolvedProof {
kind: ObligationKind::SendPermit,
resolution: Resolution::Commit,
};
assert_eq!(rp, rp_same);
}
#[test]
fn scope_proof_debug_accessors() {
let sp = ScopeProof {
label: "test".to_string(),
total_reserved: 5,
total_resolved: 5,
};
let dbg = format!("{sp:?}");
assert!(dbg.contains("ScopeProof"), "{dbg}");
assert_eq!(sp.label(), "test");
assert_eq!(sp.total_reserved(), 5);
assert_eq!(sp.total_resolved(), 5);
}
#[test]
#[should_panic(expected = "on_reserve overflowed outstanding obligation count")]
fn scope_on_reserve_overflow_panics_instead_of_wrapping() {
let mut scope = GradedScope {
label: "overflow".to_string(),
reserved: u32::MAX,
resolved: 0,
closed: false,
};
scope.on_reserve();
}
#[test]
#[should_panic(expected = "toy channel occupancy overflowed")]
fn toy_channel_capacity_overflow_panics_instead_of_wrapping() {
let mut ch =
toy_api::ToyChannel::from_state(usize::MAX, vec!["occupied".to_string()], usize::MAX);
let _ = ch.reserve_send();
}
#[test]
fn close_err_does_not_panic_when_caller_handles_error() {
let mut scope = GradedScope::open("leak-test");
scope.on_reserve();
let err = scope.close().expect_err("should return leak error");
assert_eq!(err.outstanding, 1);
}
}