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

198 statements  

« 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.structures` module.""" 

2 

3from __future__ import annotations 

4 

5import operator 

6import pickle 

7 

8import numpy as np 

9import pytest 

10 

11from colour.utilities import ( 

12 CanonicalMapping, 

13 ColourUsageWarning, 

14 LazyCanonicalMapping, 

15 Lookup, 

16 Structure, 

17) 

18 

19__author__ = "Colour Developers" 

20__copyright__ = "Copyright 2013 Colour Developers" 

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

22__maintainer__ = "Colour Developers" 

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

24__status__ = "Production" 

25 

26__all__ = [ 

27 "TestStructure", 

28 "TestLookup", 

29 "TestCanonicalMapping", 

30 "TestLazyCanonicalMapping", 

31] 

32 

33 

34class TestStructure: 

35 """ 

36 Define :class:`colour.utilities.structures.Structure` class unit 

37 tests methods. 

38 """ 

39 

40 def test_Structure(self) -> None: 

41 """Test the :class:`colour.utilities.structures.Structure` class.""" 

42 

43 structure = Structure(John="Doe", Jane="Doe") 

44 assert "John" in structure 

45 assert hasattr(structure, "John") 

46 

47 structure.John = "Nemo" 

48 assert structure["John"] == "Nemo" 

49 

50 structure["John"] = "Vador" 

51 assert structure["John"] == "Vador" 

52 

53 del structure["John"] 

54 assert "John" not in structure 

55 assert not hasattr(structure, "John") 

56 

57 structure.John = "Doe" 

58 assert "John" in structure 

59 assert hasattr(structure, "John") 

60 

61 del structure.John 

62 assert "John" not in structure 

63 assert not hasattr(structure, "John") 

64 

65 structure = Structure(John=None, Jane=None) 

66 assert structure.John is None 

67 assert structure["John"] is None 

68 

69 structure.update(John="Doe", Jane="Doe") 

70 assert structure.John == "Doe" 

71 assert structure["John"] == "Doe" 

72 

73 def test_pickling(self) -> None: 

74 """ 

75 Test whether :class:`colour.utilities.structures.Structure` class 

76 can be pickled. 

77 """ 

78 

79 structure = Structure(John="Doe", Jane="Doe") 

80 

81 data = pickle.dumps(structure) 

82 data = pickle.loads(data) # noqa: S301 

83 assert structure == data 

84 

85 data = pickle.dumps(structure, pickle.HIGHEST_PROTOCOL) 

86 data = pickle.loads(data) # noqa: S301 

87 assert structure == data 

88 

89 assert sorted(dir(data)) == ["Jane", "John"] 

90 

91 

92class TestLookup: 

93 """ 

94 Define :class:`colour.utilities.structures.Lookup` class unit tests 

95 methods. 

96 """ 

97 

98 def test_required_methods(self) -> None: 

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

100 

101 required_methods = ("keys_from_value", "first_key_from_value") 

102 

103 for method in required_methods: 

104 assert method in dir(Lookup) 

105 

106 def test_keys_from_value(self) -> None: 

107 """ 

108 Test :meth:`colour.utilities.structures.Lookup.keys_from_value` 

109 method. 

110 """ 

111 

112 lookup = Lookup(John="Doe", Jane="Doe", Luke="Skywalker") 

113 assert sorted(lookup.keys_from_value("Doe")) == ["Jane", "John"] 

114 

115 lookup = Lookup( 

116 A=np.array([0, 1, 2]), B=np.array([0, 1, 2]), C=np.array([1, 2, 3]) 

117 ) 

118 assert sorted(lookup.keys_from_value(np.array([0, 1, 2]))) == ["A", "B"] 

119 

120 def test_first_key_from_value(self) -> None: 

121 """ 

122 Test :meth:`colour.utilities.structures.\ 

123Lookup.first_key_from_value` method. 

124 """ 

125 

126 lookup = Lookup(first_name="John", last_name="Doe", gender="male") 

127 assert lookup.first_key_from_value("John") == "first_name" 

128 

129 lookup = Lookup( 

130 A=np.array([0, 1, 2]), B=np.array([1, 2, 3]), C=np.array([2, 3, 4]) 

131 ) 

132 assert lookup.first_key_from_value(np.array([0, 1, 2])) == "A" 

133 

134 def test_raise_exception_first_key_from_value(self) -> None: 

135 """ 

136 Test :meth:`colour.utilities.structures.\ 

137Lookup.first_key_from_value` method raised exception. 

138 """ 

139 

140 pytest.raises(IndexError, Lookup().first_key_from_value, "John") 

141 

142 

143class TestCanonicalMapping: 

144 """ 

145 Define :class:`colour.utilities.structures.CanonicalMapping` class 

146 unit tests methods. 

147 """ 

148 

149 def test_required_attributes(self) -> None: 

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

151 

152 required_attributes = ("data",) 

153 

154 for attribute in required_attributes: 

155 assert attribute in dir(CanonicalMapping) 

156 

157 def test_required_methods(self) -> None: 

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

159 

160 required_methods = ( 

161 "__init__", 

162 "__repr__", 

163 "__setitem__", 

164 "__getitem__", 

165 "__delitem__", 

166 "__contains__", 

167 "__iter__", 

168 "__len__", 

169 "__eq__", 

170 "__ne__", 

171 "copy", 

172 "lower_keys", 

173 "lower_items", 

174 "slugified_keys", 

175 "slugified_items", 

176 "canonical_keys", 

177 "canonical_items", 

178 ) 

179 

180 for method in required_methods: 

181 assert method in dir(CanonicalMapping) 

182 

183 def test_data(self) -> None: 

184 """ 

185 Test :meth:`colour.utilities.structures.CanonicalMapping.data` 

186 property. 

187 """ 

188 

189 assert CanonicalMapping({"John": "Doe", "Jane": "Doe"}).data == { 

190 "John": "Doe", 

191 "Jane": "Doe", 

192 } 

193 

194 def test__repr__(self) -> None: 

195 """ 

196 Test :meth:`colour.utilities.structures.CanonicalMapping.__repr__` 

197 method. 

198 """ 

199 

200 mapping = CanonicalMapping() 

201 

202 mapping["John"] = "Doe" 

203 assert repr(mapping) == "CanonicalMapping({'John': 'Doe'})" 

204 

205 def test__setitem__(self) -> None: 

206 """ 

207 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

208__setitem__` method. 

209 """ 

210 

211 mapping = CanonicalMapping() 

212 

213 mapping["John"] = "Doe" 

214 assert mapping["John"] == "Doe" 

215 assert mapping["john"] == "Doe" 

216 

217 def test__getitem__(self) -> None: 

218 """ 

219 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

220__getitem__` method. 

221 """ 

222 

223 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

224 

225 assert mapping["John"] == "Doe" 

226 assert mapping["john"] == "Doe" 

227 assert mapping["JOHN"] == "Doe" 

228 assert mapping["Jane"] == "Doe" 

229 assert mapping["jane"] == "Doe" 

230 assert mapping["JANE"] == "Doe" 

231 

232 mapping = CanonicalMapping({1: "Foo", 2: "Bar"}) 

233 

234 assert mapping[1] == "Foo" 

235 

236 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

237 

238 assert mapping["mccamy-1992"] == 1 

239 assert mapping["hernandez-1999"] == 2 

240 assert mapping["mccamy1992"] == 1 

241 assert mapping["hernandez1999"] == 2 

242 

243 def test__delitem__(self) -> None: 

244 """ 

245 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

246__delitem__` method. 

247 """ 

248 

249 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

250 

251 del mapping["john"] 

252 assert "John" not in mapping 

253 

254 del mapping["Jane"] 

255 assert "jane" not in mapping 

256 assert len(mapping) == 0 

257 

258 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

259 del mapping["JOHN"] 

260 assert "John" not in mapping 

261 

262 del mapping["jane"] 

263 assert "jane" not in mapping 

264 assert len(mapping) == 0 

265 

266 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

267 

268 del mapping["mccamy-1992"] 

269 assert "McCamy 1992" not in mapping 

270 

271 del mapping["hernandez-1999"] 

272 assert "Hernandez 1999" not in mapping 

273 

274 assert len(mapping) == 0 

275 

276 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

277 

278 del mapping["mccamy1992"] 

279 assert "McCamy 1992" not in mapping 

280 

281 del mapping["hernandez1999"] 

282 assert "Hernandez 1999" not in mapping 

283 

284 assert len(mapping) == 0 

285 

286 def test__contains__(self) -> None: 

287 """ 

288 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

289__contains__` method. 

290 """ 

291 

292 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

293 

294 assert "John" in mapping 

295 assert "john" in mapping 

296 assert "JOHN" in mapping 

297 assert "Jane" in mapping 

298 assert "jane" in mapping 

299 assert "JANE" in mapping 

300 

301 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

302 

303 assert "mccamy-1992" in mapping 

304 assert "hernandez-1999" in mapping 

305 assert "mccamy1992" in mapping 

306 assert "hernandez1999" in mapping 

307 

308 def test__iter__(self) -> None: 

309 """ 

310 Test :meth:`colour.utilities.structures.CanonicalMapping.__iter__` 

311 method. 

312 """ 

313 

314 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

315 assert sorted(item for item in mapping) == ["Jane", "John"] 

316 

317 def test__len__(self) -> None: 

318 """ 

319 Test :meth:`colour.utilities.structures.CanonicalMapping.__len__` 

320 method. 

321 """ 

322 

323 assert len(CanonicalMapping()) == 0 

324 

325 assert len(CanonicalMapping(John="Doe", Jane="Doe")) == 2 

326 

327 def test__eq__(self) -> None: 

328 """ 

329 Test :meth:`colour.utilities.structures.CanonicalMapping.__eq__` 

330 method. 

331 """ 

332 

333 mapping1 = CanonicalMapping(John="Doe", Jane="Doe") 

334 mapping2 = CanonicalMapping(John="Doe", Jane="Doe") 

335 mapping3 = CanonicalMapping(john="Doe", jane="Doe") 

336 

337 assert mapping1 == mapping2 

338 

339 assert mapping2 != mapping3 

340 

341 def test_raise_exception__eq__(self) -> None: 

342 """ 

343 Test :meth:`colour.utilities.structures.CanonicalMapping.__eq__` 

344 method raised exception. 

345 """ 

346 

347 pytest.raises( 

348 TypeError, 

349 operator.eq, 

350 CanonicalMapping(John="Doe", Jane="Doe"), 

351 ["John", "Doe", "Jane", "Doe"], 

352 ) 

353 

354 def test__ne__(self) -> None: 

355 """ 

356 Test :meth:`colour.utilities.structures.CanonicalMapping.__ne__` 

357 method. 

358 """ 

359 

360 mapping1 = CanonicalMapping(John="Doe", Jane="Doe") 

361 mapping2 = CanonicalMapping(Gi="Doe", Jane="Doe") 

362 

363 assert mapping1 != mapping2 

364 

365 def test_raise_exception__ne__(self) -> None: 

366 """ 

367 Test :meth:`colour.utilities.structures.CanonicalMapping.__ne__` 

368 method raised exception. 

369 """ 

370 

371 pytest.raises( 

372 TypeError, 

373 operator.ne, 

374 CanonicalMapping(John="Doe", Jane="Doe"), 

375 ["John", "Doe", "Jane", "Doe"], 

376 ) 

377 

378 def test_copy(self) -> None: 

379 """ 

380 Test :meth:`colour.utilities.structures.CanonicalMapping.copy` 

381 method. 

382 """ 

383 

384 mapping1 = CanonicalMapping(John="Doe", Jane="Doe") 

385 mapping2 = mapping1.copy() 

386 

387 assert mapping1 == mapping2 

388 

389 assert id(mapping1) != id(mapping2) 

390 

391 def test_lower_keys(self) -> None: 

392 """ 

393 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

394lower_keys` method. 

395 """ 

396 

397 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

398 

399 assert sorted(item for item in mapping.lower_keys()) == ["jane", "john"] 

400 

401 mapping = CanonicalMapping(John="Doe", john="Doe") 

402 

403 pytest.warns(ColourUsageWarning, lambda: list(mapping.lower_keys())) 

404 

405 def test_lower_items(self) -> None: 

406 """ 

407 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

408lower_items` method. 

409 """ 

410 

411 mapping = CanonicalMapping(John="Doe", Jane="Doe") 

412 

413 assert sorted(item for item in mapping.lower_items()) == [ 

414 ("jane", "Doe"), 

415 ("john", "Doe"), 

416 ] 

417 

418 def test_slugified_keys(self) -> None: 

419 """ 

420 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

421slugified_keys` method. 

422 """ 

423 

424 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

425 

426 assert sorted(item for item in mapping.slugified_keys()) == [ 

427 "hernandez-1999", 

428 "mccamy-1992", 

429 ] 

430 

431 mapping = CanonicalMapping({"McCamy 1992": 1, "McCamy-1992": 2}) 

432 

433 pytest.warns(ColourUsageWarning, lambda: list(mapping.slugified_keys())) 

434 

435 def test_slugified_items(self) -> None: 

436 """ 

437 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

438slugified_items` method. 

439 """ 

440 

441 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

442 assert sorted(item for item in mapping.slugified_items()) == [ 

443 ("hernandez-1999", 2), 

444 ("mccamy-1992", 1), 

445 ] 

446 

447 def test_canonical_keys(self) -> None: 

448 """ 

449 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

450canonical_keys` method. 

451 """ 

452 

453 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

454 

455 assert sorted(item for item in mapping.canonical_keys()) == [ 

456 "hernandez1999", 

457 "mccamy1992", 

458 ] 

459 

460 mapping = CanonicalMapping({"McCamy_1992": 1, "McCamy-1992": 2}) 

461 

462 pytest.warns(ColourUsageWarning, lambda: list(mapping.canonical_keys())) 

463 

464 def test_canonical_items(self) -> None: 

465 """ 

466 Test :meth:`colour.utilities.structures.CanonicalMapping.\ 

467canonical_items` method. 

468 """ 

469 

470 mapping = CanonicalMapping({"McCamy 1992": 1, "Hernandez 1999": 2}) 

471 assert sorted(item for item in mapping.canonical_items()) == [ 

472 ("hernandez1999", 2), 

473 ("mccamy1992", 1), 

474 ] 

475 

476 

477class TestLazyCanonicalMapping: 

478 """ 

479 Define :class:`colour.utilities.structures.LazyCanonicalMapping` class 

480 unit tests methods. 

481 """ 

482 

483 def test_required_attributes(self) -> None: 

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

485 

486 required_attributes = () 

487 

488 for attribute in required_attributes: # pragma: no cover 

489 assert attribute in dir(LazyCanonicalMapping) 

490 

491 def test_required_methods(self) -> None: 

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

493 

494 required_methods = ("__getitem__",) 

495 

496 for method in required_methods: 

497 assert method in dir(LazyCanonicalMapping) 

498 

499 def test__getitem__(self) -> None: 

500 """ 

501 Test :meth:`colour.utilities.structures.LazyCanonicalMapping.\ 

502__getitem__` method. 

503 """ 

504 

505 mapping = LazyCanonicalMapping(John="Doe", Jane=lambda: "Doe") 

506 

507 assert mapping["John"] == "Doe" 

508 assert mapping["john"] == "Doe" 

509 assert mapping["Jane"] == "Doe" 

510 assert mapping["jane"] == "Doe"