use std::path::{Path, PathBuf};
const MODEL: &str = "\
ref /stages/*::from_id --> /stations/*[::id = $];
ref /stages/*::to_id --> /stations/*[::id = $];
edge /stages/*: ::from_id -- ::to_id as stage;
graph /stations/*(--stage) { artpoints; conncomp; };
graph /stations/*(--stage[$-::parasangs > 50]) { conncomp; };
";
fn fixture(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!("quarb-graphdecl-{}-{tag}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let db = rusqlite::Connection::open(dir.join("roads.db")).unwrap();
db.execute_batch(
"CREATE TABLE stations (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
INSERT INTO stations VALUES (1, 'Ephesus'), (2, 'Sardis'), (3, 'Gates'), (4, 'Euphrates');
CREATE TABLE stages (id INTEGER PRIMARY KEY, from_id INTEGER REFERENCES stations(id),
to_id INTEGER REFERENCES stations(id), parasangs TEXT);
INSERT INTO stages VALUES (1, 1, 2, '18'), (2, 2, 3, '100'), (3, 2, 4, '90'), (4, 3, 4, '20');",
)
.unwrap();
std::fs::write(dir.join("roads.model"), MODEL).unwrap();
dir
}
fn qua(dir: &Path, args: &[&str]) -> (String, String, bool) {
let out = std::process::Command::new(env!("CARGO_BIN_EXE_qua"))
.env("QUARB_LOCALE", "C")
.current_dir(dir)
.args(["--model", "roads.model", "--diagnostics=info"])
.args(args)
.arg("roads.db")
.stdin(std::process::Stdio::null())
.output()
.unwrap();
(
String::from_utf8_lossy(&out.stdout).trim_end().to_string(),
String::from_utf8_lossy(&out.stderr).to_string(),
out.status.success(),
)
}
#[test]
fn bridges_artpoints_and_routes_over_a_declared_edge() {
let dir = fixture("commands");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:artpoints(--stage)"]);
assert!(ok, "{err}");
assert_eq!(out, "false\ntrue\nfalse\nfalse");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:bridges(--stage) | json"]);
assert!(ok, "{err}");
assert_eq!(out.matches("\"source\"").count(), 1, "{out}");
assert!(out.contains("/stations/"), "{out}");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:route(--stage; --from=/stations/*[::id = 1])"],
);
assert!(ok, "{err}");
let lines: Vec<&str> = out.lines().collect();
assert_eq!(lines.len(), 4, "{out}");
let last = lines[3];
let (a, b, c) = (
last.find("/stations/1").unwrap_or(usize::MAX),
last.find("/stations/2").unwrap_or(usize::MAX),
last.find("/stations/4").unwrap_or(usize::MAX),
);
assert!(a < b && b < c, "{last}");
assert!(!last.contains("/stations/3"), "{last}");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:betweenness(--stage; --edges) | json"],
);
assert!(ok, "{err}");
assert_eq!(out.matches("\"betweenness\"").count(), 4, "{out}");
}
#[test]
fn a_declared_graph_is_reported_and_decides_nothing() {
let dir = fixture("declared");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:artpoints(--stage)"]);
assert!(ok, "{err}");
assert_eq!(out, "false\ntrue\nfalse\nfalse");
assert!(
err.contains("info NOT_PUSHED (graph): the source has no answer for the declared artpoints(--stage) on /stations/*; computed over 4 nodes"),
"{err}"
);
let (out, err, ok) = qua(
&dir,
&["/stations/*[::id != 1] @| graph:artpoints(--stage)"],
);
assert!(ok, "{err}");
assert_eq!(out, "false\nfalse\nfalse");
assert!(
err.contains("info NOT_PUSHED (graph): the context is 3 of the 4 nodes of /stations/*, computed over 3 nodes"),
"{err}"
);
let (_, err, ok) = qua(&dir, &["/stations/* @| graph:artpoints(->stage)"]);
assert!(ok, "{err}");
assert!(
err.contains("info NOT_PUSHED (graph): the hop or the options differ from the declared artpoints(--stage) on /stations/*"),
"{err}"
);
let (_, err, ok) = qua(&dir, &["/stations/* @| graph:kcore(--stage)"]);
assert!(ok, "{err}");
assert!(!err.contains("(graph)"), "{err}");
let (_, err, ok) = qua(
&dir,
&[
"--graph-bound",
"5",
"/stations/* @| graph:conncomp(--stage)",
],
);
assert!(!ok);
assert!(
err.contains("refused: graph:conncomp would compute over 4 nodes and 4 edges, 8 cost units past the graph bound 5"),
"{err}"
);
}
#[test]
fn a_hop_takes_a_predicate_and_a_reference_reads_its_node() {
let dir = fixture("draft191");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| edges(--stage[$-::parasangs > 50]) | json"],
);
assert!(ok, "{err}");
assert_eq!(out.matches("\"source\"").count(), 2, "{out}");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| edges(--stage[::name = \"Sardis\"]) | json"],
);
assert!(ok, "{err}");
assert_eq!(out.matches("\"source\"").count(), 3, "{out}");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:bridges(--stage) | :source::name"],
);
assert!(ok, "{err}");
assert_eq!(out, "@(\"Ephesus\")");
let (out, err, ok) = qua(
&dir,
&[
"/stations/* @| graph:route(--stage; --from=/stations/*[::id = 1]) | map(::name) | join(\" > \")",
],
);
assert!(ok, "{err}");
assert_eq!(
out.lines().last().unwrap_or(""),
"Ephesus > Sardis > Euphrates",
"{out}"
);
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:conncomp(--stage[$-::parasangs > 50])"],
);
assert!(ok, "{err}");
assert_eq!(out, "1\n2\n2\n2");
assert!(
err.contains("info NOT_PUSHED (graph): the source has no answer for the declared conncomp(--stage[$-::parasangs > 50]) on /stations/*; computed over 4 nodes"),
"{err}"
);
}
#[test]
fn closeness_communities_modularity_and_triangles_over_the_stations() {
let dir = fixture("third");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:triangles(--stage)"]);
assert!(ok, "{err}");
assert_eq!(out, "0\n1\n1\n1");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:triangles(--stage) @| sum"]);
assert!(ok, "{err}");
assert_eq!(out, "3");
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:triangles(--stage; --coefficient)"],
);
assert!(ok, "{err}");
let c: Vec<f64> = out.lines().map(|l| l.parse().unwrap()).collect();
assert!(
c[0] == 0.0 && (c[1] - 1.0 / 3.0).abs() < 1e-12 && c[2] == 1.0 && c[3] == 1.0,
"{out}"
);
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:closeness(--stage)"]);
assert!(ok, "{err}");
let c: Vec<f64> = out.lines().map(|l| l.parse().unwrap()).collect();
assert!((c[1] - 1.0).abs() < 1e-12, "{out}");
assert!((c[0] - (3.0 / 3.0) * (3.0 / 5.0)).abs() < 1e-12, "{out}");
let (out, err, ok) = qua(&dir, &["/stations/* @| graph:communities(--stage)"]);
assert!(ok, "{err}");
assert!(out.lines().all(|l| l.parse::<i64>().is_ok()), "{out}");
assert_eq!(out.lines().next(), Some("1"));
let (out, err, ok) = qua(
&dir,
&["/stations/* @| graph:communities(--stage) | .:c @| graph:modularity(--stage; --by=:.c)"],
);
assert!(ok, "{err}");
let q: f64 = out.trim().parse().unwrap_or_else(|_| panic!("{out}"));
assert!((-0.5..=1.0).contains(&q), "{q}");
let (out, err, ok) = qua(
&dir,
&["/stations/* | .:p(::name) @| graph:modularity(--stage; --by=:.p)"],
);
assert!(ok, "{err}");
let own: f64 = out.trim().parse().unwrap();
assert!(own < 0.0, "{own}");
}