Coverage for utilities/tests/test_common.py: 100%
183 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.common` module."""
3from __future__ import annotations
5import typing
6import unicodedata
7from functools import partial
9import numpy as np
10import pytest
12if typing.TYPE_CHECKING:
13 from colour.hints import Any, Real, Tuple
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)
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"
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]
65class TestIsCachingEnabled:
66 """
67 Define :func:`colour.utilities.common.is_caching_enabled` definition unit
68 tests methods.
69 """
71 def test_is_caching_enabled(self) -> None:
72 """Test :func:`colour.utilities.common.is_caching_enabled` definition."""
74 with caching_enable(True):
75 assert is_caching_enabled()
77 with caching_enable(False):
78 assert not is_caching_enabled()
81class TestSetCachingEnabled:
82 """
83 Define :func:`colour.utilities.common.set_caching_enable` definition unit
84 tests methods.
85 """
87 def test_set_caching_enable(self) -> None:
88 """Test :func:`colour.utilities.common.set_caching_enable` definition."""
90 with caching_enable(is_caching_enabled()):
91 set_caching_enable(True)
92 assert is_caching_enabled()
94 with caching_enable(is_caching_enabled()):
95 set_caching_enable(False)
96 assert not is_caching_enabled()
99class TestCachingEnable:
100 """
101 Define :func:`colour.utilities.common.caching_enable` definition unit
102 tests methods.
103 """
105 def test_caching_enable(self) -> None:
106 """Test :func:`colour.utilities.common.caching_enable` definition."""
108 with caching_enable(True):
109 assert is_caching_enabled()
111 with caching_enable(False):
112 assert not is_caching_enabled()
114 @caching_enable(True)
115 def fn_a() -> None:
116 """:func:`caching_enable` unit tests :func:`fn_a` definition."""
118 assert is_caching_enabled()
120 fn_a()
122 @caching_enable(False)
123 def fn_b() -> None:
124 """:func:`caching_enable` unit tests :func:`fn_b` definition."""
126 assert not is_caching_enabled()
128 fn_b()
131class TestCacheRegistry:
132 """
133 Define :class:`colour.utilities.common.CacheRegistry` class unit
134 tests methods.
135 """
137 @staticmethod
138 def _default_test_cache_registry() -> CacheRegistry:
139 """Create a default test cache registry."""
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"
148 return cache_registry
150 def test_required_attributes(self) -> None:
151 """Test the presence of required attributes."""
153 required_attributes = ("registry",)
155 for attribute in required_attributes:
156 assert attribute in dir(CacheRegistry)
158 def test_required_methods(self) -> None:
159 """Test the presence of required methods."""
161 required_methods = (
162 "__init__",
163 "__str__",
164 "register_cache",
165 "unregister_cache",
166 "clear_cache",
167 "clear_all_caches",
168 )
170 for method in required_methods:
171 assert method in dir(CacheRegistry)
173 def test__str__(self) -> None:
174 """Test :class:`colour.utilities.common.CacheRegistry.__str__` method."""
176 cache_registry = self._default_test_cache_registry()
177 assert str(cache_registry) == "{'Cache A': '1 item(s)', 'Cache B': '2 item(s)'}"
179 def test_register_cache(self) -> None:
180 """
181 Test :class:`colour.utilities.common.CacheRegistry.register_cache`
182 method.
183 """
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}
191 def test_unregister_cache(self) -> None:
192 """
193 Test :class:`colour.utilities.common.CacheRegistry.unregister_cache`
194 method.
195 """
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
202 def test_clear_cache(self) -> None:
203 """
204 Test :class:`colour.utilities.common.CacheRegistry.clear_cache`
205 method.
206 """
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 }
215 def test_clear_all_caches(self) -> None:
216 """
217 Test :class:`colour.utilities.common.CacheRegistry.clear_all_caches`
218 method.
219 """
221 cache_registry = self._default_test_cache_registry()
222 cache_registry.clear_all_caches()
223 assert cache_registry.registry == {"Cache A": {}, "Cache B": {}}
226class TestAttest:
227 """
228 Define :func:`colour.utilities.common.attest` definition unit
229 tests methods.
230 """
232 def test_attest(self) -> None:
233 """Test :func:`colour.utilities.common.attest` definition."""
235 assert attest(True, "") is None
237 pytest.raises(AssertionError, attest, False)
240class TestBatch:
241 """
242 Define :func:`colour.utilities.common.batch` definition unit tests
243 methods.
244 """
246 def test_batch(self) -> None:
247 """Test :func:`colour.utilities.common.batch` definition."""
249 assert list(batch(tuple(range(10)), 3)) == [
250 (0, 1, 2),
251 (3, 4, 5),
252 (6, 7, 8),
253 (9,),
254 ]
256 assert list(batch(tuple(range(10)), 5)) == [(0, 1, 2, 3, 4), (5, 6, 7, 8, 9)]
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 ]
272def _add(a: Real, b: Real) -> Real:
273 """
274 Add two numbers.
276 This definition is intended to be used with a multiprocessing pool for unit
277 testing.
279 Parameters
280 ----------
281 a
282 Variable :math:`a`.
283 b
284 Variable :math:`b`.
286 Returns
287 -------
288 numeric
289 Addition result.
290 """
292 # NOTE: No coverage information is available as this code is executed in
293 # sub-processes.
294 return a + b # pragma: no cover
297class TestMultiprocessingPool:
298 """
299 Define :func:`colour.utilities.common.multiprocessing_pool` definition
300 unit tests methods.
301 """
303 def test_multiprocessing_pool(self) -> None:
304 """Test :func:`colour.utilities.common.multiprocessing_pool` definition."""
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 ]
321class TestIsIterable:
322 """
323 Define :func:`colour.utilities.common.is_iterable` definition unit tests
324 methods.
325 """
327 def test_is_iterable(self) -> None:
328 """Test :func:`colour.utilities.common.is_iterable` definition."""
330 assert is_iterable("")
332 assert is_iterable(())
334 assert is_iterable([])
336 assert is_iterable({})
338 assert is_iterable(set())
340 assert is_iterable(np.array([]))
342 assert not is_iterable(1)
344 assert not is_iterable(2)
346 generator = (a for a in range(10))
347 assert is_iterable(generator)
348 assert len(list(generator)) == 10
351class TestIsNumeric:
352 """
353 Define :func:`colour.utilities.common.is_numeric` definition unit tests
354 methods.
355 """
357 def test_is_numeric(self) -> None:
358 """Test :func:`colour.utilities.common.is_numeric` definition."""
360 assert is_numeric(1)
362 assert is_numeric(1)
364 assert not is_numeric((1,))
366 assert not is_numeric([1])
368 assert not is_numeric("1")
371class TestIsInteger:
372 """
373 Define :func:`colour.utilities.common.is_integer` definition unit
374 tests methods.
375 """
377 def test_is_integer(self) -> None:
378 """Test :func:`colour.utilities.common.is_integer` definition."""
380 assert is_integer(1)
382 assert is_integer(1.001)
384 assert not is_integer(1.01)
387class TestIsSibling:
388 """
389 Define :func:`colour.utilities.common.is_sibling` definition unit tests
390 methods.
391 """
393 def test_is_sibling(self) -> None:
394 """Test :func:`colour.utilities.common.is_sibling` definition."""
396 class Element:
397 """:func:`is_sibling` unit tests :class:`Element` class."""
399 def __init__(self, name: str) -> None:
400 self.name = name
402 class NotElement:
403 """:func:`is_sibling` unit tests :class:`NotElement` class."""
405 def __init__(self, name: str) -> None:
406 self.name = name
408 mapping = {
409 "Element A": Element("A"),
410 "Element B": Element("B"),
411 "Element C": Element("C"),
412 }
414 assert is_sibling(Element("D"), mapping)
416 assert not is_sibling(NotElement("Not D"), mapping)
419class TestFilterKwargs:
420 """
421 Define :func:`colour.utilities.common.filter_kwargs` definition unit
422 tests methods.
423 """
425 def test_filter_kwargs(self) -> None:
426 """Test :func:`colour.utilities.common.filter_kwargs` definition."""
428 def fn_a(a: Any) -> Any:
429 """:func:`filter_kwargs` unit tests :func:`fn_a` definition."""
431 return a
433 def fn_b(a: Any, b: float = 0) -> Tuple[Any, float]:
434 """:func:`filter_kwargs` unit tests :func:`fn_b` definition."""
436 return a, b
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."""
441 return a, b, c
443 assert fn_a(1, **filter_kwargs(fn_a, b=2, c=3)) == 1
445 assert fn_b(1, **filter_kwargs(fn_b, b=2, c=3)) == (1, 2)
447 assert fn_c(1, **filter_kwargs(fn_c, b=2, c=3)) == (1, 2, 3)
449 assert filter_kwargs(partial(fn_c, b=1), b=1) == {"b": 1}
452class TestFilterMapping:
453 """
454 Define :func:`colour.utilities.common.filter_mapping` definition unit
455 tests methods.
456 """
458 def test_filter_mapping(self) -> None:
459 """Test :func:`colour.utilities.common.filter_mapping` definition."""
461 class Element:
462 """:func:`filter_mapping` unit tests :class:`Element` class."""
464 def __init__(self, name: str) -> None:
465 self.name = name
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 }
474 assert sorted(filter_mapping(mapping, "Element A")) == ["Element A"]
476 assert filter_mapping(mapping, "Element") == {}
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 )
487 assert sorted(filter_mapping(mapping, "element a")) == ["Element A"]
489 assert sorted(filter_mapping(mapping, "element-a")) == ["Element A"]
491 assert sorted(filter_mapping(mapping, "elementa")) == ["Element A"]
494class TestFirstItem:
495 """
496 Define :func:`colour.utilities.common.first_item` definition unit
497 tests methods.
498 """
500 def test_first_item(self) -> None:
501 """Test :func:`colour.utilities.common.first_item` definition."""
503 assert first_item(range(10)) == 0
505 dictionary = {0: "a", 1: "b", 2: "c"}
506 assert first_item(dictionary.items()) == (0, "a")
508 assert first_item(dictionary.values()) == "a"
511class TestValidateMethod:
512 """
513 Define :func:`colour.utilities.common.validate_method` definition unit
514 tests methods.
515 """
517 def test_validate_method(self) -> None:
518 """Test :func:`colour.utilities.common.validate_method` definition."""
520 assert validate_method("Valid", ("Valid", "Yes", "Ok")) == "valid"
521 assert (
522 validate_method("Valid", ("Valid", "Yes", "Ok"), as_lowercase=False)
523 == "Valid"
524 )
526 def test_raise_exception_validate_method(self) -> None:
527 """
528 Test :func:`colour.utilities.common.validate_method` definition raised
529 exception.
530 """
532 pytest.raises(ValueError, validate_method, "Invalid", ("Valid", "Yes", "Ok"))
535class TestOptional:
536 """
537 Define :func:`colour.utilities.common.optional` definition unit
538 tests methods.
539 """
541 def test_optional(self) -> None:
542 """Test :func:`colour.utilities.common.optional` definition."""
544 assert optional("Foo", "Bar") == "Foo"
546 assert optional(None, "Bar") == "Bar"
549class TestSlugify:
550 """
551 Define :func:`colour.utilities.common.slugify` definition unit tests
552 methods.
553 """
555 def test_slugify(self) -> None:
556 """Test :func:`colour.utilities.common.slugify` definition."""
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 )
563 assert (
564 slugify("Un \xe9l\xe9phant \xe0 l'or\xe9e du bois")
565 == "un-elephant-a-loree-du-bois"
566 )
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 )
581 assert slugify(123) == "123"
584class TestIntDigest:
585 """
586 Define :func:`colour.utilities.common.int_digest` definition unit tests
587 methods.
588 """
590 def test_int_digest(self) -> None:
591 """Test :func:`colour.utilities.common.int_digest` definition."""
593 assert int_digest("Foo") == 7467386374397815550
595 assert int_digest(np.array([1, 2, 3]).tobytes()) == 8964613590703056768
597 assert int_digest(repr((1, 2, 3))) == 5069958125469218295