1pub mod actor;
38pub mod ambient_authority;
39pub mod cancel_correctness;
40pub mod cancel_debt;
41pub mod cancel_signal_ordering;
42pub mod cancellation_protocol;
43pub mod channel_atomicity;
44pub mod deadline_monotone;
45pub mod determinism;
46pub mod eprocess;
47pub mod evidence;
48#[cfg(feature = "messaging-fabric")]
49pub mod fabric;
50pub mod finalizer;
51pub mod loser_drain;
52pub mod obligation_leak;
53pub mod priority_inversion;
54pub mod quiescence;
55pub mod region_leak;
56pub mod region_tree;
57pub mod rref_access;
58pub mod runtime_epoch;
59pub mod spork;
60pub mod task_leak;
61pub mod waker_dedup;
62
63pub use actor::{
64 ActorLeakOracle, ActorLeakViolation, MailboxOracle, MailboxViolation, MailboxViolationKind,
65 SupervisionOracle, SupervisionViolation, SupervisionViolationKind,
66};
67pub use ambient_authority::{
68 AmbientAuthorityOracle, AmbientAuthorityViolation, CapabilityKind, CapabilitySet,
69};
70pub use cancel_correctness::{
71 CancelCorrectnessConfig, CancelCorrectnessOracle, CancelCorrectnessStatistics,
72 CancelCorrectnessViolation, InvalidInitialWitnessKind,
73};
74pub use cancel_debt::{
75 CancelDebtConfig, CancelDebtOracle, CancelDebtStatistics, CancelDebtViolation,
76};
77pub use cancel_signal_ordering::{
78 CancelOrderingConfig, CancelOrderingOracle, CancelOrderingStatistics, CancelOrderingViolation,
79};
80pub use cancellation_protocol::{
81 CancellationProtocolOracle, CancellationProtocolViolation, TaskStateKind,
82};
83pub use channel_atomicity::{
84 ChannelAtomicityConfig, ChannelAtomicityOracle, ChannelAtomicityStatistics,
85 ChannelAtomicityViolation, ChannelId, EnforcementMode, ReservationId, ViolationRecord,
86 WakerId as ChannelWakerId,
87};
88pub use deadline_monotone::{DeadlineMonotoneOracle, DeadlineMonotoneViolation};
89pub use determinism::{
90 DeterminismOracle, DeterminismViolation, TraceEventSummary, assert_deterministic,
91 assert_deterministic_multi,
92};
93pub use eprocess::{EProcess, EProcessConfig, EProcessMonitor, EValue, MonitorResult};
94pub use evidence::{
95 BayesFactor, DetectionModel, EvidenceEntry, EvidenceLedger, EvidenceLine, EvidenceStrength,
96 EvidenceSummary, LogLikelihoodContributions,
97};
98#[cfg(feature = "messaging-fabric")]
99pub use fabric::{
100 FabricPublishOracle, FabricPublishViolation, FabricQuiescenceOracle, FabricQuiescenceViolation,
101 FabricRedeliveryOracle, FabricRedeliveryViolation, FabricReplyOracle, FabricReplyViolation,
102};
103pub use finalizer::{FinalizerId, FinalizerOracle, FinalizerViolation};
104pub use loser_drain::{LoserDrainOracle, LoserDrainViolation};
105pub use obligation_leak::{ObligationLeakOracle, ObligationLeakViolation};
106pub use priority_inversion::{
107 InversionId, InversionType, Priority, PriorityInversion, PriorityInversionConfig,
108 PriorityInversionOracle, PriorityInversionStatistics, ResourceId,
109};
110pub use quiescence::{QuiescenceOracle, QuiescenceViolation};
111pub use region_leak::{
112 BudgetInfo, RegionLeakConfig, RegionLeakOracle, RegionLeakStatistics, RegionLifecycleState,
113 RegionState as RegionLeakState, RegionViolation, TaskLifecycleState, TaskState,
114 ViolationContext, ViolationType,
115};
116pub use region_tree::{RegionTreeEntry, RegionTreeOracle, RegionTreeViolation};
117pub use rref_access::{RRefAccessOracle, RRefAccessViolation, RRefAccessViolationKind, RRefId};
118pub use runtime_epoch::{
119 ConsistencyLevel, RuntimeEpochConfig, RuntimeEpochOracle, RuntimeEpochStatistics,
120 RuntimeEpochViolation, RuntimeModule,
121};
122pub use spork::{
123 DownOrderOracle, DownOrderViolation, RegistryLeaseOracle, RegistryLeaseViolation,
124 ReplyLinearityOracle, ReplyLinearityViolation, SupervisorQuiescenceOracle,
125 SupervisorQuiescenceViolation,
126};
127pub use task_leak::{TaskLeakOracle, TaskLeakViolation};
128pub use waker_dedup::{
129 ViolationRecord as WakerViolationRecord, WakerDedupConfig, WakerDedupOracle,
130 WakerDedupStatistics, WakerDedupViolation, WakerId as WakerDedupId, WakerStatus,
131};
132
133use serde::{Deserialize, Serialize};
134use std::collections::{BTreeMap, BTreeSet};
135use std::fmt::Write as _;
136
137use crate::obligation::dialectica::ContractChecker;
138use crate::obligation::marking::{MarkingAnalyzer, MarkingEvent};
139use crate::obligation::no_aliasing_proof::NoAliasingProver;
140use crate::record::region::RegionState;
141use crate::runtime::RuntimeState;
142use crate::types::Time;
143
144#[derive(Debug, Clone)]
146pub enum OracleViolation {
147 TaskLeak(TaskLeakViolation),
149 ObligationLeak(ObligationLeakViolation),
151 Quiescence(QuiescenceViolation),
153 LoserDrain(LoserDrainViolation),
155 Finalizer(FinalizerViolation),
157 RegionTree(RegionTreeViolation),
159 RegionLeak(RegionViolation),
161 AmbientAuthority(AmbientAuthorityViolation),
163 DeadlineMonotone(DeadlineMonotoneViolation),
165 CancellationProtocol(CancellationProtocolViolation),
167 CancelCorrectness(CancelCorrectnessViolation),
169 CancelDebt(CancelDebtViolation),
171 CancelOrdering(CancelOrderingViolation),
173 RuntimeEpoch(RuntimeEpochViolation),
175 ChannelAtomicity(ChannelAtomicityViolation),
177 WakerDedup(WakerDedupViolation),
179 ActorLeak(ActorLeakViolation),
181 Supervision(SupervisionViolation),
183 Mailbox(MailboxViolation),
185 RRefAccess(RRefAccessViolation),
187 ReplyLinearity(ReplyLinearityViolation),
189 RegistryLease(RegistryLeaseViolation),
191 DownOrder(DownOrderViolation),
193 SupervisorQuiescence(SupervisorQuiescenceViolation),
195 #[cfg(feature = "messaging-fabric")]
197 FabricPublish(FabricPublishViolation),
198 #[cfg(feature = "messaging-fabric")]
200 FabricReply(FabricReplyViolation),
201 #[cfg(feature = "messaging-fabric")]
203 FabricQuiescence(FabricQuiescenceViolation),
204 #[cfg(feature = "messaging-fabric")]
206 FabricRedelivery(FabricRedeliveryViolation),
207 PriorityInversion(PriorityInversion),
209}
210
211impl std::fmt::Display for OracleViolation {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 match self {
214 Self::TaskLeak(v) => write!(f, "Task leak: {v}"),
215 Self::ObligationLeak(v) => write!(f, "Obligation leak: {v}"),
216 Self::Quiescence(v) => write!(f, "Quiescence violation: {v}"),
217 Self::LoserDrain(v) => write!(f, "Loser drain violation: {v}"),
218 Self::Finalizer(v) => write!(f, "Finalizer violation: {v}"),
219 Self::RegionTree(v) => write!(f, "Region tree violation: {v}"),
220 Self::RegionLeak(v) => write!(f, "Region leak violation: {v}"),
221 Self::AmbientAuthority(v) => write!(f, "Ambient authority violation: {v}"),
222 Self::DeadlineMonotone(v) => write!(f, "Deadline monotonicity violation: {v}"),
223 Self::CancellationProtocol(v) => write!(f, "Cancellation protocol violation: {v}"),
224 Self::CancelCorrectness(v) => write!(f, "Cancel-correctness violation: {v}"),
225 Self::CancelDebt(v) => write!(f, "Cancel debt violation: {v}"),
226 Self::CancelOrdering(v) => write!(f, "Cancel ordering violation: {v}"),
227 Self::RuntimeEpoch(v) => write!(f, "Runtime epoch violation: {v}"),
228 Self::ChannelAtomicity(v) => write!(f, "Channel atomicity violation: {v}"),
229 Self::WakerDedup(v) => write!(f, "Waker deduplication violation: {v}"),
230 Self::ActorLeak(v) => write!(f, "Actor leak: {v}"),
231 Self::Supervision(v) => write!(f, "Supervision violation: {v}"),
232 Self::Mailbox(v) => write!(f, "Mailbox violation: {v}"),
233 Self::RRefAccess(v) => write!(f, "RRef access violation: {v}"),
234 Self::ReplyLinearity(v) => write!(f, "Reply linearity violation: {v}"),
235 Self::RegistryLease(v) => write!(f, "Registry lease violation: {v}"),
236 Self::DownOrder(v) => write!(f, "DOWN order violation: {v}"),
237 Self::SupervisorQuiescence(v) => write!(f, "Supervisor quiescence violation: {v}"),
238 #[cfg(feature = "messaging-fabric")]
239 Self::FabricPublish(v) => write!(f, "FABRIC publish violation: {v}"),
240 #[cfg(feature = "messaging-fabric")]
241 Self::FabricReply(v) => write!(f, "FABRIC reply violation: {v}"),
242 #[cfg(feature = "messaging-fabric")]
243 Self::FabricQuiescence(v) => write!(f, "FABRIC quiescence violation: {v}"),
244 #[cfg(feature = "messaging-fabric")]
245 Self::FabricRedelivery(v) => write!(f, "FABRIC redelivery violation: {v}"),
246 Self::PriorityInversion(v) => write!(
247 f,
248 "Priority inversion: Task {:?}(P{:?}) blocked by Task {:?}(P{:?}) on Resource {:?} for {:?}",
249 v.blocked_task,
250 v.blocked_priority,
251 v.blocking_task,
252 v.blocking_priority,
253 v.resource_id,
254 v.duration.unwrap_or_else(|| v.start_time.elapsed())
255 ),
256 }
257 }
258}
259
260impl std::error::Error for OracleViolation {}
261
262#[derive(Debug, Default)]
264pub struct OracleSuite {
265 pub task_leak: TaskLeakOracle,
267 pub obligation_leak: ObligationLeakOracle,
269 pub quiescence: QuiescenceOracle,
271 pub loser_drain: LoserDrainOracle,
273 pub finalizer: FinalizerOracle,
275 pub region_tree: RegionTreeOracle,
277 pub region_leak: RegionLeakOracle,
279 pub ambient_authority: AmbientAuthorityOracle,
281 pub deadline_monotone: DeadlineMonotoneOracle,
283 pub cancellation_protocol: CancellationProtocolOracle,
285 pub cancel_correctness: CancelCorrectnessOracle,
287 pub cancel_debt: CancelDebtOracle,
289 pub cancel_signal_ordering: CancelOrderingOracle,
291 pub runtime_epoch: RuntimeEpochOracle,
293 pub channel_atomicity: ChannelAtomicityOracle,
295 pub waker_dedup: WakerDedupOracle,
297 pub actor_leak: ActorLeakOracle,
299 pub supervision: SupervisionOracle,
301 pub mailbox: MailboxOracle,
303 pub rref_access: RRefAccessOracle,
305 pub reply_linearity: ReplyLinearityOracle,
307 pub registry_lease: RegistryLeaseOracle,
309 pub down_order: DownOrderOracle,
311 pub supervisor_quiescence: SupervisorQuiescenceOracle,
313 #[cfg(feature = "messaging-fabric")]
315 pub fabric_publish: FabricPublishOracle,
316 #[cfg(feature = "messaging-fabric")]
318 pub fabric_reply: FabricReplyOracle,
319 #[cfg(feature = "messaging-fabric")]
321 pub fabric_quiescence: FabricQuiescenceOracle,
322 #[cfg(feature = "messaging-fabric")]
324 pub fabric_redelivery: FabricRedeliveryOracle,
325 pub eprocess_monitor: Option<EProcessMonitor>,
332}
333
334impl OracleSuite {
335 #[must_use]
337 pub fn new() -> Self {
338 Self {
339 eprocess_monitor: Some(EProcessMonitor::standard()),
340 ..Self::default()
341 }
342 }
343
344 #[allow(clippy::too_many_lines)]
349 pub fn hydrate_temporal_from_state(&mut self, state: &RuntimeState, now: Time) {
350 #[derive(Clone, Copy)]
351 struct RegionSnapshot {
352 id: crate::types::RegionId,
353 parent: Option<crate::types::RegionId>,
354 state: RegionState,
355 budget: crate::types::Budget,
356 created_at: Time,
357 }
358
359 fn walk_regions(
360 id: crate::types::RegionId,
361 children: &BTreeMap<crate::types::RegionId, Vec<crate::types::RegionId>>,
362 seen: &mut BTreeSet<crate::types::RegionId>,
363 pre_order: &mut Vec<crate::types::RegionId>,
364 post_order: &mut Vec<crate::types::RegionId>,
365 ) {
366 if !seen.insert(id) {
367 return;
368 }
369 pre_order.push(id);
370 if let Some(kids) = children.get(&id) {
371 for &child in kids {
372 walk_regions(child, children, seen, pre_order, post_order);
373 }
374 }
375 post_order.push(id);
376 }
377
378 self.task_leak.reset();
379 self.obligation_leak.snapshot_from_state(state, now);
380 self.quiescence.reset();
381 self.finalizer.reset();
382 self.region_tree.reset();
383 self.deadline_monotone.reset();
384 self.cancellation_protocol.snapshot_from_state(state, now);
385 self.cancel_correctness.reset();
386 self.cancel_debt.reset();
387 self.cancel_signal_ordering.reset();
388 self.runtime_epoch.reset();
389
390 for event in state.finalizer_history() {
391 match *event {
392 crate::runtime::state::FinalizerHistoryEvent::Registered { id, region, time } => {
393 self.finalizer.on_register(FinalizerId(id), region, time);
394 }
395 crate::runtime::state::FinalizerHistoryEvent::Ran { id, time } => {
396 self.finalizer.on_run(FinalizerId(id), time);
397 }
398 crate::runtime::state::FinalizerHistoryEvent::RegionClosed { region, time } => {
399 self.finalizer.on_region_close(region, time);
400 }
401 }
402 }
403
404 let mut regions: BTreeMap<crate::types::RegionId, RegionSnapshot> = BTreeMap::new();
405 let mut children: BTreeMap<crate::types::RegionId, Vec<crate::types::RegionId>> =
406 BTreeMap::new();
407
408 for (_, region) in state.regions_iter() {
409 let snapshot = RegionSnapshot {
410 id: region.id,
411 parent: region.parent,
412 state: region.state(),
413 budget: region.budget(),
414 created_at: region.created_at(),
415 };
416 regions.insert(snapshot.id, snapshot);
417 children.entry(snapshot.id).or_default();
418 }
419
420 for snapshot in regions.values() {
421 if let Some(parent) = snapshot.parent {
422 children.entry(parent).or_default().push(snapshot.id);
423 }
424 }
425 for kids in children.values_mut() {
426 kids.sort();
427 }
428
429 let mut roots = Vec::new();
430 for (id, snapshot) in ®ions {
431 if snapshot
432 .parent
433 .is_none_or(|parent| !regions.contains_key(&parent))
434 {
435 roots.push(*id);
436 }
437 }
438
439 let mut pre_order = Vec::new();
440 let mut post_order = Vec::new();
441 let mut seen = BTreeSet::new();
442
443 for root in roots {
444 walk_regions(root, &children, &mut seen, &mut pre_order, &mut post_order);
445 }
446 for &id in regions.keys() {
447 walk_regions(id, &children, &mut seen, &mut pre_order, &mut post_order);
448 }
449
450 for region_id in &pre_order {
451 let Some(snapshot) = regions.get(region_id) else {
452 continue;
453 };
454 self.region_tree
455 .on_region_create(snapshot.id, snapshot.parent, snapshot.created_at);
456 self.quiescence
457 .on_region_create(snapshot.id, snapshot.parent);
458 self.deadline_monotone.on_region_create(
459 snapshot.id,
460 snapshot.parent,
461 &snapshot.budget,
462 now,
463 );
464 }
465
466 let mut tasks = Vec::new();
467 for (_, task) in state.tasks_iter() {
468 tasks.push((task.id, task.owner, task.state.is_terminal()));
469 }
470 tasks.sort_by_key(|(task, _, _)| *task);
471
472 for (task_id, region_id, terminal) in tasks {
473 self.task_leak.on_spawn(task_id, region_id, now);
474 self.quiescence.on_spawn(task_id, region_id);
475 if terminal {
476 self.task_leak.on_complete(task_id, now);
477 self.quiescence.on_task_complete(task_id);
478 }
479 }
480
481 for region_id in post_order {
482 let Some(snapshot) = regions.get(®ion_id) else {
483 continue;
484 };
485 if snapshot.state.is_terminal() {
486 self.task_leak.on_region_close(region_id, now);
487 self.quiescence.on_region_close(region_id, now);
488 }
489 }
490 }
491
492 #[must_use]
494 pub fn check_all(&mut self, now: Time) -> Vec<OracleViolation> {
495 let mut violations = Vec::new();
496
497 if let Err(v) = self.task_leak.check(now) {
498 violations.push(OracleViolation::TaskLeak(v));
499 }
500
501 if let Err(v) = self.obligation_leak.check(now) {
502 violations.push(OracleViolation::ObligationLeak(v));
503 }
504
505 if let Err(v) = self.quiescence.check() {
506 violations.push(OracleViolation::Quiescence(v));
507 }
508
509 if let Err(v) = self.loser_drain.check() {
510 violations.push(OracleViolation::LoserDrain(v));
511 }
512
513 if let Err(v) = self.finalizer.check() {
514 violations.push(OracleViolation::Finalizer(v));
515 }
516
517 if let Err(v) = self.region_tree.check() {
518 violations.push(OracleViolation::RegionTree(v));
519 }
520
521 let region_leak_violations = self.region_leak.check_for_violations();
522 if let Ok(violations_vec) = region_leak_violations {
523 for violation in violations_vec {
524 violations.push(OracleViolation::RegionLeak(violation));
525 }
526 } else {
527 }
530
531 if let Err(v) = self.ambient_authority.check() {
532 violations.push(OracleViolation::AmbientAuthority(v));
533 }
534
535 if let Err(v) = self.deadline_monotone.check() {
536 violations.push(OracleViolation::DeadlineMonotone(v));
537 }
538
539 if let Err(v) = self.cancellation_protocol.check() {
540 violations.push(OracleViolation::CancellationProtocol(v));
541 }
542
543 if let Err(v) = self.cancel_correctness.check(now) {
544 violations.push(OracleViolation::CancelCorrectness(v));
545 }
546
547 if let Err(v) = self.cancel_debt.check(now) {
548 violations.push(OracleViolation::CancelDebt(v));
549 }
550
551 if let Err(v) = self.cancel_signal_ordering.check(now) {
552 violations.push(OracleViolation::CancelOrdering(v));
553 }
554
555 if let Err(v) = self.runtime_epoch.check(now) {
556 violations.push(OracleViolation::RuntimeEpoch(v));
557 }
558
559 let channel_atomicity_violations = self.channel_atomicity.check_for_violations();
560 if let Ok(violations_vec) = channel_atomicity_violations {
561 for violation in violations_vec {
562 violations.push(OracleViolation::ChannelAtomicity(violation));
563 }
564 } else {
565 }
568
569 let waker_dedup_violations = self.waker_dedup.check_for_violations();
570 if let Ok(violations_vec) = waker_dedup_violations {
571 for violation in violations_vec {
572 violations.push(OracleViolation::WakerDedup(violation));
573 }
574 } else {
575 }
578
579 if let Err(v) = self.actor_leak.check(now) {
580 violations.push(OracleViolation::ActorLeak(v));
581 }
582
583 if let Err(v) = self.supervision.check(now) {
584 violations.push(OracleViolation::Supervision(v));
585 }
586
587 if let Err(v) = self.mailbox.check(now) {
588 violations.push(OracleViolation::Mailbox(v));
589 }
590
591 if let Err(v) = self.rref_access.check() {
592 violations.push(OracleViolation::RRefAccess(v));
593 }
594
595 if let Err(v) = self.reply_linearity.check() {
596 violations.push(OracleViolation::ReplyLinearity(v));
597 }
598
599 if let Err(v) = self.registry_lease.check() {
600 violations.push(OracleViolation::RegistryLease(v));
601 }
602
603 if let Err(v) = self.down_order.check() {
604 violations.push(OracleViolation::DownOrder(v));
605 }
606
607 if let Err(v) = self.supervisor_quiescence.check() {
608 violations.push(OracleViolation::SupervisorQuiescence(v));
609 }
610
611 #[cfg(feature = "messaging-fabric")]
612 if let Err(v) = self.fabric_publish.check() {
613 violations.push(OracleViolation::FabricPublish(v));
614 }
615
616 #[cfg(feature = "messaging-fabric")]
617 if let Err(v) = self.fabric_reply.check() {
618 violations.push(OracleViolation::FabricReply(v));
619 }
620
621 #[cfg(feature = "messaging-fabric")]
622 if let Err(v) = self.fabric_quiescence.check() {
623 violations.push(OracleViolation::FabricQuiescence(v));
624 }
625
626 #[cfg(feature = "messaging-fabric")]
627 if let Err(v) = self.fabric_redelivery.check() {
628 violations.push(OracleViolation::FabricRedelivery(v));
629 }
630
631 violations
632 }
633
634 pub fn reset(&mut self) {
636 self.task_leak.reset();
637 self.obligation_leak.reset();
638 self.quiescence.reset();
639 self.loser_drain.reset();
640 self.finalizer.reset();
641 self.region_tree.reset();
642 self.region_leak.reset();
643 self.ambient_authority.reset();
644 self.deadline_monotone.reset();
645 self.cancellation_protocol.reset();
646 self.cancel_correctness.reset();
647 self.cancel_debt.reset();
648 self.cancel_signal_ordering.reset();
649 self.runtime_epoch.reset();
650 self.channel_atomicity.reset();
651 self.waker_dedup.reset();
652 self.actor_leak.reset();
653 self.supervision.reset();
654 self.mailbox.reset();
655 self.rref_access.reset();
656 self.reply_linearity.reset();
657 self.registry_lease.reset();
658 self.down_order.reset();
659 self.supervisor_quiescence.reset();
660 #[cfg(feature = "messaging-fabric")]
661 self.fabric_publish.reset();
662 #[cfg(feature = "messaging-fabric")]
663 self.fabric_reply.reset();
664 #[cfg(feature = "messaging-fabric")]
665 self.fabric_quiescence.reset();
666 #[cfg(feature = "messaging-fabric")]
667 self.fabric_redelivery.reset();
668 }
669
670 #[must_use]
672 #[allow(clippy::too_many_lines)]
673 pub fn report(&mut self, now: Time) -> OracleReport {
674 let entries = vec![
675 OracleEntryReport::from_result(
676 "task_leak",
677 self.task_leak
678 .check(now)
679 .err()
680 .map(OracleViolation::TaskLeak),
681 OracleStats {
682 entities_tracked: self.task_leak.task_count(),
683 events_recorded: self.task_leak.task_count()
684 + self.task_leak.completed_count()
685 + self.task_leak.closed_region_count(),
686 },
687 ),
688 OracleEntryReport::from_result(
689 "obligation_leak",
690 self.obligation_leak
691 .check(now)
692 .err()
693 .map(OracleViolation::ObligationLeak),
694 OracleStats {
695 entities_tracked: self.obligation_leak.obligation_count(),
696 events_recorded: self.obligation_leak.obligation_count()
697 + self.obligation_leak.closed_region_count(),
698 },
699 ),
700 OracleEntryReport::from_result(
701 "quiescence",
702 self.quiescence
703 .check()
704 .err()
705 .map(OracleViolation::Quiescence),
706 OracleStats {
707 entities_tracked: self.quiescence.region_count(),
708 events_recorded: self.quiescence.region_count()
709 + self.quiescence.closed_count(),
710 },
711 ),
712 OracleEntryReport::from_result(
713 "loser_drain",
714 self.loser_drain
715 .check()
716 .err()
717 .map(OracleViolation::LoserDrain),
718 OracleStats {
719 entities_tracked: self.loser_drain.race_count(),
720 events_recorded: self.loser_drain.race_count()
721 + self.loser_drain.completed_race_count(),
722 },
723 ),
724 OracleEntryReport::from_result(
725 "finalizer",
726 self.finalizer.check().err().map(OracleViolation::Finalizer),
727 OracleStats {
728 entities_tracked: self.finalizer.registered_count(),
729 events_recorded: self.finalizer.registered_count()
730 + self.finalizer.ran_count()
731 + self.finalizer.closed_region_count(),
732 },
733 ),
734 OracleEntryReport::from_result(
735 "region_tree",
736 self.region_tree
737 .check()
738 .err()
739 .map(OracleViolation::RegionTree),
740 OracleStats {
741 entities_tracked: self.region_tree.region_count(),
742 events_recorded: self.region_tree.region_count(),
743 },
744 ),
745 OracleEntryReport::from_result(
746 "region_leak",
747 self.region_leak
748 .check_for_violations()
749 .ok()
750 .and_then(|violations| violations.first().cloned())
751 .map(OracleViolation::RegionLeak),
752 OracleStats {
753 entities_tracked: self.region_leak.statistics().active_regions as usize,
754 events_recorded: (self.region_leak.statistics().total_regions_created
755 + self.region_leak.statistics().total_tasks_spawned)
756 as usize,
757 },
758 ),
759 OracleEntryReport::from_result(
760 "ambient_authority",
761 self.ambient_authority
762 .check()
763 .err()
764 .map(OracleViolation::AmbientAuthority),
765 OracleStats {
766 entities_tracked: self.ambient_authority.task_count(),
767 events_recorded: self.ambient_authority.task_count()
768 + self.ambient_authority.effect_count(),
769 },
770 ),
771 OracleEntryReport::from_result(
772 "deadline_monotone",
773 self.deadline_monotone
774 .check()
775 .err()
776 .map(OracleViolation::DeadlineMonotone),
777 OracleStats {
778 entities_tracked: self.deadline_monotone.region_count(),
779 events_recorded: self.deadline_monotone.region_count(),
780 },
781 ),
782 OracleEntryReport::from_result(
783 "cancellation_protocol",
784 self.cancellation_protocol
785 .check()
786 .err()
787 .map(OracleViolation::CancellationProtocol),
788 OracleStats {
789 entities_tracked: self.cancellation_protocol.region_count(),
790 events_recorded: self.cancellation_protocol.region_count()
791 + self.cancellation_protocol.cancel_count(),
792 },
793 ),
794 OracleEntryReport::from_result(
795 "cancel_correctness",
796 self.cancel_correctness
797 .check(now)
798 .err()
799 .map(OracleViolation::CancelCorrectness),
800 OracleStats {
801 entities_tracked: self.cancel_correctness.get_statistics().active_tasks,
802 events_recorded: self.cancel_correctness.get_statistics().witnesses_processed
803 as usize,
804 },
805 ),
806 OracleEntryReport::from_result(
807 "cancel_debt",
808 self.cancel_debt
809 .check(now)
810 .err()
811 .map(OracleViolation::CancelDebt),
812 OracleStats {
813 entities_tracked: self.cancel_debt.get_statistics().tracked_queues,
814 events_recorded: self.cancel_debt.get_statistics().work_items_tracked as usize,
815 },
816 ),
817 OracleEntryReport::from_result(
818 "cancel_signal_ordering",
819 self.cancel_signal_ordering
820 .check(now)
821 .err()
822 .map(OracleViolation::CancelOrdering),
823 OracleStats {
824 entities_tracked: self.cancel_signal_ordering.get_statistics().tracked_signals,
825 events_recorded: self
826 .cancel_signal_ordering
827 .get_statistics()
828 .signals_processed as usize,
829 },
830 ),
831 OracleEntryReport::from_result(
832 "runtime_epoch",
833 self.runtime_epoch
834 .check(now)
835 .err()
836 .map(OracleViolation::RuntimeEpoch),
837 OracleStats {
838 entities_tracked: self.runtime_epoch.get_statistics().tracked_modules,
839 events_recorded: self.runtime_epoch.get_statistics().transitions_tracked
840 as usize,
841 },
842 ),
843 OracleEntryReport::from_result(
844 "channel_atomicity",
845 self.channel_atomicity
846 .check_for_violations()
847 .ok()
848 .and_then(|violations| violations.first().cloned())
849 .map(OracleViolation::ChannelAtomicity),
850 OracleStats {
851 entities_tracked: (self
852 .channel_atomicity
853 .statistics()
854 .total_reservations_created
855 + self.channel_atomicity.statistics().total_wakers_registered)
856 as usize,
857 events_recorded: (self
858 .channel_atomicity
859 .statistics()
860 .total_reservations_created
861 + self
862 .channel_atomicity
863 .statistics()
864 .total_reservations_committed
865 + self
866 .channel_atomicity
867 .statistics()
868 .total_reservations_aborted
869 + self.channel_atomicity.statistics().total_wakers_registered
870 + self.channel_atomicity.statistics().total_wakeups_expected
871 + self.channel_atomicity.statistics().total_wakeups_actual)
872 as usize,
873 },
874 ),
875 OracleEntryReport::from_result(
876 "waker_dedup",
877 self.waker_dedup
878 .check_for_violations()
879 .ok()
880 .and_then(|violations| violations.first().cloned())
881 .map(OracleViolation::WakerDedup),
882 OracleStats {
883 entities_tracked: self.waker_dedup.statistics().active_wakers as usize,
884 events_recorded: (self.waker_dedup.statistics().total_wakers_registered
885 + self.waker_dedup.statistics().total_wakers_woken
886 + self.waker_dedup.statistics().total_wakers_dropped)
887 as usize,
888 },
889 ),
890 OracleEntryReport::from_result(
891 "actor_leak",
892 self.actor_leak
893 .check(now)
894 .err()
895 .map(OracleViolation::ActorLeak),
896 OracleStats {
897 entities_tracked: self.actor_leak.actor_count(),
898 events_recorded: self.actor_leak.actor_count()
899 + self.actor_leak.stopped_count()
900 + self.actor_leak.closed_region_count(),
901 },
902 ),
903 OracleEntryReport::from_result(
904 "supervision",
905 self.supervision
906 .check(now)
907 .err()
908 .map(OracleViolation::Supervision),
909 OracleStats {
910 entities_tracked: self.supervision.failure_count()
911 + self.supervision.restart_count(),
912 events_recorded: self.supervision.failure_count()
913 + self.supervision.restart_count()
914 + self.supervision.escalation_count(),
915 },
916 ),
917 OracleEntryReport::from_result(
918 "mailbox",
919 self.mailbox.check(now).err().map(OracleViolation::Mailbox),
920 OracleStats {
921 entities_tracked: self.mailbox.mailbox_count(),
922 events_recorded: self.mailbox.mailbox_count(),
923 },
924 ),
925 OracleEntryReport::from_result(
926 "rref_access",
927 self.rref_access
928 .check()
929 .err()
930 .map(OracleViolation::RRefAccess),
931 OracleStats {
932 entities_tracked: self.rref_access.rref_count(),
933 events_recorded: self.rref_access.rref_count()
934 + self.rref_access.task_count()
935 + self.rref_access.closed_region_count(),
936 },
937 ),
938 OracleEntryReport::from_result(
939 "reply_linearity",
940 self.reply_linearity
941 .check()
942 .err()
943 .map(OracleViolation::ReplyLinearity),
944 OracleStats {
945 entities_tracked: self.reply_linearity.created_count(),
946 events_recorded: self.reply_linearity.created_count()
947 + self.reply_linearity.resolved_count(),
948 },
949 ),
950 OracleEntryReport::from_result(
951 "registry_lease",
952 self.registry_lease
953 .check()
954 .err()
955 .map(OracleViolation::RegistryLease),
956 OracleStats {
957 entities_tracked: self.registry_lease.acquired_count(),
958 events_recorded: self.registry_lease.acquired_count()
959 + self.registry_lease.resolved_count(),
960 },
961 ),
962 OracleEntryReport::from_result(
963 "down_order",
964 self.down_order
965 .check()
966 .err()
967 .map(OracleViolation::DownOrder),
968 OracleStats {
969 entities_tracked: self.down_order.monitor_count(),
970 events_recorded: self.down_order.down_count(),
971 },
972 ),
973 OracleEntryReport::from_result(
974 "supervisor_quiescence",
975 self.supervisor_quiescence
976 .check()
977 .err()
978 .map(OracleViolation::SupervisorQuiescence),
979 OracleStats {
980 entities_tracked: self.supervisor_quiescence.supervisor_count(),
981 events_recorded: self.supervisor_quiescence.child_count()
982 + self.supervisor_quiescence.closed_region_count(),
983 },
984 ),
985 #[cfg(feature = "messaging-fabric")]
986 self.fabric_publish.report_entry(),
987 #[cfg(feature = "messaging-fabric")]
988 self.fabric_reply.report_entry(),
989 #[cfg(feature = "messaging-fabric")]
990 self.fabric_quiescence.report_entry(),
991 #[cfg(feature = "messaging-fabric")]
992 self.fabric_redelivery.report_entry(),
993 ];
994
995 let total = entries.len();
996 let passed = entries.iter().filter(|e| e.passed).count();
997 let failed = total - passed;
998
999 OracleReport {
1000 entries,
1001 total,
1002 passed,
1003 failed,
1004 check_time_nanos: now.as_nanos(),
1005 }
1006 }
1007
1008 #[must_use]
1016 pub fn report_and_observe(&mut self, now: Time) -> OracleReport {
1017 let report = self.report(now);
1018 if let Some(ref mut monitor) = self.eprocess_monitor {
1019 monitor.observe_report(&report);
1020 }
1021 report
1022 }
1023
1024 #[must_use]
1027 pub fn eprocess_rejected_invariants(&self) -> Vec<String> {
1028 self.eprocess_monitor.as_ref().map_or_else(Vec::new, |m| {
1029 m.rejected_invariants()
1030 .into_iter()
1031 .map(String::from)
1032 .collect()
1033 })
1034 }
1035
1036 #[must_use]
1047 pub fn check_obligation_theory(
1048 &self,
1049 marking_events: &[MarkingEvent],
1050 ) -> Vec<ObligationTheoryViolation> {
1051 let mut violations = Vec::new();
1052
1053 let mut marking_analyzer = MarkingAnalyzer::new();
1055 let marking_result = marking_analyzer.analyze(marking_events);
1056 if !marking_result.is_safe() {
1057 for leak in &marking_result.leaks {
1058 violations.push(ObligationTheoryViolation::MarkingLeak {
1059 description: format!("VASS marking non-zero at region close: {leak:?}"),
1060 });
1061 }
1062 for invalid in &marking_result.invalid_transitions {
1063 violations.push(ObligationTheoryViolation::InvalidTransition {
1064 description: format!("Invalid marking transition: {invalid:?}"),
1065 });
1066 }
1067 }
1068
1069 let mut contract_checker = ContractChecker::new();
1073 let contract_result = contract_checker.check(marking_events);
1074 for violation in &contract_result.violations {
1075 violations.push(ObligationTheoryViolation::ContractViolation {
1076 description: format!("{violation:?}"),
1077 });
1078 }
1079
1080 let mut aliasing_prover = NoAliasingProver::new();
1082 let aliasing_result = aliasing_prover.check(marking_events);
1083 for counterexample in &aliasing_result.counterexamples {
1084 violations.push(ObligationTheoryViolation::AliasingViolation {
1085 description: format!("{counterexample:?}"),
1086 });
1087 }
1088
1089 violations
1090 }
1091}
1092
1093#[derive(Debug, Clone)]
1096pub enum ObligationTheoryViolation {
1097 MarkingLeak {
1099 description: String,
1101 },
1102 InvalidTransition {
1104 description: String,
1106 },
1107 ContractViolation {
1109 description: String,
1111 },
1112 AliasingViolation {
1114 description: String,
1116 },
1117}
1118
1119impl std::fmt::Display for ObligationTheoryViolation {
1120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1121 match self {
1122 Self::MarkingLeak { description } => write!(f, "Marking leak: {description}"),
1123 Self::InvalidTransition { description } => {
1124 write!(f, "Invalid transition: {description}")
1125 }
1126 Self::ContractViolation { description } => {
1127 write!(f, "Contract violation: {description}")
1128 }
1129 Self::AliasingViolation { description } => {
1130 write!(f, "Aliasing violation: {description}")
1131 }
1132 }
1133 }
1134}
1135
1136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1138pub struct OracleStats {
1139 pub entities_tracked: usize,
1141 pub events_recorded: usize,
1143}
1144
1145#[derive(Debug, Clone, Serialize, Deserialize)]
1147pub struct OracleEntryReport {
1148 pub invariant: String,
1150 pub passed: bool,
1152 #[serde(skip_serializing_if = "Option::is_none")]
1154 pub violation: Option<String>,
1155 pub stats: OracleStats,
1157}
1158
1159impl OracleEntryReport {
1160 fn from_result(
1161 invariant: &'static str,
1162 violation: Option<OracleViolation>,
1163 stats: OracleStats,
1164 ) -> Self {
1165 let passed = violation.is_none();
1166 let violation_text = violation.map(|violation| violation.to_string());
1167 crate::tracing_compat::info!(
1168 event = "oracle_check",
1169 invariant = invariant,
1170 passed,
1171 entities_tracked = stats.entities_tracked,
1172 events_recorded = stats.events_recorded,
1173 details = violation_text.as_deref().unwrap_or("clean"),
1174 "oracle_check"
1175 );
1176
1177 Self {
1178 invariant: invariant.to_owned(),
1179 passed,
1180 violation: violation_text,
1181 stats,
1182 }
1183 }
1184}
1185
1186pub trait Oracle {
1188 fn invariant_name(&self) -> &'static str;
1190
1191 fn violation(&self) -> Option<OracleViolation>;
1193
1194 fn stats(&self) -> OracleStats;
1196
1197 fn report_entry(&self) -> OracleEntryReport {
1199 OracleEntryReport::from_result(self.invariant_name(), self.violation(), self.stats())
1200 }
1201}
1202
1203#[derive(Debug, Clone, Serialize, Deserialize)]
1208pub struct OracleReport {
1209 pub entries: Vec<OracleEntryReport>,
1211 pub total: usize,
1213 pub passed: usize,
1215 pub failed: usize,
1217 pub check_time_nanos: u64,
1219}
1220
1221impl OracleReport {
1222 #[must_use]
1224 pub fn all_passed(&self) -> bool {
1225 self.failed == 0
1226 }
1227
1228 #[must_use]
1230 pub fn failures(&self) -> Vec<&OracleEntryReport> {
1231 self.entries.iter().filter(|e| !e.passed).collect()
1232 }
1233
1234 #[must_use]
1236 pub fn entry(&self, invariant: &str) -> Option<&OracleEntryReport> {
1237 self.entries
1238 .iter()
1239 .find(|e| e.invariant.as_str() == invariant)
1240 }
1241
1242 #[must_use]
1244 pub fn to_json(&self) -> serde_json::Value {
1245 serde_json::to_value(self).unwrap_or_default()
1246 }
1247
1248 #[must_use]
1250 pub fn to_text(&self) -> String {
1251 let mut out = String::new();
1252 let _ = writeln!(
1253 &mut out,
1254 "Oracle Report: {}/{} passed ({} failed)",
1255 self.passed, self.total, self.failed
1256 );
1257 let _ = writeln!(&mut out, "Check time: {}ns", self.check_time_nanos);
1258 let _ = writeln!(&mut out, "---");
1259 for entry in &self.entries {
1260 let status = if entry.passed { "PASS" } else { "FAIL" };
1261 let _ = write!(
1262 &mut out,
1263 "[{}] {} (tracked={}, events={})",
1264 status, entry.invariant, entry.stats.entities_tracked, entry.stats.events_recorded
1265 );
1266 if let Some(ref v) = entry.violation {
1267 let _ = write!(&mut out, " -- {v}");
1268 }
1269 let _ = writeln!(&mut out);
1270 }
1271 out
1272 }
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 use super::*;
1278 #[cfg(feature = "tracing-integration")]
1279 use parking_lot::Mutex;
1280 #[cfg(feature = "tracing-integration")]
1281 use std::collections::BTreeMap;
1282 #[cfg(feature = "tracing-integration")]
1283 use std::sync::Arc;
1284 #[cfg(feature = "tracing-integration")]
1285 use tracing::Subscriber;
1286 #[cfg(feature = "tracing-integration")]
1287 use tracing::field::{Field, Visit};
1288 #[cfg(feature = "tracing-integration")]
1289 use tracing_subscriber::layer::{Context, Layer};
1290 #[cfg(feature = "tracing-integration")]
1291 use tracing_subscriber::prelude::*;
1292 #[cfg(feature = "tracing-integration")]
1293 use tracing_subscriber::registry::LookupSpan;
1294
1295 #[cfg(feature = "tracing-integration")]
1296 #[derive(Debug, Clone, PartialEq, Eq)]
1297 struct RecordedEvent {
1298 fields: BTreeMap<String, String>,
1299 }
1300
1301 #[cfg(feature = "tracing-integration")]
1302 #[derive(Default)]
1303 struct EventFieldVisitor {
1304 fields: BTreeMap<String, String>,
1305 }
1306
1307 #[cfg(feature = "tracing-integration")]
1308 impl Visit for EventFieldVisitor {
1309 fn record_bool(&mut self, field: &Field, value: bool) {
1310 self.fields
1311 .insert(field.name().to_owned(), value.to_string());
1312 }
1313
1314 fn record_u64(&mut self, field: &Field, value: u64) {
1315 self.fields
1316 .insert(field.name().to_owned(), value.to_string());
1317 }
1318
1319 fn record_str(&mut self, field: &Field, value: &str) {
1320 self.fields
1321 .insert(field.name().to_owned(), value.to_owned());
1322 }
1323
1324 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
1325 self.fields
1326 .insert(field.name().to_owned(), format!("{value:?}"));
1327 }
1328 }
1329
1330 #[cfg(feature = "tracing-integration")]
1331 #[derive(Default)]
1332 struct EventRecorder {
1333 events: Arc<Mutex<Vec<RecordedEvent>>>,
1334 }
1335
1336 #[cfg(feature = "tracing-integration")]
1337 impl<S> Layer<S> for EventRecorder
1338 where
1339 S: Subscriber + for<'a> LookupSpan<'a>,
1340 {
1341 fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
1342 let mut visitor = EventFieldVisitor::default();
1343 event.record(&mut visitor);
1344 self.events.lock().push(RecordedEvent {
1345 fields: visitor.fields,
1346 });
1347 }
1348 }
1349
1350 fn init_test(name: &str) {
1351 crate::test_utils::init_test_logging();
1352 crate::test_phase!(name);
1353 }
1354
1355 #[test]
1356 fn oracle_suite_default_is_clean() {
1357 init_test("oracle_suite_default_is_clean");
1358 let mut suite = OracleSuite::new();
1359 let violations = suite.check_all(Time::ZERO);
1360 let empty = violations.is_empty();
1361 crate::assert_with_log!(empty, "suite clean", true, empty);
1362 crate::test_complete!("oracle_suite_default_is_clean");
1363 }
1364
1365 #[test]
1366 fn hydrate_temporal_from_state_replays_finalizer_history() {
1367 init_test("hydrate_temporal_from_state_replays_finalizer_history");
1368 let mut state = crate::runtime::RuntimeState::new();
1369 let region = state.create_root_region(crate::types::Budget::INFINITE);
1370
1371 state.now = Time::from_nanos(10);
1372 let registered = state.register_sync_finalizer(region, || {});
1373 crate::assert_with_log!(registered, "registered", true, registered);
1374
1375 state.now = Time::from_nanos(20);
1376 state.record_finalizer_close_for_test(region);
1377
1378 let mut suite = OracleSuite::new();
1379 suite.hydrate_temporal_from_state(&state, state.now);
1380
1381 let violation = suite
1382 .finalizer
1383 .check()
1384 .expect_err("missing finalizer run should survive report hydration");
1385 crate::assert_with_log!(
1386 violation.region == region,
1387 "region",
1388 region,
1389 violation.region
1390 );
1391 crate::assert_with_log!(
1392 violation.unrun_finalizers == vec![FinalizerId(0)],
1393 "unrun finalizers",
1394 vec![FinalizerId(0)],
1395 violation.unrun_finalizers
1396 );
1397 crate::test_complete!("hydrate_temporal_from_state_replays_finalizer_history");
1398 }
1399
1400 #[test]
1403 fn oracle_suite_debug() {
1404 let suite = OracleSuite::new();
1405 let dbg = format!("{suite:?}");
1406 assert!(dbg.contains("OracleSuite"));
1407 }
1408
1409 #[test]
1410 fn oracle_suite_reset_stays_clean() {
1411 let mut suite = OracleSuite::new();
1412 suite.reset();
1413 let violations = suite.check_all(Time::ZERO);
1414 assert!(violations.is_empty());
1415 }
1416
1417 #[test]
1418 fn oracle_suite_report_all_pass() {
1419 let mut suite = OracleSuite::new();
1420 let report = suite.report(Time::ZERO);
1421 assert!(report.all_passed());
1422 assert_eq!(report.failed, 0);
1423 assert_eq!(report.passed, report.total);
1424 assert!(report.failures().is_empty());
1425 }
1426
1427 #[test]
1428 fn oracle_report_debug_clone() {
1429 let mut suite = OracleSuite::new();
1430 let report = suite.report(Time::ZERO);
1431 let dbg = format!("{report:?}");
1432 assert!(dbg.contains("OracleReport"));
1433
1434 let cloned = report.clone();
1435 assert_eq!(cloned.total, report.total);
1436 }
1437
1438 #[test]
1439 fn oracle_report_to_json() {
1440 let mut suite = OracleSuite::new();
1441 let report = suite.report(Time::ZERO);
1442 let json = report.to_json();
1443 assert!(json.is_object());
1444 assert!(json["entries"].is_array());
1445 }
1446
1447 #[test]
1448 fn oracle_report_to_text() {
1449 let mut suite = OracleSuite::new();
1450 let report = suite.report(Time::ZERO);
1451 let text = report.to_text();
1452 assert!(text.contains("Oracle Report"));
1453 assert!(text.contains("PASS"));
1454 }
1455
1456 #[cfg(feature = "tracing-integration")]
1457 #[test]
1458 #[allow(clippy::significant_drop_tightening)]
1459 fn oracle_report_emits_structured_oracle_check_events() {
1460 let mut suite = OracleSuite::new();
1461 let events = Arc::new(Mutex::new(Vec::new()));
1462 let recorder = EventRecorder {
1463 events: events.clone(),
1464 };
1465 let subscriber = tracing_subscriber::registry().with(recorder);
1466
1467 let report = tracing::subscriber::with_default(subscriber, || suite.report(Time::ZERO));
1468 assert!(report.all_passed());
1469
1470 let events = events.lock();
1471 let task_leak_event = events.iter().find(|event| {
1472 event.fields.get("event").map(String::as_str) == Some("oracle_check")
1473 && event.fields.get("invariant").map(String::as_str) == Some("task_leak")
1474 });
1475 let task_leak_event = task_leak_event.expect("task_leak oracle_check should be emitted");
1476
1477 assert_eq!(
1478 task_leak_event.fields.get("passed").map(String::as_str),
1479 Some("true")
1480 );
1481 assert_eq!(
1482 task_leak_event.fields.get("details").map(String::as_str),
1483 Some("clean")
1484 );
1485 }
1486
1487 #[test]
1488 fn oracle_report_failure_helpers_surface_failed_entries() {
1489 let report = OracleReport {
1490 entries: vec![
1491 OracleEntryReport {
1492 invariant: "task_leak".to_string(),
1493 passed: true,
1494 violation: None,
1495 stats: OracleStats {
1496 entities_tracked: 2,
1497 events_recorded: 4,
1498 },
1499 },
1500 OracleEntryReport {
1501 invariant: "obligation_leak".to_string(),
1502 passed: false,
1503 violation: Some("Obligation leak: leaked obligation".to_string()),
1504 stats: OracleStats {
1505 entities_tracked: 3,
1506 events_recorded: 6,
1507 },
1508 },
1509 ],
1510 total: 2,
1511 passed: 1,
1512 failed: 1,
1513 check_time_nanos: 42,
1514 };
1515
1516 let failures = report.failures();
1517 assert_eq!(failures.len(), 1);
1518 assert_eq!(failures[0].invariant, "obligation_leak");
1519 assert!(!report.all_passed());
1520
1521 let text = report.to_text();
1522 assert!(text.contains("FAIL"));
1523 assert!(text.contains("Obligation leak: leaked obligation"));
1524 }
1525
1526 #[test]
1527 fn oracle_report_entry_lookup() {
1528 let mut suite = OracleSuite::new();
1529 let report = suite.report(Time::ZERO);
1530 let entry = report.entry("task_leak");
1531 assert!(entry.is_some());
1532 assert!(entry.unwrap().passed);
1533
1534 assert!(report.entry("nonexistent_oracle").is_none());
1535 }
1536
1537 #[test]
1538 fn oracle_stats_debug_clone_eq() {
1539 let stats = OracleStats {
1540 entities_tracked: 5,
1541 events_recorded: 10,
1542 };
1543 let dbg = format!("{stats:?}");
1544 assert!(dbg.contains("OracleStats"));
1545
1546 let cloned = stats.clone();
1547 assert_eq!(stats, cloned);
1548 }
1549
1550 #[test]
1551 fn oracle_stats_ne() {
1552 let a = OracleStats {
1553 entities_tracked: 5,
1554 events_recorded: 10,
1555 };
1556 let b = OracleStats {
1557 entities_tracked: 3,
1558 events_recorded: 10,
1559 };
1560 assert_ne!(a, b);
1561 }
1562
1563 #[test]
1564 fn oracle_entry_report_debug_clone() {
1565 let entry = OracleEntryReport {
1566 invariant: "test".to_owned(),
1567 passed: true,
1568 violation: None,
1569 stats: OracleStats {
1570 entities_tracked: 0,
1571 events_recorded: 0,
1572 },
1573 };
1574 let dbg = format!("{entry:?}");
1575 assert!(dbg.contains("OracleEntryReport"));
1576
1577 let cloned = entry;
1578 assert_eq!(cloned.invariant, "test");
1579 assert!(cloned.passed);
1580 }
1581
1582 #[test]
1583 fn oracle_entry_report_with_violation() {
1584 let entry = OracleEntryReport {
1585 invariant: "failing".to_owned(),
1586 passed: false,
1587 violation: Some("something leaked".to_owned()),
1588 stats: OracleStats {
1589 entities_tracked: 1,
1590 events_recorded: 1,
1591 },
1592 };
1593 assert!(!entry.passed);
1594 assert!(entry.violation.as_deref().unwrap().contains("leaked"));
1595 }
1596
1597 #[test]
1598 fn oracle_violation_debug() {
1599 let mut suite = OracleSuite::new();
1604 let violations = suite.check_all(Time::ZERO);
1605 assert!(violations.is_empty());
1607 }
1608
1609 #[test]
1610 fn oracle_violation_error_trait() {
1611 fn assert_error_impl<T: std::error::Error>() {}
1615 assert_error_impl::<OracleViolation>();
1616 }
1617}