Skip to content

Commit

Permalink
Merge pull request #21788 from ccordoba12/conda-run-no-capture
Browse files Browse the repository at this point in the history
PR: Avoid `conda run` capturing output in env activation (IPython console)
  • Loading branch information
ccordoba12 authored Feb 11, 2024
2 parents 89475f7 + 86649d2 commit 58f8d24
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 40 deletions.
5 changes: 3 additions & 2 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5521,10 +5521,11 @@ def crash_func():
# Click the run button
qtbot.mouseClick(main_window.run_button, Qt.LeftButton)

# Check segfault info is printed in the console
qtbot.waitUntil(lambda: 'Segmentation fault' in control.toPlainText(),
timeout=SHELL_TIMEOUT)
assert 'Segmentation fault' in control.toPlainText()
assert 'in crash_func' in control.toPlainText()
qtbot.waitUntil(lambda: 'in crash_func' in control.toPlainText(),
timeout=SHELL_TIMEOUT)


@flaky(max_runs=3)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import pytest

# Local imports
from spyder.config.base import running_in_ci, running_in_ci_with_conda
from spyder.config.base import running_in_ci
from spyder.plugins.editor.extensions.closebrackets import (
CloseBracketsExtension
)
Expand Down Expand Up @@ -214,11 +214,9 @@ def test_get_hints_not_triggered(qtbot, completions_codeeditor, text):
@pytest.mark.skipif(
(
sys.platform == "darwin"
or (
sys.platform.startswith("linux") and not running_in_ci_with_conda()
)
or (sys.platform.startswith("linux") and running_in_ci())
),
reason="Fails on Linux with pip packages and Mac",
reason="Fails on CIs for Linux and Mac",
)
@pytest.mark.parametrize(
"params",
Expand Down Expand Up @@ -277,11 +275,9 @@ def test_get_hints_for_builtins(qtbot, completions_codeeditor, params):
@pytest.mark.skipif(
(
sys.platform == "darwin"
or (
sys.platform.startswith("linux") and not running_in_ci_with_conda()
)
or (sys.platform.startswith("linux") and running_in_ci())
),
reason="Fails on Linux with pip packages and Mac",
reason="Fails on CIs for Linux and Mac",
)
@pytest.mark.parametrize(
"params",
Expand Down
21 changes: 14 additions & 7 deletions spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,9 @@ def test_request_syspath(ipyconsole, qtbot, tmpdir):


@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It doesn't work on Windows")
@pytest.mark.skipif(
not sys.platform.startswith("linux"), reason="Fails on Windows and Mac"
)
def test_save_history_dbg(ipyconsole, qtbot):
"""Test that browsing command history is working while debugging."""
shell = ipyconsole.get_current_shellwidget()
Expand Down Expand Up @@ -622,8 +624,7 @@ def test_save_history_dbg(ipyconsole, qtbot):


@flaky(max_runs=3)
@pytest.mark.skipif(IPython.version_info < (7, 17),
reason="insert is not the same in pre 7.17 ipython")
@pytest.mark.skipif(sys.platform == "darwin", reason="Hangs on Mac")
def test_dbg_input(ipyconsole, qtbot):
"""Test that spyder doesn't send pdb commands to unrelated input calls."""
shell = ipyconsole.get_current_shellwidget()
Expand Down Expand Up @@ -674,8 +675,10 @@ def test_unicode_vars(ipyconsole, qtbot):

@flaky(max_runs=10)
@pytest.mark.no_xvfb
@pytest.mark.skipif(running_in_ci() and os.name == 'nt',
reason="Times out on Windows")
@pytest.mark.skipif(
(running_in_ci() and os.name == 'nt') or sys.platform == "darwin",
reason="Hangs on CIs for Windows and Mac"
)
def test_values_dbg(ipyconsole, qtbot):
"""
Test that getting, setting, copying and removing values is working while
Expand Down Expand Up @@ -727,6 +730,7 @@ def is_defined(val):


@flaky(max_runs=3)
@pytest.mark.skipif(sys.platform == "darwin", reason="Hangs on Mac")
def test_execute_events_dbg(ipyconsole, qtbot):
"""Test execute events while debugging"""

Expand Down Expand Up @@ -850,7 +854,10 @@ def test_mpl_backend_change(ipyconsole, qtbot):


@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It doesn't work on Windows")
@pytest.mark.skipif(
os.name == 'nt' or sys.platform == "darwin",
reason="Doesn't work on Windows and hangs on Mac"
)
def test_clear_and_reset_magics_dbg(ipyconsole, qtbot):
"""
Test that clear and reset magics are working while debugging
Expand Down Expand Up @@ -1067,11 +1074,11 @@ def test_kernel_crash(ipyconsole, qtbot):
qtbot.waitUntil(lambda: bool(
ipyconsole.get_widget()._cached_kernel_properties[-1]._init_stderr
))

# Create a new client
ipyconsole.create_new_client()

# Assert that the console is showing an error
# even if the error happened before the connection
error_client = ipyconsole.get_clients()[-1]
qtbot.waitUntil(lambda: bool(error_client.error_text), timeout=6000)
finally:
Expand Down
6 changes: 5 additions & 1 deletion spyder/plugins/ipythonconsole/utils/kernel_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

# Local imports
from spyder.api.translations import _
from spyder.config.base import running_under_pytest
from spyder.plugins.ipythonconsole import (
SPYDER_KERNELS_MIN_VERSION, SPYDER_KERNELS_MAX_VERSION,
SPYDER_KERNELS_VERSION, SPYDER_KERNELS_CONDA, SPYDER_KERNELS_PIP)
Expand Down Expand Up @@ -488,7 +489,10 @@ def close(self, shutdown_kernel=True, now=False):
km.stop_restarter()
self.disconnect_std_pipes()

if now:
# This is probably necessary due to a weird interaction between
# `conda run --no-capture-output` and pytest capturing output
# facilities.
if now or running_under_pytest():
km.shutdown_kernel(now=True)
self.after_shutdown()
else:
Expand Down
7 changes: 5 additions & 2 deletions spyder/plugins/ipythonconsole/utils/kernelspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,11 @@ def argv(self):
# runtime environment, we need to activate the environment to run
# spyder-kernels
kernel_cmd[:0] = [
find_conda(), 'run',
'-p', get_conda_env_path(pyexec),
find_conda(),
'run',
'--no-capture-output',
'--prefix',
get_conda_env_path(pyexec),
]

logger.info('Kernel command: {}'.format(kernel_cmd))
Expand Down
43 changes: 39 additions & 4 deletions spyder/plugins/ipythonconsole/widgets/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

# Standard library imports.
import functools
import logging
import os
import os.path as osp
Expand All @@ -23,7 +24,7 @@

# Third party imports (qtpy)
from qtpy.QtCore import QUrl, QTimer, Signal, Slot
from qtpy.QtWidgets import QVBoxLayout, QWidget
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget

# Local imports
from spyder.api.translations import _
Expand Down Expand Up @@ -121,6 +122,7 @@ def __init__(self, parent, id_,
self.allow_rename = True
self.error_text = None
self.give_focus = give_focus
self.__on_close = lambda: None

css_path = self.get_conf('css_path', section='appearance')
if css_path is None:
Expand Down Expand Up @@ -553,20 +555,53 @@ def set_color_scheme(self, color_scheme, reset=True):
except AttributeError:
pass

def close_client(self, is_last_client):
def close_client(self, is_last_client, close_console=False):
"""Close the client."""
self.__on_close = lambda: None
debugging = False

# Needed to handle a RuntimeError. See spyder-ide/spyder#5568.
try:
self.stop_button_click_handler()
# This is required after spyder-ide/spyder#21788 to prevent freezes
# when closing Spyder. That happens not only when a console is in
# debugging mode before closing, but also when a kernel restart is
# requested while debugging.
if self.shellwidget.is_debugging():
debugging = True
self.__on_close = functools.partial(
self.finish_close,
is_last_client,
close_console,
debugging
)
self.shellwidget.sig_prompt_ready.connect(self.__on_close)
self.shellwidget.stop_debugging()
else:
self.interrupt_kernel()
except RuntimeError:
pass

# Disconnect timer needed to update elapsed time
if not debugging:
self.finish_close(is_last_client, close_console, debugging)

def finish_close(self, is_last_client, close_console, debugging):
"""Actions to take to finish closing the client."""
# Disconnect timer needed to update elapsed time and this slot in case
# it was connected.
try:
self.shellwidget.sig_prompt_ready.disconnect(self.__on_close)
self.timer.timeout.disconnect(self.show_time)
except (RuntimeError, TypeError):
pass

# This is a hack to prevent segfaults when closing Spyder and the
# client was debugging before doing it.
# It's a side effect of spyder-ide/spyder#21788
if debugging and close_console:
for __ in range(3):
time.sleep(0.08)
QApplication.processEvents()

self.shutdown(is_last_client)

# Prevent errors in our tests
Expand Down
9 changes: 3 additions & 6 deletions spyder/plugins/ipythonconsole/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,6 @@ def __init__(self, name=None, plugin=None, parent=None):
# Update the list of envs at startup
self.get_envs()

def on_close(self):
self.mainwindow_close = True
self.close_all_clients()

# ---- PluginMainWidget API and settings handling
# ------------------------------------------------------------------------
def get_title(self):
Expand Down Expand Up @@ -1767,8 +1763,9 @@ def close_all_clients(self):
open_clients = self.clients.copy()
for client in self.clients:
is_last_client = (
len(self.get_related_clients(client, open_clients)) == 0)
client.close_client(is_last_client)
len(self.get_related_clients(client, open_clients)) == 0
)
client.close_client(is_last_client, close_console=True)
open_clients.remove(client)

# Wait for all KernelHandler threads to shutdown.
Expand Down
8 changes: 6 additions & 2 deletions spyder/plugins/ipythonconsole/widgets/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@ def get_cached_kernel(self, kernel_spec, cache=True):
self.close_cached_kernel()
return new_kernel_handler

# Check cached kernel has the same configuration as is being asked
# Check cached kernel has the same configuration as is being asked or
# it crashed.
cached_kernel_handler = None
if self._cached_kernel_properties is not None:
cached_kernel_handler = self._cached_kernel_properties[-1]
if not self.check_cached_kernel_spec(kernel_spec):
if (
not self.check_cached_kernel_spec(kernel_spec)
or cached_kernel_handler._init_stderr
):
# Close the kernel
self.close_cached_kernel()
cached_kernel_handler = None
Expand Down
38 changes: 31 additions & 7 deletions spyder/plugins/projects/widgets/main_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Standard library imports
from collections import OrderedDict
import configparser
from contextlib import contextmanager
import logging
import os
import os.path as osp
Expand Down Expand Up @@ -400,13 +401,13 @@ def open_project(self, path=None, project_type=None, restart_console=True,
self._add_to_recent(path)

self.set_conf('current_project_path', self.get_active_project_path())

self._setup_menu_actions()

if workdir and osp.isdir(workdir):
self.sig_project_loaded.emit(workdir)
else:
self.sig_project_loaded.emit(path)
with self._disable_pdb_prevent_closing():
if workdir and osp.isdir(workdir):
self.sig_project_loaded.emit(workdir)
else:
self.sig_project_loaded.emit(path)

self.watcher.start(path)

Expand Down Expand Up @@ -440,8 +441,9 @@ def close_project(self):
self.set_conf('current_project_path', None)
self._setup_menu_actions()

self.sig_project_closed.emit(path)
self.sig_project_closed[bool].emit(True)
with self._disable_pdb_prevent_closing():
self.sig_project_closed.emit(path)
self.sig_project_closed[bool].emit(True)

# Hide pane.
self.set_conf('visible_if_project_open', self.isVisible())
Expand Down Expand Up @@ -1000,6 +1002,28 @@ def _get_valid_recent_projects(self, recent_projects):

return valid_projects

@contextmanager
def _disable_pdb_prevent_closing(self):
"""
Context manager to disable the pdb_prevent_closing option before
opening/closing the previous/current open project files.
Notes
-----
* This is necessary to correctly do that when a console was left in
debugging mode.
"""
try:
pdb_prevent_closing = self.get_conf(
"pdb_prevent_closing", section="debugger"
)
self.set_conf("pdb_prevent_closing", False, section="debugger")
yield
finally:
self.set_conf(
"pdb_prevent_closing", pdb_prevent_closing, section="debugger"
)

# ---- Private API for the Switcher
# -------------------------------------------------------------------------
def _call_fzf(self, search_text=""):
Expand Down

0 comments on commit 58f8d24

Please sign in to comment.