use super::*;
use crate::comparator::DefaultUserComparator;
use test_log::test;
#[test]
fn test_longest_shared_prefix_length() {
assert_eq!(3, longest_shared_prefix_length(b"abc", b"abc"));
assert_eq!(1, longest_shared_prefix_length(b"abc", b"a"));
assert_eq!(1, longest_shared_prefix_length(b"a", b"abc"));
assert_eq!(0, longest_shared_prefix_length(b"abc", b""));
assert_eq!(0, longest_shared_prefix_length(b"", b"abc"));
assert_eq!(0, longest_shared_prefix_length(b"", b""));
assert_eq!(0, longest_shared_prefix_length(b"", b""));
assert_eq!(0, longest_shared_prefix_length(b"abc", b"def"));
assert_eq!(1, longest_shared_prefix_length(b"abc", b"acc"));
}
fn lsp_reference(s1: &[u8], s2: &[u8]) -> usize {
s1.iter().zip(s2.iter()).take_while(|(a, b)| a == b).count()
}
#[test]
fn lsp_scalar_on_boundaries_matches_reference() {
for total_len in [
0_usize, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 127, 128,
] {
for mismatch_at in 0..=total_len {
let mut a = vec![0xAA; total_len];
let mut b = a.clone();
if mismatch_at < total_len {
#[expect(
clippy::expect_used,
reason = "test: mismatch_at < total_len = b.len() guarantees in-bounds"
)]
{
*b.get_mut(mismatch_at).expect("in bounds") ^= 0xFF;
}
}
let got = lsp_scalar(&a, &b);
let want = lsp_reference(&a, &b);
assert_eq!(
want, got,
"scalar @ len={total_len} mismatch_at={mismatch_at}"
);
a.truncate(mismatch_at);
let got_short = lsp_scalar(&a, &b);
let want_short = lsp_reference(&a, &b);
assert_eq!(
want_short, got_short,
"scalar asym len={mismatch_at} vs {total_len}"
);
}
}
}
#[test]
fn longest_shared_prefix_length_on_boundaries_matches_reference() {
for total_len in [
0_usize, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 127, 128, 255, 256,
] {
for mismatch_at in 0..=total_len {
let mut a = vec![0xAA; total_len];
let mut b = a.clone();
if mismatch_at < total_len {
#[expect(
clippy::expect_used,
reason = "test: mismatch_at < total_len = b.len() guarantees in-bounds"
)]
{
*b.get_mut(mismatch_at).expect("in bounds") ^= 0xFF;
}
}
let got = longest_shared_prefix_length(&a, &b);
let want = lsp_reference(&a, &b);
assert_eq!(
want, got,
"dispatch @ len={total_len} mismatch_at={mismatch_at}"
);
a.truncate(mismatch_at);
let got_short = longest_shared_prefix_length(&a, &b);
let want_short = lsp_reference(&a, &b);
assert_eq!(
want_short, got_short,
"dispatch asym len={mismatch_at} vs {total_len}"
);
}
}
}
#[test]
fn lsp_extreme_byte_patterns_match_reference() {
for &(label, byte_a, byte_b) in &[
("all_zero_equal", 0x00_u8, 0x00_u8),
("all_ff_equal", 0xFF, 0xFF),
("zero_vs_ff", 0x00, 0xFF),
("alternating_match", 0x55, 0x55),
] {
for len in [0_usize, 1, 8, 15, 16, 31, 32, 33, 63, 64, 128, 1023] {
let a = vec![byte_a; len];
let b = vec![byte_b; len];
let want = lsp_reference(&a, &b);
assert_eq!(want, lsp_scalar(&a, &b), "scalar {label} len={len}");
assert_eq!(
want,
longest_shared_prefix_length(&a, &b),
"dispatch {label} len={len}"
);
}
}
for len in [0_usize, 1, 8, 32, 128, 1024] {
let nonempty = vec![0x42_u8; len];
assert_eq!(0, lsp_scalar(&nonempty, &[]));
assert_eq!(0, lsp_scalar(&[], &nonempty));
assert_eq!(0, longest_shared_prefix_length(&nonempty, &[]));
assert_eq!(0, longest_shared_prefix_length(&[], &nonempty));
}
}
#[cfg(any(
target_arch = "x86_64",
target_arch = "x86",
all(target_arch = "aarch64", target_endian = "little")
))]
fn assert_kernel_matches_reference<F: Fn(&[u8], &[u8]) -> usize>(label: &str, kernel: F) {
for total_len in [
0_usize, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 127, 128, 255, 256,
] {
for mismatch_at in 0..=total_len {
let mut a = vec![0xAA; total_len];
let mut b = a.clone();
if mismatch_at < total_len {
#[expect(
clippy::expect_used,
reason = "test: mismatch_at < total_len = b.len() guarantees in-bounds"
)]
{
*b.get_mut(mismatch_at).expect("in bounds") ^= 0xFF;
}
}
let want = lsp_reference(&a, &b);
assert_eq!(
want,
kernel(&a, &b),
"{label} @ len={total_len} mismatch_at={mismatch_at}"
);
a.truncate(mismatch_at);
let want_short = lsp_reference(&a, &b);
assert_eq!(
want_short,
kernel(&a, &b),
"{label} asym len={mismatch_at} vs {total_len}"
);
}
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[test]
fn lsp_sse2_on_boundaries_matches_reference() {
#[cfg(target_arch = "x86")]
if !std::is_x86_feature_detected!("sse2") {
return;
}
assert_kernel_matches_reference("sse2", |a, b| unsafe { lsp_sse2(a, b) });
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[test]
fn lsp_avx2_on_boundaries_matches_reference() {
if !std::is_x86_feature_detected!("avx2") {
return;
}
assert_kernel_matches_reference("avx2", |a, b| unsafe { lsp_avx2(a, b) });
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[test]
fn lsp_avx512_on_boundaries_matches_reference() {
if !std::is_x86_feature_detected!("avx512bw") {
return;
}
assert_kernel_matches_reference("avx512bw", |a, b| unsafe { lsp_avx512(a, b) });
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
#[test]
fn select_lsp_kernel_picks_widest_available_lane() {
use core::ptr::fn_addr_eq;
let pick = |avx512bw, avx2, sse2| {
select_lsp_kernel(LspCpuFeatures {
avx512bw,
avx2,
sse2,
})
};
assert!(fn_addr_eq(pick(true, true, true), lsp_avx512 as LspKernel));
assert!(fn_addr_eq(pick(false, true, true), lsp_avx2 as LspKernel));
assert!(fn_addr_eq(pick(false, false, true), lsp_sse2 as LspKernel));
assert!(fn_addr_eq(
pick(false, false, false),
lsp_scalar as LspKernel
));
assert!(fn_addr_eq(
pick(true, false, false),
lsp_avx512 as LspKernel
));
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[test]
fn lsp_neon_on_boundaries_matches_reference() {
assert_kernel_matches_reference("neon", |a, b| unsafe { lsp_neon(a, b) });
}
proptest::proptest! {
#[test]
fn lsp_scalar_equals_reference(
s1 in proptest::collection::vec(proptest::num::u8::ANY, 0..=1024),
s2 in proptest::collection::vec(proptest::num::u8::ANY, 0..=1024),
) {
proptest::prop_assert_eq!(lsp_scalar(&s1, &s2), lsp_reference(&s1, &s2));
}
#[test]
fn longest_shared_prefix_length_equals_reference(
s1 in proptest::collection::vec(proptest::num::u8::ANY, 0..=1024),
s2 in proptest::collection::vec(proptest::num::u8::ANY, 0..=1024),
) {
proptest::prop_assert_eq!(longest_shared_prefix_length(&s1, &s2), lsp_reference(&s1, &s2));
}
}
#[test]
fn test_compare_prefixed_slice() {
use core::cmp::Ordering::{Equal, Greater, Less};
assert_eq!(
Greater,
compare_prefixed_slice(&[0, 161], &[], &[0], &DefaultUserComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"abc", b"xyz", b"abcxyz", &DefaultUserComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"abc", b"", b"abc", &DefaultUserComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"abc", b"abc", b"abcabc", &DefaultUserComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"", b"", b"", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"a", b"", b"y", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"a", b"", b"yyy", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"a", b"", b"yyy", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"yyyy", b"a", b"yyyyb", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"yyy", b"b", b"yyyyb", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"abc", b"d", b"abce", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"ab", b"", b"ac", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"a", b"", b"", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"", b"a", b"", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"a", b"a", b"", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"b", b"a", b"a", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"a", b"b", b"a", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"abc", b"xy", b"abcw", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"ab", b"cde", b"a", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"abcd", b"zz", b"abc", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"abc", b"d", b"abc", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"aaaa", b"aaab", b"aaaaaaaa", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"aaaa", b"aaba", b"aaaaaaaa", &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(b"abcd", b"x", b"abc", &DefaultUserComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(&[0x7F], &[], &[0x80], &DefaultUserComparator)
);
assert_eq!(
Greater,
compare_prefixed_slice(&[0xFF], &[], &[0x10], &DefaultUserComparator)
);
}
struct ReverseComparator;
impl crate::comparator::UserComparator for ReverseComparator {
fn name(&self) -> &'static str {
"test-reverse"
}
fn compare(&self, a: &[u8], b: &[u8]) -> core::cmp::Ordering {
b.cmp(a)
}
}
#[test]
fn test_compare_prefixed_slice_custom_comparator() {
use core::cmp::Ordering::{Equal, Greater, Less};
use crate::comparator::UserComparator as _;
assert_eq!(ReverseComparator.name(), "test-reverse");
assert_eq!(
Greater,
compare_prefixed_slice(b"ab", b"c", b"xyz", &ReverseComparator)
);
assert_eq!(
Less,
compare_prefixed_slice(b"xy", b"z", b"abc", &ReverseComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"ab", b"c", b"abc", &ReverseComparator)
);
assert_eq!(
Equal,
compare_prefixed_slice(b"", b"", b"", &ReverseComparator)
);
assert_eq!(
Less, compare_prefixed_slice(b"a", b"", b"", &ReverseComparator)
);
}