Skip to content

Commit

Permalink
Merge from 3.x: PR #4670
Browse files Browse the repository at this point in the history
Fixes #4664
  • Loading branch information
ccordoba12 committed Jun 30, 2017
2 parents fb69558 + 9a92ca0 commit d2593ba
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 34 deletions.
128 changes: 95 additions & 33 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import tempfile

from flaky import flaky
from jupyter_client.manager import KernelManager
import numpy as np
from numpy.testing import assert_array_equal
import pytest
Expand All @@ -25,6 +26,7 @@
from spyder.app.cli_options import get_options
from spyder.app.mainwindow import initialize, run_spyder
from spyder.py3compat import PY2
from spyder.utils.ipython.kernelspec import SpyderKernelSpec
from spyder.utils.programs import is_module_installed
from spyder.utils.test import close_save_message_box

Expand Down Expand Up @@ -74,6 +76,25 @@ def reset_run_code(qtbot, shell, code_editor, nsb):
qtbot.keyClick(code_editor, Qt.Key_Home, modifier=Qt.ControlModifier)


def start_new_kernel(startup_timeout=60, kernel_name='python', spykernel=False,
**kwargs):
"""Start a new kernel, and return its Manager and Client"""
km = KernelManager(kernel_name=kernel_name)
if spykernel:
km._kernel_spec = SpyderKernelSpec()
km.start_kernel(**kwargs)
kc = km.client()
kc.start_channels()
try:
kc.wait_for_ready(timeout=startup_timeout)
except RuntimeError:
kc.stop_channels()
km.shutdown_kernel()
raise

return km, kc


#==============================================================================
# Fixtures
#==============================================================================
Expand Down Expand Up @@ -104,6 +125,80 @@ def close_window():
#==============================================================================
# Tests
#==============================================================================
# IMPORTANT NOTE: Please leave this test to be the first one here to
# avoid possible timeouts in Appyevor
@flaky(max_runs=3)
@pytest.mark.skipif(os.name != 'nt' or not PY2,
reason="It times out on Linux and Python 3")
@pytest.mark.timeout(timeout=60, method='thread')
@pytest.mark.use_introspection
def test_calltip(main_window, qtbot):
"""Hide the calltip in the editor when a matching ')' is found."""
# Load test file
text = 'a = [1,2,3]\n(max'
main_window.editor.new(fname="test.py", text=text)
code_editor = main_window.editor.get_focus_widget()

# Set text to start
code_editor.set_text(text)
code_editor.go_to_line(2)
code_editor.move_cursor(5)
calltip = code_editor.calltip_widget
assert not calltip.isVisible()

qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000)

qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Space)
assert not calltip.isVisible()
qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)

QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
main_window.editor.close_file()


@flaky(max_runs=3)
def test_connection_to_external_kernel(main_window, qtbot):
"""Test that only Spyder kernels are connected to the Variable Explorer."""
# Test with a generic kernel
km, kc = start_new_kernel()

main_window.ipyconsole._create_client_for_kernel(kc.connection_file, None,
None, None)
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Assert that there are no variables in the variable explorer
main_window.variableexplorer.visibility_changed(True)
nsb = main_window.variableexplorer.get_focus_widget()
qtbot.wait(500)
assert nsb.editor.model.rowCount() == 0

# Test with a kernel from Spyder
spykm, spykc = start_new_kernel(spykernel=True)
main_window.ipyconsole._create_client_for_kernel(spykc.connection_file, None,
None, None)
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
with qtbot.waitSignal(shell.executed):
shell.execute('a = 10')

# Assert that a variable is visible in the variable explorer
main_window.variableexplorer.visibility_changed(True)
nsb = main_window.variableexplorer.get_focus_widget()
qtbot.wait(500)
assert nsb.editor.model.rowCount() == 1

# Shutdown the kernels
spykm.shutdown_kernel(now=True)
km.shutdown_kernel(now=True)


@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_np_threshold(main_window, qtbot):
Expand Down Expand Up @@ -156,39 +251,6 @@ def test_change_types_in_varexp(main_window, qtbot):
assert shell.get_value('a') == 10


@flaky(max_runs=3)
@pytest.mark.skipif(os.name != 'nt' or not PY2,
reason="It times out on Linux and Python 3")
@pytest.mark.timeout(timeout=60, method='thread')
@pytest.mark.use_introspection
def test_calltip(main_window, qtbot):
"""Hide the calltip in the editor when a matching ')' is found."""
# Load test file
text = 'a = [1,2,3]\n(max'
main_window.editor.new(fname="test.py", text=text)
code_editor = main_window.editor.get_focus_widget()

# Set text to start
code_editor.set_text(text)
code_editor.go_to_line(2)
code_editor.move_cursor(5)
calltip = code_editor.calltip_widget
assert not calltip.isVisible()

qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
qtbot.waitUntil(lambda: calltip.isVisible(), timeout=1000)

qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Space)
assert not calltip.isVisible()
qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)

QTimer.singleShot(1000, lambda: close_save_message_box(qtbot))
main_window.editor.close_file()


@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or not is_module_installed('Cython'),
reason="It times out sometimes on Windows and Cython is needed")
Expand Down
24 changes: 23 additions & 1 deletion spyder/plugins/ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -1384,12 +1384,30 @@ def process_finished(self, client):
if self.variableexplorer is not None:
self.variableexplorer.remove_shellwidget(id(client.shellwidget))

def connect_external_kernel(self, shellwidget):
"""
Connect an external kernel to the Variable Explorer and Help, if
it is a Spyder kernel.
"""
sw = shellwidget
kc = shellwidget.kernel_client
if self.help is not None:
self.help.set_shell(sw)
if self.variableexplorer is not None:
self.variableexplorer.add_shellwidget(sw)
sw.set_namespace_view_settings()
sw.refresh_namespacebrowser()
kc.stopped_channels.connect(lambda :
self.variableexplorer.remove_shellwidget(id(sw)))

def _create_client_for_kernel(self, connection_file, hostname, sshkey,
password):
# Verifying if the connection file exists
try:
cf_path = osp.dirname(connection_file)
cf_filename = osp.basename(connection_file)
# To change a possible empty string to None
cf_path = cf_path if cf_path else None
connection_file = find_connection_file(filename=cf_filename,
path=cf_path)
except (IOError, UnboundLocalError):
Expand Down Expand Up @@ -1458,11 +1476,15 @@ def _create_client_for_kernel(self, connection_file, hostname, sshkey,
_("Could not open ssh tunnel. The "
"error was:\n\n") + to_text_string(e))
return
kernel_client.start_channels()

# Assign kernel manager and client to shellwidget
client.shellwidget.kernel_client = kernel_client
client.shellwidget.kernel_manager = kernel_manager
kernel_client.start_channels()
if external_kernel:
client.shellwidget.sig_is_spykernel.connect(
self.connect_external_kernel)
client.shellwidget.is_spyder_kernel()

# Adding a new tab for the client
self.add_tab(client, name=client.get_name())
Expand Down
14 changes: 14 additions & 0 deletions spyder/widgets/ipythonconsole/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget):
focus_changed = Signal()
new_client = Signal()
sig_got_reply = Signal()
sig_is_spykernel = Signal(object)
sig_kernel_restarted = Signal(str)

def __init__(self, ipyclient, additional_options, interpreter_versions,
Expand Down Expand Up @@ -77,6 +78,14 @@ def is_running(self):
else:
return False

def is_spyder_kernel(self):
"""Determine if the kernel is from Spyder."""
code = u"getattr(get_ipython().kernel, 'set_value', False)"
if self._reading:
return
else:
self.silent_exec_method(code)

def set_cwd(self, dirname):
"""Set shell current working directory."""
code = u"get_ipython().kernel.set_cwd(r'{}')".format(dirname)
Expand Down Expand Up @@ -301,6 +310,11 @@ def handle_exec_method(self, msg):
else:
env = None
self.sig_show_env.emit(env)
elif 'getattr' in method:
if data is not None and 'text/plain' in data:
is_spyder_kernel = data['text/plain']
if 'SpyderKernel' in is_spyder_kernel:
self.sig_is_spykernel.emit(self)
else:
if data is not None and 'text/plain' in data:
self._kernel_reply = ast.literal_eval(data['text/plain'])
Expand Down

0 comments on commit d2593ba

Please sign in to comment.