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

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

2

3 from __future__ import annotations

4

5 import typing

6 import unicodedata

7 from functools import partial

8

9 import numpy as np

10 import pytest

11

12 if typing.TYPE_CHECKING:

13 from colour.hints import Any, Real, Tuple

14

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

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

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

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

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

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

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

272 def _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

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

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

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

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

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

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

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

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

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

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

549 class 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-éléphant-à-lorée-du-bois"

579 )

580

581 assert slugify(123) == "123"

582

583

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