Repository: microsoft/ptvsd

Issue:
sys.__breakpointhook__ should be available when __builtin__.breakpoint is set (pre Python 3.7)
This is because `pytest` checks if `__builtin__.breakpoint` is set but later moves on to use `sys.__breakpointhook__` which wouldn't be set.

i.e.:

https://github.com/pytest-dev/pytest/blob/master/src/_pytest/debugging.py#L67

I'll submit a fix for this shortly.


Discussion:

Resolving patch:
diff --git a/ptvsd/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py b/ptvsd/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py
--- a/ptvsd/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py
+++ b/ptvsd/_vendored/pydevd/pydev_sitecustomize/sitecustomize.py
@@ -60,7 +60,12 @@ def install_breakpointhook():
     def custom_sitecustomize_breakpointhook(*args, **kwargs):
         import os
         hookname = os.getenv('PYTHONBREAKPOINT')
-        if hookname is not None and len(hookname) > 0 and hasattr(sys, '__breakpointhook__'):
+        if (
+               hookname is not None 
+               and len(hookname) > 0 
+               and hasattr(sys, '__breakpointhook__')
+               and sys.__breakpointhook__ != custom_sitecustomize_breakpointhook
+            ):
             sys.__breakpointhook__(*args, **kwargs)
         else:
             sys.path.append(os.path.dirname(os.path.dirname(__file__)))
@@ -87,6 +92,7 @@ def custom_sitecustomize_breakpointhook(*args, **kwargs):
         # In older versions, breakpoint() isn't really available, so, install the hook directly
         # in the builtins.
         __builtin__.breakpoint = custom_sitecustomize_breakpointhook
+        sys.__breakpointhook__ = custom_sitecustomize_breakpointhook
 
 # Install the breakpoint hook at import time.
 install_breakpointhook()
diff --git a/ptvsd/_vendored/pydevd/pydevd.py b/ptvsd/_vendored/pydevd/pydevd.py
--- a/ptvsd/_vendored/pydevd/pydevd.py
+++ b/ptvsd/_vendored/pydevd/pydevd.py
@@ -59,7 +59,12 @@ def install_breakpointhook(pydevd_breakpointhook=None):
     if pydevd_breakpointhook is None:
         def pydevd_breakpointhook(*args, **kwargs):
             hookname = os.getenv('PYTHONBREAKPOINT')
-            if hookname is not None and len(hookname) > 0 and hasattr(sys, '__breakpointhook__'):
+            if (
+                   hookname is not None 
+                   and len(hookname) > 0 
+                   and hasattr(sys, '__breakpointhook__')
+                   and sys.__breakpointhook__ != pydevd_breakpointhook
+                ):
                 sys.__breakpointhook__(*args, **kwargs)
             else:
                 settrace(*args, **kwargs)
@@ -83,6 +88,7 @@ def pydevd_breakpointhook(*args, **kwargs):
         # In older versions, breakpoint() isn't really available, so, install the hook directly
         # in the builtins.
         __builtin__.breakpoint = pydevd_breakpointhook
+        sys.__breakpointhook__ = pydevd_breakpointhook
 
 # Install the breakpoint hook at import time.
 install_breakpointhook()


Test patch:
diff --git a/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint.py b/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint.py
--- a/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint.py
+++ b/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint.py
@@ -1,3 +1,4 @@
+
 def break_in_method():
     breakpoint()  # Builtin on Py3, but we provide a backport on Py2.
 
diff --git a/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint2.py b/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint2.py
new file mode 100644
--- /dev/null
+++ b/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_breakpoint2.py
@@ -0,0 +1,7 @@
+import sys
+def break_in_method():
+    sys.__breakpointhook__()  # Builtin on Py3, but we provide a backport on Py2.
+
+
+break_in_method()
+print('TEST SUCEEDED')
diff --git a/ptvsd/_vendored/pydevd/tests_python/test_debugger.py b/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
--- a/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
+++ b/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
@@ -1769,12 +1769,21 @@ def test_stop_on_start_regular(case_setup):
         writer.finished_ok = True
 
 
-def test_py_37_breakpoint(case_setup):
-    with case_setup.test_file('_debugger_case_breakpoint.py') as writer:
+def _get_breakpoint_cases():
+    if sys.version_info >= (3, 7):
+        # Just check breakpoint()
+        return ('_debugger_case_breakpoint.py',)
+    else:
+        # Check breakpoint() and sys.__breakpointhook__ replacement.
+        return ('_debugger_case_breakpoint.py', '_debugger_case_breakpoint2.py')
+
+@pytest.mark.parametrize("filename", _get_breakpoint_cases())
+def test_py_37_breakpoint(case_setup, filename):
+    with case_setup.test_file(filename) as writer:
         writer.write_make_initial_run()
 
         hit = writer.wait_for_breakpoint_hit(
-            REASON_THREAD_SUSPEND, file='_debugger_case_breakpoint.py', line=2)
+            REASON_THREAD_SUSPEND, file=filename, line=3)
 
         writer.write_run_thread(hit.thread_id)
 
diff --git a/ptvsd/_vendored/pydevd/tests_python/test_frame_eval_and_tracing.py b/ptvsd/_vendored/pydevd/tests_python/test_frame_eval_and_tracing.py
--- a/ptvsd/_vendored/pydevd/tests_python/test_frame_eval_and_tracing.py
+++ b/ptvsd/_vendored/pydevd/tests_python/test_frame_eval_and_tracing.py
@@ -13,6 +13,8 @@
 TEST_CYTHON = os.getenv('PYDEVD_USE_CYTHON', None) == 'YES'
 IS_APPVEYOR = os.environ.get('APPVEYOR', '') in ('True', 'true', '1')
 
+SKIP_FRAME_EVAL_TESTS = False
+
 
 class WriterThreadStepAndResume(debugger_unittest.AbstractWriterThread):
 
@@ -197,7 +199,7 @@ def run(self):
 
 
 @pytest.mark.skipif(
-    True, 
+    SKIP_FRAME_EVAL_TESTS, 
     reason='Frame eval is not currently meant to be used in the debugger and tests are flaky.\n'
            'Feature must be reviewed to be included again.\n'
 )