#[macro_export]
macro_rules! test_dictionary_contains {
(
$( $test_name:ident => $dict_expr:expr, $insert_method:tt ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn [<$test_name _inserted_terms_found>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 1..=30)
) {
let dict = $dict_expr;
let unique_terms: std::collections::HashSet<_> = terms.into_iter().collect();
test_dictionary_contains!(@insert dict, unique_terms, $insert_method);
for term in &unique_terms {
prop_assert!(
dict.contains(term),
"Term '{}' should be found by contains()",
term
);
}
}
#[test]
fn [<$test_name _nonexistent_terms_not_found>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 10), 1..=20),
missing in $crate::common::strategies::ascii_term(11, 15)
) {
let dict = $dict_expr;
let unique_terms: std::collections::HashSet<_> = terms.into_iter().collect();
test_dictionary_contains!(@insert dict, unique_terms, $insert_method);
if !unique_terms.contains(&missing) {
prop_assert!(
!dict.contains(&missing),
"Non-inserted term '{}' should not be found",
missing
);
}
}
}
}
)+
};
(@insert $dict:ident, $terms:ident, insert) => {
for term in &$terms {
$dict.insert(term);
}
};
(@insert $dict:ident, $terms:ident, from_terms) => {
let _ = &$terms; };
}
#[macro_export]
macro_rules! test_mutable_dictionary {
(
$( $test_name:ident => $dict_expr:expr ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn [<$test_name _insert_then_contains>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 1..=30)
) {
let dict = $dict_expr;
let unique_terms: std::collections::HashSet<_> = terms.into_iter().collect();
for term in &unique_terms {
dict.insert(term);
}
for term in &unique_terms {
prop_assert!(
dict.contains(term),
"Inserted term '{}' should be found",
term
);
}
}
#[test]
fn [<$test_name _remove_then_not_contains>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 5..=30)
) {
let dict = $dict_expr;
let unique_terms: Vec<_> = terms.into_iter()
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
for term in &unique_terms {
dict.insert(term);
}
let to_remove: Vec<_> = unique_terms.iter().take(unique_terms.len() / 2).cloned().collect();
for term in &to_remove {
dict.remove(term);
}
for term in &to_remove {
prop_assert!(
!dict.contains(term),
"Removed term '{}' should not be found",
term
);
}
for term in unique_terms.iter().skip(unique_terms.len() / 2) {
prop_assert!(
dict.contains(term),
"Non-removed term '{}' should still be found",
term
);
}
}
#[test]
fn [<$test_name _insert_remove_reinsert>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 1..=20)
) {
let dict = $dict_expr;
let unique_terms: Vec<_> = terms.into_iter()
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
for term in &unique_terms {
dict.insert(term);
}
for term in &unique_terms {
dict.remove(term);
}
for term in &unique_terms {
prop_assert!(
!dict.contains(term),
"Term '{}' should be gone after remove",
term
);
}
for term in &unique_terms {
dict.insert(term);
}
for term in &unique_terms {
prop_assert!(
dict.contains(term),
"Term '{}' should be back after reinsert",
term
);
}
}
}
}
)+
};
}
#[macro_export]
macro_rules! test_mapped_dictionary {
(
$( $test_name:ident => $dict_expr:expr ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn [<$test_name _value_roundtrip>](
pairs in prop::collection::vec(
($crate::common::strategies::ascii_term(1, 15), any::<u32>()),
1..=30
)
) {
use libdictenstein::MappedDictionary;
let dict = $dict_expr;
let expected: std::collections::HashMap<String, u32> = pairs.into_iter().collect();
for (term, value) in &expected {
dict.insert_with_value(term, *value);
}
for (term, expected_value) in &expected {
let actual = dict.get_value(term);
prop_assert_eq!(
actual,
Some(*expected_value),
"Term '{}' should have value {}",
term,
expected_value
);
}
}
#[test]
fn [<$test_name _value_overwrite>](
term in $crate::common::strategies::ascii_term(1, 15),
value1 in any::<u32>(),
value2 in any::<u32>()
) {
use libdictenstein::MappedDictionary;
let dict = $dict_expr;
dict.insert_with_value(&term, value1);
prop_assert_eq!(dict.get_value(&term), Some(value1));
dict.insert_with_value(&term, value2);
prop_assert_eq!(
dict.get_value(&term),
Some(value2),
"Value should be updated to new value"
);
}
}
}
)+
};
}
#[macro_export]
macro_rules! test_compactable_dictionary {
(
$( $test_name:ident => $dict_expr:expr ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn [<$test_name _compact_preserves_terms>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 10..=50)
) {
let dict = $dict_expr;
let unique_terms: std::collections::HashSet<_> = terms.into_iter().collect();
for term in &unique_terms {
dict.insert(term);
}
let to_remove: Vec<_> = unique_terms.iter().take(unique_terms.len() / 3).cloned().collect();
let removed_set: std::collections::HashSet<_> = to_remove.iter().cloned().collect();
for term in &to_remove {
dict.remove(term);
}
let remaining: std::collections::HashSet<_> = unique_terms
.difference(&removed_set)
.cloned()
.collect();
dict.compact();
for term in &remaining {
prop_assert!(
dict.contains(term),
"Term '{}' should exist after compaction",
term
);
}
for term in &to_remove {
prop_assert!(
!dict.contains(term),
"Removed term '{}' should not reappear after compaction",
term
);
}
}
}
}
)+
};
}
#[macro_export]
macro_rules! test_dictionary_iterator {
(
$( $test_name:ident => $dict_expr:expr, iter_method = $iter_method:ident, to_string = $to_string:expr ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(50))]
#[test]
fn [<$test_name _iterator_completeness>](
terms in prop::collection::vec($crate::common::strategies::ascii_term(1, 15), 1..=30)
) {
let dict = $dict_expr;
let expected: std::collections::HashSet<String> = terms.into_iter().collect();
for term in &expected {
dict.insert(term);
}
let iterated: std::collections::HashSet<String> = dict.$iter_method()
.map($to_string)
.collect();
prop_assert_eq!(
&iterated,
&expected,
"Iterator should return exactly the inserted terms"
);
}
}
}
)+
};
}
#[macro_export]
macro_rules! test_unicode_dictionary {
(
$( $test_name:ident => $dict_expr:expr ),+ $(,)?
) => {
$(
paste::paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(30))]
#[test]
fn [<$test_name _unicode_roundtrip>](
terms in prop::collection::vec($crate::common::strategies::unicode_term(1, 10), 1..=20)
) {
let dict = $dict_expr;
let unique_terms: std::collections::HashSet<_> = terms.into_iter().collect();
for term in &unique_terms {
dict.insert(term);
}
for term in &unique_terms {
prop_assert!(
dict.contains(term),
"Unicode term '{}' should be found",
term
);
}
}
#[test]
fn [<$test_name _emoji_handling>](
base in $crate::common::strategies::ascii_term(1, 5)
) {
let dict = $dict_expr;
let emoji_terms = vec![
format!("{}🚀", base),
format!("🎉{}", base),
format!("{}💡{}", base, base),
format!("{}🔥🎨", base),
];
for term in &emoji_terms {
dict.insert(term);
}
for term in &emoji_terms {
prop_assert!(
dict.contains(term),
"Emoji term '{}' should be found",
term
);
}
}
}
}
)+
};
}