##// END OF EJS Templates
Fix some tests on Python 3.13 RC1 (#14504)...
M Bussonnier -
r28829:82eba444 merge
parent child Browse files
Show More
@@ -1,579 +1,592 b''
1 """Tests for debugging machinery.
1 """Tests for debugging machinery.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import builtins
7 import builtins
8 import os
8 import os
9 import sys
9 import sys
10 import platform
10 import platform
11
11
12 from tempfile import NamedTemporaryFile
12 from tempfile import NamedTemporaryFile
13 from textwrap import dedent
13 from textwrap import dedent
14 from unittest.mock import patch
14 from unittest.mock import patch
15
15
16 from IPython.core import debugger
16 from IPython.core import debugger
17 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
17 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
18 from IPython.testing.decorators import skip_win32
18 from IPython.testing.decorators import skip_win32
19 import pytest
19 import pytest
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Helper classes, from CPython's Pdb test suite
22 # Helper classes, from CPython's Pdb test suite
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 class _FakeInput(object):
25 class _FakeInput(object):
26 """
26 """
27 A fake input stream for pdb's interactive debugger. Whenever a
27 A fake input stream for pdb's interactive debugger. Whenever a
28 line is read, print it (to simulate the user typing it), and then
28 line is read, print it (to simulate the user typing it), and then
29 return it. The set of lines to return is specified in the
29 return it. The set of lines to return is specified in the
30 constructor; they should not have trailing newlines.
30 constructor; they should not have trailing newlines.
31 """
31 """
32 def __init__(self, lines):
32 def __init__(self, lines):
33 self.lines = iter(lines)
33 self.lines = iter(lines)
34
34
35 def readline(self):
35 def readline(self):
36 line = next(self.lines)
36 line = next(self.lines)
37 print(line)
37 print(line)
38 return line+'\n'
38 return line+'\n'
39
39
40 class PdbTestInput(object):
40 class PdbTestInput(object):
41 """Context manager that makes testing Pdb in doctests easier."""
41 """Context manager that makes testing Pdb in doctests easier."""
42
42
43 def __init__(self, input):
43 def __init__(self, input):
44 self.input = input
44 self.input = input
45
45
46 def __enter__(self):
46 def __enter__(self):
47 self.real_stdin = sys.stdin
47 self.real_stdin = sys.stdin
48 sys.stdin = _FakeInput(self.input)
48 sys.stdin = _FakeInput(self.input)
49
49
50 def __exit__(self, *exc):
50 def __exit__(self, *exc):
51 sys.stdin = self.real_stdin
51 sys.stdin = self.real_stdin
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Tests
54 # Tests
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 def test_ipdb_magics():
57 def test_ipdb_magics():
58 '''Test calling some IPython magics from ipdb.
58 '''Test calling some IPython magics from ipdb.
59
59
60 First, set up some test functions and classes which we can inspect.
60 First, set up some test functions and classes which we can inspect.
61
61
62 >>> class ExampleClass(object):
62 In [1]: class ExampleClass(object):
63 ... """Docstring for ExampleClass."""
63 ...: """Docstring for ExampleClass."""
64 ... def __init__(self):
64 ...: def __init__(self):
65 ... """Docstring for ExampleClass.__init__"""
65 ...: """Docstring for ExampleClass.__init__"""
66 ... pass
66 ...: pass
67 ... def __str__(self):
67 ...: def __str__(self):
68 ... return "ExampleClass()"
68 ...: return "ExampleClass()"
69
70 >>> def example_function(x, y, z="hello"):
71 ... """Docstring for example_function."""
72 ... pass
73
69
74 >>> old_trace = sys.gettrace()
70 In [2]: def example_function(x, y, z="hello"):
71 ...: """Docstring for example_function."""
72 ...: pass
75
73
76 Create a function which triggers ipdb.
74 In [3]: old_trace = sys.gettrace()
77
75
78 >>> def trigger_ipdb():
76 Create a function which triggers ipdb.
79 ... a = ExampleClass()
80 ... debugger.Pdb().set_trace()
81
77
82 >>> with PdbTestInput([
78 In [4]: def trigger_ipdb():
83 ... 'pdef example_function',
79 ...: a = ExampleClass()
84 ... 'pdoc ExampleClass',
80 ...: debugger.Pdb().set_trace()
85 ... 'up',
81
86 ... 'down',
82 Run ipdb with faked input & check output. Because of a difference between
87 ... 'list',
83 Python 3.13 & older versions, the first bit of the output is inconsistent.
88 ... 'pinfo a',
84 We need to use ... to accommodate that, so the examples have to use IPython
89 ... 'll',
85 prompts so that ... is distinct from the Python PS2 prompt.
90 ... 'continue',
86
91 ... ]):
87 In [5]: with PdbTestInput([
92 ... trigger_ipdb()
88 ...: 'pdef example_function',
93 --Return--
89 ...: 'pdoc ExampleClass',
94 None
90 ...: 'up',
95 > <doctest ...>(3)trigger_ipdb()
91 ...: 'down',
92 ...: 'list',
93 ...: 'pinfo a',
94 ...: 'll',
95 ...: 'continue',
96 ...: ]):
97 ...: trigger_ipdb()
98 ...> <doctest ...>(3)trigger_ipdb()
96 1 def trigger_ipdb():
99 1 def trigger_ipdb():
97 2 a = ExampleClass()
100 2 a = ExampleClass()
98 ----> 3 debugger.Pdb().set_trace()
101 ----> 3 debugger.Pdb().set_trace()
99 <BLANKLINE>
102 <BLANKLINE>
100 ipdb> pdef example_function
103 ipdb> pdef example_function
101 example_function(x, y, z='hello')
104 example_function(x, y, z='hello')
102 ipdb> pdoc ExampleClass
105 ipdb> pdoc ExampleClass
103 Class docstring:
106 Class docstring:
104 Docstring for ExampleClass.
107 Docstring for ExampleClass.
105 Init docstring:
108 Init docstring:
106 Docstring for ExampleClass.__init__
109 Docstring for ExampleClass.__init__
107 ipdb> up
110 ipdb> up
108 > <doctest ...>(11)<module>()
111 > <doctest ...>(11)<module>()
109 7 'pinfo a',
112 7 'pinfo a',
110 8 'll',
113 8 'll',
111 9 'continue',
114 9 'continue',
112 10 ]):
115 10 ]):
113 ---> 11 trigger_ipdb()
116 ---> 11 trigger_ipdb()
114 <BLANKLINE>
117 <BLANKLINE>
115 ipdb> down
118 ipdb> down...
116 None
117 > <doctest ...>(3)trigger_ipdb()
119 > <doctest ...>(3)trigger_ipdb()
118 1 def trigger_ipdb():
120 1 def trigger_ipdb():
119 2 a = ExampleClass()
121 2 a = ExampleClass()
120 ----> 3 debugger.Pdb().set_trace()
122 ----> 3 debugger.Pdb().set_trace()
121 <BLANKLINE>
123 <BLANKLINE>
122 ipdb> list
124 ipdb> list
123 1 def trigger_ipdb():
125 1 def trigger_ipdb():
124 2 a = ExampleClass()
126 2 a = ExampleClass()
125 ----> 3 debugger.Pdb().set_trace()
127 ----> 3 debugger.Pdb().set_trace()
126 <BLANKLINE>
128 <BLANKLINE>
127 ipdb> pinfo a
129 ipdb> pinfo a
128 Type: ExampleClass
130 Type: ExampleClass
129 String form: ExampleClass()
131 String form: ExampleClass()
130 Namespace: Local...
132 Namespace: Local...
131 Docstring: Docstring for ExampleClass.
133 Docstring: Docstring for ExampleClass.
132 Init docstring: Docstring for ExampleClass.__init__
134 Init docstring: Docstring for ExampleClass.__init__
133 ipdb> ll
135 ipdb> ll
134 1 def trigger_ipdb():
136 1 def trigger_ipdb():
135 2 a = ExampleClass()
137 2 a = ExampleClass()
136 ----> 3 debugger.Pdb().set_trace()
138 ----> 3 debugger.Pdb().set_trace()
137 <BLANKLINE>
139 <BLANKLINE>
138 ipdb> continue
140 ipdb> continue
139
141
140 Restore previous trace function, e.g. for coverage.py
142 Restore previous trace function, e.g. for coverage.py
141
143
142 >>> sys.settrace(old_trace)
144 In [6]: sys.settrace(old_trace)
143 '''
145 '''
144
146
145 def test_ipdb_magics2():
147 def test_ipdb_magics2():
146 '''Test ipdb with a very short function.
148 '''Test ipdb with a very short function.
147
149
148 >>> old_trace = sys.gettrace()
150 >>> old_trace = sys.gettrace()
149
151
150 >>> def bar():
152 >>> def bar():
151 ... pass
153 ... pass
152
154
153 Run ipdb.
155 Run ipdb.
154
156
155 >>> with PdbTestInput([
157 >>> with PdbTestInput([
156 ... 'continue',
158 ... 'continue',
157 ... ]):
159 ... ]):
158 ... debugger.Pdb().runcall(bar)
160 ... debugger.Pdb().runcall(bar)
159 > <doctest ...>(2)bar()
161 > <doctest ...>(2)bar()
160 1 def bar():
162 1 def bar():
161 ----> 2 pass
163 ----> 2 pass
162 <BLANKLINE>
164 <BLANKLINE>
163 ipdb> continue
165 ipdb> continue
164
166
165 Restore previous trace function, e.g. for coverage.py
167 Restore previous trace function, e.g. for coverage.py
166
168
167 >>> sys.settrace(old_trace)
169 >>> sys.settrace(old_trace)
168 '''
170 '''
169
171
170 def can_quit():
172 def can_quit():
171 '''Test that quit work in ipydb
173 '''Test that quit work in ipydb
172
174
173 >>> old_trace = sys.gettrace()
175 >>> old_trace = sys.gettrace()
174
176
175 >>> def bar():
177 >>> def bar():
176 ... pass
178 ... pass
177
179
178 >>> with PdbTestInput([
180 >>> with PdbTestInput([
179 ... 'quit',
181 ... 'quit',
180 ... ]):
182 ... ]):
181 ... debugger.Pdb().runcall(bar)
183 ... debugger.Pdb().runcall(bar)
182 > <doctest ...>(2)bar()
184 > <doctest ...>(2)bar()
183 1 def bar():
185 1 def bar():
184 ----> 2 pass
186 ----> 2 pass
185 <BLANKLINE>
187 <BLANKLINE>
186 ipdb> quit
188 ipdb> quit
187
189
188 Restore previous trace function, e.g. for coverage.py
190 Restore previous trace function, e.g. for coverage.py
189
191
190 >>> sys.settrace(old_trace)
192 >>> sys.settrace(old_trace)
191 '''
193 '''
192
194
193
195
194 def can_exit():
196 def can_exit():
195 '''Test that quit work in ipydb
197 '''Test that quit work in ipydb
196
198
197 >>> old_trace = sys.gettrace()
199 >>> old_trace = sys.gettrace()
198
200
199 >>> def bar():
201 >>> def bar():
200 ... pass
202 ... pass
201
203
202 >>> with PdbTestInput([
204 >>> with PdbTestInput([
203 ... 'exit',
205 ... 'exit',
204 ... ]):
206 ... ]):
205 ... debugger.Pdb().runcall(bar)
207 ... debugger.Pdb().runcall(bar)
206 > <doctest ...>(2)bar()
208 > <doctest ...>(2)bar()
207 1 def bar():
209 1 def bar():
208 ----> 2 pass
210 ----> 2 pass
209 <BLANKLINE>
211 <BLANKLINE>
210 ipdb> exit
212 ipdb> exit
211
213
212 Restore previous trace function, e.g. for coverage.py
214 Restore previous trace function, e.g. for coverage.py
213
215
214 >>> sys.settrace(old_trace)
216 >>> sys.settrace(old_trace)
215 '''
217 '''
216
218
217
219
218 def test_interruptible_core_debugger():
220 def test_interruptible_core_debugger():
219 """The debugger can be interrupted.
221 """The debugger can be interrupted.
220
222
221 The presumption is there is some mechanism that causes a KeyboardInterrupt
223 The presumption is there is some mechanism that causes a KeyboardInterrupt
222 (this is implemented in ipykernel). We want to ensure the
224 (this is implemented in ipykernel). We want to ensure the
223 KeyboardInterrupt cause debugging to cease.
225 KeyboardInterrupt cause debugging to cease.
224 """
226 """
225 def raising_input(msg="", called=[0]):
227 def raising_input(msg="", called=[0]):
226 called[0] += 1
228 called[0] += 1
227 assert called[0] == 1, "input() should only be called once!"
229 assert called[0] == 1, "input() should only be called once!"
228 raise KeyboardInterrupt()
230 raise KeyboardInterrupt()
229
231
230 tracer_orig = sys.gettrace()
232 tracer_orig = sys.gettrace()
231 try:
233 try:
232 with patch.object(builtins, "input", raising_input):
234 with patch.object(builtins, "input", raising_input):
233 debugger.InterruptiblePdb().set_trace()
235 debugger.InterruptiblePdb().set_trace()
234 # The way this test will fail is by set_trace() never exiting,
236 # The way this test will fail is by set_trace() never exiting,
235 # resulting in a timeout by the test runner. The alternative
237 # resulting in a timeout by the test runner. The alternative
236 # implementation would involve a subprocess, but that adds issues
238 # implementation would involve a subprocess, but that adds issues
237 # with interrupting subprocesses that are rather complex, so it's
239 # with interrupting subprocesses that are rather complex, so it's
238 # simpler just to do it this way.
240 # simpler just to do it this way.
239 finally:
241 finally:
240 # restore the original trace function
242 # restore the original trace function
241 sys.settrace(tracer_orig)
243 sys.settrace(tracer_orig)
242
244
243
245
244 @skip_win32
246 @skip_win32
245 def test_xmode_skip():
247 def test_xmode_skip():
246 """that xmode skip frames
248 """that xmode skip frames
247
249
248 Not as a doctest as pytest does not run doctests.
250 Not as a doctest as pytest does not run doctests.
249 """
251 """
250 import pexpect
252 import pexpect
251 env = os.environ.copy()
253 env = os.environ.copy()
252 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
254 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
253
255
254 child = pexpect.spawn(
256 child = pexpect.spawn(
255 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
257 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
256 )
258 )
257 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
259 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
258
260
259 child.expect("IPython")
261 child.expect("IPython")
260 child.expect("\n")
262 child.expect("\n")
261 child.expect_exact("In [1]")
263 child.expect_exact("In [1]")
262
264
263 block = dedent(
265 block = dedent(
264 """
266 """
265 def f():
267 def f():
266 __tracebackhide__ = True
268 __tracebackhide__ = True
267 g()
269 g()
268
270
269 def g():
271 def g():
270 raise ValueError
272 raise ValueError
271
273
272 f()
274 f()
273 """
275 """
274 )
276 )
275
277
276 for line in block.splitlines():
278 for line in block.splitlines():
277 child.sendline(line)
279 child.sendline(line)
278 child.expect_exact(line)
280 child.expect_exact(line)
279 child.expect_exact("skipping")
281 child.expect_exact("skipping")
280
282
281 block = dedent(
283 block = dedent(
282 """
284 """
283 def f():
285 def f():
284 __tracebackhide__ = True
286 __tracebackhide__ = True
285 g()
287 g()
286
288
287 def g():
289 def g():
288 from IPython.core.debugger import set_trace
290 from IPython.core.debugger import set_trace
289 set_trace()
291 set_trace()
290
292
291 f()
293 f()
292 """
294 """
293 )
295 )
294
296
295 for line in block.splitlines():
297 for line in block.splitlines():
296 child.sendline(line)
298 child.sendline(line)
297 child.expect_exact(line)
299 child.expect_exact(line)
298
300
299 child.expect("ipdb>")
301 child.expect("ipdb>")
300 child.sendline("w")
302 child.sendline("w")
301 child.expect("hidden")
303 child.expect("hidden")
302 child.expect("ipdb>")
304 child.expect("ipdb>")
303 child.sendline("skip_hidden false")
305 child.sendline("skip_hidden false")
304 child.sendline("w")
306 child.sendline("w")
305 child.expect("__traceba")
307 child.expect("__traceba")
306 child.expect("ipdb>")
308 child.expect("ipdb>")
307
309
308 child.close()
310 child.close()
309
311
310
312
311 skip_decorators_blocks = (
313 skip_decorators_blocks = (
312 """
314 """
313 def helpers_helper():
315 def helpers_helper():
314 pass # should not stop here except breakpoint
316 pass # should not stop here except breakpoint
315 """,
317 """,
316 """
318 """
317 def helper_1():
319 def helper_1():
318 helpers_helper() # should not stop here
320 helpers_helper() # should not stop here
319 """,
321 """,
320 """
322 """
321 def helper_2():
323 def helper_2():
322 pass # should not stop here
324 pass # should not stop here
323 """,
325 """,
324 """
326 """
325 def pdb_skipped_decorator2(function):
327 def pdb_skipped_decorator2(function):
326 def wrapped_fn(*args, **kwargs):
328 def wrapped_fn(*args, **kwargs):
327 __debuggerskip__ = True
329 __debuggerskip__ = True
328 helper_2()
330 helper_2()
329 __debuggerskip__ = False
331 __debuggerskip__ = False
330 result = function(*args, **kwargs)
332 result = function(*args, **kwargs)
331 __debuggerskip__ = True
333 __debuggerskip__ = True
332 helper_2()
334 helper_2()
333 return result
335 return result
334 return wrapped_fn
336 return wrapped_fn
335 """,
337 """,
336 """
338 """
337 def pdb_skipped_decorator(function):
339 def pdb_skipped_decorator(function):
338 def wrapped_fn(*args, **kwargs):
340 def wrapped_fn(*args, **kwargs):
339 __debuggerskip__ = True
341 __debuggerskip__ = True
340 helper_1()
342 helper_1()
341 __debuggerskip__ = False
343 __debuggerskip__ = False
342 result = function(*args, **kwargs)
344 result = function(*args, **kwargs)
343 __debuggerskip__ = True
345 __debuggerskip__ = True
344 helper_2()
346 helper_2()
345 return result
347 return result
346 return wrapped_fn
348 return wrapped_fn
347 """,
349 """,
348 """
350 """
349 @pdb_skipped_decorator
351 @pdb_skipped_decorator
350 @pdb_skipped_decorator2
352 @pdb_skipped_decorator2
351 def bar(x, y):
353 def bar(x, y):
352 return x * y
354 return x * y
353 """,
355 """,
354 """import IPython.terminal.debugger as ipdb""",
356 """import IPython.terminal.debugger as ipdb""",
355 """
357 """
356 def f():
358 def f():
357 ipdb.set_trace()
359 ipdb.set_trace()
358 bar(3, 4)
360 bar(3, 4)
359 """,
361 """,
360 """
362 """
361 f()
363 f()
362 """,
364 """,
363 )
365 )
364
366
365
367
366 def _decorator_skip_setup():
368 def _decorator_skip_setup():
367 import pexpect
369 import pexpect
368
370
369 env = os.environ.copy()
371 env = os.environ.copy()
370 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
372 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
371 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
373 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
372
374
373 child = pexpect.spawn(
375 child = pexpect.spawn(
374 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
376 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
375 )
377 )
376 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
378 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
377
379
378 child.expect("IPython")
380 child.expect("IPython")
379 child.expect("\n")
381 child.expect("\n")
380
382
381 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
383 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
382 child.str_last_chars = 500
384 child.str_last_chars = 500
383
385
384 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
386 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
385 in_prompt_number = 1
387 in_prompt_number = 1
386 for cblock in dedented_blocks:
388 for cblock in dedented_blocks:
387 child.expect_exact(f"In [{in_prompt_number}]:")
389 child.expect_exact(f"In [{in_prompt_number}]:")
388 in_prompt_number += 1
390 in_prompt_number += 1
389 for line in cblock.splitlines():
391 for line in cblock.splitlines():
390 child.sendline(line)
392 child.sendline(line)
391 child.expect_exact(line)
393 child.expect_exact(line)
392 child.sendline("")
394 child.sendline("")
393 return child
395 return child
394
396
395
397
396 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
398 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
397 @skip_win32
399 @skip_win32
398 def test_decorator_skip():
400 def test_decorator_skip():
399 """test that decorator frames can be skipped."""
401 """test that decorator frames can be skipped."""
400
402
401 child = _decorator_skip_setup()
403 child = _decorator_skip_setup()
402
404
403 child.expect_exact("ipython-input-8")
405 child.expect_exact("ipython-input-8")
404 child.expect_exact("3 bar(3, 4)")
406 child.expect_exact("3 bar(3, 4)")
405 child.expect("ipdb>")
407 child.expect("ipdb>")
406
408
407 child.expect("ipdb>")
409 child.expect("ipdb>")
408 child.sendline("step")
410 child.sendline("step")
409 child.expect_exact("step")
411 child.expect_exact("step")
410 child.expect_exact("--Call--")
412 child.expect_exact("--Call--")
411 child.expect_exact("ipython-input-6")
413 child.expect_exact("ipython-input-6")
412
414
413 child.expect_exact("1 @pdb_skipped_decorator")
415 child.expect_exact("1 @pdb_skipped_decorator")
414
416
415 child.sendline("s")
417 child.sendline("s")
416 child.expect_exact("return x * y")
418 child.expect_exact("return x * y")
417
419
418 child.close()
420 child.close()
419
421
420
422
421 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
423 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
422 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
424 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
423 @skip_win32
425 @skip_win32
424 def test_decorator_skip_disabled():
426 def test_decorator_skip_disabled():
425 """test that decorator frame skipping can be disabled"""
427 """test that decorator frame skipping can be disabled"""
426
428
427 child = _decorator_skip_setup()
429 child = _decorator_skip_setup()
428
430
429 child.expect_exact("3 bar(3, 4)")
431 child.expect_exact("3 bar(3, 4)")
430
432
431 for input_, expected in [
433 for input_, expected in [
432 ("skip_predicates debuggerskip False", ""),
434 ("skip_predicates debuggerskip False", ""),
433 ("skip_predicates", "debuggerskip : False"),
435 ("skip_predicates", "debuggerskip : False"),
434 ("step", "---> 2 def wrapped_fn"),
436 ("step", "---> 2 def wrapped_fn"),
435 ("step", "----> 3 __debuggerskip__"),
437 ("step", "----> 3 __debuggerskip__"),
436 ("step", "----> 4 helper_1()"),
438 ("step", "----> 4 helper_1()"),
437 ("step", "---> 1 def helper_1():"),
439 ("step", "---> 1 def helper_1():"),
438 ("next", "----> 2 helpers_helper()"),
440 ("next", "----> 2 helpers_helper()"),
439 ("next", "--Return--"),
441 ("next", "--Return--"),
440 ("next", "----> 5 __debuggerskip__ = False"),
442 ("next", "----> 5 __debuggerskip__ = False"),
441 ]:
443 ]:
442 child.expect("ipdb>")
444 child.expect("ipdb>")
443 child.sendline(input_)
445 child.sendline(input_)
444 child.expect_exact(input_)
446 child.expect_exact(input_)
445 child.expect_exact(expected)
447 child.expect_exact(expected)
446
448
447 child.close()
449 child.close()
448
450
449
451
450 @pytest.mark.xfail(
452 @pytest.mark.xfail(
451 sys.version_info.releaselevel not in ("final", "candidate"),
453 sys.version_info.releaselevel not in ("final", "candidate"),
452 reason="fails on 3.13.dev",
454 reason="fails on 3.13.dev",
453 strict=True,
455 strict=True,
454 )
456 )
455 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
457 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
456 @skip_win32
458 @skip_win32
457 def test_decorator_skip_with_breakpoint():
459 def test_decorator_skip_with_breakpoint():
458 """test that decorator frame skipping can be disabled"""
460 """test that decorator frame skipping can be disabled"""
459
461
460 import pexpect
462 import pexpect
461
463
462 env = os.environ.copy()
464 env = os.environ.copy()
463 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
465 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
464 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
466 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
465
467
466 child = pexpect.spawn(
468 child = pexpect.spawn(
467 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
469 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
468 )
470 )
469 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
471 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
470 child.str_last_chars = 500
472 child.str_last_chars = 500
471
473
472 child.expect("IPython")
474 child.expect("IPython")
473 child.expect("\n")
475 child.expect("\n")
474
476
475 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
477 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
476
478
477 ### we need a filename, so we need to exec the full block with a filename
479 ### we need a filename, so we need to exec the full block with a filename
478 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
480 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
479 name = tf.name[:-3].split("/")[-1]
481 name = tf.name[:-3].split("/")[-1]
480 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
482 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
481 tf.flush()
483 tf.flush()
482 codeblock = f"from {name} import f"
484 codeblock = f"from {name} import f"
483
485
484 dedented_blocks = [
486 dedented_blocks = [
485 codeblock,
487 codeblock,
486 "f()",
488 "f()",
487 ]
489 ]
488
490
489 in_prompt_number = 1
491 in_prompt_number = 1
490 for cblock in dedented_blocks:
492 for cblock in dedented_blocks:
491 child.expect_exact(f"In [{in_prompt_number}]:")
493 child.expect_exact(f"In [{in_prompt_number}]:")
492 in_prompt_number += 1
494 in_prompt_number += 1
493 for line in cblock.splitlines():
495 for line in cblock.splitlines():
494 child.sendline(line)
496 child.sendline(line)
495 child.expect_exact(line)
497 child.expect_exact(line)
496 child.sendline("")
498 child.sendline("")
497
499
498 # as the filename does not exists, we'll rely on the filename prompt
500 # From 3.13, set_trace()/breakpoint() stop on the line where they're
499 child.expect_exact("47 bar(3, 4)")
501 # called, instead of the next line.
500
502 if sys.version_info >= (3, 13):
501 for input_, expected in [
503 child.expect_exact("--> 46 ipdb.set_trace()")
502 (f"b {name}.py:3", ""),
504 extra_step = [("step", "--> 47 bar(3, 4)")]
503 ("step", "1---> 3 pass # should not stop here except"),
505 else:
504 ("step", "---> 38 @pdb_skipped_decorator"),
506 child.expect_exact("--> 47 bar(3, 4)")
505 ("continue", ""),
507 extra_step = []
506 ]:
508
509 for input_, expected in (
510 [
511 (f"b {name}.py:3", ""),
512 ]
513 + extra_step
514 + [
515 ("step", "1---> 3 pass # should not stop here except"),
516 ("step", "---> 38 @pdb_skipped_decorator"),
517 ("continue", ""),
518 ]
519 ):
507 child.expect("ipdb>")
520 child.expect("ipdb>")
508 child.sendline(input_)
521 child.sendline(input_)
509 child.expect_exact(input_)
522 child.expect_exact(input_)
510 child.expect_exact(expected)
523 child.expect_exact(expected)
511
524
512 child.close()
525 child.close()
513
526
514
527
515 @skip_win32
528 @skip_win32
516 def test_where_erase_value():
529 def test_where_erase_value():
517 """Test that `where` does not access f_locals and erase values."""
530 """Test that `where` does not access f_locals and erase values."""
518 import pexpect
531 import pexpect
519
532
520 env = os.environ.copy()
533 env = os.environ.copy()
521 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
534 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
522
535
523 child = pexpect.spawn(
536 child = pexpect.spawn(
524 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
537 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
525 )
538 )
526 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
539 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
527
540
528 child.expect("IPython")
541 child.expect("IPython")
529 child.expect("\n")
542 child.expect("\n")
530 child.expect_exact("In [1]")
543 child.expect_exact("In [1]")
531
544
532 block = dedent(
545 block = dedent(
533 """
546 """
534 def simple_f():
547 def simple_f():
535 myvar = 1
548 myvar = 1
536 print(myvar)
549 print(myvar)
537 1/0
550 1/0
538 print(myvar)
551 print(myvar)
539 simple_f() """
552 simple_f() """
540 )
553 )
541
554
542 for line in block.splitlines():
555 for line in block.splitlines():
543 child.sendline(line)
556 child.sendline(line)
544 child.expect_exact(line)
557 child.expect_exact(line)
545 child.expect_exact("ZeroDivisionError")
558 child.expect_exact("ZeroDivisionError")
546 child.expect_exact("In [2]:")
559 child.expect_exact("In [2]:")
547
560
548 child.sendline("%debug")
561 child.sendline("%debug")
549
562
550 ##
563 ##
551 child.expect("ipdb>")
564 child.expect("ipdb>")
552
565
553 child.sendline("myvar")
566 child.sendline("myvar")
554 child.expect("1")
567 child.expect("1")
555
568
556 ##
569 ##
557 child.expect("ipdb>")
570 child.expect("ipdb>")
558
571
559 child.sendline("myvar = 2")
572 child.sendline("myvar = 2")
560
573
561 ##
574 ##
562 child.expect_exact("ipdb>")
575 child.expect_exact("ipdb>")
563
576
564 child.sendline("myvar")
577 child.sendline("myvar")
565
578
566 child.expect_exact("2")
579 child.expect_exact("2")
567
580
568 ##
581 ##
569 child.expect("ipdb>")
582 child.expect("ipdb>")
570 child.sendline("where")
583 child.sendline("where")
571
584
572 ##
585 ##
573 child.expect("ipdb>")
586 child.expect("ipdb>")
574 child.sendline("myvar")
587 child.sendline("myvar")
575
588
576 child.expect_exact("2")
589 child.expect_exact("2")
577 child.expect("ipdb>")
590 child.expect("ipdb>")
578
591
579 child.close()
592 child.close()
@@ -1,589 +1,589 b''
1 """Tests for the object inspection functionality.
1 """Tests for the object inspection functionality.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 from inspect import signature, Signature, Parameter
9 from inspect import signature, Signature, Parameter
10 import inspect
10 import inspect
11 import os
11 import os
12 import pytest
12 import pytest
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .. import oinspect
16 from .. import oinspect
17
17
18 from decorator import decorator
18 from decorator import decorator
19
19
20 from IPython.testing.tools import AssertPrints, AssertNotPrints
20 from IPython.testing.tools import AssertPrints, AssertNotPrints
21 from IPython.utils.path import compress_user
21 from IPython.utils.path import compress_user
22
22
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Globals and constants
25 # Globals and constants
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 inspector = None
28 inspector = None
29
29
30 def setup_module():
30 def setup_module():
31 global inspector
31 global inspector
32 inspector = oinspect.Inspector()
32 inspector = oinspect.Inspector()
33
33
34
34
35 class SourceModuleMainTest:
35 class SourceModuleMainTest:
36 __module__ = "__main__"
36 __module__ = "__main__"
37
37
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Local utilities
40 # Local utilities
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 # WARNING: since this test checks the line number where a function is
43 # WARNING: since this test checks the line number where a function is
44 # defined, if any code is inserted above, the following line will need to be
44 # defined, if any code is inserted above, the following line will need to be
45 # updated. Do NOT insert any whitespace between the next line and the function
45 # updated. Do NOT insert any whitespace between the next line and the function
46 # definition below.
46 # definition below.
47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
48
48
49
49
50 def test_find_source_lines():
50 def test_find_source_lines():
51 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
51 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
52 assert oinspect.find_source_lines(type) is None
52 assert oinspect.find_source_lines(type) is None
53 assert oinspect.find_source_lines(SourceModuleMainTest) is None
53 assert oinspect.find_source_lines(SourceModuleMainTest) is None
54 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
54 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
55
55
56
56
57 def test_getsource():
57 def test_getsource():
58 assert oinspect.getsource(type) is None
58 assert oinspect.getsource(type) is None
59 assert oinspect.getsource(SourceModuleMainTest) is None
59 assert oinspect.getsource(SourceModuleMainTest) is None
60 assert oinspect.getsource(SourceModuleMainTest()) is None
60 assert oinspect.getsource(SourceModuleMainTest()) is None
61
61
62
62
63 def test_inspect_getfile_raises_exception():
63 def test_inspect_getfile_raises_exception():
64 """Check oinspect.find_file/getsource/find_source_lines expectations"""
64 """Check oinspect.find_file/getsource/find_source_lines expectations"""
65 with pytest.raises(TypeError):
65 with pytest.raises(TypeError):
66 inspect.getfile(type)
66 inspect.getfile(type)
67 with pytest.raises(OSError):
67 with pytest.raises(OSError):
68 inspect.getfile(SourceModuleMainTest)
68 inspect.getfile(SourceModuleMainTest)
69
69
70
70
71 # A couple of utilities to ensure these tests work the same from a source or a
71 # A couple of utilities to ensure these tests work the same from a source or a
72 # binary install
72 # binary install
73 def pyfile(fname):
73 def pyfile(fname):
74 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
74 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
75
75
76
76
77 def match_pyfiles(f1, f2):
77 def match_pyfiles(f1, f2):
78 assert pyfile(f1) == pyfile(f2)
78 assert pyfile(f1) == pyfile(f2)
79
79
80
80
81 def test_find_file():
81 def test_find_file():
82 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
82 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
83 assert oinspect.find_file(type) is None
83 assert oinspect.find_file(type) is None
84 assert oinspect.find_file(SourceModuleMainTest) is None
84 assert oinspect.find_file(SourceModuleMainTest) is None
85 assert oinspect.find_file(SourceModuleMainTest()) is None
85 assert oinspect.find_file(SourceModuleMainTest()) is None
86
86
87
87
88 def test_find_file_decorated1():
88 def test_find_file_decorated1():
89
89
90 @decorator
90 @decorator
91 def noop1(f):
91 def noop1(f):
92 def wrapper(*a, **kw):
92 def wrapper(*a, **kw):
93 return f(*a, **kw)
93 return f(*a, **kw)
94 return wrapper
94 return wrapper
95
95
96 @noop1
96 @noop1
97 def f(x):
97 def f(x):
98 "My docstring"
98 "My docstring"
99
99
100 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
100 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
101 assert f.__doc__ == "My docstring"
101 assert f.__doc__ == "My docstring"
102
102
103
103
104 def test_find_file_decorated2():
104 def test_find_file_decorated2():
105
105
106 @decorator
106 @decorator
107 def noop2(f, *a, **kw):
107 def noop2(f, *a, **kw):
108 return f(*a, **kw)
108 return f(*a, **kw)
109
109
110 @noop2
110 @noop2
111 @noop2
111 @noop2
112 @noop2
112 @noop2
113 def f(x):
113 def f(x):
114 "My docstring 2"
114 "My docstring 2"
115
115
116 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
116 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
117 assert f.__doc__ == "My docstring 2"
117 assert f.__doc__ == "My docstring 2"
118
118
119
119
120 def test_find_file_magic():
120 def test_find_file_magic():
121 run = ip.find_line_magic('run')
121 run = ip.find_line_magic('run')
122 assert oinspect.find_file(run) is not None
122 assert oinspect.find_file(run) is not None
123
123
124
124
125 # A few generic objects we can then inspect in the tests below
125 # A few generic objects we can then inspect in the tests below
126
126
127 class Call(object):
127 class Call(object):
128 """This is the class docstring."""
128 """This is the class docstring."""
129
129
130 def __init__(self, x, y=1):
130 def __init__(self, x, y=1):
131 """This is the constructor docstring."""
131 """This is the constructor docstring."""
132
132
133 def __call__(self, *a, **kw):
133 def __call__(self, *a, **kw):
134 """This is the call docstring."""
134 """This is the call docstring."""
135
135
136 def method(self, x, z=2):
136 def method(self, x, z=2):
137 """Some method's docstring"""
137 """Some method's docstring"""
138
138
139 class HasSignature(object):
139 class HasSignature(object):
140 """This is the class docstring."""
140 """This is the class docstring."""
141 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
141 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
142
142
143 def __init__(self, *args):
143 def __init__(self, *args):
144 """This is the init docstring"""
144 """This is the init docstring"""
145
145
146
146
147 class SimpleClass(object):
147 class SimpleClass(object):
148 def method(self, x, z=2):
148 def method(self, x, z=2):
149 """Some method's docstring"""
149 """Some method's docstring"""
150
150
151
151
152 class Awkward(object):
152 class Awkward(object):
153 def __getattr__(self, name):
153 def __getattr__(self, name):
154 raise Exception(name)
154 raise Exception(name)
155
155
156 class NoBoolCall:
156 class NoBoolCall:
157 """
157 """
158 callable with `__bool__` raising should still be inspect-able.
158 callable with `__bool__` raising should still be inspect-able.
159 """
159 """
160
160
161 def __call__(self):
161 def __call__(self):
162 """does nothing"""
162 """does nothing"""
163 pass
163 pass
164
164
165 def __bool__(self):
165 def __bool__(self):
166 """just raise NotImplemented"""
166 """just raise NotImplemented"""
167 raise NotImplementedError('Must be implemented')
167 raise NotImplementedError('Must be implemented')
168
168
169
169
170 class SerialLiar(object):
170 class SerialLiar(object):
171 """Attribute accesses always get another copy of the same class.
171 """Attribute accesses always get another copy of the same class.
172
172
173 unittest.mock.call does something similar, but it's not ideal for testing
173 unittest.mock.call does something similar, but it's not ideal for testing
174 as the failure mode is to eat all your RAM. This gives up after 10k levels.
174 as the failure mode is to eat all your RAM. This gives up after 10k levels.
175 """
175 """
176 def __init__(self, max_fibbing_twig, lies_told=0):
176 def __init__(self, max_fibbing_twig, lies_told=0):
177 if lies_told > 10000:
177 if lies_told > 10000:
178 raise RuntimeError('Nose too long, honesty is the best policy')
178 raise RuntimeError('Nose too long, honesty is the best policy')
179 self.max_fibbing_twig = max_fibbing_twig
179 self.max_fibbing_twig = max_fibbing_twig
180 self.lies_told = lies_told
180 self.lies_told = lies_told
181 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
181 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
182
182
183 def __getattr__(self, item):
183 def __getattr__(self, item):
184 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
184 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
185
185
186 #-----------------------------------------------------------------------------
186 #-----------------------------------------------------------------------------
187 # Tests
187 # Tests
188 #-----------------------------------------------------------------------------
188 #-----------------------------------------------------------------------------
189
189
190 def test_info():
190 def test_info():
191 "Check that Inspector.info fills out various fields as expected."
191 "Check that Inspector.info fills out various fields as expected."
192 i = inspector.info(Call, oname="Call")
192 i = inspector.info(Call, oname="Call")
193 assert i["type_name"] == "type"
193 assert i["type_name"] == "type"
194 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
194 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
195 assert i["base_class"] == expected_class
195 assert i["base_class"] == expected_class
196 assert re.search(
196 assert re.search(
197 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
197 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
198 i["string_form"],
198 i["string_form"],
199 )
199 )
200 fname = __file__
200 fname = __file__
201 if fname.endswith(".pyc"):
201 if fname.endswith(".pyc"):
202 fname = fname[:-1]
202 fname = fname[:-1]
203 # case-insensitive comparison needed on some filesystems
203 # case-insensitive comparison needed on some filesystems
204 # e.g. Windows:
204 # e.g. Windows:
205 assert i["file"].lower() == compress_user(fname).lower()
205 assert i["file"].lower() == compress_user(fname).lower()
206 assert i["definition"] == None
206 assert i["definition"] == None
207 assert i["docstring"] == Call.__doc__
207 assert i["docstring"] == Call.__doc__
208 assert i["source"] == None
208 assert i["source"] == None
209 assert i["isclass"] is True
209 assert i["isclass"] is True
210 assert i["init_definition"] == "Call(x, y=1)"
210 assert i["init_definition"] == "Call(x, y=1)"
211 assert i["init_docstring"] == Call.__init__.__doc__
211 assert i["init_docstring"] == Call.__init__.__doc__
212
212
213 i = inspector.info(Call, detail_level=1)
213 i = inspector.info(Call, detail_level=1)
214 assert i["source"] is not None
214 assert i["source"] is not None
215 assert i["docstring"] == None
215 assert i["docstring"] == None
216
216
217 c = Call(1)
217 c = Call(1)
218 c.__doc__ = "Modified instance docstring"
218 c.__doc__ = "Modified instance docstring"
219 i = inspector.info(c)
219 i = inspector.info(c)
220 assert i["type_name"] == "Call"
220 assert i["type_name"] == "Call"
221 assert i["docstring"] == "Modified instance docstring"
221 assert i["docstring"] == "Modified instance docstring"
222 assert i["class_docstring"] == Call.__doc__
222 assert i["class_docstring"] == Call.__doc__
223 assert i["init_docstring"] == Call.__init__.__doc__
223 assert i["init_docstring"] == Call.__init__.__doc__
224 assert i["call_docstring"] == Call.__call__.__doc__
224 assert i["call_docstring"] == Call.__call__.__doc__
225
225
226
226
227 def test_class_signature():
227 def test_class_signature():
228 info = inspector.info(HasSignature, "HasSignature")
228 info = inspector.info(HasSignature, "HasSignature")
229 assert info["init_definition"] == "HasSignature(test)"
229 assert info["init_definition"] == "HasSignature(test)"
230 assert info["init_docstring"] == HasSignature.__init__.__doc__
230 assert info["init_docstring"] == HasSignature.__init__.__doc__
231
231
232
232
233 def test_info_awkward():
233 def test_info_awkward():
234 # Just test that this doesn't throw an error.
234 # Just test that this doesn't throw an error.
235 inspector.info(Awkward())
235 inspector.info(Awkward())
236
236
237 def test_bool_raise():
237 def test_bool_raise():
238 inspector.info(NoBoolCall())
238 inspector.info(NoBoolCall())
239
239
240 def test_info_serialliar():
240 def test_info_serialliar():
241 fib_tracker = [0]
241 fib_tracker = [0]
242 inspector.info(SerialLiar(fib_tracker))
242 inspector.info(SerialLiar(fib_tracker))
243
243
244 # Nested attribute access should be cut off at 100 levels deep to avoid
244 # Nested attribute access should be cut off at 100 levels deep to avoid
245 # infinite loops: https://github.com/ipython/ipython/issues/9122
245 # infinite loops: https://github.com/ipython/ipython/issues/9122
246 assert fib_tracker[0] < 9000
246 assert fib_tracker[0] < 9000
247
247
248 def support_function_one(x, y=2, *a, **kw):
248 def support_function_one(x, y=2, *a, **kw):
249 """A simple function."""
249 """A simple function."""
250
250
251 def test_calldef_none():
251 def test_calldef_none():
252 # We should ignore __call__ for all of these.
252 # We should ignore __call__ for all of these.
253 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
253 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
254 i = inspector.info(obj)
254 i = inspector.info(obj)
255 assert i["call_def"] is None
255 assert i["call_def"] is None
256
256
257
257
258 def f_kwarg(pos, *, kwonly):
258 def f_kwarg(pos, *, kwonly):
259 pass
259 pass
260
260
261 def test_definition_kwonlyargs():
261 def test_definition_kwonlyargs():
262 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
262 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
263 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
263 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
264
264
265
265
266 def test_getdoc():
266 def test_getdoc():
267 class A(object):
267 class A(object):
268 """standard docstring"""
268 """standard docstring"""
269 pass
269 pass
270
270
271 class B(object):
271 class B(object):
272 """standard docstring"""
272 """standard docstring"""
273 def getdoc(self):
273 def getdoc(self):
274 return "custom docstring"
274 return "custom docstring"
275
275
276 class C(object):
276 class C(object):
277 """standard docstring"""
277 """standard docstring"""
278 def getdoc(self):
278 def getdoc(self):
279 return None
279 return None
280
280
281 a = A()
281 a = A()
282 b = B()
282 b = B()
283 c = C()
283 c = C()
284
284
285 assert oinspect.getdoc(a) == "standard docstring"
285 assert oinspect.getdoc(a) == "standard docstring"
286 assert oinspect.getdoc(b) == "custom docstring"
286 assert oinspect.getdoc(b) == "custom docstring"
287 assert oinspect.getdoc(c) == "standard docstring"
287 assert oinspect.getdoc(c) == "standard docstring"
288
288
289
289
290 def test_empty_property_has_no_source():
290 def test_empty_property_has_no_source():
291 i = inspector.info(property(), detail_level=1)
291 i = inspector.info(property(), detail_level=1)
292 assert i["source"] is None
292 assert i["source"] is None
293
293
294
294
295 def test_property_sources():
295 def test_property_sources():
296 # A simple adder whose source and signature stays
296 # A simple adder whose source and signature stays
297 # the same across Python distributions
297 # the same across Python distributions
298 def simple_add(a, b):
298 def simple_add(a, b):
299 "Adds two numbers"
299 "Adds two numbers"
300 return a + b
300 return a + b
301
301
302 class A(object):
302 class A(object):
303 @property
303 @property
304 def foo(self):
304 def foo(self):
305 return 'bar'
305 return 'bar'
306
306
307 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
307 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
308
308
309 dname = property(oinspect.getdoc)
309 dname = property(oinspect.getdoc)
310 adder = property(simple_add)
310 adder = property(simple_add)
311
311
312 i = inspector.info(A.foo, detail_level=1)
312 i = inspector.info(A.foo, detail_level=1)
313 assert "def foo(self):" in i["source"]
313 assert "def foo(self):" in i["source"]
314 assert "lambda self, v:" in i["source"]
314 assert "lambda self, v:" in i["source"]
315
315
316 i = inspector.info(A.dname, detail_level=1)
316 i = inspector.info(A.dname, detail_level=1)
317 assert "def getdoc(obj)" in i["source"]
317 assert "def getdoc(obj)" in i["source"]
318
318
319 i = inspector.info(A.adder, detail_level=1)
319 i = inspector.info(A.adder, detail_level=1)
320 assert "def simple_add(a, b)" in i["source"]
320 assert "def simple_add(a, b)" in i["source"]
321
321
322
322
323 def test_property_docstring_is_in_info_for_detail_level_0():
323 def test_property_docstring_is_in_info_for_detail_level_0():
324 class A(object):
324 class A(object):
325 @property
325 @property
326 def foobar(self):
326 def foobar(self):
327 """This is `foobar` property."""
327 """This is `foobar` property."""
328 pass
328 pass
329
329
330 ip.user_ns["a_obj"] = A()
330 ip.user_ns["a_obj"] = A()
331 assert (
331 assert (
332 "This is `foobar` property."
332 "This is `foobar` property."
333 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
333 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
334 )
334 )
335
335
336 ip.user_ns["a_cls"] = A
336 ip.user_ns["a_cls"] = A
337 assert (
337 assert (
338 "This is `foobar` property."
338 "This is `foobar` property."
339 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
339 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
340 )
340 )
341
341
342
342
343 def test_pdef():
343 def test_pdef():
344 # See gh-1914
344 # See gh-1914
345 def foo(): pass
345 def foo(): pass
346 inspector.pdef(foo, 'foo')
346 inspector.pdef(foo, 'foo')
347
347
348
348
349 @contextmanager
349 @contextmanager
350 def cleanup_user_ns(**kwargs):
350 def cleanup_user_ns(**kwargs):
351 """
351 """
352 On exit delete all the keys that were not in user_ns before entering.
352 On exit delete all the keys that were not in user_ns before entering.
353
353
354 It does not restore old values !
354 It does not restore old values !
355
355
356 Parameters
356 Parameters
357 ----------
357 ----------
358
358
359 **kwargs
359 **kwargs
360 used to update ip.user_ns
360 used to update ip.user_ns
361
361
362 """
362 """
363 try:
363 try:
364 known = set(ip.user_ns.keys())
364 known = set(ip.user_ns.keys())
365 ip.user_ns.update(kwargs)
365 ip.user_ns.update(kwargs)
366 yield
366 yield
367 finally:
367 finally:
368 added = set(ip.user_ns.keys()) - known
368 added = set(ip.user_ns.keys()) - known
369 for k in added:
369 for k in added:
370 del ip.user_ns[k]
370 del ip.user_ns[k]
371
371
372
372
373 def test_pinfo_bool_raise():
373 def test_pinfo_bool_raise():
374 """
374 """
375 Test that bool method is not called on parent.
375 Test that bool method is not called on parent.
376 """
376 """
377
377
378 class RaiseBool:
378 class RaiseBool:
379 attr = None
379 attr = None
380
380
381 def __bool__(self):
381 def __bool__(self):
382 raise ValueError("pinfo should not access this method")
382 raise ValueError("pinfo should not access this method")
383
383
384 raise_bool = RaiseBool()
384 raise_bool = RaiseBool()
385
385
386 with cleanup_user_ns(raise_bool=raise_bool):
386 with cleanup_user_ns(raise_bool=raise_bool):
387 ip._inspect("pinfo", "raise_bool.attr", detail_level=0)
387 ip._inspect("pinfo", "raise_bool.attr", detail_level=0)
388
388
389
389
390 def test_pinfo_getindex():
390 def test_pinfo_getindex():
391 def dummy():
391 def dummy():
392 """
392 """
393 MARKER
393 MARKER
394 """
394 """
395
395
396 container = [dummy]
396 container = [dummy]
397 with cleanup_user_ns(container=container):
397 with cleanup_user_ns(container=container):
398 with AssertPrints("MARKER"):
398 with AssertPrints("MARKER"):
399 ip._inspect("pinfo", "container[0]", detail_level=0)
399 ip._inspect("pinfo", "container[0]", detail_level=0)
400 assert "container" not in ip.user_ns.keys()
400 assert "container" not in ip.user_ns.keys()
401
401
402
402
403 def test_qmark_getindex():
403 def test_qmark_getindex():
404 def dummy():
404 def dummy():
405 """
405 """
406 MARKER 2
406 MARKER 2
407 """
407 """
408
408
409 container = [dummy]
409 container = [dummy]
410 with cleanup_user_ns(container=container):
410 with cleanup_user_ns(container=container):
411 with AssertPrints("MARKER 2"):
411 with AssertPrints("MARKER 2"):
412 ip.run_cell("container[0]?")
412 ip.run_cell("container[0]?")
413 assert "container" not in ip.user_ns.keys()
413 assert "container" not in ip.user_ns.keys()
414
414
415
415
416 def test_qmark_getindex_negatif():
416 def test_qmark_getindex_negatif():
417 def dummy():
417 def dummy():
418 """
418 """
419 MARKER 3
419 MARKER 3
420 """
420 """
421
421
422 container = [dummy]
422 container = [dummy]
423 with cleanup_user_ns(container=container):
423 with cleanup_user_ns(container=container):
424 with AssertPrints("MARKER 3"):
424 with AssertPrints("MARKER 3"):
425 ip.run_cell("container[-1]?")
425 ip.run_cell("container[-1]?")
426 assert "container" not in ip.user_ns.keys()
426 assert "container" not in ip.user_ns.keys()
427
427
428
428
429
429
430 def test_pinfo_nonascii():
430 def test_pinfo_nonascii():
431 # See gh-1177
431 # See gh-1177
432 from . import nonascii2
432 from . import nonascii2
433 ip.user_ns['nonascii2'] = nonascii2
433 ip.user_ns['nonascii2'] = nonascii2
434 ip._inspect('pinfo', 'nonascii2', detail_level=1)
434 ip._inspect('pinfo', 'nonascii2', detail_level=1)
435
435
436 def test_pinfo_type():
436 def test_pinfo_type():
437 """
437 """
438 type can fail in various edge case, for example `type.__subclass__()`
438 type can fail in various edge case, for example `type.__subclass__()`
439 """
439 """
440 ip._inspect('pinfo', 'type')
440 ip._inspect('pinfo', 'type')
441
441
442
442
443 def test_pinfo_docstring_no_source():
443 def test_pinfo_docstring_no_source():
444 """Docstring should be included with detail_level=1 if there is no source"""
444 """Docstring should be included with detail_level=1 if there is no source"""
445 with AssertPrints('Docstring:'):
445 with AssertPrints('Docstring:'):
446 ip._inspect('pinfo', 'str.format', detail_level=0)
446 ip._inspect('pinfo', 'str.format', detail_level=0)
447 with AssertPrints('Docstring:'):
447 with AssertPrints('Docstring:'):
448 ip._inspect('pinfo', 'str.format', detail_level=1)
448 ip._inspect('pinfo', 'str.format', detail_level=1)
449
449
450
450
451 def test_pinfo_no_docstring_if_source():
451 def test_pinfo_no_docstring_if_source():
452 """Docstring should not be included with detail_level=1 if source is found"""
452 """Docstring should not be included with detail_level=1 if source is found"""
453 def foo():
453 def foo():
454 """foo has a docstring"""
454 """foo has a docstring"""
455
455
456 ip.user_ns['foo'] = foo
456 ip.user_ns['foo'] = foo
457
457
458 with AssertPrints('Docstring:'):
458 with AssertPrints('Docstring:'):
459 ip._inspect('pinfo', 'foo', detail_level=0)
459 ip._inspect('pinfo', 'foo', detail_level=0)
460 with AssertPrints('Source:'):
460 with AssertPrints('Source:'):
461 ip._inspect('pinfo', 'foo', detail_level=1)
461 ip._inspect('pinfo', 'foo', detail_level=1)
462 with AssertNotPrints('Docstring:'):
462 with AssertNotPrints('Docstring:'):
463 ip._inspect('pinfo', 'foo', detail_level=1)
463 ip._inspect('pinfo', 'foo', detail_level=1)
464
464
465
465
466 def test_pinfo_docstring_if_detail_and_no_source():
466 def test_pinfo_docstring_if_detail_and_no_source():
467 """ Docstring should be displayed if source info not available """
467 """ Docstring should be displayed if source info not available """
468 obj_def = '''class Foo(object):
468 obj_def = '''class Foo(object):
469 """ This is a docstring for Foo """
469 """ This is a docstring for Foo """
470 def bar(self):
470 def bar(self):
471 """ This is a docstring for Foo.bar """
471 """ This is a docstring for Foo.bar """
472 pass
472 pass
473 '''
473 '''
474
474
475 ip.run_cell(obj_def)
475 ip.run_cell(obj_def)
476 ip.run_cell('foo = Foo()')
476 ip.run_cell('foo = Foo()')
477
477
478 with AssertNotPrints("Source:"):
478 with AssertNotPrints("Source:"):
479 with AssertPrints('Docstring:'):
479 with AssertPrints('Docstring:'):
480 ip._inspect('pinfo', 'foo', detail_level=0)
480 ip._inspect('pinfo', 'foo', detail_level=0)
481 with AssertPrints('Docstring:'):
481 with AssertPrints('Docstring:'):
482 ip._inspect('pinfo', 'foo', detail_level=1)
482 ip._inspect('pinfo', 'foo', detail_level=1)
483 with AssertPrints('Docstring:'):
483 with AssertPrints('Docstring:'):
484 ip._inspect('pinfo', 'foo.bar', detail_level=0)
484 ip._inspect('pinfo', 'foo.bar', detail_level=0)
485
485
486 with AssertNotPrints('Docstring:'):
486 with AssertNotPrints('Docstring:'):
487 with AssertPrints('Source:'):
487 with AssertPrints('Source:'):
488 ip._inspect('pinfo', 'foo.bar', detail_level=1)
488 ip._inspect('pinfo', 'foo.bar', detail_level=1)
489
489
490
490
491 @pytest.mark.xfail(
491 @pytest.mark.xfail(
492 sys.version_info.releaselevel not in ("final", "candidate"),
492 sys.version_info.releaselevel not in ("final", "candidate"),
493 reason="fails on 3.13.dev",
493 reason="fails on 3.13.dev",
494 strict=True,
494 strict=True,
495 )
495 )
496 def test_pinfo_docstring_dynamic(capsys):
496 def test_pinfo_docstring_dynamic(capsys):
497 obj_def = """class Bar:
497 obj_def = """class Bar:
498 __custom_documentations__ = {
498 __custom_documentations__ = {
499 "prop" : "cdoc for prop",
499 "prop" : "cdoc for prop",
500 "non_exist" : "cdoc for non_exist",
500 "non_exist" : "cdoc for non_exist",
501 }
501 }
502 @property
502 @property
503 def prop(self):
503 def prop(self):
504 '''
504 '''
505 Docstring for prop
505 Docstring for prop
506 '''
506 '''
507 return self._prop
507 return self._prop
508
508
509 @prop.setter
509 @prop.setter
510 def prop(self, v):
510 def prop(self, v):
511 self._prop = v
511 self._prop = v
512 """
512 """
513 ip.run_cell(obj_def)
513 ip.run_cell(obj_def)
514
514
515 ip.run_cell("b = Bar()")
515 ip.run_cell("b = Bar()")
516
516
517 ip.run_line_magic("pinfo", "b.prop")
517 ip.run_line_magic("pinfo", "b.prop")
518 captured = capsys.readouterr()
518 captured = capsys.readouterr()
519 assert "Docstring: cdoc for prop" in captured.out
519 assert re.search(r"Docstring:\s+cdoc for prop", captured.out)
520
520
521 ip.run_line_magic("pinfo", "b.non_exist")
521 ip.run_line_magic("pinfo", "b.non_exist")
522 captured = capsys.readouterr()
522 captured = capsys.readouterr()
523 assert "Docstring: cdoc for non_exist" in captured.out
523 assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out)
524
524
525 ip.run_cell("b.prop?")
525 ip.run_cell("b.prop?")
526 captured = capsys.readouterr()
526 captured = capsys.readouterr()
527 assert "Docstring: cdoc for prop" in captured.out
527 assert re.search(r"Docstring:\s+cdoc for prop", captured.out)
528
528
529 ip.run_cell("b.non_exist?")
529 ip.run_cell("b.non_exist?")
530 captured = capsys.readouterr()
530 captured = capsys.readouterr()
531 assert "Docstring: cdoc for non_exist" in captured.out
531 assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out)
532
532
533 ip.run_cell("b.undefined?")
533 ip.run_cell("b.undefined?")
534 captured = capsys.readouterr()
534 captured = capsys.readouterr()
535 assert "Docstring: <no docstring>" in captured.out
535 assert re.search(r"Type:\s+NoneType", captured.out)
536
536
537
537
538 def test_pinfo_magic():
538 def test_pinfo_magic():
539 with AssertPrints("Docstring:"):
539 with AssertPrints("Docstring:"):
540 ip._inspect("pinfo", "lsmagic", detail_level=0)
540 ip._inspect("pinfo", "lsmagic", detail_level=0)
541
541
542 with AssertPrints("Source:"):
542 with AssertPrints("Source:"):
543 ip._inspect("pinfo", "lsmagic", detail_level=1)
543 ip._inspect("pinfo", "lsmagic", detail_level=1)
544
544
545
545
546 def test_init_colors():
546 def test_init_colors():
547 # ensure colors are not present in signature info
547 # ensure colors are not present in signature info
548 info = inspector.info(HasSignature)
548 info = inspector.info(HasSignature)
549 init_def = info["init_definition"]
549 init_def = info["init_definition"]
550 assert "[0m" not in init_def
550 assert "[0m" not in init_def
551
551
552
552
553 def test_builtin_init():
553 def test_builtin_init():
554 info = inspector.info(list)
554 info = inspector.info(list)
555 init_def = info['init_definition']
555 init_def = info['init_definition']
556 assert init_def is not None
556 assert init_def is not None
557
557
558
558
559 def test_render_signature_short():
559 def test_render_signature_short():
560 def short_fun(a=1): pass
560 def short_fun(a=1): pass
561 sig = oinspect._render_signature(
561 sig = oinspect._render_signature(
562 signature(short_fun),
562 signature(short_fun),
563 short_fun.__name__,
563 short_fun.__name__,
564 )
564 )
565 assert sig == "short_fun(a=1)"
565 assert sig == "short_fun(a=1)"
566
566
567
567
568 def test_render_signature_long():
568 def test_render_signature_long():
569 from typing import Optional
569 from typing import Optional
570
570
571 def long_function(
571 def long_function(
572 a_really_long_parameter: int,
572 a_really_long_parameter: int,
573 and_another_long_one: bool = False,
573 and_another_long_one: bool = False,
574 let_us_make_sure_this_is_looong: Optional[str] = None,
574 let_us_make_sure_this_is_looong: Optional[str] = None,
575 ) -> bool: pass
575 ) -> bool: pass
576
576
577 sig = oinspect._render_signature(
577 sig = oinspect._render_signature(
578 signature(long_function),
578 signature(long_function),
579 long_function.__name__,
579 long_function.__name__,
580 )
580 )
581 expected = """\
581 expected = """\
582 long_function(
582 long_function(
583 a_really_long_parameter: int,
583 a_really_long_parameter: int,
584 and_another_long_one: bool = False,
584 and_another_long_one: bool = False,
585 let_us_make_sure_this_is_looong: Optional[str] = None,
585 let_us_make_sure_this_is_looong: Optional[str] = None,
586 ) -> bool\
586 ) -> bool\
587 """
587 """
588
588
589 assert sig == expected
589 assert sig == expected
General Comments 0
You need to be logged in to leave comments. Login now