Repository: spyder-ide/spyder

Issue:
Tests seems to be passing but fail
Tests seems to be passing but fail. If you go to the detail of the tests, failing tests come back with a little green check somehow.

For example : https://github.com/spyder-ide/spyder/actions/runs/6132598053/job/16643637158?pr=21320

Under "run test" I get:
```
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
[90](https://github.com/spyder-ide/spyder/actions/runs/6132598053/job/16643637158?pr=21320#step:15:91)
======================== 8 warnings, 1 error in 19.75s =========================
```

But the test has a little green check???


Discussion:

Resolving patch:
diff --git a/conftest.py b/conftest.py
--- a/conftest.py
+++ b/conftest.py
@@ -84,11 +84,11 @@ def pytest_collection_modifyitems(config, items):
         ]
 
         if os.name == 'nt':
-            percentage = 0.6
+            percentage = 0.5
         elif sys.platform == 'darwin':
-            percentage = 0.6
-        else:
             percentage = 0.5
+        else:
+            percentage = 0.4
 
         for i, item in enumerate(ipyconsole_items):
             if i < len(ipyconsole_items) * percentage:
diff --git a/spyder/plugins/switcher/widgets/switcher.py b/spyder/plugins/switcher/widgets/switcher.py
--- a/spyder/plugins/switcher/widgets/switcher.py
+++ b/spyder/plugins/switcher/widgets/switcher.py
@@ -296,13 +296,9 @@ def add_separator(self):
 
     def setup(self):
         """Setup list widget content based on filtering."""
-        mode = self._mode_on
-        if mode:
-            search_text = self.search_text()[len(mode):]
-        else:
-            search_text = self.search_text()
+        search_text = self.search_text_without_mode()
 
-        # Check exited mode
+        # Build default view
         if self.search_text() == '':
             self._mode_on = ''
             self.clear()
@@ -321,13 +317,6 @@ def setup(self):
 
             return
 
-        # Check entered mode
-        for key in self._modes:
-            if self.search_text().startswith(key) and not mode:
-                self._mode_on = key
-                self.sig_mode_selected.emit(key)
-                return
-
         # Filter by text
         titles = []
         for row in range(self.model.rowCount()):
@@ -366,13 +355,8 @@ def setup(self):
 
     def setup_sections(self):
         """Setup which sections appear on the item list."""
-        mode = self._mode_on
         sections = []
-
-        if mode:
-            search_text = self.search_text()[len(mode):]
-        else:
-            search_text = self.search_text()
+        search_text = self.search_text_without_mode()
 
         for row in range(self.model.rowCount()):
             item_row = row
@@ -403,7 +387,7 @@ def setup_sections(self):
                     item.set_section_visible(visible)
             else:
                 # We need to remove this when a mode has several sections
-                if not mode:
+                if not self._mode_on:
                     item.set_section_visible(True)
 
     def remove_section(self, section):
@@ -468,7 +452,7 @@ def enter(self, itemClicked=None):
         if item:
             mode = self._mode_on
             self.sig_item_selected.emit(
-                item, mode, self.search_text()[len(mode):]
+                item, mode, self.search_text_without_mode()
             )
 
     def accept(self):
@@ -491,6 +475,16 @@ def search_text(self):
         """Get the normalized (lowecase) content of the search text."""
         return to_text_string(self.edit.text()).lower()
 
+    def search_text_without_mode(self):
+        """Get search text without mode."""
+        mode = self._mode_on
+        if mode:
+            search_text = self.search_text()[len(mode):]
+        else:
+            search_text = self.search_text()
+
+        return search_text
+
     def set_search_text(self, string):
         """Set the content of the search text."""
         self.edit.setText(string)
@@ -498,8 +492,21 @@ def set_search_text(self, string):
     def _on_search_text_changed(self):
         """Actions to take when the search text has changed."""
         if self.search_text() != "":
-            search_text = clean_string(self.search_text())
-            self.sig_search_text_available.emit(search_text)
+            search_text = self.search_text_without_mode()
+
+            # Inform if mode has changed
+            for key in self._modes:
+                if search_text.startswith(key) and not self._mode_on:
+                    self._mode_on = key
+                    self.sig_mode_selected.emit(key)
+                    break
+
+            # Emit this signal only for the files mode for now. We'll see if
+            # it's necessary for other modes later.
+            if self._mode_on == "":
+                self.sig_search_text_available.emit(clean_string(search_text))
+            else:
+                self.setup()
         else:
             self.setup()
 
diff --git a/spyder/utils/environ.py b/spyder/utils/environ.py
--- a/spyder/utils/environ.py
+++ b/spyder/utils/environ.py
@@ -104,7 +104,9 @@ def get_user_environment_variables():
             proc = run_shell_command(user_env_script, env={}, text=True)
 
             # Use timeout to fix spyder-ide/spyder#21172
-            stdout, stderr = proc.communicate(timeout=0.5)
+            stdout, stderr = proc.communicate(
+                timeout=3 if running_in_ci() else 0.5
+            )
 
             if stderr:
                 logger.info(stderr.strip())


Test patch:
diff --git a/spyder/app/tests/conftest.py b/spyder/app/tests/conftest.py
--- a/spyder/app/tests/conftest.py
+++ b/spyder/app/tests/conftest.py
@@ -462,12 +462,13 @@ def main_window(request, tmpdir, qtbot):
                 CONF.reset_to_defaults(notification=False)
             else:
                 try:
-                    # Close everything we can think of
-                    window.switcher.close()
+                    # Close or hide everything we can think of
+                    window.switcher.hide()
 
                     # Close editor related elements
                     window.editor.close_all_files()
-                    # force close all files
+
+                    # Force close all files
                     while window.editor.editorstacks[0].close_file(force=True):
                         pass
                     for editorwindow in window.editor.editorwindows:
@@ -498,7 +499,6 @@ def main_window(request, tmpdir, qtbot):
                     (window.ipyconsole.get_widget()
                         .create_new_client_if_empty) = False
                     window.ipyconsole.restart()
-
                 except Exception:
                     main_window.window = None
                     window.close()
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
@@ -2580,6 +2580,7 @@ def example_def_2():
 
 
 @flaky(max_runs=3)
+@pytest.mark.skipif(running_in_ci(), reason="Can't run on CI")
 def test_switcher_projects_integration(main_window, pytestconfig, qtbot,
                                        tmp_path):
     """Test integration between the Switcher and Projects plugins."""
diff --git a/spyder/plugins/switcher/tests/test_switcher.py b/spyder/plugins/switcher/tests/test_switcher.py
--- a/spyder/plugins/switcher/tests/test_switcher.py
+++ b/spyder/plugins/switcher/tests/test_switcher.py
@@ -14,6 +14,8 @@
 from spyder.config.base import _
 
 
+# --- Fixtures
+# -----------------------------------------------------------------------------
 @pytest.fixture
 def dlg_switcher(qtbot):
     """Set up switcher widget."""
@@ -45,12 +47,19 @@ def item_selected(item, mode, search_text):
 
     dlg_switcher.sig_mode_selected.connect(handle_modes)
     dlg_switcher.sig_item_selected.connect(item_selected)
+    dlg_switcher.sig_search_text_available.connect(
+        lambda text: dlg_switcher.setup()
+    )
 
     qtbot.addWidget(dlg_switcher)
+    dlg_switcher.show()
     create_vcs_example_switcher(dlg_switcher)
+
     return dlg_switcher
 
 
+# --- Tests
+# -----------------------------------------------------------------------------
 def test_switcher(dlg_switcher, qtbot):
     """Test that shortcut summary is visible and is not empty"""
     # Test that the dialog exists and is shown
@@ -115,9 +124,9 @@ def test_switcher_filter_unicode(dlg_switcher, qtbot):
     qtbot.wait(1000)
     assert dlg_switcher.count() == 2
 
-# Helper functions for tests
-
 
+# --- Helper functions for tests
+# -----------------------------------------------------------------------------
 def create_vcs_example_switcher(sw):
     """Add example data for vcs."""
     from spyder.utils.icon_manager import ima
diff --git a/spyder/tests/test_dependencies_in_sync.py b/spyder/tests/test_dependencies_in_sync.py
--- a/spyder/tests/test_dependencies_in_sync.py
+++ b/spyder/tests/test_dependencies_in_sync.py
@@ -204,7 +204,7 @@ def test_dependencies_for_spyder_dialog_in_sync():
     full_reqs.update(linux_reqs)
 
     # These packages are not declared in our dependencies dialog
-    for dep in ['pyqt', 'pyqtwebengine', 'python.app']:
+    for dep in ['pyqt', 'pyqtwebengine', 'python.app', 'fzf', 'fcitx-qt5']:
         full_reqs.pop(dep)
 
     assert spyder_deps == full_reqs
@@ -226,8 +226,9 @@ def test_dependencies_for_spyder_setup_install_requires_in_sync():
     full_reqs.update(mac_reqs)
     full_reqs.update(linux_reqs)
 
-    # We don't declare python.app as a dependency in other places
-    full_reqs.pop('python.app')
+    # We can't declare these as dependencies in setup.py
+    for dep in ['python.app', 'fzf', 'fcitx-qt5']:
+        full_reqs.pop(dep)
 
     assert spyder_setup == full_reqs