1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
3from __future__ import division
4from __future__ import print_function
5
6import os
7import sys
8
9import six
10
11import _pytest._code
12import pytest
13from _pytest.debugging import _validate_usepdb_cls
14
15try:
16    breakpoint
17except NameError:
18    SUPPORTS_BREAKPOINT_BUILTIN = False
19else:
20    SUPPORTS_BREAKPOINT_BUILTIN = True
21
22
23_ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "")
24
25
26def runpdb_and_get_report(testdir, source):
27    p = testdir.makepyfile(source)
28    result = testdir.runpytest_inprocess("--pdb", p)
29    reports = result.reprec.getreports("pytest_runtest_logreport")
30    assert len(reports) == 3, reports  # setup/call/teardown
31    return reports[1]
32
33
34@pytest.fixture
35def custom_pdb_calls():
36    called = []
37
38    # install dummy debugger class and track which methods were called on it
39    class _CustomPdb(object):
40        quitting = False
41
42        def __init__(self, *args, **kwargs):
43            called.append("init")
44
45        def reset(self):
46            called.append("reset")
47
48        def interaction(self, *args):
49            called.append("interaction")
50
51    _pytest._CustomPdb = _CustomPdb
52    return called
53
54
55@pytest.fixture
56def custom_debugger_hook():
57    called = []
58
59    # install dummy debugger class and track which methods were called on it
60    class _CustomDebugger(object):
61        def __init__(self, *args, **kwargs):
62            called.append("init")
63
64        def reset(self):
65            called.append("reset")
66
67        def interaction(self, *args):
68            called.append("interaction")
69
70        def set_trace(self, frame):
71            print("**CustomDebugger**")
72            called.append("set_trace")
73
74    _pytest._CustomDebugger = _CustomDebugger
75    yield called
76    del _pytest._CustomDebugger
77
78
79class TestPDB(object):
80    @pytest.fixture
81    def pdblist(self, request):
82        monkeypatch = request.getfixturevalue("monkeypatch")
83        pdblist = []
84
85        def mypdb(*args):
86            pdblist.append(args)
87
88        plugin = request.config.pluginmanager.getplugin("debugging")
89        monkeypatch.setattr(plugin, "post_mortem", mypdb)
90        return pdblist
91
92    def test_pdb_on_fail(self, testdir, pdblist):
93        rep = runpdb_and_get_report(
94            testdir,
95            """
96            def test_func():
97                assert 0
98        """,
99        )
100        assert rep.failed
101        assert len(pdblist) == 1
102        tb = _pytest._code.Traceback(pdblist[0][0])
103        assert tb[-1].name == "test_func"
104
105    def test_pdb_on_xfail(self, testdir, pdblist):
106        rep = runpdb_and_get_report(
107            testdir,
108            """
109            import pytest
110            @pytest.mark.xfail
111            def test_func():
112                assert 0
113        """,
114        )
115        assert "xfail" in rep.keywords
116        assert not pdblist
117
118    def test_pdb_on_skip(self, testdir, pdblist):
119        rep = runpdb_and_get_report(
120            testdir,
121            """
122            import pytest
123            def test_func():
124                pytest.skip("hello")
125        """,
126        )
127        assert rep.skipped
128        assert len(pdblist) == 0
129
130    def test_pdb_on_BdbQuit(self, testdir, pdblist):
131        rep = runpdb_and_get_report(
132            testdir,
133            """
134            import bdb
135            def test_func():
136                raise bdb.BdbQuit
137        """,
138        )
139        assert rep.failed
140        assert len(pdblist) == 0
141
142    def test_pdb_on_KeyboardInterrupt(self, testdir, pdblist):
143        rep = runpdb_and_get_report(
144            testdir,
145            """
146            def test_func():
147                raise KeyboardInterrupt
148        """,
149        )
150        assert rep.failed
151        assert len(pdblist) == 1
152
153    @staticmethod
154    def flush(child):
155        if child.isalive():
156            # Read if the test has not (e.g. test_pdb_unittest_skip).
157            child.read()
158            child.wait()
159        assert not child.isalive()
160
161    def test_pdb_unittest_postmortem(self, testdir):
162        p1 = testdir.makepyfile(
163            """
164            import unittest
165            class Blub(unittest.TestCase):
166                def tearDown(self):
167                    self.filename = None
168                def test_false(self):
169                    self.filename = 'debug' + '.me'
170                    assert 0
171        """
172        )
173        child = testdir.spawn_pytest("--pdb %s" % p1)
174        child.expect("Pdb")
175        child.sendline("p self.filename")
176        child.sendeof()
177        rest = child.read().decode("utf8")
178        assert "debug.me" in rest
179        self.flush(child)
180
181    def test_pdb_unittest_skip(self, testdir):
182        """Test for issue #2137"""
183        p1 = testdir.makepyfile(
184            """
185            import unittest
186            @unittest.skipIf(True, 'Skipping also with pdb active')
187            class MyTestCase(unittest.TestCase):
188                def test_one(self):
189                    assert 0
190        """
191        )
192        child = testdir.spawn_pytest("-rs --pdb %s" % p1)
193        child.expect("Skipping also with pdb active")
194        child.expect("1 skipped in")
195        child.sendeof()
196        self.flush(child)
197
198    def test_pdb_print_captured_stdout_and_stderr(self, testdir):
199        p1 = testdir.makepyfile(
200            """
201            def test_1():
202                import sys
203                sys.stderr.write("get\\x20rekt")
204                print("get\\x20rekt")
205                assert False
206
207            def test_not_called_due_to_quit():
208                pass
209        """
210        )
211        child = testdir.spawn_pytest("--pdb %s" % p1)
212        child.expect("captured stdout")
213        child.expect("get rekt")
214        child.expect("captured stderr")
215        child.expect("get rekt")
216        child.expect("traceback")
217        child.expect("def test_1")
218        child.expect("Pdb")
219        child.sendeof()
220        rest = child.read().decode("utf8")
221        assert "Exit: Quitting debugger" in rest
222        assert "= 1 failed in" in rest
223        assert "def test_1" not in rest
224        assert "get rekt" not in rest
225        self.flush(child)
226
227    def test_pdb_dont_print_empty_captured_stdout_and_stderr(self, testdir):
228        p1 = testdir.makepyfile(
229            """
230            def test_1():
231                assert False
232        """
233        )
234        child = testdir.spawn_pytest("--pdb %s" % p1)
235        child.expect("Pdb")
236        output = child.before.decode("utf8")
237        child.sendeof()
238        assert "captured stdout" not in output
239        assert "captured stderr" not in output
240        self.flush(child)
241
242    @pytest.mark.parametrize("showcapture", ["all", "no", "log"])
243    def test_pdb_print_captured_logs(self, testdir, showcapture):
244        p1 = testdir.makepyfile(
245            """
246            def test_1():
247                import logging
248                logging.warn("get " + "rekt")
249                assert False
250        """
251        )
252        child = testdir.spawn_pytest(
253            "--show-capture={} --pdb {}".format(showcapture, p1)
254        )
255        if showcapture in ("all", "log"):
256            child.expect("captured log")
257            child.expect("get rekt")
258        child.expect("Pdb")
259        child.sendeof()
260        rest = child.read().decode("utf8")
261        assert "1 failed" in rest
262        self.flush(child)
263
264    def test_pdb_print_captured_logs_nologging(self, testdir):
265        p1 = testdir.makepyfile(
266            """
267            def test_1():
268                import logging
269                logging.warn("get " + "rekt")
270                assert False
271        """
272        )
273        child = testdir.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1)
274        child.expect("get rekt")
275        output = child.before.decode("utf8")
276        assert "captured log" not in output
277        child.expect("Pdb")
278        child.sendeof()
279        rest = child.read().decode("utf8")
280        assert "1 failed" in rest
281        self.flush(child)
282
283    def test_pdb_interaction_exception(self, testdir):
284        p1 = testdir.makepyfile(
285            """
286            import pytest
287            def globalfunc():
288                pass
289            def test_1():
290                pytest.raises(ValueError, globalfunc)
291        """
292        )
293        child = testdir.spawn_pytest("--pdb %s" % p1)
294        child.expect(".*def test_1")
295        child.expect(".*pytest.raises.*globalfunc")
296        child.expect("Pdb")
297        child.sendline("globalfunc")
298        child.expect(".*function")
299        child.sendeof()
300        child.expect("1 failed")
301        self.flush(child)
302
303    def test_pdb_interaction_on_collection_issue181(self, testdir):
304        p1 = testdir.makepyfile(
305            """
306            import pytest
307            xxx
308        """
309        )
310        child = testdir.spawn_pytest("--pdb %s" % p1)
311        # child.expect(".*import pytest.*")
312        child.expect("Pdb")
313        child.sendline("c")
314        child.expect("1 error")
315        self.flush(child)
316
317    def test_pdb_interaction_on_internal_error(self, testdir):
318        testdir.makeconftest(
319            """
320            def pytest_runtest_protocol():
321                0/0
322        """
323        )
324        p1 = testdir.makepyfile("def test_func(): pass")
325        child = testdir.spawn_pytest("--pdb %s" % p1)
326        child.expect("Pdb")
327
328        # INTERNALERROR is only displayed once via terminal reporter.
329        assert (
330            len(
331                [
332                    x
333                    for x in child.before.decode().splitlines()
334                    if x.startswith("INTERNALERROR> Traceback")
335                ]
336            )
337            == 1
338        )
339
340        child.sendeof()
341        self.flush(child)
342
343    def test_pdb_interaction_capturing_simple(self, testdir):
344        p1 = testdir.makepyfile(
345            """
346            import pytest
347            def test_1():
348                i = 0
349                print("hello17")
350                pytest.set_trace()
351                i == 1
352                assert 0
353        """
354        )
355        child = testdir.spawn_pytest(str(p1))
356        child.expect(r"test_1\(\)")
357        child.expect("i == 1")
358        child.expect("Pdb")
359        child.sendline("c")
360        rest = child.read().decode("utf-8")
361        assert "AssertionError" in rest
362        assert "1 failed" in rest
363        assert "def test_1" in rest
364        assert "hello17" in rest  # out is captured
365        self.flush(child)
366
367    def test_pdb_set_trace_kwargs(self, testdir):
368        p1 = testdir.makepyfile(
369            """
370            import pytest
371            def test_1():
372                i = 0
373                print("hello17")
374                pytest.set_trace(header="== my_header ==")
375                x = 3
376                assert 0
377        """
378        )
379        child = testdir.spawn_pytest(str(p1))
380        child.expect("== my_header ==")
381        assert "PDB set_trace" not in child.before.decode()
382        child.expect("Pdb")
383        child.sendline("c")
384        rest = child.read().decode("utf-8")
385        assert "1 failed" in rest
386        assert "def test_1" in rest
387        assert "hello17" in rest  # out is captured
388        self.flush(child)
389
390    def test_pdb_set_trace_interception(self, testdir):
391        p1 = testdir.makepyfile(
392            """
393            import pdb
394            def test_1():
395                pdb.set_trace()
396        """
397        )
398        child = testdir.spawn_pytest(str(p1))
399        child.expect("test_1")
400        child.expect("Pdb")
401        child.sendline("q")
402        rest = child.read().decode("utf8")
403        assert "no tests ran" in rest
404        assert "reading from stdin while output" not in rest
405        assert "BdbQuit" not in rest
406        self.flush(child)
407
408    def test_pdb_and_capsys(self, testdir):
409        p1 = testdir.makepyfile(
410            """
411            import pytest
412            def test_1(capsys):
413                print("hello1")
414                pytest.set_trace()
415        """
416        )
417        child = testdir.spawn_pytest(str(p1))
418        child.expect("test_1")
419        child.send("capsys.readouterr()\n")
420        child.expect("hello1")
421        child.sendeof()
422        child.read()
423        self.flush(child)
424
425    def test_pdb_with_caplog_on_pdb_invocation(self, testdir):
426        p1 = testdir.makepyfile(
427            """
428            def test_1(capsys, caplog):
429                import logging
430                logging.getLogger(__name__).warning("some_warning")
431                assert 0
432        """
433        )
434        child = testdir.spawn_pytest("--pdb %s" % str(p1))
435        child.send("caplog.record_tuples\n")
436        child.expect_exact(
437            "[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]"
438        )
439        child.sendeof()
440        child.read()
441        self.flush(child)
442
443    def test_set_trace_capturing_afterwards(self, testdir):
444        p1 = testdir.makepyfile(
445            """
446            import pdb
447            def test_1():
448                pdb.set_trace()
449            def test_2():
450                print("hello")
451                assert 0
452        """
453        )
454        child = testdir.spawn_pytest(str(p1))
455        child.expect("test_1")
456        child.send("c\n")
457        child.expect("test_2")
458        child.expect("Captured")
459        child.expect("hello")
460        child.sendeof()
461        child.read()
462        self.flush(child)
463
464    def test_pdb_interaction_doctest(self, testdir, monkeypatch):
465        p1 = testdir.makepyfile(
466            """
467            import pytest
468            def function_1():
469                '''
470                >>> i = 0
471                >>> assert i == 1
472                '''
473        """
474        )
475        child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1)
476        child.expect("Pdb")
477
478        assert "UNEXPECTED EXCEPTION: AssertionError()" in child.before.decode("utf8")
479
480        child.sendline("'i=%i.' % i")
481        child.expect("Pdb")
482        assert "\r\n'i=0.'\r\n" in child.before.decode("utf8")
483
484        child.sendeof()
485        rest = child.read().decode("utf8")
486        assert "1 failed" in rest
487        self.flush(child)
488
489    def test_pdb_interaction_capturing_twice(self, testdir):
490        p1 = testdir.makepyfile(
491            """
492            import pytest
493            def test_1():
494                i = 0
495                print("hello17")
496                pytest.set_trace()
497                x = 3
498                print("hello18")
499                pytest.set_trace()
500                x = 4
501                assert 0
502        """
503        )
504        child = testdir.spawn_pytest(str(p1))
505        child.expect(r"PDB set_trace \(IO-capturing turned off\)")
506        child.expect("test_1")
507        child.expect("x = 3")
508        child.expect("Pdb")
509        child.sendline("c")
510        child.expect(r"PDB continue \(IO-capturing resumed\)")
511        child.expect(r"PDB set_trace \(IO-capturing turned off\)")
512        child.expect("x = 4")
513        child.expect("Pdb")
514        child.sendline("c")
515        child.expect("_ test_1 _")
516        child.expect("def test_1")
517        rest = child.read().decode("utf8")
518        assert "Captured stdout call" in rest
519        assert "hello17" in rest  # out is captured
520        assert "hello18" in rest  # out is captured
521        assert "1 failed" in rest
522        self.flush(child)
523
524    def test_pdb_with_injected_do_debug(self, testdir):
525        """Simulates pdbpp, which injects Pdb into do_debug, and uses
526        self.__class__ in do_continue.
527        """
528        p1 = testdir.makepyfile(
529            mytest="""
530            import pdb
531            import pytest
532
533            count_continue = 0
534
535            class CustomPdb(pdb.Pdb, object):
536                def do_debug(self, arg):
537                    import sys
538                    import types
539
540                    if sys.version_info < (3, ):
541                        do_debug_func = pdb.Pdb.do_debug.im_func
542                    else:
543                        do_debug_func = pdb.Pdb.do_debug
544
545                    newglobals = do_debug_func.__globals__.copy()
546                    newglobals['Pdb'] = self.__class__
547                    orig_do_debug = types.FunctionType(
548                        do_debug_func.__code__, newglobals,
549                        do_debug_func.__name__, do_debug_func.__defaults__,
550                    )
551                    return orig_do_debug(self, arg)
552                do_debug.__doc__ = pdb.Pdb.do_debug.__doc__
553
554                def do_continue(self, *args, **kwargs):
555                    global count_continue
556                    count_continue += 1
557                    return super(CustomPdb, self).do_continue(*args, **kwargs)
558
559            def foo():
560                print("print_from_foo")
561
562            def test_1():
563                i = 0
564                print("hello17")
565                pytest.set_trace()
566                x = 3
567                print("hello18")
568
569                assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue
570                pytest.fail("expected_failure")
571        """
572        )
573        child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1))
574        child.expect(r"PDB set_trace \(IO-capturing turned off\)")
575        child.expect(r"\n\(Pdb")
576        child.sendline("debug foo()")
577        child.expect("ENTERING RECURSIVE DEBUGGER")
578        child.expect(r"\n\(\(Pdb")
579        child.sendline("c")
580        child.expect("LEAVING RECURSIVE DEBUGGER")
581        assert b"PDB continue" not in child.before
582        # No extra newline.
583        assert child.before.endswith(b"c\r\nprint_from_foo\r\n")
584
585        # set_debug should not raise outcomes.Exit, if used recrursively.
586        child.sendline("debug 42")
587        child.sendline("q")
588        child.expect("LEAVING RECURSIVE DEBUGGER")
589        assert b"ENTERING RECURSIVE DEBUGGER" in child.before
590        assert b"Quitting debugger" not in child.before
591
592        child.sendline("c")
593        child.expect(r"PDB continue \(IO-capturing resumed\)")
594        rest = child.read().decode("utf8")
595        assert "hello17" in rest  # out is captured
596        assert "hello18" in rest  # out is captured
597        assert "1 failed" in rest
598        assert "Failed: expected_failure" in rest
599        assert "AssertionError: unexpected_failure" not in rest
600        self.flush(child)
601
602    def test_pdb_without_capture(self, testdir):
603        p1 = testdir.makepyfile(
604            """
605            import pytest
606            def test_1():
607                pytest.set_trace()
608        """
609        )
610        child = testdir.spawn_pytest("-s %s" % p1)
611        child.expect(r">>> PDB set_trace >>>")
612        child.expect("Pdb")
613        child.sendline("c")
614        child.expect(r">>> PDB continue >>>")
615        child.expect("1 passed")
616        self.flush(child)
617
618    @pytest.mark.parametrize("capture_arg", ("", "-s", "-p no:capture"))
619    def test_pdb_continue_with_recursive_debug(self, capture_arg, testdir):
620        """Full coverage for do_debug without capturing.
621
622        This is very similar to test_pdb_interaction_continue_recursive in general,
623        but mocks out ``pdb.set_trace`` for providing more coverage.
624        """
625        p1 = testdir.makepyfile(
626            """
627            try:
628                input = raw_input
629            except NameError:
630                pass
631
632            def set_trace():
633                __import__('pdb').set_trace()
634
635            def test_1(monkeypatch):
636                import _pytest.debugging
637
638                class pytestPDBTest(_pytest.debugging.pytestPDB):
639                    @classmethod
640                    def set_trace(cls, *args, **kwargs):
641                        # Init PytestPdbWrapper to handle capturing.
642                        _pdb = cls._init_pdb("set_trace", *args, **kwargs)
643
644                        # Mock out pdb.Pdb.do_continue.
645                        import pdb
646                        pdb.Pdb.do_continue = lambda self, arg: None
647
648                        print("===" + " SET_TRACE ===")
649                        assert input() == "debug set_trace()"
650
651                        # Simulate PytestPdbWrapper.do_debug
652                        cls._recursive_debug += 1
653                        print("ENTERING RECURSIVE DEBUGGER")
654                        print("===" + " SET_TRACE_2 ===")
655
656                        assert input() == "c"
657                        _pdb.do_continue("")
658                        print("===" + " SET_TRACE_3 ===")
659
660                        # Simulate PytestPdbWrapper.do_debug
661                        print("LEAVING RECURSIVE DEBUGGER")
662                        cls._recursive_debug -= 1
663
664                        print("===" + " SET_TRACE_4 ===")
665                        assert input() == "c"
666                        _pdb.do_continue("")
667
668                    def do_continue(self, arg):
669                        print("=== do_continue")
670
671                monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest)
672
673                import pdb
674                monkeypatch.setattr(pdb, "set_trace", pytestPDBTest.set_trace)
675
676                set_trace()
677        """
678        )
679        child = testdir.spawn_pytest("--tb=short %s %s" % (p1, capture_arg))
680        child.expect("=== SET_TRACE ===")
681        before = child.before.decode("utf8")
682        if not capture_arg:
683            assert ">>> PDB set_trace (IO-capturing turned off) >>>" in before
684        else:
685            assert ">>> PDB set_trace >>>" in before
686        child.sendline("debug set_trace()")
687        child.expect("=== SET_TRACE_2 ===")
688        before = child.before.decode("utf8")
689        assert "\r\nENTERING RECURSIVE DEBUGGER\r\n" in before
690        child.sendline("c")
691        child.expect("=== SET_TRACE_3 ===")
692
693        # No continue message with recursive debugging.
694        before = child.before.decode("utf8")
695        assert ">>> PDB continue " not in before
696
697        child.sendline("c")
698        child.expect("=== SET_TRACE_4 ===")
699        before = child.before.decode("utf8")
700        assert "\r\nLEAVING RECURSIVE DEBUGGER\r\n" in before
701        child.sendline("c")
702        rest = child.read().decode("utf8")
703        if not capture_arg:
704            assert "> PDB continue (IO-capturing resumed) >" in rest
705        else:
706            assert "> PDB continue >" in rest
707        assert "1 passed in" in rest
708
709    def test_pdb_used_outside_test(self, testdir):
710        p1 = testdir.makepyfile(
711            """
712            import pytest
713            pytest.set_trace()
714            x = 5
715        """
716        )
717        child = testdir.spawn("{} {}".format(sys.executable, p1))
718        child.expect("x = 5")
719        child.expect("Pdb")
720        child.sendeof()
721        self.flush(child)
722
723    def test_pdb_used_in_generate_tests(self, testdir):
724        p1 = testdir.makepyfile(
725            """
726            import pytest
727            def pytest_generate_tests(metafunc):
728                pytest.set_trace()
729                x = 5
730            def test_foo(a):
731                pass
732        """
733        )
734        child = testdir.spawn_pytest(str(p1))
735        child.expect("x = 5")
736        child.expect("Pdb")
737        child.sendeof()
738        self.flush(child)
739
740    def test_pdb_collection_failure_is_shown(self, testdir):
741        p1 = testdir.makepyfile("xxx")
742        result = testdir.runpytest_subprocess("--pdb", p1)
743        result.stdout.fnmatch_lines(
744            ["E   NameError: *xxx*", "*! *Exit: Quitting debugger !*"]  # due to EOF
745        )
746
747    @pytest.mark.parametrize("post_mortem", (False, True))
748    def test_enter_leave_pdb_hooks_are_called(self, post_mortem, testdir):
749        testdir.makeconftest(
750            """
751            mypdb = None
752
753            def pytest_configure(config):
754                config.testing_verification = 'configured'
755
756            def pytest_enter_pdb(config, pdb):
757                assert config.testing_verification == 'configured'
758                print('enter_pdb_hook')
759
760                global mypdb
761                mypdb = pdb
762                mypdb.set_attribute = "bar"
763
764            def pytest_leave_pdb(config, pdb):
765                assert config.testing_verification == 'configured'
766                print('leave_pdb_hook')
767
768                global mypdb
769                assert mypdb is pdb
770                assert mypdb.set_attribute == "bar"
771        """
772        )
773        p1 = testdir.makepyfile(
774            """
775            import pytest
776
777            def test_set_trace():
778                pytest.set_trace()
779                assert 0
780
781            def test_post_mortem():
782                assert 0
783        """
784        )
785        if post_mortem:
786            child = testdir.spawn_pytest(str(p1) + " --pdb -s -k test_post_mortem")
787        else:
788            child = testdir.spawn_pytest(str(p1) + " -k test_set_trace")
789        child.expect("enter_pdb_hook")
790        child.sendline("c")
791        if post_mortem:
792            child.expect(r"PDB continue")
793        else:
794            child.expect(r"PDB continue \(IO-capturing resumed\)")
795            child.expect("Captured stdout call")
796        rest = child.read().decode("utf8")
797        assert "leave_pdb_hook" in rest
798        assert "1 failed" in rest
799        self.flush(child)
800
801    def test_pdb_custom_cls(self, testdir, custom_pdb_calls):
802        p1 = testdir.makepyfile("""xxx """)
803        result = testdir.runpytest_inprocess("--pdb", "--pdbcls=_pytest:_CustomPdb", p1)
804        result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
805        assert custom_pdb_calls == ["init", "reset", "interaction"]
806
807    def test_pdb_custom_cls_invalid(self, testdir):
808        result = testdir.runpytest_inprocess("--pdbcls=invalid")
809        result.stderr.fnmatch_lines(
810            [
811                "*: error: argument --pdbcls: 'invalid' is not in the format 'modname:classname'"
812            ]
813        )
814
815    def test_pdb_validate_usepdb_cls(self, testdir):
816        assert _validate_usepdb_cls("os.path:dirname.__name__") == (
817            "os.path",
818            "dirname.__name__",
819        )
820
821        assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist")
822
823    def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls):
824        p1 = testdir.makepyfile("""xxx """)
825        result = testdir.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1)
826        result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
827        assert custom_pdb_calls == []
828
829    def test_pdb_custom_cls_with_set_trace(self, testdir, monkeypatch):
830        testdir.makepyfile(
831            custom_pdb="""
832            class CustomPdb(object):
833                def __init__(self, *args, **kwargs):
834                    skip = kwargs.pop("skip")
835                    assert skip == ["foo.*"]
836                    print("__init__")
837                    super(CustomPdb, self).__init__(*args, **kwargs)
838
839                def set_trace(*args, **kwargs):
840                    print('custom set_trace>')
841         """
842        )
843        p1 = testdir.makepyfile(
844            """
845            import pytest
846
847            def test_foo():
848                pytest.set_trace(skip=['foo.*'])
849        """
850        )
851        monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
852        child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1))
853
854        child.expect("__init__")
855        child.expect("custom set_trace>")
856        self.flush(child)
857
858
859class TestDebuggingBreakpoints(object):
860    def test_supports_breakpoint_module_global(self):
861        """
862        Test that supports breakpoint global marks on Python 3.7+ and not on
863        CPython 3.5, 2.7
864        """
865        if sys.version_info.major == 3 and sys.version_info.minor >= 7:
866            assert SUPPORTS_BREAKPOINT_BUILTIN is True
867        if sys.version_info.major == 3 and sys.version_info.minor == 5:
868            assert SUPPORTS_BREAKPOINT_BUILTIN is False
869        if sys.version_info.major == 2 and sys.version_info.minor == 7:
870            assert SUPPORTS_BREAKPOINT_BUILTIN is False
871
872    @pytest.mark.skipif(
873        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
874    )
875    @pytest.mark.parametrize("arg", ["--pdb", ""])
876    def test_sys_breakpointhook_configure_and_unconfigure(self, testdir, arg):
877        """
878        Test that sys.breakpointhook is set to the custom Pdb class once configured, test that
879        hook is reset to system value once pytest has been unconfigured
880        """
881        testdir.makeconftest(
882            """
883            import sys
884            from pytest import hookimpl
885            from _pytest.debugging import pytestPDB
886
887            def pytest_configure(config):
888                config._cleanup.append(check_restored)
889
890            def check_restored():
891                assert sys.breakpointhook == sys.__breakpointhook__
892
893            def test_check():
894                assert sys.breakpointhook == pytestPDB.set_trace
895        """
896        )
897        testdir.makepyfile(
898            """
899            def test_nothing(): pass
900        """
901        )
902        args = (arg,) if arg else ()
903        result = testdir.runpytest_subprocess(*args)
904        result.stdout.fnmatch_lines(["*1 passed in *"])
905
906    @pytest.mark.skipif(
907        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
908    )
909    def test_pdb_custom_cls(self, testdir, custom_debugger_hook):
910        p1 = testdir.makepyfile(
911            """
912            def test_nothing():
913                breakpoint()
914        """
915        )
916        result = testdir.runpytest_inprocess(
917            "--pdb", "--pdbcls=_pytest:_CustomDebugger", p1
918        )
919        result.stdout.fnmatch_lines(["*CustomDebugger*", "*1 passed*"])
920        assert custom_debugger_hook == ["init", "set_trace"]
921
922    @pytest.mark.parametrize("arg", ["--pdb", ""])
923    @pytest.mark.skipif(
924        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
925    )
926    def test_environ_custom_class(self, testdir, custom_debugger_hook, arg):
927        testdir.makeconftest(
928            """
929            import os
930            import sys
931
932            os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace'
933
934            def pytest_configure(config):
935                config._cleanup.append(check_restored)
936
937            def check_restored():
938                assert sys.breakpointhook == sys.__breakpointhook__
939
940            def test_check():
941                import _pytest
942                assert sys.breakpointhook is _pytest._CustomDebugger.set_trace
943        """
944        )
945        testdir.makepyfile(
946            """
947            def test_nothing(): pass
948        """
949        )
950        args = (arg,) if arg else ()
951        result = testdir.runpytest_subprocess(*args)
952        result.stdout.fnmatch_lines(["*1 passed in *"])
953
954    @pytest.mark.skipif(
955        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
956    )
957    @pytest.mark.skipif(
958        not _ENVIRON_PYTHONBREAKPOINT == "",
959        reason="Requires breakpoint() default value",
960    )
961    def test_sys_breakpoint_interception(self, testdir):
962        p1 = testdir.makepyfile(
963            """
964            def test_1():
965                breakpoint()
966        """
967        )
968        child = testdir.spawn_pytest(str(p1))
969        child.expect("test_1")
970        child.expect("Pdb")
971        child.sendline("quit")
972        rest = child.read().decode("utf8")
973        assert "Quitting debugger" in rest
974        assert "reading from stdin while output" not in rest
975        TestPDB.flush(child)
976
977    @pytest.mark.skipif(
978        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
979    )
980    def test_pdb_not_altered(self, testdir):
981        p1 = testdir.makepyfile(
982            """
983            import pdb
984            def test_1():
985                pdb.set_trace()
986                assert 0
987        """
988        )
989        child = testdir.spawn_pytest(str(p1))
990        child.expect("test_1")
991        child.expect("Pdb")
992        child.sendline("c")
993        rest = child.read().decode("utf8")
994        assert "1 failed" in rest
995        assert "reading from stdin while output" not in rest
996        TestPDB.flush(child)
997
998
999class TestTraceOption:
1000    def test_trace_sets_breakpoint(self, testdir):
1001        p1 = testdir.makepyfile(
1002            """
1003            def test_1():
1004                assert True
1005
1006            def test_2():
1007                pass
1008
1009            def test_3():
1010                pass
1011            """
1012        )
1013        child = testdir.spawn_pytest("--trace " + str(p1))
1014        child.expect("test_1")
1015        child.expect("Pdb")
1016        child.sendline("c")
1017        child.expect("test_2")
1018        child.expect("Pdb")
1019        child.sendline("c")
1020        child.expect("test_3")
1021        child.expect("Pdb")
1022        child.sendline("q")
1023        child.expect_exact("Exit: Quitting debugger")
1024        rest = child.read().decode("utf8")
1025        assert "2 passed in" in rest
1026        assert "reading from stdin while output" not in rest
1027        # Only printed once - not on stderr.
1028        assert "Exit: Quitting debugger" not in child.before.decode("utf8")
1029        TestPDB.flush(child)
1030
1031
1032def test_trace_after_runpytest(testdir):
1033    """Test that debugging's pytest_configure is re-entrant."""
1034    p1 = testdir.makepyfile(
1035        """
1036        from _pytest.debugging import pytestPDB
1037
1038        def test_outer(testdir):
1039            assert len(pytestPDB._saved) == 1
1040
1041            testdir.makepyfile(
1042                \"""
1043                from _pytest.debugging import pytestPDB
1044
1045                def test_inner():
1046                    assert len(pytestPDB._saved) == 2
1047                    print()
1048                    print("test_inner_" + "end")
1049                \"""
1050            )
1051
1052            result = testdir.runpytest("-s", "-k", "test_inner")
1053            assert result.ret == 0
1054
1055            assert len(pytestPDB._saved) == 1
1056    """
1057    )
1058    result = testdir.runpytest_subprocess("-s", "-p", "pytester", str(p1))
1059    result.stdout.fnmatch_lines(["test_inner_end"])
1060    assert result.ret == 0
1061
1062
1063def test_quit_with_swallowed_SystemExit(testdir):
1064    """Test that debugging's pytest_configure is re-entrant."""
1065    p1 = testdir.makepyfile(
1066        """
1067        def call_pdb_set_trace():
1068            __import__('pdb').set_trace()
1069
1070
1071        def test_1():
1072            try:
1073                call_pdb_set_trace()
1074            except SystemExit:
1075                pass
1076
1077
1078        def test_2():
1079            pass
1080    """
1081    )
1082    child = testdir.spawn_pytest(str(p1))
1083    child.expect("Pdb")
1084    child.sendline("q")
1085    child.expect_exact("Exit: Quitting debugger")
1086    rest = child.read().decode("utf8")
1087    assert "no tests ran" in rest
1088    TestPDB.flush(child)
1089
1090
1091@pytest.mark.parametrize("fixture", ("capfd", "capsys"))
1092def test_pdb_suspends_fixture_capturing(testdir, fixture):
1093    """Using "-s" with pytest should suspend/resume fixture capturing."""
1094    p1 = testdir.makepyfile(
1095        """
1096        def test_inner({fixture}):
1097            import sys
1098
1099            print("out_inner_before")
1100            sys.stderr.write("err_inner_before\\n")
1101
1102            __import__("pdb").set_trace()
1103
1104            print("out_inner_after")
1105            sys.stderr.write("err_inner_after\\n")
1106
1107            out, err = {fixture}.readouterr()
1108            assert out =="out_inner_before\\nout_inner_after\\n"
1109            assert err =="err_inner_before\\nerr_inner_after\\n"
1110        """.format(
1111            fixture=fixture
1112        )
1113    )
1114
1115    child = testdir.spawn_pytest(str(p1) + " -s")
1116
1117    child.expect("Pdb")
1118    before = child.before.decode("utf8")
1119    assert (
1120        "> PDB set_trace (IO-capturing turned off for fixture %s) >" % (fixture)
1121        in before
1122    )
1123
1124    # Test that capturing is really suspended.
1125    child.sendline("p 40 + 2")
1126    child.expect("Pdb")
1127    assert "\r\n42\r\n" in child.before.decode("utf8")
1128
1129    child.sendline("c")
1130    rest = child.read().decode("utf8")
1131    assert "out_inner" not in rest
1132    assert "err_inner" not in rest
1133
1134    TestPDB.flush(child)
1135    assert child.exitstatus == 0
1136    assert "= 1 passed in " in rest
1137    assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest
1138
1139
1140def test_pdbcls_via_local_module(testdir):
1141    """It should be imported in pytest_configure or later only."""
1142    p1 = testdir.makepyfile(
1143        """
1144        def test():
1145            print("before_set_trace")
1146            __import__("pdb").set_trace()
1147        """,
1148        mypdb="""
1149        class Wrapped:
1150            class MyPdb:
1151                def set_trace(self, *args):
1152                    print("set_trace_called", args)
1153
1154                def runcall(self, *args, **kwds):
1155                    print("runcall_called", args, kwds)
1156                    assert "func" in kwds
1157        """,
1158    )
1159    result = testdir.runpytest(
1160        str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True
1161    )
1162    result.stdout.fnmatch_lines(
1163        [
1164            "*= FAILURES =*",
1165            "E * --pdbcls: could not import 'really.invalid:Value': No module named *really*",
1166        ]
1167    )
1168    assert result.ret == 1
1169
1170    result = testdir.runpytest(
1171        str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True
1172    )
1173    assert result.ret == 0
1174    result.stdout.fnmatch_lines(["*set_trace_called*", "* 1 passed in *"])
1175
1176    # Ensure that it also works with --trace.
1177    result = testdir.runpytest(
1178        str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", "--trace", syspathinsert=True
1179    )
1180    assert result.ret == 0
1181    result.stdout.fnmatch_lines(["*runcall_called*", "* 1 passed in *"])
1182
1183
1184def test_raises_bdbquit_with_eoferror(testdir):
1185    """It is not guaranteed that DontReadFromInput's read is called."""
1186    if six.PY2:
1187        builtin_module = "__builtin__"
1188        input_func = "raw_input"
1189    else:
1190        builtin_module = "builtins"
1191        input_func = "input"
1192    p1 = testdir.makepyfile(
1193        """
1194        def input_without_read(*args, **kwargs):
1195            raise EOFError()
1196
1197        def test(monkeypatch):
1198            import {builtin_module}
1199            monkeypatch.setattr({builtin_module}, {input_func!r}, input_without_read)
1200            __import__('pdb').set_trace()
1201        """.format(
1202            builtin_module=builtin_module, input_func=input_func
1203        )
1204    )
1205    result = testdir.runpytest(str(p1))
1206    result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"])
1207    assert result.ret == 1
1208
1209
1210def test_pdb_wrapper_class_is_reused(testdir):
1211    p1 = testdir.makepyfile(
1212        """
1213        def test():
1214            __import__("pdb").set_trace()
1215            __import__("pdb").set_trace()
1216
1217            import mypdb
1218            instances = mypdb.instances
1219            assert len(instances) == 2
1220            assert instances[0].__class__ is instances[1].__class__
1221        """,
1222        mypdb="""
1223        instances = []
1224
1225        class MyPdb:
1226            def __init__(self, *args, **kwargs):
1227                instances.append(self)
1228
1229            def set_trace(self, *args):
1230                print("set_trace_called", args)
1231        """,
1232    )
1233    result = testdir.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True)
1234    assert result.ret == 0
1235    result.stdout.fnmatch_lines(
1236        ["*set_trace_called*", "*set_trace_called*", "* 1 passed in *"]
1237    )
1238