#![cfg(all(feature = "distributed", feature = "webrtc"))]
mod common;
use lazily::{
Context, CrdtOp, CrdtPlaneRuntime, HlcStamp, IpcValue, LwwRegister, NodeId, NodeKey, PeerId,
ReplicatedCell, WireStamp,
};
use serde_json::Value;
const SPEC: &str = "../lazily-spec/conformance/distributed";
fn load(name: &str) -> Option<Value> {
let text = crate::common::spec_read_to_string(format!("{SPEC}/{name}")).ok()?;
Some(serde_json::from_str(&text).expect("fixture parses"))
}
fn op_of(v: &Value) -> CrdtOp {
let s = &v["stamp"];
let bytes: Vec<u8> = v["state"]["Inline"]
.as_array()
.expect("Inline state")
.iter()
.map(|b| b.as_u64().expect("byte") as u8)
.collect();
CrdtOp {
node: NodeId(v["node"].as_u64().expect("node")),
key: v["key"]
.as_str()
.map(|k| NodeKey::new(k).expect("valid key")),
stamp: WireStamp {
wall_time: s["wall_time"].as_u64().expect("wall_time"),
logical: s["logical"].as_u64().expect("logical"),
peer: s["peer"].as_u64().expect("peer"),
},
state: IpcValue::Inline(bytes),
}
}
fn seeded_runtime(ctx: &Context, ops: &[CrdtOp]) -> CrdtPlaneRuntime {
let mut rt = CrdtPlaneRuntime::new(PeerId(9));
let mut seen: Vec<(NodeId, Option<NodeKey>)> = Vec::new();
for op in ops {
let id = (op.node, op.key.clone());
if seen.contains(&id) {
continue;
}
seen.push(id.clone());
let seed = HlcStamp::from(WireStamp {
wall_time: 0,
logical: 0,
peer: 9,
});
let cell: ReplicatedCell<LwwRegister<Vec<u8>>> = ReplicatedCell::lww(ctx, Vec::new(), seed);
rt.register(id.0, id.1, cell);
}
rt
}
fn ingest_ops(rt: &mut CrdtPlaneRuntime, ctx: &Context, ops: &[CrdtOp]) -> usize {
let sync = lazily::CrdtSync {
frontier: Vec::new(),
ops: ops.to_vec(),
};
rt.ingest(ctx, &sync, 0)
}
#[test]
fn anti_entropy_converge_conformance() {
let Some(fixture) = load("anti_entropy_converge.json") else {
eprintln!("SKIP: lazily-spec sibling missing");
return;
};
assert_eq!(fixture["model"], "CrdtPlane");
let scenarios = fixture["scenarios"].as_array().expect("scenarios");
assert!(
!scenarios.is_empty(),
"a replay of zero scenarios is not a replay"
);
let mut checked_counts = 0usize;
let mut checked_redeliver = 0usize;
let mut checked_converged = 0usize;
for sc in scenarios {
let name = sc["name"].as_str().expect("name");
let ops: Vec<CrdtOp> = sc["ops"]
.as_array()
.expect("ops")
.iter()
.map(op_of)
.collect();
let ctx = Context::new();
let mut rt = seeded_runtime(&ctx, &ops);
let applied = ingest_ops(&mut rt, &ctx, &ops);
let want = sc["expect"]["applied_count"]
.as_u64()
.expect("applied_count") as usize;
assert_eq!(
applied, want,
"{name}: applied_count. A superseded op still counts — it entered the \
log. Counting only ops that changed the winner is the lazily-cpp bug."
);
checked_counts += 1;
if sc
.get("redeliver")
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
let again = ingest_ops(&mut rt, &ctx, &ops);
let want_rd = sc["expect"]["redeliver_applied_count"]
.as_u64()
.expect("redeliver_applied_count") as usize;
assert_eq!(again, want_rd, "{name}: redelivery must apply {want_rd}");
checked_redeliver += 1;
}
if let Some(want_entries) = sc["expect"]["converged"].as_array() {
let got = rt.converged();
for entry in want_entries {
let node = NodeId(entry["node"].as_u64().expect("node"));
let want_bytes: Vec<u8> = entry["state"]["Inline"]
.as_array()
.expect("Inline")
.iter()
.map(|b| b.as_u64().expect("byte") as u8)
.collect();
let found = got
.iter()
.find(|e| e.node == node)
.unwrap_or_else(|| panic!("{name}: no converged entry for {node:?}"));
let IpcValue::Inline(got_bytes) = &found.state else {
panic!("{name}: converged state for {node:?} is not Inline");
};
assert_eq!(
got_bytes, &want_bytes,
"{name}: converged state for {node:?} — the winner is the \
greatest stamp, with peer as the final tiebreak"
);
checked_converged += 1;
}
}
}
assert!(
checked_counts >= 3 && checked_redeliver >= 1 && checked_converged >= 4,
"too little asserted: counts={checked_counts} redeliver={checked_redeliver} \
converged={checked_converged}"
);
}
#[test]
fn crdt_sync_frames_conformance() {
let Some(fixture) = load("crdt_sync_frames.json") else {
eprintln!("SKIP: lazily-spec sibling missing");
return;
};
let frames = fixture["frames"].as_array().expect("frames");
assert!(
!frames.is_empty(),
"a replay of zero frames is not a replay"
);
let mut checked = 0usize;
for frame in frames {
let label = frame["label"].as_str().expect("label");
let wire = &frame["wire"]["CrdtSync"];
let sync: lazily::CrdtSync =
serde_json::from_value(wire.clone()).unwrap_or_else(|e| panic!("{label}: {e}"));
let assertions = &frame["assertions"];
if let Some(want) = assertions["frontier_len"].as_u64() {
assert_eq!(
sync.frontier.len() as u64,
want,
"{label}: frontier_len after wire round-trip"
);
checked += 1;
}
if let Some(want) = assertions["op_count"].as_u64() {
assert_eq!(
sync.ops.len() as u64,
want,
"{label}: op_count after wire round-trip"
);
checked += 1;
}
let round_tripped: lazily::CrdtSync =
serde_json::from_value(serde_json::to_value(&sync).expect("serialize"))
.expect("re-deserialize");
assert_eq!(
round_tripped.ops.len(),
sync.ops.len(),
"{label}: ops survive round-trip"
);
assert_eq!(
round_tripped.frontier.len(),
sync.frontier.len(),
"{label}: frontier survives round-trip"
);
}
assert!(checked >= 4, "too little asserted across frames: {checked}");
}