Skip to content

Commit ffd799c

Browse files
committed
Add proptests for indexing::store::platform_case_compare
Four properties pinning the SQLite collation algebra and the platform-specific normalization: 1. `reflexivity`: `cmp(s, s) == Equal` for any string. 2. `antisymmetry`: `cmp(a, b)` is the reverse of `cmp(b, a)`. 3. `transitivity`: classic total-order transitivity (both `<=` and `>=` flavors). 4. (macOS) `nfc_equals_nfd_on_macos`: arbitrary NFC and NFD forms of the same string compare equal — the property that makes `resolve_path` work when users type a path in NFC but APFS stored it as NFD. 4. (non-macOS) `matches_byte_cmp_off_macos`: comparator equals `str::cmp` exactly. No bugs found. The existing example tests already pinned the specific APFS Unicode cases that matter; the proptests now guarantee the algebraic laws across the full UTF-8 input space.
1 parent 9761f1a commit ffd799c

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

  • apps/desktop/src-tauri/src/indexing

apps/desktop/src-tauri/src/indexing/store.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2245,6 +2245,97 @@ mod tests {
22452245
assert_eq!(normalize_for_comparison("hello"), "hello");
22462246
}
22472247

2248+
// ── platform_case_compare (property-based) ───────────────────────
2249+
//
2250+
// The collation is used on every `entries.name` comparison in the
2251+
// SQLite index. A bug in the comparator would corrupt the index's
2252+
// sort order and, worse, cause `resolve_path` to fail to find
2253+
// entries the user typed in a different case or Unicode form.
2254+
// These properties pin the comparator algebra (reflexivity,
2255+
// antisymmetry, transitivity) plus the platform-specific normalization
2256+
// semantics (NFC≡NFD on macOS, byte-equal off macOS).
2257+
2258+
mod platform_case_proptests {
2259+
use super::*;
2260+
use proptest::prelude::*;
2261+
2262+
proptest! {
2263+
/// Reflexivity: `cmp(a, a) == Equal` for any string.
2264+
#[test]
2265+
fn reflexivity(s in ".*") {
2266+
prop_assert_eq!(platform_case_compare(&s, &s), std::cmp::Ordering::Equal);
2267+
}
2268+
2269+
/// Antisymmetry: `cmp(a, b)` and `cmp(b, a)` must be reverses
2270+
/// of each other.
2271+
#[test]
2272+
fn antisymmetry(a in ".*", b in ".*") {
2273+
let ab = platform_case_compare(&a, &b);
2274+
let ba = platform_case_compare(&b, &a);
2275+
prop_assert_eq!(
2276+
ab,
2277+
ba.reverse(),
2278+
"cmp({:?}, {:?}) = {:?} but cmp({:?}, {:?}) = {:?} should be its reverse",
2279+
a, b, ab, b, a, ba
2280+
);
2281+
}
2282+
2283+
/// Transitivity: if `cmp(a, b) <= 0` and `cmp(b, c) <= 0`,
2284+
/// then `cmp(a, c) <= 0`. We also check the strict-less and
2285+
/// equal flavors.
2286+
#[test]
2287+
fn transitivity(a in ".*", b in ".*", c in ".*") {
2288+
use std::cmp::Ordering::*;
2289+
let ab = platform_case_compare(&a, &b);
2290+
let bc = platform_case_compare(&b, &c);
2291+
let ac = platform_case_compare(&a, &c);
2292+
if ab != Greater && bc != Greater {
2293+
prop_assert!(
2294+
ac != Greater,
2295+
"transitivity violated: cmp(a,b)={:?} cmp(b,c)={:?} cmp(a,c)={:?} for a={:?} b={:?} c={:?}",
2296+
ab, bc, ac, a, b, c
2297+
);
2298+
}
2299+
if ab != Less && bc != Less {
2300+
prop_assert!(
2301+
ac != Less,
2302+
"transitivity violated (>=): cmp(a,b)={:?} cmp(b,c)={:?} cmp(a,c)={:?}",
2303+
ab, bc, ac
2304+
);
2305+
}
2306+
}
2307+
}
2308+
2309+
// On macOS, NFC and NFD forms of the same logical string must
2310+
// compare equal: APFS stores NFD, but users may type NFC, and
2311+
// `resolve_path` must find the stored entry either way.
2312+
#[cfg(target_os = "macos")]
2313+
proptest! {
2314+
#[test]
2315+
fn nfc_equals_nfd_on_macos(s in ".*") {
2316+
use unicode_normalization::UnicodeNormalization;
2317+
let nfc: String = s.nfc().collect();
2318+
let nfd: String = s.nfd().collect();
2319+
prop_assert_eq!(
2320+
platform_case_compare(&nfc, &nfd),
2321+
std::cmp::Ordering::Equal,
2322+
"NFC {:?} and NFD {:?} of {:?} must compare equal on APFS",
2323+
nfc, nfd, s
2324+
);
2325+
}
2326+
}
2327+
2328+
// Off macOS, the comparator is exact byte comparison. We pin
2329+
// this by checking that the result matches `str::cmp`.
2330+
#[cfg(not(target_os = "macos"))]
2331+
proptest! {
2332+
#[test]
2333+
fn matches_byte_cmp_off_macos(a in ".*", b in ".*") {
2334+
prop_assert_eq!(platform_case_compare(&a, &b), a.cmp(&b));
2335+
}
2336+
}
2337+
}
2338+
22482339
#[test]
22492340
fn has_sized_entry_for_inode_multiple_entries_one_has_sizes() {
22502341
let (_store, dir) = open_temp_store();

0 commit comments

Comments
 (0)