whycodes-index 0.6.31

Fast, gitignore-aware workspace file index for WhyCodes
Documentation
use super::*;
use std::fs;
use std::io::Write;

fn tmp() -> tempfile::TempDir {
    tempfile::TempDir::new().unwrap()
}

#[test]
fn pack_and_extract_unique_sorted() {
    assert_eq!(pack_trigram(b'a', b'b', b'c'), 0x00_61_62_63);
    assert!(extract_trigrams(b"ab").is_empty());
    assert_eq!(
        extract_trigrams(b"abc"),
        vec![pack_trigram(b'a', b'b', b'c')]
    );
    let tris = extract_trigrams(b"aaaa");
    assert_eq!(tris, vec![pack_trigram(b'a', b'a', b'a')]);
    let mixed = extract_indexed_trigrams(b"Abc");
    assert!(mixed.contains(&pack_trigram(b'A', b'b', b'c')));
    assert!(mixed.contains(&pack_trigram(b'a', b'b', b'c')));
    assert_eq!(extract_indexed_trigrams(b"abc"), extract_trigrams(b"abc"));
}

#[test]
fn indexed_trigrams_keep_no_spare_capacity() {
    let text = "fn main() {}\n".repeat(200);
    for bytes in [text.as_bytes(), text.to_uppercase().as_bytes()] {
        let tris = extract_indexed_trigrams(bytes);
        assert_eq!(tris.capacity(), tris.len());
    }
}

#[test]
fn required_trigrams_literal_and_escape() {
    let t = required_trigrams("fn main", false).unwrap();
    assert!(t.contains(&pack_trigram(b'f', b'n', b' ')));
    assert!(required_trigrams("ab", false).is_none());
    assert!(required_trigrams("a.b", false).is_none());
    assert!(required_trigrams("a*", false).is_none());
    let escaped = required_trigrams(r"a\.b", false).unwrap();
    assert_eq!(escaped, extract_trigrams(b"a.b"));
    assert!(required_trigrams(r"\d+", false).is_none());
    assert!(required_trigrams(r"\x", false).is_none());
    assert!(required_trigrams(r"trailing\", false).is_none());
    let slash = required_trigrams(r"a\\b", false).unwrap();
    assert_eq!(slash, extract_trigrams(br"a\b"));
}

#[test]
fn required_trigrams_case_fold() {
    let a = required_trigrams("Hello", true).unwrap();
    let b = required_trigrams("hello", false).unwrap();
    assert_eq!(a, b);
    let cs = required_trigrams("Hello", false).unwrap();
    assert_ne!(cs, b);
    assert!(required_trigrams("İstanbul", true).is_none());
    assert!(required_trigrams("İstanbul", false).is_some());
}

#[test]
fn as_literal_rejects_each_meta() {
    for c in REGEX_META {
        assert!(as_literal(&format!("a{c}b")).is_none(), "{c}");
    }
    assert_eq!(as_literal("plain").as_deref(), Some("plain"));
    assert_eq!(as_literal(r"\.\*\+").as_deref(), Some(".*+"));
}

#[test]
fn content_index_upsert_match_and_skip() {
    let dir = tmp();
    let root = dir.path();
    fs::write(root.join("hit.rs"), "fn unique_needle() {}\n").unwrap();
    fs::write(root.join("miss.rs"), "fn other() {}\n").unwrap();
    fs::write(root.join("bin.dat"), b"abc\0def").unwrap();
    fs::write(root.join("tiny.rs"), "ab").unwrap();
    fs::create_dir_all(root.join("src")).unwrap();

    let mut idx = ContentIndex::new();
    assert!(!idx.is_complete());
    idx.upsert(root, "hit.rs", 0);
    idx.upsert(root, "miss.rs", 0);
    idx.upsert(root, "bin.dat", 0);
    idx.upsert(root, "tiny.rs", 0);
    idx.upsert(root, "src", 0);
    idx.upsert(root, "gone.rs", 0);
    idx.mark_complete();
    assert!(idx.is_complete());
    assert_eq!(idx.indexed_len(), 3);
    assert_eq!(idx.skipped_len(), 1);

    let req = required_trigrams("unique_needle", false).unwrap();
    assert!(idx.may_match("hit.rs", &req));
    assert!(!idx.may_match("miss.rs", &req));
    assert!(!idx.may_match("bin.dat", &req));
    assert!(idx.may_match("gone.rs", &req));
    assert!(!idx.may_match("tiny.rs", &req));
    assert!(idx.may_match("hit.rs", &[]));

    idx.remove("hit.rs");
    assert!(idx.may_match("hit.rs", &req));

    fs::write(root.join("Case.rs"), "HelloWorld\n").unwrap();
    idx.upsert(root, "Case.rs", 0);
    let mixed = required_trigrams("HelloWorld", false).unwrap();
    assert!(idx.may_match("Case.rs", &mixed));
    let lower_req = required_trigrams("helloworld", true).unwrap();
    assert!(idx.may_match("Case.rs", &lower_req));
    let all_caps = required_trigrams("HELLOWORLD", false).unwrap();
    assert!(!idx.may_match("Case.rs", &all_caps));

    idx.clear();
    assert!(!idx.is_complete());
    assert_eq!(idx.indexed_len(), 0);
}

#[test]
fn content_index_size_hint_and_remove_tree() {
    let dir = tmp();
    let root = dir.path();
    fs::create_dir_all(root.join("src/nested")).unwrap();
    fs::write(root.join("src/a.rs"), "fn alpha() {}\n").unwrap();
    fs::write(root.join("src/nested/b.rs"), "fn beta() {}\n").unwrap();
    fs::write(root.join("keep.rs"), "fn keep() {}\n").unwrap();

    let mut idx = ContentIndex::new();
    idx.upsert(root, "src/a.rs", 0);
    idx.upsert(root, "src/nested/b.rs", 0);
    idx.upsert(root, "keep.rs", 0);
    idx.upsert(root, "huge.rs", MAX_INDEX_BYTES + 1);
    assert_eq!(idx.skipped_len(), 0);
    let huge_req = required_trigrams("alpha", false).unwrap();
    assert!(idx.may_match("huge.rs", &huge_req));

    let req = required_trigrams("alpha", false).unwrap();
    assert!(idx.may_match("src/a.rs", &req));
    idx.remove_tree("src");
    assert!(idx.may_match("src/a.rs", &req));
    assert!(idx.may_match("src/nested/b.rs", &req));
    let keep = required_trigrams("keep", false).unwrap();
    assert!(idx.may_match("keep.rs", &keep));
}

#[test]
fn classify_metadata_skips_dir_and_missing() {
    let dir = tmp();
    assert_eq!(classify_metadata(dir.path()), Load::Unknown);
    assert_eq!(
        classify_metadata(&dir.path().join("nope.rs")),
        Load::Unknown
    );
    let p = dir.path().join("ok.rs");
    fs::write(&p, "hello world").unwrap();
    match classify_metadata(&p) {
        Load::Bytes(b) => assert_eq!(b, b"hello world"),
        other => panic!("{other:?}"),
    }
    let big = dir.path().join("big.rs");
    fs::write(&big, "x").unwrap();
    let f = fs::OpenOptions::new().write(true).open(&big).unwrap();
    f.set_len(MAX_INDEX_BYTES + 1).unwrap();
    drop(f);
    assert_eq!(classify_metadata(&big), Load::Unknown);
}

#[test]
fn load_file_and_read_helpers() {
    let dir = tmp();
    let p = dir.path().join("a.rs");
    fs::write(&p, "abc").unwrap();
    match load_file(&p, 3) {
        Load::Bytes(b) => assert_eq!(b, b"abc"),
        other => panic!("{other:?}"),
    }
    assert_eq!(load_file(&p, MAX_INDEX_BYTES + 1), Load::Unknown);
    assert_eq!(open_failed(), Load::Unknown);
    assert_eq!(read_failed(), Load::Unknown);
    assert_eq!(append_failed(), Load::Unknown);
    assert_eq!(missing_path(), Load::Unknown);
    assert_eq!(skip_non_file(), Load::Unknown);
    assert_eq!(too_large(), Load::Unknown);
    assert_eq!(tree_prefix("src"), "src/");
    assert!(contains_all(&[1, 3, 5], &[1, 5]));
    assert!(!contains_all(&[1, 3, 5], &[2]));
    assert!(!skip_unicode_casefold("Hello", true));
    assert!(skip_unicode_casefold("İ", true));
}

#[cfg(unix)]
#[test]
fn classify_skips_symlink() {
    let dir = tmp();
    let target = dir.path().join("t.rs");
    fs::write(&target, "fn target() {}\n").unwrap();
    let link = dir.path().join("l.rs");
    std::os::unix::fs::symlink(&target, &link).unwrap();
    assert_eq!(classify_metadata(&link), Load::Unknown);
}

#[test]
fn read_text_or_binary_large_and_failed_open() {
    let dir = tmp();
    let p = dir.path().join("wide.rs");
    let mut f = fs::File::create(&p).unwrap();
    let chunk = vec![b'a'; BINARY_SNIFF + 32];
    f.write_all(&chunk).unwrap();
    f.flush().unwrap();
    drop(f);
    match read_text_or_binary(&p) {
        Load::Bytes(b) => assert_eq!(b.len(), BINARY_SNIFF + 32),
        other => panic!("{other:?}"),
    }
    assert_eq!(
        read_text_or_binary(&dir.path().join("missing.rs")),
        Load::Unknown
    );
    let sniff = [b'x'; BINARY_SNIFF];
    let mut dummy = fs::File::open(&p).unwrap();
    assert_eq!(
        classify_sniff(Err(std::io::Error::other("read")), &sniff, &mut dummy),
        Load::Unknown
    );
    let mut buf = vec![b'a'; 3];
    assert_eq!(
        classify_append(
            Err(std::io::Error::other("append")),
            &mut buf,
            vec![b'b'; 2]
        ),
        Load::Unknown
    );
    let mut buf = vec![b'a'; 2];
    match classify_append(Ok(2), &mut buf, vec![b'b'; 2]) {
        Load::Bytes(b) => assert_eq!(b, b"aabb"),
        other => panic!("{other:?}"),
    }
    assert_eq!(
        classify_sniff(Ok(0), &sniff, &mut dummy),
        Load::Bytes(Vec::new())
    );
    let mut nul = [0u8; 4];
    nul[0] = b'a';
    nul[1] = 0;
    assert_eq!(classify_sniff(Ok(4), &nul, &mut dummy), Load::Binary);
}