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 la = if a_has_len {
a.len as usize
} else {
a.cmp.len()
};
let lb = if b_has_len {
b.len as usize
} else {
b.cmp.len()
};
let len = la.min(lb);
let ab = a.cmp.as_bytes();
let bb = b.cmp.as_bytes();
let take_a = ab.len().min(len);
let take_b = bb.len().min(len);
match ab[..take_a].cmp(&bb[..take_b]) {
Ordering::Equal => match (a_has_len, b_has_len) {
(true, true) => la.cmp(&lb),
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => Ordering::Equal,
},
o => o,
}
};
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 = a.to_string();
let mut b_str = bs.to_string();
if no_backslash {
a_str = a_str.chars().filter(|&c| c != '\\').collect();
b_str = b_str.chars().filter(|&c| c != '\\').collect();
}
let cmp_numeric = |a: &str, bs: &str, signed_mode: bool| -> Ordering {
let ab = a.as_bytes();
let bb = bs.as_bytes();
let n = ab.len().min(bb.len());
let mut i = 0;
while i < n && ab[i] == bb[i] {
i += 1;
}
let ac = ab.get(i).copied().unwrap_or(0);
let bc = bb.get(i).copied().unwrap_or(0);
let is_digit = |c: u8| c.is_ascii_digit();
let mut mul: i32 = 0;
let mut cmp: i32 = (ac as i32) - (bc as i32);
if signed_mode {
if ac == b'-' && ab.get(i + 1).copied().map(is_digit).unwrap_or(false) && is_digit(bc) {
return Ordering::Less;
}
if bc == b'-' && bb.get(i + 1).copied().map(is_digit).unwrap_or(false) && is_digit(ac) {
return Ordering::Greater;
}
}
if is_digit(ac) || is_digit(bc) {
let mut start = i;
while start > 0 && is_digit(ab[start - 1]) {
start -= 1;
}
if signed_mode && start > 0 && ab[start - 1] == b'-' {
mul = -1;
} else {
mul = 1;
}
let run_a: Vec<u8> = ab[start..]
.iter()
.copied()
.take_while(|&c| is_digit(c))
.collect();
let run_b: Vec<u8> = bb[start..]
.iter()
.copied()
.take_while(|&c| is_digit(c))
.collect();
let stripped_a: &[u8] = {
let z = run_a.iter().take_while(|&&c| c == b'0').count();
&run_a[z..]
};
let stripped_b: &[u8] = {
let z = run_b.iter().take_while(|&&c| c == b'0').count();
&run_b[z..]
};
match stripped_a.len().cmp(&stripped_b.len()) {
Ordering::Greater => {
return if mul >= 0 {
Ordering::Greater
} else {
Ordering::Less
};
}
Ordering::Less => {
return if mul >= 0 {
Ordering::Less
} else {
Ordering::Greater
};
}
Ordering::Equal => {
for k in 0..stripped_a.len() {
if stripped_a[k] != stripped_b[k] {
let d = (stripped_a[k] as i32) - (stripped_b[k] as i32);
let signed_cmp = if mul >= 0 { d } else { -d };
return match signed_cmp.cmp(&0) {
Ordering::Equal => Ordering::Equal,
o => o,
};
}
}
cmp = 0;
}
}
}
let _ = mul;
if cmp == 0 {
ab[i..].cmp(&bb[i..])
} else if cmp < 0 {
Ordering::Less
} else {
Ordering::Greater
}
};
if numeric {
cmp_numeric(&a_str, &b_str, numeric_signed)
} else {
let c = {
#[cfg(unix)]
{
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 ca = cstr_head(&a_str);
let cb = cstr_head(&b_str);
unsafe { libc::strcoll(ca.as_ptr(), cb.as_ptr()) }
}
#[cfg(not(unix))]
{
match a_str.cmp(&b_str) {
Ordering::Less => -1i32,
Ordering::Equal => 0,
Ordering::Greater => 1,
}
}
};
if c < 0 {
Ordering::Less
} else if c > 0 {
Ordering::Greater
} else {
Ordering::Equal
}
}
}
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| sortelt {
orig: s.clone(),
cmp: apply_transforms(s),
origlen: -1,
len: -1,
})
.collect(),
};
let mut indices: Vec<usize> = (0..elts.len()).collect();
indices.sort_by(|&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()]);
}
}