use super::*;
use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
use crate::types::Time;
fn create_test_traces() -> Vec<Vec<TraceEvent>> {
vec![
vec![],
vec![TraceEvent::new(
1,
Time::ZERO,
TraceEventKind::Spawn,
TraceData::None,
)],
vec![TraceEvent::new(
1,
Time::ZERO,
TraceEventKind::UserTrace,
TraceData::None,
)],
vec![
TraceEvent::new(1, Time::ZERO, TraceEventKind::Spawn, TraceData::None),
TraceEvent::new(
2,
Time::from_nanos(100),
TraceEventKind::UserTrace,
TraceData::None,
),
TraceEvent::new(
3,
Time::from_nanos(200),
TraceEventKind::Wake,
TraceData::None,
),
TraceEvent::new(
4,
Time::from_nanos(300),
TraceEventKind::Complete,
TraceData::None,
),
],
vec![
TraceEvent::new(1, Time::ZERO, TraceEventKind::Spawn, TraceData::None),
TraceEvent::new(
2,
Time::from_nanos(50),
TraceEventKind::RegionCreated,
TraceData::None,
),
TraceEvent::new(
3,
Time::from_nanos(100),
TraceEventKind::UserTrace,
TraceData::None,
),
TraceEvent::new(
4,
Time::from_nanos(150),
TraceEventKind::Wake,
TraceData::None,
),
TraceEvent::new(
5,
Time::from_nanos(200),
TraceEventKind::ObligationReserve,
TraceData::None,
),
TraceEvent::new(
6,
Time::from_nanos(250),
TraceEventKind::TimerScheduled,
TraceData::None,
),
TraceEvent::new(
7,
Time::from_nanos(300),
TraceEventKind::CancelRequest,
TraceData::None,
),
TraceEvent::new(
8,
Time::from_nanos(350),
TraceEventKind::TimerFired,
TraceData::None,
),
TraceEvent::new(
9,
Time::from_nanos(400),
TraceEventKind::CancelAck,
TraceData::None,
),
TraceEvent::new(
10,
Time::from_nanos(450),
TraceEventKind::ObligationCommit,
TraceData::None,
),
TraceEvent::new(
11,
Time::from_nanos(500),
TraceEventKind::RegionCloseComplete,
TraceData::None,
),
TraceEvent::new(
12,
Time::from_nanos(550),
TraceEventKind::Complete,
TraceData::None,
),
],
vec![
TraceEvent::new(1, Time::ZERO, TraceEventKind::UserTrace, TraceData::None),
TraceEvent::new(
2,
Time::from_nanos(100),
TraceEventKind::Wake,
TraceData::None,
),
TraceEvent::new(
3,
Time::from_nanos(200),
TraceEventKind::TimerScheduled,
TraceData::None,
),
TraceEvent::new(
4,
Time::from_nanos(300),
TraceEventKind::TimerFired,
TraceData::None,
),
],
vec![
TraceEvent::new(1, Time::ZERO, TraceEventKind::Spawn, TraceData::None),
TraceEvent::new(
2,
Time::from_nanos(100),
TraceEventKind::RegionCreated,
TraceData::None,
),
TraceEvent::new(
3,
Time::from_nanos(200),
TraceEventKind::ObligationReserve,
TraceData::None,
),
TraceEvent::new(
4,
Time::from_nanos(300),
TraceEventKind::CancelRequest,
TraceData::None,
),
TraceEvent::new(
5,
Time::from_nanos(400),
TraceEventKind::CancelAck,
TraceData::None,
),
TraceEvent::new(
6,
Time::from_nanos(500),
TraceEventKind::ObligationCommit,
TraceData::None,
),
TraceEvent::new(
7,
Time::from_nanos(600),
TraceEventKind::RegionCloseComplete,
TraceData::None,
),
TraceEvent::new(
8,
Time::from_nanos(700),
TraceEventKind::Complete,
TraceData::None,
),
],
]
}
#[test]
fn mr_compression_level_monotonicity() {
for trace in create_test_traces() {
let lossless = compress(&trace, Level::Lossless);
let structural = compress(&trace, Level::Structural);
let skeleton = compress(&trace, Level::Skeleton);
assert!(
skeleton.events.len() <= structural.events.len(),
"Skeleton compression ({} events) should have ≤ events than Structural ({} events)",
skeleton.events.len(),
structural.events.len()
);
assert!(
structural.events.len() <= lossless.events.len(),
"Structural compression ({} events) should have ≤ events than Lossless ({} events)",
structural.events.len(),
lossless.events.len()
);
for event in &skeleton.events {
assert!(
structural.events.contains(event),
"Skeleton event {:?} not found in structural compression",
event.kind
);
}
for event in &structural.events {
assert!(
lossless.events.contains(event),
"Structural event {:?} not found in lossless compression",
event.kind
);
}
}
}
#[test]
fn mr_compression_idempotence() {
for trace in create_test_traces() {
for level in [Level::Lossless, Level::Structural, Level::Skeleton] {
let compressed_once = compress(&trace, level);
let compressed_twice = compress(&compressed_once.events, level);
assert_eq!(
compressed_once.events.len(),
compressed_twice.events.len(),
"Double compression changed event count for level {:?}",
level
);
assert_eq!(
compressed_once.events, compressed_twice.events,
"Double compression changed events for level {:?}",
level
);
assert_eq!(
compressed_once.certificate.event_hash(),
compressed_twice.certificate.event_hash(),
"Double compression changed certificate for level {:?}",
level
);
}
}
}
#[test]
fn mr_certificate_consistency() {
for trace in create_test_traces() {
for level in [Level::Lossless, Level::Structural, Level::Skeleton] {
let compressed = compress(&trace, level);
assert!(
validate_compressed(&compressed),
"Certificate validation failed for level {:?} with {} events",
level,
trace.len()
);
assert_eq!(
compressed.certificate.event_count() as usize,
compressed.events.len(),
"Certificate event count mismatch for level {:?}",
level
);
}
}
}
#[test]
fn mr_event_order_preservation() {
for trace in create_test_traces() {
if trace.len() < 2 {
continue;
}
for level in [Level::Lossless, Level::Structural, Level::Skeleton] {
let compressed = compress(&trace, level);
for i in 1..compressed.events.len() {
let prev_event = &compressed.events[i - 1];
let curr_event = &compressed.events[i];
assert!(
prev_event.seq < curr_event.seq,
"Event ordering violated: event {} (seq {}) appears before event {} (seq {}) for level {:?}",
i - 1,
prev_event.seq,
i,
curr_event.seq,
level
);
assert!(
prev_event.time <= curr_event.time,
"Timestamp ordering violated: event {} (time {:?}) appears before event {} (time {:?}) for level {:?}",
i - 1,
prev_event.time,
i,
curr_event.time,
level
);
}
}
}
}
#[test]
fn mr_noise_event_elimination() {
let noise_events = [
TraceEventKind::UserTrace,
TraceEventKind::Wake,
TraceEventKind::TimerScheduled,
TraceEventKind::TimerFired,
];
for trace in create_test_traces() {
let structural = compress(&trace, Level::Structural);
let skeleton = compress(&trace, Level::Skeleton);
for event in &structural.events {
assert!(
!noise_events.contains(&event.kind),
"Noise event {:?} found in structural compression",
event.kind
);
}
for event in &skeleton.events {
assert!(
!noise_events.contains(&event.kind),
"Noise event {:?} found in skeleton compression",
event.kind
);
}
let lossless = compress(&trace, Level::Lossless);
let original_noise_count = trace
.iter()
.filter(|e| noise_events.contains(&e.kind))
.count();
let lossless_noise_count = lossless
.events
.iter()
.filter(|e| noise_events.contains(&e.kind))
.count();
assert_eq!(
original_noise_count, lossless_noise_count,
"Lossless compression changed noise event count"
);
}
}
#[test]
fn mr_single_event_preservation() {
let skeleton_events = [
TraceEventKind::Spawn,
TraceEventKind::Complete,
TraceEventKind::CancelRequest,
TraceEventKind::CancelAck,
TraceEventKind::ObligationReserve,
TraceEventKind::ObligationCommit,
TraceEventKind::ObligationAbort,
TraceEventKind::RegionCreated,
TraceEventKind::RegionCloseComplete,
];
for trace in create_test_traces() {
let skeleton_compressed = compress(&trace, Level::Skeleton);
let original_skeleton_count = trace
.iter()
.filter(|e| skeleton_events.contains(&e.kind))
.count();
for event in &skeleton_compressed.events {
assert!(
skeleton_events.contains(&event.kind),
"Non-skeleton event {:?} found in skeleton compression",
event.kind
);
}
assert_eq!(
original_skeleton_count,
skeleton_compressed.events.len(),
"Skeleton compression changed skeleton event count from {} to {}",
original_skeleton_count,
skeleton_compressed.events.len()
);
}
}
#[test]
fn mr_event_count_consistency() {
for trace in create_test_traces() {
for level in [Level::Lossless, Level::Structural, Level::Skeleton] {
let compressed = compress(&trace, level);
assert_eq!(
compressed.events.len() + compressed.events_removed(),
compressed.original_count,
"Event count consistency violated for level {:?}: {} + {} != {}",
level,
compressed.events.len(),
compressed.events_removed(),
compressed.original_count
);
assert_eq!(
compressed.original_count,
trace.len(),
"Original count mismatch for level {:?}: {} != {}",
level,
compressed.original_count,
trace.len()
);
let expected_ratio = if trace.is_empty() {
1.0
} else {
compressed.events.len() as f64 / trace.len() as f64
};
assert!(
(compressed.ratio() - expected_ratio).abs() < f64::EPSILON,
"Compression ratio inconsistent for level {:?}: {} != {}",
level,
compressed.ratio(),
expected_ratio
);
}
}
}
#[test]
fn mr_level_specific_event_filtering() {
for trace in create_test_traces() {
let lossless = compress(&trace, Level::Lossless);
let structural = compress(&trace, Level::Structural);
let skeleton = compress(&trace, Level::Skeleton);
assert_eq!(
lossless.events.len(),
trace.len(),
"Lossless compression should retain all events"
);
let noise_count = trace.iter().filter(|e| is_noise_event(e)).count();
if noise_count > 0 {
assert!(
structural.events.len() < lossless.events.len(),
"Structural compression should remove noise events when present"
);
}
let non_skeleton_count = trace.iter().filter(|e| !is_skeleton_event(e)).count();
if non_skeleton_count > 0 {
assert!(
skeleton.events.len() <= structural.events.len(),
"Skeleton compression should remove non-skeleton events when present"
);
}
}
}
#[test]
fn mr_composite_monotonicity_idempotence_certificate() {
for trace in create_test_traces() {
let lossless = compress(&trace, Level::Lossless);
let structural = compress(&trace, Level::Structural);
let skeleton = compress(&trace, Level::Skeleton);
assert!(skeleton.events.len() <= structural.events.len());
assert!(structural.events.len() <= lossless.events.len());
for (level, compressed) in [
(Level::Lossless, &lossless),
(Level::Structural, &structural),
(Level::Skeleton, &skeleton),
] {
let double_compressed = compress(&compressed.events, level);
assert_eq!(
compressed.events, double_compressed.events,
"Idempotence failed for level {:?}",
level
);
}
assert!(validate_compressed(&lossless));
assert!(validate_compressed(&structural));
assert!(validate_compressed(&skeleton));
let direct_skeleton = compress(&trace, Level::Skeleton);
let stepwise_skeleton = compress(&structural.events, Level::Skeleton);
if !trace.is_empty() {
assert_eq!(
direct_skeleton.events, stepwise_skeleton.events,
"Direct vs stepwise skeleton compression should yield same events"
);
}
}
}
#[cfg(test)]
mod validation_tests {
use super::*;
#[test]
fn test_mr_suite_catches_mutations() {
let trace = vec![
TraceEvent::new(1, Time::ZERO, TraceEventKind::Spawn, TraceData::None),
TraceEvent::new(
2,
Time::from_nanos(100),
TraceEventKind::UserTrace,
TraceData::None,
),
TraceEvent::new(
3,
Time::from_nanos(200),
TraceEventKind::Complete,
TraceData::None,
),
];
let mut compressed = compress(&trace, Level::Structural);
let mut bad_cert = compressed.certificate.clone();
bad_cert.record_event(&TraceEvent::new(
999,
Time::ZERO,
TraceEventKind::Wake,
TraceData::None,
));
compressed.certificate = bad_cert;
assert!(
!validate_compressed(&compressed),
"Certificate consistency should catch corrupted certificate"
);
}
}