##// END OF EJS Templates
MAINT: mark 3.13.dev failing test as xfail
M Bussonnier -
Show More
@@ -1,574 +1,582 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 @pytest.mark.xfail(
58 sys.version_info.releaselevel not in ("final", "candidate"),
59 reason="fails on 3.13.dev",
60 )
57 def test_ipdb_magics():
61 def test_ipdb_magics():
58 '''Test calling some IPython magics from ipdb.
62 '''Test calling some IPython magics from ipdb.
59
63
60 First, set up some test functions and classes which we can inspect.
64 First, set up some test functions and classes which we can inspect.
61
65
62 >>> class ExampleClass(object):
66 >>> class ExampleClass(object):
63 ... """Docstring for ExampleClass."""
67 ... """Docstring for ExampleClass."""
64 ... def __init__(self):
68 ... def __init__(self):
65 ... """Docstring for ExampleClass.__init__"""
69 ... """Docstring for ExampleClass.__init__"""
66 ... pass
70 ... pass
67 ... def __str__(self):
71 ... def __str__(self):
68 ... return "ExampleClass()"
72 ... return "ExampleClass()"
69
73
70 >>> def example_function(x, y, z="hello"):
74 >>> def example_function(x, y, z="hello"):
71 ... """Docstring for example_function."""
75 ... """Docstring for example_function."""
72 ... pass
76 ... pass
73
77
74 >>> old_trace = sys.gettrace()
78 >>> old_trace = sys.gettrace()
75
79
76 Create a function which triggers ipdb.
80 Create a function which triggers ipdb.
77
81
78 >>> def trigger_ipdb():
82 >>> def trigger_ipdb():
79 ... a = ExampleClass()
83 ... a = ExampleClass()
80 ... debugger.Pdb().set_trace()
84 ... debugger.Pdb().set_trace()
81
85
82 >>> with PdbTestInput([
86 >>> with PdbTestInput([
83 ... 'pdef example_function',
87 ... 'pdef example_function',
84 ... 'pdoc ExampleClass',
88 ... 'pdoc ExampleClass',
85 ... 'up',
89 ... 'up',
86 ... 'down',
90 ... 'down',
87 ... 'list',
91 ... 'list',
88 ... 'pinfo a',
92 ... 'pinfo a',
89 ... 'll',
93 ... 'll',
90 ... 'continue',
94 ... 'continue',
91 ... ]):
95 ... ]):
92 ... trigger_ipdb()
96 ... trigger_ipdb()
93 --Return--
97 --Return--
94 None
98 None
95 > <doctest ...>(3)trigger_ipdb()
99 > <doctest ...>(3)trigger_ipdb()
96 1 def trigger_ipdb():
100 1 def trigger_ipdb():
97 2 a = ExampleClass()
101 2 a = ExampleClass()
98 ----> 3 debugger.Pdb().set_trace()
102 ----> 3 debugger.Pdb().set_trace()
99 <BLANKLINE>
103 <BLANKLINE>
100 ipdb> pdef example_function
104 ipdb> pdef example_function
101 example_function(x, y, z='hello')
105 example_function(x, y, z='hello')
102 ipdb> pdoc ExampleClass
106 ipdb> pdoc ExampleClass
103 Class docstring:
107 Class docstring:
104 Docstring for ExampleClass.
108 Docstring for ExampleClass.
105 Init docstring:
109 Init docstring:
106 Docstring for ExampleClass.__init__
110 Docstring for ExampleClass.__init__
107 ipdb> up
111 ipdb> up
108 > <doctest ...>(11)<module>()
112 > <doctest ...>(11)<module>()
109 7 'pinfo a',
113 7 'pinfo a',
110 8 'll',
114 8 'll',
111 9 'continue',
115 9 'continue',
112 10 ]):
116 10 ]):
113 ---> 11 trigger_ipdb()
117 ---> 11 trigger_ipdb()
114 <BLANKLINE>
118 <BLANKLINE>
115 ipdb> down
119 ipdb> down
116 None
120 None
117 > <doctest ...>(3)trigger_ipdb()
121 > <doctest ...>(3)trigger_ipdb()
118 1 def trigger_ipdb():
122 1 def trigger_ipdb():
119 2 a = ExampleClass()
123 2 a = ExampleClass()
120 ----> 3 debugger.Pdb().set_trace()
124 ----> 3 debugger.Pdb().set_trace()
121 <BLANKLINE>
125 <BLANKLINE>
122 ipdb> list
126 ipdb> list
123 1 def trigger_ipdb():
127 1 def trigger_ipdb():
124 2 a = ExampleClass()
128 2 a = ExampleClass()
125 ----> 3 debugger.Pdb().set_trace()
129 ----> 3 debugger.Pdb().set_trace()
126 <BLANKLINE>
130 <BLANKLINE>
127 ipdb> pinfo a
131 ipdb> pinfo a
128 Type: ExampleClass
132 Type: ExampleClass
129 String form: ExampleClass()
133 String form: ExampleClass()
130 Namespace: Local...
134 Namespace: Local...
131 Docstring: Docstring for ExampleClass.
135 Docstring: Docstring for ExampleClass.
132 Init docstring: Docstring for ExampleClass.__init__
136 Init docstring: Docstring for ExampleClass.__init__
133 ipdb> ll
137 ipdb> ll
134 1 def trigger_ipdb():
138 1 def trigger_ipdb():
135 2 a = ExampleClass()
139 2 a = ExampleClass()
136 ----> 3 debugger.Pdb().set_trace()
140 ----> 3 debugger.Pdb().set_trace()
137 <BLANKLINE>
141 <BLANKLINE>
138 ipdb> continue
142 ipdb> continue
139
143
140 Restore previous trace function, e.g. for coverage.py
144 Restore previous trace function, e.g. for coverage.py
141
145
142 >>> sys.settrace(old_trace)
146 >>> sys.settrace(old_trace)
143 '''
147 '''
144
148
145 def test_ipdb_magics2():
149 def test_ipdb_magics2():
146 '''Test ipdb with a very short function.
150 '''Test ipdb with a very short function.
147
151
148 >>> old_trace = sys.gettrace()
152 >>> old_trace = sys.gettrace()
149
153
150 >>> def bar():
154 >>> def bar():
151 ... pass
155 ... pass
152
156
153 Run ipdb.
157 Run ipdb.
154
158
155 >>> with PdbTestInput([
159 >>> with PdbTestInput([
156 ... 'continue',
160 ... 'continue',
157 ... ]):
161 ... ]):
158 ... debugger.Pdb().runcall(bar)
162 ... debugger.Pdb().runcall(bar)
159 > <doctest ...>(2)bar()
163 > <doctest ...>(2)bar()
160 1 def bar():
164 1 def bar():
161 ----> 2 pass
165 ----> 2 pass
162 <BLANKLINE>
166 <BLANKLINE>
163 ipdb> continue
167 ipdb> continue
164
168
165 Restore previous trace function, e.g. for coverage.py
169 Restore previous trace function, e.g. for coverage.py
166
170
167 >>> sys.settrace(old_trace)
171 >>> sys.settrace(old_trace)
168 '''
172 '''
169
173
170 def can_quit():
174 def can_quit():
171 '''Test that quit work in ipydb
175 '''Test that quit work in ipydb
172
176
173 >>> old_trace = sys.gettrace()
177 >>> old_trace = sys.gettrace()
174
178
175 >>> def bar():
179 >>> def bar():
176 ... pass
180 ... pass
177
181
178 >>> with PdbTestInput([
182 >>> with PdbTestInput([
179 ... 'quit',
183 ... 'quit',
180 ... ]):
184 ... ]):
181 ... debugger.Pdb().runcall(bar)
185 ... debugger.Pdb().runcall(bar)
182 > <doctest ...>(2)bar()
186 > <doctest ...>(2)bar()
183 1 def bar():
187 1 def bar():
184 ----> 2 pass
188 ----> 2 pass
185 <BLANKLINE>
189 <BLANKLINE>
186 ipdb> quit
190 ipdb> quit
187
191
188 Restore previous trace function, e.g. for coverage.py
192 Restore previous trace function, e.g. for coverage.py
189
193
190 >>> sys.settrace(old_trace)
194 >>> sys.settrace(old_trace)
191 '''
195 '''
192
196
193
197
194 def can_exit():
198 def can_exit():
195 '''Test that quit work in ipydb
199 '''Test that quit work in ipydb
196
200
197 >>> old_trace = sys.gettrace()
201 >>> old_trace = sys.gettrace()
198
202
199 >>> def bar():
203 >>> def bar():
200 ... pass
204 ... pass
201
205
202 >>> with PdbTestInput([
206 >>> with PdbTestInput([
203 ... 'exit',
207 ... 'exit',
204 ... ]):
208 ... ]):
205 ... debugger.Pdb().runcall(bar)
209 ... debugger.Pdb().runcall(bar)
206 > <doctest ...>(2)bar()
210 > <doctest ...>(2)bar()
207 1 def bar():
211 1 def bar():
208 ----> 2 pass
212 ----> 2 pass
209 <BLANKLINE>
213 <BLANKLINE>
210 ipdb> exit
214 ipdb> exit
211
215
212 Restore previous trace function, e.g. for coverage.py
216 Restore previous trace function, e.g. for coverage.py
213
217
214 >>> sys.settrace(old_trace)
218 >>> sys.settrace(old_trace)
215 '''
219 '''
216
220
217
221
218 def test_interruptible_core_debugger():
222 def test_interruptible_core_debugger():
219 """The debugger can be interrupted.
223 """The debugger can be interrupted.
220
224
221 The presumption is there is some mechanism that causes a KeyboardInterrupt
225 The presumption is there is some mechanism that causes a KeyboardInterrupt
222 (this is implemented in ipykernel). We want to ensure the
226 (this is implemented in ipykernel). We want to ensure the
223 KeyboardInterrupt cause debugging to cease.
227 KeyboardInterrupt cause debugging to cease.
224 """
228 """
225 def raising_input(msg="", called=[0]):
229 def raising_input(msg="", called=[0]):
226 called[0] += 1
230 called[0] += 1
227 assert called[0] == 1, "input() should only be called once!"
231 assert called[0] == 1, "input() should only be called once!"
228 raise KeyboardInterrupt()
232 raise KeyboardInterrupt()
229
233
230 tracer_orig = sys.gettrace()
234 tracer_orig = sys.gettrace()
231 try:
235 try:
232 with patch.object(builtins, "input", raising_input):
236 with patch.object(builtins, "input", raising_input):
233 debugger.InterruptiblePdb().set_trace()
237 debugger.InterruptiblePdb().set_trace()
234 # The way this test will fail is by set_trace() never exiting,
238 # The way this test will fail is by set_trace() never exiting,
235 # resulting in a timeout by the test runner. The alternative
239 # resulting in a timeout by the test runner. The alternative
236 # implementation would involve a subprocess, but that adds issues
240 # implementation would involve a subprocess, but that adds issues
237 # with interrupting subprocesses that are rather complex, so it's
241 # with interrupting subprocesses that are rather complex, so it's
238 # simpler just to do it this way.
242 # simpler just to do it this way.
239 finally:
243 finally:
240 # restore the original trace function
244 # restore the original trace function
241 sys.settrace(tracer_orig)
245 sys.settrace(tracer_orig)
242
246
243
247
244 @skip_win32
248 @skip_win32
245 def test_xmode_skip():
249 def test_xmode_skip():
246 """that xmode skip frames
250 """that xmode skip frames
247
251
248 Not as a doctest as pytest does not run doctests.
252 Not as a doctest as pytest does not run doctests.
249 """
253 """
250 import pexpect
254 import pexpect
251 env = os.environ.copy()
255 env = os.environ.copy()
252 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
256 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
253
257
254 child = pexpect.spawn(
258 child = pexpect.spawn(
255 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
259 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
256 )
260 )
257 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
261 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
258
262
259 child.expect("IPython")
263 child.expect("IPython")
260 child.expect("\n")
264 child.expect("\n")
261 child.expect_exact("In [1]")
265 child.expect_exact("In [1]")
262
266
263 block = dedent(
267 block = dedent(
264 """
268 """
265 def f():
269 def f():
266 __tracebackhide__ = True
270 __tracebackhide__ = True
267 g()
271 g()
268
272
269 def g():
273 def g():
270 raise ValueError
274 raise ValueError
271
275
272 f()
276 f()
273 """
277 """
274 )
278 )
275
279
276 for line in block.splitlines():
280 for line in block.splitlines():
277 child.sendline(line)
281 child.sendline(line)
278 child.expect_exact(line)
282 child.expect_exact(line)
279 child.expect_exact("skipping")
283 child.expect_exact("skipping")
280
284
281 block = dedent(
285 block = dedent(
282 """
286 """
283 def f():
287 def f():
284 __tracebackhide__ = True
288 __tracebackhide__ = True
285 g()
289 g()
286
290
287 def g():
291 def g():
288 from IPython.core.debugger import set_trace
292 from IPython.core.debugger import set_trace
289 set_trace()
293 set_trace()
290
294
291 f()
295 f()
292 """
296 """
293 )
297 )
294
298
295 for line in block.splitlines():
299 for line in block.splitlines():
296 child.sendline(line)
300 child.sendline(line)
297 child.expect_exact(line)
301 child.expect_exact(line)
298
302
299 child.expect("ipdb>")
303 child.expect("ipdb>")
300 child.sendline("w")
304 child.sendline("w")
301 child.expect("hidden")
305 child.expect("hidden")
302 child.expect("ipdb>")
306 child.expect("ipdb>")
303 child.sendline("skip_hidden false")
307 child.sendline("skip_hidden false")
304 child.sendline("w")
308 child.sendline("w")
305 child.expect("__traceba")
309 child.expect("__traceba")
306 child.expect("ipdb>")
310 child.expect("ipdb>")
307
311
308 child.close()
312 child.close()
309
313
310
314
311 skip_decorators_blocks = (
315 skip_decorators_blocks = (
312 """
316 """
313 def helpers_helper():
317 def helpers_helper():
314 pass # should not stop here except breakpoint
318 pass # should not stop here except breakpoint
315 """,
319 """,
316 """
320 """
317 def helper_1():
321 def helper_1():
318 helpers_helper() # should not stop here
322 helpers_helper() # should not stop here
319 """,
323 """,
320 """
324 """
321 def helper_2():
325 def helper_2():
322 pass # should not stop here
326 pass # should not stop here
323 """,
327 """,
324 """
328 """
325 def pdb_skipped_decorator2(function):
329 def pdb_skipped_decorator2(function):
326 def wrapped_fn(*args, **kwargs):
330 def wrapped_fn(*args, **kwargs):
327 __debuggerskip__ = True
331 __debuggerskip__ = True
328 helper_2()
332 helper_2()
329 __debuggerskip__ = False
333 __debuggerskip__ = False
330 result = function(*args, **kwargs)
334 result = function(*args, **kwargs)
331 __debuggerskip__ = True
335 __debuggerskip__ = True
332 helper_2()
336 helper_2()
333 return result
337 return result
334 return wrapped_fn
338 return wrapped_fn
335 """,
339 """,
336 """
340 """
337 def pdb_skipped_decorator(function):
341 def pdb_skipped_decorator(function):
338 def wrapped_fn(*args, **kwargs):
342 def wrapped_fn(*args, **kwargs):
339 __debuggerskip__ = True
343 __debuggerskip__ = True
340 helper_1()
344 helper_1()
341 __debuggerskip__ = False
345 __debuggerskip__ = False
342 result = function(*args, **kwargs)
346 result = function(*args, **kwargs)
343 __debuggerskip__ = True
347 __debuggerskip__ = True
344 helper_2()
348 helper_2()
345 return result
349 return result
346 return wrapped_fn
350 return wrapped_fn
347 """,
351 """,
348 """
352 """
349 @pdb_skipped_decorator
353 @pdb_skipped_decorator
350 @pdb_skipped_decorator2
354 @pdb_skipped_decorator2
351 def bar(x, y):
355 def bar(x, y):
352 return x * y
356 return x * y
353 """,
357 """,
354 """import IPython.terminal.debugger as ipdb""",
358 """import IPython.terminal.debugger as ipdb""",
355 """
359 """
356 def f():
360 def f():
357 ipdb.set_trace()
361 ipdb.set_trace()
358 bar(3, 4)
362 bar(3, 4)
359 """,
363 """,
360 """
364 """
361 f()
365 f()
362 """,
366 """,
363 )
367 )
364
368
365
369
366 def _decorator_skip_setup():
370 def _decorator_skip_setup():
367 import pexpect
371 import pexpect
368
372
369 env = os.environ.copy()
373 env = os.environ.copy()
370 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
374 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
371 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
375 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
372
376
373 child = pexpect.spawn(
377 child = pexpect.spawn(
374 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
378 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
375 )
379 )
376 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
380 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
377
381
378 child.expect("IPython")
382 child.expect("IPython")
379 child.expect("\n")
383 child.expect("\n")
380
384
381 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
385 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
382 child.str_last_chars = 500
386 child.str_last_chars = 500
383
387
384 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
388 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
385 in_prompt_number = 1
389 in_prompt_number = 1
386 for cblock in dedented_blocks:
390 for cblock in dedented_blocks:
387 child.expect_exact(f"In [{in_prompt_number}]:")
391 child.expect_exact(f"In [{in_prompt_number}]:")
388 in_prompt_number += 1
392 in_prompt_number += 1
389 for line in cblock.splitlines():
393 for line in cblock.splitlines():
390 child.sendline(line)
394 child.sendline(line)
391 child.expect_exact(line)
395 child.expect_exact(line)
392 child.sendline("")
396 child.sendline("")
393 return child
397 return child
394
398
395
399
396 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
400 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
397 @skip_win32
401 @skip_win32
398 def test_decorator_skip():
402 def test_decorator_skip():
399 """test that decorator frames can be skipped."""
403 """test that decorator frames can be skipped."""
400
404
401 child = _decorator_skip_setup()
405 child = _decorator_skip_setup()
402
406
403 child.expect_exact("ipython-input-8")
407 child.expect_exact("ipython-input-8")
404 child.expect_exact("3 bar(3, 4)")
408 child.expect_exact("3 bar(3, 4)")
405 child.expect("ipdb>")
409 child.expect("ipdb>")
406
410
407 child.expect("ipdb>")
411 child.expect("ipdb>")
408 child.sendline("step")
412 child.sendline("step")
409 child.expect_exact("step")
413 child.expect_exact("step")
410 child.expect_exact("--Call--")
414 child.expect_exact("--Call--")
411 child.expect_exact("ipython-input-6")
415 child.expect_exact("ipython-input-6")
412
416
413 child.expect_exact("1 @pdb_skipped_decorator")
417 child.expect_exact("1 @pdb_skipped_decorator")
414
418
415 child.sendline("s")
419 child.sendline("s")
416 child.expect_exact("return x * y")
420 child.expect_exact("return x * y")
417
421
418 child.close()
422 child.close()
419
423
420
424
421 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
425 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
422 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
426 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
423 @skip_win32
427 @skip_win32
424 def test_decorator_skip_disabled():
428 def test_decorator_skip_disabled():
425 """test that decorator frame skipping can be disabled"""
429 """test that decorator frame skipping can be disabled"""
426
430
427 child = _decorator_skip_setup()
431 child = _decorator_skip_setup()
428
432
429 child.expect_exact("3 bar(3, 4)")
433 child.expect_exact("3 bar(3, 4)")
430
434
431 for input_, expected in [
435 for input_, expected in [
432 ("skip_predicates debuggerskip False", ""),
436 ("skip_predicates debuggerskip False", ""),
433 ("skip_predicates", "debuggerskip : False"),
437 ("skip_predicates", "debuggerskip : False"),
434 ("step", "---> 2 def wrapped_fn"),
438 ("step", "---> 2 def wrapped_fn"),
435 ("step", "----> 3 __debuggerskip__"),
439 ("step", "----> 3 __debuggerskip__"),
436 ("step", "----> 4 helper_1()"),
440 ("step", "----> 4 helper_1()"),
437 ("step", "---> 1 def helper_1():"),
441 ("step", "---> 1 def helper_1():"),
438 ("next", "----> 2 helpers_helper()"),
442 ("next", "----> 2 helpers_helper()"),
439 ("next", "--Return--"),
443 ("next", "--Return--"),
440 ("next", "----> 5 __debuggerskip__ = False"),
444 ("next", "----> 5 __debuggerskip__ = False"),
441 ]:
445 ]:
442 child.expect("ipdb>")
446 child.expect("ipdb>")
443 child.sendline(input_)
447 child.sendline(input_)
444 child.expect_exact(input_)
448 child.expect_exact(input_)
445 child.expect_exact(expected)
449 child.expect_exact(expected)
446
450
447 child.close()
451 child.close()
448
452
449
453
454 @pytest.mark.xfail(
455 sys.version_info.releaselevel not in ("final", "candidate"),
456 reason="fails on 3.13.dev",
457 )
450 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
458 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
451 @skip_win32
459 @skip_win32
452 def test_decorator_skip_with_breakpoint():
460 def test_decorator_skip_with_breakpoint():
453 """test that decorator frame skipping can be disabled"""
461 """test that decorator frame skipping can be disabled"""
454
462
455 import pexpect
463 import pexpect
456
464
457 env = os.environ.copy()
465 env = os.environ.copy()
458 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
466 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
459 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
467 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
460
468
461 child = pexpect.spawn(
469 child = pexpect.spawn(
462 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
470 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
463 )
471 )
464 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
472 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
465 child.str_last_chars = 500
473 child.str_last_chars = 500
466
474
467 child.expect("IPython")
475 child.expect("IPython")
468 child.expect("\n")
476 child.expect("\n")
469
477
470 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
478 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
471
479
472 ### we need a filename, so we need to exec the full block with a filename
480 ### we need a filename, so we need to exec the full block with a filename
473 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
481 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
474 name = tf.name[:-3].split("/")[-1]
482 name = tf.name[:-3].split("/")[-1]
475 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
483 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
476 tf.flush()
484 tf.flush()
477 codeblock = f"from {name} import f"
485 codeblock = f"from {name} import f"
478
486
479 dedented_blocks = [
487 dedented_blocks = [
480 codeblock,
488 codeblock,
481 "f()",
489 "f()",
482 ]
490 ]
483
491
484 in_prompt_number = 1
492 in_prompt_number = 1
485 for cblock in dedented_blocks:
493 for cblock in dedented_blocks:
486 child.expect_exact(f"In [{in_prompt_number}]:")
494 child.expect_exact(f"In [{in_prompt_number}]:")
487 in_prompt_number += 1
495 in_prompt_number += 1
488 for line in cblock.splitlines():
496 for line in cblock.splitlines():
489 child.sendline(line)
497 child.sendline(line)
490 child.expect_exact(line)
498 child.expect_exact(line)
491 child.sendline("")
499 child.sendline("")
492
500
493 # as the filename does not exists, we'll rely on the filename prompt
501 # as the filename does not exists, we'll rely on the filename prompt
494 child.expect_exact("47 bar(3, 4)")
502 child.expect_exact("47 bar(3, 4)")
495
503
496 for input_, expected in [
504 for input_, expected in [
497 (f"b {name}.py:3", ""),
505 (f"b {name}.py:3", ""),
498 ("step", "1---> 3 pass # should not stop here except"),
506 ("step", "1---> 3 pass # should not stop here except"),
499 ("step", "---> 38 @pdb_skipped_decorator"),
507 ("step", "---> 38 @pdb_skipped_decorator"),
500 ("continue", ""),
508 ("continue", ""),
501 ]:
509 ]:
502 child.expect("ipdb>")
510 child.expect("ipdb>")
503 child.sendline(input_)
511 child.sendline(input_)
504 child.expect_exact(input_)
512 child.expect_exact(input_)
505 child.expect_exact(expected)
513 child.expect_exact(expected)
506
514
507 child.close()
515 child.close()
508
516
509
517
510 @skip_win32
518 @skip_win32
511 def test_where_erase_value():
519 def test_where_erase_value():
512 """Test that `where` does not access f_locals and erase values."""
520 """Test that `where` does not access f_locals and erase values."""
513 import pexpect
521 import pexpect
514
522
515 env = os.environ.copy()
523 env = os.environ.copy()
516 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
524 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
517
525
518 child = pexpect.spawn(
526 child = pexpect.spawn(
519 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
527 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
520 )
528 )
521 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
529 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
522
530
523 child.expect("IPython")
531 child.expect("IPython")
524 child.expect("\n")
532 child.expect("\n")
525 child.expect_exact("In [1]")
533 child.expect_exact("In [1]")
526
534
527 block = dedent(
535 block = dedent(
528 """
536 """
529 def simple_f():
537 def simple_f():
530 myvar = 1
538 myvar = 1
531 print(myvar)
539 print(myvar)
532 1/0
540 1/0
533 print(myvar)
541 print(myvar)
534 simple_f() """
542 simple_f() """
535 )
543 )
536
544
537 for line in block.splitlines():
545 for line in block.splitlines():
538 child.sendline(line)
546 child.sendline(line)
539 child.expect_exact(line)
547 child.expect_exact(line)
540 child.expect_exact("ZeroDivisionError")
548 child.expect_exact("ZeroDivisionError")
541 child.expect_exact("In [2]:")
549 child.expect_exact("In [2]:")
542
550
543 child.sendline("%debug")
551 child.sendline("%debug")
544
552
545 ##
553 ##
546 child.expect("ipdb>")
554 child.expect("ipdb>")
547
555
548 child.sendline("myvar")
556 child.sendline("myvar")
549 child.expect("1")
557 child.expect("1")
550
558
551 ##
559 ##
552 child.expect("ipdb>")
560 child.expect("ipdb>")
553
561
554 child.sendline("myvar = 2")
562 child.sendline("myvar = 2")
555
563
556 ##
564 ##
557 child.expect_exact("ipdb>")
565 child.expect_exact("ipdb>")
558
566
559 child.sendline("myvar")
567 child.sendline("myvar")
560
568
561 child.expect_exact("2")
569 child.expect_exact("2")
562
570
563 ##
571 ##
564 child.expect("ipdb>")
572 child.expect("ipdb>")
565 child.sendline("where")
573 child.sendline("where")
566
574
567 ##
575 ##
568 child.expect("ipdb>")
576 child.expect("ipdb>")
569 child.sendline("myvar")
577 child.sendline("myvar")
570
578
571 child.expect_exact("2")
579 child.expect_exact("2")
572 child.expect("ipdb>")
580 child.expect("ipdb>")
573
581
574 child.close()
582 child.close()
@@ -1,579 +1,583 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(
492 sys.version_info.releaselevel not in ("final", "candidate"),
493 reason="fails on 3.13.dev",
494 )
491 def test_pinfo_docstring_dynamic():
495 def test_pinfo_docstring_dynamic():
492 obj_def = """class Bar:
496 obj_def = """class Bar:
493 __custom_documentations__ = {
497 __custom_documentations__ = {
494 "prop" : "cdoc for prop",
498 "prop" : "cdoc for prop",
495 "non_exist" : "cdoc for non_exist",
499 "non_exist" : "cdoc for non_exist",
496 }
500 }
497 @property
501 @property
498 def prop(self):
502 def prop(self):
499 '''
503 '''
500 Docstring for prop
504 Docstring for prop
501 '''
505 '''
502 return self._prop
506 return self._prop
503
507
504 @prop.setter
508 @prop.setter
505 def prop(self, v):
509 def prop(self, v):
506 self._prop = v
510 self._prop = v
507 """
511 """
508 ip.run_cell(obj_def)
512 ip.run_cell(obj_def)
509
513
510 ip.run_cell("b = Bar()")
514 ip.run_cell("b = Bar()")
511
515
512 with AssertPrints("Docstring: cdoc for prop"):
516 with AssertPrints("Docstring: cdoc for prop"):
513 ip.run_line_magic("pinfo", "b.prop")
517 ip.run_line_magic("pinfo", "b.prop")
514
518
515 with AssertPrints("Docstring: cdoc for non_exist"):
519 with AssertPrints("Docstring: cdoc for non_exist"):
516 ip.run_line_magic("pinfo", "b.non_exist")
520 ip.run_line_magic("pinfo", "b.non_exist")
517
521
518 with AssertPrints("Docstring: cdoc for prop"):
522 with AssertPrints("Docstring: cdoc for prop"):
519 ip.run_cell("b.prop?")
523 ip.run_cell("b.prop?")
520
524
521 with AssertPrints("Docstring: cdoc for non_exist"):
525 with AssertPrints("Docstring: cdoc for non_exist"):
522 ip.run_cell("b.non_exist?")
526 ip.run_cell("b.non_exist?")
523
527
524 with AssertPrints("Docstring: <no docstring>"):
528 with AssertPrints("Docstring: <no docstring>"):
525 ip.run_cell("b.undefined?")
529 ip.run_cell("b.undefined?")
526
530
527
531
528 def test_pinfo_magic():
532 def test_pinfo_magic():
529 with AssertPrints("Docstring:"):
533 with AssertPrints("Docstring:"):
530 ip._inspect("pinfo", "lsmagic", detail_level=0)
534 ip._inspect("pinfo", "lsmagic", detail_level=0)
531
535
532 with AssertPrints("Source:"):
536 with AssertPrints("Source:"):
533 ip._inspect("pinfo", "lsmagic", detail_level=1)
537 ip._inspect("pinfo", "lsmagic", detail_level=1)
534
538
535
539
536 def test_init_colors():
540 def test_init_colors():
537 # ensure colors are not present in signature info
541 # ensure colors are not present in signature info
538 info = inspector.info(HasSignature)
542 info = inspector.info(HasSignature)
539 init_def = info["init_definition"]
543 init_def = info["init_definition"]
540 assert "[0m" not in init_def
544 assert "[0m" not in init_def
541
545
542
546
543 def test_builtin_init():
547 def test_builtin_init():
544 info = inspector.info(list)
548 info = inspector.info(list)
545 init_def = info['init_definition']
549 init_def = info['init_definition']
546 assert init_def is not None
550 assert init_def is not None
547
551
548
552
549 def test_render_signature_short():
553 def test_render_signature_short():
550 def short_fun(a=1): pass
554 def short_fun(a=1): pass
551 sig = oinspect._render_signature(
555 sig = oinspect._render_signature(
552 signature(short_fun),
556 signature(short_fun),
553 short_fun.__name__,
557 short_fun.__name__,
554 )
558 )
555 assert sig == "short_fun(a=1)"
559 assert sig == "short_fun(a=1)"
556
560
557
561
558 def test_render_signature_long():
562 def test_render_signature_long():
559 from typing import Optional
563 from typing import Optional
560
564
561 def long_function(
565 def long_function(
562 a_really_long_parameter: int,
566 a_really_long_parameter: int,
563 and_another_long_one: bool = False,
567 and_another_long_one: bool = False,
564 let_us_make_sure_this_is_looong: Optional[str] = None,
568 let_us_make_sure_this_is_looong: Optional[str] = None,
565 ) -> bool: pass
569 ) -> bool: pass
566
570
567 sig = oinspect._render_signature(
571 sig = oinspect._render_signature(
568 signature(long_function),
572 signature(long_function),
569 long_function.__name__,
573 long_function.__name__,
570 )
574 )
571 expected = """\
575 expected = """\
572 long_function(
576 long_function(
573 a_really_long_parameter: int,
577 a_really_long_parameter: int,
574 and_another_long_one: bool = False,
578 and_another_long_one: bool = False,
575 let_us_make_sure_this_is_looong: Optional[str] = None,
579 let_us_make_sure_this_is_looong: Optional[str] = None,
576 ) -> bool\
580 ) -> bool\
577 """
581 """
578
582
579 assert sig == expected
583 assert sig == expected
General Comments 0
You need to be logged in to leave comments. Login now