Hello community,

here is the log from the commit of package spyder for openSUSE:Factory checked 
in at 2020-05-02 22:18:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/spyder (Old)
 and      /work/SRC/openSUSE:Factory/.spyder.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "spyder"

Sat May  2 22:18:39 2020 rev:4 rq:799656 version:4.1.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/spyder/spyder.changes    2020-04-21 
13:13:12.501116370 +0200
+++ /work/SRC/openSUSE:Factory/.spyder.new.2738/spyder.changes  2020-05-02 
22:18:42.152707639 +0200
@@ -1,0 +2,9 @@
+Thu Apr 30 18:23:14 UTC 2020 - Benjamin Greiner <c...@bnavigator.de>
+
+- Close leaks in tests for mainwindow and ipythonconsole
+  gh#spyder-ide/spyder#12534
+  spyder-pr12534-closeleaks.patch
+- Now also run the slow tests except for a few.
+- python-opengl is not a requirement
+
+-------------------------------------------------------------------

New:
----
  spyder-pr12534-closeleaks.patch

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ spyder.spec ++++++
--- /var/tmp/diff_new_pack.mKiGFX/_old  2020-05-02 22:18:43.068709557 +0200
+++ /var/tmp/diff_new_pack.mKiGFX/_new  2020-05-02 22:18:43.072709566 +0200
@@ -32,6 +32,7 @@
 Source:         
https://github.com/spyder-ide/spyder/archive/v%{version}.tar.gz#/spyder-%{version}.tar.gz
 Source1:        spyder-rpmlintrc
 Patch0:         spyder-pr11704-fixpytestargs.patch
+Patch1:         spyder-pr12534-closeleaks.patch
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 BuildRequires:  python3-Pygments >= 2.0
@@ -77,7 +78,6 @@
 Requires:       python3-keyring
 Requires:       python3-nbconvert >= 4.0
 Requires:       python3-numpydoc >= 0.6.0
-Requires:       python3-opengl
 Requires:       python3-parso >= 0.5.2
 Requires:       python3-pexpect >= 0.4.4
 Requires:       python3-pickleshare
@@ -131,7 +131,6 @@
 BuildRequires:  python3-matplotlib >= 2.0.0
 BuildRequires:  python3-matplotlib-qt5
 BuildRequires:  python3-matplotlib-tk
-BuildRequires:  python3-opengl
 BuildRequires:  python3-pandas >= 0.13.1
 BuildRequires:  python3-pyaml
 BuildRequires:  python3-pydocstyle
@@ -222,6 +221,7 @@
 %prep
 %setup -q -n spyder-%{version}
 %patch0 -p1
+%patch1 -p1
 # Fix wrong-file-end-of-line-encoding RPMLint warning
 sed -i 's/\r$//' spyder/app/restart.py
 sed -i 's/\r$//' LICENSE.txt CHANGELOG.md
@@ -270,11 +270,12 @@
 export PYTHONDONTWRITEBYTECODE=1
 
 # upstream splits the tests into slow and fast ones
-# we run the ipythonconsole tests separately because they pose issues with 
asynchonously opened sockets
-skiptests="test_ipythonconsole"
-skipslowtests="test_ipythonconsole"
+# add all tests to skip into $skiptests or $skipslowtests separated by 
whitespace
+# tests to run separately because of leakages go into separateslowtests
+skiptests=""
+skipslowtests=""
+separateslowtests=""
 
-# add all tests to skip into $skiptests or $skipttestsslow separated by 
whitespace
 # the shortcut is not sent by the editorbot
 skiptests+=" test_comment"
 # the click/tab press is not sent by the bot
@@ -288,43 +289,28 @@
 # tests not suitable for CIs or OBS as evident from the last assert which 
fails here
 skiptests+=" test_connection_dialog_remembers_input_with_ssh_passphrase"
 skiptests+=" test_connection_dialog_remembers_input_with_password"
+# running into timeouts
+skiptests+=" test_dbg_input"
+skiptests+=" test_mpl_backend_change"
+
 # completes to math.hypot(cooordinates) instead of expected 
math.hypot(*coordinates)
 skipslowtests+=" test_completions"
-
-# the linter does not report warnings with the D and E error codes
-skipslowtests+=" test_ignore_warnings test_move_warnings test_get_warnings 
test_update_warnings"
-# opens too many files ?
-# likely a test before....
-skipslowtests+=" test_open_notebooks_from_project_explorer"
-
-# the following tests rely on IPythonConsole behaving well on OBS (which does 
not..)
-skipipythontests=""
-skipipythonslowtests=""
-# xvfb does not like the repeating restart of the kernels in those tests
-skipipythontests+=" test_load_kernel_file"
-# running into timeouts
-skipipythontests+=" test_mpl_backend_change"
-skipipythontests+=" test_calltip"
-skipipythontests+=" test_dbg_input"
-# pdb seems to be the root cause of a lot of test problems
-skipipythontests+=" test_pdb"
-# timeout, hard abort, permission errors
-skipipythontests+=" test_stderr_"
-skipipythontests+=" test_kernel_kill test_conda_env_activation"
-# segfault (?)
-skipipythonslowtests+=" test_runfile_from_project_explorer"
+# Would require network connections
+skipslowtests+=" test_update"
+# runs into timeout on obs
+skipslowtests+=" test_hide_widget_completion"
+# some mainwindow tests leak open file sockets and cause python to terminate
+# the whole testrun at some later point.
+# gh#/spyder-ide/spyder#12534
+separateslowtests+=" test_runcell test_varexp test_edidorstack"
 
 %{python_expand PYTHONPATH=%{buildroot}%{$python_sitelib}
-for s in tests slowtests ipythontests ipythonslowtests; do
-    skips=skip$s
-    declare skip${s}_p="$($python -c "import sys; print(' or 
'.join('${!skips}'.split()))")"
+for s in skiptests skipslowtests separateslowtests; do
+    declare ${s}_p="$($python -c "import sys; print(' or 
'.join('${!s}'.split()))")"
 done
-$python runtests.py -k "not ($skiptests_p)"
-# the slow tests still segfault unreproducibly at different tests. Something 
does not clean up
-# $python runtests.py --run-slow -k "not ($skipslowtests_p)"
-$python runtests.py -k "test_ipythonconsole and not ($skipipythontests_p)"
-# the slow tests still fail unreproducibly at different tests. Something does 
not clean up
-# $python runtests.py --run-slow -k "test_ipythonconsole and not 
($skipipythonslowtests_p)"
+$python runtests.py -k "not ($skiptests_p)" --timeout 1800
+$python runtests.py --run-slow -k "not ($skipslowtests_p or 
$separateslowtests_p)" --timeout 1800
+$python runtests.py --run-slow -k "$separateslowtests_p" --timeout 1800
 }
 %endif
 

++++++ spyder-pr12534-closeleaks.patch ++++++
diff --git a/spyder/app/tests/test_mainwindow.py 
b/spyder/app/tests/test_mainwindow.py
index b8436ff0a..ef640cd6a 100644
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -832,8 +832,12 @@ def test_connection_to_external_kernel(main_window, qtbot):
     assert nsb.editor.source_model.rowCount() == 1
 
     # Shutdown the kernels
+    spykm.stop_restarter()
+    km.stop_restarter()
     spykm.shutdown_kernel(now=True)
     km.shutdown_kernel(now=True)
+    spykc.stop_channels()
+    kc.stop_channels()
 
 
 @pytest.mark.slow
diff --git a/spyder/plugins/ipythonconsole/plugin.py 
b/spyder/plugins/ipythonconsole/plugin.py
index 32c9fdb1d..fdcbe7338 100644
--- a/spyder/plugins/ipythonconsole/plugin.py
+++ b/spyder/plugins/ipythonconsole/plugin.py
@@ -953,6 +953,9 @@ class IPythonConsole(SpyderPluginWidget):
         if not self.tabwidget.count():
             return
         if client is not None:
+            if client not in self.clients:
+                # Client already closed
+                return
             index = self.tabwidget.indexOf(client)
             # if index is not found in tabwidget it's because this client was
             # already closed and the call was performed by the exit callback
@@ -998,7 +1001,7 @@ class IPythonConsole(SpyderPluginWidget):
 
         # if there aren't related clients we can remove stderr_file
         related_clients = self.get_related_clients(client)
-        if len(related_clients) == 0 and osp.exists(client.stderr_file):
+        if len(related_clients) == 0:
             client.remove_stderr_file()
 
         client.dialog_manager.close_all()
diff --git a/spyder/plugins/ipythonconsole/widgets/client.py 
b/spyder/plugins/ipythonconsole/widgets/client.py
index 918300d91..8cad08bfc 100644
--- a/spyder/plugins/ipythonconsole/widgets/client.py
+++ b/spyder/plugins/ipythonconsole/widgets/client.py
@@ -177,6 +177,12 @@ class ClientWidget(QWidget, SaveHistoryMixin):
         # Show timer
         self.update_time_label_visibility()
 
+    def __del__(self):
+        """Close threads to avoid segfault"""
+        if (self.restart_thread is not None
+                and self.restart_thread.isRunning()):
+            self.restart_thread.wait()
+
     #------ Public API --------------------------------------------------------
     @property
     def kernel_id(self):
@@ -501,24 +507,13 @@ class ClientWidget(QWidget, SaveHistoryMixin):
     def shutdown(self):
         """Shutdown kernel"""
         if self.get_kernel() is not None and not self.slave:
-            self.shellwidget.spyder_kernel_comm.close()
-            self.shellwidget.spyder_kernel_comm.shutdown_comm_channel()
-            self.shellwidget._pdb_history_file.save_thread.stop()
-            self.shellwidget.kernel_manager.stop_restarter()
-        self.shutdown_thread = QThread()
-        self.shutdown_thread.run = self.finalize_shutdown
-        self.shutdown_thread.finished.connect(self.stop_kernel_channels)
-        self.shutdown_thread.start()
-
-    def finalize_shutdown(self):
-        """Finalise the shutdown."""
-        if self.get_kernel() is not None and not self.slave:
-            self.shellwidget.kernel_manager.shutdown_kernel()
+            self.shellwidget.shutdown()
 
-    def stop_kernel_channels(self):
-        """Stop kernel channels."""
-        if self.shellwidget.kernel_client is not None:
-            self.shellwidget.kernel_client.stop_channels()
+    def close(self):
+        """Close client"""
+        self.shellwidget.will_close(
+            self.get_kernel() is None or self.slave)
+        super(ClientWidget, self).close()
 
     def interrupt_kernel(self):
         """Interrupt the associanted Spyder kernel if it's running"""
diff --git a/spyder/plugins/ipythonconsole/widgets/debugging.py 
b/spyder/plugins/ipythonconsole/widgets/debugging.py
index 76efc410d..088cbb814 100644
--- a/spyder/plugins/ipythonconsole/widgets/debugging.py
+++ b/spyder/plugins/ipythonconsole/widgets/debugging.py
@@ -70,6 +70,21 @@ class DebuggingWidget(RichJupyterWidget):
         self._tmp_reading = False
         self._pdb_frame_loc = (None, None)
 
+    def will_close(self, externally_managed):
+        """
+        Close communication channels with the kernel if shutdown was not
+        called. If the kernel is not externally managed, shutdown the kernel
+        as well.
+        """
+        try:
+            self._pdb_history_file.save_thread.stop()
+        except AttributeError:
+            pass
+        try:
+            self._pdb_history_file.db.close()
+        except AttributeError:
+            pass
+
     def handle_debug_state(self, in_debug_loop):
         """Update the debug state."""
         self._pdb_in_loop = in_debug_loop
diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py 
b/spyder/plugins/ipythonconsole/widgets/shell.py
index dc0eee590..20feecd16 100644
--- a/spyder/plugins/ipythonconsole/widgets/shell.py
+++ b/spyder/plugins/ipythonconsole/widgets/shell.py
@@ -15,7 +15,7 @@ import uuid
 from textwrap import dedent
 
 # Third party imports
-from qtpy.QtCore import Signal
+from qtpy.QtCore import Signal, QThread
 from qtpy.QtWidgets import QMessageBox
 
 # Local imports
@@ -92,8 +92,10 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, 
DebuggingWidget,
         # set in the qtconsole constructor. See spyder-ide/spyder#4806.
         self.set_bracket_matcher_color_scheme(self.syntax_style)
 
+        self.shutdown_called = False
         self.kernel_manager = None
         self.kernel_client = None
+        self.shutdown_thread = None
         handlers = {
             'pdb_state': self.set_pdb_state,
             'pdb_continue': self.pdb_continue,
@@ -109,6 +111,46 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, 
DebuggingWidget,
             self.spyder_kernel_comm.register_call_handler(
                 request_id, handlers[request_id])
 
+    def __del__(self):
+        """Avoid destroying thread"""
+        if (self.shutdown_thread is not None
+                and self.shutdown_thread.isRunning()):
+            self.shutdown_thread.wait()
+
+    def shutdown(self):
+        """Shutdown kernel"""
+        self.shutdown_called = True
+        self.spyder_kernel_comm.close()
+        self.spyder_kernel_comm.shutdown_comm_channel()
+        self.kernel_manager.stop_restarter()
+
+        self.shutdown_thread = QThread()
+        self.shutdown_thread.run = self.kernel_manager.shutdown_kernel
+        if self.kernel_client is not None:
+            self.shutdown_thread.finished.connect(
+                self.kernel_client.stop_channels)
+        self.shutdown_thread.start()
+
+    def will_close(self, externally_managed):
+        """
+        Close communication channels with the kernel if shutdown was not
+        called. If the kernel is not externally managed, shutdown the kernel
+        as well.
+        """
+        if not self.shutdown_called and not externally_managed:
+            # Make sure the channels are stopped
+            self.spyder_kernel_comm.close()
+            self.spyder_kernel_comm.shutdown_comm_channel()
+            self.kernel_manager.stop_restarter()
+            self.kernel_manager.shutdown_kernel(now=True)
+            if self.kernel_client is not None:
+                self.kernel_client.stop_channels()
+        if externally_managed:
+            self.spyder_kernel_comm.close()
+            if self.kernel_client is not None:
+                self.kernel_client.stop_channels()
+        super(ShellWidget, self).will_close(externally_managed)
+
     def call_kernel(self, interrupt=False, blocking=False, callback=None,
                     timeout=None):
         """
diff --git a/spyder/plugins/onlinehelp/plugin.py 
b/spyder/plugins/onlinehelp/plugin.py
index e1ed28015..214f6a9d1 100644
--- a/spyder/plugins/onlinehelp/plugin.py
+++ b/spyder/plugins/onlinehelp/plugin.py
@@ -51,7 +51,7 @@ class OnlineHelp(SpyderPluginWidget):
         else:
             history = []
         return history
-    
+
     def save_history(self):
         """Save history to a text file in user home directory"""
         open(self.LOG_PATH, 'w').write("\n".join( \
@@ -62,7 +62,7 @@ class OnlineHelp(SpyderPluginWidget):
     def toggle_view(self, checked):
         """Toggle view action."""
         if checked:
-            if not self.pydocbrowser.is_server_running():
+            if self.pydocbrowser.server is None:
                 self.pydocbrowser.initialize()
             self.dockwidget.show()
             self.dockwidget.raise_()
@@ -72,7 +72,7 @@ class OnlineHelp(SpyderPluginWidget):
     def get_plugin_title(self):
         """Return widget title"""
         return _('Online help')
-    
+
     def get_focus_widget(self):
         """
         Return the widget to give focus to when
@@ -80,12 +80,13 @@ class OnlineHelp(SpyderPluginWidget):
         """
         self.pydocbrowser.url_combo.lineEdit().selectAll()
         return self.pydocbrowser.url_combo
-        
+
     def closing_plugin(self, cancelable=False):
         """Perform actions before parent main window is closed"""
         self.save_history()
         self.set_option('zoom_factor',
                         self.pydocbrowser.webview.get_zoom_factor())
+        self.pydocbrowser.quit_server()
         return True
 
     def on_first_registration(self):
diff --git a/spyder/plugins/onlinehelp/widgets.py 
b/spyder/plugins/onlinehelp/widgets.py
index 59ce0d18a..ee648c81b 100644
--- a/spyder/plugins/onlinehelp/widgets.py
+++ b/spyder/plugins/onlinehelp/widgets.py
@@ -53,6 +53,7 @@ class PydocServer(QThread):
         self.port = port
         self.server = None
         self.complete = False
+        self.closed = False
 
     def run(self):
         if PY3:
@@ -66,15 +67,34 @@ class PydocServer(QThread):
 
     def callback(self, server):
         self.server = server
-        self.server_started.emit()
+        if self.closed:
+            self.quit_server()
+        else:
+            self.server_started.emit()
 
     def completer(self):
         self.complete = True
 
+    def is_running(self):
+        """Check if the server is running"""
+        if PY3:
+            if self.isRunning():
+                # Startup
+                return True
+            if self.server is None:
+                return False
+            return self.server.serving
+        else:
+            # Py 2
+            return self.isRunning()
+
     def quit_server(self):
+        self.closed = True
+        if self.server is None:
+            return
         if PY3:
             # Python 3
-            if self.isRunning() and self.server.serving:
+            if self.server.serving:
                 self.server.stop()
         else:
             # Python 2
@@ -106,31 +126,33 @@ class PydocBrowser(WebBrowser):
         self.go_home()
         QApplication.restoreOverrideCursor()
 
-    def is_server_running(self):
-        """Return True if pydoc server is already running"""
-        return self.server is not None
-
     def closeEvent(self, event):
-        if self.is_server_running():
-            self.server.quit_server()
-#        while not self.server.complete: #XXX Is it really necessary?
-#            pass
+        """Handle close event"""
+        self.quit_server()
         event.accept()
 
-    #------ Public API -----------------------------------------------------
+    # ------ Public API -------------------------------------------------------
     def start_server(self):
         """Start pydoc server"""
         if self.server is None:
             self.port = select_port(default_port=self.DEFAULT_PORT)
             self.set_home_url('http://127.0.0.1:%d/' % self.port)
-        elif self.server.isRunning():
-            self.server.server_started.disconnect(self.initialize_continued)
-            self.server.quit()
+        elif self.server.is_running():
+            self.quit_server()
         self.server = PydocServer(port=self.port)
         self.server.server_started.connect(self.initialize_continued)
         self.server.start()
 
-    #------ WebBrowser API 
-----------------------------------------------------
+    def quit_server(self):
+        """Quit the server"""
+        if self.server is None:
+            return
+        if self.server.is_running():
+            self.server.server_started.disconnect(self.initialize_continued)
+            self.server.quit_server()
+            self.server.quit()
+
+    # ------ WebBrowser API ---------------------------------------------------
     def get_label(self):
         """Return address label text"""
         return _("Module or package:")

Reply via email to