use dyniak::mapreduce::builtins::{reduce_count, reduce_set_union, reduce_sort};
use dyniak::proto::pb::{RpbContent, RpbLink, RpbPair};
use hegel::generators as gs;
use hegel::TestCase;
use prost::Message as _;
use serde_json::Value;
fn arb_bytes(tc: &TestCase) -> Vec<u8> {
let len = tc.draw(gs::integers::<usize>().min_value(0).max_value(8));
let mut v = Vec::with_capacity(len);
for _ in 0..len {
v.push(tc.draw(gs::integers::<u8>()));
}
v
}
fn arb_opt_bytes(tc: &TestCase) -> Option<Vec<u8>> {
if tc.draw(gs::booleans()) {
Some(arb_bytes(tc))
} else {
None
}
}
fn arb_opt_u32(tc: &TestCase) -> Option<u32> {
if tc.draw(gs::booleans()) {
Some(tc.draw(gs::integers::<u32>()))
} else {
None
}
}
fn arb_link(tc: &TestCase) -> RpbLink {
RpbLink {
bucket: arb_opt_bytes(tc),
key: arb_opt_bytes(tc),
tag: arb_opt_bytes(tc),
}
}
fn arb_pair(tc: &TestCase) -> RpbPair {
RpbPair {
key: arb_bytes(tc),
value: arb_opt_bytes(tc),
}
}
fn arb_vec<T>(tc: &TestCase, max: usize, f: impl Fn(&TestCase) -> T) -> Vec<T> {
let n = tc.draw(gs::integers::<usize>().min_value(0).max_value(max));
(0..n).map(|_| f(tc)).collect()
}
fn arb_content(tc: &TestCase) -> RpbContent {
RpbContent {
value: arb_bytes(tc),
content_type: arb_opt_bytes(tc),
charset: arb_opt_bytes(tc),
content_encoding: arb_opt_bytes(tc),
vtag: arb_opt_bytes(tc),
links: arb_vec(tc, 4, arb_link),
last_mod: arb_opt_u32(tc),
last_mod_usecs: arb_opt_u32(tc),
usermeta: arb_vec(tc, 4, arb_pair),
indexes: arb_vec(tc, 4, arb_pair),
deleted: if tc.draw(gs::booleans()) {
Some(tc.draw(gs::booleans()))
} else {
None
},
}
}
#[hegel::test(test_cases = 256)]
fn rpb_link_round_trips(tc: TestCase) {
let link = arb_link(&tc);
let bytes = link.encode_to_vec();
let back = RpbLink::decode(bytes.as_slice()).expect("decode");
assert_eq!(back, link);
}
#[hegel::test(test_cases = 256)]
fn rpb_content_round_trips(tc: TestCase) {
let content = arb_content(&tc);
let bytes = content.encode_to_vec();
let back = RpbContent::decode(bytes.as_slice()).expect("decode");
assert_eq!(back, content);
assert_eq!(back.encode_to_vec(), bytes);
}
fn arb_value(tc: &TestCase) -> Value {
match tc.draw(gs::integers::<u8>().min_value(0).max_value(4)) {
0 => Value::Null,
1 => Value::Bool(tc.draw(gs::booleans())),
2 => Value::from(tc.draw(gs::integers::<i32>().min_value(-5).max_value(5))),
3 => Value::from(tc.draw(gs::sampled_from(&[
"a".to_string(),
"b".to_string(),
"c".to_string(),
]))),
_ => serde_json::json!([tc.draw(gs::integers::<i8>())]),
}
}
fn arb_values(tc: &TestCase) -> Vec<Value> {
let n = tc.draw(gs::integers::<usize>().min_value(0).max_value(12));
(0..n).map(|_| arb_value(tc)).collect()
}
#[hegel::test(test_cases = 256)]
fn reduce_count_returns_input_length(tc: TestCase) {
let inputs = arb_values(&tc);
let out = reduce_count(&inputs, None).expect("count");
assert_eq!(
out,
vec![Value::from(
u64::try_from(inputs.len()).expect("len fits u64")
)]
);
}
#[hegel::test(test_cases = 256)]
fn reduce_set_union_dedupes_and_is_idempotent(tc: TestCase) {
let inputs = arb_values(&tc);
let once = reduce_set_union(&inputs, None).expect("union");
for i in 0..once.len() {
for j in (i + 1)..once.len() {
assert_ne!(once[i], once[j], "union must not contain duplicates");
}
}
let mut expected: Vec<Value> = Vec::new();
for v in &inputs {
if !expected.iter().any(|e| e == v) {
expected.push(v.clone());
}
}
assert_eq!(once, expected);
let twice = reduce_set_union(&once, None).expect("union^2");
assert_eq!(twice, once);
}
#[hegel::test(test_cases = 256)]
fn reduce_sort_is_an_order_independent_permutation(tc: TestCase) {
let inputs = arb_values(&tc);
let sorted = reduce_sort(&inputs, None).expect("sort");
assert_eq!(sorted.len(), inputs.len());
let resorted = reduce_sort(&sorted, None).expect("sort^2");
assert_eq!(resorted, sorted, "sort is idempotent");
let mut reversed = inputs.clone();
reversed.reverse();
let from_reversed = reduce_sort(&reversed, None).expect("sort(rev)");
assert_eq!(from_reversed, sorted, "sort is input-order independent");
}