Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.

Commit f96536e

Browse files
author
Hendrik van Antwerpen
committed
Use partial paths + stitching for tests and visualization
1 parent 91692b3 commit f96536e

File tree

6 files changed

+142
-51
lines changed

6 files changed

+142
-51
lines changed

stack-graphs/src/assert.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ use crate::graph::File;
1515
use crate::graph::Node;
1616
use crate::graph::StackGraph;
1717
use crate::graph::Symbol;
18-
use crate::paths::Path;
19-
use crate::paths::Paths;
18+
use crate::partial::PartialPath;
19+
use crate::partial::PartialPaths;
20+
use crate::stitching::Database;
21+
use crate::stitching::ForwardPartialPathStitcher;
2022
use crate::CancellationError;
2123
use crate::CancellationFlag;
2224

@@ -100,7 +102,7 @@ pub enum AssertionError {
100102
source: AssertionSource,
101103
references: Vec<Handle<Node>>,
102104
missing_targets: Vec<AssertionTarget>,
103-
unexpected_paths: Vec<Path>,
105+
unexpected_paths: Vec<PartialPath>,
104106
},
105107
IncorrectDefinitions {
106108
source: AssertionSource,
@@ -126,12 +128,13 @@ impl Assertion {
126128
pub fn run(
127129
&self,
128130
graph: &StackGraph,
129-
paths: &mut Paths,
131+
partials: &mut PartialPaths,
132+
db: &mut Database,
130133
cancellation_flag: &dyn CancellationFlag,
131134
) -> Result<(), AssertionError> {
132135
match self {
133136
Self::Defined { source, targets } => {
134-
self.run_defined(graph, paths, source, targets, cancellation_flag)
137+
self.run_defined(graph, partials, db, source, targets, cancellation_flag)
135138
}
136139
Self::Defines { source, symbols } => self.run_defines(graph, source, symbols),
137140
Self::Refers { source, symbols } => self.run_refers(graph, source, symbols),
@@ -141,7 +144,8 @@ impl Assertion {
141144
fn run_defined(
142145
&self,
143146
graph: &StackGraph,
144-
paths: &mut Paths,
147+
partials: &mut PartialPaths,
148+
db: &mut Database,
145149
source: &AssertionSource,
146150
expected_targets: &Vec<AssertionTarget>,
147151
cancellation_flag: &dyn CancellationFlag,
@@ -154,12 +158,24 @@ impl Assertion {
154158
}
155159

156160
let mut actual_paths = Vec::new();
157-
paths.find_all_paths(graph, references.clone(), cancellation_flag, |g, _ps, p| {
158-
if p.is_complete(g) {
159-
actual_paths.push(p);
161+
for reference in &references {
162+
let reference_paths = ForwardPartialPathStitcher::find_all_complete_partial_paths(
163+
graph,
164+
partials,
165+
db,
166+
vec![*reference],
167+
cancellation_flag,
168+
)?;
169+
for reference_path in &reference_paths {
170+
if reference_paths
171+
.iter()
172+
.all(|other| !other.shadows(partials, reference_path))
173+
{
174+
actual_paths.push(reference_path.clone());
175+
}
160176
}
161-
})?;
162-
paths.remove_shadowed_paths(&mut actual_paths, cancellation_flag)?;
177+
}
178+
163179
let missing_targets = expected_targets
164180
.iter()
165181
.filter(|t| {

stack-graphs/src/json.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ where
113113
}
114114
}
115115

116-
/// Struct that ensures the implications of exclusions.
116+
/// Filter implementation that enforces all implications of another filter.
117117
/// For example, that nodes frome excluded files are not included, etc.
118118
struct ImplicationFilter<'a>(&'a dyn Filter);
119119

@@ -152,18 +152,25 @@ impl Filter for ImplicationFilter<'_> {
152152
paths: &PartialPaths,
153153
path: &PartialPath,
154154
) -> bool {
155-
path.edges
155+
let super_ok = self.0.include_partial_path(graph, paths, path);
156+
if !super_ok {
157+
return false;
158+
}
159+
let all_included_edges = path
160+
.edges
156161
.iter_unordered(paths)
157162
.map(|e| graph.node_for_id(e.source_node_id).unwrap())
158163
.chain(std::iter::once(path.end_node))
159164
.tuple_windows()
160-
.all(|(source, sink)| self.include_edge(graph, &source, &sink))
161-
&& self.0.include_partial_path(graph, paths, path)
165+
.all(|(source, sink)| self.include_edge(graph, &source, &sink));
166+
if !all_included_edges {
167+
return false;
168+
}
169+
true
162170
}
163171
}
164172

165-
/// Struct that ensures the implications of exclusions.
166-
/// For example, that nodes frome excluded files are not included, etc.
173+
// Filter implementation that includes everything.
167174
pub struct NoFilter;
168175

169176
impl Filter for NoFilter {

stack-graphs/src/visualization.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::partial::PartialPath;
1515
use crate::partial::PartialPaths;
1616
use crate::paths::Path;
1717
use crate::paths::Paths;
18+
use crate::stitching::Database;
1819

1920
static CSS: &'static str = include_str!("visualization/visualization.css");
2021
static D3: &'static str = include_str!("visualization/d3.v7.min.js");
@@ -31,12 +32,13 @@ impl StackGraph {
3132
pub fn to_html_string(
3233
&self,
3334
title: &str,
34-
paths: &mut Paths,
35+
partials: &mut PartialPaths,
36+
db: &mut Database,
3537
filter: &dyn Filter,
3638
) -> Result<String, JsonError> {
3739
let filter = VisualizationFilter(filter);
3840
let graph = self.to_json(&filter).to_string()?;
39-
let paths = paths.to_json(self, &filter).to_string()?;
41+
let paths = db.to_json(self, partials, &filter).to_string()?;
4042
let html = format!(
4143
r#"
4244
<!DOCTYPE html>
@@ -151,11 +153,14 @@ impl Filter for VisualizationFilter<'_> {
151153
if !self.0.include_partial_path(graph, paths, path) {
152154
return false;
153155
}
154-
if path.start_node == path.end_node {
156+
if path.start_node == path.end_node && path.edges.len() == 0 {
155157
return false;
156158
}
157159
if !match &graph[path.start_node] {
158-
Node::PushScopedSymbol(_) | Node::PushSymbol(_) => true,
160+
Node::PushScopedSymbol(_) | Node::PushSymbol(_) => {
161+
path.symbol_stack_precondition.can_match_empty()
162+
&& path.scope_stack_precondition.can_match_empty()
163+
}
159164
Node::Root(_) => true,
160165
Node::Scope(node) => node.is_exported,
161166
_ => false,
@@ -164,8 +169,8 @@ impl Filter for VisualizationFilter<'_> {
164169
}
165170
if !match &graph[path.end_node] {
166171
Node::PopScopedSymbol(_) | Node::PopSymbol(_) => {
167-
path.symbol_stack_postcondition.contains_symbols()
168-
&& path.scope_stack_postcondition.contains_scopes()
172+
path.symbol_stack_postcondition.can_match_empty()
173+
&& path.scope_stack_postcondition.can_match_empty()
169174
}
170175
Node::Root(_) => true,
171176
Node::Scope(node) => node.is_exported,

tree-sitter-stack-graphs/src/cli/test.rs

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use stack_graphs::arena::Handle;
1414
use stack_graphs::graph::File;
1515
use stack_graphs::graph::StackGraph;
1616
use stack_graphs::json::Filter;
17-
use stack_graphs::paths::Paths;
17+
use stack_graphs::partial::PartialPaths;
18+
use stack_graphs::stitching::Database;
19+
use stack_graphs::stitching::ForwardPartialPathStitcher;
1820
use std::path::Path;
1921
use std::path::PathBuf;
2022
use tree_sitter_graph::Variables;
@@ -269,17 +271,21 @@ impl TestArgs {
269271
));
270272
}
271273
}
272-
let result = test.run(cancellation_flag)?;
274+
let mut partials = PartialPaths::new();
275+
let mut db = Database::new();
276+
let result = test.run(&mut partials, &mut db, cancellation_flag)?;
273277
let success = self.handle_result(&result, &mut file_status)?;
274278
if self.output_mode.test(!success) {
275279
let files = test.fragments.iter().map(|f| f.file).collect::<Vec<_>>();
276280
self.save_output(
277281
test_root,
278282
test_path,
279283
&test.graph,
280-
&mut test.paths,
284+
&mut partials,
285+
&mut db,
281286
&|_: &StackGraph, h: &Handle<File>| files.contains(h),
282287
success,
288+
cancellation_flag,
283289
)?;
284290
}
285291
Ok(result)
@@ -343,36 +349,47 @@ impl TestArgs {
343349
test_root: &Path,
344350
test_path: &Path,
345351
graph: &StackGraph,
346-
paths: &mut Paths,
352+
partials: &mut PartialPaths,
353+
db: &mut Database,
347354
filter: &dyn Filter,
348355
success: bool,
356+
cancellation_flag: &dyn CancellationFlag,
349357
) -> anyhow::Result<()> {
350-
if let Some(path) = self
358+
let save_graph = self
351359
.save_graph
352360
.as_ref()
353-
.map(|spec| spec.format(test_root, test_path))
354-
{
361+
.map(|spec| spec.format(test_root, test_path));
362+
let save_paths = self
363+
.save_paths
364+
.as_ref()
365+
.map(|spec| spec.format(test_root, test_path));
366+
let save_visualization = self
367+
.save_visualization
368+
.as_ref()
369+
.map(|spec| spec.format(test_root, test_path));
370+
371+
if let Some(path) = save_graph {
355372
self.save_graph(&path, &graph, filter)?;
356373
if !success || !self.quiet {
357374
println!("{}: graph at {}", test_path.display(), path.display());
358375
}
359376
}
360-
if let Some(path) = self
361-
.save_paths
362-
.as_ref()
363-
.map(|spec| spec.format(test_root, test_path))
364-
{
365-
self.save_paths(&path, paths, graph, filter)?;
377+
378+
let mut db = if save_paths.is_some() || save_visualization.is_some() {
379+
self.compute_paths(graph, partials, db, filter, cancellation_flag)?
380+
} else {
381+
Database::new()
382+
};
383+
384+
if let Some(path) = save_paths {
385+
self.save_paths(&path, graph, partials, &mut db, filter)?;
366386
if !success || !self.quiet {
367387
println!("{}: paths at {}", test_path.display(), path.display());
368388
}
369389
}
370-
if let Some(path) = self
371-
.save_visualization
372-
.as_ref()
373-
.map(|spec| spec.format(test_root, test_path))
374-
{
375-
self.save_visualization(&path, paths, graph, filter, &test_path)?;
390+
391+
if let Some(path) = save_visualization {
392+
self.save_visualization(&path, graph, partials, &mut db, filter, &test_path)?;
376393
if !success || !self.quiet {
377394
println!(
378395
"{}: visualization at {}",
@@ -399,14 +416,41 @@ impl TestArgs {
399416
Ok(())
400417
}
401418

419+
fn compute_paths(
420+
&self,
421+
graph: &StackGraph,
422+
partials: &mut PartialPaths,
423+
db: &mut Database,
424+
filter: &dyn Filter,
425+
cancellation_flag: &dyn CancellationFlag,
426+
) -> anyhow::Result<Database> {
427+
let references = graph
428+
.iter_nodes()
429+
.filter(|n| filter.include_node(graph, n))
430+
.collect::<Vec<_>>();
431+
let paths = ForwardPartialPathStitcher::find_all_complete_partial_paths(
432+
graph,
433+
partials,
434+
db,
435+
references.clone(),
436+
&cancellation_flag,
437+
)?;
438+
let mut db = Database::new();
439+
for path in paths {
440+
db.add_partial_path(graph, partials, path);
441+
}
442+
Ok(db)
443+
}
444+
402445
fn save_paths(
403446
&self,
404447
path: &Path,
405-
paths: &mut Paths,
406448
graph: &StackGraph,
449+
partials: &mut PartialPaths,
450+
db: &mut Database,
407451
filter: &dyn Filter,
408452
) -> anyhow::Result<()> {
409-
let json = paths.to_json(graph, filter).to_string_pretty()?;
453+
let json = db.to_json(graph, partials, filter).to_string_pretty()?;
410454
if let Some(dir) = path.parent() {
411455
std::fs::create_dir_all(dir)?;
412456
}
@@ -418,12 +462,13 @@ impl TestArgs {
418462
fn save_visualization(
419463
&self,
420464
path: &Path,
421-
paths: &mut Paths,
422465
graph: &StackGraph,
466+
paths: &mut PartialPaths,
467+
db: &mut Database,
423468
filter: &dyn Filter,
424469
test_path: &Path,
425470
) -> anyhow::Result<()> {
426-
let html = graph.to_html_string(&format!("{}", test_path.display()), paths, filter)?;
471+
let html = graph.to_html_string(&format!("{}", test_path.display()), paths, db, filter)?;
427472
if let Some(dir) = path.parent() {
428473
std::fs::create_dir_all(dir)?;
429474
}

tree-sitter-stack-graphs/src/test.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ use stack_graphs::graph::File;
7272
use stack_graphs::graph::Node;
7373
use stack_graphs::graph::SourceInfo;
7474
use stack_graphs::graph::StackGraph;
75-
use stack_graphs::paths::Paths;
75+
use stack_graphs::partial::PartialPaths;
76+
use stack_graphs::stitching::Database;
7677
use std::collections::HashMap;
7778
use std::path::Path;
7879
use std::path::PathBuf;
@@ -149,7 +150,6 @@ pub struct Test {
149150
pub path: PathBuf,
150151
pub fragments: Vec<TestFragment>,
151152
pub graph: StackGraph,
152-
pub paths: Paths,
153153
}
154154

155155
/// A fragment from a stack graph test
@@ -269,7 +269,6 @@ impl Test {
269269
path: path.to_path_buf(),
270270
fragments,
271271
graph,
272-
paths: Paths::new(),
273272
})
274273
}
275274

@@ -623,13 +622,28 @@ impl Test {
623622
/// the test.
624623
pub fn run(
625624
&mut self,
625+
partials: &mut PartialPaths,
626+
db: &mut Database,
626627
cancellation_flag: &dyn CancellationFlag,
627628
) -> Result<TestResult, stack_graphs::CancellationError> {
629+
// build partial paths
630+
for file in self.graph.iter_files() {
631+
partials.find_all_partial_paths_in_file(
632+
&self.graph,
633+
file,
634+
&cancellation_flag,
635+
|g, ps, p| {
636+
db.add_partial_path(g, ps, p);
637+
},
638+
)?;
639+
}
640+
641+
// test assertions
628642
let mut result = TestResult::new();
629643
for fragment in &self.fragments {
630644
for assertion in &fragment.assertions {
631645
match assertion
632-
.run(&self.graph, &mut self.paths, &cancellation_flag)
646+
.run(&self.graph, partials, db, &cancellation_flag)
633647
.map_or_else(|e| self.from_error(e), |v| Ok(v))
634648
{
635649
Ok(_) => result.add_success(),

0 commit comments

Comments
 (0)