#![allow(dead_code)]
use core_api::repograph::rules::{about_rule, concept_sources_rule, ABOUT_LABELS};
use core_api::{default_max_edges, Direction, GraphDb, Predicate, RuleDef, Value};
use std::path::{Path, PathBuf};
pub const DAY_SECS: i64 = 86_400;
const QUARTER: i64 = 91 * DAY_SECS;
pub const T0: i64 = 1_600_000_000;
pub const SYNCED_AT: i64 = T0 + 4 * QUARTER + 7 * DAY_SECS + 60;
const PER_QUARTER: usize = 8;
pub const COMMITS: usize = 40;
pub const DIRS: [(&str, usize, &str); 3] = [
("src/core", 12, "c"),
("src/web", 10, "w"),
("tests", 8, "t"),
];
#[must_use]
pub fn tmp(name: &str) -> PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos();
let d = std::env::temp_dir().join(format!(
"graphdb-repograph-{}-{}-{}",
name,
std::process::id(),
nanos
));
let _ = std::fs::remove_dir_all(&d);
d
}
#[must_use]
pub fn open(dir: &Path) -> GraphDb<core_storage::fs::RealFs> {
GraphDb::open(dir).expect("open")
}
#[must_use]
pub fn file_key(d: usize, i: usize) -> String {
let (dir, _, stem) = DIRS[d];
format!("{dir}/{stem}{i:02}.rs")
}
#[must_use]
pub fn all_files() -> Vec<String> {
let mut out = Vec::new();
for (d, &(_, n, _)) in DIRS.iter().enumerate() {
for i in 0..n {
out.push(file_key(d, i));
}
}
out
}
#[must_use]
pub fn sha(i: usize) -> String {
format!("{:07x}{:033x}", 0x00a1_b2c3usize + i * 7919, i)
}
#[must_use]
pub fn commit_ts(i: usize) -> i64 {
T0 + (i / PER_QUARTER) as i64 * QUARTER + (i % PER_QUARTER) as i64 * DAY_SECS
}
#[must_use]
pub fn newest_ts() -> i64 {
commit_ts(COMMITS - 1)
}
#[must_use]
pub fn touched(i: usize) -> Vec<String> {
let d = i % DIRS.len();
let n = DIRS[d].1;
let mut files: Vec<String> = (0..3).map(|k| file_key(d, k)).collect();
files.push(file_key(d, 3 + (i / DIRS.len()) % (n - 3)));
files
}
#[must_use]
pub fn hash_of(key: &str) -> String {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in key.bytes() {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
format!("{h:016x}")
}
#[must_use]
pub fn doc_key() -> String {
file_key(2, DIRS[2].1 - 1)
}
#[must_use]
pub fn doc_mentions() -> String {
file_key(2, 1)
}
pub const DOC_HEADINGS: [&str; 3] = ["Test notes", "Fixtures", "Running"];
pub const DOC_HEADING: &str = DOC_HEADINGS[1];
#[must_use]
pub fn doc_body() -> String {
format!(
"# {}\n\nWhat the suite covers.\n\n## {}\n\nThe sample is built by `{}`.\n\n## {}\n\ncargo test\n",
DOC_HEADINGS[0],
DOC_HEADINGS[1],
doc_mentions(),
DOC_HEADINGS[2],
)
}
#[must_use]
pub fn commit_author(i: usize) -> &'static str {
AUTHORS[i % AUTHORS.len()].0
}
#[must_use]
pub fn owner_of(d: usize, i: usize) -> &'static str {
match (d, i) {
(2, 7) => "d@example.test",
(0, _) => "a@example.test",
(1, _) => "b@example.test",
_ => "c@example.test",
}
}
pub const AUTHORS: [(&str, &str); 4] = [
("a@example.test", "Ada Example"),
("b@example.test", "Bea Example"),
("c@example.test", "Cy Example"),
("d@example.test", "Dee Example"),
];
const SYMBOLS: [(usize, usize, &str, Option<usize>); 12] = [
(0, 0, "core::init", None),
(0, 1, "core::run", Some(0)),
(0, 2, "core::stop", Some(0)),
(0, 3, "core::load", Some(1)),
(0, 4, "core::save", Some(1)),
(0, 5, "core::flush", Some(4)),
(1, 0, "web::serve", Some(0)),
(1, 1, "web::route", Some(6)),
(1, 2, "web::render", Some(1)),
(1, 3, "web::auth", Some(2)),
(2, 0, "tests::smoke", Some(6)),
(2, 1, "tests::api", Some(7)),
];
fn symbol_key(n: usize) -> String {
let (d, i, name, _) = SYMBOLS[n];
format!("{}#{name}", file_key(d, i))
}
fn s(v: &str) -> Value {
Value::Str(v.to_string())
}
fn list(items: &[String]) -> Value {
Value::List(items.iter().map(|i| s(i)).collect())
}
#[must_use]
pub fn synthetic_repo_store(dir: &Path) -> GraphDb<core_storage::fs::RealFs> {
let mut db = open(dir);
for (key, name) in AUTHORS {
db.insert_node("Author", key, vec![("name".into(), s(name))])
.expect("author");
}
let mut commits_of: std::collections::BTreeMap<String, Vec<String>> = Default::default();
let mut authors_of: std::collections::BTreeMap<
String,
std::collections::BTreeMap<&str, usize>,
> = Default::default();
for i in 0..COMMITS {
for f in touched(i) {
commits_of.entry(f.clone()).or_default().push(sha(i));
*authors_of
.entry(f)
.or_default()
.entry(commit_author(i))
.or_default() += 1;
}
}
for (d, &(dir_name, n, _)) in DIRS.iter().enumerate() {
for i in 0..n {
let key = file_key(d, i);
let mut props = vec![
("id".into(), s(&key)),
("path".into(), s(&key)),
("dir".into(), s(dir_name)),
("ext".into(), s("rs")),
("lang".into(), s("rust")),
("hash".into(), s(&hash_of(&key))),
("lines".into(), Value::Int(40 + i as i64)),
("top_author_id".into(), s(owner_of(d, i))),
];
if i > 0 {
let target = file_key(d, 0);
props.push(("imports".into(), list(std::slice::from_ref(&target))));
props.push((
"import_lines".into(),
list(&[format!("{target}\t{}", 3 + i)]),
));
}
if key == doc_key() {
props.push(("mentions".into(), list(&[doc_mentions()])));
props.push(("headings".into(), list(&DOC_HEADINGS.map(str::to_string))));
props.push(("body".into(), s(&doc_body())));
}
let cs = commits_of.get(&key).cloned().unwrap_or_default();
if !cs.is_empty() {
props.push(("n_commits".into(), Value::Int(cs.len() as i64)));
props.push(("commits".into(), list(&cs)));
let counts: Vec<String> = authors_of
.get(&key)
.into_iter()
.flatten()
.map(|(email, n)| format!("{email}\t{n}"))
.collect();
props.push(("author_counts".into(), list(&counts)));
}
db.insert_node("File", &key, props).expect("file");
}
}
for (n, &(d, i, name, callee)) in SYMBOLS.iter().enumerate() {
let key = symbol_key(n);
let file = file_key(d, i);
let mut props = vec![
("id".into(), s(&key)),
("name".into(), s(name)),
("kind".into(), s("function")),
("path".into(), s(&file)),
("file_id".into(), s(&file)),
("line_start".into(), Value::Int(10 + n as i64)),
("line_end".into(), Value::Int(20 + n as i64)),
("signature".into(), s(&format!("fn {name}()"))),
("doc".into(), s(&format!("what {name} does"))),
];
if let Some(c) = callee {
let target = symbol_key(c);
props.push(("calls_to".into(), list(std::slice::from_ref(&target))));
props.push((
"call_lines".into(),
list(&[format!("{target}\t{}", 12 + n)]),
));
}
db.insert_node("Symbol", &key, props).expect("symbol");
}
for i in 0..COMMITS {
let key = sha(i);
db.insert_node(
"Commit",
&key,
vec![
("id".into(), s(&key)),
("message".into(), s(&format!("change {i:02}"))),
("ts".into(), Value::Int(commit_ts(i))),
("author_id".into(), s(AUTHORS[i % AUTHORS.len()].0)),
],
)
.expect("commit");
}
for i in 0..COMMITS {
for f in touched(i) {
db.insert_edge("TOUCHED", &sha(i), &f).expect("touched");
}
}
let fresh = file_key(0, 0);
db.insert_node(
"Concept",
"concept:startup",
vec![
("id".into(), s("concept:startup")),
("name".into(), s("startup path")),
("summary".into(), s("how the core boots")),
("source_files".into(), list(std::slice::from_ref(&fresh))),
("source_hashes".into(), list(&[hash_of(&fresh)])),
("extracted_by".into(), s("agent")),
("extracted_at".into(), Value::Int(newest_ts())),
],
)
.expect("concept");
let stale = file_key(1, 0);
db.insert_node(
"Concept",
"concept:routing",
vec![
("id".into(), s("concept:routing")),
("name".into(), s("routing")),
("summary".into(), s("how requests are routed")),
("source_files".into(), list(&[stale])),
(
"source_hashes".into(),
list(&["0000000000000000".to_string()]),
),
("extracted_by".into(), s("agent")),
("extracted_at".into(), Value::Int(T0)),
],
)
.expect("concept");
db.insert_node(
"Note",
"note:0001",
vec![
("id".into(), s("note:0001")),
(
"text".into(),
s("the core entry point is worth reading first"),
),
("kind".into(), s("note")),
("ts".into(), Value::Int(newest_ts())),
("source".into(), s("agent")),
("about".into(), list(&[file_key(0, 0)])),
],
)
.expect("note");
db.insert_node(
"GitSync",
"__mushroomdb_git_sync__",
vec![
("id".into(), s("__mushroomdb_git_sync__")),
("sha".into(), s(&sha(COMMITS - 1))),
("synced_at".into(), Value::Int(SYNCED_AT)),
("repo".into(), s("/synthetic/repo")),
("recurse".into(), Value::Bool(false)),
("prs".into(), Value::Bool(false)),
("structure".into(), Value::Bool(true)),
("docs".into(), Value::Bool(true)),
],
)
.expect("gitsync");
for def in rules() {
db.create_rule(def).expect("rule");
}
db
}
#[must_use]
pub fn rules() -> Vec<RuleDef> {
let mut out = vec![
key_rule(
"auto_fk_symbol_file_id",
"Symbol",
"File",
"file_id",
"DEFINES",
),
key_rule("imports", "File", "File", "imports", "IMPORTS"),
key_rule("calls", "Symbol", "Symbol", "calls_to", "CALLS"),
key_rule("mentions", "File", "File", "mentions", "MENTIONS"),
concept_sources_rule(),
key_rule(
"auto_fk_commit_author_id",
"Commit",
"Author",
"author_id",
"AUTHOR",
),
key_rule(
"auto_fk_file_top_author_id",
"File",
"Author",
"top_author_id",
"TOP_AUTHOR",
),
];
for label in ABOUT_LABELS {
out.push(about_rule(label));
}
let co = Predicate::Overlap {
field: "commits".into(),
min: 0.25,
};
out.push(RuleDef {
name: "co_changed".into(),
src_label: "File".into(),
dst_label: "File".into(),
predicate: co.clone(),
edge_type: "CO_CHANGED".into(),
weight_prop: Some("score".into()),
max_edges: Some(10),
approximate: false,
via_label: None,
via_edge: None,
via_dir: None,
namespace: None,
});
out.push(RuleDef {
name: "knows".into(),
src_label: "Author".into(),
dst_label: "File".into(),
predicate: co,
edge_type: "KNOWS".into(),
weight_prop: Some("score".into()),
max_edges: Some(20),
approximate: false,
via_label: Some("File".into()),
via_edge: Some("TOP_AUTHOR".into()),
via_dir: Some(Direction::In),
namespace: None,
});
out
}
fn key_rule(name: &str, src: &str, dst: &str, field: &str, edge: &str) -> RuleDef {
let predicate = Predicate::KeyMatch {
field: field.into(),
};
let max_edges = Some(default_max_edges(&predicate));
RuleDef {
name: name.into(),
src_label: src.into(),
dst_label: dst.into(),
predicate,
edge_type: edge.into(),
weight_prop: None,
max_edges,
approximate: false,
via_label: None,
via_edge: None,
via_dir: None,
namespace: None,
}
}