#![cfg_attr(miri, allow(dead_code))]
use crate::traits::{Checksum, ChecksumCombine};
pub struct CrcTestHarness<C> {
_phantom: core::marker::PhantomData<C>,
}
impl<C> CrcTestHarness<C>
where
C: Checksum + ChecksumCombine,
C::Output: Eq + core::fmt::Debug,
{
#[inline]
pub fn test_combine_property(data: &[u8], split: usize) {
let split = if data.is_empty() { 0 } else { split % data.len() };
let (a, b) = data.split_at(split);
let crc_a = C::checksum(a);
let crc_b = C::checksum(b);
let combined = C::combine(crc_a, crc_b, b.len());
let expected = C::checksum(data);
assert_eq!(combined, expected, "combine(crc(A), crc(B), len(B)) != crc(A || B)");
}
#[inline]
#[cfg_attr(test, allow(dead_code))]
pub fn test_combine_all_splits(data: &[u8]) {
let full = C::checksum(data);
for split in 0..=data.len() {
let (a, b) = data.split_at(split);
let crc_a = C::checksum(a);
let crc_b = C::checksum(b);
let combined = C::combine(crc_a, crc_b, b.len());
assert_eq!(combined, full, "combine failed at split point {split}/{}", data.len());
}
}
#[inline]
pub fn test_combine_empty_suffix(data: &[u8]) {
let crc_data = C::checksum(data);
let crc_empty = C::checksum(&[]);
let combined = C::combine(crc_data, crc_empty, 0);
assert_eq!(combined, crc_data, "combine(crc(A), crc(''), 0) != crc(A)");
}
#[inline]
pub fn test_combine_empty_prefix(data: &[u8]) {
let crc_empty = C::checksum(&[]);
let crc_data = C::checksum(data);
let combined = C::combine(crc_empty, crc_data, data.len());
assert_eq!(combined, crc_data, "combine(crc(''), crc(B), len(B)) != crc(B)");
}
#[inline]
#[cfg_attr(test, allow(dead_code))]
pub fn test_streaming_equals_oneshot(data: &[u8]) {
let oneshot = C::checksum(data);
let mut hasher = C::new();
hasher.update(data);
let streaming = hasher.finalize();
assert_eq!(streaming, oneshot, "streaming != oneshot");
}
#[inline]
pub fn test_streaming_byte_at_a_time(data: &[u8]) {
let oneshot = C::checksum(data);
let mut hasher = C::new();
for &byte in data {
hasher.update(&[byte]);
}
let streaming = hasher.finalize();
assert_eq!(streaming, oneshot, "byte-at-a-time streaming != oneshot");
}
#[inline]
pub fn test_streaming_chunked(data: &[u8], chunk_size: usize) {
if chunk_size == 0 {
return;
}
let oneshot = C::checksum(data);
let mut hasher = C::new();
for chunk in data.chunks(chunk_size) {
hasher.update(chunk);
}
let streaming = hasher.finalize();
assert_eq!(streaming, oneshot, "streaming with chunk_size={chunk_size} != oneshot");
}
#[inline]
pub fn test_finalize_idempotent(data: &[u8]) {
let mut hasher = C::new();
hasher.update(data);
let first = hasher.finalize();
let second = hasher.finalize();
let third = hasher.finalize();
assert_eq!(first, second, "finalize() not idempotent (1st != 2nd)");
assert_eq!(second, third, "finalize() not idempotent (2nd != 3rd)");
}
#[inline]
pub fn test_reset(data: &[u8]) {
let fresh = C::checksum(data);
let mut hasher = C::new();
hasher.update(b"garbage data that should be discarded");
hasher.reset();
hasher.update(data);
let after_reset = hasher.finalize();
assert_eq!(after_reset, fresh, "reset did not restore initial state");
}
#[inline]
pub fn test_streaming_and_combine(data: &[u8]) {
if data.is_empty() {
return;
}
let full = C::checksum(data);
let mid = data.len() / 2;
let (first_half, second_half) = data.split_at(mid);
let mut hasher = C::new();
hasher.update(first_half);
let crc_first = hasher.finalize();
let crc_second = C::checksum(second_half);
let combined = C::combine(crc_first, crc_second, second_half.len());
assert_eq!(combined, full, "streaming + combine != full CRC");
}
#[inline]
pub fn test_empty_input() {
let oneshot = C::checksum(&[]);
let hasher = C::new();
let streaming = hasher.finalize();
assert_eq!(streaming, oneshot, "empty streaming != empty oneshot");
}
#[inline]
pub fn test_single_bytes() {
for byte in 0u8..=255 {
let oneshot = C::checksum(&[byte]);
let mut hasher = C::new();
hasher.update(&[byte]);
let streaming = hasher.finalize();
assert_eq!(streaming, oneshot, "single byte {byte} mismatch");
}
}
}
#[macro_export]
macro_rules! define_crc_property_tests {
($mod_name:ident, $crc_type:ty) => {
#[cfg(all(test, not(miri)))]
mod $mod_name {
use proptest::prelude::*;
use $crate::checksum::common::tests::CrcTestHarness;
use super::*;
proptest! {
#[test]
fn combine_property(data in proptest::collection::vec(any::<u8>(), 0..1024), split in 0usize..1024) {
CrcTestHarness::<$crc_type>::test_combine_property(&data, split);
}
#[test]
fn streaming_byte_at_a_time(data in proptest::collection::vec(any::<u8>(), 0..256)) {
CrcTestHarness::<$crc_type>::test_streaming_byte_at_a_time(&data);
}
#[test]
fn streaming_chunked(data in proptest::collection::vec(any::<u8>(), 0..512), chunk_size in 1usize..64) {
CrcTestHarness::<$crc_type>::test_streaming_chunked(&data, chunk_size);
}
#[test]
fn finalize_idempotent(data in proptest::collection::vec(any::<u8>(), 0..256)) {
CrcTestHarness::<$crc_type>::test_finalize_idempotent(&data);
}
#[test]
fn reset_works(data in proptest::collection::vec(any::<u8>(), 0..256)) {
CrcTestHarness::<$crc_type>::test_reset(&data);
}
#[test]
fn streaming_and_combine(data in proptest::collection::vec(any::<u8>(), 1..512)) {
CrcTestHarness::<$crc_type>::test_streaming_and_combine(&data);
}
}
#[test]
fn test_empty_input() {
CrcTestHarness::<$crc_type>::test_empty_input();
}
#[test]
fn test_single_bytes() {
CrcTestHarness::<$crc_type>::test_single_bytes();
}
#[test]
fn test_combine_empty_suffix() {
CrcTestHarness::<$crc_type>::test_combine_empty_suffix(b"test data");
}
#[test]
fn test_combine_empty_prefix() {
CrcTestHarness::<$crc_type>::test_combine_empty_prefix(b"test data");
}
}
};
}
#[cfg(not(miri))]
pub const TEST_LENGTHS: &[usize] = &[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 2047, 2048, 2049, 4095, 4096, 4097, 8192, 16384, 32768, 65536, ];
#[cfg(miri)]
pub const TEST_LENGTHS: &[usize] = &[
0, 1, 2, 3, 7, 8, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025,
];
#[cfg(not(miri))]
pub const STREAMING_CHUNK_SIZES: &[usize] = &[1, 3, 7, 13, 17, 31, 37, 61, 127, 251];
#[cfg(miri)]
pub const STREAMING_CHUNK_SIZES: &[usize] = &[1, 3, 7, 13, 31, 61];
#[cfg_attr(test, allow(dead_code))]
pub fn generate_test_data(len: usize) -> alloc::vec::Vec<u8> {
(0..len)
.map(|i| (i as u64).wrapping_mul(17).wrapping_add(i as u64) as u8)
.collect()
}
#[cfg(test)]
mod harness_self_tests {
#[cfg(feature = "crc64")]
use super::*;
#[cfg(feature = "crc64")]
use crate::checksum::Crc64Xz;
#[test]
#[cfg(feature = "crc64")]
fn harness_test_combine_all_splits() {
CrcTestHarness::<Crc64Xz>::test_combine_all_splits(b"hello world");
}
#[test]
#[cfg(feature = "crc64")]
fn harness_test_streaming_equals_oneshot() {
CrcTestHarness::<Crc64Xz>::test_streaming_equals_oneshot(b"The quick brown fox");
}
#[test]
#[cfg(feature = "crc64")]
fn harness_test_empty_input() {
CrcTestHarness::<Crc64Xz>::test_empty_input();
}
#[test]
#[cfg(feature = "crc64")]
fn harness_test_single_bytes() {
CrcTestHarness::<Crc64Xz>::test_single_bytes();
}
#[test]
#[cfg(feature = "crc64")]
fn harness_test_finalize_idempotent() {
CrcTestHarness::<Crc64Xz>::test_finalize_idempotent(b"test data");
}
#[test]
#[cfg(feature = "crc64")]
fn harness_test_reset() {
CrcTestHarness::<Crc64Xz>::test_reset(b"fresh data");
}
}