#![cfg(test)]
use core::cmp::Ordering;
use std::collections::HashSet;
use crate::hash::FuzzyHashData;
use crate::hash::block::block_size;
use crate::hash::parser_state::{ParseError, ParseErrorKind, ParseErrorOrigin};
use crate::hash::tests::{FuzzyHashStringBytes, PARSER_ERR_CASES};
use crate::hash::test_utils::test_blockhash_contents_all;
use crate::hash_dual::{DualFuzzyHash, LongDualFuzzyHash, rle_encoding};
use crate::test_utils::test_for_each_type;
#[test]
fn data_model_new() {
macro_rules! test {
($ty: ty) => {
let typename = stringify!($ty);
let hash_new: $ty = <$ty>::new();
let hash_default: $ty = <$ty>::default();
let hash_cloned: $ty = hash_new.clone();
let hash_from_str: $ty = str::parse("3::").unwrap();
assert!(hash_new.is_valid(), "failed (1-1) on typename={}", typename);
assert!(hash_default.is_valid(), "failed (1-2) on typename={}", typename);
assert!(hash_cloned.is_valid(), "failed (1-3) on typename={}", typename);
assert!(hash_from_str.is_valid(), "failed (2) on typename={}", typename);
assert_eq!(hash_new, hash_default, "failed (3-1) on typename={}", typename);
assert_eq!(hash_new, hash_cloned, "failed (3-1) on typename={}", typename);
assert_eq!(hash_new, hash_from_str, "failed (3-1) on typename={}", typename);
};
}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn data_model_internal_ref() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
type NormalizedType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, true>;
let hash = <$ty>::new();
let norm_hash_1: &NormalizedType = &hash.norm_hash;
let norm_hash_2: &NormalizedType = hash.as_ref();
let norm_hash_3: &NormalizedType = hash.as_normalized();
let p1 = norm_hash_1 as *const NormalizedType;
let p2 = norm_hash_2 as *const NormalizedType;
let p3 = norm_hash_3 as *const NormalizedType;
assert_eq!(p1, p2, "failed (1) on typename={}", typename);
assert_eq!(p1, p3, "failed (2) on typename={}", typename);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn data_model_init_and_basic() {
test_blockhash_contents_all(&mut |bh1, bh2, bh1_norm, bh2_norm| {
let is_normalized = bh1 == bh1_norm && bh2 == bh2_norm;
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log(log_block_size).unwrap();
let bobj_raw = FuzzyHashStringBytes::new(log_block_size, bh1, bh2);
let bytes_raw = bobj_raw.as_bytes();
let bytes_str = core::str::from_utf8(bytes_raw).unwrap();
macro_rules! test {
($ty: ty) => {
let typename = stringify!($ty);
type FuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, true>;
type RawFuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, false>;
if (bh2.len() > <$ty>::MAX_BLOCK_HASH_SIZE_2) { break; }
let mut blockhash1 = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_1];
let mut blockhash2 = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_2];
let mut blockhash1_norm = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_1];
let mut blockhash2_norm = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_2];
blockhash1[..bh1.len()].copy_from_slice(bh1);
blockhash2[..bh2.len()].copy_from_slice(bh2);
blockhash1_norm[..bh1_norm.len()].copy_from_slice(bh1_norm);
blockhash2_norm[..bh2_norm.len()].copy_from_slice(bh2_norm);
let len_bh1_raw = u8::try_from(bh1.len()).unwrap();
let len_bh2_raw = u8::try_from(bh2.len()).unwrap();
let len_bh1_norm = u8::try_from(bh1_norm.len()).unwrap();
let len_bh2_norm = u8::try_from(bh2_norm.len()).unwrap();
let hash_raw = RawFuzzyHashType::new_from_internals_raw(log_block_size, &blockhash1, &blockhash2, len_bh1_raw, len_bh2_raw);
let hash_norm = FuzzyHashType::new_from_internals_raw(log_block_size, &blockhash1_norm, &blockhash2_norm, len_bh1_norm, len_bh2_norm);
let mut hash: $ty = <$ty>::new();
hash.init_from_raw_form(&hash_raw);
let hash1: $ty = <$ty>::from_raw_form(&hash_raw);
let hash2: $ty = <$ty>::from(hash_raw);
let hash3: $ty = <$ty>::from_bytes(bytes_raw).unwrap();
let hash4: $ty = str::parse::<$ty>(bytes_str).unwrap();
let hash5: $ty = hash1.clone();
let hash6: $ty = <$ty>::new_from_internals_internal(bs, bh1, bh2);
let hash7: $ty = <$ty>::new_from_internals(bs, bh1, bh2);
let hash8: $ty = <$ty>::new_from_internals_near_raw_internal(log_block_size, bh1, bh2);
let hash9: $ty = <$ty>::new_from_internals_near_raw(log_block_size, bh1, bh2);
assert_eq!(hash, hash1, "failed (1-1-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash2, "failed (1-1-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash3, "failed (1-1-3) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash4, "failed (1-1-4) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash5, "failed (1-1-5) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash6, "failed (1-1-6) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash7, "failed (1-1-7) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash8, "failed (1-1-8) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash9, "failed (1-1-9) on typename={}, bytes_str={:?}", typename, bytes_str);
#[cfg(feature = "unchecked")]
unsafe {
let hash_u6: $ty = <$ty>::new_from_internals_unchecked(bs, bh1, bh2);
let hash_u8: $ty = <$ty>::new_from_internals_near_raw_unchecked(log_block_size, bh1, bh2);
assert_eq!(hash, hash_u6, "failed (1-2-6) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash, hash_u8, "failed (1-2-8) on typename={}, bytes_str={:?}", typename, bytes_str);
}
assert!(hash.is_valid(), "failed (2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.norm_hash, hash_norm, "failed (3-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.rle_block1 == [0u8; <$ty>::RLE_BLOCK_SIZE_1] && hash.rle_block2 == [0u8; <$ty>::RLE_BLOCK_SIZE_2], is_normalized,
"failed (3-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.log_block_size(), log_block_size, "failed (4-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.block_size(), bs, "failed (4-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.as_normalized(), &hash_norm, "failed (4-3) on typename={}, bytes_str={:?}", typename, bytes_str);
};
}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
});
}
#[test]
fn data_model_init_from_normalized() {
test_blockhash_contents_all(&mut |bh1, bh2, bh1_norm, bh2_norm| {
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log(log_block_size).unwrap();
let bobj_raw = FuzzyHashStringBytes::new(log_block_size, bh1, bh2);
let bytes_str = core::str::from_utf8(bobj_raw.as_bytes()).unwrap();
macro_rules! test {
($ty: ty) => {
let typename = stringify!($ty);
type FuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, true>;
type RawFuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, false>;
if (bh2_norm.len() > <$ty>::MAX_BLOCK_HASH_SIZE_2) { break; }
let mut blockhash1_norm = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_1];
let mut blockhash2_norm = [0u8; <$ty>::MAX_BLOCK_HASH_SIZE_2];
blockhash1_norm[..bh1_norm.len()].copy_from_slice(bh1_norm);
blockhash2_norm[..bh2_norm.len()].copy_from_slice(bh2_norm);
let len_bh1_norm = u8::try_from(bh1_norm.len()).unwrap();
let len_bh2_norm = u8::try_from(bh2_norm.len()).unwrap();
let hash_raw = RawFuzzyHashType::new_from_internals_raw(log_block_size, &blockhash1_norm, &blockhash2_norm, len_bh1_norm, len_bh2_norm);
let hash_norm = FuzzyHashType::new_from_internals_raw(log_block_size, &blockhash1_norm, &blockhash2_norm, len_bh1_norm, len_bh2_norm);
let hash1: $ty = <$ty>::from_normalized(&hash_norm);
let hash2: $ty = <$ty>::from(hash_norm);
assert_eq!(hash1, hash2, "failed (1-1) on typename={}, bytes_str={:?}", typename, bytes_str);
let hash: $ty = hash1;
assert!(hash.is_valid(), "failed (2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.rle_block1, [0u8; <$ty>::RLE_BLOCK_SIZE_1], "failed (3-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.rle_block2, [0u8; <$ty>::RLE_BLOCK_SIZE_2], "failed (3-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.norm_hash, hash_norm, "failed (4-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.to_raw_form(), hash_raw, "failed (4-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.log_block_size(), log_block_size, "failed (5-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.block_size(), bs, "failed (5-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(hash.as_normalized(), &hash_norm, "failed (5-3) on typename={}, bytes_str={:?}", typename, bytes_str);
};
}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
});
}
#[test]
fn data_model_corresponding_fuzzy_hashes() {
test_blockhash_contents_all(&mut |bh1, bh2, bh1_norm, bh2_norm| {
let is_normalized = bh1 == bh1_norm && bh2 == bh2_norm;
for log_block_size in block_size::RANGE_LOG_VALID {
let bobj_raw = FuzzyHashStringBytes::new(log_block_size, bh1, bh2);
let bobj_norm = FuzzyHashStringBytes::new(log_block_size, bh1_norm, bh2_norm);
let bytes_raw = bobj_raw.as_bytes();
let bytes_norm = bobj_norm.as_bytes();
let bytes_str = core::str::from_utf8(bytes_raw).unwrap();
macro_rules! test {
($ty: ty) => {
let typename = stringify!($ty);
type FuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, true>;
type RawFuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, false>;
if (bh2.len() > <$ty>::MAX_BLOCK_HASH_SIZE_2) { break; }
let hash_raw: RawFuzzyHashType = RawFuzzyHashType::from_bytes(bytes_raw).unwrap();
let hash_raw_from_norm: RawFuzzyHashType = RawFuzzyHashType::from_bytes(bytes_norm).unwrap();
let hash_norm: FuzzyHashType = FuzzyHashType::from_bytes(bytes_norm).unwrap();
let mut dual_hash_from_raw: $ty = <$ty>::from(hash_raw);
let mut dual_hash_from_norm: $ty = <$ty>::from(hash_norm);
let dual_hash_norm: $ty = dual_hash_from_norm;
assert!(dual_hash_from_raw.is_valid(), "failed (2-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert!(dual_hash_from_norm.is_valid(), "failed (2-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.norm_hash, hash_norm, "failed (3-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.norm_hash, hash_norm, "failed (3-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.is_normalized(), is_normalized, "failed (3-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert!(dual_hash_from_norm.is_normalized(), "failed (3-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.as_normalized(), &hash_norm, "failed (4-1-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.to_normalized(), hash_norm, "failed (4-1-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.to_raw_form(), hash_raw, "failed (4-1-3) on typename={}, bytes_str={:?}", typename, bytes_str);
let mut hash_raw_dest: RawFuzzyHashType = RawFuzzyHashType::new();
dual_hash_from_raw.into_mut_raw_form(&mut hash_raw_dest);
assert_eq!(hash_raw_dest, hash_raw, "failed (4-1-4) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.as_normalized(), &hash_norm, "failed (4-2-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.to_normalized(), hash_norm, "failed (4-2-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.to_raw_form(), hash_raw_from_norm, "failed (4-2-3) on typename={}, bytes_str={:?}", typename, bytes_str);
let mut hash_raw_dest: RawFuzzyHashType = RawFuzzyHashType::new();
dual_hash_from_norm.into_mut_raw_form(&mut hash_raw_dest);
assert_eq!(hash_raw_dest, hash_raw_from_norm, "failed (4-2-4) on typename={}, bytes_str={:?}", typename, bytes_str);
dual_hash_from_raw.normalize_in_place();
dual_hash_from_norm.normalize_in_place();
assert!(dual_hash_from_raw.is_valid(), "failed (5-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert!(dual_hash_from_norm.is_valid(), "failed (5-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_norm, dual_hash_from_raw, "failed (5-3) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_norm, dual_hash_from_norm, "failed (5-4) on typename={}, bytes_str={:?}", typename, bytes_str);
assert!(dual_hash_norm.is_normalized(), "failed (5-5) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_norm.as_normalized(), &hash_norm, "failed (6-1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_norm.to_normalized(), hash_norm, "failed (6-2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_norm.to_raw_form(), hash_raw_from_norm, "failed (6-3) on typename={}, bytes_str={:?}", typename, bytes_str);
let mut hash_raw_dest: RawFuzzyHashType = RawFuzzyHashType::new();
dual_hash_norm.into_mut_raw_form(&mut hash_raw_dest);
assert_eq!(hash_raw_dest, hash_raw_from_norm, "failed (6-4) on typename={}, bytes_str={:?}", typename, bytes_str);
};
}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
});
}
#[cfg(feature = "alloc")]
#[test]
fn data_model_corresponding_fuzzy_hash_strings() {
test_blockhash_contents_all(&mut |bh1, bh2, bh1_norm, bh2_norm| {
for log_block_size in block_size::RANGE_LOG_VALID {
let bobj_raw = FuzzyHashStringBytes::new(log_block_size, bh1, bh2);
let bobj_norm = FuzzyHashStringBytes::new(log_block_size, bh1_norm, bh2_norm);
let bytes_raw = bobj_raw.as_bytes();
let bytes_norm = bobj_norm.as_bytes();
let bytes_str = core::str::from_utf8(bytes_raw).unwrap();
macro_rules! test {
($ty: ty) => {
let typename = stringify!($ty);
type FuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, true>;
type RawFuzzyHashType = FuzzyHashData<{<$ty>::MAX_BLOCK_HASH_SIZE_1}, {<$ty>::MAX_BLOCK_HASH_SIZE_2}, false>;
if (bh2.len() > <$ty>::MAX_BLOCK_HASH_SIZE_2) { break; }
let hash_raw: RawFuzzyHashType = RawFuzzyHashType::from_bytes(bytes_raw).unwrap();
let hash_norm: FuzzyHashType = FuzzyHashType::from_bytes(bytes_norm).unwrap();
let str_raw = hash_raw.to_string();
let str_norm = hash_norm.to_string();
let dual_hash_from_raw: $ty = <$ty>::from(hash_raw);
let dual_hash_from_norm: $ty = <$ty>::from(hash_norm);
assert_eq!(dual_hash_from_raw.to_raw_form_string(), str_raw, "failed (1) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_raw.to_normalized_string(), str_norm, "failed (2) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.to_raw_form_string(), str_norm, "failed (3) on typename={}, bytes_str={:?}", typename, bytes_str);
assert_eq!(dual_hash_from_norm.to_normalized_string(), str_norm, "failed (4) on typename={}, bytes_str={:?}", typename, bytes_str);
};
}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
});
}
#[test]
fn data_model_corruption() {
assert_eq!(rle_encoding::BITS_POSITION, 6);
assert_eq!(rle_encoding::MAX_RUN_LENGTH, 4);
macro_rules! hash_is_invalid {
($ty: ty, $hash: expr, $fmt: literal) => {
let typename = stringify!($ty);
assert!(!$hash.is_valid(), $fmt, 1, typename);
assert!(format!("{:?}", $hash).starts_with("FuzzyHashDualData { ILL_FORMED: true,"),
$fmt, 2, typename);
};
($ty: ty, $hash: expr, $fmt: literal, $($arg:tt)+) => {
let typename = stringify!($ty);
assert!(!$hash.is_valid(), $fmt, 1, typename, $($arg)+);
assert!(format!("{:?}", $hash).starts_with("FuzzyHashDualData { ILL_FORMED: true,"),
$fmt, 2, typename, $($arg)+);
};
}
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
let mut hash: $ty = <$ty>::new();
hash.norm_hash.blockhash1[..7].clone_from_slice(&[1, 1, 1, 2, 3, 3, 3]);
hash.norm_hash.blockhash2[..7].clone_from_slice(&[4, 4, 4, 5, 6, 6, 6]);
hash.norm_hash.len_blockhash1 = 7;
hash.norm_hash.len_blockhash2 = 7;
hash.norm_hash.log_blocksize = 0;
assert!(hash.is_valid(), "failed (1-1) on typename={}", typename);
assert!(hash.norm_hash.is_valid(), "failed (1-2) on typename={}", typename);
for log_block_size in u8::MIN..=u8::MAX {
hash.norm_hash.log_blocksize = log_block_size;
let is_valid = block_size::is_log_valid(log_block_size);
assert_eq!(hash.is_valid(), is_valid, "failed (1-3) on typename={}", typename);
assert_eq!(hash.norm_hash.is_valid(), is_valid, "failed (1-4) on typename={}", typename);
}
hash.norm_hash.log_blocksize = 0; assert!(hash.rle_block1.iter().all(|&x| x == 0), "failed (2-1) on typename={}", typename);
assert!(hash.rle_block2.iter().all(|&x| x == 0), "failed (2-2) on typename={}", typename);
{
for index in 1..<$ty>::RLE_BLOCK_SIZE_1 {
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
let mut hash = hash;
hash.rle_block1[index] = 1 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (3-1-{}) on typename={}, index={}, length={}", index, length);
}
}
}
{
for index in 1..<$ty>::RLE_BLOCK_SIZE_2 {
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
let mut hash = hash;
hash.rle_block2[index] = 1 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (3-2-{}) on typename={}, index={}, length={}", index, length);
}
}
}
{
let mut hash = hash;
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
hash.rle_block1[0] = 6 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (4-1-1) on typename={}, length={}", typename, length);
}
for index in hash.norm_hash.len_blockhash1..=rle_encoding::MASK_POSITION {
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
hash.rle_block1[0] = index | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (4-1-2-{}) on typename={}, index={}, length={}", index, length);
}
}
}
{
let mut hash = hash;
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
hash.rle_block2[0] = 6 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (4-2-1) on typename={}, length={}", typename, length);
}
for index in hash.norm_hash.len_blockhash2..=rle_encoding::MASK_POSITION {
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
hash.rle_block2[0] = index | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (4-2-2-{}) on typename={}, index={}, length={}", index, length);
}
}
}
{
let mut hash = hash;
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
if length != 0 {
hash.rle_block1[0] = 0 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-1-1-{}) on typename={}, length={}", length); }
hash.rle_block1[0] = 1 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-1-2-{}) on typename={}, length={}", length); hash.rle_block1[0] = 2 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (5-1-3) on typename={}", typename); hash.rle_block1[0] = 3 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-1-4-{}) on typename={}, length={}", length); hash.rle_block1[0] = 4 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-1-5-{}) on typename={}, length={}", length); hash.rle_block1[0] = 5 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-1-6-{}) on typename={}, length={}", length); hash.rle_block1[0] = 6 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (5-1-7) on typename={}", typename); }
}
{
let mut hash = hash;
for length in 0..rle_encoding::MAX_RUN_LENGTH as u8 {
if length != 0 {
hash.rle_block2[0] = 0 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-2-1-{}) on typename={}, length={}", length); }
hash.rle_block2[0] = 1 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-2-2-{}) on typename={}, length={}", length); hash.rle_block2[0] = 2 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (5-2-3) on typename={}", typename); hash.rle_block2[0] = 3 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-2-4-{}) on typename={}, length={}", length); hash.rle_block2[0] = 4 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-2-5-{}) on typename={}, length={}", length); hash.rle_block2[0] = 5 | (length << rle_encoding::BITS_POSITION);
hash_is_invalid!($ty, hash, "failed (5-2-6-{}) on typename={}, length={}", length); hash.rle_block2[0] = 6 | (length << rle_encoding::BITS_POSITION);
assert!(hash.is_valid(), "failed (5-2-7) on typename={}", typename); }
}
{
let mut hash = hash;
hash.rle_block1[0] = 2;
hash.rle_block1[1] = 6;
assert!(hash.is_valid(), "failed (6-1-1) on typename={}", typename);
hash.rle_block1[0] = 6;
hash.rle_block1[1] = 2;
hash_is_invalid!($ty, hash, "failed (6-1-2-{}) on typename={}");
}
{
let mut hash = hash;
hash.rle_block2[0] = 2;
hash.rle_block2[1] = 6;
assert!(hash.is_valid(), "failed (6-2-1) on typename={}", typename);
hash.rle_block2[0] = 6;
hash.rle_block2[1] = 2;
hash_is_invalid!($ty, hash, "failed (6-2-2-{}) on typename={}");
}
{
let mut hash = hash;
hash.rle_block1[0] = 0xc2; hash.rle_block1[1] = 0x02; assert!(hash.is_valid(), "failed (7-1-1) on typename={}", typename);
hash.rle_block1[0] = 0x02; hash.rle_block1[1] = 0xc2; hash_is_invalid!($ty, hash, "failed (7-1-2-{}) on typename={}");
hash.rle_block1[0] = 0x42; hash.rle_block1[1] = 0x82; hash_is_invalid!($ty, hash, "failed (7-1-3-{}) on typename={}");
hash.rle_block1[0] = 0x82; hash.rle_block1[1] = 0x42; hash_is_invalid!($ty, hash, "failed (7-1-4-{}) on typename={}");
hash.rle_block1[0] = 0xc2; hash.rle_block1[1] = 0x02; assert!(hash.is_valid(), "failed (7-1-5) on typename={}", typename);
hash.rle_block1[2] = 0x06; assert!(hash.is_valid(), "failed (7-1-6) on typename={}", typename);
}
{
let mut hash = hash;
hash.rle_block2[0] = 0xc2; hash.rle_block2[1] = 0x02; assert!(hash.is_valid(), "failed (7-2-1) on typename={}", typename);
hash.rle_block2[0] = 0x02; hash.rle_block2[1] = 0xc2; hash_is_invalid!($ty, hash, "failed (7-2-2-{}) on typename={}");
hash.rle_block2[0] = 0x42; hash.rle_block2[1] = 0x82; hash_is_invalid!($ty, hash, "failed (7-2-3-{}) on typename={}");
hash.rle_block2[0] = 0x82; hash.rle_block2[1] = 0x42; hash_is_invalid!($ty, hash, "failed (7-2-4-{}) on typename={}");
hash.rle_block2[0] = 0xc2; hash.rle_block2[1] = 0x02; assert!(hash.is_valid(), "failed (7-2-5) on typename={}", typename);
hash.rle_block2[2] = 0x06; assert!(hash.is_valid(), "failed (7-2-6) on typename={}", typename);
}
{
let mut hash = hash;
hash.rle_block1.fill(0xc2); hash_is_invalid!($ty, hash, "failed (8-1-1-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 1] = 0x82;
hash_is_invalid!($ty, hash, "failed (8-1-2-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 1] = 0x42;
hash_is_invalid!($ty, hash, "failed (8-1-3-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 1] = 0x02;
hash_is_invalid!($ty, hash, "failed (8-1-4-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 1] = 0x00;
hash.rle_block1[hash.rle_block1.len() - 2] = 0xc2;
hash_is_invalid!($ty, hash, "failed (8-1-5-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 2] = 0x82;
hash_is_invalid!($ty, hash, "failed (8-1-6-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 2] = 0x42;
hash_is_invalid!($ty, hash, "failed (8-1-7-{}) on typename={}"); hash.rle_block1[hash.rle_block1.len() - 2] = 0x02;
assert!(hash.is_valid(), "failed (8-1-8) on typename={}", typename); }
{
let mut hash = hash;
hash.rle_block2.fill(0xc2); hash_is_invalid!($ty, hash, "failed (8-2-1-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 1] = 0x82;
hash_is_invalid!($ty, hash, "failed (8-2-2-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 1] = 0x42;
hash_is_invalid!($ty, hash, "failed (8-2-3-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 1] = 0x02;
hash_is_invalid!($ty, hash, "failed (8-2-4-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 1] = 0x00;
hash.rle_block2[hash.rle_block2.len() - 2] = 0xc2;
hash_is_invalid!($ty, hash, "failed (8-2-5-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 2] = 0x82;
hash_is_invalid!($ty, hash, "failed (8-2-6-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 2] = 0x42;
hash_is_invalid!($ty, hash, "failed (8-2-7-{}) on typename={}"); hash.rle_block2[hash.rle_block2.len() - 2] = 0x02;
assert!(hash.is_valid(), "failed (8-2-8) on typename={}", typename); }
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn parse_overflow_examples_long_and_short() {
const HASH_STR_1: &str = "3\
:0123456789012345678901234567890123456789012345678901234567890123\
:012345678901234567890123456789012";
assert_eq!(
str::parse::<DualFuzzyHash>(HASH_STR_1),
Err(ParseError(ParseErrorKind::BlockHashIsTooLong, ParseErrorOrigin::BlockHash2, 2 + 64 + 1 + 32))
);
assert!(str::parse::<LongDualFuzzyHash>(HASH_STR_1).is_ok());
const HASH_STR_2: &str = "3\
:0123456789012345678901234567890123456789012345678901234567890123\
:01234567890123456789012345678901234567890123456789012345678901234";
assert_eq!(
str::parse::<DualFuzzyHash>(HASH_STR_2),
Err(ParseError(ParseErrorKind::BlockHashIsTooLong, ParseErrorOrigin::BlockHash2, 2 + 64 + 1 + 32))
);
assert_eq!(
str::parse::<LongDualFuzzyHash>(HASH_STR_2),
Err(ParseError(ParseErrorKind::BlockHashIsTooLong, ParseErrorOrigin::BlockHash2, 2 + 64 + 1 + 64))
);
}
#[test]
fn parse_errors() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
assert_eq!(
<$ty>::from_bytes(b""),
Err(ParseError(ParseErrorKind::UnexpectedEndOfString, ParseErrorOrigin::BlockSize, 0)),
"failed on typename={}", typename
);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn parse_patterns() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
for &(hash_str, result_short, result_long) in PARSER_ERR_CASES {
let err = if <$ty>::IS_LONG_FORM { result_long } else { result_short };
let mut index1 = 0;
let mut index2 = usize::MAX;
assert_eq!(
<$ty>::from_bytes(hash_str.as_bytes()).map(|_| ()), err,
"failed (1-1) on typename={}, hash_str={:?}", typename, hash_str
);
assert_eq!(
<$ty>::from_bytes_with_last_index(hash_str.as_bytes(), &mut index1).map(|_| ()), err,
"failed (1-2-1-1-1) on typename={}, hash_str={:?}", typename, hash_str
);
assert_eq!(
<$ty>::from_bytes_with_last_index(hash_str.as_bytes(), &mut index2).map(|_| ()), err,
"failed (1-2-1-1-2) on typename={}, hash_str={:?}", typename, hash_str
);
match err {
Ok(_) => {
assert_eq!(index1, index2,
"failed (1-2-1-2) on typename={}, hash_str={:?}", typename, hash_str);
if index1 != hash_str.len() {
assert!(hash_str.as_bytes()[index1] == b',',
"failed (1-2-2-1) on typename={}, hash_str={:?}", typename, hash_str);
assert_eq!(hash_str.find(','), Some(index1),
"failed (1-2-2-2) on typename={}, hash_str={:?}", typename, hash_str);
}
}
Err(_) => {
assert_eq!(index1, 0,
"failed (1-2-2-3-1) on typename={}, hash_str={:?}", typename, hash_str);
assert_eq!(index2, usize::MAX,
"failed (1-2-2-3-2) on typename={}, hash_str={:?}", typename, hash_str);
}
}
assert_eq!(
str::parse::<$ty>(hash_str).map(|_| ()), err,
"failed (2) on typename={}, hash_str={:?}", typename, hash_str
);
}
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn cover_hash() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
let mut hashes = HashSet::<$ty>::new();
assert!(hashes.insert(<$ty>::new()), "failed (1) on typename={}", typename);
assert!(!hashes.insert(<$ty>::new()), "failed (2) on typename={}", typename);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn ord_and_sorting() {
let sorted_dict = vec![
vec![
"ABBBR",
"ABBBBR",
],
vec![
"ABBBRA",
"ABBBBRA",
],
vec![
"ABBBRAA",
"ABBBBRAA",
],
vec![
"ABBBREVIATES",
"ABBBBREVIATES",
],
vec!["ABBR"],
vec!["ABBRA"],
vec!["ABBRAA"],
vec![
"ABBRAAA",
"ABBRAAAA",
],
vec!["ABBREVIATES"],
vec![
"abbbr",
"abbbbr",
],
vec![
"abbbrA",
"abbbbrA",
],
vec![
"abbbrAA",
"abbbbrAA",
],
vec![
"abbbreviates",
"abbbbreviates",
],
vec!["abbr"],
vec!["abbrA"],
vec!["abbrAA"],
vec![
"abbrAAA",
"abbrAAAA",
],
vec!["abbreviates"],
vec![
"000123",
"0000123",
],
vec!["0123"],
vec!["0123A"],
vec!["0123AA"],
vec![
"0123AAA",
"0123AAAA",
],
vec!["01234567"],
];
{
let mut strings = HashSet::<&str>::new();
for bh in &sorted_dict {
for &bh_entry in bh {
assert!(strings.insert(bh_entry), "failed on bh_entry={:?}", bh_entry);
}
}
}
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
let mut hashes: Vec<$ty> = Vec::new();
for log_block_size in 0u8..=3 {
for bh1 in &sorted_dict {
for bh2 in &sorted_dict {
for &bh1_entry in bh1 {
for &bh2_entry in bh2 {
let mut s = block_size::from_log_internal(log_block_size).to_string();
s += ":";
s += bh1_entry;
s += ":";
s += bh2_entry;
hashes.push(str::parse(s.as_str()).unwrap());
}
}
}
}
}
for (i1, h1) in hashes.iter().enumerate() {
for (i2, h2) in hashes.iter().enumerate() {
match h1.as_normalized().cmp(h2.as_normalized()) {
Ordering::Equal => {
assert_eq!(h1.cmp(&h2) == Ordering::Equal, i1 == i2,
"failed (1) on typename={}, i1={}, i2={}, h1={:?}, h2={:?}", typename, i1, i2, h1, h2);
},
Ordering::Greater => {
assert_eq!(h1.cmp(&h2), Ordering::Greater,
"failed (2-1) on typename={}, i1={}, i2={}, h1={:?}, h2={:?}", typename, i1, i2, h1, h2);
assert!(i1 > i2,
"failed (2-2) on typename={}, i1={}, i2={}, h1={:?}, h2={:?}", typename, i1, i2, h1, h2);
},
Ordering::Less => {
assert_eq!(h1.cmp(&h2), Ordering::Less,
"failed (3-1) on typename={}, i1={}, i2={}, h1={:?}, h2={:?}", typename, i1, i2, h1, h2);
assert!(i1 < i2,
"failed (3-2) on typename={}, i1={}, i2={}, h1={:?}, h2={:?}", typename, i1, i2, h1, h2);
},
}
}
}
let cloned = hashes.clone();
hashes.reverse();
hashes.sort();
assert_eq!(hashes, cloned, "failed (4) on typename={}", typename);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[test]
fn impl_debug() {
let hash = DualFuzzyHash::new();
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
LONG: false, \
block_size: 3, \
blockhash1: \"\", \
blockhash2: \"\", \
rle_block1: [], \
rle_block2: [] \
}"
);
let hash = LongDualFuzzyHash::new();
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
LONG: true, \
block_size: 3, \
blockhash1: \"\", \
blockhash2: \"\", \
rle_block1: [], \
rle_block2: [] \
}"
);
let hash: DualFuzzyHash = str::parse("3\
:AAAA\
:01234567BBBBB").unwrap();
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
LONG: false, \
block_size: 3, \
blockhash1: \"AAA\", \
blockhash2: \"01234567BBB\", \
rle_block1: [\
RLE(2, 1)\
], \
rle_block2: [\
RLE(10, 2)\
] \
}"
);
let hash: DualFuzzyHash = str::parse("3\
:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB").unwrap();
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
LONG: false, \
block_size: 3, \
blockhash1: \"AAA\", \
blockhash2: \"BBB\", \
rle_block1: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
], \
rle_block2: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
] \
}"
);
let hash: LongDualFuzzyHash = str::parse("3\
:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB").unwrap();
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
LONG: true, \
block_size: 3, \
blockhash1: \"AAA\", \
blockhash2: \"BBB\", \
rle_block1: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
], \
rle_block2: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
] \
}"
);
let mut hash: DualFuzzyHash = str::parse("3\
:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB").unwrap();
hash.rle_block1[4] = 0;
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
ILL_FORMED: true, \
LONG: false, \
log_blocksize: 0, \
len_blockhash1: 3, \
len_blockhash2: 3, \
blockhash1: [\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\
], \
blockhash2: [\
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\
], \
rle_block1: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLENull, RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
], \
rle_block2: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 1)\
] \
}"
);
let mut hash: DualFuzzyHash = str::parse("3\
:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB").unwrap();
hash.rle_block1[15] = rle_encoding::encode(2, 4);
assert_eq!(
format!("{:?}", hash),
"FuzzyHashDualData { \
ILL_FORMED: true, \
LONG: false, \
log_blocksize: 0, \
len_blockhash1: 3, \
len_blockhash2: 3, \
blockhash1: [\
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\
], \
blockhash2: [\
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\
], \
rle_block1: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4)\
], \
rle_block2: [\
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLE(2, 4), \
RLE(2, 4), RLE(2, 4), RLE(2, 4), RLENull\
] \
}"
);
}
const TEST_VECTOR_SHORT_FHASH_NORM_0: &str = "3:ABBCCCDDDDEEEEE:555554444333221";
const TEST_VECTOR_SHORT_FHASH_NORM_1: &str = "3:ABBCCCDDDEEE:555444333221";
#[test]
fn impl_display() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
let hash = str::parse::<$ty>(TEST_VECTOR_SHORT_FHASH_NORM_0).unwrap();
assert_eq!(
format!("{}", hash),
format!("{{{}|{}}}", TEST_VECTOR_SHORT_FHASH_NORM_1, TEST_VECTOR_SHORT_FHASH_NORM_0),
"failed on typename={}", typename
);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}
#[cfg(feature = "alloc")]
#[test]
fn impl_to_string() {
macro_rules! test {($ty: ty) => {
let typename = stringify!($ty);
let hash = str::parse::<$ty>(TEST_VECTOR_SHORT_FHASH_NORM_0).unwrap();
assert_eq!(hash.to_raw_form_string(), TEST_VECTOR_SHORT_FHASH_NORM_0, "failed (1) on typename={}", typename);
assert_eq!(hash.to_normalized_string(), TEST_VECTOR_SHORT_FHASH_NORM_1, "failed (2) on typename={}", typename);
}}
test_for_each_type!(test, [DualFuzzyHash, LongDualFuzzyHash]);
}