use super::super::Backend;
use super::super::{BackendScalar8, BackendScalar16U8};
use super::generator::{ByteCursor, run_generated_inputs, test_bound};
use crate::Scoring;
use crate::smith_waterman::{Kernel, SmithWaterman, score_fits_in_u8};
#[derive(Debug, Clone)]
struct BackendCase {
needle: String,
haystack: Vec<u8>,
max_typos: Option<u16>,
case_sensitive: bool,
}
impl BackendCase {
fn from_bytes(input: &[u8]) -> Self {
let mut cursor = ByteCursor::new(input);
let needle_len = cursor
.len(test_bound(96, 32), &[1, 7, 8, 15, 16, 31, 32, 63, 64])
.max(1);
let haystack_len = cursor.len(
test_bound(768, 128),
&[0, 1, 7, 8, 15, 16, 31, 32, 63, 64, 1023, 1024, 1025],
);
let max_typos = match cursor.next() % 5 {
0 => None,
byte => Some((byte as u16 - 1) % 17),
};
let case_sensitive = cursor.bool();
Self {
needle: String::from_utf8_lossy(&cursor.bytes(needle_len)).to_string(),
haystack: cursor.bytes(haystack_len),
max_typos,
case_sensitive,
}
}
}
fn miri_inputs() -> &'static [&'static [u8]] {
&[
b"",
b"abcABC012 /.,_-:",
b"lane-boundary-8-16-32-64",
b"greedy-1023-1024-1025",
]
}
macro_rules! for_each_backend {
($callback:ident !( $($ctx:tt)* )) => {
$callback!($($ctx)* ; all(), "Scalar-u16", BackendScalar8, BackendScalar8, false);
$callback!($($ctx)* ; all(), "Scalar-u8", BackendScalar16U8, BackendScalar16U8, true);
$callback!($($ctx)* ; target_arch = "x86_64", "SSE-u16",
super::super::BackendSSE, BackendScalar8, false);
$callback!($($ctx)* ; target_arch = "x86_64", "AVX-u16",
super::super::BackendAVX, super::super::scalar::TestScalar16, false);
$callback!($($ctx)* ; target_arch = "x86_64", "AVX-512-u16",
super::super::BackendAVX512, super::super::scalar::TestScalar32, false);
$callback!($($ctx)* ; target_arch = "x86_64", "SSE-u8",
super::super::BackendSSEU8, BackendScalar16U8, true);
$callback!($($ctx)* ; target_arch = "x86_64", "AVX-u8",
super::super::BackendAVXU8, super::super::scalar::TestScalar32U8, true);
$callback!($($ctx)* ; target_arch = "x86_64", "AVX-512-u8",
super::super::BackendAVX512U8, super::super::scalar::TestScalar64U8, true);
$callback!($($ctx)* ; target_arch = "aarch64", "NEON-u16",
super::super::BackendNEON, BackendScalar8, false);
$callback!($($ctx)* ; target_arch = "aarch64", "NEON-u8",
super::super::BackendNEONU8, BackendScalar16U8, true);
};
}
fn cases() -> Vec<(&'static str, &'static str)> {
vec![
("a", "abc"),
("abc", "abc"),
("foo", "fooBar"),
("foo", "012345foo"),
("foo", "01234567foo"),
("foo", "0123456789foo"),
("foo", "0123456789012345foo"),
("foo", "0123456789012345678901234567foo"),
("test", "Utooooeoooosoooot"),
("test", "Utooooooeoooooosoooooot"),
("foo", "Ufooo"),
("foo", "Ufo"),
("hw", "hello_world"),
("fBr", "fooBar"),
("D", "FOR_DIST"),
("needle", "____________needle____________"),
("abcdefghij", "abcdefghij"),
("abcdefghijklmnopqrst", "abcdefghijklmnopqrst"),
]
}
fn score_with<B: Backend>(needle: &str, haystack: &str) -> u16 {
let mut matcher = SmithWaterman::<B>::new(needle, &Scoring::default(), false);
matcher.score_haystack(haystack.as_bytes())
}
fn indices_with<B: Backend>(needle: &str, haystack: &str) -> Option<Vec<usize>> {
let mut matcher = SmithWaterman::<B>::new(needle, &Scoring::default(), false);
matcher
.score_haystack_indices(haystack.as_bytes(), 0, None)
.map(|(_, indices)| indices)
}
fn assert_score_backend<B: Backend>(label: &str, needle: &str, haystack: &str, want: u16) {
if B::is_available() {
let got = score_with::<B>(needle, haystack);
assert_eq!(
got, want,
"{label} score mismatch for needle={needle:?} haystack={haystack:?}"
);
}
}
fn assert_indices_backend<B: Backend>(
label: &str,
needle: &str,
haystack: &str,
want: Option<Vec<usize>>,
) {
if B::is_available() {
let got = indices_with::<B>(needle, haystack);
assert_eq!(
got, want,
"{label} indices mismatch for needle={needle:?} haystack={haystack:?}"
);
}
}
macro_rules! assert_score_row {
($needle:expr, $haystack:expr, $want:expr ;
$cfg:meta, $label:literal, $backend:ty, $reference:ty, $is_u8:literal) => {
#[cfg($cfg)]
{
if !$is_u8 || score_fits_in_u8($needle.len(), &Scoring::default()) {
assert_score_backend::<$backend>($label, $needle, $haystack, $want);
}
}
};
}
macro_rules! assert_indices_row {
($needle:expr, $haystack:expr, $want:expr ;
$cfg:meta, $label:literal, $backend:ty, $reference:ty, $is_u8:literal) => {
#[cfg($cfg)]
{
if !$is_u8 || score_fits_in_u8($needle.len(), &Scoring::default()) {
assert_indices_backend::<$backend>($label, $needle, $haystack, $want.clone());
}
}
};
}
#[test]
fn cross_backend_parity_score() {
for (needle, haystack) in cases() {
let want = score_with::<BackendScalar8>(needle, haystack);
for_each_backend!(assert_score_row!(needle, haystack, want));
}
}
#[test]
fn cross_backend_parity_indices() {
for (needle, haystack) in cases() {
let want = indices_with::<BackendScalar8>(needle, haystack);
for_each_backend!(assert_indices_row!(needle, haystack, want));
}
}
fn score_bytes_with<B: Backend>(needle: &str, haystack: &[u8], case_sensitive: bool) -> u16 {
let mut matcher = SmithWaterman::<B>::new(needle, &Scoring::default(), case_sensitive);
matcher.score_haystack(haystack)
}
fn indices_bytes_with<B: Backend>(
needle: &str,
haystack: &[u8],
max_typos: Option<u16>,
case_sensitive: bool,
) -> Option<(u16, Vec<usize>)> {
let mut matcher = SmithWaterman::<B>::new(needle, &Scoring::default(), case_sensitive);
matcher.score_haystack_indices(haystack, 0, max_typos)
}
fn assert_backend<B: Backend>(
label: &str,
needle: &str,
haystack: &[u8],
max_typos: Option<u16>,
case_sensitive: bool,
want_score: u16,
want_indices_score: Option<u16>,
) {
if B::is_available() {
assert_eq!(
score_bytes_with::<B>(needle, haystack, case_sensitive),
want_score,
"{label} score mismatch for needle={needle:?} haystack_len={}",
haystack.len()
);
let indices = indices_bytes_with::<B>(needle, haystack, max_typos, case_sensitive);
assert_eq!(
indices.as_ref().map(|(score, _)| *score),
want_indices_score,
"{label} indexed score mismatch for needle={needle:?} haystack_len={}",
haystack.len()
);
if let Some((_, indices)) = indices {
assert_indices_valid(label, needle, haystack, &indices);
}
}
}
fn assert_backend_matches_reference<B: Backend, R: Backend>(
label: &str,
needle: &str,
haystack: &[u8],
max_typos: Option<u16>,
case_sensitive: bool,
) {
if B::is_available() {
let want_score = score_bytes_with::<R>(needle, haystack, case_sensitive);
let want_indices_score =
indices_bytes_with::<R>(needle, haystack, max_typos, case_sensitive)
.as_ref()
.map(|(score, _)| *score);
assert_backend::<B>(
label,
needle,
haystack,
max_typos,
case_sensitive,
want_score,
want_indices_score,
);
}
}
fn assert_indices_valid(label: &str, needle: &str, haystack: &[u8], indices: &[usize]) {
assert!(
indices.windows(2).all(|window| window[0] > window[1]),
"{} indices are not in reverse order: {:?}",
label,
indices
);
assert!(
indices.len() <= needle.len(),
"{} indices contain more positions than needle bytes: indices={:?} needle_len={}",
label,
indices,
needle.len()
);
for &index in indices {
assert!(
index < haystack.len(),
"{} index {} is out of bounds for haystack_len={}",
label,
index,
haystack.len()
);
}
}
macro_rules! assert_case_row {
($case:expr ;
$cfg:meta, $label:literal, $backend:ty, $reference:ty, $is_u8:literal) => {
#[cfg($cfg)]
{
if !$is_u8 || score_fits_in_u8($case.needle.len(), &Scoring::default()) {
assert_backend_matches_reference::<$backend, $reference>(
$label,
&$case.needle,
&$case.haystack,
$case.max_typos,
$case.case_sensitive,
);
}
}
};
}
fn assert_backend_case(case: &BackendCase) {
for_each_backend!(assert_case_row!(case));
}
#[test]
fn randomized_cross_backend_parity() {
if cfg!(miri) {
for input in miri_inputs() {
assert_backend_case(&BackendCase::from_bytes(input));
}
return;
}
run_generated_inputs(192, test_bound(2048, 384), |input| {
assert_backend_case(&BackendCase::from_bytes(input));
});
}