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: CI: Add workflow to run tests with PyQt6/PySide6 #23118

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 5 additions & 1 deletion .github/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ fi

# Install dependencies
if [ "$USE_CONDA" = "true" ]; then
if [ -n "$SPYDER_QT_BINDING" ]; then
# conda has no PyQt6 package
echo "Cannot use Qt 6 with Conda" 1>&2
exit 1
fi

# Install dependencies per operating system
if [ "$OS" = "win" ]; then
Expand Down Expand Up @@ -69,7 +74,6 @@ else
if [ "$OS" = "linux" ]; then
pip install jedi==0.19.1
fi

fi

# Install subrepos from source
Expand Down
160 changes: 160 additions & 0 deletions .github/workflows/test-linux-qt6.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Linux tests with PyQt6/PySide6

on:
push:
branches:
- master
- qt6_ci # for testing only
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a cron-based trigger is probably more sensible for now?

The qt6_ci is for testing only and will be removed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a cron-based trigger is probably more sensible for now?

I don't think so. We should run the Qt6 slots as we do for other ones so we know when we break things for that version.

The qt6_ci is for testing only and will be removed

Ok, no problem.

paths:
- '.github/scripts/*.sh'
- '.github/workflows/*.yml'
- 'requirements/*.yml'
- 'MANIFEST.in'
- '**.bat'
- '**.py'
- '**.sh'
- '!installers-conda/**'
- '!.github/workflows/installers-conda.yml'
- '!.github/workflows/build-subrepos.yml'
- '!.github/workflows/purge-cache.yml'
- '!.github/scripts/installer_test.sh'

workflow_call:

workflow_dispatch:
inputs:
ssh:
# github_cli: gh workflow run test-linux.yml --ref <branch> -f ssh=true
description: 'Enable ssh debugging'
required: false
default: false
type: boolean

concurrency:
group: test-linux-${{ github.ref }}
cancel-in-progress: true

env:
ENABLE_SSH: ${{ github.event_name == 'workflow_dispatch' && inputs.ssh }}

jobs:
build:
# Use this to disable the workflow
# if: false
name: Linux - Py${{ matrix.PYTHON_VERSION }}, ${{ matrix.SPYDER_QT_BINDING }}, ${{ matrix.INSTALL_TYPE }}, ${{ matrix.TEST_TYPE }}
runs-on: ubuntu-20.04
env:
CI: 'true'
QTCONSOLE_TESTING: 'true'
CODECOV_TOKEN: "56731c25-9b1f-4340-8b58-35739bfbc52d"
OS: 'linux'
PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }}
RUN_SLOW: ${{ matrix.TEST_TYPE == 'slow' }}
USE_CONDA: ${{ matrix.INSTALL_TYPE == 'conda' }}
USE_GDB: 'false'
SPYDER_QT_BINDING: ${{ matrix.SPYDER_QT_BINDING }}
strategy:
fail-fast: false
matrix:
INSTALL_TYPE: ['pip'] # conda has no PyQt6 package
PYTHON_VERSION: ['3.10']
TEST_TYPE: ['fast', 'slow']
SPYDER_QT_BINDING: ['pyqt6'] # TODO add 'pyside6' once Spyder supports it
timeout-minutes: 90
steps:
- name: Setup Remote SSH Connection
if: env.ENABLE_SSH == 'true'
uses: mxschmitt/action-tmate@v3
timeout-minutes: 60
with:
detached: true
- name: Checkout Pull Requests
if: github.event_name == 'pull_request'
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout Push
if: github.event_name != 'pull_request'
uses: actions/checkout@v4
- name: Fetch branches
run: git fetch --prune --unshallow
- name: Install dependencies
shell: bash
run: |
sudo apt-get update --fix-missing
sudo apt-get install -qq pyqt5-dev-tools libxcb-xinerama0 libxcb-cursor0 xterm --fix-missing
- name: Cache conda
uses: actions/cache@v4
env:
# Increase this value to reset cache if requirements/*.txt has not changed
CACHE_NUMBER: 1
with:
path: ~/conda_pkgs_dir
key: ${{ runner.os }}-cacheconda-install${{ matrix.INSTALL_TYPE }}-${{ matrix.PYTHON_VERSION }}-${{ env.CACHE_NUMBER }}-${{ hashFiles('requirements/*.yml') }}
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-cachepip-install${{ matrix.INSTALL_TYPE }}-${{ env.CACHE_NUMBER }}-${{ hashFiles('setup.py') }}
- name: Create conda test environment
if: env.USE_CONDA == 'true'
uses: mamba-org/setup-micromamba@v1
with:
micromamba-version: '1.5.10-0'
environment-file: requirements/main.yml
environment-name: test
cache-downloads: true
create-args: python=${{ matrix.PYTHON_VERSION }}
- name: Create pip test environment
if: env.USE_CONDA != 'true'
uses: mamba-org/setup-micromamba@v1
with:
micromamba-version: '1.5.10-0'
environment-name: test
cache-downloads: true
create-args: python=${{ matrix.PYTHON_VERSION }}
condarc: |
channels:
- conda-forge
- name: Install additional dependencies
shell: bash -l {0}
run: bash -l .github/scripts/install.sh
- name: Show conda test environment
if: env.USE_CONDA == 'true'
shell: bash -l {0}
run: |
micromamba info
micromamba list
- name: Show pip test environment
if: env.USE_CONDA != 'true'
shell: bash -l {0}
run: |
micromamba info
micromamba list
pip list
- name: Run manifest checks
shell: bash -l {0}
run: check-manifest
- name: Run tests with gdb
if: env.USE_GDB == 'true'
shell: bash -l {0}
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Comment on lines +141 to +142
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing only, will be re-added

run: xvfb-run --auto-servernum gdb -return-child-result -batch -ex r -ex py-bt --args python runtests.py -s
- name: Run tests
shell: bash -l {0}
env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testing only, will be re-added

QT_API: ${{ matrix.SPYDER_QT_BINDING }}
PYTEST_QT_API: ${{ matrix.SPYDER_QT_BINDING }}
run: |
rm -f pytest_log.txt # Must remove any log file from a previous run
# .github/scripts/run_tests.sh || \
# .github/scripts/run_tests.sh || \
# .github/scripts/run_tests.sh || \
.github/scripts/run_tests.sh
Comment on lines +152 to +155
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out for faster testing.

- name: Coverage
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: false
verbose: true
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ def run(self):
'pyqt6-webengine>=6.5,<7',
'qtconsole>=5.6.1,<5.7.0',
],
'pyside6': [
'pyside6>=6.5,<7',
'qtconsole>=5.6.1,<5.7.0',
],
'conda-forge': [
'qtconsole>=5.6.1,<5.7.0',
]
Expand Down Expand Up @@ -328,7 +332,6 @@ def run(self):
install_requires.append('qtconsole>=5.5.1,<5.7.0')

extras_require = {
'test:platform_system == "Windows"': ['pywin32'],
'test': [
'coverage',
'cython',
Expand All @@ -343,6 +346,7 @@ def run(self):
'pytest-order',
'pytest-qt',
'pytest-timeout',
'pywin32;platform_system=="Windows"',
'pyyaml',
'scipy',
'sympy',
Expand Down
14 changes: 13 additions & 1 deletion spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from packaging.version import parse
import pylint
import pytest
from qtpy import PYQT_VERSION, PYQT5
from qtpy import PYQT_VERSION, PYQT5, PYQT6
from qtpy.QtCore import QPoint, Qt, QTimer, QUrl
from qtpy.QtGui import QImage, QTextCursor
from qtpy.QtWidgets import QAction, QApplication, QInputDialog, QWidget
Expand Down Expand Up @@ -423,6 +423,7 @@ def test_get_help_combo(main_window, qtbot):


@pytest.mark.known_leak # Opens Spyder/QtWebEngine/Default/Cookies
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_get_help_ipython_console_dot_notation(main_window, qtbot, tmpdir):
"""
Test that Help works when called from the IPython console
Expand Down Expand Up @@ -788,6 +789,7 @@ def test_runconfig_workdir(main_window, qtbot, tmpdir):
@pytest.mark.no_new_console
@flaky(max_runs=3)
@pytest.mark.skipif(sys.platform == 'darwin', reason='Hangs sometimes on Mac')
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_dedicated_consoles(main_window, qtbot):
"""Test running code in dedicated consoles."""

Expand Down Expand Up @@ -1153,6 +1155,7 @@ def test_change_cwd_explorer(main_window, qtbot, tmpdir, test_directory):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.skipif(
(os.name == 'nt' or sys.platform == 'darwin' or
parse(ipy_release.version) == parse('7.11.0')),
Expand Down Expand Up @@ -1415,6 +1418,7 @@ def test_set_new_breakpoints(main_window, qtbot):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.order(after="test_debug_unsaved_function")
def test_run_code(main_window, qtbot, tmpdir):
"""Test all the different ways we have to run code"""
Expand Down Expand Up @@ -1750,6 +1754,7 @@ def test_close_when_file_is_changed(main_window, qtbot):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_maximize_minimize_plugins(main_window, qtbot):
"""Test that the maximize button is working as expected."""
# Wait until the window is fully up
Expand Down Expand Up @@ -3589,6 +3594,7 @@ def test_runcell_leading_indent(main_window, qtbot, tmpdir):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.order(after="test_debug_unsaved_function")
def test_varexp_rename(main_window, qtbot, tmpdir):
"""
Expand Down Expand Up @@ -3656,6 +3662,7 @@ def data(cm, i, j):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.order(after="test_debug_unsaved_function")
def test_varexp_remove(main_window, qtbot, tmpdir):
"""
Expand Down Expand Up @@ -4393,6 +4400,7 @@ def test_post_mortem(main_window, qtbot, tmpdir):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.order(after="test_debug_unsaved_function")
def test_run_unsaved_file_multiprocessing(main_window, qtbot):
"""Test that we can run an unsaved file with multiprocessing."""
Expand Down Expand Up @@ -5600,6 +5608,7 @@ def test_func():


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.skipif(
os.name == 'nt',
reason="ctypes.string_at(0) doesn't segfaults on Windows")
Expand Down Expand Up @@ -5749,6 +5758,7 @@ def test_history_from_ipyconsole(main_window, qtbot):
assert text.splitlines()[-1] == code


@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_debug_unsaved_function(main_window, qtbot):
"""
Test that a breakpoint in an unsaved file is reached.
Expand Down Expand Up @@ -5827,6 +5837,7 @@ def test_out_runfile_runcell(main_window, qtbot):


@flaky(max_runs=3)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.skipif(
not sys.platform.startswith('linux'),
reason="Does not work on Mac and Windows")
Expand Down Expand Up @@ -6716,6 +6727,7 @@ def test_runfile_namespace(main_window, qtbot, tmpdir):


@pytest.mark.skipif(os.name == "nt", reason="No quotes on Windows file paths")
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_quotes_rename_ipy(main_window, qtbot, tmp_path):
"""
Test that we can run files with quotes in name, renamed files,
Expand Down
7 changes: 5 additions & 2 deletions spyder/plugins/editor/widgets/codeeditor/codeeditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4585,8 +4585,11 @@ def delayed_popup_docstring(self):
line_text = self.textCursor().block().text()
pos = self.textCursor().position()

timer = QTimer()
timer.singleShot(300, lambda: self.popup_docstring(line_text, pos))
timer = QTimer(self)
timer.setInterval(300)
timer.setSingleShot(True)
timer.timeout.connect(lambda: self.popup_docstring(line_text, pos))
timer.start()

def set_current_project_path(self, root_path=None):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from unittest.mock import MagicMock

# Third party imports
from qtpy import QT_VERSION
from qtpy import QT_VERSION, PYQT6
from qtpy.QtCore import Qt, QEvent, QPointF
from qtpy.QtGui import QTextCursor, QMouseEvent
from qtpy.QtWidgets import QApplication, QMainWindow, QTextEdit
Expand Down Expand Up @@ -438,6 +438,7 @@ def test_editor_delete_selection(codeeditor, qtbot):

@pytest.mark.skipif(QT_VERSION.startswith('5.15'),
reason='Fixed on Qt 5.15')
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_qtbug35861(qtbot):
"""This test will detect if upstream QTBUG-35861 is fixed.
If that happens, then the workarounds for spyder-ide/spyder#12663
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from flaky import flaky
import pytest
from qtpy import PYQT6
from qtpy.QtCore import Qt
from qtpy.QtGui import QFont, QTextCursor

Expand Down Expand Up @@ -93,6 +94,7 @@ def test_decorations(codeeditor, qtbot):


@flaky(max_runs=10)
@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_update_decorations_when_scrolling(qtbot):
"""
Test how many calls we're doing to update decorations when
Expand Down
3 changes: 3 additions & 0 deletions spyder/plugins/editor/widgets/codeeditor/tests/test_goto.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import tempfile

# Third party imports
from qtpy import PYQT6
from qtpy.QtCore import Qt, QPoint, QTimer
from qtpy.QtGui import QDesktopServices, QTextCursor
from qtpy.QtWidgets import QMessageBox
Expand All @@ -28,6 +29,7 @@
TEST_FILE_REL = 'conftest.py'


@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
@pytest.mark.parametrize('params', [
# Parameter, expected output 1, full file path, expected output 2
# ----------------------------------------------------------------
Expand Down Expand Up @@ -118,6 +120,7 @@ def test_goto_uri(qtbot, codeeditor, mocker, params):
assert expected_output_2 == output_2


@pytest.mark.skipif(PYQT6, reason="Fails with PyQt6")
def test_goto_uri_project_root_path(qtbot, codeeditor, mocker, tmpdir):
"""Test that the uri search is working correctly."""
code_editor = codeeditor
Expand Down
Loading
Loading