diff --git a/Cargo.lock b/Cargo.lock index 9addf420a..cfdd5b0e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,6 +1049,7 @@ dependencies = [ "config", "criterion", "digest", + "esl01-renderdag", "git2", "hex", "insta", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e5407fe43..f30223bc6 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -63,6 +63,7 @@ rustix = { version = "0.38.4", features = ["fs"] } [dev-dependencies] assert_matches = "1.5.0" criterion = "0.5.1" +esl01-renderdag = "0.3.0" insta = "1.31.0" num_cpus = "1.16.0" test-case = "3.1.0" diff --git a/lib/src/revset_graph.rs b/lib/src/revset_graph.rs index d1cf5cabc..238f5f0b3 100644 --- a/lib/src/revset_graph.rs +++ b/lib/src/revset_graph.rs @@ -93,3 +93,85 @@ impl Iterator for ReverseRevsetGraphIterator { self.items.pop() } } + +#[cfg(test)] +mod tests { + use itertools::Itertools as _; + use renderdag::{Ancestor, GraphRowRenderer, Renderer as _}; + + use super::*; + use crate::backend::ObjectId; + + fn id(c: char) -> CommitId { + let d = u8::try_from(c).unwrap(); + CommitId::new(vec![d]) + } + + fn missing(c: char) -> RevsetGraphEdge { + RevsetGraphEdge::missing(id(c)) + } + + fn direct(c: char) -> RevsetGraphEdge { + RevsetGraphEdge::direct(id(c)) + } + + fn indirect(c: char) -> RevsetGraphEdge { + RevsetGraphEdge::indirect(id(c)) + } + + fn format_edge(edge: &RevsetGraphEdge) -> String { + let c = char::from(edge.target.as_bytes()[0]); + match edge.edge_type { + RevsetGraphEdgeType::Missing => format!("missing({c})"), + RevsetGraphEdgeType::Direct => format!("direct({c})"), + RevsetGraphEdgeType::Indirect => format!("indirect({c})"), + } + } + + fn format_graph( + graph_iter: impl IntoIterator)>, + ) -> String { + let mut renderer = GraphRowRenderer::new() + .output() + .with_min_row_height(2) + .build_box_drawing(); + graph_iter + .into_iter() + .map(|(id, edges)| { + let glyph = char::from(id.as_bytes()[0]).to_string(); + let message = edges.iter().map(format_edge).join(", "); + let parents = edges + .into_iter() + .map(|edge| match edge.edge_type { + RevsetGraphEdgeType::Missing => Ancestor::Anonymous, + RevsetGraphEdgeType::Direct => Ancestor::Parent(edge.target), + RevsetGraphEdgeType::Indirect => Ancestor::Ancestor(edge.target), + }) + .collect(); + renderer.next_row(id, parents, glyph, message) + }) + .collect() + } + + #[test] + fn test_format_graph() { + let graph = vec![ + (id('D'), vec![direct('C'), indirect('B')]), + (id('C'), vec![direct('A')]), + (id('B'), vec![missing('X')]), + (id('A'), vec![]), + ]; + insta::assert_snapshot!(format_graph(graph), @r###" + D direct(C), indirect(B) + ├─╮ + C ╷ direct(A) + │ ╷ + │ B missing(X) + │ │ + │ ~ + │ + A + + "###); + } +}