Skip to content

Commit 2db8e92

Browse files
committed
leaves added, tests fixed
Signed-off-by: Max Chesterfield <max.chesterfield@zepben.com>
1 parent b136555 commit 2db8e92

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

src/zepben/ewb/services/network/tracing/networktrace/actions/equipment_tree_builder.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class EquipmentTreeBuilder(StepActionWithContextValue):
3535
"""
3636

3737
_roots: dict[ConductingEquipment, EquipmentTreeNode] = {}
38+
_leaves: set[EquipmentTreeNode] = set()
3839

3940
def __init__(self):
4041
super().__init__(key=str(uuid.uuid4()))
@@ -43,6 +44,26 @@ def __init__(self):
4344
def roots(self) -> Generator[TreeNode[ConductingEquipment], None, None]:
4445
return (r for r in self._roots.values())
4546

47+
def recurse_nodes(self) -> Generator[TreeNode[ConductingEquipment], None, None]:
48+
"""
49+
Returns a generator that will yield every node in the tree structure.
50+
"""
51+
def recurse(node: TreeNode[ConductingEquipment]):
52+
yield node
53+
for child in node.children:
54+
yield from recurse(child)
55+
56+
for root in self._roots.values():
57+
yield from recurse(root)
58+
59+
@property
60+
def leaves(self) -> set[EquipmentTreeNode]:
61+
"""
62+
Return the leaves of the tree structure. Depending on how the backing trace is configured,
63+
there may be extra unexpected leaves in loops.
64+
"""
65+
return set(self._leaves)
66+
4667
def compute_initial_value(self, item: NetworkTraceStep[Any]) -> EquipmentTreeNode:
4768
node = self._roots.get(item.path.to_equipment)
4869
if node is None:
@@ -64,7 +85,9 @@ def compute_next_value(
6485

6586
def _apply(self, item: NetworkTraceStep[Any], context: StepContext):
6687
current_node: TreeNode = self.get_context_value(context)
88+
self._leaves.add(current_node) # add this node to _leaves as it has no children
6789
if current_node.parent:
90+
self._leaves.discard(current_node.parent) # this nodes parent now has a child, it's not a leaf anymore
6891
current_node.parent.add_child(current_node)
6992

7093
def clear(self):

src/zepben/ewb/services/network/tracing/networktrace/actions/tree_node.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,31 @@
55

66
__all__ = ['TreeNode']
77

8-
from typing import List, TypeVar, Generic
9-
10-
from zepben.ewb import IdentifiedObject
8+
from typing import TypeVar, Generic, Set
119

1210
T = TypeVar('T')
1311

1412

1513
class TreeNode(Generic[T]):
1614
"""
17-
represents a node in the NetworkTrace tree
15+
Represents a node in the NetworkTrace tree
1816
"""
1917

20-
def __init__(self, identified_object: IdentifiedObject, parent=None):
18+
def __init__(self, identified_object: T, parent=None):
2119
self.identified_object = identified_object
2220
self._parent: TreeNode = parent
23-
self._children: List[TreeNode] = []
21+
self._children: Set[TreeNode] = set()
2422

2523
@property
2624
def parent(self) -> 'TreeNode[T]':
2725
return self._parent
2826

2927
@property
30-
def children(self):
31-
return list(self._children)
28+
def children(self) -> Set['TreeNode[T]']:
29+
return set(self._children)
3230

33-
def add_child(self, child: 'TreeNode'):
34-
self._children.append(child)
31+
def add_child(self, child: 'TreeNode[T]'):
32+
self._children.add(child)
3533

3634
def __str__(self):
3735
return f"{{object: {self.identified_object}, parent: {self.parent or ''}, num children: {len(self.children)}}}"

test/services/network/tracing/networktrace/actions/test_equipment_tree_builder.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ async def test_downstream_tree():
3333
start = n.get("j1", ConductingEquipment)
3434
assert start is not None
3535
tree_builder = EquipmentTreeBuilder()
36-
trace = Tracing.network_trace_branching(
37-
network_state_operators=normal,
38-
action_step_type=NetworkTraceActionType.FIRST_STEP_ON_EQUIPMENT) \
39-
.add_condition(downstream()) \
40-
.add_step_action(tree_builder) \
36+
trace = (
37+
Tracing.network_trace_branching(
38+
network_state_operators=normal,
39+
action_step_type=NetworkTraceActionType.FIRST_STEP_ON_EQUIPMENT
40+
)
41+
.add_condition(downstream())
42+
.add_step_action(tree_builder)
4143
.add_step_action(lambda item, context: visited_ce.append(item.path.to_equipment.mrid))
44+
)
4245

4346
await trace.run(start)
4447

@@ -51,34 +54,39 @@ async def test_downstream_tree():
5154

5255
pprint.pprint(visit_counts)
5356

54-
root = list(tree_builder.roots)[0]
57+
root = tree_builder._roots[start]
5558

5659
assert root is not None
5760
_verify_tree_asset(root, n["j1"], None, [n["ac1"], n["ac3"]])
5861

59-
test_node = root.children[0]
60-
_verify_tree_asset(test_node, n["ac1"], n["j1"], [n["j2"]])
62+
assert len(root.children) == 2
63+
for test_node in root.children:
64+
if test_node.identified_object == n['ac1']:
65+
_verify_tree_asset(test_node, n["ac1"], n["j1"], [n["j2"]])
6166

62-
test_node = test_node.children[0]
63-
_verify_tree_asset(test_node, n["j2"], n["ac1"], [n["ac2"]])
67+
test_node = test_node.children.pop()
68+
_verify_tree_asset(test_node, n["j2"], n["ac1"], [n["ac2"]])
6469

65-
test_node = test_node.children[0]
66-
_verify_tree_asset(test_node, n["ac2"], n["j2"], [n["j3"]])
70+
test_node = test_node.children.pop()
71+
_verify_tree_asset(test_node, n["ac2"], n["j2"], [n["j3"]])
6772

68-
test_node = next(iter(test_node.children))
69-
_verify_tree_asset(test_node, n["j3"], n["ac2"], [n["ac4"]])
73+
test_node = next(iter(test_node.children))
74+
_verify_tree_asset(test_node, n["j3"], n["ac2"], [n["ac4"]])
7075

71-
test_node = next(iter(test_node.children))
72-
_verify_tree_asset(test_node, n["ac4"], n["j3"], [n["j6"]])
76+
test_node = next(iter(test_node.children))
77+
_verify_tree_asset(test_node, n["ac4"], n["j3"], [n["j6"]])
7378

74-
test_node = next(iter(test_node.children))
75-
_verify_tree_asset(test_node, n["j6"], n["ac4"], [])
79+
test_node = next(iter(test_node.children))
80+
_verify_tree_asset(test_node, n["j6"], n["ac4"], [])
81+
break
7682

77-
test_node = list(root.children)[1]
78-
_verify_tree_asset(test_node, n["ac3"], n["j1"], [n["j4"]])
83+
elif test_node.identified_object == n['ac3']:
84+
_verify_tree_asset(test_node, n["ac3"], n["j1"], [n["j4"]])
7985

80-
test_node = next(iter(test_node.children))
81-
_verify_tree_asset(test_node, n["j4"], n["ac3"], [n["ac5"], n["ac6"]])
86+
test_node = next(iter(test_node.children))
87+
_verify_tree_asset(test_node, n["j4"], n["ac3"], [n["ac5"], n["ac6"]])
88+
else:
89+
assert False
8290

8391
assert len(_find_nodes(root, "j0")) == 0
8492
assert len(_find_nodes(root, "ac0")) == 0
@@ -147,6 +155,10 @@ async def test_downstream_tree():
147155
assert _find_node_depths(root, "ac16") == [8, 9, 11, 14]
148156

149157

158+
for ce in (n['j5'], n['j13']):
159+
assert ce in {l.identified_object for l in tree_builder.leaves}
160+
161+
150162
def _verify_tree_asset(
151163
tree_node: TreeNode,
152164
expected_asset: Optional[ConductingEquipment],
@@ -162,8 +174,15 @@ def _verify_tree_asset(
162174
else:
163175
assert tree_node.parent is None
164176

165-
children_nodes = list(c.identified_object for c in tree_node.children)
166-
assert children_nodes == expected_children
177+
children_nodes = [c.identified_object for c in tree_node.children]
178+
try:
179+
for child in expected_children:
180+
assert child in children_nodes
181+
for child in children_nodes:
182+
assert child in expected_children
183+
except AssertionError as e:
184+
e.args = (expected_children, children_nodes)
185+
raise e
167186

168187

169188
def _find_nodes(root: TreeNode[ConductingEquipment], asset_id: str) -> List[TreeNode[ConductingEquipment]]:

0 commit comments

Comments
 (0)