test_property.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. import sys
  2. import pytest
  3. from panda3d import core
  4. from contextlib import contextmanager
  5. import collections
  6. @contextmanager
  7. def constant_refcount(var):
  8. """ with block that checks that the Python refcount remains the same. """
  9. rc = sys.getrefcount(var)
  10. yield
  11. assert sys.getrefcount(var) == rc
  12. def test_property():
  13. # This is a property defined by MAKE_PROPERTY.
  14. np = core.PandaNode("")
  15. # Getter
  16. transform = np.get_transform()
  17. assert transform == np.transform
  18. # Setter
  19. new_transform = transform.set_pos((1, 0, 0))
  20. np.transform = new_transform
  21. assert np.transform == new_transform
  22. # Invalid assignments
  23. with pytest.raises(TypeError):
  24. np.transform = None
  25. with pytest.raises(TypeError):
  26. np.transform = "nonsense"
  27. with pytest.raises(TypeError):
  28. del np.transform
  29. def test_property2():
  30. # This is a property defined by MAKE_PROPERTY2, that can be None.
  31. mat = core.Material()
  32. mat.ambient = (1, 0, 0, 1)
  33. assert mat.ambient == (1, 0, 0, 1)
  34. mat.ambient = None
  35. assert mat.ambient is None
  36. with pytest.raises(TypeError):
  37. mat.ambient = "nonsense"
  38. with pytest.raises(TypeError):
  39. del mat.ambient
  40. # The next tests are for MAKE_SEQ_PROPERTY.
  41. @pytest.fixture
  42. def seq_property(*items):
  43. """ Returns a sequence property initialized with the given items. """
  44. # It doesn't matter which property we use; I just happened to pick
  45. # CollisionNode.solids.
  46. cn = core.CollisionNode("")
  47. append = cn.add_solid
  48. for item in items:
  49. append(item)
  50. assert len(cn.solids) == len(items)
  51. return cn.solids
  52. # Arbitrary items we can use as items for the above seq property.
  53. item_a = core.CollisionSphere((0, 0, 0), 1)
  54. item_b = core.CollisionSphere((0, 0, 0), 2)
  55. item_c = core.CollisionSphere((0, 0, 0), 3)
  56. def test_seq_property_abc():
  57. prop = seq_property()
  58. assert isinstance(prop, collections.Container)
  59. assert isinstance(prop, collections.Sized)
  60. assert isinstance(prop, collections.Iterable)
  61. assert isinstance(prop, collections.MutableSequence)
  62. assert isinstance(prop, collections.Sequence)
  63. def test_seq_property_empty():
  64. prop = seq_property()
  65. assert not prop
  66. assert len(prop) == 0
  67. with pytest.raises(IndexError):
  68. prop[0]
  69. with pytest.raises(IndexError):
  70. prop[-1]
  71. def test_seq_property_iter():
  72. prop = seq_property(item_a, item_b, item_b)
  73. assert prop
  74. assert len(prop) == 3
  75. assert tuple(prop) == (item_a, item_b, item_b)
  76. assert item_a in prop
  77. assert item_c not in prop
  78. assert None not in prop
  79. def test_seq_property_reversed():
  80. prop = seq_property(item_a, item_b, item_b)
  81. assert tuple(reversed(prop)) == tuple(reversed(tuple(prop)))
  82. def test_seq_property_getitem():
  83. prop = seq_property(item_a, item_b, item_b)
  84. assert prop[0] == item_a
  85. assert prop[1] == item_b
  86. assert prop[2] == item_b
  87. # Reverse index
  88. assert prop[-1] == item_b
  89. assert prop[-2] == item_b
  90. assert prop[-3] == item_a
  91. # Long index
  92. if sys.version_info[0] < 3:
  93. assert prop[long(1)] == item_b
  94. assert prop[long(-1)] == item_b
  95. # Out of bounds access
  96. with pytest.raises(IndexError):
  97. prop[-4]
  98. with pytest.raises(IndexError):
  99. prop[3]
  100. with pytest.raises(IndexError):
  101. prop[2**63]
  102. # Invalid index
  103. with pytest.raises(TypeError):
  104. prop[None]
  105. with pytest.raises(TypeError):
  106. prop[item_a]
  107. with pytest.raises(TypeError):
  108. prop["nonsense"]
  109. # Reference count check
  110. i = 1
  111. with constant_refcount(i):
  112. prop[i]
  113. # Make sure it preserves refcount of invalid indices
  114. i = "nonsense"
  115. with constant_refcount(i):
  116. try:
  117. prop[i]
  118. except TypeError:
  119. pass
  120. def test_seq_property_setitem():
  121. prop = seq_property(item_c, item_c, item_c)
  122. prop[0] = item_a
  123. prop[1] = item_b
  124. assert tuple(prop) == (item_a, item_b, item_c)
  125. # Refcount of key and value stays the same?
  126. i = 0
  127. with constant_refcount(i):
  128. with constant_refcount(item_a):
  129. prop[0] = item_a
  130. # Reverse index
  131. prop[-1] = item_a
  132. prop[-2] = item_b
  133. prop[-3] = item_c
  134. assert tuple(prop) == (item_c, item_b, item_a)
  135. # Long index
  136. if sys.version_info[0] < 3:
  137. prop[long(1)] = item_b
  138. assert prop[1] == item_b
  139. prop[long(-1)] = item_b
  140. assert prop[-1] == item_b
  141. # Out of bounds access
  142. with pytest.raises(IndexError):
  143. prop[-4] = item_c
  144. with pytest.raises(IndexError):
  145. prop[3] = item_c
  146. with pytest.raises(IndexError):
  147. prop[2**63] = item_c
  148. # Invalid index
  149. with pytest.raises(TypeError):
  150. prop[None] = item_c
  151. with pytest.raises(TypeError):
  152. prop[item_a] = item_c
  153. with pytest.raises(TypeError):
  154. prop["nonsense"] = item_c
  155. # Invalid type
  156. with pytest.raises(TypeError):
  157. prop[0] = None
  158. with pytest.raises(TypeError):
  159. prop[0] = "nonsense"
  160. def test_seq_property_delitem():
  161. prop = seq_property(item_a, item_b, item_c)
  162. # Out of bounds
  163. with pytest.raises(IndexError):
  164. prop[3]
  165. with pytest.raises(IndexError):
  166. prop[-4]
  167. # Positive index
  168. del prop[0]
  169. assert tuple(prop) == (item_b, item_c)
  170. # Negative index
  171. del prop[-2]
  172. assert tuple(prop) == (item_c,)
  173. # Invalid index
  174. with pytest.raises(TypeError):
  175. del prop[None]
  176. def test_seq_property_index():
  177. prop = seq_property(item_a, item_b, item_b)
  178. assert prop.index(item_a) == 0
  179. assert prop.index(item_b) == 1
  180. with pytest.raises(ValueError):
  181. prop.index(item_c)
  182. with pytest.raises(ValueError):
  183. prop.index(None)
  184. with pytest.raises(ValueError):
  185. prop.index("nonsense")
  186. # Refcount is properly decreased
  187. with constant_refcount(item_b):
  188. prop.index(item_b)
  189. with constant_refcount(item_c):
  190. try:
  191. prop.index(item_c)
  192. except ValueError:
  193. pass
  194. nonsense = "nonsense"
  195. with constant_refcount(nonsense):
  196. try:
  197. prop.index(nonsense)
  198. except ValueError:
  199. pass
  200. def test_seq_property_count():
  201. prop = seq_property(item_a, item_b, item_b)
  202. prop.count(item_a) == 1
  203. prop.count(item_b) == 2
  204. prop.count(item_c) == 0
  205. prop.count(None) == 0
  206. prop.count(("nonsense", -3.5)) == 0
  207. # Refcount does not leak
  208. with constant_refcount(item_b):
  209. prop.count(item_b)
  210. nonsense = "nonsense"
  211. with constant_refcount(nonsense):
  212. prop.count(nonsense)
  213. def test_seq_property_clear():
  214. prop = seq_property(item_a, item_b, item_b)
  215. prop.clear()
  216. assert not prop
  217. assert len(prop) == 0
  218. assert tuple(prop) == ()
  219. def test_seq_property_pop():
  220. prop = seq_property(item_a, item_b, item_c, item_b)
  221. # Test out of bounds pop
  222. with pytest.raises(IndexError):
  223. prop.pop(4)
  224. with pytest.raises(IndexError):
  225. prop.pop(-5)
  226. assert prop.pop(1) == item_b
  227. assert prop.pop(-1) == item_b
  228. assert prop.pop() == item_c
  229. assert prop.pop(0) == item_a
  230. # Wrong args
  231. with pytest.raises(TypeError):
  232. prop.pop(0, 0)
  233. # Pop on empty sequence
  234. with pytest.raises(IndexError):
  235. prop.pop()
  236. with pytest.raises(IndexError):
  237. prop.pop(0)
  238. with pytest.raises(IndexError):
  239. prop.pop(-1)
  240. def test_seq_property_remove():
  241. prop = seq_property(item_a, item_b, item_c, item_b)
  242. with constant_refcount(item_b):
  243. prop.remove(item_b)
  244. assert tuple(prop) == (item_a, item_c, item_b)
  245. prop.remove(item_b)
  246. assert tuple(prop) == (item_a, item_c)
  247. with pytest.raises(ValueError):
  248. prop.remove(item_b)
  249. with pytest.raises(ValueError):
  250. prop.remove(None)
  251. with pytest.raises(ValueError):
  252. prop.remove("nonsense")
  253. def test_seq_property_append():
  254. prop = seq_property(item_a, item_b)
  255. with constant_refcount(item_c):
  256. prop.append(item_c)
  257. assert tuple(prop) == (item_a, item_b, item_c)
  258. with pytest.raises(TypeError):
  259. prop.append(None)
  260. with pytest.raises(TypeError):
  261. prop.append("nonsense")
  262. def test_seq_property_insert():
  263. # Adding at the beginning
  264. prop = seq_property(item_a, item_a, item_a)
  265. with constant_refcount(item_b):
  266. prop.insert(0, item_b)
  267. assert tuple(prop) == (item_b, item_a, item_a, item_a)
  268. # Adding in the middle
  269. prop = seq_property(item_a, item_a, item_a)
  270. with constant_refcount(item_b):
  271. prop.insert(2, item_b)
  272. assert tuple(prop) == (item_a, item_a, item_b, item_a)
  273. # Adding at the end
  274. prop = seq_property(item_a, item_a, item_a)
  275. with constant_refcount(item_b):
  276. prop.insert(len(prop), item_b)
  277. assert tuple(prop) == (item_a, item_a, item_a, item_b)
  278. # Adding with negative index
  279. prop = seq_property(item_a, item_a, item_a)
  280. with constant_refcount(item_b):
  281. prop.insert(-2, item_b)
  282. assert tuple(prop) == (item_a, item_b, item_a, item_a)
  283. # Adding at the end with overflowing index
  284. prop = seq_property(item_a, item_a, item_a)
  285. with constant_refcount(item_b):
  286. prop.insert(2345, item_b)
  287. assert tuple(prop) == (item_a, item_a, item_a, item_b)
  288. # Adding at the beginning with negative overflowing index
  289. prop = seq_property(item_a, item_a, item_a)
  290. with constant_refcount(item_b):
  291. prop.insert(-2345, item_b)
  292. assert tuple(prop) == (item_b, item_a, item_a, item_a)
  293. def test_seq_property_extend():
  294. prop = seq_property(item_a)
  295. with constant_refcount(item_b):
  296. prop.extend((item_b, item_c))
  297. assert tuple(prop) == (item_a, item_b, item_c)
  298. with pytest.raises(TypeError):
  299. prop.extend(None)
  300. with pytest.raises(TypeError):
  301. prop.extend("nonsense")
  302. with pytest.raises(TypeError):
  303. prop.extend(item_a)
  304. with pytest.raises(TypeError):
  305. prop.extend(item_a, item_b)
  306. with pytest.raises(TypeError):
  307. prop.extend()
  308. with pytest.raises(TypeError):
  309. prop.extend((item_a, None))
  310. with pytest.raises(TypeError):
  311. prop.extend(["nonsense"])
  312. # The next tests are for MAKE_MAP_PROPERTY.
  313. @pytest.fixture
  314. def map_property(**items):
  315. """ Returns a mapping property initialized with the given values. """
  316. # It doesn't matter which property we use; I just happened to pick
  317. # NodePath.tags.
  318. np = core.NodePath("")
  319. for k, v in items.items():
  320. np.set_tag(k, v)
  321. return np.tags
  322. def test_map_property_abc():
  323. prop = map_property()
  324. assert isinstance(prop, collections.Container)
  325. assert isinstance(prop, collections.Sized)
  326. assert isinstance(prop, collections.Iterable)
  327. assert isinstance(prop, collections.MutableMapping)
  328. assert isinstance(prop, collections.Mapping)
  329. def test_map_property_empty():
  330. prop = map_property()
  331. assert not prop
  332. assert len(prop) == 0
  333. with pytest.raises(KeyError):
  334. prop.popitem()
  335. with pytest.raises(KeyError):
  336. prop['nonsense']
  337. def test_map_property_getitem():
  338. key = 'key'
  339. value = 'value'
  340. prop = map_property(**{key: value})
  341. with constant_refcount(key):
  342. with constant_refcount(value):
  343. assert prop[key] == value
  344. with pytest.raises(KeyError):
  345. prop['nonsense']
  346. with pytest.raises(TypeError):
  347. prop[None]
  348. def test_map_property_setitem():
  349. key = 'key'
  350. value = 'value'
  351. prop = map_property()
  352. # Setting new key
  353. with constant_refcount(key):
  354. with constant_refcount(value):
  355. prop[key] = value
  356. assert prop[key] == value
  357. # Setting existing key
  358. with constant_refcount(key):
  359. with constant_refcount(value):
  360. prop[key] = value
  361. assert prop[key] == value
  362. # Unknown key
  363. with pytest.raises(TypeError):
  364. prop[None] = value
  365. # Unknown value
  366. with pytest.raises(TypeError):
  367. prop[key] = None
  368. def test_map_property_delitem():
  369. key = 'key'
  370. value = 'value'
  371. prop = map_property(**{key: value})
  372. with constant_refcount(key):
  373. with constant_refcount(value):
  374. del prop[key]
  375. with pytest.raises(KeyError):
  376. assert prop[key]
  377. # Nonexistent key
  378. with pytest.raises(KeyError):
  379. del prop['nonsense']
  380. # Invalid type key
  381. with pytest.raises(TypeError):
  382. del prop[None]
  383. def test_map_property_contains():
  384. prop = map_property(key='value')
  385. assert 'key' in prop
  386. assert None not in prop
  387. assert 'value' not in prop
  388. def test_map_property_get():
  389. key = 'key'
  390. value = 'value'
  391. prop = map_property(**{key: value})
  392. default = 'default'
  393. with constant_refcount(key):
  394. with constant_refcount(default):
  395. assert prop.get(key) == value
  396. with constant_refcount(key):
  397. with constant_refcount(default):
  398. assert prop.get(key, default) == value
  399. assert prop.get('unknown') is None
  400. with constant_refcount(default):
  401. assert prop.get('unknown', default) == default
  402. def test_map_property_pop():
  403. key = 'key'
  404. value = 'value'
  405. prop = map_property(**{key: value})
  406. assert prop.pop('nonsense', None) is None
  407. assert prop.pop('nonsense', 'default') == 'default'
  408. assert prop.pop('key', 'default') == 'value'
  409. assert 'key' not in prop
  410. def test_map_property_popitem():
  411. key = 'key'
  412. value = 'value'
  413. prop = map_property(**{key: value})
  414. assert prop.popitem() == (key, value)
  415. with pytest.raises(KeyError):
  416. assert prop.popitem()
  417. def test_map_property_clear():
  418. prop = map_property(key='value', key2='value2')
  419. prop.clear()
  420. assert len(prop) == 0
  421. def test_map_property_setdefault():
  422. prop = map_property(key='value')
  423. # Don't change value of key that already exists
  424. prop.setdefault('key', 'value2')
  425. assert prop['key'] == 'value'
  426. # Change values of nonexistent key
  427. prop.setdefault('key2', 'value2')
  428. assert prop['key2'] == 'value2'
  429. # These should error because you can't set None on this property
  430. with pytest.raises(TypeError):
  431. prop.setdefault('key3', None)
  432. with pytest.raises(TypeError):
  433. prop.setdefault('key3')
  434. def test_map_property_update():
  435. prop = map_property()
  436. # Empty update
  437. prop.update()
  438. # Passing in dictionary
  439. prop.update({'key': 'value'})
  440. # Passing in keywords
  441. prop.update(key2='value2')
  442. # Don't pass in both!
  443. with pytest.raises(TypeError):
  444. prop.update({}, k='v')
  445. assert prop['key'] == 'value'
  446. assert prop['key2'] == 'value2'
  447. def test_map_property_keys():
  448. prop = map_property(key='value', key2='value2')
  449. assert isinstance(prop.keys(), collections.MappingView)
  450. assert frozenset(prop.keys()) == frozenset(('key', 'key2'))
  451. def test_map_property_values():
  452. prop = map_property(key='value', key2='value2')
  453. assert isinstance(prop.values(), collections.ValuesView)
  454. assert frozenset(prop.values()) == frozenset(('value', 'value2'))
  455. def test_map_property_items():
  456. prop = map_property(key='value', key2='value2')
  457. assert isinstance(prop.items(), collections.MappingView)
  458. assert frozenset(prop.items()) == frozenset((('key', 'value'), ('key2', 'value2')))