Coverage for utilities/tests/test_network.py: 100%
515 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""Define the unit tests for the :mod:`colour.utilities.network` module."""
3from __future__ import annotations
5import re
6import typing
8import numpy as np
10if typing.TYPE_CHECKING:
11 from colour.hints import Any
13from colour.utilities import (
14 ExecutionNode,
15 For,
16 ParallelForMultiprocess,
17 ParallelForThread,
18 Port,
19 PortGraph,
20 PortNode,
21 TreeNode,
22 is_pydot_installed,
23)
24from colour.utilities.network import (
25 ProcessPoolExecutorManager,
26 ThreadPoolExecutorManager,
27)
29__author__ = "Colour Developers"
30__copyright__ = "Copyright 2013 Colour Developers"
31__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
32__maintainer__ = "Colour Developers"
33__email__ = "colour-developers@colour-science.org"
34__status__ = "Production"
36__all__ = [
37 "TestTreeNode",
38 "TestPort",
39 "TestPortNode",
40 "TestPortGraph",
41 "TestFor",
42 "TestThreadPoolExecutorManager",
43 "TestParallelForThread",
44 "TestProcessPoolExecutorManager",
45 "TestParallelForMultiProcess",
46]
49class TestTreeNode:
50 """
51 Define :class:`colour.utilities.network.TreeNode` class unit tests
52 methods.
53 """
55 def setup_method(self) -> None:
56 """Initialise the common tests attributes."""
58 self._data = {"John": "Doe"}
60 self._node_a = TreeNode("Node A", data=self._data)
61 self._node_b = TreeNode("Node B", self._node_a)
62 self._node_c = TreeNode("Node C", self._node_a)
63 self._node_d = TreeNode("Node D", self._node_b)
64 self._node_e = TreeNode("Node E", self._node_b)
65 self._node_f = TreeNode("Node F", self._node_d)
66 self._node_g = TreeNode("Node G", self._node_f)
67 self._node_h = TreeNode("Node H", self._node_g)
69 self._tree = self._node_a
71 def test_required_attributes(self) -> None:
72 """Test the presence of required attributes."""
74 required_attributes = (
75 "id",
76 "name",
77 "parent",
78 "children",
79 "root",
80 "leaves",
81 "siblings",
82 "data",
83 )
85 for attribute in required_attributes:
86 assert attribute in dir(TreeNode)
88 def test_required_methods(self) -> None:
89 """Test the presence of required methods."""
91 required_methods = (
92 "__new__",
93 "__init__",
94 "__str__",
95 "__len__",
96 "is_root",
97 "is_inner",
98 "is_leaf",
99 "walk_hierarchy",
100 "render",
101 )
103 for method in required_methods:
104 assert method in dir(TreeNode)
106 def test_name(self) -> None:
107 """Test :attr:`colour.utilities.network.TreeNode.name` property."""
109 assert self._tree.name == "Node A"
110 assert "Node#" in TreeNode().name
112 def test_parent(self) -> None:
113 """Test :attr:`colour.utilities.network.TreeNode.parent` property."""
115 assert self._node_b.parent is self._node_a
116 assert self._node_h.parent is self._node_g
118 def test_children(self) -> None:
119 """Test :attr:`colour.utilities.network.TreeNode.children` property."""
121 assert self._node_a.children == [self._node_b, self._node_c]
123 def test_id(self) -> None:
124 """Test :attr:`colour.utilities.network.TreeNode.id` property."""
126 assert isinstance(self._node_a.id, int)
128 def test_root(self) -> None:
129 """Test :attr:`colour.utilities.network.TreeNode.root` property."""
131 assert self._node_a.root is self._node_a
132 assert self._node_f.root is self._node_a
133 assert self._node_g.root is self._node_a
134 assert self._node_h.root is self._node_a
136 def test_leaves(self) -> None:
137 """Test :attr:`colour.utilities.network.TreeNode.leaves` property."""
139 assert list(self._node_h.leaves) == [self._node_h]
141 assert list(self._node_a.leaves) == [self._node_h, self._node_e, self._node_c]
143 def test_siblings(self) -> None:
144 """Test :attr:`colour.utilities.network.TreeNode.siblings` property."""
146 assert list(self._node_a.siblings) == []
148 assert list(self._node_b.siblings) == [self._node_c]
150 def test_data(self) -> None:
151 """Test :attr:`colour.utilities.network.TreeNode.data` property."""
153 assert self._node_a.data is self._data
155 def test__str__(self) -> None:
156 """Test :attr:`colour.utilities.network.TreeNode.__str__` method."""
158 assert "TreeNode#" in str(self._node_a)
159 assert "{'John': 'Doe'})" in str(self._node_a)
161 def test__len__(self) -> None:
162 """Test :attr:`colour.utilities.network.TreeNode.__len__` method."""
164 assert len(self._node_a) == 7
166 def test_is_root(self) -> None:
167 """Test :attr:`colour.utilities.network.TreeNode.is_root` method."""
169 assert self._node_a.is_root()
170 assert not self._node_b.is_root()
171 assert not self._node_c.is_root()
172 assert not self._node_h.is_root()
174 def test_is_inner(self) -> None:
175 """Test :attr:`colour.utilities.network.TreeNode.is_inner` method."""
177 assert not self._node_a.is_inner()
178 assert self._node_b.is_inner()
179 assert not self._node_c.is_inner()
180 assert not self._node_h.is_inner()
182 def test_is_leaf(self) -> None:
183 """Test :attr:`colour.utilities.network.TreeNode.is_leaf` method."""
185 assert not self._node_a.is_leaf()
186 assert not self._node_b.is_leaf()
187 assert self._node_c.is_leaf()
188 assert self._node_h.is_leaf()
190 def test_walk_hierarchy(self) -> None:
191 """Test :attr:`colour.utilities.network.TreeNode.walk_hierarchy` method."""
193 assert list(self._node_a.walk_hierarchy()) == [
194 self._node_b,
195 self._node_d,
196 self._node_f,
197 self._node_g,
198 self._node_h,
199 self._node_e,
200 self._node_c,
201 ]
203 assert list(self._node_h.walk_hierarchy(ascendants=True)) == [
204 self._node_g,
205 self._node_f,
206 self._node_d,
207 self._node_b,
208 self._node_a,
209 ]
211 def test_render(self) -> None:
212 """Test :attr:`colour.utilities.network.TreeNode.render` method."""
214 assert isinstance(self._node_a.render(), str)
217class TestPort:
218 """
219 Define :class:`colour.utilities.network.Port` class unit tests methods.
220 """
222 def setup_method(self) -> None:
223 """Initialise the common tests attributes."""
225 class Node(PortNode): ...
227 self._node_a = Node("Node A")
228 self._port_a_node_a = self._node_a.add_input_port("a", 1, "Port A")
229 self._port_b_node_a = self._node_a.add_input_port("b")
230 self._port_output_node_a = self._node_a.add_output_port(
231 "output", description="Output"
232 )
234 self._node_b = Node("Node B")
235 self._port_a_node_b = self._node_b.add_input_port("a", 1, "Port A")
236 self._port_b_node_b = self._node_b.add_input_port("b")
237 self._port_output_node_b = self._node_b.add_output_port(
238 "output", description="Output"
239 )
241 self._ports = [
242 self._port_a_node_a,
243 self._port_b_node_a,
244 self._port_output_node_a,
245 self._port_a_node_b,
246 self._port_b_node_b,
247 self._port_output_node_b,
248 ]
250 def test_required_attributes(self) -> None:
251 """Test the presence of required attributes."""
253 required_attributes = (
254 "name",
255 "value",
256 "description",
257 "node",
258 "connections",
259 )
261 for attribute in required_attributes:
262 assert attribute in dir(Port)
264 def test_required_methods(self) -> None:
265 """Test the presence of required methods."""
267 required_methods = (
268 "__init__",
269 "__str__",
270 "is_input_port",
271 "is_output_port",
272 "connect",
273 "disconnect",
274 "to_graphviz",
275 )
277 for method in required_methods:
278 assert method in dir(Port)
280 def test_name(self) -> None:
281 """Test :attr:`colour.utilities.network.Port.name` property."""
283 assert self._port_a_node_a.name == "a"
284 assert self._port_b_node_a.name == "b"
285 assert self._port_output_node_a.name == "output"
286 assert self._port_a_node_b.name == "a"
287 assert self._port_b_node_b.name == "b"
288 assert self._port_output_node_b.name == "output"
290 def test_value(self) -> None:
291 """Test :attr:`colour.utilities.network.Port.value` property."""
293 assert self._port_a_node_a.value == 1
294 assert self._port_b_node_a.value is None
295 assert self._port_output_node_a.value is None
296 assert self._port_a_node_b.value == 1
297 assert self._port_b_node_b.value is None
298 assert self._port_output_node_b.value is None
300 self._port_output_node_a.connect(self._port_a_node_b)
301 self._port_output_node_a.connect(self._port_b_node_b)
303 self._port_output_node_a.value = 2
304 assert self._port_output_node_a.node is not None
305 assert self._port_output_node_a.node.dirty is True
306 assert self._port_a_node_b.node is not None
307 assert self._port_a_node_b.node.dirty is True
308 assert self._port_a_node_b.value == 2
309 assert self._port_b_node_b.value == 2
311 self._port_a_node_b.value = 3
312 assert self._port_a_node_b.node is not None
313 assert self._port_a_node_b.node.dirty is True
314 assert self._port_output_node_a.node is not None
315 assert self._port_output_node_a.node.dirty is True
316 assert self._port_output_node_a.value == 3
317 assert self._port_b_node_b.value == 3
319 self._port_output_node_a.disconnect(self._port_a_node_b)
321 self._port_output_node_a.value = 2
322 self._port_output_node_a.node.process()
323 assert self._port_output_node_a.node is not None
324 assert self._port_output_node_a.node.dirty is False
325 assert self._port_a_node_b.node.dirty is True
326 assert self._port_a_node_b.value == 3
327 assert self._port_b_node_b.value == 2
329 self._port_output_node_a.disconnect(self._port_b_node_b)
331 def test_description(self) -> None:
332 """Test :attr:`colour.utilities.network.Port.description` property."""
334 assert self._port_a_node_a.description == "Port A"
335 assert self._port_b_node_a.description == ""
336 assert self._port_output_node_a.description == "Output"
337 assert self._port_a_node_b.description == "Port A"
338 assert self._port_b_node_b.description == ""
339 assert self._port_output_node_b.description == "Output"
341 def test_node(self) -> None:
342 """Test :attr:`colour.utilities.network.Port.node` property."""
344 assert self._port_a_node_a.node is self._node_a
345 assert self._port_a_node_b.node is self._node_b
347 port = Port("a", 1, "Port A Description")
349 assert port.node is None
351 def test_connections(self) -> None:
352 """Test :attr:`colour.utilities.network.Port.connections` property."""
354 for port in self._ports:
355 assert len(port.connections) == 0
357 self._port_output_node_a.connect(self._port_a_node_b)
358 self._port_output_node_a.connect(self._port_b_node_b)
360 assert len(self._port_output_node_a.connections) == 2
361 assert len(self._port_a_node_b.connections) == 1
362 assert len(self._port_b_node_b.connections) == 1
364 self._port_output_node_a.disconnect(self._port_a_node_b)
365 self._port_output_node_a.disconnect(self._port_b_node_b)
367 for port in self._ports:
368 assert len(port.connections) == 0
370 def test___str__(self) -> None:
371 """Test :meth:`colour.utilities.network.Port.__str__` method."""
373 assert str(self._port_a_node_a) == "Node A.a (<- [])"
374 assert str(self._port_b_node_a) == "Node A.b (<- [])"
375 assert str(self._port_output_node_a) == "Node A.output (-> [])"
377 assert str(self._port_a_node_b) == "Node B.a (<- [])"
378 assert str(self._port_b_node_b) == "Node B.b (<- [])"
379 assert str(self._port_output_node_b) == "Node B.output (-> [])"
381 self._port_output_node_a.connect(self._port_a_node_b)
382 self._port_output_node_a.connect(self._port_b_node_b)
384 assert str(self._port_a_node_a) == "Node A.a (<- [])"
385 assert str(self._port_b_node_a) == "Node A.b (<- [])"
386 assert (
387 str(self._port_output_node_a)
388 == "Node A.output (-> ['Node B.a', 'Node B.b'])"
389 )
391 assert str(self._port_a_node_b) == "Node B.a (<- ['Node A.output'])"
392 assert str(self._port_b_node_b) == "Node B.b (<- ['Node A.output'])"
393 assert str(self._port_output_node_b) == "Node B.output (-> [])"
395 self._port_output_node_a.disconnect(self._port_a_node_b)
396 self._port_output_node_a.disconnect(self._port_b_node_b)
398 def test_is_input_port(self) -> None:
399 """Test :meth:`colour.utilities.network.Port.is_input_port` method."""
401 assert self._port_a_node_a.is_input_port() is True
402 assert self._port_b_node_a.is_input_port() is True
403 assert self._port_output_node_a.is_input_port() is False
405 assert self._port_a_node_b.is_input_port() is True
406 assert self._port_b_node_b.is_input_port() is True
407 assert self._port_output_node_b.is_input_port() is False
409 def test_is_output_port(self) -> None:
410 """Test :meth:`colour.utilities.network.Port.is_output_port` method."""
412 assert self._port_a_node_a.is_output_port() is False
413 assert self._port_b_node_a.is_output_port() is False
414 assert self._port_output_node_a.is_output_port() is True
416 assert self._port_a_node_b.is_output_port() is False
417 assert self._port_b_node_b.is_output_port() is False
418 assert self._port_output_node_b.is_output_port() is True
420 def test_connect(self) -> None:
421 """Test :meth:`colour.utilities.network.Port.connect` method."""
423 self.test_connections()
425 def test_disconnect(self) -> None:
426 """Test :meth:`colour.utilities.network.Port.disconnect` method."""
428 self.test_connections()
430 def test_to_graphviz(self) -> None:
431 """Test :meth:`colour.utilities.network.Port.test_to_graphviz` method."""
433 assert self._port_a_node_a.to_graphviz() == "<a> a"
434 assert self._port_b_node_a.to_graphviz() == "<b> b"
435 assert self._port_output_node_a.to_graphviz() == "<output> output"
437 assert self._port_a_node_b.to_graphviz() == "<a> a"
438 assert self._port_b_node_b.to_graphviz() == "<b> b"
439 assert self._port_output_node_b.to_graphviz() == "<output> output"
442class _NodeAdd(ExecutionNode):
443 def __init__(self, *args: Any, **kwargs: Any) -> None:
444 super().__init__(*args, **kwargs)
446 self.description = "Perform the addition of the two input port values."
448 self.add_input_port("a")
449 self.add_input_port("b")
450 self.add_output_port("output")
452 def process(self) -> None:
453 a = self.get_input("a")
454 b = self.get_input("b")
456 if a is None or b is None:
457 return
459 self.set_output("output", a + b)
461 self.dirty = False
464class _NodeMultiply(ExecutionNode):
465 def __init__(self, *args: Any, **kwargs: Any) -> None:
466 super().__init__(*args, **kwargs)
468 self.description = "Perform the multiplication of the two input port values."
470 self.add_input_port("a")
471 self.add_input_port("b")
472 self.add_output_port("output")
474 def process(self) -> None:
475 a = self.get_input("a")
476 b = self.get_input("b")
478 if a is None or b is None:
479 return
481 self.set_output("output", a * b)
483 self.dirty = False
486class TestPortNode:
487 """
488 Define :class:`colour.utilities.network.PortNode` class unit tests methods.
489 """
491 def setup_method(self) -> None:
492 """Initialise the common tests attributes."""
494 self._add_node_1 = _NodeAdd("Node Add 1")
495 self._multiply_node_1 = _NodeMultiply("Node Multiply 1")
496 self._add_node_2 = _NodeAdd("Node Add 2")
498 self._nodes = [self._add_node_1, self._multiply_node_1, self._add_node_2]
500 def test_required_attributes(self) -> None:
501 """Test the presence of required attributes."""
503 required_attributes = (
504 "input_ports",
505 "output_ports",
506 "dirty",
507 "edges",
508 "description",
509 )
511 for attribute in required_attributes:
512 assert attribute in dir(PortNode)
514 def test_required_methods(self) -> None:
515 """Test the presence of required methods."""
517 required_methods = (
518 "__init__",
519 "add_input_port",
520 "remove_input_port",
521 "add_output_port",
522 "remove_output_port",
523 "get_input",
524 "set_input",
525 "get_output",
526 "set_output",
527 "connect",
528 "disconnect",
529 "process",
530 "to_graphviz",
531 )
533 for method in required_methods:
534 assert method in dir(PortNode)
536 def test_input_ports(self) -> None:
537 """Test :attr:`colour.utilities.network.PortNode.input_ports` property."""
539 for name in ("a", "b"):
540 assert name in self._add_node_1.input_ports
541 assert name in self._multiply_node_1.input_ports
542 assert name in self._add_node_2.input_ports
544 def test_output_ports(self) -> None:
545 """Test :attr:`colour.utilities.network.PortNode.output_ports` property."""
547 for name in ("output",):
548 assert name in self._add_node_1.output_ports
549 assert name in self._multiply_node_1.output_ports
550 assert name in self._add_node_2.output_ports
552 def test_dirty(self) -> None:
553 """Test :attr:`colour.utilities.network.PortNode.dirty` property."""
555 assert self._add_node_1.dirty is True
556 assert self._multiply_node_1.dirty is True
557 assert self._add_node_2.dirty is True
559 self._add_node_1.process()
560 self._multiply_node_1.process()
561 self._add_node_2.process()
563 assert self._add_node_1.dirty is True
564 assert self._multiply_node_1.dirty is True
565 assert self._add_node_2.dirty is True
567 self._add_node_1.set_input("a", 1)
568 self._add_node_1.set_input("b", 1)
569 self._multiply_node_1.set_input("a", 1)
570 self._multiply_node_1.set_input("b", 1)
571 self._add_node_2.set_input("a", 1)
572 self._add_node_2.set_input("b", 1)
574 self._add_node_1.process()
575 self._multiply_node_1.process()
576 self._add_node_2.process()
578 assert self._add_node_1.dirty is False
579 assert self._multiply_node_1.dirty is False
580 assert self._add_node_2.dirty is False
582 self._add_node_1.set_input("a", None)
583 self._add_node_1.set_input("b", None)
584 self._multiply_node_1.set_input("a", None)
585 self._multiply_node_1.set_input("b", None)
586 self._add_node_2.set_input("a", None)
587 self._add_node_2.set_input("b", None)
589 assert self._add_node_1.dirty is True
590 assert self._multiply_node_1.dirty is True
591 assert self._add_node_2.dirty is True
593 def test_edges(self) -> None:
594 """Test :attr:`colour.utilities.network.PortNode.edges` property."""
596 assert self._add_node_1.edges == ({}, {})
597 assert self._multiply_node_1.edges == ({}, {})
598 assert self._add_node_2.edges == ({}, {})
600 self._add_node_1.connect("output", self._multiply_node_1, "a")
601 self._multiply_node_1.connect("output", self._add_node_2, "a")
603 assert self._add_node_1.edges == (
604 {},
605 {
606 (
607 self._add_node_1.output_ports["output"],
608 self._multiply_node_1.input_ports["a"],
609 ): None,
610 },
611 )
612 assert self._multiply_node_1.edges == (
613 {
614 (
615 self._multiply_node_1.input_ports["a"],
616 self._add_node_1.output_ports["output"],
617 ): None,
618 },
619 {
620 (
621 self._multiply_node_1.output_ports["output"],
622 self._add_node_2.input_ports["a"],
623 ): None,
624 },
625 )
626 assert self._add_node_2.edges == (
627 {
628 (
629 self._add_node_2.input_ports["a"],
630 self._multiply_node_1.output_ports["output"],
631 ): None,
632 },
633 {},
634 )
636 self._add_node_1.disconnect("output", self._multiply_node_1, "a")
637 self._multiply_node_1.disconnect("output", self._add_node_2, "a")
639 assert self._add_node_1.edges == ({}, {})
640 assert self._multiply_node_1.edges == ({}, {})
641 assert self._add_node_2.edges == ({}, {})
643 def test_description(self) -> None:
644 """Test :attr:`colour.utilities.network.PortNode.description` property."""
646 assert (
647 self._add_node_1.description
648 == "Perform the addition of the two input port values."
649 )
650 assert (
651 self._multiply_node_1.description
652 == "Perform the multiplication of the two input port values."
653 )
654 assert (
655 self._add_node_2.description
656 == "Perform the addition of the two input port values."
657 )
659 def test_add_input_port(self) -> None:
660 """Test :meth:`colour.utilities.network.PortNode.add_input_port` method."""
662 node = PortNode()
663 node.add_input_port("a", 1, 'Input Port "a"')
665 assert node.input_ports["a"].value == 1
666 assert node.input_ports["a"].description == 'Input Port "a"'
668 def test_remove_input_port(self) -> None:
669 """Test :meth:`colour.utilities.network.PortNode.remove_input_port` method."""
671 node = PortNode()
672 node.add_input_port("a", 1, 'Input Port "a"')
673 node.remove_input_port("a")
675 assert len(node.input_ports) == 0
677 def test_add_output_port(self) -> None:
678 """Test :meth:`colour.utilities.network.PortNode.add_output_port` method."""
680 node = PortNode()
681 node.add_output_port("output", 1, 'Output Port "output"')
683 assert node.output_ports["output"].value == 1
684 assert node.output_ports["output"].description == 'Output Port "output"'
686 def test_remove_output_port(self) -> None:
687 """Test :meth:`colour.utilities.network.PortNode.remove_output_port` method."""
689 node = PortNode()
690 node.add_output_port("output", 1, 'Output Port "output"')
691 node.remove_output_port("output")
693 assert len(node.input_ports) == 0
695 def test_get_input(self) -> None:
696 """Test :meth:`colour.utilities.network.PortNode.get_input` method."""
698 node = PortNode()
699 node.add_input_port("a", 1, 'Input Port "a"')
701 assert node.get_input("a") == 1
703 def test_set_input(self) -> None:
704 """Test :meth:`colour.utilities.network.PortNode.set_input` method."""
706 node = PortNode()
707 node.add_input_port("a", 1, 'Input Port "a"')
709 assert node.input_ports["a"].value == 1
711 node.set_input("a", 2)
713 assert node.input_ports["a"].value == 2
715 def test_get_output(self) -> None:
716 """Test :meth:`colour.utilities.network.PortNode.get_output` method."""
718 node = PortNode()
719 node.add_output_port("output", 1, 'Output Port "output"')
721 assert node.get_output("output") == 1
723 def test_set_output(self) -> None:
724 """Test :meth:`colour.utilities.network.PortNode.set_output` method."""
726 node = PortNode()
727 node.add_output_port("output", 1, 'Output Port "output"')
729 assert node.output_ports["output"].value == 1
731 node.set_output("output", 2)
733 assert node.output_ports["output"].value == 2
735 def test_connect(self) -> None:
736 """Test :meth:`colour.utilities.network.PortNode.connect` method."""
738 self.test_edges()
740 def test_disconnect(self) -> None:
741 """Test :meth:`colour.utilities.network.PortNode.disconnect` method."""
743 self.test_edges()
745 def test_process(self) -> None:
746 """Test :meth:`colour.utilities.network.PortNode.process` method."""
748 self._add_node_1.connect("output", self._multiply_node_1, "a")
749 self._multiply_node_1.connect("output", self._add_node_2, "a")
751 self._add_node_1.set_input("a", 1)
752 self._add_node_1.set_input("b", 1)
753 self._multiply_node_1.set_input("b", 2)
754 self._add_node_2.set_input("b", 1)
756 assert self._add_node_2.get_output("output") is None
758 self._add_node_1.process()
759 self._multiply_node_1.process()
760 self._add_node_2.process()
762 assert self._add_node_2.get_output("output") == 5
764 self._add_node_1.disconnect("output", self._multiply_node_1, "a")
765 self._multiply_node_1.disconnect("output", self._add_node_2, "a")
767 def test_to_graphviz(self) -> None:
768 """Test :meth:`colour.utilities.network.PortNode.to_graphviz` method."""
770 assert (
771 re.sub(r"\(#\d+\)", "(#)", self._add_node_1.to_graphviz())
772 == "Node Add 1 (#) | {{<execution_input> execution_input|<a> "
773 "a|<b> b} | {<execution_output> execution_output|<output> output}}"
774 )
775 assert (
776 re.sub(r"\(#\d+\)", "(#)", self._multiply_node_1.to_graphviz())
777 == "Node Multiply 1 (#) | {{<execution_input> execution_input|<a> "
778 "a|<b> b} | {<execution_output> execution_output|<output> output}}"
779 )
780 assert (
781 re.sub(r"\(#\d+\)", "(#)", self._add_node_2.to_graphviz())
782 == "Node Add 2 (#) | {{<execution_input> execution_input|<a> "
783 "a|<b> b} | {<execution_output> execution_output|<output> output}}"
784 )
787class TestPortGraph:
788 """
789 Define :class:`colour.utilities.network.PortGraph` class unit tests methods.
790 """
792 def setup_method(self) -> None:
793 """Initialise the common tests attributes."""
795 self._add_node_1 = _NodeAdd("Node Add 1")
796 self._multiply_node_1 = _NodeMultiply("Node Multiply 1")
797 self._add_node_2 = _NodeAdd("Node Add 2")
799 self._add_node_1.connect("output", self._multiply_node_1, "a")
800 self._multiply_node_1.connect("output", self._add_node_2, "a")
802 self._add_node_1.set_input("a", 1)
803 self._add_node_1.set_input("b", 1)
804 self._multiply_node_1.set_input("b", 2)
805 self._add_node_2.set_input("b", 1)
807 self._nodes = {
808 self._add_node_1.name: self._add_node_1,
809 self._multiply_node_1.name: self._multiply_node_1,
810 self._add_node_2.name: self._add_node_2,
811 }
813 self._graph = PortGraph("Port Graph")
815 for node in self._nodes.values():
816 self._graph.add_node(node)
818 def test_required_attributes(self) -> None:
819 """Test the presence of required attributes."""
821 required_attributes = ("nodes",)
823 for attribute in required_attributes:
824 assert attribute in dir(PortGraph)
826 def test_required_methods(self) -> None:
827 """Test the presence of required methods."""
829 required_methods = (
830 "__init__",
831 "__str__",
832 "add_node",
833 "remove_node",
834 "walk_ports",
835 "process",
836 "to_graphviz",
837 )
839 for method in required_methods:
840 assert method in dir(PortGraph)
842 def test_nodes(self) -> None:
843 """Test :attr:`colour.utilities.network.PortGraph.nodes` property."""
845 assert self._graph.nodes == self._nodes
847 def test___str__(self) -> None:
848 """Test :meth:`colour.utilities.network.PortGraph.__str__` method."""
850 assert str(self._graph) == "PortGraph(3)"
852 def test_add_node(self) -> None:
853 """Test :meth:`colour.utilities.network.PortGraph.add_node` method."""
855 for node in self._nodes.values():
856 self._graph.remove_node(node)
858 assert len(self._graph.nodes) == 0
860 for node in self._nodes.values():
861 self._graph.add_node(node)
863 assert len(self._graph.nodes) == 3
865 def test_remove_node(self) -> None:
866 """Test :meth:`colour.utilities.network.PortGraph.remove_node` method."""
868 self.test_add_node()
870 def test_walk_ports(self) -> None:
871 """Test :meth:`colour.utilities.network.PortGraph.walk_ports` method."""
873 assert list(self._graph.walk_ports()) == list(self._nodes.values())
875 def test_process(self) -> None:
876 """Test :meth:`colour.utilities.network.PortGraph.process` method."""
878 self._graph.process()
880 assert self._add_node_2.get_output("output") == 5
882 def test_to_graphviz(self) -> None:
883 """Test :meth:`colour.utilities.network.PortGraph.to_graphviz` method."""
885 if not is_pydot_installed(): # pragma: no cover
886 return
888 import pydot # noqa: PLC0415
890 assert isinstance(self._graph.to_graphviz(), pydot.Dot)
893class _AddItem(ExecutionNode):
894 def __init__(self, *args: Any, **kwargs: Any) -> None:
895 super().__init__(*args, **kwargs)
897 self.description = "Add the item with input key and value to the input mapping."
899 self.add_input_port("key")
900 self.add_input_port("value")
901 self.add_input_port("mapping", {})
903 def process(self) -> None:
904 """
905 Process the node.
906 """
908 key = self.get_input("key")
909 value = self.get_input("value")
911 if key is None or value is None: # pragma: no cover
912 return
914 self.get_input("mapping")[key] = value
916 self.dirty = False
919class _NodeSumMappingValues(ExecutionNode):
920 def __init__(self, *args: Any, **kwargs: Any) -> None:
921 super().__init__(*args, **kwargs)
923 self.description = "Sum the input mapping values."
925 self.add_input_port("mapping", {})
926 self.add_output_port("summation")
928 def process(self) -> None:
929 mapping = self.get_input("mapping")
930 if len(mapping) == 0: # pragma: no cover
931 return
933 self.set_output("summation", np.sum(list(mapping.values())))
935 self.dirty = False
938class _SubGraph1(ExecutionNode, PortGraph):
939 def __init__(self, *args: Any, **kwargs: Any) -> None:
940 super().__init__(*args, **kwargs)
942 self.add_input_port("input")
943 self.add_output_port("output", {})
945 for node in [
946 _NodeAdd("Add 1"),
947 _NodeMultiply("Multiply 1"),
948 _NodeAdd("Add 2"),
949 _AddItem("Add Item"),
950 ]:
951 self.add_node(node)
953 for connection in [
954 (
955 ("Add 1", "output"),
956 ("Multiply 1", "a"),
957 ),
958 (
959 ("Add 1", "execution_output"),
960 ("Multiply 1", "execution_input"),
961 ),
962 (
963 ("Multiply 1", "output"),
964 ("Add 2", "a"),
965 ),
966 (
967 ("Multiply 1", "execution_output"),
968 ("Add 2", "execution_input"),
969 ),
970 (
971 ("Add 2", "execution_output"),
972 ("Add Item", "execution_input"),
973 ),
974 (
975 ("Add 2", "output"),
976 ("Add Item", "value"),
977 ),
978 ]:
979 (input_node, input_port), (output_node, output_port) = connection
980 self.nodes[input_node].connect(
981 input_port,
982 self.nodes[output_node],
983 output_port,
984 )
986 self.connect("input", self.nodes["Add 1"], "b")
987 self.connect("input", self.nodes["Add Item"], "key")
988 self.nodes["Add Item"].connect("mapping", self, "output")
990 def process(self, **kwargs: Any) -> None:
991 # Coverage can't track execution in subprocesses # pragma: no cover
992 self.nodes["Add 1"].set_input("a", 1) # pragma: no cover
993 self.nodes["Multiply 1"].set_input("b", 2) # pragma: no cover
994 self.nodes["Add 2"].set_input("b", 3) # pragma: no cover
995 super().process(**kwargs) # pragma: no cover
998class TestFor:
999 """
1000 Define :class:`colour.utilities.network.For` class unit tests methods.
1001 """
1003 def test_For(self) -> None:
1004 """Test :class:`colour.utilities.network.For` class."""
1006 sum_mapping_values = _NodeSumMappingValues()
1008 sub_graph = _SubGraph1()
1009 sub_graph.connect("output", sum_mapping_values, "mapping")
1011 loop = For()
1012 loop.connect("loop_output", sub_graph, "execution_input")
1013 loop.connect("index", sub_graph, "input")
1014 loop.connect("execution_output", sum_mapping_values, "execution_input")
1015 loop.set_input("array", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
1016 loop.process()
1018 assert sum_mapping_values.get_output("summation") == 140
1021class _NodeSumArray(ExecutionNode):
1022 def __init__(self, *args: Any, **kwargs: Any) -> None:
1023 super().__init__(*args, **kwargs)
1025 self.description = "Sum the input array."
1027 self.add_input_port("array", [])
1028 self.add_output_port("summation")
1030 def process(self) -> None:
1031 array = self.get_input("array")
1032 if len(array) == 0: # pragma: no cover
1033 return
1035 self.set_output("summation", np.sum(array))
1037 self.dirty = False
1040class _SubGraph2(ExecutionNode, PortGraph):
1041 def __init__(self, *args: Any, **kwargs: Any) -> None:
1042 super().__init__(*args, **kwargs)
1044 self.add_input_port("input")
1045 self.add_output_port("output")
1047 for node in [
1048 _NodeAdd("Add 1"),
1049 _NodeMultiply("Multiply 1"),
1050 _NodeAdd("Add 2"),
1051 ]:
1052 self.add_node(node)
1054 for connection in [
1055 (
1056 ("Add 1", "output"),
1057 ("Multiply 1", "a"),
1058 ),
1059 (
1060 ("Add 1", "execution_output"),
1061 ("Multiply 1", "execution_input"),
1062 ),
1063 (
1064 ("Multiply 1", "output"),
1065 ("Add 2", "a"),
1066 ),
1067 (
1068 ("Multiply 1", "execution_output"),
1069 ("Add 2", "execution_input"),
1070 ),
1071 ]:
1072 (input_node, input_port), (output_node, output_port) = connection
1073 self.nodes[input_node].connect(
1074 input_port,
1075 self.nodes[output_node],
1076 output_port,
1077 )
1079 self.connect("input", self.nodes["Add 1"], "b")
1080 self.nodes["Add 2"].connect("output", self, "output")
1082 def process(self, **kwargs: Any) -> None: # pragma: no cover
1083 # Coverage can't track execution in subprocesses
1085 self.nodes["Add 1"].set_input("a", 1)
1086 self.nodes["Multiply 1"].set_input("b", 2)
1087 self.nodes["Add 2"].set_input("b", 3)
1089 super().process(**kwargs)
1092class TestThreadPoolExecutorManager:
1093 """
1094 Define :class:`colour.utilities.network.ThreadPoolExecutorManager` class unit tests
1095 methods.
1096 """
1098 def test_ThreadPoolExecutorManager(self) -> None:
1099 """Test :class:`colour.utilities.network.ThreadPoolExecutorManager` class."""
1101 executor = ThreadPoolExecutorManager.get_executor()
1103 assert executor is not None
1106class TestParallelForThread:
1107 """
1108 Define :class:`colour.utilities.network.ParallelForThread` class unit tests
1109 methods.
1110 """
1112 def test_ParallelForThread(self) -> None:
1113 """Test :class:`colour.utilities.network.ParallelForThread` class."""
1115 sum_array = _NodeSumArray()
1117 sub_graph = _SubGraph2()
1119 loop = ParallelForThread()
1120 loop.connect("loop_output", sub_graph, "execution_input")
1121 loop.connect("index", sub_graph, "input")
1122 loop.connect("execution_output", sum_array, "execution_input")
1123 loop.set_input("array", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
1124 loop.connect("results", sum_array, "array")
1125 loop.process()
1127 assert sum_array.get_output("summation") == 140
1130class TestProcessPoolExecutorManager:
1131 """
1132 Define :class:`colour.utilities.network.ProcessPoolExecutorManager` class unit tests
1133 methods.
1134 """
1136 def test_ProcessPoolExecutorManager(self) -> None:
1137 """Test :class:`colour.utilities.network.ProcessPoolExecutorManager` class."""
1139 executor = ProcessPoolExecutorManager.get_executor()
1141 assert executor is not None
1144class TestParallelForMultiProcess:
1145 """
1146 Define :class:`colour.utilities.network.ParallelForMultiProcess` class unit
1147 tests methods.
1148 """
1150 def test_ParallelForMultiProcess(self) -> None:
1151 """Test :class:`colour.utilities.network.ParallelForMultiProcess` class."""
1153 sum_array = _NodeSumArray()
1155 sub_graph = _SubGraph2()
1157 loop = ParallelForMultiprocess()
1158 loop.connect("loop_output", sub_graph, "execution_input")
1159 loop.connect("index", sub_graph, "input")
1160 loop.connect("execution_output", sum_array, "execution_input")
1161 loop.set_input("array", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
1162 loop.connect("results", sum_array, "array")
1163 loop.process()
1165 assert sum_array.get_output("summation") == 140