use std::collections::{HashMap, HashSet};
use crate::functions::expression::{
ArrayInputSchema, BooleanInputSchema, InputValue, InputSchema, StringInputSchema,
};
use crate::functions::check::example_inputs::array;
fn assert_per_index_coverage(schema: &ArrayInputSchema) {
let item_perms = super::optional::inner_permutations(schema.items.as_ref());
let total = array::permutations(schema);
let rng = rand::rng();
let iter = array::generate(schema, rng);
let mut seen: HashMap<usize, HashMap<usize, HashSet<InputValue>>> = HashMap::new();
let mut count = 0usize;
for item in iter.take(total) {
count += 1;
let arr = match item {
InputValue::Array(a) => a,
other => panic!("expected Array, got {other:?}"),
};
let len = arr.len();
let by_index = seen.entry(len).or_default();
for (idx, val) in arr.into_iter().enumerate() {
by_index.entry(idx).or_default().insert(val);
}
}
assert_eq!(count, total, "generator yielded {count} items, expected {total}");
let num_lengths = seen.len();
for (&len, by_index) in &seen {
assert_eq!(
by_index.len(),
len,
"length {len}: expected {len} index entries, got {}",
by_index.len(),
);
for idx in 0..len {
let unique = by_index.get(&idx).map_or(0, |s| s.len());
assert_eq!(
unique, item_perms,
"length {len}, index {idx}: expected {item_perms} unique items, got {unique}",
);
}
}
assert_eq!(
total,
item_perms * num_lengths,
"total permutations mismatch: {total} != {item_perms} * {num_lengths}",
);
}
#[test]
fn bool_array_three_lengths() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: Some(2),
max_items: Some(6),
};
assert_per_index_coverage(&schema);
}
#[test]
fn bool_array_two_lengths() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: Some(3),
max_items: Some(4),
};
assert_per_index_coverage(&schema);
}
#[test]
fn bool_array_one_length() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: Some(5),
max_items: Some(5),
};
assert_per_index_coverage(&schema);
}
#[test]
fn enum_array_three_lengths() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::String(StringInputSchema {
r#type: Default::default(),
description: None,
r#enum: Some(vec!["a".into(), "b".into(), "c".into()]),
})),
min_items: Some(1),
max_items: Some(5),
};
assert_per_index_coverage(&schema);
}
#[test]
fn enum_array_one_length() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::String(StringInputSchema {
r#type: Default::default(),
description: None,
r#enum: Some(vec!["x".into(), "y".into(), "z".into()]),
})),
min_items: Some(2),
max_items: Some(2),
};
assert_per_index_coverage(&schema);
}
#[test]
fn plain_string_array_two_lengths() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::String(StringInputSchema {
r#type: Default::default(),
description: None,
r#enum: None,
})),
min_items: Some(0),
max_items: Some(1),
};
assert_per_index_coverage(&schema);
}
#[test]
fn defaults_no_bounds() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: None,
max_items: None,
};
assert_per_index_coverage(&schema);
}
#[test]
fn defaults_only_min() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: Some(3),
max_items: None,
};
assert_per_index_coverage(&schema);
}
#[test]
fn defaults_only_max() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::Boolean(BooleanInputSchema {
r#type: Default::default(),
description: None,
})),
min_items: None,
max_items: Some(8),
};
assert_per_index_coverage(&schema);
}
#[test]
fn enum_4_variants_three_lengths() {
let schema = ArrayInputSchema {
r#type: Default::default(),
description: None,
items: Box::new(InputSchema::String(StringInputSchema {
r#type: Default::default(),
description: None,
r#enum: Some(vec!["w".into(), "x".into(), "y".into(), "z".into()]),
})),
min_items: Some(1),
max_items: Some(3),
};
assert_per_index_coverage(&schema);
}