#![cfg(test)]
#![cfg_attr(not(feature = "maint-lints"), allow(clippy::needless_borrow))]
#![cfg_attr(not(feature = "maint-lints"), allow(clippy::needless_borrows_for_generic_args))]
use crate::compare::FuzzyHashCompareTarget;
use crate::compare::position_array::{
BlockHashPositionArrayData,
BlockHashPositionArrayDataMut,
BlockHashPositionArrayRef,
};
use crate::hash::{FuzzyHash, RawFuzzyHash, LongFuzzyHash, LongRawFuzzyHash};
use crate::hash::block::{block_size, block_hash, BlockSizeRelation};
use crate::hash::test_utils::{
test_blockhash_contents_all,
test_blockhash_contents_no_sequences
};
use crate::hash_dual::{DualFuzzyHash, LongDualFuzzyHash};
use crate::test_utils::assert_fits_in;
use crate::utils::u64_lsb_ones;
#[test]
fn common_prerequisites() {
assert_fits_in!(block_size::NUM_VALID, u8);
assert_fits_in!(block_hash::FULL_SIZE, u8);
}
#[test]
fn data_model_internal_refs() {
type BlockHashPointerType = *const [u64; block_hash::ALPHABET_SIZE];
type LengthPointerType = *const u8;
let mut hash = FuzzyHashCompareTarget::new();
{
let p_blockhash1_1 = &hash.blockhash1 as BlockHashPointerType;
let p_blockhash1_2 = hash.block_hash_1().representation() as BlockHashPointerType;
let p_blockhash1_3 = hash.block_hash_1_internal().representation() as BlockHashPointerType;
let p_blockhash1_4 = hash.block_hash_1_mut().representation() as BlockHashPointerType;
let p_blockhash1_5 = hash.block_hash_1_mut().representation_mut() as BlockHashPointerType;
assert_eq!(p_blockhash1_1, p_blockhash1_2);
assert_eq!(p_blockhash1_1, p_blockhash1_3);
assert_eq!(p_blockhash1_1, p_blockhash1_4);
assert_eq!(p_blockhash1_1, p_blockhash1_5);
}
{
let p_len_blockhash1_1 = &hash.len_blockhash1 as LengthPointerType;
let p_len_blockhash1_2 = unsafe {
let p_block_hash_1 =
(&hash.block_hash_1() as *const _) as *const BlockHashPositionArrayRef;
(*p_block_hash_1).1 as LengthPointerType
};
let p_len_blockhash1_3 = unsafe {
let p_block_hash_1 =
(&hash.block_hash_1_internal() as *const _) as *const BlockHashPositionArrayRef;
(*p_block_hash_1).1 as LengthPointerType
};
let p_len_blockhash1_4 = hash.block_hash_1_mut().len_mut() as LengthPointerType;
assert_eq!(p_len_blockhash1_1, p_len_blockhash1_2);
assert_eq!(p_len_blockhash1_1, p_len_blockhash1_3);
assert_eq!(p_len_blockhash1_1, p_len_blockhash1_4);
}
{
let p_blockhash2_1 = &hash.blockhash2 as BlockHashPointerType;
let p_blockhash2_2 = hash.block_hash_2().representation() as BlockHashPointerType;
let p_blockhash2_3 = hash.block_hash_2_internal().representation() as BlockHashPointerType;
let p_blockhash2_4 = hash.block_hash_2_mut().representation() as BlockHashPointerType;
let p_blockhash2_5 = hash.block_hash_2_mut().representation_mut() as BlockHashPointerType;
assert_eq!(p_blockhash2_1, p_blockhash2_2);
assert_eq!(p_blockhash2_1, p_blockhash2_3);
assert_eq!(p_blockhash2_1, p_blockhash2_4);
assert_eq!(p_blockhash2_1, p_blockhash2_5);
}
{
let p_len_blockhash2_1 = &hash.len_blockhash2 as LengthPointerType;
let p_len_blockhash2_2 = unsafe {
let p_block_hash_2 =
(&hash.block_hash_2() as *const _) as *const BlockHashPositionArrayRef;
(*p_block_hash_2).1 as LengthPointerType
};
let p_len_blockhash2_3 = unsafe {
let p_block_hash_2 =
(&hash.block_hash_2_internal() as *const _) as *const BlockHashPositionArrayRef;
(*p_block_hash_2).1 as LengthPointerType
};
let p_len_blockhash2_4 = hash.block_hash_2_mut().len_mut() as LengthPointerType;
assert_eq!(p_len_blockhash2_1, p_len_blockhash2_2);
assert_eq!(p_len_blockhash2_1, p_len_blockhash2_3);
assert_eq!(p_len_blockhash2_1, p_len_blockhash2_4);
}
}
#[test]
fn data_model_new() {
let hash = {
let hash = FuzzyHashCompareTarget::new();
let hash_default = FuzzyHashCompareTarget::default();
let hash_cloned = hash.clone();
assert!(hash.full_eq(&hash_default));
assert!(hash.full_eq(&hash_cloned));
let mut hash2 =
FuzzyHashCompareTarget::from(str::parse::<FuzzyHash>("6:3ll7QzDkmJmMHkQoO/llSZEnEuLszmbMAWn:VqDk5QtLbW").unwrap());
hash2.clone_from(&hash);
assert!(hash.full_eq(&hash2));
hash
};
assert_eq!(hash.log_blocksize, 0);
assert_eq!(hash.len_blockhash1, 0);
assert_eq!(hash.len_blockhash2, 0);
assert_eq!(hash.blockhash1, [0; block_hash::ALPHABET_SIZE]);
assert_eq!(hash.blockhash2, [0; block_hash::ALPHABET_SIZE]);
assert!(hash.is_valid());
assert_eq!(hash.log_block_size(), 0);
assert_eq!(hash.block_size(), block_size::MIN);
assert!(hash.block_hash_1().is_valid());
assert!(hash.block_hash_1().is_valid_and_normalized());
assert!(hash.block_hash_1().is_empty());
assert!(hash.block_hash_2().is_valid());
assert!(hash.block_hash_1().is_valid_and_normalized());
assert!(hash.block_hash_2().is_empty());
assert!(hash.is_equiv(&FuzzyHash::new()));
assert!(hash.is_equiv(&LongFuzzyHash::new()));
}
#[test]
fn data_model_basic() {
test_blockhash_contents_all(&mut |bh1, bh2, bh1_norm, bh2_norm| {
for log_block_size in block_size::RANGE_LOG_VALID {
let len_blockhash1 = bh1_norm.len();
let len_blockhash2 = bh2_norm.len();
let len_blockhash1_raw = len_blockhash1 as u8;
let len_blockhash2_raw = len_blockhash2 as u8;
let bs = block_size::from_log_internal(log_block_size);
macro_rules! test_all {
($hash: ident, $dual_hash: ident) => {
let mut target = {
let target1_1 = FuzzyHashCompareTarget::from($hash);
let target1_2 = FuzzyHashCompareTarget::from(&$hash);
let mut target1_3 = FuzzyHashCompareTarget::new();
target1_3.init_from(&$hash);
let target2_1 = FuzzyHashCompareTarget::from($dual_hash);
let target2_2 = FuzzyHashCompareTarget::from(&$dual_hash);
let mut target2_3 = FuzzyHashCompareTarget::new();
target2_3.init_from($dual_hash);
assert!(target1_1.full_eq(&target1_2),
"failed (1-1-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target1_1.full_eq(&target1_3),
"failed (1-1-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target1_1.full_eq(&target2_1),
"failed (1-2-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target1_1.full_eq(&target2_2),
"failed (1-2-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target1_1.full_eq(&target2_3),
"failed (1-2-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
target1_1
};
assert!(target.is_valid(),
"failed (1-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target.is_equiv(&$hash),
"failed (1-4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.log_blocksize, log_block_size,
"failed (1-5) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.len_blockhash1, len_blockhash1_raw,
"failed (1-6) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.len_blockhash2, len_blockhash2_raw,
"failed (1-7) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.log_block_size(), log_block_size,
"failed (1-8) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_size(), bs,
"failed (1-9) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_1().is_empty(), target.block_hash_1().len() == 0,
"failed (2-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_2().is_empty(), target.block_hash_2().len() == 0,
"failed (2-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_1().len(), target.len_blockhash1,
"failed (2-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_2().len(), target.len_blockhash2,
"failed (2-4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_1_internal().is_empty(), target.block_hash_1().len() == 0,
"failed (2-5) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_2_internal().is_empty(), target.block_hash_2().len() == 0,
"failed (2-6) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_1_internal().len(), target.len_blockhash1,
"failed (2-7) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_2_internal().len(), target.len_blockhash2,
"failed (2-8) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
let bh1_len = target.block_hash_1_mut().len();
let bh2_len = target.block_hash_2_mut().len();
assert_eq!(target.block_hash_1_mut().is_empty(), bh1_len == 0,
"failed (3-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.block_hash_2_mut().is_empty(), bh2_len == 0,
"failed (3-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(bh1_len, target.len_blockhash1,
"failed (3-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(bh2_len, target.len_blockhash2,
"failed (3-4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
};
}
if len_blockhash2 <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
assert_eq!(hash, FuzzyHash::try_from(LongRawFuzzyHash::new_from_internals(bs, bh1, bh2).normalize()).unwrap(),
"failed on log_block_size={}, bh1={:?}, bh2={:?}", log_block_size, bh1, bh2);
if bh2.len() <= block_hash::HALF_SIZE {
let dual_hash = DualFuzzyHash::from_raw_form(&RawFuzzyHash::new_from_internals(bs, bh1, bh2));
test_all!(hash, dual_hash);
}
else {
let dual_hash = LongDualFuzzyHash::from_raw_form(&LongRawFuzzyHash::new_from_internals(bs, bh1, bh2));
test_all!(hash, dual_hash);
}
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
assert_eq!(hash, LongRawFuzzyHash::new_from_internals(bs, bh1, bh2).normalize(),
"failed on log_block_size={}, bh1={:?}, bh2={:?}", log_block_size, bh1, bh2);
let dual_hash = LongDualFuzzyHash::from_raw_form(&LongRawFuzzyHash::new_from_internals(bs, bh1, bh2));
test_all!(hash, dual_hash);
}
}
});
}
#[test]
fn data_model_equiv() {
test_blockhash_contents_all(&mut |_bh1, _bh2, bh1_norm, bh2_norm| {
let empty_hash_s = FuzzyHash::new();
let empty_hash_l = LongFuzzyHash::new();
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log_internal(log_block_size);
macro_rules! test_all {
($hash: ident) => {
let target = FuzzyHashCompareTarget::from(&$hash);
let is_empty = bh1_norm.is_empty() && bh2_norm.is_empty();
let is_equiv_with_empty = log_block_size == 0 && is_empty;
assert_eq!(is_equiv_with_empty, target.is_equiv(&empty_hash_s),
"failed (1-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(is_equiv_with_empty, target.is_equiv(&empty_hash_l),
"failed (1-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(is_empty, target.is_equiv_except_block_size(&empty_hash_s),
"failed (1-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(is_empty, target.is_equiv_except_block_size(&empty_hash_l),
"failed (1-4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target.is_equiv(&$hash),
"failed (1-5) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(target.is_equiv_except_block_size(&$hash),
"failed (1-6) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
let mut hash2 = $hash;
for log_block_size_2 in block_size::RANGE_LOG_VALID {
hash2.log_blocksize = log_block_size_2;
assert_eq!(target.is_equiv(&hash2), log_block_size == log_block_size_2,
"failed (2-1) on \
log_block_size={}, \
log_block_size_2={}, \
bh1_norm={:?}, \
bh2_norm={:?}",
log_block_size,
log_block_size_2,
bh1_norm,
bh2_norm
);
assert!(target.is_equiv_except_block_size(&hash2),
"failed (2-2) on \
log_block_size={}, \
log_block_size_2={}, \
bh1_norm={:?}, \
bh2_norm={:?}",
log_block_size,
log_block_size_2,
bh1_norm,
bh2_norm
);
}
};
}
if bh2_norm.len() <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
}
});
}
#[test]
fn data_model_equiv_inequality_block_hash() {
test_blockhash_contents_no_sequences(&mut |_bh1, _bh2, bh1_norm, bh2_norm| {
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log_internal(log_block_size);
macro_rules! test_all {
($hash: ident) => {
let target = FuzzyHashCompareTarget::from(&$hash);
for i in 0..bh1_norm.len() {
let mut hash = $hash;
hash.blockhash1[i] = if i == 2 { 0 } else { 2 };
assert!(!target.is_equiv(&hash),
"failed (1-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(!target.is_equiv_except_block_size(&hash),
"failed (1-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
for i in 0..bh2_norm.len() {
let mut hash = $hash;
hash.blockhash2[i] = if i == block_hash::FULL_SIZE - 1 - 2 { 0 } else { 2 };
assert!(!target.is_equiv(&hash),
"failed (2-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert!(!target.is_equiv_except_block_size(&hash),
"failed (2-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
};
}
if bh2_norm.len() <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
}
});
}
#[test]
fn data_model_inequality_slightly_different() {
let hash1 = FuzzyHashCompareTarget::new();
let mut hash2 = FuzzyHashCompareTarget::new();
hash2.blockhash1[0] = 1; assert!(!hash1.full_eq(&hash2));
assert!(!hash2.full_eq(&hash1));
let mut hash2 = FuzzyHashCompareTarget::new();
hash2.blockhash2[0] = 1; assert!(!hash1.full_eq(&hash2));
assert!(!hash2.full_eq(&hash1));
let mut hash2 = FuzzyHashCompareTarget::new();
hash2.len_blockhash1 = 1; assert!(!hash1.full_eq(&hash2));
assert!(!hash2.full_eq(&hash1));
let mut hash2 = FuzzyHashCompareTarget::new();
hash2.len_blockhash2 = 1; assert!(!hash1.full_eq(&hash2));
assert!(!hash2.full_eq(&hash1));
let mut hash2 = FuzzyHashCompareTarget::new();
hash2.log_blocksize = 1; assert!(!hash1.full_eq(&hash2));
assert!(!hash2.full_eq(&hash1));
}
#[test]
fn data_model_corruption() {
assert_eq!(block_hash::FULL_SIZE, 64);
assert_eq!(block_hash::ALPHABET_SIZE, 64);
{
let target = FuzzyHashCompareTarget::new();
assert!(target.is_valid());
}
{
let mut target = FuzzyHashCompareTarget::new();
for log_block_size in u8::MIN..=u8::MAX {
target.log_blocksize = log_block_size;
assert_eq!(target.is_valid(), block_size::is_log_valid(log_block_size),
"failed on log_block_size={}", log_block_size);
}
}
{
let target = {
let mut target = FuzzyHashCompareTarget::new();
target.len_blockhash1 = 1;
assert!(!target.is_valid());
target
};
for index in 0..target.blockhash1.len() {
let mut target = target.clone();
target.blockhash1[index] = 1; assert!(target.is_valid(), "failed on index={}", index);
}
let mut target = FuzzyHashCompareTarget::new();
for (i, pa) in target.blockhash1.iter_mut().enumerate() {
*pa = 1 << i;
}
target.len_blockhash1 = 64;
assert!(target.is_valid());
for len in 65u8..=u8::MAX {
target.len_blockhash1 = len;
assert!(!target.is_valid(), "failed on len={}", len);
}
}
{
let target = {
let mut target = FuzzyHashCompareTarget::new();
target.len_blockhash2 = 1;
assert!(!target.is_valid());
target
};
for index in 0..target.blockhash2.len() {
let mut target = target.clone();
target.blockhash2[index] = 1; assert!(target.is_valid(), "failed on index={}", index);
}
let mut target = FuzzyHashCompareTarget::new();
for (i, pa) in target.blockhash2.iter_mut().enumerate() {
*pa = 1 << i;
}
target.len_blockhash2 = 64;
assert!(target.is_valid());
for len in 65u8..=u8::MAX {
target.len_blockhash2 = len;
assert!(!target.is_valid(), "failed on len={}", len);
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
for i in 0..len {
assert!(i < 64);
target.blockhash1[i] = 1 << i;
}
target.len_blockhash1 = len as u8;
assert!(target.is_valid(), "failed on len={}", len);
target
};
for invalid_pos in (len as u32)..u64::BITS {
let bitpos = 1u64 << invalid_pos;
for ch in 0..target.blockhash1.len() {
let mut target = target.clone();
target.blockhash1[ch] ^= bitpos;
assert!(!target.is_valid(),
"failed on len={}, invalid_pos={}, ch={}", len, invalid_pos, ch);
}
}
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
for i in 0..len {
target.blockhash2[i] = 1 << i;
}
target.len_blockhash2 = len as u8;
assert!(target.is_valid(), "failed on len={}", len);
target
};
for invalid_pos in (len as u32)..u64::BITS {
let bitpos = 1u64 << invalid_pos;
for ch in 0..target.blockhash2.len() {
let mut target = target.clone();
target.blockhash2[ch] ^= bitpos;
assert!(!target.is_valid(),
"failed on len={}, invalid_pos={}, ch={}", len, invalid_pos, ch);
}
}
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
for i in 0..len {
assert!(i < 64);
target.blockhash1[i] = 1 << i;
}
target.len_blockhash1 = len as u8;
assert!(target.is_valid(), "failed on len={}", len);
target
};
for invalid_pos in 0..len {
let bitpos = 1u64 << (invalid_pos as u32);
for ch in 0..target.blockhash1.len() {
let mut target = target.clone();
target.blockhash1[ch] ^= bitpos;
assert!(!target.is_valid(),
"failed on len={}, invalid_pos={}, ch={}", len, invalid_pos, ch);
}
}
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
for i in 0..len {
assert!(i < 64);
target.blockhash2[i] = 1 << i;
}
target.len_blockhash2 = len as u8;
assert!(target.is_valid(), "failed on len={}", len);
target
};
for invalid_pos in 0..len {
let bitpos = 1u64 << (invalid_pos as u32);
for ch in 0..target.blockhash1.len() {
let mut target = target.clone();
target.blockhash1[ch] ^= bitpos;
assert!(!target.is_valid(),
"failed on len={}, invalid_pos={}, ch={}", len, invalid_pos, ch);
}
}
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
target.len_blockhash1 = len as u8;
assert_eq!(target.is_valid(), len == 0, "failed on len={}", len);
target
};
for index in 0..target.blockhash1.len() {
let mut target = target.clone();
target.blockhash1[index] = u64_lsb_ones(len as u32);
assert_eq!(target.is_valid(), len <= block_hash::MAX_SEQUENCE_SIZE,
"failed on len={}, index={}", len, index);
}
}
}
{
for len in 0..=block_hash::FULL_SIZE {
let target = {
let mut target = FuzzyHashCompareTarget::new();
target.len_blockhash2 = len as u8;
assert_eq!(target.is_valid(), len == 0, "failed on len={}", len);
target
};
for index in 0..target.blockhash2.len() {
let mut target = target.clone();
target.blockhash2[index] = u64_lsb_ones(len as u32);
assert_eq!(target.is_valid(), len <= block_hash::MAX_SEQUENCE_SIZE,
"failed on len={}, index={}", len, index);
}
}
}
}
#[test]
fn raw_scores_on_block_hash_comparison() {
for len1 in block_hash::MIN_LCS_FOR_COMPARISON as u8..=block_hash::FULL_SIZE as u8 {
for len2 in block_hash::MIN_LCS_FOR_COMPARISON as u8..=block_hash::FULL_SIZE as u8 {
let mut score = 100;
for edit_distance in 0..=(len1 as u32 + len2 as u32 - 2 * block_hash::MIN_LCS_FOR_COMPARISON as u32) {
let new_score =
FuzzyHashCompareTarget::raw_score_by_edit_distance(len1, len2, edit_distance);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(
new_score,
FuzzyHashCompareTarget::raw_score_by_edit_distance_unchecked(len1, len2, edit_distance),
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance
);
}
if edit_distance == 0 {
assert_eq!(new_score, 100,
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance);
}
assert!(new_score <= 100,
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance);
assert_ne!(new_score, 0,
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance);
assert_eq!(
new_score,
FuzzyHashCompareTarget::raw_score_by_edit_distance(len2, len1, edit_distance),
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance
);
assert!(new_score <= score,
"failed on len1={}, len2={}, edit_distance={}", len1, len2, edit_distance);
score = new_score;
}
}
}
}
#[test]
fn score_caps_on_block_hash_comparison() {
for log_block_size in 0..FuzzyHashCompareTarget::LOG_BLOCK_SIZE_CAPPING_BORDER {
let mut score_cap = 0;
for len in block_hash::MIN_LCS_FOR_COMPARISON as u8..=block_hash::FULL_SIZE as u8 {
let new_score_cap =
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(log_block_size, len, len);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(
new_score_cap,
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison_unchecked(log_block_size, len, len),
"failed on log_block_size={}, len={}", log_block_size, len
);
}
assert_ne!(new_score_cap, 0,
"failed on log_block_size={}, len={}", log_block_size, len);
if len == block_hash::MIN_LCS_FOR_COMPARISON as u8 {
assert!(new_score_cap < 100,
"failed on log_block_size={}, len={}", log_block_size, len);
}
else {
assert_eq!(new_score_cap - score_cap, 1u32 << log_block_size,
"failed on log_block_size={}, len={}", log_block_size, len);
}
score_cap = new_score_cap;
}
}
for log_block_size in FuzzyHashCompareTarget::LOG_BLOCK_SIZE_CAPPING_BORDER..u8::MAX {
for len in block_hash::MIN_LCS_FOR_COMPARISON as u8..=block_hash::FULL_SIZE as _ {
assert!(FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(log_block_size, len, len) >= 100,
"failed on log_block_size={}, len={}", log_block_size, len);
}
}
}
#[test]
fn compare_self() {
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_internal(log_block_size);
macro_rules! test_all {
($hash: ident) => {
let target = FuzzyHashCompareTarget::from(&$hash);
assert_eq!(target.compare(&$hash), 100,
"failed (1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.compare_near_eq(&$hash), 100,
"failed (2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(target.compare_near_eq_internal(&$hash), 100,
"failed (3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(target.compare_near_eq_unchecked(&$hash), 100,
"failed (4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
assert_eq!($hash.compare(&$hash), 100,
"failed (5) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
};
}
if bh2_norm.len() <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
}
});
}
#[test]
fn compare_slightly_different() {
test_blockhash_contents_no_sequences(&mut |_bh1, _bh2, bh1_norm, bh2_norm| {
let len_blockhash1 = bh1_norm.len();
let len_blockhash2 = bh2_norm.len();
let len_blockhash1_raw = len_blockhash1 as u8;
let len_blockhash2_raw = len_blockhash2 as u8;
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log_internal(log_block_size);
macro_rules! test_all {
($hash: ident) => {
let target = FuzzyHashCompareTarget::from(&$hash);
let hash = $hash;
macro_rules! compare {
($test_num: literal, $score: ident, $diff_hash: ident) => {
let test_num = $test_num;
assert_eq!($score, target.compare_near_eq(&$diff_hash),
"failed ({}-1-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_near_eq_internal(&$diff_hash),
"failed ({}-1-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, hash.compare(&$diff_hash),
"failed ({}-1-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal(&$diff_hash),
"failed ({}-1-4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal_internal(&$diff_hash),
"failed ({}-1-5) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal_near_eq(&$diff_hash),
"failed ({}-1-6) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal_near_eq_internal(&$diff_hash),
"failed ({}-1-7) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, hash.compare_unequal(&$diff_hash),
"failed ({}-1-8) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, hash.compare_unequal_internal(&$diff_hash),
"failed ({}-1-9) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!($score, target.compare_near_eq_unchecked(&$diff_hash),
"failed ({}-1-10) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal_unchecked(&$diff_hash),
"failed ({}-1-11) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, target.compare_unequal_near_eq_unchecked(&$diff_hash),
"failed ({}-1-12) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
assert_eq!($score, hash.compare_unequal_unchecked(&$diff_hash),
"failed ({}-1-13) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}",
test_num, log_block_size, bh1_norm, bh2_norm);
}
};
}
if hash.len_blockhash1 > 0 {
let mut diff_hash = hash;
diff_hash.blockhash1[0] = 2; let score = target.compare(&diff_hash);
compare!(1, score, diff_hash);
let score_cap_1 =
if len_blockhash1 >= block_hash::MIN_LCS_FOR_COMPARISON {
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
log_block_size, len_blockhash1_raw, len_blockhash1_raw)
}
else {
100
};
let score_cap_2 =
if len_blockhash2 >= block_hash::MIN_LCS_FOR_COMPARISON {
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
log_block_size + 1, len_blockhash2_raw, len_blockhash2_raw)
}
else {
100
};
let score_cap = u32::max(score_cap_1, score_cap_2);
assert!(score <= score_cap,
"failed (1-2-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
if len_blockhash1 < block_hash::MIN_LCS_FOR_COMPARISON &&
len_blockhash2 < block_hash::MIN_LCS_FOR_COMPARISON
{
assert_eq!(score, 0,
"failed (1-2-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
else if len_blockhash2 >= block_hash::MIN_LCS_FOR_COMPARISON &&
score_cap_2 >= 100
{
assert_eq!(score, 100,
"failed (1-2-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
}
if hash.len_blockhash2 > 0 {
let mut diff_hash = hash;
diff_hash.blockhash2[0] = 0; let score = target.compare(&diff_hash);
compare!(2, score, diff_hash);
let score_cap_1 =
if len_blockhash1 >= block_hash::MIN_LCS_FOR_COMPARISON {
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
log_block_size, len_blockhash1_raw, len_blockhash1_raw)
}
else {
100
};
let score_cap_2 =
if len_blockhash2 >= block_hash::MIN_LCS_FOR_COMPARISON {
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
log_block_size + 1, len_blockhash2_raw, len_blockhash2_raw)
}
else {
100
};
let score_cap = u32::max(score_cap_1, score_cap_2);
assert!(score <= score_cap,
"failed (2-2-1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
if len_blockhash1 < block_hash::MIN_LCS_FOR_COMPARISON &&
len_blockhash2 < block_hash::MIN_LCS_FOR_COMPARISON
{
assert_eq!(score, 0,
"failed (2-2-2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
else if len_blockhash1 >= block_hash::MIN_LCS_FOR_COMPARISON &&
score_cap_1 >= 100
{
assert_eq!(score, 100,
"failed (2-2-3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
}
};
}
if bh2_norm.len() <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
}
});
}
#[test]
fn comparison_with_block_size_pairs() {
const BLOCK_HASH_SAMPLE_DATA: [&[u8]; 4] = [
&[59, 12, 10, 19, 21, 28, 60, 56, 61, 42, 56, 18, 19, 16, 17, 45, 34, 50, 57, 13], &[45, 12, 10, 19, 21, 28, 60, 56, 22, 22, 27, 56, 18, 16, 39, 14, 14, 34, 60, 57], &[47, 12, 10, 19, 21, 28, 60, 56, 30, 40, 26, 22, 22, 30, 29, 42, 19, 39, 34, 46], &[24, 12, 10, 19, 21, 28, 60, 56, 14, 12, 18, 52, 37, 50, 31, 32, 47, 33, 56, 53], ];
const BLOCK_HASH_SAMPLE_SCORES: [[u32; 4]; 4] = [
[100, 61, 50, 46],
[ 61, 100, 57, 41],
[ 50, 57, 100, 36],
[ 46, 41, 36, 100],
];
for (i, &sample_data) in BLOCK_HASH_SAMPLE_DATA.iter().enumerate() {
assert!(sample_data.len() <= block_hash::HALF_SIZE, "failed on i={}", i);
}
let mut target_s = FuzzyHashCompareTarget::new();
let mut target_l = FuzzyHashCompareTarget::new();
for bs1 in block_size::RANGE_LOG_VALID {
let block_size_1 = block_size::from_log(bs1).unwrap();
let hash1_s = FuzzyHash::new_from_internals(
block_size_1,
BLOCK_HASH_SAMPLE_DATA[0],
BLOCK_HASH_SAMPLE_DATA[1]
);
let hash1_l = hash1_s.to_long_form();
target_s.init_from(&hash1_s);
target_l.init_from(&hash1_l);
assert!(target_s.full_eq(&target_l), "failed on bs1={}", bs1);
let target: &FuzzyHashCompareTarget = &target_s;
for bs2 in block_size::RANGE_LOG_VALID {
let block_size_2 = block_size::from_log(bs2).unwrap();
let hash2_s = FuzzyHash::new_from_internals(
block_size_2,
BLOCK_HASH_SAMPLE_DATA[2],
BLOCK_HASH_SAMPLE_DATA[3]
);
let hash2_l = hash2_s.to_long_form();
let score = target.compare(&hash2_s);
assert_eq!(score, target.compare(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_s.compare(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_l.compare(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_s.compare_unequal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_l.compare_unequal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_s.compare_unequal_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_l.compare_unequal_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(score, target.compare_unequal_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_s.compare_unequal_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, hash1_l.compare_unequal_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
match block_size::compare_sizes(bs1, bs2) {
BlockSizeRelation::Far => {
assert_eq!(score, 0, "failed on bs1={}, bs2={}", bs1, bs2);
assert!(!target.is_comparison_candidate(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(!target.is_comparison_candidate(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
BlockSizeRelation::NearEq => {
assert!(target.is_comparison_candidate(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_eq(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_eq(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_eq_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_eq_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert!(target.is_comparison_candidate_near_eq_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_eq_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
let score_cap_1 =
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
bs1,
hash1_s.len_blockhash1,
hash2_s.len_blockhash1
);
let score_cap_2 =
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
bs1 + 1,
hash1_s.len_blockhash2,
hash2_s.len_blockhash2
);
let expected_score_uncapped_1 = BLOCK_HASH_SAMPLE_SCORES[0][2];
let expected_score_uncapped_2 = BLOCK_HASH_SAMPLE_SCORES[1][3];
let expected_score_capped_1 = u32::min(expected_score_uncapped_1, score_cap_1);
let expected_score_capped_2 = u32::min(expected_score_uncapped_2, score_cap_2);
let expected_score = u32::max(expected_score_capped_1, expected_score_capped_2);
assert_eq!(score, expected_score, "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_near_eq(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_near_eq(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_near_eq_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_near_eq_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(score, target.compare_near_eq_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_near_eq_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_eq_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
}
BlockSizeRelation::NearGt => {
assert!(target.is_comparison_candidate(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_gt(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_gt(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_gt_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_gt_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert!(target.is_comparison_candidate_near_gt_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_gt_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
let score_cap =
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
bs1,
hash1_s.len_blockhash1,
hash2_s.len_blockhash2
);
let expected_score_uncapped = BLOCK_HASH_SAMPLE_SCORES[0][3];
let expected_score = u32::min(expected_score_uncapped, score_cap);
assert_eq!(score, expected_score, "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_gt(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_gt(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_gt_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_gt_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(score, target.compare_unequal_near_gt_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_gt_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
}
BlockSizeRelation::NearLt => {
assert!(target.is_comparison_candidate(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_lt(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_lt(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_lt_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_lt_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert!(target.is_comparison_candidate_near_lt_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert!(target.is_comparison_candidate_near_lt_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
let score_cap =
FuzzyHashCompareTarget::score_cap_on_block_hash_comparison(
bs2,
hash1_s.len_blockhash2,
hash2_s.len_blockhash1
);
let expected_score_uncapped = BLOCK_HASH_SAMPLE_SCORES[1][2];
let expected_score = u32::min(expected_score_uncapped, score_cap);
assert_eq!(score, expected_score, "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_lt(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_lt(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_lt_internal(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_lt_internal(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(score, target.compare_unequal_near_lt_unchecked(&hash2_s), "failed on bs1={}, bs2={}", bs1, bs2);
assert_eq!(score, target.compare_unequal_near_lt_unchecked(&hash2_l), "failed on bs1={}, bs2={}", bs1, bs2);
}
}
}
}
}
}
#[test]
fn compare_candidate_self() {
test_blockhash_contents_all(&mut |_bh1, _bh2, bh1_norm, bh2_norm| {
let expected_value =
bh1_norm.len() >= block_hash::MIN_LCS_FOR_COMPARISON ||
bh2_norm.len() >= block_hash::MIN_LCS_FOR_COMPARISON;
for log_block_size in block_size::RANGE_LOG_VALID {
let bs = block_size::from_log_internal(log_block_size);
macro_rules! test_all {
($hash: ident) => {
let target = FuzzyHashCompareTarget::from(&$hash);
assert_eq!(expected_value, target.is_comparison_candidate(&$hash),
"failed (1) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(expected_value, target.is_comparison_candidate_near_eq(&$hash),
"failed (2) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
assert_eq!(expected_value, target.is_comparison_candidate_near_eq_internal(&$hash),
"failed (3) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(expected_value, target.is_comparison_candidate_near_eq_unchecked(&$hash),
"failed (4) on log_block_size={}, bh1_norm={:?}, bh2_norm={:?}", log_block_size, bh1_norm, bh2_norm);
}
};
}
if bh2_norm.len() <= block_hash::HALF_SIZE {
let hash = FuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
{
let hash = LongFuzzyHash::new_from_internals(bs, bh1_norm, bh2_norm);
test_all!(hash);
}
}
});
}
#[allow(clippy::type_complexity)]
#[test]
fn compare_candidate_with_block_size_pairs() {
const BH_STR_A: &[u8] = &[1, 2, 1, 2, 1, 2, 1];
const BH_STR_B: &[u8] = &[3, 4, 3, 4, 3, 4, 3];
const BH_STR_C: &[u8] = &[5, 6, 5, 6, 5, 6, 5];
const BH_STR_D: &[u8] = &[7, 8, 7, 8, 7, 8, 7];
const BH_STRS: [&[u8]; 4] = [BH_STR_A, BH_STR_B, BH_STR_C, BH_STR_D];
const BLOCK_HASH_PAIRS: [(&[u8], &[u8], &[u8], &[u8], bool, bool, bool); 12] = [
(BH_STR_A, BH_STR_B, BH_STR_C, BH_STR_D, false, false, false),
(BH_STR_A, BH_STR_B, BH_STR_C, BH_STR_B, true, false, false),
(BH_STR_A, BH_STR_B, BH_STR_C, BH_STR_A, false, true, false),
(BH_STR_A, BH_STR_A, BH_STR_C, BH_STR_A, true, true, false),
(BH_STR_A, BH_STR_B, BH_STR_B, BH_STR_D, false, false, true ),
(BH_STR_A, BH_STR_B, BH_STR_B, BH_STR_B, true, false, true ),
(BH_STR_A, BH_STR_B, BH_STR_B, BH_STR_A, false, true, true ),
(BH_STR_A, BH_STR_A, BH_STR_A, BH_STR_A, true, true, true ),
(BH_STR_A, BH_STR_B, BH_STR_A, BH_STR_D, true, false, false),
(BH_STR_A, BH_STR_B, BH_STR_A, BH_STR_B, true, false, false),
(BH_STR_A, BH_STR_B, BH_STR_A, BH_STR_A, true, true, false),
(BH_STR_A, BH_STR_A, BH_STR_A, BH_STR_D, true, false, true ),
];
for (idx_1, &bh_str_1) in BH_STRS.iter().enumerate() {
use crate::compare::position_array::{
BlockHashPositionArray,
BlockHashPositionArrayData,
BlockHashPositionArrayImpl
};
let mut target = BlockHashPositionArray::new();
target.init_from(bh_str_1);
assert!(target.is_valid_and_normalized(), "failed on idx_1={}", idx_1);
for (idx_2, &bh_str_2) in BH_STRS.iter().enumerate() {
assert_eq!(idx_1 == idx_2, target.has_common_substring(bh_str_2),
"failed on idx_1={}, idx_2={}", idx_1, idx_2);
}
}
let mut target_s = FuzzyHashCompareTarget::new();
let mut target_l = FuzzyHashCompareTarget::new();
for (pair_idx, &(bh_1_1, bh_1_2, bh_2_1, bh_2_2, cand_eq, cand_gt, cand_lt))
in BLOCK_HASH_PAIRS.iter().enumerate()
{
for bs1 in block_size::RANGE_LOG_VALID {
let block_size_1 = block_size::from_log(bs1).unwrap();
let hash1_s =
FuzzyHash::new_from_internals(block_size_1, bh_1_1, bh_1_2);
let hash1_l = hash1_s.to_long_form();
target_s.init_from(&hash1_s);
target_l.init_from(&hash1_l);
assert!(target_s.full_eq(&target_l), "failed on pair_idx={}, bs1={}", pair_idx, bs1);
let target: &FuzzyHashCompareTarget = &target_s;
for bs2 in block_size::RANGE_LOG_VALID {
let block_size_2 = block_size::from_log(bs2).unwrap();
let hash2_s =
FuzzyHash::new_from_internals(block_size_2, bh_2_1, bh_2_2);
let hash2_l = hash2_s.to_long_form();
match block_size::compare_sizes(bs1, bs2) {
BlockSizeRelation::Far => {
assert!(!target.is_comparison_candidate(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert!(!target.is_comparison_candidate(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
}
BlockSizeRelation::NearEq => {
assert_eq!(cand_eq, target.is_comparison_candidate(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq_internal(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq_internal(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq_unchecked(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_eq, target.is_comparison_candidate_near_eq_unchecked(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
}
}
BlockSizeRelation::NearGt => {
assert_eq!(cand_gt, target.is_comparison_candidate(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt_internal(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt_internal(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt_unchecked(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_gt, target.is_comparison_candidate_near_gt_unchecked(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
}
}
BlockSizeRelation::NearLt => {
assert_eq!(cand_lt, target.is_comparison_candidate(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt_internal(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt_internal(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
#[cfg(feature = "unchecked")]
unsafe {
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt_unchecked(&hash2_s), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
assert_eq!(cand_lt, target.is_comparison_candidate_near_lt_unchecked(&hash2_l), "failed on pair_idx={}, bs1={}, bs2={}", pair_idx, bs1, bs2);
}
}
}
}
}
}
}