Skip to content

Commit

Permalink
Merge from 3.x: PR #4019
Browse files Browse the repository at this point in the history
Fixes #4003
  • Loading branch information
ccordoba12 committed Feb 25, 2017
2 parents 8b2b222 + e48c456 commit b559d9a
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 26 deletions.
63 changes: 61 additions & 2 deletions spyder/plugins/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from flaky import flaky
import pytest
from qtpy.QtCore import Qt, QTimer
from qtpy.QtWidgets import QApplication
from qtpy.QtWidgets import QApplication, QMessageBox

from spyder.plugins.ipythonconsole import (IPythonConsole,
KernelConnectionDialog)
Expand All @@ -35,6 +35,13 @@ def open_client_from_connection_info(connection_info, qtbot):
qtbot.keyClick(w, Qt.Key_Enter)


def restart_kernel(qtbot):
top_level_widgets = QApplication.topLevelWidgets()
for w in top_level_widgets:
if isinstance(w, QMessageBox):
qtbot.keyClick(w, Qt.Key_Enter)


#==============================================================================
# Qt Test Fixtures
#==============================================================================
Expand All @@ -52,7 +59,59 @@ def close_widget():
#==============================================================================
# Tests
#==============================================================================
@flaky(max_runs=3)
@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_forced_restart_kernel(ipyconsole, qtbot):
"""
Test that kernel is restarted if we force it do it
during debugging
"""
shell = ipyconsole.get_current_shellwidget()
client = ipyconsole.get_current_client()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Do an assigment to verify that it's not there after restarting
shell.execute('a = 10')
qtbot.wait(500)

# Generate a traceback and enter debugging mode
shell.execute('1/0')
qtbot.wait(500)
shell.execute('%debug')
qtbot.wait(500)

# Force a kernel restart and wait until it's up again
shell._prompt_html = None
shell.silent_exec_input("1+1") # The return type of this must be a dict!!
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

assert not shell.is_defined('a')


@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_restart_kernel(ipyconsole, qtbot):
"""
Test that kernel is restarted correctly
"""
shell = ipyconsole.get_current_shellwidget()
client = ipyconsole.get_current_client()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Do an assigment to verify that it's not there after restarting
shell.execute('a = 10')
qtbot.wait(500)

# Restart kernel and wait until it's up again
shell._prompt_html = None
QTimer.singleShot(1000, lambda: restart_kernel(qtbot))
client.restart_kernel()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

assert not shell.is_defined('a')


@flaky(max_runs=10)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_load_kernel_file_from_id(ipyconsole, qtbot):
"""
Expand Down
6 changes: 4 additions & 2 deletions spyder/utils/tests/test_programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
INVALID_INTERPRETER = os.path.join(home_dir, 'miniconda', 'bin', 'ipython')


@pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
@pytest.mark.skipif(os.name == 'nt' or os.environ.get('CI', None) is None,
reason='gets stuck on Windows and fails sometimes locally') # FIXME
def test_run_python_script_in_terminal(tmpdir, qtbot):
scriptpath = tmpdir.join('write-done.py')
outfilepath = tmpdir.join('out.txt')
Expand All @@ -37,7 +38,8 @@ def test_run_python_script_in_terminal(tmpdir, qtbot):
assert res == 'done'


@pytest.mark.skipif(os.name == 'nt', reason='gets stuck on Windows') # FIXME
@pytest.mark.skipif(os.name == 'nt' or os.environ.get('CI', None) is None,
reason='gets stuck on Windows and fails sometimes locally') # FIXME
def test_run_python_script_in_terminal_with_wdir_empty(tmpdir, qtbot):
scriptpath = tmpdir.join('write-done.py')
outfilepath = tmpdir.join('out.txt')
Expand Down
39 changes: 29 additions & 10 deletions spyder/widgets/ipythonconsole/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ def configure_shellwidget(self, give_focus=True):
self.shellwidget.sig_kernel_restarted.connect(
self.kernel_restarted_message)

# To restart the kernel when errors happened while debugging
# See issue 4003
self.shellwidget.sig_dbg_kernel_restart.connect(
self.restart_kernel)

def enable_stop_button(self):
self.stop_button.setEnabled(True)

Expand Down Expand Up @@ -323,12 +328,20 @@ def restart_kernel(self):
Took this code from the qtconsole project
Licensed under the BSD license
"""
message = _('Are you sure you want to restart the kernel?')
buttons = QMessageBox.Yes | QMessageBox.No
result = QMessageBox.question(self, _('Restart kernel?'),
message, buttons)
if result == QMessageBox.Yes:
sw = self.shellwidget
sw = self.shellwidget

# This is needed to restart the kernel without a prompt
# when an error in stdout corrupts the debugging process.
# See issue 4003
if not sw._input_reply_failed:
message = _('Are you sure you want to restart the kernel?')
buttons = QMessageBox.Yes | QMessageBox.No
result = QMessageBox.question(self, _('Restart kernel?'),
message, buttons)
else:
result = None

if result == QMessageBox.Yes or sw._input_reply_failed:
if sw.kernel_manager:
if self.infowidget.isVisible():
self.infowidget.hide()
Expand All @@ -341,10 +354,16 @@ def restart_kernel(self):
before_prompt=True
)
else:
sw.reset(clear=True)
sw._append_html(_("<br>Restarting kernel...\n<hr><br>"),
before_prompt=False,
)
sw.reset(clear=not sw._input_reply_failed)
if sw._input_reply_failed:
sw._append_html(_("<br>Restarting kernel because "
"an error occurred while "
"debugging\n<hr><br>"),
before_prompt=False)
sw._input_reply_failed = False
else:
sw._append_html(_("<br>Restarting kernel...\n<hr><br>"),
before_prompt=False)
else:
sw._append_plain_text(
_('Cannot restart a kernel not started by Spyder\n'),
Expand Down
41 changes: 29 additions & 12 deletions spyder/widgets/ipythonconsole/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class DebuggingWidget(RichJupyterWidget):
Spyder
"""

_input_reply = None
_input_reply = {}
_input_reply_failed = False

# --- Public API --------------------------------------------------
def silent_exec_input(self, code):
Expand All @@ -47,20 +48,33 @@ def silent_exec_input(self, code):
self._hidden = False

# Emit signal
if 'pdb_step' in code and self._input_reply is not None:
fname = self._input_reply['fname']
lineno = self._input_reply['lineno']
self.sig_pdb_step.emit(fname, lineno)
elif 'get_namespace_view' in code:
view = self._input_reply
self.sig_namespace_view.emit(view)
elif 'get_var_properties' in code:
properties = self._input_reply
self.sig_var_properties.emit(properties)
if isinstance(self._input_reply, dict):
if 'pdb_step' in code and 'fname' in self._input_reply:
fname = self._input_reply['fname']
lineno = self._input_reply['lineno']
self.sig_pdb_step.emit(fname, lineno)
elif 'get_namespace_view' in code:
if 'fname' not in self._input_reply:
view = self._input_reply
else:
view = None
self.sig_namespace_view.emit(view)
elif 'get_var_properties' in code:
if 'fname' not in self._input_reply:
properties = self._input_reply
else:
properties = None
self.sig_var_properties.emit(properties)
else:
self.kernel_client.iopub_channel.flush()
self._input_reply = {}
self._input_reply_failed = True
self.sig_dbg_kernel_restart.emit()

def write_to_stdin(self, line):
"""Send raw characters to the IPython kernel through stdin"""
wait_loop = QEventLoop()
self.kernel_client.iopub_channel.flush()
self.sig_prompt_ready.connect(wait_loop.quit)
self.kernel_client.input(line)
wait_loop.exec_()
Expand Down Expand Up @@ -136,7 +150,10 @@ def _handle_stream(self, msg):
reply = ast.literal_eval(text)
except:
reply = None
self._input_reply = reply
if not isinstance(reply, dict):
self._input_reply = None
else:
self._input_reply = reply
self.sig_input_reply.emit()
else:
self._input_reply = None
Expand Down
1 change: 1 addition & 0 deletions spyder/widgets/ipythonconsole/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget):
sig_input_reply = Signal()
sig_pdb_step = Signal(str, int)
sig_prompt_ready = Signal()
sig_dbg_kernel_restart = Signal()

# For ShellWidget
focus_changed = Signal()
Expand Down

0 comments on commit b559d9a

Please sign in to comment.