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