|
1 | 1 | from __future__ import absolute_import, division, print_function
|
2 | 2 | import sys
|
3 | 3 | import platform
|
| 4 | +import os |
4 | 5 |
|
5 | 6 | import _pytest._code
|
| 7 | +from _pytest.debugging import SUPPORTS_BREAKPOINT_BUILTIN |
6 | 8 | import pytest
|
7 | 9 |
|
8 | 10 |
|
| 11 | +_ENVIRON_PYTHONBREAKPOINT = os.environ.get('PYTHONBREAKPOINT', '') |
| 12 | + |
| 13 | + |
9 | 14 | def runpdb_and_get_report(testdir, source):
|
10 | 15 | p = testdir.makepyfile(source)
|
11 | 16 | result = testdir.runpytest_inprocess("--pdb", p)
|
@@ -33,6 +38,30 @@ def interaction(self, *args):
|
33 | 38 | return called
|
34 | 39 |
|
35 | 40 |
|
| 41 | +@pytest.fixture |
| 42 | +def custom_debugger_hook(): |
| 43 | + called = [] |
| 44 | + |
| 45 | + # install dummy debugger class and track which methods were called on it |
| 46 | + class _CustomDebugger(object): |
| 47 | + def __init__(self, *args, **kwargs): |
| 48 | + called.append("init") |
| 49 | + |
| 50 | + def reset(self): |
| 51 | + called.append("reset") |
| 52 | + |
| 53 | + def interaction(self, *args): |
| 54 | + called.append("interaction") |
| 55 | + |
| 56 | + def set_trace(self, frame): |
| 57 | + print("**CustomDebugger**") |
| 58 | + called.append("set_trace") |
| 59 | + |
| 60 | + _pytest._CustomDebugger = _CustomDebugger |
| 61 | + yield called |
| 62 | + del _pytest._CustomDebugger |
| 63 | + |
| 64 | + |
36 | 65 | class TestPDB(object):
|
37 | 66 |
|
38 | 67 | @pytest.fixture
|
@@ -470,3 +499,122 @@ def test_foo():
|
470 | 499 |
|
471 | 500 | child.expect('custom set_trace>')
|
472 | 501 | self.flush(child)
|
| 502 | + |
| 503 | + |
| 504 | +class TestDebuggingBreakpoints(object): |
| 505 | + |
| 506 | + def test_supports_breakpoint_module_global(self): |
| 507 | + """ |
| 508 | + Test that supports breakpoint global marks on Python 3.7+ and not on |
| 509 | + CPython 3.5, 2.7 |
| 510 | + """ |
| 511 | + if sys.version_info.major == 3 and sys.version_info.minor >= 7: |
| 512 | + assert SUPPORTS_BREAKPOINT_BUILTIN is True |
| 513 | + if sys.version_info.major == 3 and sys.version_info.minor == 5: |
| 514 | + assert SUPPORTS_BREAKPOINT_BUILTIN is False |
| 515 | + if sys.version_info.major == 2 and sys.version_info.minor == 7: |
| 516 | + assert SUPPORTS_BREAKPOINT_BUILTIN is False |
| 517 | + |
| 518 | + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") |
| 519 | + @pytest.mark.parametrize('arg', ['--pdb', '']) |
| 520 | + def test_sys_breakpointhook_configure_and_unconfigure(self, testdir, arg): |
| 521 | + """ |
| 522 | + Test that sys.breakpointhook is set to the custom Pdb class once configured, test that |
| 523 | + hook is reset to system value once pytest has been unconfigured |
| 524 | + """ |
| 525 | + testdir.makeconftest(""" |
| 526 | + import sys |
| 527 | + from pytest import hookimpl |
| 528 | + from _pytest.debugging import pytestPDB |
| 529 | +
|
| 530 | + def pytest_configure(config): |
| 531 | + config._cleanup.append(check_restored) |
| 532 | +
|
| 533 | + def check_restored(): |
| 534 | + assert sys.breakpointhook == sys.__breakpointhook__ |
| 535 | +
|
| 536 | + def test_check(): |
| 537 | + assert sys.breakpointhook == pytestPDB.set_trace |
| 538 | + """) |
| 539 | + testdir.makepyfile(""" |
| 540 | + def test_nothing(): pass |
| 541 | + """) |
| 542 | + args = (arg,) if arg else () |
| 543 | + result = testdir.runpytest_subprocess(*args) |
| 544 | + result.stdout.fnmatch_lines([ |
| 545 | + '*1 passed in *', |
| 546 | + ]) |
| 547 | + |
| 548 | + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") |
| 549 | + def test_pdb_custom_cls(self, testdir, custom_debugger_hook): |
| 550 | + p1 = testdir.makepyfile(""" |
| 551 | + def test_nothing(): |
| 552 | + breakpoint() |
| 553 | + """) |
| 554 | + result = testdir.runpytest_inprocess( |
| 555 | + "--pdb", "--pdbcls=_pytest:_CustomDebugger", p1) |
| 556 | + result.stdout.fnmatch_lines([ |
| 557 | + "*CustomDebugger*", |
| 558 | + "*1 passed*", |
| 559 | + ]) |
| 560 | + assert custom_debugger_hook == ["init", "set_trace"] |
| 561 | + |
| 562 | + @pytest.mark.parametrize('arg', ['--pdb', '']) |
| 563 | + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") |
| 564 | + def test_environ_custom_class(self, testdir, custom_debugger_hook, arg): |
| 565 | + testdir.makeconftest(""" |
| 566 | + import os |
| 567 | + import sys |
| 568 | +
|
| 569 | + os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' |
| 570 | +
|
| 571 | + def pytest_configure(config): |
| 572 | + config._cleanup.append(check_restored) |
| 573 | +
|
| 574 | + def check_restored(): |
| 575 | + assert sys.breakpointhook == sys.__breakpointhook__ |
| 576 | +
|
| 577 | + def test_check(): |
| 578 | + import _pytest |
| 579 | + assert sys.breakpointhook is _pytest._CustomDebugger.set_trace |
| 580 | + """) |
| 581 | + testdir.makepyfile(""" |
| 582 | + def test_nothing(): pass |
| 583 | + """) |
| 584 | + args = (arg,) if arg else () |
| 585 | + result = testdir.runpytest_subprocess(*args) |
| 586 | + result.stdout.fnmatch_lines([ |
| 587 | + '*1 passed in *', |
| 588 | + ]) |
| 589 | + |
| 590 | + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") |
| 591 | + @pytest.mark.skipif(not _ENVIRON_PYTHONBREAKPOINT == '', reason="Requires breakpoint() default value") |
| 592 | + def test_sys_breakpoint_interception(self, testdir): |
| 593 | + p1 = testdir.makepyfile(""" |
| 594 | + def test_1(): |
| 595 | + breakpoint() |
| 596 | + """) |
| 597 | + child = testdir.spawn_pytest(str(p1)) |
| 598 | + child.expect("test_1") |
| 599 | + child.expect("(Pdb)") |
| 600 | + child.sendeof() |
| 601 | + rest = child.read().decode("utf8") |
| 602 | + assert "1 failed" in rest |
| 603 | + assert "reading from stdin while output" not in rest |
| 604 | + TestPDB.flush(child) |
| 605 | + |
| 606 | + @pytest.mark.skipif(not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin") |
| 607 | + def test_pdb_not_altered(self, testdir): |
| 608 | + p1 = testdir.makepyfile(""" |
| 609 | + import pdb |
| 610 | + def test_1(): |
| 611 | + pdb.set_trace() |
| 612 | + """) |
| 613 | + child = testdir.spawn_pytest(str(p1)) |
| 614 | + child.expect("test_1") |
| 615 | + child.expect("(Pdb)") |
| 616 | + child.sendeof() |
| 617 | + rest = child.read().decode("utf8") |
| 618 | + assert "1 failed" in rest |
| 619 | + assert "reading from stdin while output" not in rest |
| 620 | + TestPDB.flush(child) |
0 commit comments