mod common;
use std::cmp::Ordering;
use std::collections::HashSet;
use o192::{
decode_sortable64, encode_sortable64, is_valid, parse, OrionIdError, OrionIdGenerator,
ID_SIZE_BYTES, ID_SIZE_CHARS, MAX_COUNTER, MAX_RELATIVE_MS,
};
struct Mulberry32(u32);
impl Mulberry32 {
const fn new(seed: u32) -> Self {
Self(seed)
}
fn next_u32(&mut self) -> u32 {
self.0 = self.0.wrapping_add(0x6d2b_79f5);
let mut t = self.0;
t = t.wrapping_mul(t | 1);
t ^= t.wrapping_add(t.wrapping_mul(t | 0x3d) ^ t);
t ^ (t >> 14)
}
fn next_byte(&mut self) -> u8 {
(self.next_u32() & 0xff) as u8
}
}
fn shuffle_in_place<T>(items: &mut [T], rng: &mut Mulberry32) {
let n = items.len();
for i in (1..n).rev() {
let j = (rng.next_u32() as usize) % (i + 1);
items.swap(i, j);
}
}
fn driven_generator() -> OrionIdGenerator {
OrionIdGenerator::new().with_random_fill(|out| {
out.fill(0);
Ok(())
})
}
#[test]
fn lex_order_of_encoded_strings_matches_lex_order_of_bytes() {
let n: usize = 5_000;
let mut rng = Mulberry32::new(0xC0FF_EE42);
let mut buffers: Vec<[u8; ID_SIZE_BYTES]> = Vec::with_capacity(n);
for _ in 0..n {
let mut buf = [0u8; ID_SIZE_BYTES];
for byte in &mut buf {
*byte = rng.next_byte();
}
buffers.push(buf);
}
let mut by_bytes = buffers.clone();
by_bytes.sort_unstable();
let mut paired: Vec<(String, [u8; ID_SIZE_BYTES])> = buffers
.iter()
.map(|b| (encode_sortable64(b).unwrap(), *b))
.collect();
paired.sort_by(|a, b| a.0.cmp(&b.0));
for (i, (expected, (_, actual))) in by_bytes.iter().zip(paired.iter()).enumerate() {
assert_eq!(expected, actual, "divergence at rank {i}");
}
}
#[test]
fn lex_order_matches_relative_ms_fraction_counter_tuple() {
let mut gen = driven_generator();
let n = 2_000u128;
let mut ids: Vec<String> = Vec::with_capacity(n as usize);
for i in 0..n {
let ms: u128 = 1_000 + i;
let frac: u128 = (i * 137) & 0xfff;
gen.set_physical_time_key_for_test((ms << 12) | frac);
ids.push(gen.next().unwrap());
}
for i in 1..ids.len() {
assert!(ids[i - 1] < ids[i], "not strictly monotonic at {i}");
}
let mut by_tuple: Vec<(u128, u16, u32, String)> = ids
.iter()
.map(|id| {
let view = parse(id, 0).unwrap();
(
view.relative_ms,
view.fraction4096,
view.counter,
id.clone(),
)
})
.collect();
let mut by_string: Vec<String> = ids.clone();
by_tuple.sort_by_key(|entry| (entry.0, entry.1, entry.2));
by_string.sort();
for (i, (tuple_entry, string_entry)) in by_tuple.iter().zip(by_string.iter()).enumerate() {
assert_eq!(&tuple_entry.3, string_entry, "divergence at {i}");
}
}
#[test]
fn shuffled_ids_sort_back_to_generation_order() {
let mut gen = OrionIdGenerator::new();
let n = 10_000usize;
let original: Vec<String> = (0..n).map(|_| gen.next().unwrap()).collect();
let mut shuffled = original.clone();
let mut rng = Mulberry32::new(0x9E37_79B9);
shuffle_in_place(&mut shuffled, &mut rng);
shuffled.sort();
for (i, (expected, actual)) in original.iter().zip(shuffled.iter()).enumerate() {
assert_eq!(expected, actual, "sort mismatch at rank {i}");
}
}
#[test]
fn frozen_clock_makes_counter_increment_by_exactly_one() {
let mut gen = driven_generator();
gen.set_physical_time_key_for_test(42u128 << 12);
let first = parse(&gen.next().unwrap(), 0).unwrap();
let initial = first.counter;
for i in 1..1_000 {
let view = parse(&gen.next().unwrap(), 0).unwrap();
assert_eq!(
view.counter,
initial + i as u32,
"counter not +1 at step {i}"
);
assert_eq!(view.relative_ms, 42, "relative_ms must stay frozen");
assert_eq!(view.fraction4096, 0, "fraction must stay frozen");
}
}
#[test]
fn two_generators_interleave_correctly_under_external_clock() {
let mut gen_a = driven_generator();
let mut gen_b = driven_generator();
let mut entries: Vec<(u32, String)> = Vec::new();
for tick in 0u32..500 {
let key: u128 = u128::from(tick + 1) << 12;
gen_a.set_physical_time_key_for_test(key);
gen_b.set_physical_time_key_for_test(key);
entries.push((tick, gen_a.next().unwrap()));
entries.push((tick, gen_b.next().unwrap()));
}
entries.sort_by(|a, b| a.1.cmp(&b.1));
for i in 1..entries.len() {
assert!(
entries[i - 1].0 <= entries[i].0,
"sorted ordering inverted ticks at {i}",
);
}
}
#[test]
fn parse_fields_reencode_back_to_original_bytes() {
let mut gen = OrionIdGenerator::new();
for i in 0..200 {
let id = gen.next().unwrap();
let view = parse(&id, 0).unwrap();
let mut out = [0u8; ID_SIZE_BYTES];
let mut ms = view.relative_ms;
for offset in (0..6).rev() {
out[offset] = (ms & 0xff) as u8;
ms >>= 8;
}
out[6] = ((view.fraction4096 >> 4) & 0xff) as u8;
out[7] = (((view.fraction4096 & 0x0f) as u8) << 4) | (((view.counter >> 16) & 0x0f) as u8);
out[8] = ((view.counter >> 8) & 0xff) as u8;
out[9] = (view.counter & 0xff) as u8;
let random_bytes = common::hex_to_bytes(&view.random_hex);
out[10..24].copy_from_slice(&random_bytes);
assert_eq!(out, view.bytes, "byte mismatch at iter {i}");
assert_eq!(
encode_sortable64(&out).unwrap(),
id,
"encode mismatch at iter {i}"
);
}
}
#[test]
fn relative_ms_close_to_48bit_max_still_produces_valid_id() {
let mut gen = driven_generator();
gen.set_physical_time_key_for_test((MAX_RELATIVE_MS - 1) << 12);
let id = gen.next().unwrap();
assert_eq!(id.len(), ID_SIZE_CHARS);
assert!(is_valid(&id));
let view = parse(&id, 0).unwrap();
assert_eq!(view.relative_ms, MAX_RELATIVE_MS - 1);
assert_eq!(view.fraction4096, 0);
}
#[test]
fn whitespace_framing_inputs_are_rejected() {
let zeros = "0".repeat(ID_SIZE_CHARS);
let cases = [
format!(" {zeros}"),
format!("{zeros} "),
format!("{zeros}\n"),
format!("\n{zeros}"),
];
for id in &cases {
assert!(decode_sortable64(id).is_err(), "accepted: {id:?}");
assert!(!is_valid(id));
}
let all_spaces = " ".repeat(ID_SIZE_CHARS);
assert_eq!(
decode_sortable64(&all_spaces),
Err(OrionIdError::InvalidCharacter),
);
}
#[test]
fn random_tail_differs_across_ids_from_one_generator() {
let mut gen = OrionIdGenerator::new();
let mut tails: HashSet<String> = HashSet::with_capacity(1_000);
for _ in 0..1_000 {
tails.insert(parse(&gen.next().unwrap(), 0).unwrap().random_hex);
}
assert!(
tails.len() >= 999,
"random tail entropy too low: only {}/1000 unique",
tails.len(),
);
}
#[test]
fn counter_overflow_under_frozen_clock_stays_strictly_monotonic() {
let mut gen = driven_generator();
gen.set_physical_time_key_for_test(7u128 << 12);
let mut prev = gen.next().unwrap();
let iterations = MAX_COUNTER + 100;
for i in 0..iterations {
let cur = gen.next().unwrap();
assert_eq!(
prev.as_str().cmp(cur.as_str()),
Ordering::Less,
"counter overflow broke lex order at iteration {i}",
);
prev = cur;
}
assert!(
gen.synthetic_ticks() >= 1,
"expected at least one synthetic tick"
);
}
#[test]
fn boundary_counter_vectors_sort_by_counter_regardless_of_random_tail() {
let a_id = "000000FdW000PF~~~~~~~~~~~~~~~~~~";
let b_id = "000000FdW000PG000000000000000000";
assert!(
a_id < b_id,
"counter must dominate the random tail in lex order"
);
let view_a = parse(a_id, 0).unwrap();
let view_b = parse(b_id, 0).unwrap();
assert_eq!(view_a.relative_ms, view_b.relative_ms);
assert_eq!(view_a.fraction4096, view_b.fraction4096);
assert_eq!(view_a.counter + 1, view_b.counter);
}