Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Avoid showing dialogs in IPython console tests to avoid segfaults #6602

Merged
merged 7 commits into from
Mar 5, 2018
126 changes: 44 additions & 82 deletions spyder/plugins/tests/test_ipythonconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,16 @@
import ipykernel
from pygments.token import Name
import pytest
from qtpy import PYQT4, PYQT5, PYQT_VERSION
from qtpy.QtCore import Qt, QTimer
from qtpy.QtWidgets import QApplication
from qtpy import PYQT4, PYQT5
from qtpy.QtCore import Qt
import zmq

from spyder.config.gui import get_color_scheme
from spyder.config.main import CONF
from spyder.py3compat import PY2, PY3
from spyder.plugins.ipythonconsole import (IPythonConsole,
KernelConnectionDialog)
from spyder.utils.environ import listdict2envdict
from spyder.py3compat import PY2, to_text_string
from spyder.plugins.ipythonconsole import IPythonConsole
from spyder.utils.ipython.style import create_style_class
from spyder.utils.programs import TEMPDIR
from spyder.utils.test import close_message_box
from spyder.widgets.variableexplorer.collectionseditor import CollectionsEditor


#==============================================================================
Expand All @@ -44,24 +39,18 @@
#==============================================================================
# Utillity Functions
#==============================================================================
def open_client_from_connection_info(connection_info, qtbot):
top_level_widgets = QApplication.topLevelWidgets()
for w in top_level_widgets:
if isinstance(w, KernelConnectionDialog):
w.cf.setText(connection_info)
qtbot.keyClick(w, Qt.Key_Enter)

def get_console_font_color(syntax_style):
styles = create_style_class(syntax_style).styles
font_color = styles[Name]

return font_color


def get_console_background_color(style_sheet):
background_color = style_sheet.split('background-color:')[1]
background_color = background_color.split(';')[0]
return background_color


#==============================================================================
# Qt Test Fixtures
#==============================================================================
Expand Down Expand Up @@ -137,7 +126,6 @@ def test_auto_backend(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_tab_rename_for_slaves(ipyconsole, qtbot):
"""Test slave clients are renamed correctly."""
# Wait until the window is fully up
Expand All @@ -159,9 +147,11 @@ def test_tab_rename_for_slaves(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_no_repeated_tabs_name(ipyconsole, qtbot):
"""Test that tabs can't have repeated given names."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Rename first client
ipyconsole.rename_tabs_after_change('foo')

Expand All @@ -176,9 +166,11 @@ def test_no_repeated_tabs_name(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_tabs_preserve_name_after_move(ipyconsole, qtbot):
"""Test that tabs preserve their names after they are moved."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Create a new client
ipyconsole.create_new_client()

Expand All @@ -192,7 +184,6 @@ def test_tabs_preserve_name_after_move(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_conf_env_vars(ipyconsole, qtbot):
"""Test that kernels have env vars set by our kernel spec."""
# Wait until the window is fully up
Expand All @@ -210,7 +201,6 @@ def test_conf_env_vars(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
@pytest.mark.no_stderr_file
def test_no_stderr_file(ipyconsole, qtbot):
"""Test that consoles can run without an stderr."""
Expand All @@ -230,7 +220,7 @@ def test_no_stderr_file(ipyconsole, qtbot):
@pytest.mark.slow
@pytest.mark.non_ascii_dir
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
@pytest.mark.skipif(os.name == 'nt', reason="It fails on Windows")
def test_non_ascii_stderr_file(ipyconsole, qtbot):
"""Test the creation of a console with a stderr file in a non-ascii dir."""
# Wait until the window is fully up
Expand All @@ -248,7 +238,6 @@ def test_non_ascii_stderr_file(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
def test_console_import_namespace(ipyconsole, qtbot):
"""Test an import of the form 'from foo import *'."""
# Wait until the window is fully up
Expand All @@ -267,6 +256,9 @@ def test_console_import_namespace(ipyconsole, qtbot):
@flaky(max_runs=3)
def test_console_disambiguation(ipyconsole, qtbot):
"""Test the disambiguation of dedicated consoles."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Create directories and file for TEMP_DIRECTORY/a/b/c.py
# and TEMP_DIRECTORY/a/d/c.py
dir_b = osp.join(TEMP_DIRECTORY, 'a', 'b')
Expand Down Expand Up @@ -301,6 +293,9 @@ def test_console_disambiguation(ipyconsole, qtbot):
@pytest.mark.slow
@flaky(max_runs=3)
def test_console_coloring(ipyconsole, qtbot):
"""Test that console gets the same coloring present in the Editor."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

config_options = ipyconsole.config_options()

Expand All @@ -325,10 +320,8 @@ def test_console_coloring(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It doesn't work on Windows and segfaults in PyQt4")
def test_get_env(ipyconsole, qtbot):
"""Test that showing env var contents is working as expected."""
"""Test that getting env vars from the kernel is working as expected."""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

Expand All @@ -337,55 +330,43 @@ def test_get_env(ipyconsole, qtbot):
shell.execute("import os; os.environ['FOO'] = 'bar'" )

# Ask for os.environ contents
with qtbot.waitSignal(shell.sig_show_env):
with qtbot.waitSignal(shell.sig_show_env) as blocker:
shell.get_env()

# Get env contents from the generated widget
top_level_widgets = QApplication.topLevelWidgets()
for w in top_level_widgets:
if isinstance(w, CollectionsEditor):
env_contents = w.get_value()
qtbot.keyClick(w, Qt.Key_Enter)
# Get env contents from the signal
env_contents = blocker.args[0]

# Assert that our added entry is part of os.environ
env_contents = listdict2envdict(env_contents)
assert env_contents['FOO'] == 'bar'


@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It doesn't work on Windows and segfaults in PyQt4")
def test_get_syspath(ipyconsole, qtbot):
"""Test that showing sys.path contents is working as expected."""
@pytest.mark.skipif(os.name == 'nt',
reason="Fails due to differences in path handling")
def test_get_syspath(ipyconsole, qtbot, tmpdir):
"""
Test that getting sys.path contents from the kernel is working as
expected.
"""
shell = ipyconsole.get_current_shellwidget()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

# Add a new entry to sys.path
with qtbot.waitSignal(shell.executed):
tmp_dir = tempfile.mkdtemp()
shell.execute("import sys, tempfile; sys.path.append('%s')" % tmp_dir)
tmp_dir = to_text_string(tmpdir)
shell.execute("import sys; sys.path.append('%s')" % tmp_dir)

# Ask for sys.path contents
with qtbot.waitSignal(shell.sig_show_syspath):
with qtbot.waitSignal(shell.sig_show_syspath) as blocker:
shell.get_syspath()

# Get sys.path contents from the generated widget
top_level_widgets = QApplication.topLevelWidgets()
for w in top_level_widgets:
if isinstance(w, CollectionsEditor):
syspath_contents = w.get_value()
qtbot.keyClick(w, Qt.Key_Enter)
# Get sys.path contents from the signal
syspath_contents = blocker.args[0]

# Assert that our added entry is part of sys.path
assert tmp_dir in syspath_contents

# Remove temporary directory
try:
os.rmdir(tmp_dir)
except:
pass


@pytest.mark.slow
@flaky(max_runs=10)
Expand Down Expand Up @@ -423,8 +404,7 @@ def test_browse_history_dbg(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PY2,
reason="It times out sometimes on Windows and doesn't work on PY2")
@pytest.mark.skipif(PY2, reason="It doesn't work on PY2")
def test_unicode_vars(ipyconsole, qtbot):
"""
Test that the Variable Explorer Works with unicode variables.
Expand Down Expand Up @@ -546,7 +526,6 @@ def test_plot_magic_dbg(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_run_doctest(ipyconsole, qtbot):
"""
Test that doctests can be run without problems
Expand Down Expand Up @@ -684,8 +663,6 @@ def test_clear_and_reset_magics_dbg(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It doesn't work on Windows and segfaults in PyQt4")
def test_restart_kernel(ipyconsole, qtbot):
"""
Test that kernel is restarted correctly
Expand All @@ -700,17 +677,15 @@ def test_restart_kernel(ipyconsole, qtbot):

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

assert 'Restarting kernel...' in shell._control.toPlainText()
assert not shell.is_defined('a')


@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It doesn't work on Windows and segfaults in PyQt4")
def test_load_kernel_file_from_id(ipyconsole, qtbot):
"""
Test that a new client is created using its id
Expand All @@ -722,9 +697,7 @@ def test_load_kernel_file_from_id(ipyconsole, qtbot):
connection_file = osp.basename(client.connection_file)
id_ = connection_file.split('kernel-')[-1].split('.json')[0]

QTimer.singleShot(2000, lambda: open_client_from_connection_info(
id_, qtbot))
ipyconsole.create_client_for_kernel()
ipyconsole._create_client_for_kernel(id_, None, None, None)
qtbot.wait(1000)

new_client = ipyconsole.get_clients()[1]
Expand All @@ -733,9 +706,7 @@ def test_load_kernel_file_from_id(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It segfaults frequently")
def test_load_kernel_file_from_location(ipyconsole, qtbot):
def test_load_kernel_file_from_location(ipyconsole, qtbot, tmpdir):
"""
Test that a new client is created using a connection file
placed in a different location from jupyter_runtime_dir
Expand All @@ -744,24 +715,19 @@ def test_load_kernel_file_from_location(ipyconsole, qtbot):
client = ipyconsole.get_current_client()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

connection_file = osp.join(tempfile.gettempdir(),
osp.basename(client.connection_file))
fname = osp.basename(client.connection_file)
connection_file = to_text_string(tmpdir.join(fname))
shutil.copy2(client.connection_file, connection_file)

QTimer.singleShot(2000, lambda: open_client_from_connection_info(
connection_file,
qtbot))
ipyconsole.create_client_for_kernel()
ipyconsole._create_client_for_kernel(connection_file, None, None, None)
qtbot.wait(1000)

assert len(ipyconsole.get_clients()) == 2


@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt' or PYQT4,
reason="It segfaults frequently")
def test_load_kernel_file(ipyconsole, qtbot):
def test_load_kernel_file(ipyconsole, qtbot, tmpdir):
"""
Test that a new client is created using the connection file
of an existing client
Expand All @@ -770,10 +736,7 @@ def test_load_kernel_file(ipyconsole, qtbot):
client = ipyconsole.get_current_client()
qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

QTimer.singleShot(2000, lambda: open_client_from_connection_info(
client.connection_file,
qtbot))
ipyconsole.create_client_for_kernel()
ipyconsole._create_client_for_kernel(client.connection_file, None, None, None)
qtbot.wait(1000)

new_client = ipyconsole.get_clients()[1]
Expand All @@ -787,7 +750,6 @@ def test_load_kernel_file(ipyconsole, qtbot):

@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It times out on Windows")
def test_sys_argv_clear(ipyconsole, qtbot):
"""Test that sys.argv is cleared up correctly"""
shell = ipyconsole.get_current_shellwidget()
Expand Down
17 changes: 10 additions & 7 deletions spyder/widgets/ipythonconsole/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@
QToolButton, QVBoxLayout, QWidget)

# Local imports
from spyder.config.base import _, get_image_path, get_module_source_path
from spyder.config.base import (_, get_image_path, get_module_source_path,
running_under_pytest)
from spyder.config.gui import get_font, get_shortcut
from spyder.utils import icon_manager as ima
from spyder.utils import sourcecode
from spyder.utils.encoding import get_coding
from spyder.utils.environ import RemoteEnvDialog
from spyder.utils.ipython.style import create_qss_style
from spyder.utils.programs import TEMPDIR
from spyder.utils.qthelpers import (add_actions, create_action,
create_toolbutton, DialogManager,
Expand Down Expand Up @@ -459,12 +459,15 @@ def restart_kernel(self):
"""
sw = self.shellwidget

message = _('Are you sure you want to restart the kernel?')
buttons = QMessageBox.Yes | QMessageBox.No
result = QMessageBox.question(self, _('Restart kernel?'),
message, buttons)
if not running_under_pytest():
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:
if result == QMessageBox.Yes or running_under_pytest():
if sw.kernel_manager:
if self.infowidget.isVisible():
self.infowidget.hide()
Expand Down