use crate::ported::zsh_h::sortelt;
use crate::zsh_h::{
SORTIT_ANYOLDHOW, SORTIT_BACKWARDS, SORTIT_IGNORING_BACKSLASHES, SORTIT_IGNORING_CASE,
SORTIT_NUMERICALLY, SORTIT_NUMERICALLY_SIGNED, SORTIT_SOMEHOW,
};
use libc;
use std::cmp::Ordering;
use std::ffi::CString;
pub fn eltpcmp(a: &sortelt, b: &sortelt, sort_flags: u32) -> Ordering {
let reverse = (sort_flags & (SORTIT_BACKWARDS as u32)) != 0;
let a_has_len = a.len >= 0;
let b_has_len = b.len >= 0;
let result = if !a_has_len && !b_has_len {
zstrcmp(&a.cmp, &b.cmp, sort_flags & !(SORTIT_BACKWARDS as u32))
} else {
let ab = a.cmp.as_bytes();
let bb = b.cmp.as_bytes();
let mut len: i64 = if a_has_len {
if b_has_len {
(a.len as i64).min(b.len as i64)
} else {
a.len as i64
}
} else {
b.len as i64
};
let mut laststarta: usize = 0; let mut i: usize = 0;
loop {
let ca = ab.get(i).copied().unwrap_or(0);
let cb = bb.get(i).copied().unwrap_or(0);
if ca != cb {
break; }
if len <= 0 {
break; }
len -= 1;
if ca == 0 {
if !a_has_len || !b_has_len {
break; }
laststarta = i + 1; }
i += 1;
}
let ca = ab.get(i).copied().unwrap_or(0);
let cb = bb.get(i).copied().unwrap_or(0);
let alen = if a_has_len { a.len as i64 } else { -1 };
let blen = if b_has_len { b.len as i64 } else { -1 };
if ca == cb && alen != blen {
if a_has_len {
if b_has_len {
alen.cmp(&blen)
} else {
Ordering::Greater }
} else {
Ordering::Less }
} else {
let at = a.cmp.get(laststarta..).unwrap_or("");
let bt = b.cmp.get(laststarta..).unwrap_or("");
zstrcmp(at, bt, sort_flags & !(SORTIT_BACKWARDS as u32)) }
};
if reverse {
result.reverse()
} else {
result
}
}
pub fn zstrcmp(a: &str, bs: &str, sortflags: u32) -> Ordering {
let sortnumeric = if sortflags & (SORTIT_NUMERICALLY_SIGNED as u32) != 0 {
-1 } else if sortflags & (SORTIT_NUMERICALLY as u32) != 0 {
1
} else {
0
};
let numeric = sortnumeric != 0;
let numeric_signed = sortnumeric < 0;
let no_backslash = (sortflags & (SORTIT_IGNORING_BACKSLASHES as u32)) != 0;
let mut a_str: &str = a;
let mut b_str: &str = bs;
let a_owned: String;
let b_owned: String;
if no_backslash {
let mut done = false;
if !numeric {
if !a.as_bytes().contains(&b'\\') && !bs.as_bytes().contains(&b'\\') {
done = true; } else {
let (ab, bb) = (a.as_bytes(), bs.as_bytes());
let (alen, blen) = (ab.len(), bb.len()); let (mut i, mut j) = (0usize, 0usize);
while i < alen && j < blen {
if ab[i] == b'\\' {
i += 1; }
if bb[j] == b'\\' {
j += 1; }
if i >= alen || j >= blen || ab[i] != bb[j] {
break;
}
i += 1; j += 1;
}
if let (Some(ar), Some(br)) = (a.get(i..), bs.get(j..)) {
a_str = ar;
b_str = br;
done = true;
}
}
}
if !done {
a_owned = a_str.chars().filter(|&c| c != '\\').collect();
b_owned = b_str.chars().filter(|&c| c != '\\').collect();
a_str = &a_owned;
b_str = &b_owned;
}
}
let strcoll_cmp = |a: &str, b: &str| -> Ordering {
#[cfg(unix)]
{
const SCRATCH: usize = 256;
let mut abuf = [0u8; SCRATCH];
let mut bbuf = [0u8; SCRATCH];
let fill = |s: &str, buf: &mut [u8; SCRATCH]| -> bool {
let sb = s.as_bytes();
if sb.len() >= SCRATCH {
return false;
}
buf[..sb.len()].copy_from_slice(sb);
buf[sb.len()] = 0;
true
};
if fill(a, &mut abuf) && fill(b, &mut bbuf) {
let c = unsafe {
libc::strcoll(
abuf.as_ptr() as *const libc::c_char,
bbuf.as_ptr() as *const libc::c_char,
)
};
return c.cmp(&0);
}
let cstr_head = |s: &str| -> CString {
let bs = s.as_bytes();
let n = bs.iter().position(|&x| x == 0).unwrap_or(bs.len());
CString::new(&bs[..n]).unwrap_or_else(|_| CString::new(vec![0u8]).expect("nul"))
};
let c = unsafe { libc::strcoll(cstr_head(a).as_ptr(), cstr_head(b).as_ptr()) };
c.cmp(&0)
}
#[cfg(not(unix))]
{
a.cmp(b)
}
};
let cmp_numeric = |a: &str, bs: &str, signed_mode: bool| -> Ordering {
let ab = a.as_bytes();
let bb = bs.as_bytes();
let at = |s: &[u8], i: usize| -> u8 { s.get(i).copied().unwrap_or(0) };
let is_digit = |c: u8| c.is_ascii_digit();
let (mut ai, mut bi) = (0usize, 0usize);
while at(ab, ai) == at(bb, bi) && at(ab, ai) != 0 {
ai += 1;
bi += 1;
}
if signed_mode {
if at(ab, ai) == b'-' && is_digit(at(ab, ai + 1)) && is_digit(at(bb, bi)) {
return Ordering::Less; }
if at(bb, bi) == b'-' && is_digit(at(bb, bi + 1)) && is_digit(at(ab, ai)) {
return Ordering::Greater; }
}
if is_digit(at(ab, ai)) || is_digit(at(bb, bi)) {
while ai > 0 && is_digit(at(ab, ai - 1)) {
ai -= 1;
bi -= 1;
}
let mul: i32 = if signed_mode && ai > 0 && at(ab, ai - 1) == b'-' {
-1
} else {
1
};
if is_digit(at(ab, ai)) && is_digit(at(bb, bi)) {
while at(ab, ai) == b'0' {
ai += 1;
}
while at(bb, bi) == b'0' {
bi += 1;
}
while is_digit(at(ab, ai)) && at(ab, ai) == at(bb, bi) {
ai += 1;
bi += 1;
}
if is_digit(at(ab, ai)) || is_digit(at(bb, bi)) {
let cmp = mul * ((at(ab, ai) as i32) - (at(bb, bi) as i32));
while is_digit(at(ab, ai)) && is_digit(at(bb, bi)) {
ai += 1;
bi += 1;
}
if is_digit(at(ab, ai)) && !is_digit(at(bb, bi)) {
return if mul >= 0 {
Ordering::Greater
} else {
Ordering::Less
}; }
if is_digit(at(bb, bi)) && !is_digit(at(ab, ai)) {
return if mul >= 0 {
Ordering::Less
} else {
Ordering::Greater
}; }
return cmp.cmp(&0); }
}
}
strcoll_cmp(a, bs)
};
if numeric {
cmp_numeric(a_str, b_str, numeric_signed)
} else {
strcoll_cmp(a_str, b_str)
}
}
pub fn strmetasort(
arr: &mut [String],
sort_flags: u32,
unmetalenp: Option<&mut [usize]>,
) {
if arr.len() < 2 {
return;
}
let apply_transforms = |s: &str| -> String {
let mut t = s.to_string();
if sort_flags & (SORTIT_IGNORING_CASE as u32) != 0 {
t = t.to_lowercase(); }
if sort_flags & (SORTIT_IGNORING_BACKSLASHES as u32) != 0 {
t = t.chars().filter(|&c| c != '\\').collect(); }
t
};
let elts: Vec<sortelt> = match unmetalenp.as_deref() {
Some(lens) => arr
.iter()
.zip(lens.iter())
.map(|(s, &l)| sortelt {
orig: s.clone(),
cmp: apply_transforms(s),
origlen: l as i32,
len: l as i32,
})
.collect(),
None => arr
.iter()
.map(|s| {
let cmp = apply_transforms(s);
let needlen = cmp.as_bytes().contains(&0u8); let len = if needlen { cmp.len() as i32 } else { -1 }; sortelt {
orig: s.clone(),
cmp,
origlen: -1,
len,
}
})
.collect(),
};
let mut indices: Vec<usize> = (0..elts.len()).collect();
crate::tolerant_sort::qsort_tolerant(&mut indices, |&i, &j| {
eltpcmp(&elts[i], &elts[j], sort_flags)
});
let original: Vec<String> = arr.to_vec();
let original_lens: Option<Vec<usize>> = unmetalenp.as_deref().map(|l| l.to_vec());
for (dst, &src) in indices.iter().enumerate() {
arr[dst] = original[src].clone();
}
if let (Some(out), Some(orig_lens)) = (unmetalenp, original_lens) {
for (dst, &src) in indices.iter().enumerate() {
out[dst] = orig_lens[src];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flag_values_match_c_sortit() {
let _g = crate::test_util::global_state_lock();
assert_eq!(SORTIT_ANYOLDHOW, 0);
assert_eq!(SORTIT_IGNORING_CASE, 1);
assert_eq!(SORTIT_NUMERICALLY, 2);
assert_eq!(SORTIT_NUMERICALLY_SIGNED, 4);
assert_eq!(SORTIT_BACKWARDS, 8);
assert_eq!(SORTIT_IGNORING_BACKSLASHES, 16);
assert_eq!(SORTIT_SOMEHOW, 32);
}
#[test]
fn test_zstrcmp_basic() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zstrcmp("abc", "def", 0), Ordering::Less);
assert_eq!(zstrcmp("def", "abc", 0), Ordering::Greater);
assert_eq!(zstrcmp("abc", "abc", 0), Ordering::Equal);
}
#[test]
fn test_zstrcmp_ignores_backwards_per_c() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("abc", "def", SORTIT_BACKWARDS as u32),
zstrcmp("abc", "def", 0)
);
}
#[test]
fn test_zstrcmp_ignores_case_flag_per_c() {
let _g = crate::test_util::global_state_lock();
let with = zstrcmp("ABC", "abc", SORTIT_IGNORING_CASE as u32);
let without = zstrcmp("ABC", "abc", 0);
assert_eq!(with, without);
}
#[test]
fn test_zstrcmp_numeric() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("file2", "file10", SORTIT_NUMERICALLY as u32),
Ordering::Less
);
assert_eq!(
zstrcmp("file10", "file2", SORTIT_NUMERICALLY as u32),
Ordering::Greater
);
assert_eq!(
zstrcmp("100", "20", SORTIT_NUMERICALLY as u32),
Ordering::Greater
);
}
#[test]
fn test_zstrcmp_numeric_signed() {
let _g = crate::test_util::global_state_lock();
let f = (SORTIT_NUMERICALLY | SORTIT_NUMERICALLY_SIGNED) as u32;
assert_eq!(zstrcmp("-5", "3", f), Ordering::Less);
assert_eq!(zstrcmp("-10", "-2", f), Ordering::Less);
assert_eq!(zstrcmp("5", "-3", f), Ordering::Greater);
}
#[test]
fn test_natural_sort() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![
"file10".to_string(),
"file2".to_string(),
"file1".to_string(),
"file20".to_string(),
];
strmetasort(
&mut arr,
(SORTIT_NUMERICALLY | SORTIT_NUMERICALLY_SIGNED) as u32,
None,
);
assert_eq!(arr, vec!["file1", "file2", "file10", "file20"]);
}
#[test]
fn test_strmetasort() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![
"zebra".to_string(),
"apple".to_string(),
"mango".to_string(),
];
strmetasort(&mut arr, 0, None);
assert_eq!(arr, vec!["apple", "mango", "zebra"]);
}
#[test]
fn test_reverse_sort() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["a".to_string(), "b".to_string(), "c".to_string()];
strmetasort(&mut arr, SORTIT_BACKWARDS as u32, None);
assert_eq!(arr, vec!["c", "b", "a"]);
}
#[test]
fn test_case_insensitive_sort() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![
"Banana".to_string(),
"apple".to_string(),
"Cherry".to_string(),
];
strmetasort(&mut arr, SORTIT_IGNORING_CASE as u32, None);
assert_eq!(arr, vec!["apple", "Banana", "Cherry"]);
}
#[test]
fn test_no_backslash() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("a\\bc", "abc", SORTIT_IGNORING_BACKSLASHES as u32),
Ordering::Equal
);
}
#[test]
fn test_strmetasort_lowercases_via_ignoring_case() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["BANANA".to_string(), "apple".to_string()];
strmetasort(&mut arr, SORTIT_IGNORING_CASE as u32, None);
assert_eq!(arr, vec!["apple", "BANANA"]);
}
#[test]
fn test_strmetasort_strips_backslashes_via_ignoring_bs() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["a\\b".to_string(), "ab".to_string()];
strmetasort(&mut arr, SORTIT_IGNORING_BACKSLASHES as u32, None);
assert_eq!(arr[0], "a\\b");
assert_eq!(arr[1], "ab");
}
#[test]
fn test_eltpcmp_embedded_null_shorter_sorts_below() {
let _g = crate::test_util::global_state_lock();
let a = sortelt {
orig: "abc".to_string(),
cmp: "abc".to_string(),
origlen: 3,
len: 3,
};
let b = sortelt {
orig: "abc".to_string(),
cmp: "abc".to_string(),
origlen: 5,
len: 5,
};
assert_eq!(eltpcmp(&a, &b, 0), Ordering::Less);
assert_eq!(eltpcmp(&b, &a, 0), Ordering::Greater);
}
#[test]
fn test_strmetasort_reorders_lens_in_lockstep() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![
"banana".to_string(),
"apple".to_string(),
"cherry".to_string(),
];
let mut lens = vec![6, 5, 6];
strmetasort(&mut arr, 0, Some(&mut lens));
assert_eq!(arr, vec!["apple", "banana", "cherry"]);
assert_eq!(lens, vec![5, 6, 6]);
}
#[test]
fn test_strmetasort_single_or_empty() {
let _g = crate::test_util::global_state_lock();
let mut empty: Vec<String> = vec![];
strmetasort(&mut empty, 0, None);
assert!(empty.is_empty());
let mut single = vec!["only".to_string()];
strmetasort(&mut single, SORTIT_BACKWARDS as u32, None);
assert_eq!(single, vec!["only"]);
}
use std::cmp::Ordering;
#[test]
fn zstrcmp_equal_strings_return_equal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("foo", "foo", SORTIT_ANYOLDHOW as u32),
Ordering::Equal
);
}
#[test]
fn zstrcmp_lex_order_default() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("apple", "banana", SORTIT_ANYOLDHOW as u32),
Ordering::Less
);
assert_eq!(
zstrcmp("banana", "apple", SORTIT_ANYOLDHOW as u32),
Ordering::Greater
);
}
#[test]
fn zstrcmp_default_case_sensitive() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("ABC", "abc", SORTIT_ANYOLDHOW as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_ignore_case_makes_abc_equal_to_uppercase_anchored() {
let _g = crate::test_util::global_state_lock();
let with = zstrcmp("ABC", "abc", SORTIT_IGNORING_CASE as u32);
let without = zstrcmp("ABC", "abc", SORTIT_ANYOLDHOW as u32);
assert_eq!(
with, without,
"c:zstrcmp ignores IGNORING_CASE; pre-pass lives in strmetasort"
);
}
#[test]
fn zstrcmp_ignore_case_still_compares_differing_letters() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("AbC", "abd", SORTIT_IGNORING_CASE as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_numeric_mode_two_less_than_ten() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("2", "10", SORTIT_NUMERICALLY as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_lex_mode_ten_less_than_two() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zstrcmp("10", "2", SORTIT_ANYOLDHOW as u32), Ordering::Less);
}
#[test]
fn zstrcmp_numeric_mode_embedded_numbers() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("file2", "file10", SORTIT_NUMERICALLY as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_numeric_mode_no_digits_falls_back_to_lex() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("abc", "abd", SORTIT_NUMERICALLY as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_both_empty_returns_equal() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zstrcmp("", "", SORTIT_ANYOLDHOW as u32), Ordering::Equal);
}
#[test]
fn zstrcmp_empty_less_than_non_empty() {
let _g = crate::test_util::global_state_lock();
assert_eq!(zstrcmp("", "x", SORTIT_ANYOLDHOW as u32), Ordering::Less);
assert_eq!(zstrcmp("x", "", SORTIT_ANYOLDHOW as u32), Ordering::Greater);
}
#[test]
fn zstrcmp_prefix_is_less_than_longer_string() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("foo", "foobar", SORTIT_ANYOLDHOW as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_is_antisymmetric() {
let _g = crate::test_util::global_state_lock();
for (a, b) in [
("alpha", "beta"),
("file2", "file10"),
("", "x"),
("foo", "foobar"),
] {
let ab = zstrcmp(a, b, SORTIT_ANYOLDHOW as u32);
let ba = zstrcmp(b, a, SORTIT_ANYOLDHOW as u32);
assert_eq!(ab, ba.reverse(), "antisymmetry for ({a:?}, {b:?})");
}
}
#[test]
fn zstrcmp_numeric_unsigned_treats_minus_as_lex() {
let _g = crate::test_util::global_state_lock();
let ord = zstrcmp("-5", "5", SORTIT_NUMERICALLY as u32);
assert_eq!(ord, Ordering::Less);
}
#[test]
fn zstrcmp_numeric_continues_lex_after_digits() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
zstrcmp("file2bc", "file2bd", SORTIT_NUMERICALLY as u32),
Ordering::Less
);
}
#[test]
fn zstrcmp_numeric_leading_zeros_compare_equal_value() {
let _g = crate::test_util::global_state_lock();
let ord = zstrcmp("file02", "file2", SORTIT_NUMERICALLY as u32);
assert_ne!(ord, Ordering::Greater);
}
#[test]
fn strmetasort_backwards_reverses_lex_order() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
strmetasort(&mut arr, SORTIT_BACKWARDS as u32, None);
assert_eq!(arr, vec!["gamma", "beta", "alpha"]);
}
#[test]
fn strmetasort_stable_on_equal_keys() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec![
"apple".to_string(),
"Apple".to_string(),
"APPLE".to_string(),
];
strmetasort(&mut arr, SORTIT_IGNORING_CASE as u32, None);
assert_eq!(arr[0], "apple");
assert_eq!(arr[1], "Apple");
assert_eq!(arr[2], "APPLE");
}
#[test]
fn strmetasort_ignore_backslashes_equates_escaped_unescaped() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["a\\b\\c".to_string(), "abc".to_string()];
strmetasort(&mut arr, SORTIT_IGNORING_BACKSLASHES as u32, None);
assert_eq!(arr[0], "a\\b\\c");
assert_eq!(arr[1], "abc");
}
#[test]
fn strmetasort_empty_unmetalenp_does_not_panic() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec![];
let mut lens: Vec<usize> = vec![];
strmetasort(&mut arr, 0, Some(&mut lens));
assert!(arr.is_empty());
assert!(lens.is_empty());
}
#[test]
fn eltpcmp_backwards_inverts_ordering() {
let _g = crate::test_util::global_state_lock();
let a = sortelt {
orig: "a".to_string(),
cmp: "a".to_string(),
origlen: 1,
len: -1,
};
let b = sortelt {
orig: "b".to_string(),
cmp: "b".to_string(),
origlen: 1,
len: -1,
};
let fwd = eltpcmp(&a, &b, 0);
let rev = eltpcmp(&a, &b, SORTIT_BACKWARDS as u32);
assert_eq!(fwd, Ordering::Less);
assert_eq!(rev, Ordering::Greater);
}
#[test]
fn eltpcmp_equal_under_reverse_stays_equal() {
let _g = crate::test_util::global_state_lock();
let a = sortelt {
orig: "x".to_string(),
cmp: "x".to_string(),
origlen: 1,
len: -1,
};
let b = sortelt {
orig: "x".to_string(),
cmp: "x".to_string(),
origlen: 1,
len: -1,
};
assert_eq!(eltpcmp(&a, &b, SORTIT_BACKWARDS as u32), Ordering::Equal);
}
#[test]
fn strmetasort_already_sorted_is_idempotent() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()];
let expected = arr.clone();
strmetasort(&mut arr, 0, None);
assert_eq!(arr, expected);
}
#[test]
fn strmetasort_numeric_sorts_by_value() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["100".to_string(), "20".to_string(), "3".to_string()];
strmetasort(
&mut arr,
(SORTIT_NUMERICALLY | SORTIT_NUMERICALLY_SIGNED) as u32,
None,
);
assert_eq!(arr, vec!["3", "20", "100"]);
}
#[test]
fn strmetasort_numeric_backwards_descends() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["1".to_string(), "10".to_string(), "2".to_string()];
strmetasort(
&mut arr,
(SORTIT_NUMERICALLY | SORTIT_NUMERICALLY_SIGNED | SORTIT_BACKWARDS) as u32,
None,
);
assert_eq!(arr, vec!["10", "2", "1"]);
}
#[test]
fn zstrcmp_reflexive_full_sweep() {
for s in ["", "a", "abc", "1", "100", "Hello World", "日本"] {
assert_eq!(
zstrcmp(s, s, 0),
Ordering::Equal,
"zstrcmp({:?}, {:?}) must be Equal",
s,
s
);
}
}
#[test]
fn zstrcmp_antisymmetric() {
let pairs = [("a", "b"), ("apple", "banana"), ("", "x"), ("abc", "abd")];
for (a, b) in pairs {
let ab = zstrcmp(a, b, 0);
let ba = zstrcmp(b, a, 0);
assert_eq!(
ab.reverse(),
ba,
"zstrcmp must be antisymmetric for ({:?}, {:?})",
a,
b
);
}
}
#[test]
fn zstrcmp_is_deterministic() {
for (a, b) in [("foo", "bar"), ("a", "z"), ("100", "20")] {
let first = zstrcmp(a, b, 0);
for _ in 0..5 {
assert_eq!(
zstrcmp(a, b, 0),
first,
"zstrcmp({:?}, {:?}) must be deterministic",
a,
b
);
}
}
}
#[test]
fn zstrcmp_numeric_leading_zeros_string_compare() {
let r = zstrcmp(
"007",
"7",
(SORTIT_NUMERICALLY | SORTIT_NUMERICALLY_SIGNED) as u32,
);
assert_ne!(
r,
Ordering::Equal,
"007 vs 7 differ after numeric tie via lex fallback"
);
}
#[test]
fn zstrcmp_lex_mode_case_sensitive() {
let r = zstrcmp("abc", "ABC", 0);
assert_ne!(r, Ordering::Equal, "lex mode must distinguish ABC from abc");
}
#[test]
fn strmetasort_empty_array_is_noop() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec![];
strmetasort(&mut arr, 0, None);
assert!(arr.is_empty());
}
#[test]
fn strmetasort_single_element_unchanged() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["only".to_string()];
strmetasort(&mut arr, 0, None);
assert_eq!(arr, vec!["only"]);
}
#[test]
fn strmetasort_double_sort_idempotent_full_sweep() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["c", "a", "b", "d"]
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
strmetasort(&mut arr, 0, None);
let after_first = arr.clone();
strmetasort(&mut arr, 0, None);
assert_eq!(arr, after_first, "double sort must be idempotent");
}
#[test]
fn eltpcmp_is_deterministic() {
let a = sortelt {
orig: "a".to_string(),
cmp: "a".to_string(),
origlen: 1,
len: 1,
};
let b = sortelt {
orig: "b".to_string(),
cmp: "b".to_string(),
origlen: 1,
len: 1,
};
let first = eltpcmp(&a, &b, 0);
for _ in 0..5 {
assert_eq!(eltpcmp(&a, &b, 0), first, "eltpcmp must be deterministic");
}
}
#[test]
fn eltpcmp_returns_ordering_type() {
let a = sortelt {
orig: "a".into(),
cmp: "a".into(),
origlen: 1,
len: 1,
};
let b = sortelt {
orig: "b".into(),
cmp: "b".into(),
origlen: 1,
len: 1,
};
let _: Ordering = eltpcmp(&a, &b, 0);
}
#[test]
fn eltpcmp_same_element_returns_equal() {
let a = sortelt {
orig: "x".into(),
cmp: "x".into(),
origlen: 1,
len: 1,
};
assert_eq!(
eltpcmp(&a, &a, 0),
Ordering::Equal,
"comparing element with itself must return Equal"
);
}
#[test]
fn eltpcmp_antisymmetric() {
let a = sortelt {
orig: "a".into(),
cmp: "a".into(),
origlen: 1,
len: 1,
};
let b = sortelt {
orig: "b".into(),
cmp: "b".into(),
origlen: 1,
len: 1,
};
let ab = eltpcmp(&a, &b, 0);
let ba = eltpcmp(&b, &a, 0);
assert_eq!(
ab.reverse(),
ba,
"cmp(a,b) must be reverse of cmp(b,a); got {:?} vs {:?}",
ab,
ba
);
}
#[test]
fn zstrcmp_returns_ordering_type() {
let _: Ordering = zstrcmp("a", "b", 0);
}
#[test]
fn zstrcmp_both_empty_returns_equal_alt() {
assert_eq!(zstrcmp("", "", 0), Ordering::Equal);
}
#[test]
fn zstrcmp_identity_returns_equal() {
for s in ["", "a", "abc", "hello world", "日本"] {
assert_eq!(
zstrcmp(s, s, 0),
Ordering::Equal,
"identity zstrcmp({:?}, {:?}, 0) must be Equal",
s,
s
);
}
}
#[test]
fn zstrcmp_antisymmetric_alt() {
for (a, b) in &[("a", "b"), ("abc", "abd"), ("", "x"), ("xy", "x")] {
let ab = zstrcmp(a, b, 0);
let ba = zstrcmp(b, a, 0);
assert_eq!(
ab.reverse(),
ba,
"antisymmetry: zstrcmp({:?},{:?}) = {:?} ≠ reverse({:?})",
a,
b,
ab,
ba
);
}
}
#[test]
fn strmetasort_returns_void_type() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec![];
let _: () = strmetasort(&mut arr, 0, None);
}
#[test]
fn strmetasort_sorts_lex_ascending() {
let _g = crate::test_util::global_state_lock();
let mut arr = vec!["c".to_string(), "a".to_string(), "b".to_string()];
strmetasort(&mut arr, 0, None);
assert_eq!(arr, vec!["a", "b", "c"], "default lex ascending");
}
#[test]
fn strmetasort_preserves_length() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = (0..20).map(|i| format!("item_{}", i)).collect();
let before_len = arr.len();
strmetasort(&mut arr, 0, None);
assert_eq!(arr.len(), before_len, "length must be preserved");
}
#[test]
fn strmetasort_already_sorted_unchanged() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec!["a".into(), "b".into(), "c".into(), "d".into()];
let before = arr.clone();
strmetasort(&mut arr, 0, None);
assert_eq!(arr, before, "already-sorted unchanged");
}
#[test]
fn sortit_anyoldhow_is_zero_sentinel() {
assert_eq!(SORTIT_ANYOLDHOW, 0);
}
#[test]
fn sortit_flags_pairwise_distinct() {
let bits = [
SORTIT_IGNORING_CASE,
SORTIT_NUMERICALLY,
SORTIT_NUMERICALLY_SIGNED,
SORTIT_BACKWARDS,
SORTIT_IGNORING_BACKSLASHES,
SORTIT_SOMEHOW,
];
let unique: std::collections::HashSet<_> = bits.iter().copied().collect();
assert_eq!(
unique.len(),
bits.len(),
"SORTIT_* flags must be pairwise distinct"
);
}
#[test]
fn sortit_flags_all_powers_of_two() {
for v in [
SORTIT_IGNORING_CASE,
SORTIT_NUMERICALLY,
SORTIT_NUMERICALLY_SIGNED,
SORTIT_BACKWARDS,
SORTIT_IGNORING_BACKSLASHES,
SORTIT_SOMEHOW,
] {
assert!(
(v as u32).is_power_of_two(),
"SORTIT_* {} must be single bit",
v
);
}
}
#[test]
fn sortit_flags_or_covers_low_6_bits() {
let or_all = SORTIT_IGNORING_CASE
| SORTIT_NUMERICALLY
| SORTIT_NUMERICALLY_SIGNED
| SORTIT_BACKWARDS
| SORTIT_IGNORING_BACKSLASHES
| SORTIT_SOMEHOW;
assert_eq!(or_all, 63, "SORTIT_* must cover bits 0..=5");
}
#[test]
fn zstrcmp_reflexive_non_empty() {
for s in ["a", "abc", " ", "hello world"] {
assert_eq!(
zstrcmp(s, s, 0),
Ordering::Equal,
"zstrcmp({:?}, {:?}, 0) must be Equal",
s,
s
);
}
}
#[test]
fn zstrcmp_does_not_honor_backwards_flag() {
let normal = zstrcmp("a", "b", 0);
let with_back = zstrcmp("a", "b", SORTIT_BACKWARDS as u32);
assert_eq!(
normal, with_back,
"zstrcmp must not invert on BACKWARDS — that's eltpcmp's job"
);
}
#[test]
fn zstrcmp_does_not_honor_ignoring_case_flag() {
let no_flag = zstrcmp("ABC", "abc", 0);
let with_flag = zstrcmp("ABC", "abc", SORTIT_IGNORING_CASE as u32);
assert_eq!(
no_flag, with_flag,
"zstrcmp must not case-fold; flag is no-op at this level"
);
}
#[test]
fn zstrcmp_numeric_orders_two_before_ten() {
let r = zstrcmp("2", "10", SORTIT_NUMERICALLY as u32);
assert_eq!(
r,
Ordering::Less,
"numeric sort: 2 < 10 (lex would say 1 < 2)"
);
}
#[test]
fn strmetasort_corpus_round_trip() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec![
"zebra".into(),
"apple".into(),
"mango".into(),
"banana".into(),
];
strmetasort(&mut arr, 0, None);
assert_eq!(
arr,
vec![
"apple".to_string(),
"banana".into(),
"mango".into(),
"zebra".into()
]
);
}
#[test]
fn strmetasort_empty_input_no_panic() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = Vec::new();
strmetasort(&mut arr, 0, None);
assert!(arr.is_empty());
}
#[test]
fn strmetasort_single_element_no_op() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec!["only".into()];
strmetasort(&mut arr, 0, None);
assert_eq!(arr, vec!["only".to_string()]);
}
#[test]
fn strmetasort_backwards_reverses_lex_order_alt() {
let _g = crate::test_util::global_state_lock();
let mut arr: Vec<String> = vec!["a".into(), "b".into(), "c".into()];
strmetasort(&mut arr, SORTIT_BACKWARDS as u32, None);
assert_eq!(arr, vec!["c".to_string(), "b".into(), "a".into()]);
}
}