1use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
60use crate::trace::independence::independent;
61use crate::util::DetHasher;
62use serde::{Deserialize, Serialize};
63use std::hash::{Hash, Hasher};
64
65#[derive(Debug)]
67pub struct FoataTrace {
68 layers: Vec<Vec<TraceEvent>>,
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
77pub struct TraceEventKey {
78 pub kind: u8,
80 pub primary: u64,
82 pub secondary: u64,
84 pub tertiary: u64,
86}
87
88impl TraceEventKey {
89 #[must_use]
94 pub const fn new(kind: u8, primary: u64, secondary: u64, tertiary: u64) -> Self {
95 Self {
96 kind,
97 primary,
98 secondary,
99 tertiary,
100 }
101 }
102}
103
104impl FoataTrace {
105 #[must_use]
107 pub fn depth(&self) -> usize {
108 self.layers.len()
109 }
110
111 #[must_use]
113 pub fn len(&self) -> usize {
114 self.layers.iter().map(Vec::len).sum()
115 }
116
117 #[must_use]
119 pub fn is_empty(&self) -> bool {
120 self.layers.is_empty()
121 }
122
123 #[must_use]
125 pub fn layers(&self) -> &[Vec<TraceEvent>] {
126 &self.layers
127 }
128
129 #[must_use]
131 pub fn flatten(&self) -> Vec<TraceEvent> {
132 self.layers.iter().flat_map(|l| l.iter().cloned()).collect()
133 }
134
135 #[must_use]
140 pub fn fingerprint(&self) -> u64 {
141 let mut hasher = DetHasher::for_lab();
142 for (layer_idx, layer) in self.layers.iter().enumerate() {
143 layer_idx.hash(&mut hasher);
144 layer.len().hash(&mut hasher);
145 for event in layer {
146 event_hash_key(event).hash(&mut hasher);
147 }
148 }
149 hasher.finish()
150 }
151}
152
153#[derive(Debug)]
167pub struct TraceMonoid {
168 canonical: FoataTrace,
170 fingerprint: u64,
172}
173
174impl TraceMonoid {
175 #[must_use]
177 pub fn identity() -> Self {
178 let canonical = FoataTrace { layers: vec![] };
179 let fingerprint = canonical.fingerprint();
180 Self {
181 canonical,
182 fingerprint,
183 }
184 }
185
186 #[must_use]
192 pub fn from_events(events: &[TraceEvent]) -> Self {
193 let canonical = canonicalize(events);
194 let fingerprint = canonical.fingerprint();
195 Self {
196 canonical,
197 fingerprint,
198 }
199 }
200
201 #[must_use]
211 pub fn concat(&self, other: &Self) -> Self {
212 if self.is_identity() {
213 return Self {
214 canonical: FoataTrace {
215 layers: other.canonical.layers.clone(),
216 },
217 fingerprint: other.fingerprint,
218 };
219 }
220 if other.is_identity() {
221 return Self {
222 canonical: FoataTrace {
223 layers: self.canonical.layers.clone(),
224 },
225 fingerprint: self.fingerprint,
226 };
227 }
228
229 let mut combined = self.canonical.flatten();
230 combined.extend(other.canonical.flatten());
231 Self::from_events(&combined)
232 }
233
234 #[must_use]
236 pub fn is_identity(&self) -> bool {
237 self.canonical.is_empty()
238 }
239
240 #[must_use]
242 pub fn canonical_form(&self) -> &FoataTrace {
243 &self.canonical
244 }
245
246 #[must_use]
248 pub fn class_fingerprint(&self) -> u64 {
249 self.fingerprint
250 }
251
252 #[must_use]
254 pub fn len(&self) -> usize {
255 self.canonical.len()
256 }
257
258 #[must_use]
260 pub fn is_empty(&self) -> bool {
261 self.canonical.is_empty()
262 }
263
264 #[must_use]
269 pub fn critical_path_length(&self) -> usize {
270 self.canonical.depth()
271 }
272
273 #[must_use]
278 pub fn max_parallelism(&self) -> usize {
279 self.canonical
280 .layers
281 .iter()
282 .map(Vec::len)
283 .max()
284 .unwrap_or(0)
285 }
286
287 #[must_use]
293 pub fn equivalent(&self, other: &Self) -> bool {
294 self.fingerprint == other.fingerprint
295 }
296
297 #[must_use]
303 pub fn equivalent_exact(&self, other: &Self) -> bool {
304 if self.canonical.depth() != other.canonical.depth() {
305 return false;
306 }
307 for (la, lb) in self
308 .canonical
309 .layers
310 .iter()
311 .zip(other.canonical.layers.iter())
312 {
313 if la.len() != lb.len() {
314 return false;
315 }
316 for (ea, eb) in la.iter().zip(lb.iter()) {
317 if !semantically_equal_event(ea, eb) {
318 return false;
319 }
320 }
321 }
322 true
323 }
324}
325
326impl PartialEq for TraceMonoid {
327 fn eq(&self, other: &Self) -> bool {
328 if self.fingerprint != other.fingerprint {
330 return false;
331 }
332 self.equivalent_exact(other)
334 }
335}
336
337impl Eq for TraceMonoid {}
338
339#[must_use]
355pub fn canonicalize(events: &[TraceEvent]) -> FoataTrace {
356 let n = events.len();
357 if n == 0 {
358 return FoataTrace { layers: vec![] };
359 }
360
361 let mut layer_of = vec![0usize; n];
365 let mut max_layer = 0usize;
366
367 for j in 1..n {
368 for i in 0..j {
369 if !independent(&events[i], &events[j]) {
370 layer_of[j] = layer_of[j].max(layer_of[i] + 1);
371 }
372 }
373 max_layer = max_layer.max(layer_of[j]);
374 }
375
376 let mut layers: Vec<Vec<TraceEvent>> = vec![vec![]; max_layer + 1];
378 for (idx, event) in events.iter().enumerate() {
379 layers[layer_of[idx]].push(event.clone());
380 }
381
382 for layer in &mut layers {
384 layer.sort_by_cached_key(event_total_order_key);
385 }
386
387 FoataTrace { layers }
388}
389
390#[must_use]
395pub fn trace_fingerprint(events: &[TraceEvent]) -> u64 {
396 let n = events.len();
397 if n == 0 {
398 return FoataTrace { layers: vec![] }.fingerprint();
400 }
401
402 let mut layer_of = vec![0usize; n];
404 let mut max_layer = 0usize;
405
406 for j in 1..n {
407 for i in 0..j {
408 if !independent(&events[i], &events[j]) {
409 layer_of[j] = layer_of[j].max(layer_of[i] + 1);
410 }
411 }
412 max_layer = max_layer.max(layer_of[j]);
413 }
414
415 let mut layer_indices: Vec<Vec<usize>> = vec![vec![]; max_layer + 1];
417 for (idx, &layer) in layer_of.iter().enumerate() {
418 layer_indices[layer].push(idx);
419 }
420
421 let mut hasher = DetHasher::for_lab();
422 for (layer_idx, indices) in layer_indices.iter_mut().enumerate() {
423 indices.sort_by_cached_key(|&idx| event_total_order_key(&events[idx]));
424 layer_idx.hash(&mut hasher);
425 indices.len().hash(&mut hasher);
426 for &idx in indices.iter() {
427 event_hash_key(&events[idx]).hash(&mut hasher);
428 }
429 }
430 hasher.finish()
431}
432
433fn kind_discriminant(kind: TraceEventKind) -> u8 {
439 match kind {
440 TraceEventKind::Spawn => 0,
441 TraceEventKind::Schedule => 1,
442 TraceEventKind::Yield => 2,
443 TraceEventKind::Wake => 3,
444 TraceEventKind::Poll => 4,
445 TraceEventKind::Complete => 5,
446 TraceEventKind::CancelRequest => 6,
447 TraceEventKind::CancelAck => 7,
448 TraceEventKind::RegionCloseBegin => 8,
449 TraceEventKind::RegionCloseComplete => 9,
450 TraceEventKind::RegionCreated => 10,
451 TraceEventKind::RegionCancelled => 11,
452 TraceEventKind::ObligationReserve => 12,
453 TraceEventKind::ObligationCommit => 13,
454 TraceEventKind::ObligationAbort => 14,
455 TraceEventKind::ObligationLeak => 15,
456 TraceEventKind::TimeAdvance => 16,
457 TraceEventKind::TimerScheduled => 17,
458 TraceEventKind::TimerFired => 18,
459 TraceEventKind::TimerCancelled => 19,
460 TraceEventKind::IoRequested => 20,
461 TraceEventKind::IoReady => 21,
462 TraceEventKind::IoResult => 22,
463 TraceEventKind::IoError => 23,
464 TraceEventKind::RngSeed => 24,
465 TraceEventKind::RngValue => 25,
466 TraceEventKind::Checkpoint => 26,
467 TraceEventKind::FuturelockDetected => 27,
468 TraceEventKind::ChaosInjection => 28,
469 TraceEventKind::UserTrace => 29,
470 TraceEventKind::MonitorCreated => 30,
471 TraceEventKind::MonitorDropped => 31,
472 TraceEventKind::DownDelivered => 32,
473 TraceEventKind::LinkCreated => 33,
474 TraceEventKind::LinkDropped => 34,
475 TraceEventKind::ExitDelivered => 35,
476 TraceEventKind::WorkerCancelRequested => 36,
478 TraceEventKind::WorkerCancelAcknowledged => 37,
479 TraceEventKind::WorkerDrainStarted => 38,
480 TraceEventKind::WorkerDrainCompleted => 39,
481 TraceEventKind::WorkerFinalizeCompleted => 40,
482 TraceEventKind::TaskSpawnEnqueued => 41,
483 TraceEventKind::TaskAdmitted => 42,
484 TraceEventKind::BudgetInstalled => 43,
487 TraceEventKind::BudgetConsumed => 44,
488 }
489}
490
491fn pack_arena(idx: crate::util::ArenaIndex) -> u64 {
493 (u64::from(idx.index()) << 32) | u64::from(idx.generation())
494}
495
496fn event_sort_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
502 let k = kind_discriminant(event.kind);
503 match &event.data {
504 TraceData::Task { task, region }
505 | TraceData::Cancel { task, region, .. }
506 | TraceData::Budget { task, region, .. } => {
507 (k, pack_arena(task.0), pack_arena(region.0), 0)
508 }
509 TraceData::Futurelock {
510 task,
511 region,
512 idle_steps,
513 held,
514 } => {
515 let mut hasher = DetHasher::for_lab();
526 idle_steps.hash(&mut hasher);
527 held.hash(&mut hasher);
528 (k, pack_arena(task.0), pack_arena(region.0), hasher.finish())
529 }
530 TraceData::Region { region, parent } => (
531 k,
532 pack_arena(region.0),
533 parent.map_or(0, |p| pack_arena(p.0)),
534 0,
535 ),
536 TraceData::RegionCancel { region, .. } => (k, pack_arena(region.0), 0, 0),
537 TraceData::Obligation {
538 obligation,
539 task,
540 region,
541 ..
542 } => (
543 k,
544 pack_arena(obligation.0),
545 pack_arena(task.0),
546 pack_arena(region.0),
547 ),
548 TraceData::Time { old, new } => (k, old.as_nanos(), new.as_nanos(), 0),
549 TraceData::Timer { timer_id, .. } => (k, *timer_id, 0, 0),
550 TraceData::IoRequested { token, .. } | TraceData::IoReady { token, .. } => {
551 (k, *token, 0, 0)
552 }
553 TraceData::IoResult { token, bytes } => {
554 let bytes_key = (*bytes).cast_unsigned() ^ (1u64 << 63);
556 (k, *token, bytes_key, 0)
557 }
558 TraceData::IoError { token, kind } => (k, *token, u64::from(*kind), 0),
559 TraceData::RngSeed { seed } => (k, *seed, 0, 0),
560 TraceData::RngValue { value } => (k, *value, 0, 0),
561 TraceData::Checkpoint {
562 sequence,
563 active_tasks,
564 active_regions,
565 } => (
566 k,
567 *sequence,
568 u64::from(*active_tasks),
569 u64::from(*active_regions),
570 ),
571 TraceData::Chaos { task, .. } => {
572 let task_key = task.map_or(0, |t| pack_arena(t.0));
573 (k, task_key, 0, 0)
574 }
575 TraceData::Message(msg) => {
576 let mut h = DetHasher::for_lab();
577 msg.hash(&mut h);
578 (k, h.finish(), 0, 0)
579 }
580 TraceData::Monitor {
581 monitor_ref,
582 watcher,
583 monitored,
584 ..
585 } => (
586 k,
587 *monitor_ref,
588 pack_arena(watcher.0),
589 pack_arena(monitored.0),
590 ),
591 TraceData::Down {
592 monitor_ref,
593 monitored,
594 completion_vt,
595 ..
596 } => (
597 k,
598 completion_vt.as_nanos(),
599 pack_arena(monitored.0),
600 *monitor_ref,
601 ),
602 TraceData::Link {
603 link_ref,
604 task_a,
605 task_b,
606 ..
607 } => (k, *link_ref, pack_arena(task_a.0), pack_arena(task_b.0)),
608 TraceData::Exit {
609 link_ref,
610 from,
611 failure_vt,
612 ..
613 } => (k, failure_vt.as_nanos(), pack_arena(from.0), *link_ref),
614 TraceData::Worker {
615 job_id,
616 task,
617 region,
618 ..
619 } => (k, *job_id, pack_arena(task.0), pack_arena(region.0)),
620 TraceData::None => (k, 0, 0, 0),
621 }
622}
623
624#[must_use]
626pub fn trace_event_key(event: &TraceEvent) -> TraceEventKey {
627 let (kind, primary, secondary, tertiary) = event_sort_key(event);
628 TraceEventKey::new(kind, primary, secondary, tertiary)
629}
630
631fn event_total_order_key(event: &TraceEvent) -> ((u8, u64, u64, u64), Vec<u8>) {
633 (event_sort_key(event), event_total_order_tiebreak(event))
634}
635
636fn event_total_order_tiebreak(event: &TraceEvent) -> Vec<u8> {
643 serde_json::to_vec(event).unwrap_or_else(|_| format!("{event:?}").into_bytes())
644}
645
646fn semantically_equal_event(a: &TraceEvent, b: &TraceEvent) -> bool {
648 a.version == b.version && a.kind == b.kind && a.data == b.data
649}
650
651fn event_hash_key(event: &TraceEvent) -> (u8, u64, u64, u64) {
656 event_sort_key(event)
657}
658
659#[cfg(test)]
660mod tests {
661 #![allow(
662 clippy::pedantic,
663 clippy::nursery,
664 clippy::expect_fun_call,
665 clippy::map_unwrap_or,
666 clippy::cast_possible_wrap,
667 clippy::future_not_send
668 )]
669 use super::*;
670 use crate::monitor::DownReason;
671 use crate::record::{ObligationAbortReason, ObligationKind};
672 use crate::types::{CancelReason, ObligationId, RegionId, TaskId, Time};
673 use insta::assert_json_snapshot;
674 use serde::Serialize;
675
676 #[derive(Debug, Serialize)]
677 struct CanonicalTraceSnapshot {
678 depth: usize,
679 len: usize,
680 layers: Vec<Vec<CanonicalTraceEventSnapshot>>,
681 }
682
683 #[derive(Debug, Serialize)]
684 struct CanonicalTraceEventSnapshot {
685 seq: u64,
686 time_ns: u64,
687 kind: &'static str,
688 key: TraceEventKey,
689 data: String,
690 }
691
692 fn tid(n: u32) -> TaskId {
693 TaskId::new_for_test(n, 0)
694 }
695
696 fn rid(n: u32) -> RegionId {
697 RegionId::new_for_test(n, 0)
698 }
699
700 fn oid(n: u32) -> ObligationId {
701 ObligationId::new_for_test(n, 0)
702 }
703
704 fn canonical_trace_snapshot(events: &[TraceEvent]) -> CanonicalTraceSnapshot {
705 let foata = canonicalize(events);
706 CanonicalTraceSnapshot {
707 depth: foata.depth(),
708 len: foata.len(),
709 layers: foata
710 .layers()
711 .iter()
712 .map(|layer| layer.iter().map(snapshot_event).collect())
713 .collect(),
714 }
715 }
716
717 fn snapshot_event(event: &TraceEvent) -> CanonicalTraceEventSnapshot {
718 CanonicalTraceEventSnapshot {
719 seq: event.seq,
720 time_ns: event.time.as_nanos(),
721 kind: event.kind.stable_name(),
722 key: trace_event_key(event),
723 data: snapshot_event_data(&event.data),
724 }
725 }
726
727 fn snapshot_event_data(data: &TraceData) -> String {
728 match data {
729 TraceData::None => "none".to_string(),
730 TraceData::Task { task, region } => {
731 format!("task={} region={}", task.as_u64(), region.as_u64())
732 }
733 TraceData::Region { region, parent } => format!(
734 "region={} parent={}",
735 region.as_u64(),
736 parent.map_or_else(|| "none".to_string(), |region| region.as_u64().to_string())
737 ),
738 TraceData::Obligation {
739 obligation,
740 task,
741 region,
742 kind,
743 state,
744 duration_ns,
745 abort_reason,
746 } => format!(
747 "obligation={} task={} region={} kind={} state={state:?} duration_ns={} abort_reason={}",
748 pack_arena(obligation.arena_index()),
749 task.as_u64(),
750 region.as_u64(),
751 kind.as_str(),
752 duration_ns.map_or_else(|| "none".to_string(), |value| value.to_string()),
753 abort_reason
754 .map_or_else(|| "none".to_string(), |reason| reason.as_str().to_string())
755 ),
756 TraceData::Cancel {
757 task,
758 region,
759 reason,
760 } => format!(
761 "task={} region={} reason={reason}",
762 task.as_u64(),
763 region.as_u64()
764 ),
765 TraceData::RegionCancel { region, reason } => {
766 format!("region={} reason={reason}", region.as_u64())
767 }
768 TraceData::Time { old, new } => {
769 format!("old={} new={}", old.as_nanos(), new.as_nanos())
770 }
771 TraceData::Timer { timer_id, deadline } => format!(
772 "timer_id={timer_id} deadline={}",
773 deadline.map_or_else(|| "none".to_string(), |time| time.as_nanos().to_string())
774 ),
775 TraceData::Checkpoint {
776 sequence,
777 active_tasks,
778 active_regions,
779 } => format!(
780 "sequence={sequence} active_tasks={active_tasks} active_regions={active_regions}"
781 ),
782 other => format!("{other:?}"),
783 }
784 }
785
786 #[test]
787 fn trace_canonicalize_happy_path_snapshot() {
788 let events = [
789 TraceEvent::region_created(1, Time::ZERO, rid(1), None),
790 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
791 TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
792 TraceEvent::poll(4, Time::from_nanos(5), tid(1), rid(1)),
793 TraceEvent::complete(5, Time::from_nanos(9), tid(1), rid(1)),
794 TraceEvent::complete(6, Time::from_nanos(11), tid(2), rid(1)),
795 ];
796
797 assert_json_snapshot!(
798 "trace_canonicalize_happy_path",
799 canonical_trace_snapshot(&events)
800 );
801 }
802
803 #[test]
804 fn trace_canonicalize_empty_snapshot() {
805 assert_json_snapshot!("trace_canonicalize_empty", canonical_trace_snapshot(&[]));
806 }
807
808 #[test]
809 fn trace_canonicalize_max_budget_snapshot() {
810 let events = [
811 TraceEvent::region_created(1, Time::ZERO, rid(9), None),
812 TraceEvent::spawn(2, Time::from_nanos(1), tid(9), rid(9)),
813 TraceEvent::obligation_reserve(
814 3,
815 Time::from_nanos(2),
816 oid(7),
817 tid(9),
818 rid(9),
819 ObligationKind::Lease,
820 ),
821 TraceEvent::time_advance(4, Time::from_nanos(3), Time::ZERO, Time::MAX),
822 TraceEvent::timer_scheduled(5, Time::MAX, u64::MAX, Time::MAX),
823 TraceEvent::obligation_commit(
824 6,
825 Time::MAX,
826 oid(7),
827 tid(9),
828 rid(9),
829 ObligationKind::Lease,
830 u64::MAX,
831 ),
832 TraceEvent::checkpoint(7, Time::MAX, u64::MAX, u32::MAX, u32::MAX),
833 ];
834
835 assert_json_snapshot!(
836 "trace_canonicalize_max_budget",
837 canonical_trace_snapshot(&events)
838 );
839 }
840
841 #[test]
842 fn trace_canonicalize_cancellation_chain_snapshot() {
843 let cancel = CancelReason::timeout();
844 let events = [
845 TraceEvent::region_created(1, Time::ZERO, rid(3), None),
846 TraceEvent::spawn(2, Time::from_nanos(1), tid(4), rid(3)),
847 TraceEvent::obligation_reserve(
848 3,
849 Time::from_nanos(2),
850 oid(4),
851 tid(4),
852 rid(3),
853 ObligationKind::SendPermit,
854 ),
855 TraceEvent::cancel_request(4, Time::from_nanos(3), tid(4), rid(3), cancel.clone()),
856 TraceEvent::new(
857 5,
858 Time::from_nanos(4),
859 TraceEventKind::CancelAck,
860 TraceData::Cancel {
861 task: tid(4),
862 region: rid(3),
863 reason: cancel.clone(),
864 },
865 ),
866 TraceEvent::obligation_abort(
867 6,
868 Time::from_nanos(5),
869 oid(4),
870 tid(4),
871 rid(3),
872 ObligationKind::SendPermit,
873 17,
874 ObligationAbortReason::Cancel,
875 ),
876 TraceEvent::region_cancelled(7, Time::from_nanos(6), rid(3), cancel),
877 ];
878
879 assert_json_snapshot!(
880 "trace_canonicalize_cancellation_chain",
881 canonical_trace_snapshot(&events)
882 );
883 }
884
885 #[test]
888 fn empty_trace() {
889 let foata = canonicalize(&[]);
890 assert!(foata.is_empty());
891 assert_eq!(foata.depth(), 0);
892 assert_eq!(foata.len(), 0);
893 }
894
895 #[test]
896 fn single_event() {
897 let events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
898 let foata = canonicalize(&events);
899 assert_eq!(foata.depth(), 1);
900 assert_eq!(foata.len(), 1);
901 }
902
903 #[test]
904 fn all_independent_events_in_one_layer() {
905 let events = [
907 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
908 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
909 TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
910 ];
911 let foata = canonicalize(&events);
912 assert_eq!(foata.depth(), 1);
913 assert_eq!(foata.layers()[0].len(), 3);
914 }
915
916 #[test]
917 fn chain_of_dependent_events() {
918 let events = [
920 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
921 TraceEvent::poll(2, Time::ZERO, tid(1), rid(1)),
922 TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
923 ];
924 let foata = canonicalize(&events);
925 assert_eq!(foata.depth(), 3);
926 assert_eq!(foata.layers()[0].len(), 1);
927 assert_eq!(foata.layers()[1].len(), 1);
928 assert_eq!(foata.layers()[2].len(), 1);
929 }
930
931 #[test]
932 fn diamond_dependency() {
933 let events = [
946 TraceEvent::region_created(1, Time::ZERO, rid(1), None),
947 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
948 TraceEvent::spawn(3, Time::ZERO, tid(2), rid(1)),
949 TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
950 TraceEvent::complete(5, Time::ZERO, tid(2), rid(1)),
951 ];
952 let foata = canonicalize(&events);
953 assert_eq!(foata.depth(), 3);
954 assert_eq!(foata.layers()[0].len(), 1); assert_eq!(foata.layers()[1].len(), 2); assert_eq!(foata.layers()[2].len(), 2); }
958
959 #[test]
962 fn swapped_independent_events_same_fingerprint() {
963 let trace_a = [
964 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
965 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
966 ];
967 let trace_b = [
968 TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
969 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
970 ];
971 let fp_a = trace_fingerprint(&trace_a);
972 let fp_b = trace_fingerprint(&trace_b);
973 assert_eq!(fp_a, fp_b);
974 }
975
976 #[test]
977 fn swapped_independent_events_same_canonical_form() {
978 let trace_a = [
979 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
980 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
981 ];
982 let trace_b = [
983 TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
984 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
985 ];
986 let foata_a = canonicalize(&trace_a);
987 let foata_b = canonicalize(&trace_b);
988 assert_eq!(foata_a.depth(), foata_b.depth());
989 assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
990 }
991
992 #[test]
993 fn different_dependent_orders_different_fingerprints() {
994 let trace_a = [
996 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
997 TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
998 ];
999 let trace_b = [
1000 TraceEvent::complete(1, Time::ZERO, tid(1), rid(1)),
1001 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1002 ];
1003 let fp_a = trace_fingerprint(&trace_a);
1004 let fp_b = trace_fingerprint(&trace_b);
1005 assert_ne!(fp_a, fp_b);
1007 }
1008
1009 #[test]
1012 fn down_delivered_canonicalizes_by_completion_vt_monitored_monitor_ref() {
1013 let e0 = TraceEvent::down_delivered(
1014 1,
1015 Time::ZERO,
1016 1,
1017 tid(10),
1018 tid(3),
1019 Time::from_nanos(4),
1020 DownReason::Normal,
1021 );
1022 let e1 = TraceEvent::down_delivered(
1023 2,
1024 Time::ZERO,
1025 10,
1026 tid(11),
1027 tid(1),
1028 Time::from_nanos(5),
1029 DownReason::Normal,
1030 );
1031 let e2 = TraceEvent::down_delivered(
1032 3,
1033 Time::ZERO,
1034 9,
1035 tid(12),
1036 tid(1),
1037 Time::from_nanos(5),
1038 DownReason::Normal,
1039 );
1040 let e3 = TraceEvent::down_delivered(
1041 4,
1042 Time::ZERO,
1043 1,
1044 tid(13),
1045 tid(2),
1046 Time::from_nanos(5),
1047 DownReason::Normal,
1048 );
1049
1050 let trace_a = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
1054 let trace_b = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
1055
1056 let foata_a = canonicalize(&trace_a);
1057 let foata_b = canonicalize(&trace_b);
1058 assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
1059 assert_eq!(foata_a.depth(), 1);
1060
1061 let flat = foata_a.flatten();
1062 assert_eq!(flat.len(), 4);
1063 assert_eq!(flat[0], e0);
1064 assert_eq!(flat[1], e2);
1065 assert_eq!(flat[2], e1);
1066 assert_eq!(flat[3], e3);
1067 }
1068
1069 #[test]
1070 fn exit_delivered_canonicalizes_by_failure_vt_from_link_ref() {
1071 let e0 = TraceEvent::exit_delivered(
1072 1,
1073 Time::ZERO,
1074 1,
1075 tid(2),
1076 tid(20),
1077 Time::from_nanos(9),
1078 DownReason::Error("boom".to_string()),
1079 );
1080 let e1 = TraceEvent::exit_delivered(
1081 2,
1082 Time::ZERO,
1083 10,
1084 tid(1),
1085 tid(21),
1086 Time::from_nanos(10),
1087 DownReason::Error("boom".to_string()),
1088 );
1089 let e2 = TraceEvent::exit_delivered(
1090 3,
1091 Time::ZERO,
1092 9,
1093 tid(1),
1094 tid(22),
1095 Time::from_nanos(10),
1096 DownReason::Error("boom".to_string()),
1097 );
1098 let e3 = TraceEvent::exit_delivered(
1099 4,
1100 Time::ZERO,
1101 1,
1102 tid(3),
1103 tid(23),
1104 Time::from_nanos(10),
1105 DownReason::Error("boom".to_string()),
1106 );
1107
1108 let trace_a = [e3.clone(), e2.clone(), e1.clone(), e0.clone()];
1111 let trace_b = [e0.clone(), e1.clone(), e2.clone(), e3.clone()];
1112
1113 let foata_a = canonicalize(&trace_a);
1114 let foata_b = canonicalize(&trace_b);
1115 assert_eq!(foata_a.fingerprint(), foata_b.fingerprint());
1116 assert_eq!(foata_a.depth(), 1);
1117
1118 let flat = foata_a.flatten();
1119 assert_eq!(flat.len(), 4);
1120 assert_eq!(flat[0], e0);
1121 assert_eq!(flat[1], e2);
1122 assert_eq!(flat[2], e1);
1123 assert_eq!(flat[3], e3);
1124 }
1125
1126 #[test]
1129 fn three_independent_events_all_permutations_same_fingerprint() {
1130 let e1 = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
1131 let e2 = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
1132 let e3 = TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3));
1133
1134 let permutations: Vec<Vec<TraceEvent>> = vec![
1135 vec![e1.clone(), e2.clone(), e3.clone()],
1136 vec![e1.clone(), e3.clone(), e2.clone()],
1137 vec![e2.clone(), e1.clone(), e3.clone()],
1138 vec![e2.clone(), e3.clone(), e1.clone()],
1139 vec![e3.clone(), e1.clone(), e2.clone()],
1140 vec![e3, e2, e1],
1141 ];
1142
1143 let fp0 = trace_fingerprint(&permutations[0]);
1144 for (i, perm) in permutations.iter().enumerate() {
1145 let fp = trace_fingerprint(perm);
1146 assert_eq!(fp, fp0, "Permutation {i} has different fingerprint");
1147 }
1148 }
1149
1150 #[test]
1153 fn mixed_trace_canonical_form() {
1154 let events = [
1158 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1159 TraceEvent::time_advance(2, Time::ZERO, Time::ZERO, Time::from_nanos(100)),
1160 TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1161 TraceEvent::timer_fired(4, Time::ZERO, 1),
1162 ];
1163 let foata = canonicalize(&events);
1164 assert_eq!(foata.depth(), 2);
1168 assert_eq!(foata.layers()[0].len(), 3); assert_eq!(foata.layers()[1].len(), 1); }
1171
1172 #[test]
1175 fn intra_layer_ordering_is_deterministic() {
1176 let events = [
1177 TraceEvent::spawn(1, Time::ZERO, tid(3), rid(3)),
1178 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1179 TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1180 ];
1181 let foata = canonicalize(&events);
1182 assert_eq!(foata.depth(), 1);
1183
1184 let layer = &foata.layers()[0];
1187 let keys: Vec<_> = layer.iter().map(event_sort_key).collect();
1188 assert!(keys.windows(2).all(|w| w[0] <= w[1]));
1189 }
1190
1191 #[test]
1192 fn equal_canonical_keys_still_have_total_sort_order() {
1193 let events = (0..16)
1194 .map(|idx| {
1195 TraceEvent::new(
1196 idx,
1197 Time::from_nanos(idx),
1198 TraceEventKind::UserTrace,
1199 TraceData::Message("same canonical message".to_string()),
1200 )
1201 })
1202 .collect::<Vec<_>>();
1203
1204 let foata = canonicalize(&events);
1205 assert_eq!(foata.depth(), 1);
1206 assert_eq!(foata.len(), events.len());
1207
1208 let fingerprint = trace_fingerprint(&events);
1209 assert_ne!(fingerprint, 0);
1210 }
1211
1212 #[test]
1215 fn fingerprint_matches_foata_fingerprint() {
1216 let events = [
1217 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1218 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1219 TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1220 ];
1221 let foata = canonicalize(&events);
1222 let direct = trace_fingerprint(&events);
1223 assert_eq!(foata.fingerprint(), direct);
1224 }
1225
1226 #[test]
1227 fn empty_trace_fingerprint_matches_identity() {
1228 let id = TraceMonoid::identity();
1229 let empty = TraceMonoid::from_events(&[]);
1230 assert_eq!(trace_fingerprint(&[]), id.class_fingerprint());
1231 assert_eq!(id.class_fingerprint(), empty.class_fingerprint());
1232 assert!(id.equivalent(&empty));
1233 }
1234
1235 #[test]
1238 fn depth_reflects_critical_path() {
1239 let events = [
1243 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1244 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1245 TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1246 TraceEvent::complete(4, Time::ZERO, tid(2), rid(2)),
1247 ];
1248 let foata = canonicalize(&events);
1249 assert_eq!(foata.depth(), 2);
1250 assert_eq!(foata.layers()[0].len(), 2); assert_eq!(foata.layers()[1].len(), 2); }
1253
1254 #[test]
1257 fn flatten_preserves_event_count() {
1258 let events = [
1259 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1260 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1261 TraceEvent::complete(3, Time::ZERO, tid(1), rid(1)),
1262 ];
1263 let foata = canonicalize(&events);
1264 assert_eq!(foata.flatten().len(), events.len());
1265 }
1266
1267 #[test]
1270 fn monoid_identity_left() {
1271 let events = [
1272 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1273 TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1274 ];
1275 let t = TraceMonoid::from_events(&events);
1276 let result = TraceMonoid::identity().concat(&t);
1277 assert!(result.equivalent(&t));
1278 assert!(result.equivalent_exact(&t));
1279 }
1280
1281 #[test]
1282 fn monoid_identity_right() {
1283 let events = [
1284 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1285 TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1286 ];
1287 let t = TraceMonoid::from_events(&events);
1288 let result = t.concat(&TraceMonoid::identity());
1289 assert!(result.equivalent(&t));
1290 assert!(result.equivalent_exact(&t));
1291 }
1292
1293 #[test]
1294 fn monoid_identity_is_empty() {
1295 let id = TraceMonoid::identity();
1296 assert!(id.is_identity());
1297 assert!(id.is_empty());
1298 assert_eq!(id.len(), 0);
1299 assert_eq!(id.critical_path_length(), 0);
1300 assert_eq!(id.max_parallelism(), 0);
1301 }
1302
1303 #[test]
1306 fn monoid_associativity() {
1307 let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1308 let b_events = [TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2))];
1309 let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3))];
1310
1311 let a = TraceMonoid::from_events(&a_events);
1312 let b = TraceMonoid::from_events(&b_events);
1313 let c = TraceMonoid::from_events(&c_events);
1314
1315 let ab_c = a.concat(&b).concat(&c);
1316 let a_bc = a.concat(&b.concat(&c));
1317 assert!(ab_c.equivalent(&a_bc));
1318 }
1319
1320 #[test]
1321 fn monoid_associativity_with_dependencies() {
1322 let a_events = [TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1))];
1324 let b_events = [TraceEvent::complete(2, Time::ZERO, tid(1), rid(1))];
1325 let c_events = [TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2))];
1326
1327 let a = TraceMonoid::from_events(&a_events);
1328 let b = TraceMonoid::from_events(&b_events);
1329 let c = TraceMonoid::from_events(&c_events);
1330
1331 let ab_c = a.concat(&b).concat(&c);
1332 let a_bc = a.concat(&b.concat(&c));
1333 assert!(ab_c.equivalent(&a_bc));
1334 }
1335
1336 #[test]
1339 fn monoid_equivalent_traces() {
1340 let trace_a = [
1342 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1343 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1344 ];
1345 let trace_b = [
1346 TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1347 TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
1348 ];
1349 let ma = TraceMonoid::from_events(&trace_a);
1350 let mb = TraceMonoid::from_events(&trace_b);
1351 assert_eq!(ma, mb);
1352 assert!(ma.equivalent_exact(&mb));
1353 }
1354
1355 #[test]
1356 fn partial_eq_requires_exact_canonical_match_not_just_fingerprint() {
1357 let trace_a = [
1358 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1359 TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1360 ];
1361 let trace_b = [
1362 TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1363 TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1364 ];
1365
1366 let ma = TraceMonoid::from_events(&trace_a);
1367 let mb = TraceMonoid::from_events(&trace_b);
1368 assert!(!ma.equivalent_exact(&mb));
1369
1370 let TraceMonoid {
1373 canonical: FoataTrace { layers },
1374 ..
1375 } = mb;
1376 let spoof = TraceMonoid {
1377 canonical: FoataTrace { layers },
1378 fingerprint: ma.fingerprint,
1379 };
1380
1381 assert!(ma.equivalent(&spoof));
1383 assert_ne!(ma, spoof);
1384 }
1385
1386 #[test]
1387 fn exact_equivalence_distinguishes_region_cancel_reason_payload() {
1388 let trace_a = [TraceEvent::region_cancelled(
1389 1,
1390 Time::ZERO,
1391 rid(1),
1392 CancelReason::shutdown(),
1393 )];
1394 let trace_b = [TraceEvent::region_cancelled(
1395 1,
1396 Time::ZERO,
1397 rid(1),
1398 CancelReason::timeout(),
1399 )];
1400
1401 let ma = TraceMonoid::from_events(&trace_a);
1402 let mb = TraceMonoid::from_events(&trace_b);
1403
1404 assert!(ma.equivalent(&mb));
1405 assert!(!ma.equivalent_exact(&mb));
1406 assert_ne!(ma, mb);
1407 }
1408
1409 #[test]
1410 fn monoid_nonequivalent_traces() {
1411 let trace_a = [
1413 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1414 TraceEvent::complete(2, Time::ZERO, tid(1), rid(1)),
1415 ];
1416 let trace_b = [
1417 TraceEvent::spawn(1, Time::ZERO, tid(2), rid(2)),
1418 TraceEvent::complete(2, Time::ZERO, tid(2), rid(2)),
1419 ];
1420 let ma = TraceMonoid::from_events(&trace_a);
1421 let mb = TraceMonoid::from_events(&trace_b);
1422 assert_ne!(ma, mb);
1423 }
1424
1425 #[test]
1428 fn monoid_parallelism_metrics() {
1429 let events = [
1430 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1431 TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2)),
1432 TraceEvent::spawn(3, Time::ZERO, tid(3), rid(3)),
1433 TraceEvent::complete(4, Time::ZERO, tid(1), rid(1)),
1434 TraceEvent::complete(5, Time::ZERO, tid(2), rid(2)),
1435 ];
1436 let m = TraceMonoid::from_events(&events);
1437 assert_eq!(m.len(), 5);
1438 assert_eq!(m.critical_path_length(), 2); assert_eq!(m.max_parallelism(), 3); }
1441
1442 #[test]
1443 fn monoid_from_events_mapping() {
1444 let events = [
1446 TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1)),
1447 TraceEvent::region_created(2, Time::ZERO, rid(2), None),
1448 TraceEvent::spawn(3, Time::ZERO, tid(2), rid(2)),
1449 ];
1450 let m = TraceMonoid::from_events(&events);
1451 assert_eq!(m.len(), events.len());
1452 assert_eq!(m.class_fingerprint(), trace_fingerprint(&events));
1453 }
1454
1455 #[test]
1458 fn independent_obligations_same_layer() {
1459 let events = [
1460 TraceEvent::obligation_reserve(
1461 1,
1462 Time::ZERO,
1463 oid(1),
1464 tid(1),
1465 rid(1),
1466 ObligationKind::SendPermit,
1467 ),
1468 TraceEvent::obligation_reserve(
1469 2,
1470 Time::ZERO,
1471 oid(2),
1472 tid(2),
1473 rid(2),
1474 ObligationKind::Ack,
1475 ),
1476 ];
1477 let foata = canonicalize(&events);
1478 assert_eq!(foata.depth(), 1);
1479 }
1480
1481 #[test]
1482 fn same_obligation_events_form_chain() {
1483 let events = [
1484 TraceEvent::obligation_reserve(
1485 1,
1486 Time::ZERO,
1487 oid(1),
1488 tid(1),
1489 rid(1),
1490 ObligationKind::Lease,
1491 ),
1492 TraceEvent::obligation_commit(
1493 2,
1494 Time::ZERO,
1495 oid(1),
1496 tid(1),
1497 rid(1),
1498 ObligationKind::Lease,
1499 5000,
1500 ),
1501 ];
1502 let foata = canonicalize(&events);
1503 assert_eq!(foata.depth(), 2);
1504 }
1505
1506 #[test]
1509 fn trace_event_key_debug_clone_copy_eq_hash() {
1510 use std::collections::HashSet;
1511 let k = TraceEventKey::new(1, 2, 3, 4);
1512 let k2 = k; let k3 = k;
1514 assert_eq!(k, k2);
1515 assert_eq!(k, k3);
1516 assert_ne!(k, TraceEventKey::new(1, 2, 3, 5));
1517 let dbg = format!("{k:?}");
1518 assert!(dbg.contains("TraceEventKey"));
1519 let mut set = HashSet::new();
1520 set.insert(k);
1521 assert!(set.contains(&k2));
1522 }
1523
1524 mod metamorphic {
1528 use super::*;
1529
1530 struct Rng(u64);
1532 impl Rng {
1533 fn new(seed: u64) -> Self {
1534 Self(seed.wrapping_add(0x9E37_79B9_7F4A_7C15))
1535 }
1536 fn next_u64(&mut self) -> u64 {
1537 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
1538 let mut z = self.0;
1539 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1540 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1541 z ^ (z >> 31)
1542 }
1543 fn below(&mut self, n: usize) -> usize {
1544 (self.next_u64() % n as u64) as usize
1545 }
1546 }
1547
1548 fn task(n: u32) -> TaskId {
1549 TaskId::new_for_test(n, 0)
1550 }
1551 fn region(n: u32) -> RegionId {
1552 RegionId::new_for_test(n, 0)
1553 }
1554
1555 fn independent_set(n: u32) -> Vec<TraceEvent> {
1558 (1..=n)
1559 .map(|k| TraceEvent::spawn(u64::from(k), Time::ZERO, task(k), region(k)))
1560 .collect()
1561 }
1562
1563 fn dependent_chain(n: u64) -> Vec<TraceEvent> {
1565 (0..n)
1566 .map(|k| {
1567 let t = Time::from_nanos(k);
1568 match k % 4 {
1569 0 => TraceEvent::spawn(k, t, task(1), region(1)),
1570 1 => TraceEvent::schedule(k, t, task(1), region(1)),
1571 2 => TraceEvent::poll(k, t, task(1), region(1)),
1572 _ => TraceEvent::complete(k, t, task(1), region(1)),
1573 }
1574 })
1575 .collect()
1576 }
1577
1578 fn shuffled(items: &[TraceEvent], rng: &mut Rng) -> Vec<TraceEvent> {
1579 let mut v = items.to_vec();
1580 for i in (1..v.len()).rev() {
1581 let j = rng.below(i + 1);
1582 v.swap(i, j);
1583 }
1584 v
1585 }
1586
1587 fn canonical_seqs(events: &[TraceEvent]) -> Vec<u64> {
1588 canonicalize(events)
1589 .flatten()
1590 .iter()
1591 .map(|e| e.seq)
1592 .collect()
1593 }
1594
1595 #[test]
1596 fn canonicalize_and_fingerprint_are_deterministic() {
1597 for n in [0u64, 1, 2, 5, 9, 16] {
1598 let trace = dependent_chain(n);
1599 assert_eq!(
1600 trace_fingerprint(&trace),
1601 trace_fingerprint(&trace),
1602 "trace_fingerprint non-deterministic (n={n})"
1603 );
1604 assert_eq!(
1605 canonical_seqs(&trace),
1606 canonical_seqs(&trace),
1607 "canonicalize non-deterministic (n={n})"
1608 );
1609 }
1610 }
1611
1612 #[test]
1613 fn trace_fingerprint_equals_canonicalize_fingerprint() {
1614 let mixed = {
1617 let z = Time::ZERO;
1618 vec![
1619 TraceEvent::spawn(1, z, task(1), region(1)),
1620 TraceEvent::spawn(2, z, task(2), region(2)),
1621 TraceEvent::complete(3, z, task(1), region(1)),
1622 TraceEvent::complete(4, z, task(2), region(2)),
1623 ]
1624 };
1625 let cases = [
1626 Vec::new(),
1627 independent_set(1),
1628 independent_set(6),
1629 dependent_chain(7),
1630 mixed,
1631 ];
1632 for trace in cases {
1633 assert_eq!(
1634 trace_fingerprint(&trace),
1635 canonicalize(&trace).fingerprint(),
1636 "trace_fingerprint diverged from canonicalize().fingerprint()"
1637 );
1638 }
1639 }
1640
1641 #[test]
1642 fn permuting_fully_independent_events_yields_one_identical_layer() {
1643 for n in [2u32, 3, 5, 8, 13] {
1646 let base = independent_set(n);
1647 let baseline = canonicalize(&base);
1648 assert_eq!(
1649 baseline.depth(),
1650 1,
1651 "independent events must collapse to one layer"
1652 );
1653 let baseline_fp = baseline.fingerprint();
1654
1655 for seed in 0..16u64 {
1656 let mut rng = Rng::new(seed ^ u64::from(n) << 8);
1657 let permuted = shuffled(&base, &mut rng);
1658 let canon = canonicalize(&permuted);
1659 assert_eq!(
1660 canon.fingerprint(),
1661 baseline_fp,
1662 "independent permutation changed the fingerprint (n={n}, seed={seed})"
1663 );
1664 assert_eq!(canon.depth(), 1);
1665 assert_eq!(trace_fingerprint(&permuted), baseline_fp);
1666 }
1667 }
1668 }
1669
1670 #[test]
1671 fn swapping_an_adjacent_independent_pair_preserves_the_canonical_form() {
1672 let z = Time::ZERO;
1675 let original = vec![
1676 TraceEvent::spawn(1, z, task(1), region(1)),
1677 TraceEvent::spawn(2, z, task(2), region(2)),
1678 TraceEvent::spawn(3, z, task(3), region(3)),
1679 TraceEvent::complete(4, z, task(1), region(1)),
1680 ];
1681 let mut swapped = original.clone();
1682 swapped.swap(1, 2);
1683 assert_eq!(
1684 trace_fingerprint(&original),
1685 trace_fingerprint(&swapped),
1686 "swapping an adjacent independent pair changed the class"
1687 );
1688 assert_eq!(
1689 canonicalize(&original).fingerprint(),
1690 canonicalize(&swapped).fingerprint()
1691 );
1692 }
1693
1694 #[test]
1695 fn a_dependent_chain_keeps_one_event_per_layer() {
1696 for n in 1u64..=12 {
1699 let canon = canonicalize(&dependent_chain(n));
1700 assert_eq!(canon.depth(), n as usize, "chain of {n} needs {n} layers");
1701 for layer in canon.layers() {
1702 assert_eq!(layer.len(), 1, "dependent-chain layer holds one event");
1703 }
1704 }
1705 }
1706
1707 #[test]
1708 fn flatten_preserves_the_event_count() {
1709 for n in [0u64, 1, 4, 10] {
1710 let dep = dependent_chain(n);
1711 assert_eq!(canonicalize(&dep).flatten().len(), dep.len());
1712 }
1713 for n in [1u32, 5, 11] {
1714 let ind = independent_set(n);
1715 assert_eq!(canonicalize(&ind).flatten().len(), ind.len());
1716 }
1717 }
1718
1719 #[test]
1720 fn monoid_identity_is_empty_and_neutral() {
1721 let identity = TraceMonoid::identity();
1722 assert!(identity.is_identity());
1723 assert!(identity.is_empty());
1724 assert_eq!(identity.len(), 0);
1725
1726 let m = TraceMonoid::from_events(&dependent_chain(5));
1727 assert_eq!(identity.concat(&m), m, "left identity law");
1728 assert_eq!(m.concat(&identity), m, "right identity law");
1729 }
1730
1731 #[test]
1732 fn monoid_concat_is_associative() {
1733 let a = TraceMonoid::from_events(&independent_set(3));
1734 let b = TraceMonoid::from_events(&dependent_chain(4));
1735 let c = TraceMonoid::from_events(&independent_set(2));
1736 assert_eq!(
1737 a.concat(&b).concat(&c),
1738 a.concat(&b.concat(&c)),
1739 "concat must be associative"
1740 );
1741 }
1742
1743 #[test]
1744 fn monoid_groups_independent_reorderings_into_one_class() {
1745 let base = independent_set(7);
1746 let mut rng = Rng::new(0xC0FFEE);
1747 let permuted = shuffled(&base, &mut rng);
1748 let m1 = TraceMonoid::from_events(&base);
1749 let m2 = TraceMonoid::from_events(&permuted);
1750 assert!(m1.equivalent(&m2), "independent reorderings are one class");
1751 assert_eq!(m1, m2, "and structurally equal");
1752 }
1753
1754 #[test]
1755 fn monoid_equivalence_is_reflexive() {
1756 for trace in [independent_set(6), dependent_chain(8)] {
1757 let m = TraceMonoid::from_events(&trace);
1758 assert!(m.equivalent(&m), "equivalence must be reflexive");
1759 assert!(
1760 m.equivalent_exact(&m),
1761 "exact equivalence must be reflexive"
1762 );
1763 let again = TraceMonoid::from_events(&trace);
1764 assert_eq!(m, again, "PartialEq must be reflexive");
1765 assert_eq!(m.class_fingerprint(), canonicalize(&trace).fingerprint());
1766 }
1767 }
1768
1769 #[test]
1770 fn critical_path_and_parallelism_track_the_canonical_shape() {
1771 let ind = TraceMonoid::from_events(&independent_set(9));
1772 assert_eq!(ind.critical_path_length(), 1);
1773 assert_eq!(ind.max_parallelism(), 9);
1774
1775 let dep = TraceMonoid::from_events(&dependent_chain(6));
1776 assert_eq!(dep.critical_path_length(), 6);
1777 assert_eq!(dep.max_parallelism(), 1);
1778
1779 let empty = TraceMonoid::identity();
1780 assert_eq!(empty.critical_path_length(), 0);
1781 assert_eq!(empty.max_parallelism(), 0);
1782 }
1783
1784 #[test]
1785 fn empty_trace_canonicalizes_to_zero_layers() {
1786 let canon = canonicalize(&[]);
1787 assert_eq!(canon.depth(), 0);
1788 assert!(canon.is_empty());
1789 assert!(canon.flatten().is_empty());
1790 assert_eq!(canon.layers().len(), 0);
1791 assert_eq!(trace_fingerprint(&[]), canon.fingerprint());
1792 }
1793 }
1794}
1795
1796#[cfg(test)]
1797#[path = "canonicalize_metamorphic_tests.rs"]
1798mod canonicalize_metamorphic_tests;