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

183 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:39 +1300

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

2 

3from __future__ import annotations 

4 

5import typing 

6import unicodedata 

7from functools import partial 

8 

9import numpy as np 

10import pytest 

11 

12if typing.TYPE_CHECKING: 

13 from colour.hints import Any, Real, Tuple 

14 

15from colour.utilities import ( 

16 CacheRegistry, 

17 CanonicalMapping, 

18 attest, 

19 batch, 

20 caching_enable, 

21 filter_kwargs, 

22 filter_mapping, 

23 first_item, 

24 int_digest, 

25 is_caching_enabled, 

26 is_integer, 

27 is_iterable, 

28 is_numeric, 

29 is_sibling, 

30 multiprocessing_pool, 

31 optional, 

32 set_caching_enable, 

33 slugify, 

34 validate_method, 

35) 

36 

37__author__ = "Colour Developers" 

38__copyright__ = "Copyright 2013 Colour Developers" 

39__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

40__maintainer__ = "Colour Developers" 

41__email__ = "colour-developers@colour-science.org" 

42__status__ = "Production" 

43 

44__all__ = [ 

45 "TestIsCachingEnabled", 

46 "TestSetCachingEnabled", 

47 "TestCachingEnable", 

48 "TestCacheRegistry", 

49 "TestAttest", 

50 "TestBatch", 

51 "TestMultiprocessingPool", 

52 "TestIsIterable", 

53 "TestIsNumeric", 

54 "TestIsInteger", 

55 "TestIsSibling", 

56 "TestFilterKwargs", 

57 "TestFilterMapping", 

58 "TestFirstItem", 

59 "TestValidateMethod", 

60 "TestOptional", 

61 "TestSlugify", 

62] 

63 

64 

65class TestIsCachingEnabled: 

66 """ 

67 Define :func:`colour.utilities.common.is_caching_enabled` definition unit 

68 tests methods. 

69 """ 

70 

71 def test_is_caching_enabled(self) -> None: 

72 """Test :func:`colour.utilities.common.is_caching_enabled` definition.""" 

73 

74 with caching_enable(True): 

75 assert is_caching_enabled() 

76 

77 with caching_enable(False): 

78 assert not is_caching_enabled() 

79 

80 

81class TestSetCachingEnabled: 

82 """ 

83 Define :func:`colour.utilities.common.set_caching_enable` definition unit 

84 tests methods. 

85 """ 

86 

87 def test_set_caching_enable(self) -> None: 

88 """Test :func:`colour.utilities.common.set_caching_enable` definition.""" 

89 

90 with caching_enable(is_caching_enabled()): 

91 set_caching_enable(True) 

92 assert is_caching_enabled() 

93 

94 with caching_enable(is_caching_enabled()): 

95 set_caching_enable(False) 

96 assert not is_caching_enabled() 

97 

98 

99class TestCachingEnable: 

100 """ 

101 Define :func:`colour.utilities.common.caching_enable` definition unit 

102 tests methods. 

103 """ 

104 

105 def test_caching_enable(self) -> None: 

106 """Test :func:`colour.utilities.common.caching_enable` definition.""" 

107 

108 with caching_enable(True): 

109 assert is_caching_enabled() 

110 

111 with caching_enable(False): 

112 assert not is_caching_enabled() 

113 

114 @caching_enable(True) 

115 def fn_a() -> None: 

116 """:func:`caching_enable` unit tests :func:`fn_a` definition.""" 

117 

118 assert is_caching_enabled() 

119 

120 fn_a() 

121 

122 @caching_enable(False) 

123 def fn_b() -> None: 

124 """:func:`caching_enable` unit tests :func:`fn_b` definition.""" 

125 

126 assert not is_caching_enabled() 

127 

128 fn_b() 

129 

130 

131class TestCacheRegistry: 

132 """ 

133 Define :class:`colour.utilities.common.CacheRegistry` class unit 

134 tests methods. 

135 """ 

136 

137 @staticmethod 

138 def _default_test_cache_registry() -> CacheRegistry: 

139 """Create a default test cache registry.""" 

140 

141 cache_registry = CacheRegistry() 

142 cache_a = cache_registry.register_cache("Cache A") 

143 cache_a["Foo"] = "Bar" 

144 cache_b = cache_registry.register_cache("Cache B") 

145 cache_b["John"] = "Doe" 

146 cache_b["Luke"] = "Skywalker" 

147 

148 return cache_registry 

149 

150 def test_required_attributes(self) -> None: 

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

152 

153 required_attributes = ("registry",) 

154 

155 for attribute in required_attributes: 

156 assert attribute in dir(CacheRegistry) 

157 

158 def test_required_methods(self) -> None: 

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

160 

161 required_methods = ( 

162 "__init__", 

163 "__str__", 

164 "register_cache", 

165 "unregister_cache", 

166 "clear_cache", 

167 "clear_all_caches", 

168 ) 

169 

170 for method in required_methods: 

171 assert method in dir(CacheRegistry) 

172 

173 def test__str__(self) -> None: 

174 """Test :class:`colour.utilities.common.CacheRegistry.__str__` method.""" 

175 

176 cache_registry = self._default_test_cache_registry() 

177 assert str(cache_registry) == "{'Cache A': '1 item(s)', 'Cache B': '2 item(s)'}" 

178 

179 def test_register_cache(self) -> None: 

180 """ 

181 Test :class:`colour.utilities.common.CacheRegistry.register_cache` 

182 method. 

183 """ 

184 

185 cache_registry = CacheRegistry() 

186 cache_a = cache_registry.register_cache("Cache A") 

187 assert cache_registry.registry == {"Cache A": cache_a} 

188 cache_b = cache_registry.register_cache("Cache B") 

189 assert cache_registry.registry == {"Cache A": cache_a, "Cache B": cache_b} 

190 

191 def test_unregister_cache(self) -> None: 

192 """ 

193 Test :class:`colour.utilities.common.CacheRegistry.unregister_cache` 

194 method. 

195 """ 

196 

197 cache_registry = self._default_test_cache_registry() 

198 cache_registry.unregister_cache("Cache A") 

199 assert "Cache A" not in cache_registry.registry 

200 assert "Cache B" in cache_registry.registry 

201 

202 def test_clear_cache(self) -> None: 

203 """ 

204 Test :class:`colour.utilities.common.CacheRegistry.clear_cache` 

205 method. 

206 """ 

207 

208 cache_registry = self._default_test_cache_registry() 

209 cache_registry.clear_cache("Cache A") 

210 assert cache_registry.registry == { 

211 "Cache A": {}, 

212 "Cache B": {"John": "Doe", "Luke": "Skywalker"}, 

213 } 

214 

215 def test_clear_all_caches(self) -> None: 

216 """ 

217 Test :class:`colour.utilities.common.CacheRegistry.clear_all_caches` 

218 method. 

219 """ 

220 

221 cache_registry = self._default_test_cache_registry() 

222 cache_registry.clear_all_caches() 

223 assert cache_registry.registry == {"Cache A": {}, "Cache B": {}} 

224 

225 

226class TestAttest: 

227 """ 

228 Define :func:`colour.utilities.common.attest` definition unit 

229 tests methods. 

230 """ 

231 

232 def test_attest(self) -> None: 

233 """Test :func:`colour.utilities.common.attest` definition.""" 

234 

235 assert attest(True, "") is None 

236 

237 pytest.raises(AssertionError, attest, False) 

238 

239 

240class TestBatch: 

241 """ 

242 Define :func:`colour.utilities.common.batch` definition unit tests 

243 methods. 

244 """ 

245 

246 def test_batch(self) -> None: 

247 """Test :func:`colour.utilities.common.batch` definition.""" 

248 

249 assert list(batch(tuple(range(10)), 3)) == [ 

250 (0, 1, 2), 

251 (3, 4, 5), 

252 (6, 7, 8), 

253 (9,), 

254 ] 

255 

256 assert list(batch(tuple(range(10)), 5)) == [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9)] 

257 

258 assert list(batch(tuple(range(10)), 1)) == [ 

259 (0,), 

260 (1,), 

261 (2,), 

262 (3,), 

263 (4,), 

264 (5,), 

265 (6,), 

266 (7,), 

267 (8,), 

268 (9,), 

269 ] 

270 

271 

272def _add(a: Real, b: Real) -> Real: 

273 """ 

274 Add two numbers. 

275 

276 This definition is intended to be used with a multiprocessing pool for unit 

277 testing. 

278 

279 Parameters 

280 ---------- 

281 a 

282 Variable :math:`a`. 

283 b 

284 Variable :math:`b`. 

285 

286 Returns 

287 ------- 

288 numeric 

289 Addition result. 

290 """ 

291 

292 # NOTE: No coverage information is available as this code is executed in 

293 # sub-processes. 

294 return a + b # pragma: no cover 

295 

296 

297class TestMultiprocessingPool: 

298 """ 

299 Define :func:`colour.utilities.common.multiprocessing_pool` definition 

300 unit tests methods. 

301 """ 

302 

303 def test_multiprocessing_pool(self) -> None: 

304 """Test :func:`colour.utilities.common.multiprocessing_pool` definition.""" 

305 

306 with multiprocessing_pool() as pool: 

307 assert pool.map(partial(_add, b=2), range(10)) == [ 

308 2, 

309 3, 

310 4, 

311 5, 

312 6, 

313 7, 

314 8, 

315 9, 

316 10, 

317 11, 

318 ] 

319 

320 

321class TestIsIterable: 

322 """ 

323 Define :func:`colour.utilities.common.is_iterable` definition unit tests 

324 methods. 

325 """ 

326 

327 def test_is_iterable(self) -> None: 

328 """Test :func:`colour.utilities.common.is_iterable` definition.""" 

329 

330 assert is_iterable("") 

331 

332 assert is_iterable(()) 

333 

334 assert is_iterable([]) 

335 

336 assert is_iterable({}) 

337 

338 assert is_iterable(set()) 

339 

340 assert is_iterable(np.array([])) 

341 

342 assert not is_iterable(1) 

343 

344 assert not is_iterable(2) 

345 

346 generator = (a for a in range(10)) 

347 assert is_iterable(generator) 

348 assert len(list(generator)) == 10 

349 

350 

351class TestIsNumeric: 

352 """ 

353 Define :func:`colour.utilities.common.is_numeric` definition unit tests 

354 methods. 

355 """ 

356 

357 def test_is_numeric(self) -> None: 

358 """Test :func:`colour.utilities.common.is_numeric` definition.""" 

359 

360 assert is_numeric(1) 

361 

362 assert is_numeric(1) 

363 

364 assert not is_numeric((1,)) 

365 

366 assert not is_numeric([1]) 

367 

368 assert not is_numeric("1") 

369 

370 

371class TestIsInteger: 

372 """ 

373 Define :func:`colour.utilities.common.is_integer` definition unit 

374 tests methods. 

375 """ 

376 

377 def test_is_integer(self) -> None: 

378 """Test :func:`colour.utilities.common.is_integer` definition.""" 

379 

380 assert is_integer(1) 

381 

382 assert is_integer(1.001) 

383 

384 assert not is_integer(1.01) 

385 

386 

387class TestIsSibling: 

388 """ 

389 Define :func:`colour.utilities.common.is_sibling` definition unit tests 

390 methods. 

391 """ 

392 

393 def test_is_sibling(self) -> None: 

394 """Test :func:`colour.utilities.common.is_sibling` definition.""" 

395 

396 class Element: 

397 """:func:`is_sibling` unit tests :class:`Element` class.""" 

398 

399 def __init__(self, name: str) -> None: 

400 self.name = name 

401 

402 class NotElement: 

403 """:func:`is_sibling` unit tests :class:`NotElement` class.""" 

404 

405 def __init__(self, name: str) -> None: 

406 self.name = name 

407 

408 mapping = { 

409 "Element A": Element("A"), 

410 "Element B": Element("B"), 

411 "Element C": Element("C"), 

412 } 

413 

414 assert is_sibling(Element("D"), mapping) 

415 

416 assert not is_sibling(NotElement("Not D"), mapping) 

417 

418 

419class TestFilterKwargs: 

420 """ 

421 Define :func:`colour.utilities.common.filter_kwargs` definition unit 

422 tests methods. 

423 """ 

424 

425 def test_filter_kwargs(self) -> None: 

426 """Test :func:`colour.utilities.common.filter_kwargs` definition.""" 

427 

428 def fn_a(a: Any) -> Any: 

429 """:func:`filter_kwargs` unit tests :func:`fn_a` definition.""" 

430 

431 return a 

432 

433 def fn_b(a: Any, b: float = 0) -> Tuple[Any, float]: 

434 """:func:`filter_kwargs` unit tests :func:`fn_b` definition.""" 

435 

436 return a, b 

437 

438 def fn_c(a: Any, b: float = 0, c: float = 0) -> Tuple[float, float, float]: 

439 """:func:`filter_kwargs` unit tests :func:`fn_c` definition.""" 

440 

441 return a, b, c 

442 

443 assert fn_a(1, **filter_kwargs(fn_a, b=2, c=3)) == 1 

444 

445 assert fn_b(1, **filter_kwargs(fn_b, b=2, c=3)) == (1, 2) 

446 

447 assert fn_c(1, **filter_kwargs(fn_c, b=2, c=3)) == (1, 2, 3) 

448 

449 assert filter_kwargs(partial(fn_c, b=1), b=1) == {"b": 1} 

450 

451 

452class TestFilterMapping: 

453 """ 

454 Define :func:`colour.utilities.common.filter_mapping` definition unit 

455 tests methods. 

456 """ 

457 

458 def test_filter_mapping(self) -> None: 

459 """Test :func:`colour.utilities.common.filter_mapping` definition.""" 

460 

461 class Element: 

462 """:func:`filter_mapping` unit tests :class:`Element` class.""" 

463 

464 def __init__(self, name: str) -> None: 

465 self.name = name 

466 

467 mapping = { 

468 "Element A": Element("A"), 

469 "Element B": Element("B"), 

470 "Element C": Element("C"), 

471 "Not Element C": Element("Not C"), 

472 } 

473 

474 assert sorted(filter_mapping(mapping, "Element A")) == ["Element A"] 

475 

476 assert filter_mapping(mapping, "Element") == {} 

477 

478 mapping = CanonicalMapping( 

479 { 

480 "Element A": Element("A"), 

481 "Element B": Element("B"), 

482 "Element C": Element("C"), 

483 "Not Element C": Element("Not C"), 

484 } 

485 ) 

486 

487 assert sorted(filter_mapping(mapping, "element a")) == ["Element A"] 

488 

489 assert sorted(filter_mapping(mapping, "element-a")) == ["Element A"] 

490 

491 assert sorted(filter_mapping(mapping, "elementa")) == ["Element A"] 

492 

493 

494class TestFirstItem: 

495 """ 

496 Define :func:`colour.utilities.common.first_item` definition unit 

497 tests methods. 

498 """ 

499 

500 def test_first_item(self) -> None: 

501 """Test :func:`colour.utilities.common.first_item` definition.""" 

502 

503 assert first_item(range(10)) == 0 

504 

505 dictionary = {0: "a", 1: "b", 2: "c"} 

506 assert first_item(dictionary.items()) == (0, "a") 

507 

508 assert first_item(dictionary.values()) == "a" 

509 

510 

511class TestValidateMethod: 

512 """ 

513 Define :func:`colour.utilities.common.validate_method` definition unit 

514 tests methods. 

515 """ 

516 

517 def test_validate_method(self) -> None: 

518 """Test :func:`colour.utilities.common.validate_method` definition.""" 

519 

520 assert validate_method("Valid", ("Valid", "Yes", "Ok")) == "valid" 

521 assert ( 

522 validate_method("Valid", ("Valid", "Yes", "Ok"), as_lowercase=False) 

523 == "Valid" 

524 ) 

525 

526 def test_raise_exception_validate_method(self) -> None: 

527 """ 

528 Test :func:`colour.utilities.common.validate_method` definition raised 

529 exception. 

530 """ 

531 

532 pytest.raises(ValueError, validate_method, "Invalid", ("Valid", "Yes", "Ok")) 

533 

534 

535class TestOptional: 

536 """ 

537 Define :func:`colour.utilities.common.optional` definition unit 

538 tests methods. 

539 """ 

540 

541 def test_optional(self) -> None: 

542 """Test :func:`colour.utilities.common.optional` definition.""" 

543 

544 assert optional("Foo", "Bar") == "Foo" 

545 

546 assert optional(None, "Bar") == "Bar" 

547 

548 

549class TestSlugify: 

550 """ 

551 Define :func:`colour.utilities.common.slugify` definition unit tests 

552 methods. 

553 """ 

554 

555 def test_slugify(self) -> None: 

556 """Test :func:`colour.utilities.common.slugify` definition.""" 

557 

558 assert ( 

559 slugify(" Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/") 

560 == "jack-jill-like-numbers-123-and-4-and-silly-characters" 

561 ) 

562 

563 assert ( 

564 slugify("Un \xe9l\xe9phant \xe0 l'or\xe9e du bois") 

565 == "un-elephant-a-loree-du-bois" 

566 ) 

567 

568 # NOTE: Our "utilities/unicode_to_ascii.py" utility script normalises 

569 # the reference string. 

570 assert ( 

571 unicodedata.normalize( 

572 "NFD", 

573 slugify( 

574 "Un \xe9l\xe9phant \xe0 l'or\xe9e du bois", 

575 allow_unicode=True, 

576 ), 

577 ) 

578 == "un-éléphant-à-lorée-du-bois" 

579 ) 

580 

581 assert slugify(123) == "123" 

582 

583 

584class TestIntDigest: 

585 """ 

586 Define :func:`colour.utilities.common.int_digest` definition unit tests 

587 methods. 

588 """ 

589 

590 def test_int_digest(self) -> None: 

591 """Test :func:`colour.utilities.common.int_digest` definition.""" 

592 

593 assert int_digest("Foo") == 7467386374397815550 

594 

595 assert int_digest(np.array([1, 2, 3]).tobytes()) == 8964613590703056768 

596 

597 assert int_digest(repr((1, 2, 3))) == 5069958125469218295