use super::*;
fn needles(q: &str) -> Vec<String> {
q.split_whitespace().map(str::to_string).collect()
}
#[test]
fn file_find_matches_like_github_go_to_file() {
let s = file_find_score("djangosite/urls/default.py", &needles("urldef"));
assert!(s.is_some(), "subsequence across dir + file must match");
assert!(file_find_score("something/term/cargo.toml", &needles("term")).is_some());
assert!(file_find_score("djangosite/urls/default.py", &needles("url def")).is_some());
assert!(file_find_score("djangosite/urls/default.py", &needles("url zzz")).is_none());
assert!(file_find_score("src/main.rs", &needles("msni")).is_none());
assert!(file_find_score("src/main.rs", &needles("mrs")).is_some());
assert_eq!(file_find_score("src/main.rs", &[]), Some(0));
}
#[test]
fn file_find_ranks_filename_above_path_above_scattered() {
let starts = file_find_score("src/terminal.rs", &needles("term")).unwrap();
let inside = file_find_score("src/myterm.rs", &needles("term")).unwrap();
let dir = file_find_score("src/term/cargo.toml", &needles("term")).unwrap();
let scattered = file_find_score("src/test/remote.rs", &needles("term")).unwrap();
assert!(
starts > inside,
"needle starting the file name beats one inside it"
);
assert!(inside > dir, "file-name match beats a directory-only match");
assert!(
dir > scattered,
"contiguous path match beats scattered chars"
);
}
#[test]
fn locate_in_blob_folds_regions_and_counts() {
let text = b"line one\nmatch here\nbetween\nanother match\ntail";
let needles = vec!["match".to_string()];
let (line, preview, count) = locate_in_blob(text, &needles).unwrap();
assert_eq!(line, 2);
assert_eq!(count, 2);
let nos: Vec<u32> = preview.iter().map(|(n, _)| *n).collect();
assert_eq!(nos, vec![1, 2, 3, 4, 5]);
assert!(locate_in_blob(b"\x00\x01match", &needles).is_none());
}
#[test]
fn code_query_maps_scope_and_extension() {
use super::SearchKind;
assert_eq!(
code_query(SearchKind::Grep, "needle", "global", ""),
"needle"
);
assert_eq!(
code_query(SearchKind::FileFind, "main", "repo:o/r", ".rs"),
"path:main repo:o/r extension:rs"
);
assert_eq!(
code_query(SearchKind::Grep, "q", "org:x", "rs"),
"q org:x extension:rs"
);
assert_eq!(
code_query(
SearchKind::FileFind,
r#"language:"c++" "my file" -extension:hh"#,
"global",
""
),
r#"language:"c++" path:"my file" -extension:hh"#
);
let raw = r#" 'language:cpp' language:"c++" -path:'vendor files' "#;
assert_eq!(code_query(SearchKind::Grep, raw, "global", ""), raw);
}
#[test]
fn scoped_grep_falls_back_to_tarball_on_silent_zero() {
use rootle_provider::{Capabilities, Provider, ProviderResult, SearchCodeResult, TreeResult};
let a_rs = b"fn target() {}\n";
let b_rs = b"nothing relevant here\n";
let c_rs = b"target\nand target again\n";
let bin = vec![0u8, 159, 146, 150, 0, 1, 2, 3];
let enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast());
let mut builder = tar::Builder::new(enc);
let mut add = |path: &str, bytes: &[u8]| {
let mut header = tar::Header::new_gnu();
header.set_size(bytes.len() as u64);
header.set_mode(0o644);
header.set_entry_type(tar::EntryType::Regular);
header.set_cksum();
builder
.append_data(&mut header, format!("o-r-deadbeef/{path}"), bytes)
.unwrap();
};
add("src/a.rs", a_rs);
add("src/b.rs", b_rs);
add("src/c.rs", c_rs);
add("img.bin", &bin);
add("src/cppneedle.cp", b"cppneedle\n");
add("src/cppneedle.hxx", b"cppneedle\n");
add("vendor/cppneedle.hpp", b"cppneedle\n");
add("src/cppneedle.rs", b"cppneedle\n");
let tarball = builder.into_inner().unwrap().finish().unwrap();
struct Mock(Vec<u8>);
impl Provider for Mock {
fn name(&self) -> &str {
"mock"
}
fn capabilities(&self) -> Capabilities {
Capabilities {
commit: false,
orgs: false,
code_search: true,
file_search: true,
refs: false,
log: false,
blame: false,
}
}
fn search(&self, _: &str) -> ProviderResult<Vec<rootle_provider::SearchItem>> {
Err("mock".into())
}
fn org_repos(&self, _: &str) -> ProviderResult<Vec<rootle_provider::RepoInfo>> {
Err("mock".into())
}
fn fetch_tree(
&self,
_: &rootle_provider::RepoId,
_: Option<&rootle_provider::GitRef>,
) -> ProviderResult<TreeResult> {
Ok(TreeResult {
entries: [
"src/cppneedle.cp",
"src/cppneedle.hxx",
"vendor/cppneedle.hpp",
"src/cppneedle.rs",
]
.into_iter()
.map(|path| rootle_provider::TreeNode {
path: path.into(),
is_dir: false,
sha: path.into(),
size: None,
})
.collect(),
truncated: false,
branch: "main".into(),
})
}
fn fetch_blob(
&self,
_: &rootle_provider::RepoId,
_: &rootle_provider::Sha,
) -> ProviderResult<Vec<u8>> {
Err("mock".into())
}
fn search_code(&self, _: &str) -> ProviderResult<SearchCodeResult> {
Ok(SearchCodeResult {
hits: Vec::new(),
truncated: false,
index_as_of: None,
})
}
fn clone_url(&self, _: &rootle_provider::RepoId) -> ProviderResult<String> {
Err("mock".into())
}
fn web_url(
&self,
_: &rootle_provider::RepoId,
_: &str,
_: Option<&rootle_provider::GitRef>,
_: Option<u32>,
_: Option<u32>,
_: bool,
) -> ProviderResult<String> {
Err("mock".into())
}
fn org_url(&self, _: &str) -> ProviderResult<String> {
Err("mock".into())
}
fn source_tarball(&self, _: &rootle_provider::RepoId) -> ProviderResult<Vec<u8>> {
Ok(self.0.clone())
}
}
let provider = Mock(tarball);
let run = |query: &str| -> Vec<RawHit> {
let hits = std::sync::Mutex::new(Vec::new());
run_view_search(
&provider,
SearchKind::Grep,
query,
"repo:o/r",
"",
&|batch: Vec<RawHit>| hits.lock().unwrap().extend(batch),
)
.unwrap();
hits.into_inner().unwrap()
};
let hits = run("target");
let paths: Vec<&str> = hits.iter().map(|h| h.path.as_str()).collect();
assert_eq!(
paths,
vec!["src/a.rs", "src/c.rs"],
"binary skipped, non-matches skipped"
);
assert_eq!(hits[0].line, 1);
assert_eq!(hits[0].match_count, 1, "a.rs has one matching line");
assert_eq!(hits[1].match_count, 2, "c.rs has two");
assert_eq!(hits[0].branch, "main");
let want = {
use sha1::{Digest, Sha1};
let mut h = Sha1::new();
h.update(format!("blob {}\0", a_rs.len()));
h.update(a_rs);
h.finalize()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>()
};
assert_eq!(hits[0].sha, want);
assert!(!hits[0].stale);
assert!(
hits[0].preview.iter().any(|(_, l)| l.contains("target")),
"preview carries the matched line"
);
let hits = run("target again");
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].path, "src/c.rs");
let hits = run("target -again");
assert_eq!(hits.len(), 1);
assert_eq!(hits[0].path, "src/a.rs");
for alias in ["cpp", "c++", "cxx"] {
let query = format!("cppneedle language:'{alias}' -path:vendor");
let content = run(&query);
let files = std::sync::Mutex::new(Vec::new());
run_view_search(
&provider,
SearchKind::FileFind,
&query,
"repo:o/r",
"",
&|batch| files.lock().unwrap().extend(batch),
)
.unwrap();
let mut file_paths: Vec<_> = files
.into_inner()
.unwrap()
.into_iter()
.map(|h| h.path)
.collect();
file_paths.sort();
let mut content_paths: Vec<_> = content.into_iter().map(|h| h.path).collect();
content_paths.sort();
assert_eq!(file_paths, ["src/cppneedle.cp", "src/cppneedle.hxx"]);
assert_eq!(content_paths, file_paths);
}
let metadata_only = run("language:cpp");
assert_eq!(metadata_only.len(), 3);
assert!(metadata_only.iter().all(|hit| hit.match_count == 0));
let narrowed = std::sync::Mutex::new(Vec::new());
run_view_search(
&provider,
SearchKind::Grep,
"cppneedle language:cpp",
"repo:o/r",
"hxx",
&|batch| narrowed.lock().unwrap().extend(batch),
)
.unwrap();
let narrowed = narrowed.into_inner().unwrap();
assert_eq!(narrowed.len(), 1);
assert_eq!(narrowed[0].path, "src/cppneedle.hxx");
for kind in [SearchKind::FileFind, SearchKind::Grep] {
let error = run_view_search(&provider, kind, "language:cp", "repo:o/r", "", &|_| {
panic!("invalid query emitted hits")
})
.unwrap_err();
assert!(error.message.contains("unknown language: cp"));
}
}