Skip to content

Commit 4f82fa8

Browse files
committed
[tests] Test suite for node attributeChanged callback mechanism
Suite of tests to validate the behavior of desc.Node 'on{Attribute}Changed' behavior.
1 parent 5b72131 commit 4f82fa8

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# coding:utf-8
2+
3+
from meshroom.core.graph import Graph, loadGraph
4+
from meshroom.core import desc, registerNodeType, unregisterNodeType
5+
from meshroom.core.node import Node
6+
7+
8+
class NodeWithAttributeChangedCallback(desc.Node):
9+
"""
10+
A Node containing an input Attribute with an 'on{Attribute}Changed' method,
11+
called whenever the value of this attribute is changed explicitly.
12+
"""
13+
14+
inputs = [
15+
desc.IntParam(
16+
name="input",
17+
label="Input",
18+
description="Attribute with a value changed callback (onInputChanged)",
19+
value=0,
20+
range=None,
21+
),
22+
desc.IntParam(
23+
name="affectedInput",
24+
label="Affected Input",
25+
description="Updated to input.value * 2 whenever 'input' is explicitly modified",
26+
value=0,
27+
range=None,
28+
),
29+
]
30+
31+
def onInputChanged(self, instance: Node):
32+
instance.affectedInput.value = instance.input.value * 2
33+
34+
def processChunk(self, chunk):
35+
pass # No-op.
36+
37+
38+
39+
class TestNodeWithAttributeChangedCallback:
40+
@classmethod
41+
def setup_class(cls):
42+
registerNodeType(NodeWithAttributeChangedCallback)
43+
44+
@classmethod
45+
def teardown_class(cls):
46+
unregisterNodeType(NodeWithAttributeChangedCallback)
47+
48+
def test_assignValueTriggersCallback(self):
49+
node = Node(NodeWithAttributeChangedCallback.__name__)
50+
assert node.affectedInput.value == 0
51+
52+
node.input.value = 10
53+
assert node.affectedInput.value == 20
54+
55+
def test_specifyDefaultValueDoesNotTriggerCallback(self):
56+
node = Node(NodeWithAttributeChangedCallback.__name__, input=10)
57+
assert node.affectedInput.value == 0
58+
59+
def test_assignDefaultValueDoesNotTriggerCallback(self):
60+
node = Node(NodeWithAttributeChangedCallback.__name__, input=10)
61+
node.input.value = 10
62+
assert node.affectedInput.value == 0
63+
64+
def test_assignNonDefaultValueTriggersCallback(self):
65+
node = Node(NodeWithAttributeChangedCallback.__name__, input=10)
66+
node.input.value = 2
67+
assert node.affectedInput.value == 4
68+
69+
70+
class TestAttributeCallbackTriggerInGraph:
71+
@classmethod
72+
def setup_class(cls):
73+
registerNodeType(NodeWithAttributeChangedCallback)
74+
75+
@classmethod
76+
def teardown_class(cls):
77+
unregisterNodeType(NodeWithAttributeChangedCallback)
78+
79+
def test_connectionTriggersCallback(self):
80+
graph = Graph("")
81+
nodeA = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
82+
nodeB = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
83+
84+
assert nodeA.affectedInput.value == nodeB.affectedInput.value == 0
85+
86+
nodeA.input.value = 1
87+
graph.addEdge(nodeA.input, nodeB.input)
88+
89+
assert nodeA.affectedInput.value == nodeB.affectedInput.value == 2
90+
91+
def test_connectedValueChangeTriggersCallback(self):
92+
graph = Graph("")
93+
nodeA = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
94+
nodeB = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
95+
96+
assert nodeA.affectedInput.value == nodeB.affectedInput.value == 0
97+
98+
graph.addEdge(nodeA.input, nodeB.input)
99+
nodeA.input.value = 1
100+
101+
assert nodeA.affectedInput.value == 2
102+
assert nodeB.affectedInput.value == 2
103+
104+
def test_defaultValueOnlyTriggersCallbackDownstream(self):
105+
graph = Graph("")
106+
nodeA = graph.addNewNode(NodeWithAttributeChangedCallback.__name__, input=1)
107+
nodeB = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
108+
109+
assert nodeA.affectedInput.value == 0
110+
assert nodeB.affectedInput.value == 0
111+
112+
graph.addEdge(nodeA.input, nodeB.input)
113+
114+
assert nodeA.affectedInput.value == 0
115+
assert nodeB.affectedInput.value == 2
116+
117+
def test_valueChangeIsPropagatedAlongNodeChain(self):
118+
graph = Graph("")
119+
nodeA = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
120+
nodeB = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
121+
nodeC = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
122+
nodeD = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
123+
124+
graph.addEdges(
125+
(nodeA.affectedInput, nodeB.input),
126+
(nodeB.affectedInput, nodeC.input),
127+
(nodeC.affectedInput, nodeD.input),
128+
)
129+
130+
nodeA.input.value = 5
131+
132+
assert nodeA.affectedInput.value == nodeB.input.value == 10
133+
assert nodeB.affectedInput.value == nodeC.input.value == 20
134+
assert nodeC.affectedInput.value == nodeD.input.value == 40
135+
assert nodeD.affectedInput.value == 80
136+
137+
def test_disconnectionTriggersCallback(self):
138+
graph = Graph("")
139+
nodeA = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
140+
nodeB = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
141+
142+
graph.addEdge(nodeA.input, nodeB.input)
143+
nodeA.input.value = 5
144+
assert nodeB.affectedInput.value == 10
145+
146+
graph.removeEdge(nodeB.input)
147+
148+
assert nodeB.input.value == 0
149+
assert nodeB.affectedInput.value == 0
150+
151+
def test_loadingGraphDoesNotTriggerCallback(self, graphSavedOnDisk):
152+
graph: Graph = graphSavedOnDisk
153+
node = graph.addNewNode(NodeWithAttributeChangedCallback.__name__)
154+
155+
node.input.value = 5
156+
node.affectedInput.value = 2
157+
graph.save()
158+
159+
loadedGraph = loadGraph(graph.filepath)
160+
loadedNode = loadedGraph.node(node.name)
161+
assert loadedNode
162+
assert loadedNode.affectedInput.value == 2

0 commit comments

Comments
 (0)