#[cfg(test)]
mod tests {
use crate::price_level::PriceLevelStatistics;
use std::str::FromStr;
use std::sync::Arc;
use std::thread;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn test_new() {
let stats = PriceLevelStatistics::new();
assert_eq!(stats.orders_added(), 0);
assert_eq!(stats.orders_removed(), 0);
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0);
assert_eq!(stats.value_executed(), 0);
assert_eq!(stats.last_execution_time(), 0);
assert!(stats.first_arrival_time() > 0);
assert_eq!(stats.sum_waiting_time(), 0);
}
#[test]
fn test_record_execution_error_paths() {
let stats = PriceLevelStatistics::new();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
assert!(stats.record_execution(1, 100, now + 1_000, now).is_err());
assert!(stats.record_execution(u64::MAX, u128::MAX, 0, now).is_err());
}
#[test]
fn test_default() {
let stats = PriceLevelStatistics::default();
assert_eq!(stats.orders_added(), 0);
assert_eq!(stats.orders_removed(), 0);
assert_eq!(stats.orders_executed(), 0);
}
#[test]
fn test_record_operations() {
let stats = PriceLevelStatistics::new();
for _ in 0..5 {
stats.record_order_added();
}
assert_eq!(stats.orders_added(), 5);
for _ in 0..3 {
stats.record_order_removed();
}
assert_eq!(stats.orders_removed(), 3);
let execution_time: u64 = 1_716_000_000_000;
assert!(stats.record_execution(10, 100, 0, execution_time).is_ok()); assert_eq!(stats.orders_executed(), 1);
assert_eq!(stats.quantity_executed(), 10);
assert_eq!(stats.value_executed(), 1000); assert!(stats.last_execution_time() > 0);
let timestamp = execution_time - 1000;
assert!(
stats
.record_execution(5, 200, timestamp, execution_time)
.is_ok()
);
assert_eq!(stats.orders_executed(), 2);
assert_eq!(stats.quantity_executed(), 15); assert_eq!(stats.value_executed(), 2000); assert!(stats.sum_waiting_time() >= 1000); }
#[test]
fn test_average_execution_price() {
let stats = PriceLevelStatistics::new();
assert_eq!(stats.average_execution_price(), None);
let execution_time: u64 = 1_716_000_000_000;
assert!(stats.record_execution(10, 100, 0, execution_time).is_ok()); assert!(stats.record_execution(20, 150, 0, execution_time).is_ok());
let avg_price = stats.average_execution_price().unwrap();
assert!((avg_price - 133.33).abs() < 0.01);
}
#[test]
fn test_average_waiting_time() {
let stats = PriceLevelStatistics::new();
assert_eq!(stats.average_waiting_time(), None);
let now: u64 = 1_716_000_000_000;
assert!(stats.record_execution(10, 100, now - 1000, now).is_ok()); assert!(stats.record_execution(20, 150, now - 3000, now).is_ok());
let avg_wait = stats.average_waiting_time().unwrap();
assert!((1900.0..=2100.0).contains(&avg_wait));
}
#[test]
fn test_time_since_last_execution() {
let stats = PriceLevelStatistics::new();
assert_eq!(stats.time_since_last_execution(), None);
let past = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
- 1000;
assert!(stats.record_execution(10, 100, 0, past).is_ok());
let time_since = stats.time_since_last_execution().unwrap();
assert!(time_since > 0);
}
#[test]
fn test_reset() {
let stats = PriceLevelStatistics::new();
stats.record_order_added();
stats.record_order_removed();
assert!(
stats
.record_execution(10, 100, 0, 1_716_000_000_000)
.is_ok()
);
assert_eq!(stats.orders_added(), 1);
assert_eq!(stats.orders_removed(), 1);
assert_eq!(stats.orders_executed(), 1);
stats.reset();
assert_eq!(stats.orders_added(), 0);
assert_eq!(stats.orders_removed(), 0);
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0);
assert_eq!(stats.value_executed(), 0);
assert_eq!(stats.last_execution_time(), 0);
assert!(stats.first_arrival_time() > 0);
assert_eq!(stats.sum_waiting_time(), 0);
}
#[test]
fn test_display() {
let stats = PriceLevelStatistics::new();
stats.record_order_added();
stats.record_order_removed();
assert!(
stats
.record_execution(10, 100, 0, 1_716_000_000_000)
.is_ok()
);
let display_str = stats.to_string();
assert!(display_str.starts_with("PriceLevelStatistics:"));
assert!(display_str.contains("orders_added=1"));
assert!(display_str.contains("orders_removed=1"));
assert!(display_str.contains("orders_executed=1"));
assert!(display_str.contains("quantity_executed=10"));
assert!(display_str.contains("value_executed=1000"));
}
#[test]
fn test_from_str() {
let input = "PriceLevelStatistics:orders_added=5;orders_removed=3;orders_executed=2;quantity_executed=15;value_executed=2000;last_execution_time=1616823000000;first_arrival_time=1616823000001;sum_waiting_time=1000";
let stats = PriceLevelStatistics::from_str(input).unwrap();
assert_eq!(stats.orders_added(), 5);
assert_eq!(stats.orders_removed(), 3);
assert_eq!(stats.orders_executed(), 2);
assert_eq!(stats.quantity_executed(), 15);
assert_eq!(stats.value_executed(), 2000);
assert_eq!(stats.last_execution_time(), 1616823000000);
assert_eq!(stats.first_arrival_time(), 1616823000001);
assert_eq!(stats.sum_waiting_time(), 1000);
}
#[test]
fn test_from_str_invalid_format() {
let input = "InvalidFormat";
assert!(PriceLevelStatistics::from_str(input).is_err());
}
#[test]
fn test_from_str_missing_field() {
let input = "PriceLevelStatistics:orders_added=5;orders_removed=3;orders_executed=2;quantity_executed=15;value_executed=2000;last_execution_time=1616823000000;first_arrival_time=1616823000001";
assert!(PriceLevelStatistics::from_str(input).is_err());
}
#[test]
fn test_from_str_invalid_field_value() {
let input = "PriceLevelStatistics:orders_added=invalid;orders_removed=3;orders_executed=2;quantity_executed=15;value_executed=2000;last_execution_time=1616823000000;first_arrival_time=1616823000001;sum_waiting_time=1000";
assert!(PriceLevelStatistics::from_str(input).is_err());
}
#[test]
fn test_serialize_deserialize_json() {
let stats = PriceLevelStatistics::new();
stats.record_order_added();
stats.record_order_removed();
assert!(
stats
.record_execution(10, 100, 0, 1_716_000_000_000)
.is_ok()
);
let json = serde_json::to_string(&stats).unwrap();
assert!(json.contains("\"orders_added\":1"));
assert!(json.contains("\"orders_removed\":1"));
assert!(json.contains("\"orders_executed\":1"));
assert!(json.contains("\"quantity_executed\":10"));
assert!(json.contains("\"value_executed\":1000"));
let deserialized: PriceLevelStatistics = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.orders_added(), 1);
assert_eq!(deserialized.orders_removed(), 1);
assert_eq!(deserialized.orders_executed(), 1);
assert_eq!(deserialized.quantity_executed(), 10);
assert_eq!(deserialized.value_executed(), 1000);
}
#[test]
fn test_round_trip_display_parse() {
let current_time: u64 = 1616823000000;
let input = format!(
"PriceLevelStatistics:orders_added=2;orders_removed=1;orders_executed=2;quantity_executed=15;value_executed=2000;last_execution_time={};first_arrival_time={};sum_waiting_time=1000",
current_time,
current_time + 1
);
let stats = PriceLevelStatistics::from_str(&input).unwrap();
let string_representation = stats.to_string();
let parsed = PriceLevelStatistics::from_str(&string_representation).unwrap();
assert_eq!(parsed.orders_added(), stats.orders_added());
assert_eq!(parsed.orders_removed(), stats.orders_removed());
assert_eq!(parsed.orders_executed(), stats.orders_executed());
assert_eq!(parsed.quantity_executed(), stats.quantity_executed());
assert_eq!(parsed.value_executed(), stats.value_executed());
assert_eq!(parsed.last_execution_time(), stats.last_execution_time());
assert_eq!(parsed.first_arrival_time(), stats.first_arrival_time());
assert_eq!(parsed.sum_waiting_time(), stats.sum_waiting_time());
}
#[test]
fn test_thread_safety() {
let stats = PriceLevelStatistics::new();
let stats_arc = Arc::new(stats);
let mut handles = vec![];
for _ in 0..10 {
let stats_clone = Arc::clone(&stats_arc);
let handle = thread::spawn(move || {
for _ in 0..100 {
stats_clone.record_order_added();
stats_clone.record_order_removed();
if let Err(error) = stats_clone.record_execution(1, 100, 0, 1_716_000_000_000) {
panic!("record_execution failed in thread: {error}");
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(stats_arc.orders_added(), 1000); assert_eq!(stats_arc.orders_removed(), 1000);
assert_eq!(stats_arc.orders_executed(), 1000);
assert_eq!(stats_arc.quantity_executed(), 1000);
assert_eq!(stats_arc.value_executed(), 100000); }
#[test]
fn test_statistics_reset_and_verify() {
let stats = PriceLevelStatistics::new();
stats.record_order_added();
stats.record_order_added();
stats.record_order_removed();
assert!(
stats
.record_execution(10, 100, 0, 1_716_000_000_000)
.is_ok()
);
assert_eq!(stats.orders_added(), 2);
assert_eq!(stats.orders_removed(), 1);
assert_eq!(stats.orders_executed(), 1);
stats.reset();
assert_eq!(stats.orders_added(), 0);
assert_eq!(stats.orders_removed(), 0);
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0);
assert_eq!(stats.value_executed(), 0);
assert_eq!(stats.last_execution_time(), 0);
assert!(stats.first_arrival_time() > 0);
assert_eq!(stats.sum_waiting_time(), 0);
}
#[test]
fn test_statistics_serialize_deserialize_fields() {
let input = "PriceLevelStatistics:orders_added=1;orders_removed=2;orders_executed=3;quantity_executed=4;value_executed=5;last_execution_time=6;first_arrival_time=7;sum_waiting_time=8";
let stats = PriceLevelStatistics::from_str(input).unwrap();
let serialized = serde_json::to_string(&stats).unwrap();
assert!(serialized.contains("\"orders_added\":1"));
assert!(serialized.contains("\"orders_removed\":2"));
assert!(serialized.contains("\"orders_executed\":3"));
assert!(serialized.contains("\"quantity_executed\":4"));
assert!(serialized.contains("\"value_executed\":5"));
assert!(serialized.contains("\"last_execution_time\":6"));
assert!(serialized.contains("\"first_arrival_time\":7"));
assert!(serialized.contains("\"sum_waiting_time\":8"));
let deserialized: PriceLevelStatistics = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.orders_added(), 1);
assert_eq!(deserialized.orders_removed(), 2);
assert_eq!(deserialized.orders_executed(), 3);
assert_eq!(deserialized.quantity_executed(), 4);
assert_eq!(deserialized.value_executed(), 5);
assert_eq!(deserialized.last_execution_time(), 6);
assert_eq!(deserialized.first_arrival_time(), 7);
assert_eq!(deserialized.sum_waiting_time(), 8);
}
#[test]
fn test_statistics_visitor_missing_fields() {
let json = r#"{
"orders_added": 1,
"orders_removed": 2,
"orders_executed": 3
}"#;
let deserialized: PriceLevelStatistics = serde_json::from_str(json).unwrap();
assert_eq!(deserialized.orders_added(), 1);
assert_eq!(deserialized.orders_removed(), 2);
assert_eq!(deserialized.orders_executed(), 3);
assert_eq!(deserialized.quantity_executed(), 0);
assert_eq!(deserialized.value_executed(), 0);
assert!(!deserialized.stats_degraded());
}
fn seed_stats(
orders_executed: usize,
quantity_executed: u64,
value_executed: u64,
sum_waiting_time: u64,
) -> PriceLevelStatistics {
let text = format!(
"PriceLevelStatistics:orders_added=0;orders_removed=0;orders_executed={orders_executed};\
quantity_executed={quantity_executed};value_executed={value_executed};\
last_execution_time=0;first_arrival_time=0;sum_waiting_time={sum_waiting_time};\
stats_degraded=false"
);
PriceLevelStatistics::from_str(&text).expect("seed stats must parse")
}
#[test]
fn test_record_execution_all_or_nothing_on_each_overflow() {
{
let stats = seed_stats(0, u64::MAX - 5, 0, 0);
assert!(stats.record_execution(10, 1, 0, 1_000).is_err());
assert_eq!(stats.orders_executed(), 0, "orders_executed rolled back");
assert_eq!(
stats.quantity_executed(),
u64::MAX - 5,
"quantity unchanged"
);
assert_eq!(stats.value_executed(), 0, "value unchanged");
assert!(stats.stats_degraded(), "degraded flag set");
}
{
let stats = seed_stats(0, 0, u64::MAX - 5, 0);
assert!(stats.record_execution(1, 10, 0, 1_000).is_err());
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0, "quantity rolled back");
assert_eq!(stats.value_executed(), u64::MAX - 5, "value unchanged");
assert!(stats.stats_degraded());
}
{
let stats = seed_stats(0, 0, 0, u64::MAX - 5);
assert!(stats.record_execution(1, 1, 1, 100).is_err());
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0, "quantity rolled back");
assert_eq!(stats.value_executed(), 0, "value rolled back");
assert_eq!(stats.sum_waiting_time(), u64::MAX - 5, "sum unchanged");
assert!(stats.stats_degraded());
}
{
let stats = seed_stats(0, 0, 0, 0);
assert!(stats.record_execution(u64::MAX, 2, 0, 1_000).is_err());
assert_eq!(stats.orders_executed(), 0);
assert_eq!(stats.quantity_executed(), 0);
assert_eq!(stats.value_executed(), 0);
assert!(stats.stats_degraded());
}
{
let stats = seed_stats(0, 0, 0, 0);
assert!(stats.record_execution(1, 1, 200, 100).is_err());
assert_eq!(stats.orders_executed(), 0);
assert!(stats.stats_degraded());
}
}
#[test]
fn test_record_execution_success_leaves_flag_clear() {
let stats = PriceLevelStatistics::new();
assert!(stats.record_execution(10, 5, 0, 1_000).is_ok());
assert_eq!(stats.orders_executed(), 1);
assert_eq!(stats.quantity_executed(), 10);
assert_eq!(stats.value_executed(), 50);
assert!(
!stats.stats_degraded(),
"a successful record must not degrade"
);
}
#[test]
fn test_concurrent_record_execution_cross_aggregate_consistency() {
use std::sync::Barrier;
const N: usize = 8;
const K: u64 = 3;
const Q: u64 = 1_000;
let seed = u64::MAX - K * Q;
for _ in 0..50 {
let stats = Arc::new(seed_stats(0, seed, seed, 0));
let barrier = Arc::new(Barrier::new(N));
let mut handles = Vec::with_capacity(N);
for _ in 0..N {
let stats = Arc::clone(&stats);
let barrier = Arc::clone(&barrier);
handles.push(thread::spawn(move || {
barrier.wait();
stats.record_execution(Q, 1, 0, 1_000).is_ok()
}));
}
let successes: u64 = handles
.into_iter()
.map(|h| u64::from(h.join().expect("thread panicked")))
.sum();
assert_eq!(successes, K, "exactly K records must fit the headroom");
assert_eq!(stats.orders_executed() as u64, K);
assert_eq!(
stats.quantity_executed(),
stats.value_executed(),
"quantity and value advance in lockstep (each success adds to both)"
);
assert_eq!(
stats.quantity_executed() - seed,
K * Q,
"the surplus over the seed equals orders_executed * Q"
);
assert!(
stats.stats_degraded(),
"some records overflowed -> degraded"
);
}
}
#[test]
fn test_serialize_omits_degraded_flag_when_false() {
let stats = PriceLevelStatistics::new();
assert!(!stats.stats_degraded());
let json = serde_json::to_string(&stats).expect("serialize");
assert!(
!json.contains("stats_degraded"),
"a non-degraded statistics must omit the flag (old-v2 checksum compat): {json}"
);
let back: PriceLevelStatistics = serde_json::from_str(&json).expect("deserialize");
assert!(!back.stats_degraded());
let degraded = seed_stats(0, 0, 0, 0);
assert!(degraded.record_execution(u64::MAX, 2, 0, 1_000).is_err()); assert!(degraded.stats_degraded());
let degraded_json = serde_json::to_string(°raded).expect("serialize degraded");
assert!(
degraded_json.contains("\"stats_degraded\":true"),
"a degraded statistics must emit the flag: {degraded_json}"
);
let degraded_back: PriceLevelStatistics =
serde_json::from_str(°raded_json).expect("deserialize degraded");
assert!(degraded_back.stats_degraded(), "the flag must round-trip");
}
#[test]
fn test_orders_executed_overflow_seeded_via_fromstr_is_all_or_nothing() {
let seeded = format!(
"PriceLevelStatistics:orders_added=0;orders_removed=0;orders_executed={};quantity_executed=0;value_executed=0;last_execution_time=0;first_arrival_time=0;sum_waiting_time=0;stats_degraded=false",
usize::MAX
);
let stats = PriceLevelStatistics::from_str(&seeded).expect("parse MAX orders_executed");
assert_eq!(stats.orders_executed(), usize::MAX);
assert!(
stats.record_execution(5, 100, 0, 1_000).is_err(),
"the checked orders_executed overflow must be rejected"
);
assert_eq!(stats.orders_executed(), usize::MAX, "no wrap to 0");
assert_eq!(stats.quantity_executed(), 0);
assert_eq!(stats.value_executed(), 0);
assert_eq!(stats.sum_waiting_time(), 0);
assert!(
stats.stats_degraded(),
"the dropped execution is observable"
);
}
#[test]
fn test_mark_degraded_transitions_once() {
let stats = PriceLevelStatistics::new();
assert!(!stats.stats_degraded());
assert!(
stats.mark_degraded(),
"first drop transitions false -> true"
);
assert!(stats.stats_degraded());
assert!(!stats.mark_degraded(), "second drop is silent");
assert!(!stats.mark_degraded(), "still silent");
stats.reset();
assert!(!stats.stats_degraded());
assert!(stats.mark_degraded(), "post-reset drop transitions again");
}
#[test]
fn test_last_execution_time_is_monotonic() {
let stats = PriceLevelStatistics::new();
stats.record_execution(1, 100, 0, 5_000).expect("newer");
assert_eq!(stats.last_execution_time(), 5_000);
stats.record_execution(1, 100, 0, 2_000).expect("older");
assert_eq!(
stats.last_execution_time(),
5_000,
"an older record must not move the last-execution time back"
);
stats.record_execution(1, 100, 0, 9_000).expect("newest");
assert_eq!(stats.last_execution_time(), 9_000);
}
#[test]
fn test_last_execution_time_max_wins_under_barrier() {
use std::sync::{Arc as StdArc, Barrier};
const HI: u64 = 9_000;
const LO: u64 = 3_000;
for _ in 0..500 {
let stats = StdArc::new(PriceLevelStatistics::new());
let barrier = StdArc::new(Barrier::new(2));
let hi = {
let stats = StdArc::clone(&stats);
let barrier = StdArc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
let _ = stats.record_execution(1, 100, 0, HI);
})
};
let lo = {
let stats = StdArc::clone(&stats);
let barrier = StdArc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
let _ = stats.record_execution(1, 100, 0, LO);
})
};
hi.join().expect("hi");
lo.join().expect("lo");
assert_eq!(
stats.last_execution_time(),
HI,
"the max timestamp must win"
);
}
}
#[test]
fn test_clone_is_consistent_under_concurrent_record() {
use std::sync::{Arc as StdArc, Barrier};
const N: u64 = 3_000;
let stats = StdArc::new(PriceLevelStatistics::new());
let barrier = StdArc::new(Barrier::new(2));
let writer = {
let stats = StdArc::clone(&stats);
let barrier = StdArc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
for _ in 0..N {
stats.record_execution(1, 100, 0, 1_000).expect("record");
}
})
};
let reader = {
let stats = StdArc::clone(&stats);
let barrier = StdArc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
for _ in 0..50_000 {
let copy = (*stats).clone();
let q = copy.quantity_executed();
assert_eq!(
copy.value_executed(),
q * 100,
"clone must not tear value vs quantity"
);
assert_eq!(
copy.orders_executed() as u64,
q,
"clone must not tear orders vs quantity"
);
}
})
};
writer.join().expect("writer");
reader.join().expect("reader");
assert_eq!(stats.orders_executed() as u64, N);
assert_eq!(stats.quantity_executed(), N);
assert_eq!(stats.value_executed(), N * 100);
}
}