Skip to content

Commit d330b27

Browse files
committed
Add tests
1 parent aa7b359 commit d330b27

5 files changed

Lines changed: 123 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,21 @@
3232

3333
## Feature todo
3434

35-
* Implement sub_graph
35+
* ~~Implement sub_graph~~
3636
* ~~fix graph node enforcement of uid~~
3737
* ~~Delete node~~
3838
* ~~Partial graph traversal~~
3939

4040
## Documentation todo
4141

42+
* ~~Add documentation on sub_graph~~
4243
* ~~Add doc about node / graph creation~~
4344
* ~~Add documentation about graph nodes~~
4445
* ~~Add documentation about custom hook~~
4546
* ~~Add documentation Composed Visitor~~
46-
* Add documentation on sub_graph
4747

4848
## Testing todo
4949

50-
* Test sub_graph
50+
* ~~Test sub_graph~~
5151
* ~~Test Composed Visitor~~
5252
* ~~Custom hook~~

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ It provide:
1919
* [Graph creation](#graph-creation)
2020
* [Graph Node](#graph-node)
2121
* [Fork mechanism](#fork)
22+
* [Sub graph](#sub-graph)
2223
* [Visitors](#visitors)
2324
* [Visitor hook](#abstract-visitor-hooks)
2425
* [Standard visitors](#standard-visitor-provided-by-freexgraph)
@@ -258,6 +259,20 @@ It is also possible to provide a join node. It will be a node used as join for t
258259
But if you want to do a map reduce with a fork, it is do-able by setting the join_id to the `fork_from_node` method. The join_id has to be an existing node on which, for every parents that are part of the fork has only this join node as child.
259260
See [test using this mechanism](https://github.com/FreeYourSoul/FreExGraph/blob/ae707cf0fcb8486bde783cd0c7fe67217a56b3d2/test/fork_test.py#L41-L66) for more details
260261

262+
### Sub-Graph
263+
264+
A method exist in the `FreExGraph` class in order to make sub graph out of the graph.
265+
The signature is the following:
266+
```python
267+
def sub_graph(
268+
self, from_node_id: str, to_nodes_id: Optional[List[str]] = None
269+
) -> FreExGraph:
270+
```
271+
272+
Providing a node to start the subgraph and optionally a set of node where you want the sub graph to end. If no node matching the ``to_nodes_id`` is encountered, the subgraph go until the leaf nodes of the graph.
273+
274+
This feature may be used as a fork mechanism (as seen above). It is easier to manipulate and monitor.
275+
261276
## Visitors
262277

263278
### Abstract Visitor hooks

freexgraph/freexgraph.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -296,26 +296,29 @@ def sub_graph(
296296
), f"Error sub graph from node {from_node_id}, node has to be in the execution graph"
297297

298298
nodes_in_subgraph: List[FreExNode] = []
299-
nodes_in_subgraph_id: List[str] = []
299+
nodes_in_subgraph_id: Set[str] = set()
300300

301301
def add_node_in_subgraph(current_node: FreExNode):
302-
current_node.parents = {
303-
p for p in current_node.parents if p in nodes_in_subgraph_id
304-
}
302+
if current_node.id in nodes_in_subgraph_id:
303+
return
305304
nodes_in_subgraph.append(current_node)
306-
if to_nodes_id is not None and current_node in to_nodes_id:
305+
nodes_in_subgraph_id.add(current_node.id)
306+
if to_nodes_id is not None and current_node.id in to_nodes_id:
307307
return
308308
all_suc = list(self._graph.successors(current_node.id))
309309
for successor in all_suc:
310-
n = self.get_node(successor)
310+
node_suc = self.get_node(successor)
311311
assert (
312-
n is not None
313-
), f"Error sub graph to node {n.id}, node has to be in the execution graph"
314-
add_node_in_subgraph(n)
315-
nodes_in_subgraph_id.append(n.id)
312+
node_suc is not None
313+
), f"Error sub graph to node {node_suc.id}, node has to be in the execution graph"
314+
add_node_in_subgraph(node_suc)
316315

317316
add_node_in_subgraph(from_node)
318317

318+
# cleanup parents
319+
for n in nodes_in_subgraph:
320+
n.parents = {p for p in n.parents if p in nodes_in_subgraph_id}
321+
319322
sub_graph = FreExGraph()
320323
sub_graph.add_nodes(nodes_in_subgraph)
321324
return sub_graph

test/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def unordered_node_list_for_complex_graph() -> List[NodeForTest]:
125125
#
126126
return [
127127
NodeForTest("C", parents={"A"}),
128-
NodeForTest("K", parents={"G"}),
128+
NodeForTest("K", parents={"G", "F"}),
129129
NodeForTest("M", parents={"K", "B"}),
130130
NodeForTest("D", parents={"A"}),
131131
NodeForTest("J", parents={"F"}),

test/sub_graph_test.py

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@
2323

2424
import pytest
2525

26-
from freexgraph import GraphNode, FreExNode, FreExGraph
26+
from freexgraph import FreExGraph
2727

28-
# Graph used in thoses tests are from valid_complex_graph fixture
28+
# Graph used in theses tests are from valid_complex_graph fixture
2929
#
3030
#
3131
# A B
@@ -51,30 +51,108 @@ def test_sub_graph_without_to_node(valid_complex_graph, visitor_test):
5151

5252
sub_graph: FreExGraph = valid_complex_graph.sub_graph(from_node_id="F")
5353

54-
visitor_test.visit(valid_complex_graph)
5554
visitor_test.visit(sub_graph.root)
55+
5656
assert len(visitor_test.visited) == 7
57+
sorted_visit = sorted(visitor_test.visited)
58+
59+
assert sorted_visit[0] == "F"
60+
assert sorted_visit[1] == "H"
61+
assert sorted_visit[2] == "I"
62+
assert sorted_visit[3] == "J"
63+
assert sorted_visit[4] == "K"
64+
assert sorted_visit[5] == "L"
65+
assert sorted_visit[6] == "M"
66+
67+
68+
def test_sub_graph_of_one_element(valid_complex_graph, visitor_test):
69+
sub_graph: FreExGraph = valid_complex_graph.sub_graph(
70+
from_node_id="E", to_nodes_id=["E"]
71+
)
72+
73+
visitor_test.visit(sub_graph.root)
5774

75+
assert len(visitor_test.visited) == 1
76+
assert visitor_test.visited[0] == "E"
5877

59-
def test_sub_graph_of_one_element(valid_complex_graph):
60-
...
6178

79+
def test_sub_graph_with_to_node(valid_complex_graph, visitor_test):
80+
# From A to K and J (L and M are not taken as K and J are stopping the subgraph)
81+
#
82+
# A
83+
# / \
84+
# C D
85+
# \
86+
# F .______,
87+
# / | \ \
88+
# H I J `K
89+
#
90+
sub_graph: FreExGraph = valid_complex_graph.sub_graph(
91+
from_node_id="A", to_nodes_id=["K", "J"]
92+
)
93+
94+
visitor_test.visit(sub_graph.root)
6295

63-
def test_sub_graph_with_to_node_not_found(valid_complex_graph):
64-
...
96+
assert len(visitor_test.visited) == 8
97+
sorted_visit = sorted(visitor_test.visited)
6598

99+
assert sorted_visit[0] == "A"
100+
assert sorted_visit[1] == "C"
101+
assert sorted_visit[2] == "D"
102+
assert sorted_visit[3] == "F"
103+
assert sorted_visit[4] == "H"
104+
assert sorted_visit[5] == "I"
105+
assert sorted_visit[6] == "J"
106+
assert sorted_visit[7] == "K"
66107

67-
def test_sub_graph_with_to_node(valid_complex_graph):
68-
...
69108

109+
def test_sub_graph_with_to_node_not_found(valid_complex_graph, visitor_test):
110+
# From F to A, A is not in the sub graph starting from F, so it's like no to_node where set
111+
#
112+
# F .______,
113+
# / | \ \
114+
# H I J `,K.
115+
# /,_____/ \
116+
# L M
117+
118+
sub_graph: FreExGraph = valid_complex_graph.sub_graph(from_node_id="F", to_nodes_id="A")
119+
120+
visitor_test.visit(sub_graph.root)
121+
122+
assert len(visitor_test.visited) == 7
123+
sorted_visit = sorted(visitor_test.visited)
70124

71-
def test_sub_graph_with_to_node_partial(valid_complex_graph):
72-
...
125+
assert sorted_visit[0] == "F"
126+
assert sorted_visit[1] == "H"
127+
assert sorted_visit[2] == "I"
128+
assert sorted_visit[3] == "J"
129+
assert sorted_visit[4] == "K"
130+
assert sorted_visit[5] == "L"
131+
assert sorted_visit[6] == "M"
73132

74133

75134
def test_sub_graph_error_on_from_node(valid_complex_graph):
76-
...
135+
with pytest.raises(AssertionError) as e:
136+
valid_complex_graph.sub_graph(from_node_id="NOT_EXISTING")
137+
assert (
138+
"Error sub graph from node NOT_EXISTING, node has to be in the execution graph"
139+
== e
140+
)
77141

78142

79143
def test_sub_graph_error_on_to_node(valid_complex_graph):
80-
...
144+
with pytest.raises(AssertionError) as e:
145+
valid_complex_graph.sub_graph(from_node_id="F", to_nodes_id=["NOT_EXIST"])
146+
assert (
147+
"Error sub graph to node NOT_EXIST, node has to be in the execution graph"
148+
== e
149+
)
150+
151+
with pytest.raises(AssertionError) as e:
152+
valid_complex_graph.sub_graph(
153+
from_node_id="F", to_nodes_id=["H", "M", "NOT_EXISTING"]
154+
)
155+
assert (
156+
"Error sub graph to node NOT_EXISTING, node has to be in the execution graph"
157+
== e
158+
)

0 commit comments

Comments
 (0)