Repository: spyder-ide/spyder

Issue:
Regression: Spyder 5.1.5 doesn't enter on debug mode on breakpoint() command
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [ ] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [x] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [x] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description

Doesn't enter on debug mode on breakpoint() command. It was working very well with Spyder 5.1.1 but broke as soon as I updated to 5.1.5. Is there any new setting to be changed?

### What steps reproduce the problem?
```python
for i in range(3):
    breakpoint()
    print(i)
```
### What is the expected output? What do you see instead?

Should enter in debug mode and stop before `print(i)`. It is working within IPython started from the terminal:

```
(py38_forge) C:\Windows\system32>ipython
Python 3.8.12 | packaged by conda-forge | (default, Sep 16 2021, 01:40:49) [MSC v.1916 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.27.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: for i in range(3):
   ...:     breakpoint()
   ...:     print(i)
   ...:
<ipython-input-1-f0a609844131> in <module>
      1 for i in range(3):
      2     breakpoint()
----> 3     print(i)
      4
```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 5.1.5 None
* Python version: 3.8.12 64-bit
* Qt version: 5.12.9
* PyQt5 version: 5.12.3
* Operating System: Windows 10

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
# Mandatory:
atomicwrites >=1.2.0          :  1.4.0 (OK)
chardet >=2.0.0               :  4.0.0 (OK)
cloudpickle >=0.5.0           :  2.0.0 (OK)
cookiecutter >=1.6.0          :  1.6.0 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.0.2 (OK)
IPython >=7.6.0               :  7.27.0 (OK)
jedi >=0.17.2;<0.19.0         :  0.18.0 (OK)
jsonschema >=3.2.0            :  3.2.0 (OK)
keyring >=17.0.0              :  23.2.1 (OK)
nbconvert >=4.0               :  6.1.0 (OK)
numpydoc >=0.6.0              :  1.1.0 (OK)
paramiko >=2.4.0              :  2.7.2 (OK)
parso >=0.7.0;<0.9.0          :  0.8.2 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.8.0 (OK)
pygments >=2.0                :  2.10.0 (OK)
pylint >=2.5.0;<2.10.0        :  2.9.6 (OK)
pyls_spyder >=0.4.0           :  0.4.0 (OK)
pylsp >=1.2.2;<1.3.0          :  1.2.2 (OK)
pylsp_black >=1.0.0           :  None (OK)
qdarkstyle =3.0.2             :  3.0.2 (OK)
qstylizer >=0.1.10            :  0.2.1 (OK)
qtawesome >=1.0.2             :  1.0.3 (OK)
qtconsole >=5.1.0             :  5.1.1 (OK)
qtpy >=1.5.0                  :  1.11.1 (OK)
rtree >=0.9.7                 :  0.9.7 (OK)
setuptools >=49.6.0           :  58.0.4 (OK)
sphinx >=0.6.6                :  4.2.0 (OK)
spyder_kernels >=2.1.1;<2.2.0 :  2.1.1 (OK)
textdistance >=4.2.0          :  4.2.1 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3             :  2.1.5 (OK)
zmq >=17                      :  22.3.0 (OK)

# Optional:
cython >=0.21                 :  None (OK)
matplotlib >=2.0.0            :  3.3.2 (OK)
numpy >=1.7                   :  1.21.2 (OK)
pandas >=1.1.1                :  1.2.4 (OK)
scipy >=0.17.0                :  1.6.3 (OK)
sympy >=0.7.3                 :  None (OK)
```



Discussion:
HI @mhabets thanks for reporting. I can successfully reproduce this issue in 5.1.5. We will need to fix it in our next version. @ccordoba12 
Hey @juanis2112, thanks for the ping and sorry for my late reply! I have a fix for this, which will be part of the next version of spyder-kernels, to be released tomorrow.

@mhabets, if you're using pip or conda-forge, you'll need to update to version **2.1.2** (once it's out) to get it.

Resolving patch:
diff --git a/external-deps/spyder-kernels/spyder_kernels/console/start.py b/external-deps/spyder-kernels/spyder_kernels/console/start.py
--- a/external-deps/spyder-kernels/spyder_kernels/console/start.py
+++ b/external-deps/spyder-kernels/spyder_kernels/console/start.py
@@ -121,6 +121,15 @@ def kernel_config():
             "del builtins.spyder_runfile; del builtins"
         )
 
+    # Prevent other libraries to change the breakpoint builtin.
+    # This started to be a problem since IPykernel 6.3.0.
+    if sys.version_info[0:2] >= (3, 7):
+        spy_cfg.IPKernelApp.exec_lines.append(
+            "import sys; import pdb; "
+            "sys.breakpointhook = pdb.set_trace; "
+            "del sys; del pdb"
+        )
+
     # Run lines of code at startup
     run_lines_o = os.environ.get('SPY_RUN_LINES_O')
     if run_lines_o is not None:
diff --git a/external-deps/spyder-kernels/spyder_kernels/customize/spydercustomize.py b/external-deps/spyder-kernels/spyder_kernels/customize/spydercustomize.py
--- a/external-deps/spyder-kernels/spyder_kernels/customize/spydercustomize.py
+++ b/external-deps/spyder-kernels/spyder_kernels/customize/spydercustomize.py
@@ -487,7 +487,7 @@ def get_file_code(filename, save_all=True):
     try:
         file_code = frontend_request().get_file_code(
             filename, save_all=save_all)
-    except (CommError, TimeoutError):
+    except (CommError, TimeoutError, RuntimeError):
         file_code = None
     if file_code is None:
         with open(filename, 'r') as f:
diff --git a/spyder/plugins/ipythonconsole/plugin.py b/spyder/plugins/ipythonconsole/plugin.py
--- a/spyder/plugins/ipythonconsole/plugin.py
+++ b/spyder/plugins/ipythonconsole/plugin.py
@@ -213,8 +213,9 @@ def __init__(self, parent, testing=False, test_dir=None,
 
         self.tabwidget.set_close_function(self.close_client)
 
-        self.main.editor.sig_file_debug_message_requested.connect(
-            self.print_debug_file_msg)
+        if self.main.editor:
+            self.main.editor.sig_file_debug_message_requested.connect(
+                self.print_debug_file_msg)
 
         if sys.platform == 'darwin':
             tab_container = QWidget()


Test patch:
diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
--- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
+++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
@@ -102,6 +102,8 @@ def get_spyder_pythonpath(self):
         def __getattr__(self, attr):
             if attr == 'consoles_menu_actions':
                 return []
+            elif attr == 'editor':
+                return None
             else:
                 return Mock()
 
@@ -1946,5 +1948,33 @@ def test_pdb_code_and_cmd_separation(ipyconsole, qtbot):
     assert "Unknown command 'abba'" in control.toPlainText()
 
 
+@flaky(max_runs=3)
+@pytest.mark.skipif(os.name == 'nt', reason="Falis on Windows")
+def test_breakpoint_builtin(ipyconsole, qtbot, tmpdir):
+    """Check that the breakpoint builtin is working."""
+    shell = ipyconsole.get_current_shellwidget()
+    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+                    timeout=SHELL_TIMEOUT)
+    control = ipyconsole.get_focus_widget()
+
+    # Code to run
+    code = dedent("""
+    print('foo')
+    breakpoint()
+    """)
+
+    # Write code to file on disk
+    file = tmpdir.join('test_breakpoint.py')
+    file.write(code)
+
+    # Run file
+    with qtbot.waitSignal(shell.executed):
+        shell.execute(f"runfile(filename='{str(file)}')")
+
+    # Assert we entered debugging after the print statement
+    assert 'foo' in control.toPlainText()
+    assert 'IPdb [1]:' in control.toPlainText()
+
+
 if __name__ == "__main__":
     pytest.main()