use super::*;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn below(&mut self, bound: u64) -> u64 {
self.next() % bound
}
}
fn stepped_identities(buffer: &LogRecordBuffer) -> Vec<LogRecordIdentity> {
let records = buffer.records();
if records.is_empty() {
return Vec::new();
}
let mut sequence_number = buffer.total_record_count();
for _ in 1..records.len() {
sequence_number = if sequence_number == 1 {
u32::MAX
} else {
sequence_number - 1
};
}
records
.iter()
.map(|record| {
let identity =
LogRecordIdentity::new(u64::from(sequence_number), record.date, record.time)
.unwrap();
sequence_number = next_sequence(sequence_number);
identity
})
.collect()
}
fn scanned_order(identities: &[LogRecordIdentity]) -> TimestampOrder {
let keys: Option<Vec<_>> = identities
.iter()
.map(LogRecordIdentity::timestamp_key)
.collect();
match keys {
None => TimestampOrder::Unkeyed,
Some(keys) if keys.windows(2).any(|pair| pair[1] < pair[0]) => TimestampOrder::Unordered,
Some(_) => TimestampOrder::Ascending,
}
}
struct Walked<'a>(&'a LogRecordBuffer);
impl LogBufferRecords for Walked<'_> {
fn record_count(&self) -> usize {
self.0.record_count()
}
fn encode_record(&self, index: usize, buf: &mut BytesMut) {
self.0.encode_record(index, buf);
}
fn record_identity(&self, index: usize) -> LogRecordIdentity {
self.0.record_identity(index)
}
}
fn stamped(second: u32) -> BACnetLogRecord {
BACnetLogRecord {
date: Date {
year: 126,
month: 8,
day: 1 + (second / 86_400 % 28) as u8,
day_of_week: 1 + (second / 86_400 % 7) as u8,
},
time: Time {
hour: (second / 3_600 % 24) as u8,
minute: (second / 60 % 60) as u8,
second: (second % 60) as u8,
hundredths: 0,
},
log_datum: LogDatum::UnsignedValue(u64::from(second)),
status_flags: None,
}
}
fn sample(rng: &mut Rng, second: u32) -> BACnetLogRecord {
let mut record = stamped(second);
match rng.below(64) {
0 => record.date.year = Date::UNSPECIFIED,
1 => record.date.day_of_week = 0,
2 => record.time.hour = 24,
3 => record.time.hundredths = Time::UNSPECIFIED,
_ => {}
}
record
}
fn check(buffer: &LogRecordBuffer, rng: &mut Rng, context: &str) {
let expected = stepped_identities(buffer);
assert_eq!(buffer.identities(), expected, "{context}");
for (index, identity) in expected.iter().enumerate() {
assert_eq!(buffer.record_identity(index), *identity, "{context}");
}
let mut probes = vec![0, u64::from(u32::MAX), u64::from(u32::MAX) + 1, u64::MAX];
probes.extend(expected.iter().map(LogRecordIdentity::sequence_number));
if let (Some(first), Some(last)) = (expected.first(), expected.last()) {
for delta in 1..=3 {
probes.push(first.sequence_number().wrapping_sub(delta));
probes.push(last.sequence_number().wrapping_add(delta));
}
}
probes.push(u64::from(buffer.total_record_count()));
probes.push(rng.next());
probes.push(rng.below(u64::from(u32::MAX) + 2));
for probe in probes {
let searched = expected
.iter()
.position(|identity| identity.sequence_number() == probe);
assert_eq!(
buffer.record_position(probe),
searched,
"{context} #{probe}"
);
assert_eq!(
Walked(buffer).record_position(probe),
searched,
"{context} #{probe}"
);
}
let order = scanned_order(&expected);
assert_eq!(buffer.timestamp_order(), order, "{context}");
assert_eq!(Walked(buffer).timestamp_order(), order, "{context}");
}
#[test]
fn computed_lookups_match_the_walks_over_random_histories() {
for seed in 0..64 {
let mut rng = Rng(seed);
let capacity = [0, 1, 2, 3, 7, 16, 40][rng.below(7) as usize];
let mut buffer = LogRecordBuffer::new(capacity);
if rng.below(4) != 0 {
let before_wrap = rng.below(60) as u32;
buffer
.restore(u32::MAX - before_wrap, VecDeque::new(), false)
.unwrap();
}
let mut clock = 10_000u32;
for step in 0..300 {
let context = format!("seed {seed} step {step} capacity {capacity}");
match rng.below(40) {
0 => buffer.clear(),
1 => {
buffer.insert_forced(sample(&mut rng, clock));
}
2 => {
let held = rng.below(u64::from(capacity) + 1) as u32;
let records = (0..held)
.map(|offset| sample(&mut rng, clock + 7 * offset))
.collect();
let total =
[1 + rng.below(8) as u32, rng.next() as u32 | 1][rng.below(2) as usize];
buffer.restore(total, records, false).unwrap();
clock += 7 * held;
}
_ => {
clock = match rng.below(12) {
0 => clock.saturating_sub(rng.below(5_000) as u32),
1 => clock,
_ => clock + 1 + rng.below(90) as u32,
};
buffer.admit_ordinary(sample(&mut rng, clock), true, false);
}
}
check(&buffer, &mut rng, &context);
}
}
}
#[test]
fn an_unkeyed_record_outranks_disorder_until_it_is_evicted() {
let mut buffer = LogRecordBuffer::new(3);
let mut rng = Rng(7);
let mut unkeyed = stamped(40);
unkeyed.date.year = Date::UNSPECIFIED;
buffer.insert_forced(stamped(50));
buffer.insert_forced(unkeyed);
buffer.insert_forced(stamped(30));
assert_eq!(buffer.timestamp_order(), TimestampOrder::Unkeyed);
check(&buffer, &mut rng, "unkeyed middle");
buffer.insert_forced(stamped(60));
assert_eq!(buffer.timestamp_order(), TimestampOrder::Unkeyed);
buffer.insert_forced(stamped(70));
assert_eq!(buffer.timestamp_order(), TimestampOrder::Ascending);
buffer.insert_forced(stamped(65));
assert_eq!(buffer.timestamp_order(), TimestampOrder::Unordered);
check(&buffer, &mut rng, "descent after eviction");
buffer.clear();
assert_eq!(buffer.timestamp_order(), TimestampOrder::Ascending);
}