#![allow(
clippy::indexing_slicing,
clippy::unwrap_used,
clippy::expect_used,
clippy::arithmetic_side_effects,
clippy::default_numeric_fallback,
clippy::cast_possible_truncation,
clippy::as_conversions,
clippy::missing_panics_doc,
clippy::needless_pass_by_value,
clippy::missing_errors_doc,
dead_code
)]
use bitcut::{apply_patch, base_fingerprint, inspect, make_patch, Op, PatchError};
use proptest::collection::vec;
use proptest::prelude::*;
fn common_prefix_len(a: &[u8], b: &[u8]) -> usize {
a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count()
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 2048,
max_shrink_iters: 10_000,
..ProptestConfig::default()
})]
#[test]
fn roundtrip_arbitrary(
old in vec(any::<u8>(), 0..512),
new in vec(any::<u8>(), 0..512),
) {
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let restored = apply_patch(&old, &patch).expect("apply_patch must succeed on a valid patch");
prop_assert_eq!(restored, new);
}
#[test]
fn roundtrip_shifted(
old in vec(any::<u8>(), 32..512),
skip in 0usize..32,
tail in vec(any::<u8>(), 0..32),
) {
let take = old.len().saturating_sub(skip);
let mut new: Vec<u8> = old.iter().copied().skip(skip).take(take).collect();
new.extend_from_slice(&tail);
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let restored = apply_patch(&old, &patch).expect("valid patch");
prop_assert_eq!(restored, new);
}
#[test]
fn roundtrip_repeated_pattern(
unit in vec(any::<u8>(), 1..16),
repeat_old in 1usize..40,
repeat_new in 1usize..40,
) {
let old: Vec<u8> = unit.iter().cycle().copied().take(unit.len() * repeat_old).collect();
let new: Vec<u8> = unit.iter().cycle().copied().take(unit.len() * repeat_new).collect();
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let restored = apply_patch(&old, &patch).expect("valid patch");
prop_assert_eq!(restored, new);
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 4096,
max_shrink_iters: 10_000,
..ProptestConfig::default()
})]
#[test]
fn apply_patch_never_panics(
old in vec(any::<u8>(), 0..256),
patch in vec(any::<u8>(), 0..1024),
) {
let _ = apply_patch(&old, &patch);
}
#[test]
fn op_iter_never_panics(patch in vec(any::<u8>(), 0..1024)) {
for _ in Op::iter(&patch) {
}
}
#[test]
fn crafted_copy_overflow_never_panics(
old in vec(any::<u8>(), 0..64),
offset in any::<u32>(),
len in any::<u32>(),
) {
let mut patch = Vec::with_capacity(9);
patch.push(0x00);
patch.extend_from_slice(&offset.to_le_bytes());
patch.extend_from_slice(&len.to_le_bytes());
let _ = apply_patch(&old, &patch);
}
#[test]
fn crafted_add_overflow_never_panics(
old in vec(any::<u8>(), 0..64),
declared_len in any::<u32>(),
body in vec(any::<u8>(), 0..64),
) {
let mut patch = Vec::with_capacity(5 + body.len());
patch.push(0x01);
patch.extend_from_slice(&declared_len.to_le_bytes());
patch.extend_from_slice(&body);
let _ = apply_patch(&old, &patch);
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 2048,
..ProptestConfig::default()
})]
#[test]
fn simd_memcmp_mismatch_at_every_offset(
(n, pos) in (1usize..200).prop_flat_map(|n| (Just(n), 0usize..n)),
) {
let old = vec![0xAA_u8; n];
let mut new = vec![0xAA_u8; n];
new[pos] = 0xBB;
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let restored = apply_patch(&old, &patch).expect("valid patch");
prop_assert_eq!(restored, new);
}
#[test]
fn simd_memcmp_full_match(n in 0usize..300) {
let buf = vec![0xCC_u8; n];
let patch = make_patch(&buf, &buf).expect("make_patch must succeed");
let restored = apply_patch(&buf, &patch).expect("valid patch");
prop_assert_eq!(restored, buf);
}
}
#[test]
fn common_prefix_len_oracle_smoke() {
assert_eq!(common_prefix_len(b"abcdef", b"abcxyz"), 3);
assert_eq!(common_prefix_len(b"", b"x"), 0);
assert_eq!(common_prefix_len(b"abc", b"abc"), 3);
}
#[test]
fn simd_memcmp_exhaustive_small() {
for n in 0_usize..=160 {
let a = vec![0x55_u8; n];
let patch = make_patch(&a, &a).expect("make_patch must succeed");
let restored = apply_patch(&a, &patch).expect("valid patch");
assert_eq!(restored, a, "full-match failed at n={n}");
for pos in 0..n {
let mut b = a.clone();
b[pos] = 0xAA;
let patch = make_patch(&a, &b).expect("make_patch must succeed");
let restored = apply_patch(&a, &patch).expect("valid patch");
assert_eq!(restored, b, "mismatch at pos={pos}, n={n}");
let patch = make_patch(&b, &a).expect("make_patch must succeed");
let restored = apply_patch(&b, &patch).expect("valid patch");
assert_eq!(restored, a, "reverse mismatch at pos={pos}, n={n}");
}
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 512, ..ProptestConfig::default() })]
#[test]
fn wrong_base_is_always_rejected(
old in vec(any::<u8>(), 1..512),
new in vec(any::<u8>(), 0..512),
flip in 0usize..512,
) {
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let mut other = old.clone();
let at = flip % other.len();
other[at] ^= 0xFF;
let rejected = matches!(
apply_patch(&other, &patch),
Err(PatchError::WrongBase { .. })
);
prop_assert!(rejected);
prop_assert_eq!(apply_patch(&old, &patch).expect("valid base"), new);
}
#[test]
fn header_matches_the_base(
old in vec(any::<u8>(), 0..512),
new in vec(any::<u8>(), 0..512),
) {
let patch = make_patch(&old, &new).expect("make_patch must succeed");
let header = inspect(&patch).expect("readable header").expect("v2 patch");
prop_assert_eq!((header.base_len, header.base_hash), base_fingerprint(&old));
}
}
#[test]
#[ignore = "allocates more than 4 GiB"]
fn four_gib_boundary_is_refused() {
let over = vec![0_u8; usize::try_from(u32::MAX).unwrap() + 1];
let small = b"small".to_vec();
assert_eq!(
make_patch(&over, &small),
Err(PatchError::InputTooLarge { len: over.len() })
);
assert_eq!(
make_patch(&small, &over),
Err(PatchError::InputTooLarge { len: over.len() })
);
let at_limit = vec![0_u8; usize::try_from(u32::MAX).unwrap()];
let patch = make_patch(&at_limit, &at_limit).expect("u32::MAX bytes must be accepted");
assert_eq!(
apply_patch(&at_limit, &patch).expect("valid patch"),
at_limit
);
}