gonzalo_graph/
assembly.rs1use crate::{CodeGraph, GraphStore, InMemoryGraphStore};
9use gonzalo_core::{BlobStore, CoreError, Manifest, Result};
10
11pub async fn assemble<B: BlobStore + ?Sized>(
19 manifest: &Manifest,
20 blobs: &B,
21) -> Result<InMemoryGraphStore> {
22 let mut store = InMemoryGraphStore::new();
23 for (path, hash) in &manifest.entries {
24 if let Some(bytes) = blobs.get_blob(hash).await? {
25 let graph =
26 CodeGraph::from_slice_bytes(&bytes).map_err(|e| CoreError::Serde(e.to_string()))?;
27 store.insert(path, graph);
28 }
29 }
30 Ok(store)
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use crate::build_rust;
37 use gonzalo_store_fs::FsStore;
38
39 fn fresh_store() -> FsStore {
40 let dir = tempfile::tempdir().expect("tempdir");
41 FsStore::new(dir.keep())
42 }
43
44 async fn put_slice(blobs: &FsStore, src: &str) -> gonzalo_core::ContentHash {
46 blobs
47 .put_blob(&build_rust(src).to_slice_bytes())
48 .await
49 .unwrap()
50 }
51
52 #[tokio::test]
53 async fn assembles_slices_under_their_manifest_paths() {
54 let blobs = fresh_store();
55 let lib = put_slice(&blobs, "fn helper() {}").await;
56 let main = put_slice(&blobs, "fn main() { helper(); }").await;
57
58 let mut manifest = Manifest::new();
59 manifest.insert("src/lib.rs", lib);
60 manifest.insert("src/main.rs", main);
61
62 let graph = assemble(&manifest, &blobs).await.unwrap();
63
64 let defs = graph.definitions("helper");
66 assert_eq!(defs.len(), 1);
67 assert_eq!(defs[0].path, "src/lib.rs");
68 let callers = graph.callers_of("helper");
70 assert_eq!(callers, vec!["main".to_string()]);
71 assert!(
72 graph
73 .symbols_in_file("src/main.rs")
74 .iter()
75 .any(|s| s.name == "main")
76 );
77 }
78
79 #[tokio::test]
80 async fn tolerates_a_missing_slice_blob() {
81 let blobs = fresh_store();
82 let present = put_slice(&blobs, "fn present() {}").await;
83
84 let mut manifest = Manifest::new();
85 manifest.insert("present.rs", present);
86 manifest.insert("missing.rs", gonzalo_core::ContentHash::of(b"never stored"));
88
89 let graph = assemble(&manifest, &blobs).await.unwrap();
90 assert_eq!(graph.definitions("present").len(), 1);
91 assert!(graph.symbols_in_file("missing.rs").is_empty());
92 }
93
94 #[tokio::test]
95 async fn path_comes_from_manifest_so_identical_content_dedups() {
96 let blobs = fresh_store();
97 let hash = put_slice(&blobs, "fn shared() {}").await;
100 let same = put_slice(&blobs, "fn shared() {}").await;
101 assert_eq!(hash, same, "identical content must dedup to one blob");
102
103 let mut manifest = Manifest::new();
104 manifest.insert("a.rs", hash.clone());
105 manifest.insert("vendor/a.rs", hash);
106
107 let graph = assemble(&manifest, &blobs).await.unwrap();
108 let mut paths: Vec<String> = graph
109 .definitions("shared")
110 .into_iter()
111 .map(|l| l.path)
112 .collect();
113 paths.sort();
114 assert_eq!(paths, vec!["a.rs".to_string(), "vendor/a.rs".to_string()]);
115 }
116
117 #[tokio::test]
118 async fn empty_manifest_assembles_empty_graph() {
119 let blobs = fresh_store();
120 let graph = assemble(&Manifest::new(), &blobs).await.unwrap();
121 assert!(graph.slices().is_empty());
122 }
123}