use crate::BoxGen;
use std::fmt::Debug;
pub type Distribution<T> = std::collections::BTreeMap<T, usize>;
pub fn single_value_distribution<T: Clone + Ord>(t: T) -> Distribution<T> {
even_distribution_of::<T>(&[t])
}
pub fn even_distribution_of<T: Clone + Ord>(ts: &[T]) -> Distribution<T> {
let mut result = Distribution::<T>::new();
for t in ts {
result.insert(t.clone(), 1);
}
result
}
pub fn distribution_from_pairs<T: Clone + Ord>(
t_ratio_pairs: &[(usize, T)],
) -> Distribution<T> {
let mut result = Distribution::<T>::new();
for (ratio, t) in t_ratio_pairs {
result.insert(t.clone(), *ratio);
}
result
}
fn collect_distribution<E>(gen_to_check: BoxGen<E>) -> Distribution<E>
where
E: Clone + Ord + 'static,
{
let mut result = Distribution::<E>::new();
for example in gen_to_check.examples(1234, 0..=1000).take(10_000) {
let count = result.get(&example).map_or(1, |n| n + 1);
result.insert(example, count);
}
result
}
pub fn assert_generator_has_distribution_within_percent<E>(
actual_gen: BoxGen<E>,
expected: Distribution<E>,
max_allowed_deviation_in_percent: f64,
) where
E: Clone + Ord + Debug + 'static,
{
let actual = collect_distribution(actual_gen);
let actual_total_count: usize = actual.values().sum();
let expected_total_count: usize = expected.values().sum();
for example in actual.keys() {
if !expected.contains_key(example) {
let key_count = actual.get(example).expect("Key should exist");
let percent = format_percent(*key_count, actual_total_count);
let formatted = format_distribution(&actual);
panic!(
"Unexpected generator example <{example:?}> with \
frequency {percent}.\n\
\n\
Got distribution:\n\
{formatted}"
)
}
}
for example in expected.keys() {
if !actual.contains_key(example) {
let key_count = expected.get(example).expect("Key should exist");
let percent = format_percent(*key_count, expected_total_count);
let formatted = format_distribution(&actual);
panic!(
"Generator never returned expected example <{example:?}>. \
Expected to have frequency {percent}.\n\
\n\
Got distribution:\n\
{formatted}"
)
}
}
for example in actual.keys() {
let actual_key_count = actual.get(example).expect("Key should exist");
let expected_key_count =
expected.get(example).expect("Key should exist");
let actual_percent =
calc_percent(*actual_key_count, actual_total_count);
let expected_percent =
calc_percent(*expected_key_count, expected_total_count);
if (expected_percent - actual_percent).abs()
> max_allowed_deviation_in_percent
{
let formatted = format_distribution(&actual);
panic!(
"Frequency of example <{example:?}> is expected to be \
{expected_percent:0.1}%, but actually is \
{actual_percent:0.1}%.\n\
\n\
Got distribution:\
{formatted}"
)
}
}
}
fn calc_percent(this_count: usize, total_count: usize) -> f64 {
this_count as f64 * 100.0 / total_count as f64
}
fn format_percent(this_count: usize, total_count: usize) -> String {
let p = calc_percent(this_count, total_count);
format! {"{p:0.1}%"}
}
fn format_distribution<T: Debug>(d: &Distribution<T>) -> String {
let total_count = d.values().sum();
d.iter().fold("{\n".to_string(), |result, (key, count)| {
let p = format_percent(*count, total_count);
result + &format! {" {key:?}\t{p}\n"}
}) + "}\n"
}
#[test]
#[should_panic(
expected = "Unexpected generator example <10> with frequency 33.3%."
)]
fn assert_should_fail_on_unexpected_additional_generator_example() {
let generator = crate::gens::fixed::in_loop(&[10, 11, 12]);
let expected = even_distribution_of::<u8>(&[11, 12]);
assert_generator_has_distribution_within_percent(generator, expected, 1.0);
}
#[test]
#[should_panic = "Generator never returned expected example <13>. Expected \
to have frequency 33.3%."]
fn assert_should_fail_on_missing_example_in_generator() {
let generator = crate::gens::fixed::in_loop(&[11, 12]);
let expected = even_distribution_of::<u8>(&[11, 12, 13]);
assert_generator_has_distribution_within_percent(generator, expected, 1.0);
}
#[test]
#[should_panic = "Frequency of example <11> is expected to be 75.0%, but \
actually is 50.0%."]
fn assert_should_fail_on_frequency_missmatch() {
let generator = crate::gens::fixed::in_loop(&[11, 12]);
let mut expected = Distribution::<u8>::new();
expected.insert(11, 3);
expected.insert(12, 1);
assert_generator_has_distribution_within_percent(generator, expected, 1.0);
}