use std::cell::RefCell;
use std::collections::VecDeque;
use std::thread_local;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InvariantType {
#[default]
Correctness,
Safety,
Performance,
State,
}
#[derive(Debug, Clone)]
pub struct InvariantRecord {
pub message: String,
pub invariant_type: InvariantType,
pub context: String,
pub passed: bool,
}
thread_local! {
static INVARIANT_HISTORY: RefCell<VecDeque<InvariantRecord>> = RefCell::new(VecDeque::with_capacity(100));
}
#[macro_export]
macro_rules! assert_invariant {
($condition:expr, $message:expr) => {
$crate::invariant_ppt::__assert_invariant_impl(
$condition,
$message,
None,
$crate::invariant_ppt::InvariantType::Correctness,
)
};
($condition:expr, $message:expr, $context:expr) => {
$crate::invariant_ppt::__assert_invariant_impl(
$condition,
$message,
Some($context),
$crate::invariant_ppt::InvariantType::Correctness,
)
};
}
#[doc(hidden)]
pub fn __assert_invariant_impl(
condition: bool,
message: &str,
context: Option<&str>,
type_: InvariantType,
) {
let ctx = context.unwrap_or("unknown");
let record = InvariantRecord {
message: message.to_string(),
invariant_type: type_,
context: ctx.to_string(),
passed: condition,
};
INVARIANT_HISTORY.with(|history| {
let mut h = history.borrow_mut();
if h.len() >= 100 {
h.pop_front();
}
h.push_back(record);
});
assert!(condition, "INVARIANT VIOLATION [{ctx}]: {message}");
}
pub fn contract_test(test_name: &str, required_invariants: &[&str]) {
let history = INVARIANT_HISTORY.with(|h| h.borrow().clone());
let mut missing: Vec<&str> = Vec::new();
for invariant in required_invariants {
let found = history.iter().any(|r| r.message == *invariant);
if !found {
missing.push(invariant);
}
}
assert!(
missing.is_empty(),
"CONTRACT FAILURE [{}]: The following invariants were not checked:\n - {}",
test_name,
missing.join("\n - ")
);
}
pub fn clear_invariant_log() {
INVARIANT_HISTORY.with(|h| {
h.borrow_mut().clear();
});
}
#[derive(Debug, Clone, PartialEq)]
pub struct PerfSnapshot {
pub label: String,
pub latency_ms: f64,
pub throughput_ops: f64,
pub memory_delta_kb: i64,
}
pub fn assert_performance_invariant(
snapshot: &PerfSnapshot,
baseline_latency: f64,
tolerance_factor: f64,
) {
let max_latency = baseline_latency * (1.0 + tolerance_factor);
__assert_invariant_impl(
snapshot.latency_ms <= max_latency,
&format!("PERF: {} latency within predicted envelope", snapshot.label),
Some("performance"),
InvariantType::Performance,
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invariant_type_default() {
assert_eq!(InvariantType::default(), InvariantType::Correctness);
}
#[test]
fn test_contract_test_passes_when_invariant_logged() {
clear_invariant_log();
__assert_invariant_impl(true, "must exist", Some("ctx"), InvariantType::State);
contract_test("contract pass", &["must exist"]);
}
#[test]
#[should_panic(expected = "CONTRACT FAILURE")]
fn test_contract_test_fails_when_invariant_missing() {
clear_invariant_log();
contract_test("contract fail", &["missing invariant"]);
}
#[test]
fn test_invariant_history_is_ring_buffered_to_100() {
clear_invariant_log();
for i in 0..105 {
__assert_invariant_impl(
true,
&format!("inv-{i}"),
Some("ring"),
InvariantType::Correctness,
);
}
contract_test("latest survives", &["inv-104"]);
}
#[test]
#[should_panic(expected = "CONTRACT FAILURE")]
fn test_invariant_history_drops_oldest_entries() {
clear_invariant_log();
for i in 0..105 {
__assert_invariant_impl(
true,
&format!("inv-{i}"),
Some("ring"),
InvariantType::Correctness,
);
}
contract_test("oldest evicted", &["inv-0"]);
}
#[test]
fn test_performance_invariant_passes_in_envelope() {
clear_invariant_log();
let snapshot = PerfSnapshot {
label: "latency_ok".to_string(),
latency_ms: 9.0,
throughput_ops: 10.0,
memory_delta_kb: 0,
};
assert_performance_invariant(&snapshot, 10.0, 0.10);
contract_test(
"perf pass",
&["PERF: latency_ok latency within predicted envelope"],
);
}
#[test]
#[should_panic(expected = "INVARIANT VIOLATION")]
fn test_performance_invariant_fails_outside_envelope() {
clear_invariant_log();
let snapshot = PerfSnapshot {
label: "latency_bad".to_string(),
latency_ms: 20.0,
throughput_ops: 10.0,
memory_delta_kb: 0,
};
assert_performance_invariant(&snapshot, 10.0, 0.10);
}
}