diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py index 08f9e1f749a..de62590f99a 100644 --- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py +++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py @@ -13,6 +13,7 @@ # Standard library imports import os import os.path as osp +import re import shutil import sys from textwrap import dedent @@ -40,8 +41,6 @@ from spyder.plugins.ipythonconsole.tests.conftest import ( get_conda_test_env, get_console_background_color, get_console_font_color, NEW_DIR, SHELL_TIMEOUT, TEMP_DIRECTORY) -from spyder.plugins.ipythonconsole.utils.kernel_handler import ( - KernelConnectionState) from spyder.plugins.ipythonconsole.widgets import ShellWidget from spyder.utils.conda import get_list_conda_envs @@ -1355,30 +1354,57 @@ def test_conda_env_activation(ipyconsole, qtbot): @flaky(max_runs=3) +@pytest.mark.parametrize("external_interpreter", [True, False]) @pytest.mark.skipif(os.name == 'nt', reason="no SIGTERM on Windows") -def test_kernel_kill(ipyconsole, qtbot): +def test_kernel_kill(ipyconsole, qtbot, external_interpreter): """ Test that the kernel correctly restarts after a kill. """ + if external_interpreter: + if running_in_ci(): + ipyconsole.set_conf('default', False, section='main_interpreter') + pyexec = get_conda_test_env()[1] + ipyconsole.set_conf( + 'executable', pyexec, section='main_interpreter' + ) + ipyconsole.create_new_client() + else: + # We can't check this locally + return + shell = ipyconsole.get_current_shellwidget() + # Wait for the restarter to start qtbot.wait(3000) crash_string = 'import os, signal; os.kill(os.getpid(), signal.SIGTERM)' + # Check only one comm is open old_open_comms = list(shell.kernel_handler.kernel_comm._comms.keys()) assert len(old_open_comms) == 1 with qtbot.waitSignal(shell.sig_prompt_ready, timeout=30000): shell.execute(crash_string) - assert crash_string in shell._control.toPlainText() - assert "Restarting kernel..." in shell._control.toPlainText() + + console_text = shell._control.toPlainText() + assert crash_string in console_text + assert "The kernel died, restarting..." in console_text + + # Check we don't show error generated by `conda run` + assert "conda.cli.main_run" not in console_text + + # Check IPython version is shown as expected + assert list(re.finditer(r"IPython \d+\.", console_text)) + # Check a new comm replaced the old one new_open_comms = list(shell.kernel_handler.kernel_comm._comms.keys()) assert len(new_open_comms) == 1 assert old_open_comms[0] != new_open_comms[0] + # Wait until the comm replies qtbot.waitUntil( lambda: shell.kernel_handler.kernel_comm._comms[new_open_comms[0]][ - 'status'] == 'ready') + 'status'] == 'ready' + ) + assert shell.kernel_handler.kernel_comm._comms[new_open_comms[0]][ 'status'] == 'ready' diff --git a/spyder/plugins/ipythonconsole/utils/kernelspec.py b/spyder/plugins/ipythonconsole/utils/kernelspec.py index 417025621b0..9d14fd3f4fa 100644 --- a/spyder/plugins/ipythonconsole/utils/kernelspec.py +++ b/spyder/plugins/ipythonconsole/utils/kernelspec.py @@ -25,8 +25,7 @@ from spyder.plugins.ipythonconsole import ( SPYDER_KERNELS_CONDA, SPYDER_KERNELS_PIP, SPYDER_KERNELS_VERSION, SpyderKernelError) -from spyder.utils.conda import (add_quotes, get_conda_env_path, is_conda_env, - find_conda) +from spyder.utils.conda import get_conda_env_path, is_conda_env, find_conda from spyder.utils.environ import clean_env, get_user_environment_variables from spyder.utils.misc import get_python_executable from spyder.utils.programs import ( diff --git a/spyder/plugins/ipythonconsole/widgets/client.py b/spyder/plugins/ipythonconsole/widgets/client.py index 05dfd184bc3..31bdf35c794 100644 --- a/spyder/plugins/ipythonconsole/widgets/client.py +++ b/spyder/plugins/ipythonconsole/widgets/client.py @@ -412,10 +412,6 @@ def connect_shellwidget_signals(self): self.shellwidget.executed.connect( self.sig_execution_state_changed) - # To show kernel restarted/died messages - self.shellwidget.sig_kernel_restarted_message.connect( - self.kernel_restarted_message) - # To correctly change Matplotlib backend interactively self.shellwidget.executing.connect( self.shellwidget.change_mpl_backend) @@ -509,7 +505,10 @@ def is_benign_error(self, error): "Note: Debugging will proceed. " "Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.", # Argument not expected error. See spyder-ide/spyder#19298 - "The following argument was not expected" + "The following argument was not expected", + # Avoid showing error for kernel restarts after kernel dies when + # using an external interpreter + "conda.cli.main_run" ] return any([err in error for err in benign_errors]) @@ -614,18 +613,11 @@ def replace_kernel(self, kernel_handler, shutdown_kernel): # Reset shellwidget and print restart message self.shellwidget.reset(clear=True) - self.shellwidget.print_restart_message() + self.shellwidget._kernel_restarted_message(died=False) def print_fault(self, fault): """Print fault text.""" - self.shellwidget._append_plain_text( - '\n' + fault, before_prompt=True) - - @Slot(str) - def kernel_restarted_message(self, msg): - """Show kernel restarted/died messages.""" - self.shellwidget._append_html("
%s

" % msg, - before_prompt=False) + self.shellwidget._append_plain_text('\n' + fault, before_prompt=True) @Slot() def enter_array_inline(self): diff --git a/spyder/plugins/ipythonconsole/widgets/main_widget.py b/spyder/plugins/ipythonconsole/widgets/main_widget.py index f84cd091036..6e1bc063174 100644 --- a/spyder/plugins/ipythonconsole/widgets/main_widget.py +++ b/spyder/plugins/ipythonconsole/widgets/main_widget.py @@ -13,6 +13,7 @@ import os import os.path as osp import shlex +import subprocess import sys # Third-party imports @@ -1368,16 +1369,17 @@ def interpreter_versions(self, path_to_custom_interpreter=None): ipython_version=release.version ) else: - import subprocess versions = {} pyexec = self.get_conf('executable', section='main_interpreter') if path_to_custom_interpreter: pyexec = path_to_custom_interpreter - py_cmd = u'%s -c "import sys; print(sys.version)"' % pyexec + + py_cmd = '%s -c "import sys; print(sys.version)"' % pyexec ipy_cmd = ( - u'%s -c "import IPython.core.release as r; print(r.version)"' + '%s -c "import IPython.core.release as r; print(r.version)"' % pyexec ) + for cmd in [py_cmd, ipy_cmd]: try: # Use clean environment @@ -1385,7 +1387,9 @@ def interpreter_versions(self, path_to_custom_interpreter=None): output, _err = proc.communicate() except subprocess.CalledProcessError: output = '' + output = output.decode().split('\n')[0].strip() + if 'IPython' in cmd: versions['ipython_version'] = output else: diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py b/spyder/plugins/ipythonconsole/widgets/shell.py index ae1d0201eea..e7179fd76e2 100644 --- a/spyder/plugins/ipythonconsole/widgets/shell.py +++ b/spyder/plugins/ipythonconsole/widgets/shell.py @@ -99,7 +99,6 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget, # For ShellWidget sig_focus_changed = Signal() sig_new_client = Signal() - sig_kernel_restarted_message = Signal(str) # Kernel died and restarted (not user requested) sig_prompt_ready = Signal() @@ -308,12 +307,6 @@ def reset_kernel_state(self): self._pdb_recursion_level = 0 self._reading = False - def print_restart_message(self): - """Print restart message.""" - self._append_html( - _("
Restarting kernel...
"), before_prompt=True) - self.insert_horizontal_ruler() - def call_kernel(self, interrupt=False, blocking=False, callback=None, timeout=None, display_error=False): """ @@ -731,8 +724,15 @@ def long_banner(self): 'Python %s\n' % py_ver, 'Type "copyright", "credits" or "license" for more information.', '\n\n', - 'IPython %s -- An enhanced Interactive Python.\n' % ipy_ver ] + + if ipy_ver: + banner_parts.append( + 'IPython %s -- An enhanced Interactive Python.\n' % ipy_ver + ) + else: + banner_parts.append('IPython -- An enhanced Interactive Python.\n') + banner = ''.join(banner_parts) # Pylab additions @@ -1215,7 +1215,10 @@ def _banner_default(self): return self.short_banner() def _kernel_restarted_message(self, died=True): - msg = _("Kernel died, restarting") if died else _("Kernel restarting") + msg = ( + _("The kernel died, restarting...") if died + else _("Restarting kernel...") + ) if died and self.kernel_manager is None: # The kernel might never restart, show position of fault file @@ -1224,15 +1227,13 @@ def _kernel_restarted_message(self, died=True): + self.kernel_handler.fault_filename() ) - self.sig_kernel_restarted_message.emit(msg) + self._append_html(f"
{msg}
", before_prompt=False) + self.insert_horizontal_ruler() def _handle_kernel_restarted(self, *args, **kwargs): """The kernel restarted.""" super()._handle_kernel_restarted(*args, **kwargs) - # Print restart message - self.print_restart_message() - # Reset Pdb state self.reset_kernel_state()