use super::*;
use core::num::NonZeroU64;
fn guarantee(value: u8) -> Guarantee {
match value {
0 => Guarantee::Accepted,
1 => Guarantee::LocallyDurable,
_ => Guarantee::ReplicatedDurable,
}
}
fn receipt(sequence: u64) -> Receipt {
Receipt::from_operation(OperationId {
history: HistoryId([7; 16]),
epoch: 1,
session: SessionId(1),
sequence,
})
}
fn invariant(command: &PendingMutation<u8>) -> bool {
(command.status == MutationStatus::AwaitingReply) == command.request.is_some()
}
#[kani::proof]
fn initialization_retains_exact_operation() {
let receipt = receipt(kani::any());
let command: u8 = kani::any();
let guarantee = guarantee(kani::any());
let result = PendingMutation::new(TableRequest::<(), _>::Mutate {
receipt,
command,
guarantee,
});
assert!(result.is_ok());
if let Ok(pending) = result {
assert!(invariant(&pending));
assert_eq!(pending.status(), MutationStatus::Ready);
assert_eq!(pending.receipt(), receipt);
assert_eq!(*pending.command(), command);
assert_eq!(pending.guarantee(), guarantee);
kani::cover!(
pending.status() == MutationStatus::Ready,
"pending.initialized"
);
}
}
#[kani::proof]
fn arbitrary_step_preserves_receipt_and_confirmation() {
let status = match kani::any::<u8>() {
0 => MutationStatus::Ready,
1 => MutationStatus::AwaitingReply,
2 => MutationStatus::Uncertain,
_ => MutationStatus::Confirmed,
};
let required = guarantee(kani::any());
let established = guarantee(kani::any());
let id = RequestId(NonZeroU64::MIN);
let command: u8 = kani::any();
let mut pending = PendingMutation {
receipt: receipt(1),
command,
guarantee: required,
request: if status == MutationStatus::AwaitingReply {
Some(id)
} else {
None
},
status,
};
assert!(invariant(&pending));
let action: u8 = kani::any();
let tag: u8 = kani::any();
let matching_receipt: bool = kani::any();
let matching_id: bool = kani::any();
let reply_receipt = receipt(if matching_receipt { 1 } else { 2 });
let reply: Result<TableReply<(), (), ()>, StorageFailure> = match tag {
0 => Ok(TableReply::Mutation(MutationAnswer::Pending {
receipt: reply_receipt,
outcome: Ok(()),
established,
})),
1 => Ok(TableReply::Mutation(MutationAnswer::Confirmed {
receipt: reply_receipt,
outcome: Ok(()),
established,
})),
2 => Ok(TableReply::Mutation(MutationAnswer::Unknown)),
3 => Ok(TableReply::Mutation(MutationAnswer::Unavailable(
Error::ExpiredOperation,
))),
4 => Err(StorageFailure::WorkerClosed),
5 => Ok(TableReply::Read(Answer::Pending)),
_ => Ok(TableReply::Retired(Ok(()))),
};
match action {
0 => {
let _request = pending.retry::<(), ()>(id);
}
1 => {
let _request = pending.inquire::<(), ()>(id);
}
2 => {
pending.disconnect();
}
_ => {
let received = pending.step(ReceiveMutation {
id: if matching_id {
id
} else {
RequestId(id.0.saturating_add(1))
},
reply: &reply,
});
kani::cover!(
received == Err(MutationReceiveError::WrongReceipt),
"pending.wrong_receipt"
);
kani::cover!(
received == Err(MutationReceiveError::InsufficientGuarantee),
"pending.insufficient"
);
kani::cover!(
tag == 2 && received == Ok(MutationStatus::Uncertain),
"pending.unknown"
);
}
}
assert!(invariant(&pending));
assert_eq!(pending.receipt(), receipt(1));
assert_eq!(*pending.command(), command);
assert_eq!(pending.guarantee(), required);
if status == MutationStatus::Confirmed {
assert_eq!(pending.status(), MutationStatus::Confirmed);
}
if status != MutationStatus::Confirmed && pending.status() == MutationStatus::Confirmed {
assert!(action > 2 && tag == 1 && matching_id && matching_receipt);
assert!(
required == Guarantee::Accepted
|| established == Guarantee::ReplicatedDurable
|| (required == Guarantee::LocallyDurable
&& established == Guarantee::LocallyDurable)
);
kani::cover!(true, "pending.confirmed");
}
kani::cover!(
action == 2
&& status == MutationStatus::AwaitingReply
&& pending.status() == MutationStatus::Uncertain,
"pending.disconnected"
);
}