use super::random;
use super::writes::segs_of;
use crate::expr::{path, rules};
use crate::state::splice;
use bumpalo::Bump;
use datavalue::{DataValue, OwnedDataValue};
#[derive(Debug)]
enum Op {
Set(String, OwnedDataValue),
Remove(String),
Append(String, Vec<OwnedDataValue>),
MergeRoot(&'static str, OwnedDataValue),
}
fn op(rng: &mut random::Rng, tree: &OwnedDataValue) -> Op {
match rng.below(10) {
0..=4 => Op::Set(random::path(rng, tree), random::value(rng)),
5 | 6 => Op::Remove(random::path(rng, tree)),
7 | 8 => {
let n = rng.below(3);
Op::Append(
random::path(rng, tree),
(0..n).map(|_| random::value(rng)).collect(),
)
}
_ => Op::MergeRoot(
rng.pick(&["data", "metadata", "temp_data", "a"]),
random::value(rng),
),
}
}
fn reference_append(
context: &mut OwnedDataValue,
segs: &[crate::expr::Seg],
items: Vec<OwnedDataValue>,
) -> Result<(), ()> {
match path::get(context, segs) {
None | Some(OwnedDataValue::Null) => {
path::set(context, segs, OwnedDataValue::Array(items));
Ok(())
}
Some(OwnedDataValue::Array(existing)) => {
let mut all = existing.clone();
all.extend(items);
path::set(context, segs, OwnedDataValue::Array(all));
Ok(())
}
Some(_) => Err(()),
}
}
fn apply<'a>(
case: usize,
arena: &'a Bump,
owned: &mut OwnedDataValue,
view: DataValue<'a>,
op: &Op,
) -> DataValue<'a> {
match op {
Op::Set(dotted, value) => {
let segs = random::segs(dotted);
let hops = splice::resolve(view, &segs);
if let Some(hops) = hops.map(|r| r.hops(&segs)) {
let mut by_hops = owned.clone();
path::set(&mut by_hops, &segs_of(&hops), value.clone());
let mut by_path = owned.clone();
path::set(&mut by_path, &segs, value.clone());
assert_eq!(
random::exact(&by_hops),
random::exact(&by_path),
"case {case}: hops of {dotted:?} are {hops:?}"
);
}
let wrote = path::set(owned, &segs, value.clone());
let tracked = splice::set_resolved(arena, view, &segs, value.to_arena(arena));
assert_eq!(
tracked.map(|(_, r, _)| r),
hops,
"case {case}: one walk resolves as resolve does"
);
let spliced = tracked.map(|(root, _, _)| root);
assert_eq!(
spliced.is_some(),
wrote,
"case {case}: set {dotted:?}, no-op agreement"
);
assert_eq!(hops.is_some(), wrote, "case {case}: resolve {dotted:?}");
spliced.unwrap_or(view)
}
Op::Remove(dotted) => {
let segs = random::segs(dotted);
let removed = path::remove(owned, &segs);
let spliced = splice::remove_resolved(arena, view, &segs);
assert_eq!(
spliced.is_some(),
removed.is_some(),
"case {case}: remove {dotted:?}"
);
if let Some((_, kinds, removed_view)) = spliced {
assert_eq!(
Some(kinds),
splice::resolve(view, &segs).map(|r| r.kinds),
"case {case}: remove {dotted:?} resolves"
);
assert_eq!(
Some(random::exact(&removed_view.to_owned())),
removed.as_ref().map(random::exact),
"case {case}: remove {dotted:?} removed value"
);
}
spliced.map_or(view, |(root, _, _)| root)
}
Op::Append(dotted, items) => {
let segs = random::segs(dotted);
let mut reference = owned.clone();
let expected = reference_append(&mut reference, &segs, items.clone());
let got = path::append(owned, &segs, items.clone());
assert_eq!(
got.is_ok(),
expected.is_ok(),
"case {case}: append {dotted:?} ok"
);
assert_eq!(
random::exact(owned),
random::exact(&reference),
"case {case}: owned append {dotted:?}"
);
let items_av: Vec<DataValue<'a>> = items.iter().map(|v| v.to_arena(arena)).collect();
let existing = splice::get(view, &segs);
match splice::append(arena, view, &segs, existing, &items_av) {
Ok(Some((root, r))) => {
assert_eq!(
Some(r),
splice::resolve(view, &segs),
"case {case}: append {dotted:?} resolves"
);
root
}
Ok(None) => {
assert_eq!(got, Ok(false), "case {case}: append {dotted:?} no-op");
view
}
Err(_) => {
assert!(got.is_err(), "case {case}: append {dotted:?} error");
view
}
}
}
Op::MergeRoot(key, value) => {
let mut reference = owned.clone();
random::reference_merge_root(&mut reference, key, value.clone());
crate::expr::path::merge_root(owned, key, value.clone());
assert_eq!(
random::exact(owned),
random::exact(&reference),
"case {case}: owned merge into {key}"
);
splice::merge_root(arena, view, key, value.to_arena(arena))
}
}
}
#[test]
fn every_splice_matches_the_owned_write() {
let seed = random::seed(0xa11ce);
let mut rng = random::Rng::new(seed);
for case in 0..50_000 {
let owned = random::tree(&mut rng, 4);
let arena = Bump::new();
let view = owned.view_in(&arena);
let op = op(&mut rng, &owned);
let mut written = owned.clone();
let root = apply(case, &arena, &mut written, view, &op);
assert_eq!(
random::exact(&root.to_owned()),
random::exact(&written),
"seed {seed:#x}, case {case}: {op:?} on {owned:?}"
);
}
}
#[test]
fn sequences_of_splices_over_a_spliced_root_match() {
let seed = random::seed(0xb0b);
let mut rng = random::Rng::new(seed);
for case in 0..5_000 {
let start = random::tree(&mut rng, 4);
let arena = Bump::new();
let mut view = start.view_in(&arena);
let mut owned = start.clone();
for step in 0..1 + rng.below(20) {
let op = op(&mut rng, &owned);
view = apply(case * 100 + step, &arena, &mut owned, view, &op);
assert_eq!(
random::exact(&view.to_owned()),
random::exact(&owned),
"seed {seed:#x}, case {case}, step {step}"
);
}
}
}
fn small_trees(depth: usize) -> Vec<OwnedDataValue> {
let mut out = vec![
OwnedDataValue::Null,
OwnedDataValue::from(1),
OwnedDataValue::String("s".to_owned()),
];
if depth == 0 {
return out;
}
let below = small_trees(depth - 1);
out.push(OwnedDataValue::Array(Vec::new()));
out.push(OwnedDataValue::Object(Vec::new()));
for t in &below {
out.push(OwnedDataValue::Array(vec![t.clone()]));
out.push(OwnedDataValue::Array(vec![OwnedDataValue::Null, t.clone()]));
for key in ["a", "0", "#0"] {
out.push(OwnedDataValue::Object(vec![(key.to_owned(), t.clone())]));
}
}
out
}
fn small_paths() -> Vec<String> {
const SEGS: &[&str] = &["a", "0", "1", "#0", "01", "+1", "2", ""];
let mut out: Vec<String> = SEGS.iter().map(|s| format!("data.{s}")).collect();
let mut last = out.clone();
for _ in 1..3 {
last = last
.iter()
.flat_map(|p| SEGS.iter().map(move |s| format!("{p}.{s}")))
.collect();
out.extend(last.iter().cloned());
}
out
}
#[test]
fn every_small_tree_and_path_agrees_with_the_owned_rule_and_the_model() {
use dataflow_corpus::{Json, path as model};
let value = OwnedDataValue::from(7);
let mut case = 0;
for tree in small_trees(2) {
let context = OwnedDataValue::Object(vec![("data".to_owned(), tree)]);
let text = random::exact(&context);
for dotted in small_paths() {
let segs = random::segs(&dotted);
let arena = Bump::new();
let reference = rules::resolve(&context, &segs);
assert_eq!(
rules::resolve(context.view_in(&arena), &segs),
reference,
"the reference over the view, {dotted} on {text}"
);
assert_eq!(
splice::resolve(context.view_in(&arena), &segs),
reference,
"the splice's walk, {dotted} on {text}"
);
let ops = [
Op::Set(dotted.clone(), value.clone()),
Op::Remove(dotted.clone()),
Op::Append(dotted.clone(), vec![value.clone()]),
];
for op in ops {
case += 1;
let arena = Bump::new();
let view = context.view_in(&arena);
let mut written = context.clone();
let mut theirs = Json::from_text(&text).expect("a context parses");
match &op {
Op::Set(at, v) => {
model::set(&mut theirs, at, Json::from_text(&random::exact(v)).unwrap())
}
Op::Remove(at) => {
model::remove(&mut theirs, at);
}
_ => {}
}
let checks_model = !matches!(op, Op::Append(..));
let root = apply(case, &arena, &mut written, view, &op);
assert_eq!(
random::exact(&root.to_owned()),
random::exact(&written),
"case {case}: {op:?} on {text}"
);
if checks_model {
assert_eq!(
random::exact(&written),
theirs.to_text(),
"case {case}: model, {op:?} on {text}"
);
}
}
}
}
assert!(case > 100_000, "{case} cases");
}