use std::collections::HashMap;
use std::fs;
use lazily::{Context, MaterializationMode, MaterializedFamily};
use serde_json::Value;
const SPEC_DIR: &str = "../lazily-spec/conformance/materialization";
fn load_fixture(name: &str) -> Value {
let path = format!("{SPEC_DIR}/{name}");
let raw =
fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read fixture {path}: {e}"));
serde_json::from_str(&raw).unwrap_or_else(|e| panic!("failed to parse fixture {path}: {e}"))
}
fn spec_fixtures_present() -> bool {
std::path::Path::new(&format!("{SPEC_DIR}/observational_transparency.json")).exists()
}
fn parse_val(spec: &Value) -> HashMap<String, i64> {
spec.get("val")
.and_then(|v| v.as_object())
.expect("spec.val object")
.iter()
.map(|(k, v)| (k.clone(), v.as_i64().expect("integer val")))
.collect()
}
fn str_vec(v: &Value) -> Vec<String> {
v.as_array()
.expect("array")
.iter()
.map(|e| e.as_str().expect("string").to_string())
.collect()
}
fn make_family(
ctx: &Context,
mode: MaterializationMode,
val: &HashMap<String, i64>,
) -> MaterializedFamily<String, i64> {
let table = val.clone();
let keys: Vec<String> = val.keys().cloned().collect();
MaterializedFamily::new(ctx, mode, keys, move |_ctx, k: &String| {
*table.get(k).expect("key in spec.val")
})
}
fn run_fixture(name: &str) {
let fx = load_fixture(name);
let spec = fx.get("spec").expect("spec");
let val = parse_val(spec);
let expected = fx.get("expected").expect("expected");
let ctx = Context::new();
if let Some(dm) = expected.get("default_mode").and_then(|v| v.as_str()) {
assert_eq!(dm, "eager", "{name}: fixture pins eager default");
assert_eq!(MaterializationMode::default(), MaterializationMode::Eager);
}
let eager = make_family(&ctx, MaterializationMode::Eager, &val);
let lazy = make_family(&ctx, MaterializationMode::Lazy, &val);
let eager_present = str_vec(expected.get("eager_present").expect("eager_present"));
assert_eq!(
eager.materialized_count(),
eager_present.len(),
"{name}: eager present-set size"
);
for k in &eager_present {
assert!(eager.is_materialized(k), "{name}: eager present {k}");
}
assert_eq!(lazy.materialized_count(), 0, "{name}: lazy starts deferred");
let reads = str_vec(fx.get("reads").expect("reads"));
let per_read = expected.get("present_after_each_read").map(|v| {
v.as_array()
.expect("array")
.iter()
.map(|n| n.as_u64().expect("u64") as usize)
.collect::<Vec<_>>()
});
let mut last_count = 0usize;
for (i, k) in reads.iter().enumerate() {
let got = lazy.observe(&ctx, k);
let want = *val.get(k).expect("read key in val");
assert_eq!(got, want, "{name}: lazy observe {k}");
assert_eq!(
eager.observe(&ctx, k),
got,
"{name}: eager==lazy observe {k}"
);
let count = lazy.materialized_count();
assert!(
count >= last_count,
"{name}: present set monotone at read {i}"
);
last_count = count;
if let Some(per) = &per_read {
assert_eq!(count, per[i], "{name}: present_after_each_read[{i}]");
}
}
let lazy_present = str_vec(
expected
.get("lazy_present_after_reads")
.expect("lazy_present_after_reads"),
);
for k in &val.keys().cloned().collect::<Vec<_>>() {
let present = lazy.is_materialized(k);
assert_eq!(
present,
lazy_present.contains(k),
"{name}: lazy present({k}) matches fixture"
);
if present {
assert!(
eager.is_materialized(k),
"{name}: lazy present ⊆ eager present for {k}"
);
}
}
let fresh_lazy = make_family(&ctx, MaterializationMode::Lazy, &val);
let observe = expected
.get("observe")
.and_then(|v| v.as_object())
.expect("observe map");
for (k, want) in observe {
let want = want.as_i64().expect("integer");
assert_eq!(eager.observe(&ctx, k), want, "{name}: eager observe {k}");
assert_eq!(
fresh_lazy.observe(&ctx, k),
want,
"{name}: lazy observe {k}"
);
}
}
#[test]
fn observational_transparency() {
if !spec_fixtures_present() {
eprintln!("skipping: lazily-spec conformance fixtures not present");
return;
}
run_fixture("observational_transparency.json");
}
#[test]
fn deferral_not_deallocation() {
if !spec_fixtures_present() {
eprintln!("skipping: lazily-spec conformance fixtures not present");
return;
}
run_fixture("deferral_not_deallocation.json");
}