use quarb_compose::ComposeAdapter;
use quarb_fs::{FsAdapter, FsOptions};
fn fixture(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("quarb-compose-test-{name}"));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("store.json"),
r#"{"books": [{"t": "Dune", "p": 9}, {"t": "Emma", "p": 7}]}"#,
)
.unwrap();
std::fs::write(dir.join("names.csv"), "name,qty\nAda,2\nBo,1\n").unwrap();
std::fs::write(dir.join("plain.txt"), "not a tree").unwrap();
dir
}
fn values(a: &impl quarb::AstAdapter, q: &str) -> Vec<String> {
match quarb::run(q, a).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect(),
quarb::QueryResult::Nodes(ns) => ns.iter().map(|n| format!("{:?}", n)).collect(),
}
}
#[test]
fn grafts_parse_lazily_and_compose() {
let dir = fixture("lazy");
let a = ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap());
assert_eq!(values(&a, "/store.json/books/*[/p:: < 8]/t::"), ["Emma"]);
assert_eq!(values(&a, "/names.csv/*[::qty > 1]::name"), ["Ada"]);
assert_eq!(values(&a, "/plain.txt/* @| count"), ["1"]);
assert_eq!(values(&a, "/plain.txt/paragraph::"), ["not a tree"]);
assert_eq!(values(&a, "/plain.txt::"), ["not a tree"]);
assert_eq!(
values(&a, "/store.json/books\\\\store.json/books/*/t:: @| count"),
["2"]
);
}
#[test]
fn archive_leaves_graft_by_path() {
let dir = fixture("archive");
let tarball = {
let gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
let mut ar = tar::Builder::new(gz);
let json = br#"{"services": [{"name": "web", "port": 8080}, {"name": "db", "port": 5432}]}"#;
let mut h = tar::Header::new_gnu();
h.set_size(json.len() as u64);
h.set_mode(0o644);
h.set_cksum();
ar.append_data(&mut h, "app/config/services.json", &json[..])
.unwrap();
ar.into_inner().unwrap().finish().unwrap()
};
std::fs::write(dir.join("app.tar.gz"), tarball).unwrap();
let with_paths = ComposeAdapter::with_source_paths(
FsAdapter::with_options(&dir, FsOptions::default()).unwrap(),
|fs, n| Some(fs.path(n)),
);
assert_eq!(
values(
&with_paths,
"//*.tar.gz//services.json/services/*[/port:: > 6000]/name::"
),
["web"]
);
let without =
ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap());
assert_eq!(values(&without, "/app.tar.gz/* @| count"), ["0"]);
}
#[test]
fn grafted_ids_fit_the_mount_inner_window() {
let dir = fixture("mount-window");
let a = ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap());
match quarb::run("/store.json/books/*", &a).unwrap() {
quarb::QueryResult::Nodes(ns) => {
assert!(!ns.is_empty(), "expected grafted book nodes");
for n in ns {
assert_eq!(
n.0 >> 56,
0,
"grafted id {:#x} escapes the 56-bit mount inner window",
n.0
);
}
}
quarb::QueryResult::Values(_) => panic!("expected nodes"),
}
}
#[test]
fn grafted_nodes_inherit_outer_leaf_provenance() {
let dir = fixture("prov");
let a = ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap());
assert_eq!(
values(&a, "/store.json/books/*[/t:: = 'Dune']:::source"),
values(&a, "/store.json:::source")
);
assert_eq!(
values(&a, "/store.json/books/*[/t:: = 'Dune']:::instant"),
values(&a, "/store.json:::instant")
);
assert!(values(&a, "/store.json/books:::source")[0].ends_with("store.json"));
assert!(!values(&a, "/store.json/books:::source")[0].contains('!'));
}
#[test]
fn dual_exposure_at_graft_root() {
let dir = std::env::temp_dir().join("quarb-compose-dualroot-test");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("meta.json"),
r#"{"device":"phone","geo":{"city":"London"}}"#,
)
.unwrap();
let fs = quarb_fs::FsAdapter::new(&dir).unwrap();
let a = quarb_compose::ComposeAdapter::new(fs);
let got = match quarb::run("/'meta.json'::device", &a).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect::<Vec<_>>(),
_ => panic!("expected values"),
};
assert_eq!(got, ["phone"]);
}
#[test]
fn source_graft_level_is_per_mount() {
use quarb_compose::SourceGraft;
let dir = fixture("source-graft");
std::fs::write(
dir.join("lexer.rs"),
"/// Scan.\npub fn lex(input: &str) -> usize {\n fn is_name_char(c: char) -> bool {\n c.is_alphanumeric()\n }\n input.chars().filter(|c| is_name_char(*c)).count()\n}\n",
)
.unwrap();
std::fs::write(
dir.join("util.py"),
"def helper(a, b):\n return a + b\n",
)
.unwrap();
std::fs::write(
dir.join("main.c"),
"static int helper(int a, int b) { return a + b; }\n",
)
.unwrap();
let a = ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap());
assert_eq!(values(&a, "/lexer.rs//function_item::name"), ["lex", "is_name_char"]);
assert_eq!(values(&a, "//lex @| count"), ["0"]);
let a = ComposeAdapter::new(FsAdapter::with_options(&dir, FsOptions::default()).unwrap())
.with_source_graft(SourceGraft::Code);
assert_eq!(values(&a, "/lexer.rs/lex/is_name_char @| count"), ["1"]);
assert_eq!(values(&a, "//*<function>:::name @| count"), ["5"]);
assert_eq!(values(&a, "//helper:::name @| count"), ["2"]);
assert_eq!(values(&a, "//function_item @| count"), ["0"]);
let node = match quarb::run("//is_name_char", &a).unwrap() {
quarb::QueryResult::Nodes(ns) => ns[0],
_ => panic!("expected nodes"),
};
let fs_locator = |n| a.outer().path(n).display().to_string();
assert!(a.locator(node, fs_locator).ends_with("lexer.rs!/lex/is_name_char"));
}