test_none_safe.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. """ Test that functions are reasonably behaved with None as input.
  2. Typed Cython objects (like dict) may also be None. Using functions from
  3. Python's C API that expect a specific type but receive None instead can cause
  4. problems such as throwing an uncatchable SystemError (and some systems may
  5. segfault instead). We obviously don't what that to happen! As the tests
  6. below discovered, this turned out to be a rare occurence. The only changes
  7. required were to use `d.copy()` instead of `PyDict_Copy(d)`, and to always
  8. return Python objects from functions instead of int or bint (so exceptions
  9. can propagate).
  10. The vast majority of functions throw TypeError. The vast majority of
  11. functions also behave the same in `toolz` and `cytoolz`. However, there
  12. are a few minor exceptions. Since passing None to functions are edge cases
  13. that don't have well-established behavior yet (other than raising TypeError),
  14. the tests in this file serve to verify that the behavior is at least
  15. reasonably well-behaved and don't cause SystemErrors.
  16. """
  17. # XXX: This file could be back-ported to `toolz` once unified testing exists.
  18. import cytoolz
  19. from cytoolz import *
  20. from cytoolz.utils import raises
  21. from operator import add
  22. class GenException(object):
  23. def __init__(self, exc):
  24. self.exc = exc
  25. def __iter__(self):
  26. return self
  27. def __next__(self):
  28. raise self.exc
  29. def next(self):
  30. raise self.exc
  31. def test_dicttoolz():
  32. tested = []
  33. assert raises((TypeError, AttributeError), lambda: assoc(None, 1, 2))
  34. tested.append('assoc')
  35. assert raises((TypeError, AttributeError), lambda: dissoc(None, 1))
  36. tested.append('dissoc')
  37. # XXX
  38. assert (raises(TypeError, lambda: get_in(None, {})) or
  39. get_in(None, {}) is None)
  40. assert raises(TypeError, lambda: get_in(None, {}, no_default=True))
  41. assert get_in([0, 1], None) is None
  42. assert raises(TypeError, lambda: get_in([0, 1], None, no_default=True))
  43. tested.append('get_in')
  44. assert raises(TypeError, lambda: keyfilter(None, {1: 2}))
  45. assert raises((AttributeError, TypeError), lambda: keyfilter(identity, None))
  46. tested.append('keyfilter')
  47. # XXX
  48. assert (raises(TypeError, lambda: keymap(None, {1: 2})) or
  49. keymap(None, {1: 2}) == {(1,): 2})
  50. assert raises((AttributeError, TypeError), lambda: keymap(identity, None))
  51. tested.append('keymap')
  52. assert raises(TypeError, lambda: merge(None))
  53. assert raises((TypeError, AttributeError), lambda: merge(None, None))
  54. tested.append('merge')
  55. assert raises(TypeError, lambda: merge_with(None, {1: 2}, {3: 4}))
  56. assert raises(TypeError, lambda: merge_with(identity, None))
  57. assert raises((TypeError, AttributeError),
  58. lambda: merge_with(identity, None, None))
  59. tested.append('merge_with')
  60. assert raises(TypeError, lambda: update_in({1: {2: 3}}, [1, 2], None))
  61. assert raises(TypeError, lambda: update_in({1: {2: 3}}, None, identity))
  62. assert raises((TypeError, AttributeError),
  63. lambda: update_in(None, [1, 2], identity))
  64. tested.append('update_in')
  65. assert raises(TypeError, lambda: valfilter(None, {1: 2}))
  66. assert raises((AttributeError, TypeError), lambda: valfilter(identity, None))
  67. tested.append('valfilter')
  68. # XXX
  69. assert (raises(TypeError, lambda: valmap(None, {1: 2})) or
  70. valmap(None, {1: 2}) == {1: (2,)})
  71. assert raises((AttributeError, TypeError), lambda: valmap(identity, None))
  72. tested.append('valmap')
  73. assert (raises(TypeError, lambda: itemmap(None, {1: 2})) or
  74. itemmap(None, {1: 2}) == {1: (2,)})
  75. assert raises((AttributeError, TypeError), lambda: itemmap(identity, None))
  76. tested.append('itemmap')
  77. assert raises(TypeError, lambda: itemfilter(None, {1: 2}))
  78. assert raises((AttributeError, TypeError), lambda: itemfilter(identity, None))
  79. tested.append('itemfilter')
  80. assert raises((AttributeError, TypeError), lambda: assoc_in(None, [2, 2], 3))
  81. assert raises(TypeError, lambda: assoc_in({}, None, 3))
  82. tested.append('assoc_in')
  83. s1 = set(tested)
  84. s2 = set(cytoolz.dicttoolz.__all__)
  85. assert s1 == s2, '%s not tested for being None-safe' % ', '.join(s2 - s1)
  86. def test_functoolz():
  87. tested = []
  88. assert raises(TypeError, lambda: complement(None)())
  89. tested.append('complement')
  90. assert compose(None) is None
  91. assert raises(TypeError, lambda: compose(None, None)())
  92. tested.append('compose')
  93. assert compose_left(None) is None
  94. assert raises(TypeError, lambda: compose_left(None, None)())
  95. tested.append('compose_left')
  96. assert raises(TypeError, lambda: curry(None))
  97. tested.append('curry')
  98. assert raises(TypeError, lambda: do(None, 1))
  99. tested.append('do')
  100. assert identity(None) is None
  101. tested.append('identity')
  102. assert raises(TypeError, lambda: juxt(None))
  103. assert raises(TypeError, lambda: list(juxt(None, None)()))
  104. tested.append('juxt')
  105. assert memoize(identity, key=None)(1) == 1
  106. assert memoize(identity, cache=None)(1) == 1
  107. tested.append('memoize')
  108. assert raises(TypeError, lambda: pipe(1, None))
  109. tested.append('pipe')
  110. assert thread_first(1, None) is None
  111. tested.append('thread_first')
  112. assert thread_last(1, None) is None
  113. tested.append('thread_last')
  114. assert flip(lambda a, b: (a, b))(None)(None) == (None, None)
  115. tested.append('flip')
  116. assert apply(identity, None) is None
  117. assert raises(TypeError, lambda: apply(None))
  118. tested.append('apply')
  119. excepts(None, lambda x: x)
  120. excepts(TypeError, None)
  121. tested.append('excepts')
  122. s1 = set(tested)
  123. s2 = set(cytoolz.functoolz.__all__)
  124. assert s1 == s2, '%s not tested for being None-safe' % ', '.join(s2 - s1)
  125. def test_itertoolz():
  126. tested = []
  127. assert raises(TypeError, lambda: list(accumulate(None, [1, 2])))
  128. assert raises(TypeError, lambda: list(accumulate(identity, None)))
  129. tested.append('accumulate')
  130. assert raises(TypeError, lambda: concat(None))
  131. assert raises(TypeError, lambda: list(concat([None])))
  132. tested.append('concat')
  133. assert raises(TypeError, lambda: list(concatv(None)))
  134. tested.append('concatv')
  135. assert raises(TypeError, lambda: list(cons(1, None)))
  136. tested.append('cons')
  137. assert raises(TypeError, lambda: count(None))
  138. tested.append('count')
  139. # XXX
  140. assert (raises(TypeError, lambda: list(drop(None, [1, 2]))) or
  141. list(drop(None, [1, 2])) == [1, 2])
  142. assert raises(TypeError, lambda: list(drop(1, None)))
  143. tested.append('drop')
  144. assert raises(TypeError, lambda: first(None))
  145. tested.append('first')
  146. assert raises(TypeError, lambda: frequencies(None))
  147. tested.append('frequencies')
  148. assert raises(TypeError, lambda: get(1, None))
  149. assert raises(TypeError, lambda: get([1, 2], None))
  150. tested.append('get')
  151. assert raises(TypeError, lambda: groupby(None, [1, 2]))
  152. assert raises(TypeError, lambda: groupby(identity, None))
  153. tested.append('groupby')
  154. assert raises(TypeError, lambda: list(interleave(None)))
  155. assert raises(TypeError, lambda: list(interleave([None, None])))
  156. assert raises(TypeError,
  157. lambda: list(interleave([[1, 2], GenException(ValueError)],
  158. pass_exceptions=None)))
  159. tested.append('interleave')
  160. assert raises(TypeError, lambda: list(interpose(1, None)))
  161. tested.append('interpose')
  162. assert raises(TypeError, lambda: isdistinct(None))
  163. tested.append('isdistinct')
  164. assert isiterable(None) is False
  165. tested.append('isiterable')
  166. assert raises(TypeError, lambda: list(iterate(None, 1)))
  167. tested.append('iterate')
  168. assert raises(TypeError, lambda: last(None))
  169. tested.append('last')
  170. # XXX
  171. assert (raises(TypeError, lambda: list(mapcat(None, [[1], [2]]))) or
  172. list(mapcat(None, [[1], [2]])) == [[1], [2]])
  173. assert raises(TypeError, lambda: list(mapcat(identity, [None, [2]])))
  174. assert raises(TypeError, lambda: list(mapcat(identity, None)))
  175. tested.append('mapcat')
  176. assert raises(TypeError, lambda: list(merge_sorted(None, [1, 2])))
  177. tested.append('merge_sorted')
  178. assert raises(TypeError, lambda: nth(None, [1, 2]))
  179. assert raises(TypeError, lambda: nth(0, None))
  180. tested.append('nth')
  181. assert raises(TypeError, lambda: partition(None, [1, 2, 3]))
  182. assert raises(TypeError, lambda: partition(1, None))
  183. tested.append('partition')
  184. assert raises(TypeError, lambda: list(partition_all(None, [1, 2, 3])))
  185. assert raises(TypeError, lambda: list(partition_all(1, None)))
  186. tested.append('partition_all')
  187. assert raises(TypeError, lambda: list(pluck(None, [[1], [2]])))
  188. assert raises(TypeError, lambda: list(pluck(0, [None, [2]])))
  189. assert raises(TypeError, lambda: list(pluck(0, None)))
  190. tested.append('pluck')
  191. assert raises(TypeError, lambda: reduceby(None, add, [1, 2, 3], 0))
  192. assert raises(TypeError, lambda: reduceby(identity, None, [1, 2, 3], 0))
  193. assert raises(TypeError, lambda: reduceby(identity, add, None, 0))
  194. tested.append('reduceby')
  195. assert raises(TypeError, lambda: list(remove(None, [1, 2])))
  196. assert raises(TypeError, lambda: list(remove(identity, None)))
  197. tested.append('remove')
  198. assert raises(TypeError, lambda: second(None))
  199. tested.append('second')
  200. # XXX
  201. assert (raises(TypeError, lambda: list(sliding_window(None, [1, 2, 3]))) or
  202. list(sliding_window(None, [1, 2, 3])) == [])
  203. assert raises(TypeError, lambda: list(sliding_window(1, None)))
  204. tested.append('sliding_window')
  205. # XXX
  206. assert (raises(TypeError, lambda: list(take(None, [1, 2])) == [1, 2]) or
  207. list(take(None, [1, 2])) == [1, 2])
  208. assert raises(TypeError, lambda: list(take(1, None)))
  209. tested.append('take')
  210. # XXX
  211. assert (raises(TypeError, lambda: list(tail(None, [1, 2])) == [1, 2]) or
  212. list(tail(None, [1, 2])) == [1, 2])
  213. assert raises(TypeError, lambda: list(tail(1, None)))
  214. tested.append('tail')
  215. # XXX
  216. assert (raises(TypeError, lambda: list(take_nth(None, [1, 2]))) or
  217. list(take_nth(None, [1, 2])) == [1, 2])
  218. assert raises(TypeError, lambda: list(take_nth(1, None)))
  219. tested.append('take_nth')
  220. assert raises(TypeError, lambda: list(unique(None)))
  221. assert list(unique([1, 1, 2], key=None)) == [1, 2]
  222. tested.append('unique')
  223. assert raises(TypeError, lambda: join(first, None, second, (1, 2, 3)))
  224. assert raises(TypeError, lambda: join(first, (1, 2, 3), second, None))
  225. tested.append('join')
  226. assert raises(TypeError, lambda: topk(None, [1, 2, 3]))
  227. assert raises(TypeError, lambda: topk(3, None))
  228. tested.append('topk')
  229. assert raises(TypeError, lambda: list(diff(None, [1, 2, 3])))
  230. assert raises(TypeError, lambda: list(diff(None)))
  231. assert raises(TypeError, lambda: list(diff([None])))
  232. assert raises(TypeError, lambda: list(diff([None, None])))
  233. tested.append('diff')
  234. assert raises(TypeError, lambda: peek(None))
  235. tested.append('peek')
  236. assert raises(TypeError, lambda: peekn(None, [1, 2, 3]))
  237. assert raises(TypeError, lambda: peekn(3, None))
  238. tested.append('peekn')
  239. assert raises(TypeError, lambda: list(random_sample(None, [1])))
  240. assert raises(TypeError, lambda: list(random_sample(0.1, None)))
  241. tested.append('random_sample')
  242. s1 = set(tested)
  243. s2 = set(cytoolz.itertoolz.__all__)
  244. assert s1 == s2, '%s not tested for being None-safe' % ', '.join(s2 - s1)
  245. def test_recipes():
  246. tested = []
  247. # XXX
  248. assert (raises(TypeError, lambda: countby(None, [1, 2])) or
  249. countby(None, [1, 2]) == {(1,): 1, (2,): 1})
  250. assert raises(TypeError, lambda: countby(identity, None))
  251. tested.append('countby')
  252. # XXX
  253. assert (raises(TypeError, lambda: list(partitionby(None, [1, 2]))) or
  254. list(partitionby(None, [1, 2])) == [(1,), (2,)])
  255. assert raises(TypeError, lambda: list(partitionby(identity, None)))
  256. tested.append('partitionby')
  257. s1 = set(tested)
  258. s2 = set(cytoolz.recipes.__all__)
  259. assert s1 == s2, '%s not tested for being None-safe' % ', '.join(s2 - s1)