Repository: spyder-ide/spyder

Issue:
Check code update due to QTextEdit bug solved on PyQt 5.15
I think we should open a new issue to not forget removing the code added in PR #12663 in our next version.

_Originally posted by @ccordoba12 in https://github.com/spyder-ide/spyder/pull/16409#discussion_r827409446_


Discussion:

Resolving patch:
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -1186,7 +1186,7 @@ def post_visible_setup(self):
         if (CONF.get('main', 'single_instance') and not self.new_instance
                 and self.open_files_server):
             t = threading.Thread(target=self.start_open_files_server)
-            t.setDaemon(True)
+            t.daemon = True
             t.start()
 
             # Connect the window to the signal emitted by the previous server
diff --git a/spyder/plugins/editor/widgets/codeeditor.py b/spyder/plugins/editor/widgets/codeeditor.py
--- a/spyder/plugins/editor/widgets/codeeditor.py
+++ b/spyder/plugins/editor/widgets/codeeditor.py
@@ -20,15 +20,18 @@
 from unicodedata import category
 import logging
 import functools
+import os
 import os.path as osp
 import re
 import sre_constants
 import sys
 import textwrap
+from pkg_resources import parse_version
 
 # Third party imports
 from diff_match_patch import diff_match_patch
 from IPython.core.inputtransformer2 import TransformerManager
+from qtpy import QT_VERSION
 from qtpy.compat import to_qvariant
 from qtpy.QtCore import (QEvent, QRegExp, Qt, QTimer, QThread, QUrl, Signal,
                          Slot)
@@ -4573,11 +4576,14 @@ def _handle_keypress_event(self, event):
         text = to_text_string(event.text())
         if text:
             # The next three lines are a workaround for a quirk of
-            # QTextEdit. See spyder-ide/spyder#12663 and
+            # QTextEdit on Linux with Qt < 5.15, MacOs and Windows.
+            # See spyder-ide/spyder#12663 and
             # https://bugreports.qt.io/browse/QTBUG-35861
-            cursor = self.textCursor()
-            cursor.setPosition(cursor.position())
-            self.setTextCursor(cursor)
+            if (parse_version(QT_VERSION) < parse_version('5.15')
+                    or os.name == 'nt' or sys.platform == 'darwin'):
+                cursor = self.textCursor()
+                cursor.setPosition(cursor.position())
+                self.setTextCursor(cursor)
             self.sig_text_was_inserted.emit()
 
     def keyPressEvent(self, event):
diff --git a/spyder/widgets/mixins.py b/spyder/widgets/mixins.py
--- a/spyder/widgets/mixins.py
+++ b/spyder/widgets/mixins.py
@@ -19,8 +19,10 @@
 import sre_constants
 import sys
 import textwrap
+from pkg_resources import parse_version
 
 # Third party imports
+from qtpy import QT_VERSION
 from qtpy.QtCore import QPoint, QRegularExpression, Qt
 from qtpy.QtGui import QCursor, QTextCursor, QTextDocument
 from qtpy.QtWidgets import QApplication
@@ -1194,11 +1196,14 @@ def remove_selected_text(self):
         """Delete selected text."""
         self.textCursor().removeSelectedText()
         # The next three lines are a workaround for a quirk of
-        # QTextEdit. See spyder-ide/spyder#12663 and
+        # QTextEdit on Linux with Qt < 5.15, MacOs and Windows.
+        # See spyder-ide/spyder#12663 and
         # https://bugreports.qt.io/browse/QTBUG-35861
-        cursor = self.textCursor()
-        cursor.setPosition(cursor.position())
-        self.setTextCursor(cursor)
+        if (parse_version(QT_VERSION) < parse_version('5.15')
+                or os.name == 'nt' or sys.platform == 'darwin'):
+            cursor = self.textCursor()
+            cursor.setPosition(cursor.position())
+            self.setTextCursor(cursor)
 
     def replace(self, text, pattern=None):
         """Replace selected text by *text*.


Test patch:
diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -844,7 +844,8 @@ def check_control(control, value):
        return value in control.toPlainText()
 
     qtbot.keyClicks(control, u'aa\t')
-    qtbot.waitUntil(lambda: check_control(control, u'aaʹbb'), timeout=2000)
+    qtbot.waitUntil(lambda: check_control(control, u'aaʹbb'),
+                    timeout=SHELL_TIMEOUT)
 
     # Get help
     control.inspect_current_object()
@@ -1091,6 +1092,7 @@ def test_runconfig_workdir(main_window, qtbot, tmpdir):
 
 
 @pytest.mark.slow
+@pytest.mark.order(1)
 @pytest.mark.no_new_console
 @flaky(max_runs=3)
 def test_dedicated_consoles(main_window, qtbot):
@@ -2169,6 +2171,9 @@ def test_plots_plugin(main_window, qtbot, tmpdir, mocker):
     (parse_version(ipy_release.version) >= parse_version('7.23.0') and
      parse_version(ipykernel.__version__) <= parse_version('5.5.3')),
     reason="Fails due to a bug in the %matplotlib magic")
+@pytest.mark.skipif(
+    sys.platform.startswith('linux'),
+    reason="Timeouts a lot on Linux")
 def test_tight_layout_option_for_inline_plot(main_window, qtbot, tmpdir):
     """
     Test that the option to set bbox_inches to 'tight' or 'None' is
@@ -2235,9 +2240,10 @@ def test_tight_layout_option_for_inline_plot(main_window, qtbot, tmpdir):
     CONF.set('ipython_console', 'pylab/inline/bbox_inches', False)
 
     # Restart the kernel and wait until it's up again
-    shell._prompt_html = None
-    client.restart_kernel()
-    qtbot.waitUntil(lambda: shell._prompt_html is not None,
+    with qtbot.waitSignal(client.sig_execution_state_changed,
+                          timeout=SHELL_TIMEOUT):
+        client.restart_kernel()
+    qtbot.waitUntil(lambda: 'In [1]:' in control.toPlainText(),
                     timeout=SHELL_TIMEOUT)
 
     # Generate the same plot inline with bbox_inches='tight' and save the
@@ -2476,6 +2482,9 @@ def test_troubleshooting_menu_item_and_url(main_window, qtbot, monkeypatch):
 @flaky(max_runs=3)
 @pytest.mark.slow
 @pytest.mark.skipif(os.name == 'nt', reason="It fails on Windows")
+@pytest.mark.skipif(
+    sys.platform == 'darwin' and running_in_ci(),
+    reason="It stalls the CI sometimes on MacOS")
 def test_help_opens_when_show_tutorial_full(main_window, qtbot):
     """
     Test fix for spyder-ide/spyder#6317.
@@ -3835,14 +3844,25 @@ def test_run_unsaved_file_multiprocessing(main_window, qtbot):
     # create new file
     main_window.editor.new()
     code_editor = main_window.editor.get_focus_widget()
-    code_editor.set_text(
-        "import multiprocessing\n"
-        "import traceback\n"
-        'if __name__ is "__main__":\n'
-        "    p = multiprocessing.Process(target=traceback.print_exc)\n"
-        "    p.start()\n"
-        "    p.join()\n"
-    )
+    if sys.platform == 'darwin':
+        # Since Python 3.8 MacOS uses by default `spawn` instead of `fork`
+        # and that causes problems.
+        # See https://stackoverflow.com/a/65666298/15954282
+        text = ("import multiprocessing\n"
+                'multiprocessing.set_start_method("fork")\n'
+                "import traceback\n"
+                'if __name__ == "__main__":\n'
+                "    p = multiprocessing.Process(target=traceback.print_exc)\n"
+                "    p.start()\n"
+                "    p.join()\n")
+    else:
+        text = ("import multiprocessing\n"
+                "import traceback\n"
+                'if __name__ == "__main__":\n'
+                "    p = multiprocessing.Process(target=traceback.print_exc)\n"
+                "    p.start()\n"
+                "    p.join()\n")
+    code_editor.set_text(text)
     # This code should run even on windows
 
     # Start running
@@ -3853,11 +3873,13 @@ def test_run_unsaved_file_multiprocessing(main_window, qtbot):
     # be broken.
     if os.name == 'nt':
         qtbot.waitUntil(
-            lambda: "Warning: multiprocessing" in shell._control.toPlainText())
+            lambda: "Warning: multiprocessing" in shell._control.toPlainText(),
+            timeout=SHELL_TIMEOUT)
     else:
         # There is no exception, so the exception is None
         qtbot.waitUntil(
-            lambda: 'None' in shell._control.toPlainText())
+            lambda: 'None' in shell._control.toPlainText(),
+            timeout=SHELL_TIMEOUT)
 
 
 @pytest.mark.slow
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
@@ -1109,36 +1109,6 @@ def test_mpl_backend_change(ipyconsole, qtbot):
     assert shell._control.toHtml().count('img src') == 1
 
 
-@flaky(max_runs=10)
-@pytest.mark.skipif(running_in_ci(), reason="Fails frequently in CI")
-@pytest.mark.skipif(os.name == 'nt',
-                    reason="Failing on Windows. In fact on Windows you can "
-                           "copy with Ctrl + C i.e a KeyboardInterrupt isn't "
-                           "triggered when doing Ctrl + C while debugging")
-def test_ctrl_c_dbg(ipyconsole, qtbot):
-    """
-    Test that Ctrl+C works while debugging
-    """
-    shell = ipyconsole.get_current_shellwidget()
-
-    # Give focus to the widget that's going to receive clicks
-    control = ipyconsole.get_widget().get_focus_widget()
-    control.setFocus()
-
-    # Enter debugging mode
-    with qtbot.waitSignal(shell.executed):
-        shell.execute('%debug print()')
-
-    # Test Ctrl+C
-    qtbot.waitUntil(lambda: 'IPdb [1]: ' in control.toPlainText())
-    control.setFocus()
-    qtbot.keyClick(control, Qt.Key_C, modifier=Qt.ControlModifier)
-    qtbot.waitUntil(
-        lambda: 'For copying text while debugging, use Ctrl+Shift+C' in
-        control.toPlainText(), timeout=2000)
-
-    assert 'For copying text while debugging, use Ctrl+Shift+C' in control.toPlainText()
-
 
 @flaky(max_runs=10)
 @pytest.mark.skipif(os.name == 'nt', reason="It doesn't work on Windows")
diff --git a/spyder/plugins/onlinehelp/tests/test_pydocgui.py b/spyder/plugins/onlinehelp/tests/test_pydocgui.py
--- a/spyder/plugins/onlinehelp/tests/test_pydocgui.py
+++ b/spyder/plugins/onlinehelp/tests/test_pydocgui.py
@@ -8,6 +8,7 @@
 Tests for pydocgui.py
 """
 # Standard library imports
+import os
 import sys
 from unittest.mock import MagicMock
 
@@ -18,6 +19,7 @@
 from flaky import flaky
 
 # Local imports
+from spyder.config.base import running_in_ci
 from spyder.plugins.onlinehelp.widgets import PydocBrowser
 
 
@@ -48,6 +50,10 @@ def pydocbrowser(qtbot):
      NumpyVersion(np.__version__) < NumpyVersion('1.21.0')),
     reason="Fails on Mac and older versions of Numpy"
 )
+@pytest.mark.skipif(
+    sys.platform.startswith('linux') or os.name == 'nt' and running_in_ci(),
+    reason="Stalls CI frequenly on Linux and Windows"
+)
 def test_get_pydoc(pydocbrowser, qtbot, lib):
     """
     Go to the documentation by url.
diff --git a/spyder/plugins/pylint/tests/test_pylint.py b/spyder/plugins/pylint/tests/test_pylint.py
--- a/spyder/plugins/pylint/tests/test_pylint.py
+++ b/spyder/plugins/pylint/tests/test_pylint.py
@@ -13,6 +13,7 @@
 from unittest.mock import Mock, MagicMock
 
 # Third party imports
+from flaky import flaky
 import pytest
 from qtpy.QtCore import Signal
 from qtpy.QtWidgets import QApplication, QMainWindow
@@ -216,6 +217,7 @@ def test_pylint_widget_noproject(pylint_plugin, pylint_test_script, mocker,
     assert pylint_data[1] is not None
 
 
+@flaky(max_runs=3)
 def test_pylint_widget_pylintrc(
         pylint_plugin, pylint_test_script, pylintrc_files, mocker, qtbot):
     """Test that entire pylint widget gets results depending on pylintrc."""