use doublets::{data::Flow, mem::Global, split, unit, Doublets, Link, Links};
fn assert_indexes(store: &impl Doublets<usize>) {
let mut all = Vec::new();
store.each(|link| {
all.push(link);
Flow::Continue
});
assert_eq!(store.count(), all.len());
let any = store.constants().any;
for link in &all {
assert_eq!(store.get_link(link.index), Some(link.clone()));
for query in [
[any, link.source, any],
[any, any, link.target],
[any, link.source, link.target],
] {
if query.contains(&0) {
continue;
}
let mut expected: Vec<_> = all
.iter()
.filter(|link| {
(query[1] == any || link.source == query[1])
&& (query[2] == any || link.target == query[2])
})
.map(|link| link.index)
.collect();
let mut found = Vec::new();
store.each_by(query, |link| {
found.push(link.index);
Flow::Continue
});
expected.sort_unstable();
found.sort_unstable();
assert_eq!(found, expected, "query {query:?}");
assert_eq!(store.count_by(query), expected.len(), "query {query:?}");
}
if link.source != 0 && link.target != 0 {
assert_eq!(store.search(link.source, link.target), Some(link.index));
}
}
}
fn forward_reference(mut store: impl Doublets<usize>, source: bool) {
store.create_point().unwrap();
let b = store.create_point().unwrap();
let pair = if source { (3, b) } else { (b, 3) };
store.update(b, pair.0, pair.1).unwrap();
let any = store.constants().any;
let query = [any, pair.0, pair.1];
let mut found = Vec::new();
store.each_by(query, |link| {
found.push(link.index);
Flow::Continue
});
assert_eq!(found, [b], "before creating the referenced ID");
assert_eq!(store.create_point().unwrap(), 3);
assert_eq!(store.get_link(b), Some(Link::new(b, pair.0, pair.1)));
found.clear();
store.each_by(query, |link| {
found.push(link.index);
Flow::Continue
});
assert_eq!(found, [b], "after creating the referenced ID");
assert_eq!(store.count_by(query), 1);
assert_indexes(&store);
}
#[test]
fn split_forward_source_reference() {
forward_reference(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
true,
);
}
#[test]
fn split_forward_target_reference() {
forward_reference(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
false,
);
}
#[test]
fn unit_forward_references_control() {
for source in [true, false] {
forward_reference(unit::Store::<usize, _>::new(Global::new()).unwrap(), source);
}
}
#[test]
fn multiple_forward_usages_populate_both_internal_indexes() {
let mut store = split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap();
for _ in 0..8 {
store.create_point().unwrap();
}
for (index, source, target) in [
(1, 9, 2),
(2, 3, 9),
(3, 9, 9),
(4, 9, 100),
(5, 100, 9),
(6, 9, 101),
(7, 102, 9),
] {
store.update(index, source, target).unwrap();
}
assert_indexes(&store);
assert_eq!(store.create().unwrap(), 9);
assert_indexes(&store);
store.update(9, 9, 8).unwrap();
assert_indexes(&store);
store.update(1, 2, 9).unwrap();
store.delete(3).unwrap();
assert_indexes(&store);
let any = store.constants().any;
assert_eq!(store.count_by([any, 100, 999]), 0);
assert_eq!(store.count_by([any, 100, 9]), 1);
}
#[test]
fn two_forward_endpoints_can_be_created_separately() {
let mut store = split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap();
let link = store.create_link(2, 3).unwrap();
assert_indexes(&store);
assert_eq!(store.create_point().unwrap(), 2);
assert_indexes(&store);
assert_eq!(store.create_point().unwrap(), 3);
assert_indexes(&store);
store.delete(link).unwrap();
assert_indexes(&store);
}
fn delete_and_recreate(mut store: impl Doublets<usize>, referenced: usize) {
for _ in 0..6 {
store.create_point().unwrap();
}
store.update(referenced, referenced, 4).unwrap();
store.update(1, referenced, 2).unwrap();
store.update(2, 3, referenced).unwrap();
store.update(3, referenced, referenced).unwrap();
assert_indexes(&store);
store.delete(referenced).unwrap();
assert!(!store.exist(referenced));
assert_indexes(&store);
assert_eq!(store.create().unwrap(), referenced);
store.update(referenced, referenced, 4).unwrap();
assert_indexes(&store);
store.update(1, 2, referenced).unwrap();
store.delete(2).unwrap();
assert_indexes(&store);
}
#[test]
fn references_survive_tail_deletion_and_recreation() {
delete_and_recreate(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
6,
);
}
#[test]
fn references_survive_free_list_reuse() {
delete_and_recreate(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
5,
);
}
fn empty_indexes_with_free_links(mut store: impl Doublets<usize>) {
for _ in 0..4 {
store.create().unwrap();
}
store.delete(1).unwrap();
store.delete(2).unwrap();
let any = store.constants().any;
for query in [[any, 99, any], [any, any, 99]] {
assert_eq!(store.count_by(query), 0, "query {query:?}");
let mut found = Vec::new();
store.each_by(query, |link| {
found.push(link.index);
Flow::Continue
});
assert_eq!(found, []);
}
assert_eq!(store.create().unwrap(), 2);
assert_eq!(store.create().unwrap(), 1);
assert_indexes(&store);
}
#[test]
fn empty_indexes_ignore_free_list_header() {
empty_indexes_with_free_links(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
);
empty_indexes_with_free_links(unit::Store::<usize, _>::new(Global::new()).unwrap());
}
#[test]
fn reindexing_preserves_creation_callback_and_read_break() {
let mut store = split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap();
store.create_link(3, 1).unwrap();
store.create_link(2, 3).unwrap();
let mut events = Vec::new();
assert_eq!(
store
.create_with(|before, after| {
events.push((before, after));
Flow::Break
})
.unwrap(),
Flow::Break
);
assert_eq!(events, [(Link::nothing(), Link::new(3, 0, 0))]);
assert_eq!(store.get_link(3), Some(Link::new(3, 0, 0)));
assert_indexes(&store);
let any = store.constants().any;
for query in [[any, 3, any], [any, any, 3]] {
let mut calls = 0;
assert_eq!(
store.each_by(query, |_| {
calls += 1;
Flow::Break
}),
Flow::Break
);
assert_eq!(calls, 1);
}
}
fn random_changes(mut store: impl Doublets<usize>, seed: u64) {
use rand::{rngs::StdRng, RngExt, SeedableRng};
use std::collections::BTreeMap;
let mut random = StdRng::seed_from_u64(seed);
let mut model = BTreeMap::new();
let trace = std::env::var_os("DOUBLETS_TRACE_RANDOM_CHANGES").is_some();
for step in 0..256 {
let operation = random.random_range(0..4);
if model.is_empty() || (operation == 0 && model.len() < 24) {
if trace {
eprintln!("seed {seed}, step {step}: create; model {model:?}");
}
let index = store.create().unwrap();
model.insert(index, (0, 0));
} else {
let index = *model
.keys()
.nth(random.random_range(0..model.len()))
.unwrap();
if operation == 1 {
if trace {
eprintln!("seed {seed}, step {step}: delete {index}");
}
store.delete(index).unwrap();
model.remove(&index);
} else {
let pair = (random.random_range(1..=32), random.random_range(1..=32));
if model.values().any(|&value| value == pair) {
continue;
}
if trace {
eprintln!("seed {seed}, step {step}: update {index} -> {pair:?}");
}
store.update(index, pair.0, pair.1).unwrap();
model.insert(index, pair);
}
}
assert_eq!(store.count(), model.len(), "seed {seed}, step {step}");
for (&index, &(source, target)) in &model {
assert_eq!(
store.get_link(index),
Some(Link::new(index, source, target)),
"seed {seed}, step {step}"
);
}
assert_indexes(&store);
}
}
#[test]
fn seeded_changes_match_a_plain_map() {
for seed in [1, 74, 1024, 65535] {
random_changes(
split::Store::<usize, _, _>::new(Global::new(), Global::new()).unwrap(),
seed,
);
random_changes(unit::Store::<usize, _>::new(Global::new()).unwrap(), seed);
}
}