Skip to content

Commit

Permalink
Merge pull request spyder-ide#457 from jitseniesen/spy600b1
Browse files Browse the repository at this point in the history
 PR: Compatibility fixes for Spyder 6.0.0 beta 1
  • Loading branch information
jitseniesen authored Jun 17, 2024
2 parents b171420 + e2fae39 commit 2268a81
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 89 deletions.
10 changes: 7 additions & 3 deletions .github/scripts/generate-without-spyder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
"""Script to generate requirements/without-spyder.txt"""

import re
from pathlib import Path

with open('requirements/conda.txt') as infile:
with open('requirements/without-spyder.txt', 'w') as outfile:
rootdir = Path(__file__).parents[2]
input_filename = rootdir / 'requirements' / 'conda.txt'
output_filename = rootdir / 'requirements' / 'without-spyder.txt'

with open(input_filename) as infile:
with open(output_filename, 'w') as outfile:
for line in infile:
package_name = re.match('[-a-z0-9_]*', line).group(0)
if package_name != 'spyder':
outfile.write(line)

58 changes: 27 additions & 31 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
OS: ['ubuntu', 'macos', 'windows']
PYTHON_VERSION: ['3.8', '3.9', '3.10']
PYTHON_VERSION: ['3.9', '3.10', '3.11']
SPYDER_SOURCE: ['git']
name: ${{ matrix.OS }} py${{ matrix.PYTHON_VERSION }} spyder-from-${{ matrix.SPYDER_SOURCE }}
runs-on: ${{ matrix.OS }}-latest
Expand All @@ -23,101 +23,97 @@ jobs:
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
steps:
- name: Checkout branch
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: spyder-notebook
- name: Install System Packages
if: matrix.OS == 'ubuntu'
run: |
sudo apt-get update --fix-missing
sudo apt-get install -qq pyqt5-dev-tools libxcb-xinerama0 xterm --fix-missing
- name: Install Conda
uses: conda-incubator/setup-miniconda@v2
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-variant: Mambaforge
auto-update-conda: true
python-version: ${{ matrix.PYTHON_VERSION }}
auto-update-conda: true
python-version: ${{ matrix.PYTHON_VERSION }}
- name: Checkout Spyder from git
if: matrix.SPYDER_SOURCE == 'git'
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: 'spyder-ide/spyder'
path: 'spyder'
- name: Install Spyder's dependencies (main)
if: matrix.SPYDER_SOURCE == 'git'
shell: bash -l {0}
run: mamba env update --file spyder/requirements/main.yml
run: conda env update --file spyder/requirements/main.yml
- name: Install Spyder's dependencies (Linux)
if: matrix.SPYDER_SOURCE == 'git' && matrix.OS == 'ubuntu'
shell: bash -l {0}
run: mamba env update --file spyder/requirements/linux.yml
run: conda env update --file spyder/requirements/linux.yml
- name: Install Spyder's dependencies (Mac / Windows)
if: matrix.SPYDER_SOURCE == 'git' && matrix.OS != 'ubuntu'
shell: bash -l {0}
run: mamba env update --file spyder/requirements/${{ matrix.OS }}.yml
run: conda env update --file spyder/requirements/${{ matrix.OS }}.yml
- name: Install Spyder from source
if: matrix.SPYDER_SOURCE == 'git'
shell: bash -l {0}
run: pip install --no-deps -e spyder
- name: Install node.js
shell: bash -l {0}
run: mamba install nodejs -y
run: conda install nodejs -y
- name: Install plugin dependencies (without Spyder)
if: matrix.SPYDER_SOURCE == 'git'
shell: bash -l {0}
run: |
python .github/scripts/generate-without-spyder.py
mamba install --file requirements/without-spyder.txt -y
python spyder-notebook/.github/scripts/generate-without-spyder.py
conda install --file spyder-notebook/requirements/without-spyder.txt -y
- name: Install plugin dependencies
if: matrix.SPYDER_SOURCE == 'conda'
shell: bash -l {0}
run: mamba install --file requirements/conda.txt -y
run: conda install --file spyder-notebook/requirements/conda.txt -y
- name: Install test dependencies
shell: bash -l {0}
run: |
mamba install nomkl -y -q
mamba install --file requirements/tests.txt -y
conda install nomkl -y -q
conda install --file spyder-notebook/requirements/tests.txt -y
- name: Build JavaScript
shell: bash -l {0}
run: |
cd spyder_notebook/server
cd spyder-notebook/spyder_notebook/server
jlpm install
jlpm build
- name: Install plugin
shell: bash -l {0}
run: pip install --no-deps -e .
run: pip install --no-deps -e spyder-notebook
- name: Show environment information
shell: bash -l {0}
run: |
mamba info
mamba list
conda info
conda list
- name: Run tests (Linux)
if: matrix.OS == 'ubuntu'
uses: nick-fields/retry@v2
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
shell: bash
command: |
. ~/.profile
xvfb-run --auto-servernum pytest spyder_notebook --cov=spyder_notebook --cov-report=xml -vv
xvfb-run --auto-servernum pytest spyder-notebook/spyder_notebook -vv
- name: Run tests (MacOS)
if: matrix.OS == 'macos'
uses: nick-fields/retry@v2
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
shell: bash
command: |
. ~/.profile
pytest spyder_notebook -vv
pytest spyder-notebook/spyder_notebook -vv
- name: Run tests (Windows)
if: matrix.OS == 'windows'
uses: nick-fields/retry@v2
uses: nick-fields/retry@v3
with:
timeout_minutes: 10
max_attempts: 3
command: pytest spyder_notebook -vv
- name: Upload coverage to Codecov
if: matrix.OS == 'ubuntu' && matrix.PYTHON_VERSION == '3.10'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
command: pytest spyder-notebook/spyder_notebook -vv
2 changes: 1 addition & 1 deletion requirements/conda.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
spyder >=5.4.3,<6
spyder >=6.0.0.dev0,<7
jupyter_core
jupyter_server
nbformat
Expand Down
142 changes: 142 additions & 0 deletions spyder_notebook/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# Copyright © Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# ----------------------------------------------------------------------------

"""
Testing utilities to be used with pytest.
"""

# Copied from conftest.py in main Spyder repo, with one change in
# register_plugin() inside ConfigDialogTester.

# Standard library imports
import sys
import types
from unittest.mock import Mock, MagicMock

# Third party imports
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QWidget, QMainWindow
import pytest

# Local imports
from spyder.api.plugins import Plugins
from spyder.api.plugin_registration.registry import PLUGIN_REGISTRY
from spyder.app.cli_options import get_options
from spyder.config.manager import CONF
from spyder.utils.icon_manager import ima


class MainWindowMock(QMainWindow):
register_shortcut = Mock()

def __init__(self, parent):
# This import assumes that an QApplication is already running,
# so we can not put it at the top of the file
from spyder.plugins.preferences.plugin import Preferences

super().__init__(parent)
self.default_style = None
self.widgetlist = []
self.thirdparty_plugins = []
self.shortcut_data = []
self.prefs_dialog_instance = None
self._APPLICATION_TOOLBARS = MagicMock()

self.console = Mock()

# To provide command line options for plugins that need them
sys_argv = [sys.argv[0]] # Avoid options passed to pytest
self._cli_options = get_options(sys_argv)[0]

PLUGIN_REGISTRY.reset()
PLUGIN_REGISTRY.sig_plugin_ready.connect(self.register_plugin)
PLUGIN_REGISTRY.register_plugin(self, Preferences)

# Load shortcuts for tests
for context, name, __ in CONF.iter_shortcuts():
self.shortcut_data.append((None, context, name, None, None))

for attr in ['mem_status', 'cpu_status']:
mock_attr = Mock()
setattr(mock_attr, 'toolTip', lambda: '')
setattr(mock_attr, 'setToolTip', lambda x: '')
setattr(mock_attr, 'prefs_dialog_instance', lambda: '')
setattr(self, attr, mock_attr)

def register_plugin(self, plugin_name, external=False):
plugin = PLUGIN_REGISTRY.get_plugin(plugin_name)
plugin._register(omit_conf=True)

def get_plugin(self, plugin_name, error=True):
if plugin_name in PLUGIN_REGISTRY:
return PLUGIN_REGISTRY.get_plugin(plugin_name)


class ConfigDialogTester(QWidget):
def __init__(self, parent, main_class,
general_config_plugins, plugins):
# This import assumes that an QApplication is already running,
# so we can not put it at the top of the file
from spyder.plugins.preferences.plugin import Preferences

super().__init__(parent)
self._main = main_class(self) if main_class else None
if self._main is None:
self._main = MainWindowMock(self)

def register_plugin(self, plugin_name, external=False):
plugin = PLUGIN_REGISTRY.get_plugin(plugin_name)

# If plugin.CONF_FILE is True, then plugin is registered in conf
# in _instantiate_spyder5_plugin() so no need to go so here
plugin._register(omit_conf=plugin.CONF_FILE)

def get_plugin(self, plugin_name, error=True):
if plugin_name in PLUGIN_REGISTRY:
return PLUGIN_REGISTRY.get_plugin(plugin_name)
return None

setattr(self._main, 'register_plugin',
types.MethodType(register_plugin, self._main))
setattr(self._main, 'get_plugin',
types.MethodType(get_plugin, self._main))

PLUGIN_REGISTRY.reset()
PLUGIN_REGISTRY.sig_plugin_ready.connect(self._main.register_plugin)
PLUGIN_REGISTRY.register_plugin(self._main, Preferences)

if plugins:
for Plugin in plugins:
if hasattr(Plugin, 'CONF_WIDGET_CLASS'):
for required in (Plugin.REQUIRES or []):
if required not in PLUGIN_REGISTRY:
PLUGIN_REGISTRY.plugin_registry[required] = MagicMock()

PLUGIN_REGISTRY.register_plugin(self._main, Plugin)
else:
plugin = Plugin(self._main)
preferences = self._main.get_plugin(Plugins.Preferences)
preferences.register_plugin_preferences(plugin)


@pytest.fixture
def config_dialog(qtbot, request, mocker):
mocker.patch.object(ima, 'icon', lambda x, *_: QIcon())
main_class, general_config_plugins, plugins = request.param

main_ref = ConfigDialogTester(
None, main_class, general_config_plugins, plugins)
qtbot.addWidget(main_ref)

preferences = main_ref._main.get_plugin(Plugins.Preferences)
preferences.open_dialog()
container = preferences.get_container()
dlg = container.dialog

yield dlg

dlg.close()
39 changes: 21 additions & 18 deletions spyder_notebook/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@
# Licensed under the terms of the MIT License
#

"""Tests for config.py"""
"""Tests for plugin config dialog."""

# Third-party library imports
import pytest
from unittest.mock import MagicMock

# Spyder imports
from spyder.plugins.preferences.widgets.configdialog import ConfigDialog
# Test library imports
import pytest
from qtpy.QtWidgets import QMainWindow

# Local imports
from spyder_notebook.confpage import NotebookConfigPage
from spyder_notebook.tests.test_plugin import plugin_no_server
from spyder_notebook.notebookplugin import NotebookPlugin


class MainWindowMock(QMainWindow):
register_shortcut = MagicMock()
editor = MagicMock()

def test_config(plugin_no_server, qtbot):
"""Test that config page can be created and shown."""
dlg = ConfigDialog()
page = NotebookConfigPage(plugin_no_server, parent=plugin_no_server.main)
page.initialize()
dlg.add_page(page)
dlg.show()
qtbot.addWidget(dlg)
# no assert, just check that the config page can be created
def __getattr__(self, attr):
return MagicMock()


if __name__ == "__main__":
pytest.main()
@pytest.mark.parametrize(
'config_dialog',
# [[MainWindowMock, [ConfigPlugins], [Plugins]]]
[[MainWindowMock, [], [NotebookPlugin]]],
indirect=True)
def test_config_dialog(config_dialog):
configpage = config_dialog.get_page()
assert configpage
configpage.save_to_conf()
29 changes: 0 additions & 29 deletions spyder_notebook/tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,35 +119,6 @@ def fake_get_server(filename, interpreter, start):
# =============================================================================
# Tests
# =============================================================================
# Teardown sometimes fails on Mac with Python 3.8 due to NoProcessException
# in shutdown_server() in notebookapp.py in external notebook library
@flaky
def test_open_console_when_no_kernel(notebook, qtbot, mocker):
"""Test that open_console() handles the case when there is no kernel."""
# Create mock IPython console plugin and QMessageBox
MockMessageBox = mocker.patch(
'spyder_notebook.widgets.main_widget.QMessageBox')

# Wait for prompt
nbwidget = notebook.get_widget().tabwidget.currentWidget().notebookwidget
qtbot.waitUntil(lambda: prompt_present(nbwidget, qtbot),
timeout=NOTEBOOK_UP)

# Shut the kernel down and check that this is successful
client = notebook.get_widget().tabwidget.currentWidget()
kernel_id = client.get_kernel_id()
sessions_url = client.get_session_url()
client.shutdown_kernel()
assert not is_kernel_up(kernel_id, sessions_url)

# Try opening a console
notebook.get_widget().open_console(client)

# Assert that a dialog is displayed and no console was opened
MockMessageBox.critical.assert_called()
ipyconsole = notebook.get_plugin(Plugins.IPythonConsole)
ipyconsole._create_client_for_kernel.assert_not_called()


def test_on_mainwindow_visible_with_opened_notebooks(plugin_no_server):
"""
Expand Down
Loading

0 comments on commit 2268a81

Please sign in to comment.