test_callback.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import math
  2. import textwrap
  3. import sys
  4. import pytest
  5. import threading
  6. import traceback
  7. import time
  8. import random
  9. import numpy as np
  10. from numpy.testing import assert_, assert_equal, IS_PYPY
  11. from . import util
  12. class TestF77Callback(util.F2PyTest):
  13. code = """
  14. subroutine t(fun,a)
  15. integer a
  16. cf2py intent(out) a
  17. external fun
  18. call fun(a)
  19. end
  20. subroutine func(a)
  21. cf2py intent(in,out) a
  22. integer a
  23. a = a + 11
  24. end
  25. subroutine func0(a)
  26. cf2py intent(out) a
  27. integer a
  28. a = 11
  29. end
  30. subroutine t2(a)
  31. cf2py intent(callback) fun
  32. integer a
  33. cf2py intent(out) a
  34. external fun
  35. call fun(a)
  36. end
  37. subroutine string_callback(callback, a)
  38. external callback
  39. double precision callback
  40. double precision a
  41. character*1 r
  42. cf2py intent(out) a
  43. r = 'r'
  44. a = callback(r)
  45. end
  46. subroutine string_callback_array(callback, cu, lencu, a)
  47. external callback
  48. integer callback
  49. integer lencu
  50. character*8 cu(lencu)
  51. integer a
  52. cf2py intent(out) a
  53. a = callback(cu, lencu)
  54. end
  55. subroutine hidden_callback(a, r)
  56. external global_f
  57. cf2py intent(callback, hide) global_f
  58. integer a, r, global_f
  59. cf2py intent(out) r
  60. r = global_f(a)
  61. end
  62. subroutine hidden_callback2(a, r)
  63. external global_f
  64. integer a, r, global_f
  65. cf2py intent(out) r
  66. r = global_f(a)
  67. end
  68. """
  69. @pytest.mark.parametrize('name', 't,t2'.split(','))
  70. def test_all(self, name):
  71. self.check_function(name)
  72. @pytest.mark.xfail(IS_PYPY,
  73. reason="PyPy cannot modify tp_doc after PyType_Ready")
  74. def test_docstring(self):
  75. expected = textwrap.dedent("""\
  76. a = t(fun,[fun_extra_args])
  77. Wrapper for ``t``.
  78. Parameters
  79. ----------
  80. fun : call-back function
  81. Other Parameters
  82. ----------------
  83. fun_extra_args : input tuple, optional
  84. Default: ()
  85. Returns
  86. -------
  87. a : int
  88. Notes
  89. -----
  90. Call-back functions::
  91. def fun(): return a
  92. Return objects:
  93. a : int
  94. """)
  95. assert_equal(self.module.t.__doc__, expected)
  96. def check_function(self, name):
  97. t = getattr(self.module, name)
  98. r = t(lambda: 4)
  99. assert_(r == 4, repr(r))
  100. r = t(lambda a: 5, fun_extra_args=(6,))
  101. assert_(r == 5, repr(r))
  102. r = t(lambda a: a, fun_extra_args=(6,))
  103. assert_(r == 6, repr(r))
  104. r = t(lambda a: 5 + a, fun_extra_args=(7,))
  105. assert_(r == 12, repr(r))
  106. r = t(lambda a: math.degrees(a), fun_extra_args=(math.pi,))
  107. assert_(r == 180, repr(r))
  108. r = t(math.degrees, fun_extra_args=(math.pi,))
  109. assert_(r == 180, repr(r))
  110. r = t(self.module.func, fun_extra_args=(6,))
  111. assert_(r == 17, repr(r))
  112. r = t(self.module.func0)
  113. assert_(r == 11, repr(r))
  114. r = t(self.module.func0._cpointer)
  115. assert_(r == 11, repr(r))
  116. class A:
  117. def __call__(self):
  118. return 7
  119. def mth(self):
  120. return 9
  121. a = A()
  122. r = t(a)
  123. assert_(r == 7, repr(r))
  124. r = t(a.mth)
  125. assert_(r == 9, repr(r))
  126. @pytest.mark.skipif(sys.platform=='win32',
  127. reason='Fails with MinGW64 Gfortran (Issue #9673)')
  128. def test_string_callback(self):
  129. def callback(code):
  130. if code == 'r':
  131. return 0
  132. else:
  133. return 1
  134. f = getattr(self.module, 'string_callback')
  135. r = f(callback)
  136. assert_(r == 0, repr(r))
  137. @pytest.mark.skipif(sys.platform=='win32',
  138. reason='Fails with MinGW64 Gfortran (Issue #9673)')
  139. def test_string_callback_array(self):
  140. # See gh-10027
  141. cu = np.zeros((1, 8), 'S1')
  142. def callback(cu, lencu):
  143. if cu.shape != (lencu, 8):
  144. return 1
  145. if cu.dtype != 'S1':
  146. return 2
  147. if not np.all(cu == b''):
  148. return 3
  149. return 0
  150. f = getattr(self.module, 'string_callback_array')
  151. res = f(callback, cu, len(cu))
  152. assert_(res == 0, repr(res))
  153. def test_threadsafety(self):
  154. # Segfaults if the callback handling is not threadsafe
  155. errors = []
  156. def cb():
  157. # Sleep here to make it more likely for another thread
  158. # to call their callback at the same time.
  159. time.sleep(1e-3)
  160. # Check reentrancy
  161. r = self.module.t(lambda: 123)
  162. assert_(r == 123)
  163. return 42
  164. def runner(name):
  165. try:
  166. for j in range(50):
  167. r = self.module.t(cb)
  168. assert_(r == 42)
  169. self.check_function(name)
  170. except Exception:
  171. errors.append(traceback.format_exc())
  172. threads = [threading.Thread(target=runner, args=(arg,))
  173. for arg in ("t", "t2") for n in range(20)]
  174. for t in threads:
  175. t.start()
  176. for t in threads:
  177. t.join()
  178. errors = "\n\n".join(errors)
  179. if errors:
  180. raise AssertionError(errors)
  181. def test_hidden_callback(self):
  182. try:
  183. self.module.hidden_callback(2)
  184. except Exception as msg:
  185. assert_(str(msg).startswith('Callback global_f not defined'))
  186. try:
  187. self.module.hidden_callback2(2)
  188. except Exception as msg:
  189. assert_(str(msg).startswith('cb: Callback global_f not defined'))
  190. self.module.global_f = lambda x: x + 1
  191. r = self.module.hidden_callback(2)
  192. assert_(r == 3)
  193. self.module.global_f = lambda x: x + 2
  194. r = self.module.hidden_callback(2)
  195. assert_(r == 4)
  196. del self.module.global_f
  197. try:
  198. self.module.hidden_callback(2)
  199. except Exception as msg:
  200. assert_(str(msg).startswith('Callback global_f not defined'))
  201. self.module.global_f = lambda x=0: x + 3
  202. r = self.module.hidden_callback(2)
  203. assert_(r == 5)
  204. # reproducer of gh18341
  205. r = self.module.hidden_callback2(2)
  206. assert_(r == 3)
  207. class TestF77CallbackPythonTLS(TestF77Callback):
  208. """
  209. Callback tests using Python thread-local storage instead of
  210. compiler-provided
  211. """
  212. options = ["-DF2PY_USE_PYTHON_TLS"]
  213. class TestF90Callback(util.F2PyTest):
  214. suffix = '.f90'
  215. code = textwrap.dedent(
  216. """
  217. function gh17797(f, y) result(r)
  218. external f
  219. integer(8) :: r, f
  220. integer(8), dimension(:) :: y
  221. r = f(0)
  222. r = r + sum(y)
  223. end function gh17797
  224. """)
  225. def test_gh17797(self):
  226. def incr(x):
  227. return x + 123
  228. y = np.array([1, 2, 3], dtype=np.int64)
  229. r = self.module.gh17797(incr, y)
  230. assert r == 123 + 1 + 2 + 3
  231. class TestGH18335(util.F2PyTest):
  232. """The reproduction of the reported issue requires specific input that
  233. extensions may break the issue conditions, so the reproducer is
  234. implemented as a separate test class. Do not extend this test with
  235. other tests!
  236. """
  237. suffix = '.f90'
  238. code = textwrap.dedent(
  239. """
  240. ! When gh18335_workaround is defined as an extension,
  241. ! the issue cannot be reproduced.
  242. !subroutine gh18335_workaround(f, y)
  243. ! implicit none
  244. ! external f
  245. ! integer(kind=1) :: y(1)
  246. ! call f(y)
  247. !end subroutine gh18335_workaround
  248. function gh18335(f) result (r)
  249. implicit none
  250. external f
  251. integer(kind=1) :: y(1), r
  252. y(1) = 123
  253. call f(y)
  254. r = y(1)
  255. end function gh18335
  256. """)
  257. def test_gh18335(self):
  258. def foo(x):
  259. x[0] += 1
  260. y = np.array([1, 2, 3], dtype=np.int8)
  261. r = self.module.gh18335(foo)
  262. assert r == 123 + 1