Skip to content

Commit

Permalink
Merge pull request spyder-ide#23118 from procitec/qt6_ci
Browse files Browse the repository at this point in the history
PR: Add workflow to run tests with PyQt6 (CI)
  • Loading branch information
ccordoba12 authored Feb 18, 2025
2 parents 09b4392 + 5d0941c commit d9b31d6
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 52 deletions.
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
179 changes: 179 additions & 0 deletions .github/workflows/test-linux-qt6.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Linux tests with PyQt6/PySide6

on:
push:
branches:
- master
- 6.*
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'

pull_request:
branches:
- master
- 6.*
- 5.*
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: 0
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 }}
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 }}
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
- name: Coverage
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: false
verbose: true
11 changes: 11 additions & 0 deletions runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
# Don't remove it or change it to a different location!
# pylint: disable=wrong-import-position
from qtpy import QtWebEngineWidgets # noqa

from qtpy.QtCore import QThread
import pytest


Expand Down Expand Up @@ -59,6 +61,15 @@ def run_pytest(run_slow=False, extra_args=None, remoteclient=False):
print("Pytest Arguments: " + str(pytest_args))
errno = pytest.main(pytest_args)

# Disconnect all signal-slots connection of the main QThread just before
# runtests.py exits. This prevents a SEGFAULT when the finished signal of
# the main QThread is triggered. This is an issue in the PyQt6 bindings
# (PySide6 is also affected).
try:
QThread.currentThread().disconnect()
except TypeError: # raised when no signals are connected
pass

# sys.exit doesn't work here because some things could be running in the
# background (e.g. closing the main window) when this point is reached.
# If that's the case, sys.exit doesn't stop the script as you would expect.
Expand Down
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 @@ -329,7 +333,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 @@ -344,6 +347,7 @@ def run(self):
'pytest-order',
'pytest-qt',
'pytest-timeout',
'pywin32;platform_system=="Windows"',
'pyyaml',
'scipy',
'sympy',
Expand Down
13 changes: 12 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 (
Expand Down Expand Up @@ -434,6 +434,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 @@ -1166,6 +1167,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 @@ -1428,6 +1430,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 @@ -1788,6 +1791,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 @@ -3628,6 +3632,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 @@ -3695,6 +3700,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 @@ -4432,6 +4438,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 @@ -5639,6 +5646,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 @@ -5788,6 +5796,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 @@ -5866,6 +5875,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 @@ -6755,6 +6765,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 @@ -4760,8 +4760,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
Loading

0 comments on commit d9b31d6

Please sign in to comment.