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

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

2

3 from __future__ import annotations

4

5 import re

6 import typing

7

8 import numpy as np

9

10 if typing.TYPE_CHECKING:

11 from colour.hints import Any

12

13 from colour.utilities import (

14 ExecutionNode,

15 For,

16 ParallelForMultiprocess,

17 ParallelForThread,

18 Port,

19 PortGraph,

20 PortNode,

21 TreeNode,

22 is_pydot_installed,

23)

24 from 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

49 class 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

217 class 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

442 class _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

464 class _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

486 class 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

787 class 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

893 class _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

919 class _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

938 class _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

998 class 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

1021 class _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

1040 class _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

1092 class 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

1106 class 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

1130 class 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

1144 class 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