Coverage for colour/utilities/tests/test_network.py: 100%

515 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 23:01 +1300

1"""Define the unit tests for the :mod:`colour.utilities.network` module.""" 

2 

3from __future__ import annotations 

4 

5import re 

6import typing 

7 

8import numpy as np 

9 

10if typing.TYPE_CHECKING: 

11 from colour.hints import Any 

12 

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) 

28 

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" 

35 

36__all__ = [ 

37 "TestTreeNode", 

38 "TestPort", 

39 "TestPortNode", 

40 "TestPortGraph", 

41 "TestFor", 

42 "TestThreadPoolExecutorManager", 

43 "TestParallelForThread", 

44 "TestProcessPoolExecutorManager", 

45 "TestParallelForMultiProcess", 

46] 

47 

48 

49class TestTreeNode: 

50 """ 

51 Define :class:`colour.utilities.network.TreeNode` class unit tests 

52 methods. 

53 """ 

54 

55 def setup_method(self) -> None: 

56 """Initialise the common tests attributes.""" 

57 

58 self._data = {"John": "Doe"} 

59 

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) 

68 

69 self._tree = self._node_a 

70 

71 def test_required_attributes(self) -> None: 

72 """Test the presence of required attributes.""" 

73 

74 required_attributes = ( 

75 "id", 

76 "name", 

77 "parent", 

78 "children", 

79 "root", 

80 "leaves", 

81 "siblings", 

82 "data", 

83 ) 

84 

85 for attribute in required_attributes: 

86 assert attribute in dir(TreeNode) 

87 

88 def test_required_methods(self) -> None: 

89 """Test the presence of required methods.""" 

90 

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 ) 

102 

103 for method in required_methods: 

104 assert method in dir(TreeNode) 

105 

106 def test_name(self) -> None: 

107 """Test :attr:`colour.utilities.network.TreeNode.name` property.""" 

108 

109 assert self._tree.name == "Node A" 

110 assert "Node#" in TreeNode().name 

111 

112 def test_parent(self) -> None: 

113 """Test :attr:`colour.utilities.network.TreeNode.parent` property.""" 

114 

115 assert self._node_b.parent is self._node_a 

116 assert self._node_h.parent is self._node_g 

117 

118 def test_children(self) -> None: 

119 """Test :attr:`colour.utilities.network.TreeNode.children` property.""" 

120 

121 assert self._node_a.children == [self._node_b, self._node_c] 

122 

123 def test_id(self) -> None: 

124 """Test :attr:`colour.utilities.network.TreeNode.id` property.""" 

125 

126 assert isinstance(self._node_a.id, int) 

127 

128 def test_root(self) -> None: 

129 """Test :attr:`colour.utilities.network.TreeNode.root` property.""" 

130 

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 

135 

136 def test_leaves(self) -> None: 

137 """Test :attr:`colour.utilities.network.TreeNode.leaves` property.""" 

138 

139 assert list(self._node_h.leaves) == [self._node_h] 

140 

141 assert list(self._node_a.leaves) == [self._node_h, self._node_e, self._node_c] 

142 

143 def test_siblings(self) -> None: 

144 """Test :attr:`colour.utilities.network.TreeNode.siblings` property.""" 

145 

146 assert list(self._node_a.siblings) == [] 

147 

148 assert list(self._node_b.siblings) == [self._node_c] 

149 

150 def test_data(self) -> None: 

151 """Test :attr:`colour.utilities.network.TreeNode.data` property.""" 

152 

153 assert self._node_a.data is self._data 

154 

155 def test__str__(self) -> None: 

156 """Test :attr:`colour.utilities.network.TreeNode.__str__` method.""" 

157 

158 assert "TreeNode#" in str(self._node_a) 

159 assert "{'John': 'Doe'})" in str(self._node_a) 

160 

161 def test__len__(self) -> None: 

162 """Test :attr:`colour.utilities.network.TreeNode.__len__` method.""" 

163 

164 assert len(self._node_a) == 7 

165 

166 def test_is_root(self) -> None: 

167 """Test :attr:`colour.utilities.network.TreeNode.is_root` method.""" 

168 

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() 

173 

174 def test_is_inner(self) -> None: 

175 """Test :attr:`colour.utilities.network.TreeNode.is_inner` method.""" 

176 

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() 

181 

182 def test_is_leaf(self) -> None: 

183 """Test :attr:`colour.utilities.network.TreeNode.is_leaf` method.""" 

184 

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() 

189 

190 def test_walk_hierarchy(self) -> None: 

191 """Test :attr:`colour.utilities.network.TreeNode.walk_hierarchy` method.""" 

192 

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 ] 

202 

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 ] 

210 

211 def test_render(self) -> None: 

212 """Test :attr:`colour.utilities.network.TreeNode.render` method.""" 

213 

214 assert isinstance(self._node_a.render(), str) 

215 

216 

217class TestPort: 

218 """ 

219 Define :class:`colour.utilities.network.Port` class unit tests methods. 

220 """ 

221 

222 def setup_method(self) -> None: 

223 """Initialise the common tests attributes.""" 

224 

225 class Node(PortNode): ... 

226 

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 ) 

233 

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 ) 

240 

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 ] 

249 

250 def test_required_attributes(self) -> None: 

251 """Test the presence of required attributes.""" 

252 

253 required_attributes = ( 

254 "name", 

255 "value", 

256 "description", 

257 "node", 

258 "connections", 

259 ) 

260 

261 for attribute in required_attributes: 

262 assert attribute in dir(Port) 

263 

264 def test_required_methods(self) -> None: 

265 """Test the presence of required methods.""" 

266 

267 required_methods = ( 

268 "__init__", 

269 "__str__", 

270 "is_input_port", 

271 "is_output_port", 

272 "connect", 

273 "disconnect", 

274 "to_graphviz", 

275 ) 

276 

277 for method in required_methods: 

278 assert method in dir(Port) 

279 

280 def test_name(self) -> None: 

281 """Test :attr:`colour.utilities.network.Port.name` property.""" 

282 

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" 

289 

290 def test_value(self) -> None: 

291 """Test :attr:`colour.utilities.network.Port.value` property.""" 

292 

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 

299 

300 self._port_output_node_a.connect(self._port_a_node_b) 

301 self._port_output_node_a.connect(self._port_b_node_b) 

302 

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 

310 

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 

318 

319 self._port_output_node_a.disconnect(self._port_a_node_b) 

320 

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 

328 

329 self._port_output_node_a.disconnect(self._port_b_node_b) 

330 

331 def test_description(self) -> None: 

332 """Test :attr:`colour.utilities.network.Port.description` property.""" 

333 

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" 

340 

341 def test_node(self) -> None: 

342 """Test :attr:`colour.utilities.network.Port.node` property.""" 

343 

344 assert self._port_a_node_a.node is self._node_a 

345 assert self._port_a_node_b.node is self._node_b 

346 

347 port = Port("a", 1, "Port A Description") 

348 

349 assert port.node is None 

350 

351 def test_connections(self) -> None: 

352 """Test :attr:`colour.utilities.network.Port.connections` property.""" 

353 

354 for port in self._ports: 

355 assert len(port.connections) == 0 

356 

357 self._port_output_node_a.connect(self._port_a_node_b) 

358 self._port_output_node_a.connect(self._port_b_node_b) 

359 

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 

363 

364 self._port_output_node_a.disconnect(self._port_a_node_b) 

365 self._port_output_node_a.disconnect(self._port_b_node_b) 

366 

367 for port in self._ports: 

368 assert len(port.connections) == 0 

369 

370 def test___str__(self) -> None: 

371 """Test :meth:`colour.utilities.network.Port.__str__` method.""" 

372 

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 (-> [])" 

376 

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 (-> [])" 

380 

381 self._port_output_node_a.connect(self._port_a_node_b) 

382 self._port_output_node_a.connect(self._port_b_node_b) 

383 

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 ) 

390 

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 (-> [])" 

394 

395 self._port_output_node_a.disconnect(self._port_a_node_b) 

396 self._port_output_node_a.disconnect(self._port_b_node_b) 

397 

398 def test_is_input_port(self) -> None: 

399 """Test :meth:`colour.utilities.network.Port.is_input_port` method.""" 

400 

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 

404 

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 

408 

409 def test_is_output_port(self) -> None: 

410 """Test :meth:`colour.utilities.network.Port.is_output_port` method.""" 

411 

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 

415 

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 

419 

420 def test_connect(self) -> None: 

421 """Test :meth:`colour.utilities.network.Port.connect` method.""" 

422 

423 self.test_connections() 

424 

425 def test_disconnect(self) -> None: 

426 """Test :meth:`colour.utilities.network.Port.disconnect` method.""" 

427 

428 self.test_connections() 

429 

430 def test_to_graphviz(self) -> None: 

431 """Test :meth:`colour.utilities.network.Port.test_to_graphviz` method.""" 

432 

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" 

436 

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" 

440 

441 

442class _NodeAdd(ExecutionNode): 

443 def __init__(self, *args: Any, **kwargs: Any) -> None: 

444 super().__init__(*args, **kwargs) 

445 

446 self.description = "Perform the addition of the two input port values." 

447 

448 self.add_input_port("a") 

449 self.add_input_port("b") 

450 self.add_output_port("output") 

451 

452 def process(self) -> None: 

453 a = self.get_input("a") 

454 b = self.get_input("b") 

455 

456 if a is None or b is None: 

457 return 

458 

459 self.set_output("output", a + b) 

460 

461 self.dirty = False 

462 

463 

464class _NodeMultiply(ExecutionNode): 

465 def __init__(self, *args: Any, **kwargs: Any) -> None: 

466 super().__init__(*args, **kwargs) 

467 

468 self.description = "Perform the multiplication of the two input port values." 

469 

470 self.add_input_port("a") 

471 self.add_input_port("b") 

472 self.add_output_port("output") 

473 

474 def process(self) -> None: 

475 a = self.get_input("a") 

476 b = self.get_input("b") 

477 

478 if a is None or b is None: 

479 return 

480 

481 self.set_output("output", a * b) 

482 

483 self.dirty = False 

484 

485 

486class TestPortNode: 

487 """ 

488 Define :class:`colour.utilities.network.PortNode` class unit tests methods. 

489 """ 

490 

491 def setup_method(self) -> None: 

492 """Initialise the common tests attributes.""" 

493 

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") 

497 

498 self._nodes = [self._add_node_1, self._multiply_node_1, self._add_node_2] 

499 

500 def test_required_attributes(self) -> None: 

501 """Test the presence of required attributes.""" 

502 

503 required_attributes = ( 

504 "input_ports", 

505 "output_ports", 

506 "dirty", 

507 "edges", 

508 "description", 

509 ) 

510 

511 for attribute in required_attributes: 

512 assert attribute in dir(PortNode) 

513 

514 def test_required_methods(self) -> None: 

515 """Test the presence of required methods.""" 

516 

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 ) 

532 

533 for method in required_methods: 

534 assert method in dir(PortNode) 

535 

536 def test_input_ports(self) -> None: 

537 """Test :attr:`colour.utilities.network.PortNode.input_ports` property.""" 

538 

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 

543 

544 def test_output_ports(self) -> None: 

545 """Test :attr:`colour.utilities.network.PortNode.output_ports` property.""" 

546 

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 

551 

552 def test_dirty(self) -> None: 

553 """Test :attr:`colour.utilities.network.PortNode.dirty` property.""" 

554 

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 

558 

559 self._add_node_1.process() 

560 self._multiply_node_1.process() 

561 self._add_node_2.process() 

562 

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 

566 

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) 

573 

574 self._add_node_1.process() 

575 self._multiply_node_1.process() 

576 self._add_node_2.process() 

577 

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 

581 

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) 

588 

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 

592 

593 def test_edges(self) -> None: 

594 """Test :attr:`colour.utilities.network.PortNode.edges` property.""" 

595 

596 assert self._add_node_1.edges == ({}, {}) 

597 assert self._multiply_node_1.edges == ({}, {}) 

598 assert self._add_node_2.edges == ({}, {}) 

599 

600 self._add_node_1.connect("output", self._multiply_node_1, "a") 

601 self._multiply_node_1.connect("output", self._add_node_2, "a") 

602 

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 ) 

635 

636 self._add_node_1.disconnect("output", self._multiply_node_1, "a") 

637 self._multiply_node_1.disconnect("output", self._add_node_2, "a") 

638 

639 assert self._add_node_1.edges == ({}, {}) 

640 assert self._multiply_node_1.edges == ({}, {}) 

641 assert self._add_node_2.edges == ({}, {}) 

642 

643 def test_description(self) -> None: 

644 """Test :attr:`colour.utilities.network.PortNode.description` property.""" 

645 

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 ) 

658 

659 def test_add_input_port(self) -> None: 

660 """Test :meth:`colour.utilities.network.PortNode.add_input_port` method.""" 

661 

662 node = PortNode() 

663 node.add_input_port("a", 1, 'Input Port "a"') 

664 

665 assert node.input_ports["a"].value == 1 

666 assert node.input_ports["a"].description == 'Input Port "a"' 

667 

668 def test_remove_input_port(self) -> None: 

669 """Test :meth:`colour.utilities.network.PortNode.remove_input_port` method.""" 

670 

671 node = PortNode() 

672 node.add_input_port("a", 1, 'Input Port "a"') 

673 node.remove_input_port("a") 

674 

675 assert len(node.input_ports) == 0 

676 

677 def test_add_output_port(self) -> None: 

678 """Test :meth:`colour.utilities.network.PortNode.add_output_port` method.""" 

679 

680 node = PortNode() 

681 node.add_output_port("output", 1, 'Output Port "output"') 

682 

683 assert node.output_ports["output"].value == 1 

684 assert node.output_ports["output"].description == 'Output Port "output"' 

685 

686 def test_remove_output_port(self) -> None: 

687 """Test :meth:`colour.utilities.network.PortNode.remove_output_port` method.""" 

688 

689 node = PortNode() 

690 node.add_output_port("output", 1, 'Output Port "output"') 

691 node.remove_output_port("output") 

692 

693 assert len(node.input_ports) == 0 

694 

695 def test_get_input(self) -> None: 

696 """Test :meth:`colour.utilities.network.PortNode.get_input` method.""" 

697 

698 node = PortNode() 

699 node.add_input_port("a", 1, 'Input Port "a"') 

700 

701 assert node.get_input("a") == 1 

702 

703 def test_set_input(self) -> None: 

704 """Test :meth:`colour.utilities.network.PortNode.set_input` method.""" 

705 

706 node = PortNode() 

707 node.add_input_port("a", 1, 'Input Port "a"') 

708 

709 assert node.input_ports["a"].value == 1 

710 

711 node.set_input("a", 2) 

712 

713 assert node.input_ports["a"].value == 2 

714 

715 def test_get_output(self) -> None: 

716 """Test :meth:`colour.utilities.network.PortNode.get_output` method.""" 

717 

718 node = PortNode() 

719 node.add_output_port("output", 1, 'Output Port "output"') 

720 

721 assert node.get_output("output") == 1 

722 

723 def test_set_output(self) -> None: 

724 """Test :meth:`colour.utilities.network.PortNode.set_output` method.""" 

725 

726 node = PortNode() 

727 node.add_output_port("output", 1, 'Output Port "output"') 

728 

729 assert node.output_ports["output"].value == 1 

730 

731 node.set_output("output", 2) 

732 

733 assert node.output_ports["output"].value == 2 

734 

735 def test_connect(self) -> None: 

736 """Test :meth:`colour.utilities.network.PortNode.connect` method.""" 

737 

738 self.test_edges() 

739 

740 def test_disconnect(self) -> None: 

741 """Test :meth:`colour.utilities.network.PortNode.disconnect` method.""" 

742 

743 self.test_edges() 

744 

745 def test_process(self) -> None: 

746 """Test :meth:`colour.utilities.network.PortNode.process` method.""" 

747 

748 self._add_node_1.connect("output", self._multiply_node_1, "a") 

749 self._multiply_node_1.connect("output", self._add_node_2, "a") 

750 

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) 

755 

756 assert self._add_node_2.get_output("output") is None 

757 

758 self._add_node_1.process() 

759 self._multiply_node_1.process() 

760 self._add_node_2.process() 

761 

762 assert self._add_node_2.get_output("output") == 5 

763 

764 self._add_node_1.disconnect("output", self._multiply_node_1, "a") 

765 self._multiply_node_1.disconnect("output", self._add_node_2, "a") 

766 

767 def test_to_graphviz(self) -> None: 

768 """Test :meth:`colour.utilities.network.PortNode.to_graphviz` method.""" 

769 

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 ) 

785 

786 

787class TestPortGraph: 

788 """ 

789 Define :class:`colour.utilities.network.PortGraph` class unit tests methods. 

790 """ 

791 

792 def setup_method(self) -> None: 

793 """Initialise the common tests attributes.""" 

794 

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") 

798 

799 self._add_node_1.connect("output", self._multiply_node_1, "a") 

800 self._multiply_node_1.connect("output", self._add_node_2, "a") 

801 

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) 

806 

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 } 

812 

813 self._graph = PortGraph("Port Graph") 

814 

815 for node in self._nodes.values(): 

816 self._graph.add_node(node) 

817 

818 def test_required_attributes(self) -> None: 

819 """Test the presence of required attributes.""" 

820 

821 required_attributes = ("nodes",) 

822 

823 for attribute in required_attributes: 

824 assert attribute in dir(PortGraph) 

825 

826 def test_required_methods(self) -> None: 

827 """Test the presence of required methods.""" 

828 

829 required_methods = ( 

830 "__init__", 

831 "__str__", 

832 "add_node", 

833 "remove_node", 

834 "walk_ports", 

835 "process", 

836 "to_graphviz", 

837 ) 

838 

839 for method in required_methods: 

840 assert method in dir(PortGraph) 

841 

842 def test_nodes(self) -> None: 

843 """Test :attr:`colour.utilities.network.PortGraph.nodes` property.""" 

844 

845 assert self._graph.nodes == self._nodes 

846 

847 def test___str__(self) -> None: 

848 """Test :meth:`colour.utilities.network.PortGraph.__str__` method.""" 

849 

850 assert str(self._graph) == "PortGraph(3)" 

851 

852 def test_add_node(self) -> None: 

853 """Test :meth:`colour.utilities.network.PortGraph.add_node` method.""" 

854 

855 for node in self._nodes.values(): 

856 self._graph.remove_node(node) 

857 

858 assert len(self._graph.nodes) == 0 

859 

860 for node in self._nodes.values(): 

861 self._graph.add_node(node) 

862 

863 assert len(self._graph.nodes) == 3 

864 

865 def test_remove_node(self) -> None: 

866 """Test :meth:`colour.utilities.network.PortGraph.remove_node` method.""" 

867 

868 self.test_add_node() 

869 

870 def test_walk_ports(self) -> None: 

871 """Test :meth:`colour.utilities.network.PortGraph.walk_ports` method.""" 

872 

873 assert list(self._graph.walk_ports()) == list(self._nodes.values()) 

874 

875 def test_process(self) -> None: 

876 """Test :meth:`colour.utilities.network.PortGraph.process` method.""" 

877 

878 self._graph.process() 

879 

880 assert self._add_node_2.get_output("output") == 5 

881 

882 def test_to_graphviz(self) -> None: 

883 """Test :meth:`colour.utilities.network.PortGraph.to_graphviz` method.""" 

884 

885 if not is_pydot_installed(): # pragma: no cover 

886 return 

887 

888 import pydot # noqa: PLC0415 

889 

890 assert isinstance(self._graph.to_graphviz(), pydot.Dot) 

891 

892 

893class _AddItem(ExecutionNode): 

894 def __init__(self, *args: Any, **kwargs: Any) -> None: 

895 super().__init__(*args, **kwargs) 

896 

897 self.description = "Add the item with input key and value to the input mapping." 

898 

899 self.add_input_port("key") 

900 self.add_input_port("value") 

901 self.add_input_port("mapping", {}) 

902 

903 def process(self) -> None: 

904 """ 

905 Process the node. 

906 """ 

907 

908 key = self.get_input("key") 

909 value = self.get_input("value") 

910 

911 if key is None or value is None: # pragma: no cover 

912 return 

913 

914 self.get_input("mapping")[key] = value 

915 

916 self.dirty = False 

917 

918 

919class _NodeSumMappingValues(ExecutionNode): 

920 def __init__(self, *args: Any, **kwargs: Any) -> None: 

921 super().__init__(*args, **kwargs) 

922 

923 self.description = "Sum the input mapping values." 

924 

925 self.add_input_port("mapping", {}) 

926 self.add_output_port("summation") 

927 

928 def process(self) -> None: 

929 mapping = self.get_input("mapping") 

930 if len(mapping) == 0: # pragma: no cover 

931 return 

932 

933 self.set_output("summation", np.sum(list(mapping.values()))) 

934 

935 self.dirty = False 

936 

937 

938class _SubGraph1(ExecutionNode, PortGraph): 

939 def __init__(self, *args: Any, **kwargs: Any) -> None: 

940 super().__init__(*args, **kwargs) 

941 

942 self.add_input_port("input") 

943 self.add_output_port("output", {}) 

944 

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) 

952 

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 ) 

985 

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") 

989 

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 

996 

997 

998class TestFor: 

999 """ 

1000 Define :class:`colour.utilities.network.For` class unit tests methods. 

1001 """ 

1002 

1003 def test_For(self) -> None: 

1004 """Test :class:`colour.utilities.network.For` class.""" 

1005 

1006 sum_mapping_values = _NodeSumMappingValues() 

1007 

1008 sub_graph = _SubGraph1() 

1009 sub_graph.connect("output", sum_mapping_values, "mapping") 

1010 

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() 

1017 

1018 assert sum_mapping_values.get_output("summation") == 140 

1019 

1020 

1021class _NodeSumArray(ExecutionNode): 

1022 def __init__(self, *args: Any, **kwargs: Any) -> None: 

1023 super().__init__(*args, **kwargs) 

1024 

1025 self.description = "Sum the input array." 

1026 

1027 self.add_input_port("array", []) 

1028 self.add_output_port("summation") 

1029 

1030 def process(self) -> None: 

1031 array = self.get_input("array") 

1032 if len(array) == 0: # pragma: no cover 

1033 return 

1034 

1035 self.set_output("summation", np.sum(array)) 

1036 

1037 self.dirty = False 

1038 

1039 

1040class _SubGraph2(ExecutionNode, PortGraph): 

1041 def __init__(self, *args: Any, **kwargs: Any) -> None: 

1042 super().__init__(*args, **kwargs) 

1043 

1044 self.add_input_port("input") 

1045 self.add_output_port("output") 

1046 

1047 for node in [ 

1048 _NodeAdd("Add 1"), 

1049 _NodeMultiply("Multiply 1"), 

1050 _NodeAdd("Add 2"), 

1051 ]: 

1052 self.add_node(node) 

1053 

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 ) 

1078 

1079 self.connect("input", self.nodes["Add 1"], "b") 

1080 self.nodes["Add 2"].connect("output", self, "output") 

1081 

1082 def process(self, **kwargs: Any) -> None: # pragma: no cover 

1083 # Coverage can't track execution in subprocesses 

1084 

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) 

1088 

1089 super().process(**kwargs) 

1090 

1091 

1092class TestThreadPoolExecutorManager: 

1093 """ 

1094 Define :class:`colour.utilities.network.ThreadPoolExecutorManager` class unit tests 

1095 methods. 

1096 """ 

1097 

1098 def test_ThreadPoolExecutorManager(self) -> None: 

1099 """Test :class:`colour.utilities.network.ThreadPoolExecutorManager` class.""" 

1100 

1101 executor = ThreadPoolExecutorManager.get_executor() 

1102 

1103 assert executor is not None 

1104 

1105 

1106class TestParallelForThread: 

1107 """ 

1108 Define :class:`colour.utilities.network.ParallelForThread` class unit tests 

1109 methods. 

1110 """ 

1111 

1112 def test_ParallelForThread(self) -> None: 

1113 """Test :class:`colour.utilities.network.ParallelForThread` class.""" 

1114 

1115 sum_array = _NodeSumArray() 

1116 

1117 sub_graph = _SubGraph2() 

1118 

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() 

1126 

1127 assert sum_array.get_output("summation") == 140 

1128 

1129 

1130class TestProcessPoolExecutorManager: 

1131 """ 

1132 Define :class:`colour.utilities.network.ProcessPoolExecutorManager` class unit tests 

1133 methods. 

1134 """ 

1135 

1136 def test_ProcessPoolExecutorManager(self) -> None: 

1137 """Test :class:`colour.utilities.network.ProcessPoolExecutorManager` class.""" 

1138 

1139 executor = ProcessPoolExecutorManager.get_executor() 

1140 

1141 assert executor is not None 

1142 

1143 

1144class TestParallelForMultiProcess: 

1145 """ 

1146 Define :class:`colour.utilities.network.ParallelForMultiProcess` class unit 

1147 tests methods. 

1148 """ 

1149 

1150 def test_ParallelForMultiProcess(self) -> None: 

1151 """Test :class:`colour.utilities.network.ParallelForMultiProcess` class.""" 

1152 

1153 sum_array = _NodeSumArray() 

1154 

1155 sub_graph = _SubGraph2() 

1156 

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() 

1164 

1165 assert sum_array.get_output("summation") == 140