mod common;
use std::io::Write as _;
use common::{fixture, rete};
const TTL: &str = concat!(
"@prefix ex: <http://example.test/> .\n",
"ex:alice ex:knows ex:bob ;\n",
" ex:name \"Alice\"@en .\n",
"ex:bob ex:name \"Bob\"@en .\n",
);
const TRIG: &str = concat!(
"@prefix ex: <http://example.test/> .\n",
"ex:people {\n",
" ex:alice ex:knows ex:bob ;\n",
" ex:name \"Alice\"@en .\n",
" ex:bob ex:name \"Bob\"@en .\n",
"}\n",
);
fn gzip(bytes: &[u8]) -> Vec<u8> {
let mut enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
enc.write_all(bytes).unwrap();
enc.finish().unwrap()
}
#[test]
fn gzipped_turtle_builds_identically() {
let f = fixture();
let plain = f.write("g.ttl", TTL);
let gz = f.write("g.ttl.gz", gzip(TTL.as_bytes()));
let out_plain = f.path("plain.rete");
let out_gz = f.path("gz.rete");
for (src, out) in [(&plain, &out_plain), (&gz, &out_gz)] {
rete()
.args(["build"])
.arg(src)
.arg("-o")
.arg(out)
.assert()
.success();
}
assert_eq!(
std::fs::read(&out_plain).unwrap(),
std::fs::read(&out_gz).unwrap(),
"gzip must not change the built file"
);
}
#[test]
fn concatenated_gzip_members_all_read() {
let f = fixture();
let first = "<http://example.test/a> <http://example.test/p> \"1\" .\n";
let second = "<http://example.test/b> <http://example.test/p> \"2\" .\n";
let mut both = gzip(first.as_bytes());
both.extend_from_slice(&gzip(second.as_bytes()));
let src = f.write("both.nt.gz", both);
let out = f.path("both.rete");
rete()
.args(["build"])
.arg(&src)
.arg("-o")
.arg(&out)
.assert()
.success()
.stdout(predicates::str::contains("2 triples"));
}
#[test]
fn external_build_from_gzipped_turtle_matches_in_ram() {
let f = fixture();
let gz = f.write("g.ttl.gz", gzip(TTL.as_bytes()));
let plain = f.write("g.ttl", TTL);
let out_ext = f.path("ext.rete");
let out_ram = f.path("ram.rete");
rete()
.args(["build"])
.arg(&gz)
.args(["--memory-budget-mb", "64", "-o"])
.arg(&out_ext)
.assert()
.success();
rete()
.args(["build"])
.arg(&plain)
.args(["--no-pyramid", "-o"])
.arg(&out_ram)
.assert()
.success();
assert_eq!(
std::fs::read(&out_ext).unwrap(),
std::fs::read(&out_ram).unwrap(),
"external build of .ttl.gz must equal the in-RAM --no-pyramid build"
);
}
#[test]
fn trig_keeps_its_named_graph() {
let f = fixture();
let src = f.write("g.trig", TRIG);
let out = f.path("g.rete");
rete()
.args(["build"])
.arg(&src)
.arg("-o")
.arg(&out)
.assert()
.success()
.stdout(predicates::str::contains("named graph"));
rete()
.args(["graphs"])
.arg(&out)
.assert()
.success()
.stdout(predicates::str::contains("http://example.test/people"));
}
#[test]
fn collapse_graphs_matches_the_default_graph_build() {
let f = fixture();
let trig = f.write("g.trig", TRIG);
let ttl = f.write("g.ttl", TTL);
let out_collapsed = f.path("collapsed.rete");
let out_ttl = f.path("ttl.rete");
rete()
.args(["build"])
.arg(&trig)
.args(["--collapse-graphs", "-o"])
.arg(&out_collapsed)
.assert()
.success();
rete()
.args(["build"])
.arg(&ttl)
.arg("-o")
.arg(&out_ttl)
.assert()
.success();
assert_eq!(
std::fs::read(&out_collapsed).unwrap(),
std::fs::read(&out_ttl).unwrap(),
"a collapsed TriG must build to the same file as the equivalent Turtle"
);
}
#[test]
fn external_build_keeps_named_graphs_byte_identically() {
let f = fixture();
let src = f.write("g.trig", TRIG);
let out_ext = f.path("ext.rete");
let out_ram = f.path("ram.rete");
rete()
.args(["build"])
.arg(&src)
.args(["--memory-budget-mb", "64", "-o"])
.arg(&out_ext)
.assert()
.success()
.stdout(predicates::str::contains("named graph"));
rete()
.args(["build"])
.arg(&src)
.args(["--no-pyramid", "-o"])
.arg(&out_ram)
.assert()
.success();
assert_eq!(
std::fs::read(&out_ext).unwrap(),
std::fs::read(&out_ram).unwrap(),
"external build of a TriG must equal the in-RAM --no-pyramid build"
);
rete()
.args(["graphs"])
.arg(&out_ext)
.assert()
.success()
.stdout(predicates::str::contains("http://example.test/people"));
let out_flat = f.path("flat.rete");
rete()
.args(["build"])
.arg(&src)
.args(["--collapse-graphs", "--memory-budget-mb", "64", "-o"])
.arg(&out_flat)
.assert()
.success();
}
#[test]
fn merge_folds_named_graph_shards() {
let f = fixture();
let a = f.write(
"a.nq",
concat!(
"<http://ex/s1> <http://ex/p> <http://ex/o1> <http://ex/gA> .\n",
"<http://ex/d> <http://ex/p> <http://ex/o> .\n",
),
);
let b = f.write(
"b.nq",
concat!(
"<http://ex/s2> <http://ex/p> <http://ex/o2> <http://ex/gB> .\n",
"<http://ex/s1> <http://ex/p> <http://ex/o1> <http://ex/gA> .\n",
),
);
let (ra, rb, out) = (f.path("a.rete"), f.path("b.rete"), f.path("all.rete"));
for (src, dst) in [(&a, &ra), (&b, &rb)] {
rete()
.args(["build"])
.arg(src)
.arg("-o")
.arg(dst)
.assert()
.success();
}
rete()
.args(["merge"])
.arg(&ra)
.arg(&rb)
.arg("-o")
.arg(&out)
.assert()
.success();
let graphs = rete().args(["graphs"]).arg(&out).assert().success();
let listed = String::from_utf8(graphs.get_output().stdout.clone()).unwrap();
assert!(listed.contains("http://ex/gA"), "gA missing: {listed}");
assert!(listed.contains("http://ex/gB"), "gB missing: {listed}");
rete()
.args(["info"])
.arg(&out)
.assert()
.success()
.stdout(predicates::str::contains("quad_count: 3"));
}
#[test]
fn external_build_rejects_text_index_with_a_reason() {
let f = fixture();
let src = f.write("g.nt", "<http://ex/s> <http://ex/p> \"hello world\" .\n");
let out = f.path("ext.rete");
rete()
.args(["build"])
.arg(&src)
.args(["--text-index", "--memory-budget-mb", "64", "-o"])
.arg(&out)
.assert()
.failure()
.stderr(predicates::str::contains("separate external sort"));
}
#[test]
fn external_build_rejects_rdfxml_with_a_clear_error() {
let f = fixture();
let src = f.write("o.rdf", "<rdf:RDF/>");
let out = f.path("ext.rete");
rete()
.args(["build"])
.arg(&src)
.args(["--memory-budget-mb", "64", "-o"])
.arg(&out)
.assert()
.failure()
.stderr(predicates::str::contains("rdfxml"));
}
#[test]
fn turtle_with_anonymous_blank_nodes_builds_on_both_paths() {
let f = fixture();
let ttl = concat!(
"@prefix ex: <http://ex/> .\n",
"ex:a ex:p [ ex:q \"1\" ] .\n",
"ex:b ex:list ( \"x\" \"y\" ) .\n",
);
let src = f.write("bn.ttl", ttl);
let out_plain = f.path("bn.rete");
let out_ext = f.path("bn-ext.rete");
rete()
.args(["build"])
.arg(&src)
.args(["--no-pyramid", "-o"])
.arg(&out_plain)
.assert()
.success()
.stdout(predicates::str::contains("7 triples"));
rete()
.args(["build"])
.arg(&src)
.args(["--memory-budget-mb", "64", "-o"])
.arg(&out_ext)
.assert()
.success()
.stdout(predicates::str::contains("7 triples"));
}
#[test]
fn estimate_reads_gzipped_turtle() {
let f = fixture();
let gz = f.write("g.ttl.gz", gzip(TTL.as_bytes()));
rete()
.args(["estimate"])
.arg(&gz)
.assert()
.success()
.stdout(predicates::str::contains("statements"));
}