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