Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pytest-qt for openSUSE:Factory checked in at 2022-07-05 12:09:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytest-qt (Old) and /work/SRC/openSUSE:Factory/.python-pytest-qt.new.1548 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest-qt" Tue Jul 5 12:09:49 2022 rev:9 rq:986733 version:4.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytest-qt/python-pytest-qt.changes 2021-07-13 22:37:58.933864215 +0200 +++ /work/SRC/openSUSE:Factory/.python-pytest-qt.new.1548/python-pytest-qt.changes 2022-07-05 12:10:24.388636554 +0200 @@ -1,0 +2,25 @@ +Fri Jul 1 19:06:12 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Update to version 4.1.0 + * pytest-qt now requires Python 3.7+. + * Improved PEP-8 aliases definition so they have a smaller call + stack depth by one and better parameter suggestions in IDEs. + (#383). Thanks @luziferius for the PR. + * Updated model tester handling around hasChildren based on Qt's + updates. + * New qapp_cls fixture returning the QApplication class to use, + thus making it easier to use a custom subclass without having + to override the whole qapp fixture. Thanks @The-Compiler for + the PR. + * Updated model tester to track/verify in-flight changes based on + Qt's updates. Thanks @The-Compiler for the PR. + * New qtbot.screenshot() method which can be used to take a + screenshot of the given widget. Thanks @The-Compiler for the + PR. + +------------------------------------------------------------------- +Thu Apr 21 13:13:18 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Enable pyside6 test flavor + +------------------------------------------------------------------- Old: ---- pytest-qt-4.0.2.tar.gz New: ---- pytest-qt-4.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytest-qt.spec ++++++ --- /var/tmp/diff_new_pack.BKnTmg/_old 2022-07-05 12:10:24.788637129 +0200 +++ /var/tmp/diff_new_pack.BKnTmg/_new 2022-07-05 12:10:24.796637140 +0200 @@ -1,7 +1,7 @@ # # spec file # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -65,13 +65,14 @@ %endif Name: python-pytest-qt%{psuffix} -Version: 4.0.2 +Version: 4.1.0 Release: 0 Summary: Pytest support for PyQt and PySide applications License: MIT Group: Development/Languages/Python URL: https://github.com/pytest-dev/pytest-qt Source: https://files.pythonhosted.org/packages/source/p/pytest-qt/pytest-qt-%{version}.tar.gz +BuildRequires: %{python_module base >= 3.7} BuildRequires: %{python_module setuptools_scm} BuildRequires: %{python_module setuptools} BuildRequires: dos2unix @@ -91,9 +92,8 @@ Requires: (python-qt5 or python-PyQt6) %endif %if %{with test} -# https://github.com/pytest-dev/pytest-qt/issues/376 -BuildRequires: %{python_module pytest >= 4.5} BuildRequires: %{python_module pytest-qt = %{version}} +BuildRequires: %{python_module pytest} %endif %python_subpackages ++++++ _multibuild ++++++ --- /var/tmp/diff_new_pack.BKnTmg/_old 2022-07-05 12:10:24.836637198 +0200 +++ /var/tmp/diff_new_pack.BKnTmg/_new 2022-07-05 12:10:24.840637204 +0200 @@ -2,6 +2,6 @@ <flavor>test-pyqt5</flavor> <flavor>test-pyqt6</flavor> <flavor>test-pyside2</flavor> - <!-- flavor>test-pyside6</flavor --> + <flavor>test-pyside6</flavor> </multibuild> ++++++ pytest-qt-4.0.2.tar.gz -> pytest-qt-4.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/.github/workflows/main.yml new/pytest-qt-4.1.0/.github/workflows/main.yml --- old/pytest-qt-4.0.2/.github/workflows/main.yml 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/.github/workflows/main.yml 2022-06-23 16:39:11.000000000 +0200 @@ -10,23 +10,39 @@ fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] qt-lib: [pyqt5, pyqt6, pyside2, pyside6] os: [ubuntu-20.04, windows-latest, macos-latest] include: - - python-version: "3.6" - tox-env: "py36" - python-version: "3.7" tox-env: "py37" - python-version: "3.8" tox-env: "py38" - python-version: "3.9" tox-env: "py39" + - python-version: "3.10" + tox-env: "py310" + - python-version: "3.11-dev" + tox-env: "py311" + # https://bugreports.qt.io/browse/PYSIDE-1797 + exclude: + - qt-lib: pyside6 + os: macos-latest + python-version: "3.7" + - qt-lib: pyside6 + os: ubuntu-20.04 + python-version: "3.7" + # Not installable so far + - qt-lib: pyside6 + python-version: "3.11-dev" + - qt-lib: pyside2 + os: windows-latest + python-version: "3.11-dev" steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -48,9 +64,9 @@ runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: "3.7" - name: Install tox @@ -73,9 +89,9 @@ needs: [build, checks] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v3 with: python-version: "3.7" - name: Build package @@ -85,7 +101,7 @@ python setup.py sdist bdist_wheel - name: Publish package to PyPI if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@v1.5.0 with: user: __token__ password: ${{ secrets.pypi_token }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/.pre-commit-config.yaml new/pytest-qt-4.1.0/.pre-commit-config.yaml --- old/pytest-qt-4.0.2/.pre-commit-config.yaml 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/.pre-commit-config.yaml 2022-06-23 16:39:11.000000000 +0200 @@ -1,33 +1,33 @@ repos: - repo: https://github.com/psf/black - rev: 21.5b2 + rev: 22.3.0 hooks: - id: black args: [--safe, --quiet] language_version: python3 - repo: https://github.com/asottile/blacken-docs - rev: v1.10.0 + rev: v1.12.1 hooks: - id: blacken-docs additional_dependencies: [black==20.8b1] language_version: python3 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: debug-statements - repo: https://github.com/asottile/pyupgrade - rev: v2.19.1 + rev: v2.32.1 hooks: - id: pyupgrade - repo: https://github.com/PyCQA/flake8 - rev: 3.9.2 + rev: 4.0.1 hooks: - id: flake8 - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.8.0 + rev: v1.9.0 hooks: - id: rst-backticks - repo: local diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/CHANGELOG.rst new/pytest-qt-4.1.0/CHANGELOG.rst --- old/pytest-qt-4.0.2/CHANGELOG.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/CHANGELOG.rst 2022-06-23 16:39:11.000000000 +0200 @@ -1,3 +1,23 @@ +UNRELEASED +---------- + +4.1.0 (2022-06-23) +------------------ + +- ``pytest-qt`` now requires Python 3.7+. +- Improved PEP-8 aliases definition so they have a smaller call stack depth by one and better parameter suggestions in IDEs. (`#383`_). Thanks `@luziferius`_ for the PR. +- Updated model tester handling around ``hasChildren`` based on Qt's updates. +- New ``qapp_cls`` fixture returning the ``QApplication`` class to use, thus + making it easier to use a custom subclass without having to override the + whole ``qapp`` fixture. Thanks `@The-Compiler`_ for the PR. +- Updated model tester to track/verify in-flight changes based on Qt's updates. + Thanks `@The-Compiler`_ for the PR. +- New ``qtbot.screenshot()`` method which can be used to take a screenshot of + the given widget. Thanks `@The-Compiler`_ for the PR. + +.. _#383: https://github.com/pytest-dev/pytest-qt/pull/383 +.. _@luziferius: https://github.com/luziferius + 4.0.2 (2021-06-14) ------------------ @@ -78,7 +98,7 @@ pass ``timeout=1000`` to those functions (`#306`_). Thanks `@The-Compiler`_ for the PR. - ``waitUntil`` now raises a ``TimeoutError`` when a timeout occurs to make the - cause of the timeout more explict (`#222`_). Thanks `@karlch`_ for the PR. + cause of the timeout more explicit (`#222`_). Thanks `@karlch`_ for the PR. - The ``QtTest::keySequence`` method is now exposed (if available, with Qt >= 5.10) (`#289`_). Thanks `@The-Compiler`_ for the PR. - ``addWidget`` now enforces that its argument is a ``QWidget`` in order to @@ -534,7 +554,7 @@ when tests fail, similar to `pytest-catchlog`_. Also, tests can be configured to automatically fail if an unexpected message is generated. -- New method ``waitSignals``: will block untill **all** signals given are +- New method ``waitSignals``: will block until **all** signals given are triggered (thanks `@The-Compiler`_ for idea and complete PR). - New parameter ``raising`` to ``waitSignals`` and ``waitSignals``: when ``True`` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/PKG-INFO new/pytest-qt-4.1.0/PKG-INFO --- old/pytest-qt-4.0.2/PKG-INFO 2021-06-14 00:54:52.000000000 +0200 +++ new/pytest-qt-4.1.0/PKG-INFO 2022-06-23 16:39:27.000000000 +0200 @@ -1,28 +1,27 @@ Metadata-Version: 2.1 Name: pytest-qt -Version: 4.0.2 +Version: 4.1.0 Summary: pytest support for PyQt and PySide applications Home-page: http://github.com/pytest-dev/pytest-qt Author: Bruno Oliveira Author-email: nicodde...@gmail.com License: MIT Keywords: pytest qt test unittest -Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Desktop Environment :: Window Managers Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: User Interfaces -Requires-Python: >=3.6 +Requires-Python: >=3.7 Provides-Extra: doc Provides-Extra: dev License-File: LICENSE @@ -32,7 +31,7 @@ ========= pytest-qt is a `pytest`_ plugin that allows programmers to write tests -for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications. +for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications. The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp`` creation as needed and provides methods to simulate user interaction, @@ -103,7 +102,7 @@ Requirements ============ -Since version 4.0.0, ``pytest-qt`` requires Python 3.6+. +Since version 4.1.0, ``pytest-qt`` requires Python 3.7+. Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever is available on the system, giving preference to the first one installed in @@ -115,7 +114,7 @@ - ``PyQt5`` To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to -``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``: +``pyqt5``, ``pyside2``, or ``pyqt6``: .. code-block:: ini @@ -207,8 +206,9 @@ **Powered by** -.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png +.. |pycharm| image:: https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png :target: https://www.jetbrains.com/pycharm + :width: 400 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png :target: https://www.pydev.org @@ -218,5 +218,3 @@ |pydev| .. _tox: https://tox.readthedocs.io - - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/README.rst new/pytest-qt-4.1.0/README.rst --- old/pytest-qt-4.0.2/README.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/README.rst 2022-06-23 16:39:11.000000000 +0200 @@ -3,7 +3,7 @@ ========= pytest-qt is a `pytest`_ plugin that allows programmers to write tests -for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications. +for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications. The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp`` creation as needed and provides methods to simulate user interaction, @@ -74,7 +74,7 @@ Requirements ============ -Since version 4.0.0, ``pytest-qt`` requires Python 3.6+. +Since version 4.1.0, ``pytest-qt`` requires Python 3.7+. Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever is available on the system, giving preference to the first one installed in @@ -86,7 +86,7 @@ - ``PyQt5`` To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to -``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``: +``pyqt5``, ``pyside2``, or ``pyqt6``: .. code-block:: ini @@ -178,8 +178,9 @@ **Powered by** -.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png +.. |pycharm| image:: https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png :target: https://www.jetbrains.com/pycharm + :width: 400 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png :target: https://www.pydev.org Binary files old/pytest-qt-4.0.2/docs/_static/button.png and new/pytest-qt-4.1.0/docs/_static/button.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/conf.py new/pytest-qt-4.1.0/docs/conf.py --- old/pytest-qt-4.0.2/docs/conf.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/conf.py 2022-06-23 16:39:11.000000000 +0200 @@ -41,8 +41,8 @@ master_doc = "index" # General information about the project. -project = u"pytest-qt" -copyright = u"2013, Bruno Oliveira" +project = "pytest-qt" +copyright = "2013, Bruno Oliveira" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -180,7 +180,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "pytest-qt.tex", u"pytest-qt Documentation", u"Bruno Oliveira", "manual") + ("index", "pytest-qt.tex", "pytest-qt Documentation", "Bruno Oliveira", "manual") ] # The name of an image file (relative to this directory) to place at the top of @@ -208,7 +208,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "pytest-qt", u"pytest-qt Documentation", [u"Bruno Oliveira"], 1)] +man_pages = [("index", "pytest-qt", "pytest-qt Documentation", ["Bruno Oliveira"], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -223,8 +223,8 @@ ( "index", "pytest-qt", - u"pytest-qt Documentation", - u"Bruno Oliveira", + "pytest-qt Documentation", + "Bruno Oliveira", "pytest-qt", "One line description of project.", "Miscellaneous", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/debugging.rst new/pytest-qt-4.1.0/docs/debugging.rst --- old/pytest-qt-4.0.2/docs/debugging.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-qt-4.1.0/docs/debugging.rst 2022-06-23 16:39:11.000000000 +0200 @@ -0,0 +1,54 @@ +Debugging failing tests +======================= + +When a GUI-related test fails, it can sometimes be hard to find out where the culprit lies. To aid +with debugging such tests, the ``qtbot`` fixture allows to stop the current +test and to take screenshots of widgets. + +Stopping the current test +------------------------- + +By calling :meth:`pytestqt.qtbot.QtBot.stop`, the current test gets +interrupted. After closing all visible windows, ``qtbot`` attempts to restore +the previous state and the test continues to run. + +.. note:: + + If you use Xvfb or the ``offscreen`` platform plugin (e.g. via + ``QT_QPA_PLATFORM=offscreen``), remember to disable those to see the windows. With the + `pytest-xvfb <https://github.com/The-Compiler/pytest-xvfb/>`_ plugin, this + is possible by passing ``--no-xvfb`` to pytest. + +Taking screenshots +------------------ + +.. versionadded:: 4.1 + +Via :meth:`pytestqt.qtbot.QtBot.screenshot`, a screenshot of a given widget +can be taken. That screenshot is then saved into a temporary directory provided +by pytest. For example, this test: + +.. code:: python + + from pytestqt.qt_compat import qt_api + + + def test_screenshot(qtbot): + button = qt_api.QtWidgets.QPushButton() + button.setText("Hello World!") + qtbot.add_widget(button) + path = qtbot.screenshot(button) + assert False, path # show the path and fail the test + +would result in the following file at a location like +``/tmp/pytest-of-USER/pytest-N/test_screenshot0/screenshot_QPushButton.png``: + +.. image:: _static/button.png + +The filename is generated based on the following parts: + +* ``screenshot`` +* The class of the widget (e.g. ``QWidget`` or ``QPushButton``) +* The widget's ``objectName()``, if set +* The given ``suffix``, if passed +* A counter to make the filename unique, if another screenshot already exists diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/index.rst new/pytest-qt-4.1.0/docs/index.rst --- old/pytest-qt-4.0.2/docs/index.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/index.rst 2022-06-23 16:39:11.000000000 +0200 @@ -20,6 +20,7 @@ modeltester qapplication note_dialogs + debugging troubleshooting reference changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/intro.rst new/pytest-qt-4.1.0/docs/intro.rst --- old/pytest-qt-4.0.2/docs/intro.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/intro.rst 2022-06-23 16:39:11.000000000 +0200 @@ -74,7 +74,7 @@ Requirements ============ -Since version 4.0.0, ``pytest-qt`` requires Python 3.6+. +``pytest-qt`` requires Python 3.7+. Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever is available on the system, giving preference to the first one installed in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/qapplication.rst new/pytest-qt-4.1.0/docs/qapplication.rst --- old/pytest-qt-4.0.2/docs/qapplication.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/qapplication.rst 2022-06-23 16:39:11.000000000 +0200 @@ -64,13 +64,16 @@ If your tests require access to app-level functions, like ``CustomQApplication.custom_function()``, you can override the built-in -``qapp`` fixture in your ``conftest.py`` to use your own app: +``qapp_cls`` fixture in your ``conftest.py`` to return your custom class: .. code-block:: python @pytest.fixture(scope="session") - def qapp(): - yield CustomQApplication([]) + def qapp_cls(): + return CustomQApplication + +The ``qapp`` fixture will then use the returned class instead of the default +``QApplication`` from ``QtWidgets``. Setting a QApplication name --------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/troubleshooting.rst new/pytest-qt-4.1.0/docs/troubleshooting.rst --- old/pytest-qt-4.0.2/docs/troubleshooting.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/troubleshooting.rst 2022-06-23 16:39:11.000000000 +0200 @@ -103,3 +103,19 @@ ~~~~~~~~~~~ Instead of running Xvfb manually it is possible to use ``pytest-xvfb`` plugin. + +Using with other Qt-related packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using Python's Qt modules (``PySide`` or ``PyQt5``) with other packages which +use Qt (e.g. ``cv2``) can result in conflicts. This is because the latter builds +their own Qt and modify Qt-related environment variables. This may not raise errors +in your local app, but running the tests on CI servers can fail. + +In this case, try use the package without Qt dependency. For example, if your +code does not rely on ``cv2``'s Qt feature you can use +``opencv-python-headless`` instead of full ``opencv-python``. + +More details can be found in `issue #396`_. + +.. _issue #396: https://github.com/pytest-dev/pytest-qt/issues/396 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/docs/wait_until.rst new/pytest-qt-4.1.0/docs/wait_until.rst --- old/pytest-qt-4.0.2/docs/wait_until.rst 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/docs/wait_until.rst 2022-06-23 16:39:11.000000000 +0200 @@ -59,7 +59,7 @@ E + Please input a number _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > qtbot.waitUntil(check_label) - E pytestqt.exceptions.TimeoutError: waitUntil timed out in 1000 miliseconds + E pytestqt.exceptions.TimeoutError: waitUntil timed out in 1000 milliseconds A second way to use ``qtbot.waitUntil`` is to pass a callback which returns ``True`` when the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/setup.py new/pytest-qt-4.1.0/setup.py --- old/pytest-qt-4.0.2/setup.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/setup.py 2022-06-23 16:39:11.000000000 +0200 @@ -23,7 +23,7 @@ url="http://github.com/pytest-dev/pytest-qt", use_scm_version={"write_to": "src/pytestqt/_version.py"}, setup_requires=["setuptools_scm"], - python_requires=">=3.6", + python_requires=">=3.7", classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Pytest", @@ -31,10 +31,10 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Desktop Environment :: Window Managers", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Testing", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytest_qt.egg-info/PKG-INFO new/pytest-qt-4.1.0/src/pytest_qt.egg-info/PKG-INFO --- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/PKG-INFO 2021-06-14 00:54:52.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/PKG-INFO 2022-06-23 16:39:26.000000000 +0200 @@ -1,28 +1,27 @@ Metadata-Version: 2.1 Name: pytest-qt -Version: 4.0.2 +Version: 4.1.0 Summary: pytest support for PyQt and PySide applications Home-page: http://github.com/pytest-dev/pytest-qt Author: Bruno Oliveira Author-email: nicodde...@gmail.com License: MIT Keywords: pytest qt test unittest -Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Pytest Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Desktop Environment :: Window Managers Classifier: Topic :: Software Development :: Quality Assurance Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: User Interfaces -Requires-Python: >=3.6 +Requires-Python: >=3.7 Provides-Extra: doc Provides-Extra: dev License-File: LICENSE @@ -32,7 +31,7 @@ ========= pytest-qt is a `pytest`_ plugin that allows programmers to write tests -for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications. +for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications. The main usage is to use the ``qtbot`` fixture, responsible for handling ``qApp`` creation as needed and provides methods to simulate user interaction, @@ -103,7 +102,7 @@ Requirements ============ -Since version 4.0.0, ``pytest-qt`` requires Python 3.6+. +Since version 4.1.0, ``pytest-qt`` requires Python 3.7+. Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever is available on the system, giving preference to the first one installed in @@ -115,7 +114,7 @@ - ``PyQt5`` To force a particular API, set the configuration variable ``qt_api`` in your ``pytest.ini`` file to -``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``: +``pyqt5``, ``pyside2``, or ``pyqt6``: .. code-block:: ini @@ -207,8 +206,9 @@ **Powered by** -.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png +.. |pycharm| image:: https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png :target: https://www.jetbrains.com/pycharm + :width: 400 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png :target: https://www.pydev.org @@ -218,5 +218,3 @@ |pydev| .. _tox: https://tox.readthedocs.io - - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytest_qt.egg-info/SOURCES.txt new/pytest-qt-4.1.0/src/pytest_qt.egg-info/SOURCES.txt --- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/SOURCES.txt 2021-06-14 00:54:52.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/SOURCES.txt 2022-06-23 16:39:27.000000000 +0200 @@ -17,6 +17,7 @@ docs/Makefile docs/changelog.rst docs/conf.py +docs/debugging.rst docs/index.rst docs/intro.rst docs/logging.rst @@ -31,6 +32,7 @@ docs/virtual_methods.rst docs/wait_callback.rst docs/wait_until.rst +docs/_static/button.png docs/_static/find_files_dialog.png src/pytest_qt.egg-info/PKG-INFO src/pytest_qt.egg-info/SOURCES.txt @@ -53,6 +55,8 @@ tests/test_exceptions.py tests/test_logging.py tests/test_modeltest.py +tests/test_qtbot_pep8_aliases.py tests/test_qtest_proxies.py +tests/test_screenshot.py tests/test_wait_signal.py tests/test_wait_until.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytest_qt.egg-info/entry_points.txt new/pytest-qt-4.1.0/src/pytest_qt.egg-info/entry_points.txt --- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/entry_points.txt 2021-06-14 00:54:52.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/entry_points.txt 2022-06-23 16:39:26.000000000 +0200 @@ -1,3 +1,2 @@ [pytest11] pytest-qt = pytestqt.plugin - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/_version.py new/pytest-qt-4.1.0/src/pytestqt/_version.py --- old/pytest-qt-4.0.2/src/pytestqt/_version.py 2021-06-14 00:54:52.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/_version.py 2022-06-23 16:39:25.000000000 +0200 @@ -1,5 +1,5 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '4.0.2' -version_tuple = (4, 0, 2) +__version__ = version = '4.1.0' +__version_tuple__ = version_tuple = (4, 1, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/exceptions.py new/pytest-qt-4.1.0/src/pytestqt/exceptions.py --- old/pytest-qt-4.0.2/src/pytestqt/exceptions.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/exceptions.py 2022-06-23 16:39:11.000000000 +0200 @@ -101,3 +101,14 @@ """ pass + + +class ScreenshotError(Exception): + """ + .. versionadded:: 4.1 + + Exception thrown by :method:`pytestqt.qtbot.QtBot.screenshot` if taking the + screenshot failed. + """ + + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/logging.py new/pytest-qt-4.1.0/src/pytestqt/logging.py --- old/pytest-qt-4.0.2/src/pytestqt/logging.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/logging.py 2022-06-23 16:39:11.000000000 +0200 @@ -10,7 +10,7 @@ class QtLoggingPlugin: """ - Pluging responsible for installing a QtMessageHandler before each + Plugin responsible for installing a QtMessageHandler before each test and augment reporting if the test failed with the messages captured. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/modeltest.py new/pytest-qt-4.1.0/src/pytestqt/modeltest.py --- old/pytest-qt-4.0.2/src/pytestqt/modeltest.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/modeltest.py 2022-06-23 16:39:11.000000000 +0200 @@ -1,6 +1,6 @@ # This file is based on the original C++ qabstractitemmodeltester.cpp from: # http://code.qt.io/cgit/qt/qtbase.git/tree/src/testlib/qabstractitemmodeltester.cpp -# Commit 4af292fe5158c2d19e8ab1351c71c3940c7f1032 +# Commit b6759ff81c1b6ecb7e18144db0b7c9c5884d7f24 # # Licensed under the following terms: # @@ -41,6 +41,7 @@ # # $QT_END_LICENSE$ +import enum import collections from pytestqt.qt_compat import qt_api @@ -52,6 +53,18 @@ HAS_QT_TESTER = hasattr(qt_api.QtTest, "QAbstractItemModelTester") +class _ChangeInFlight(enum.Enum): + + COLUMNS_INSERTED = enum.auto() + COLUMNS_MOVED = enum.auto() + COLUMNS_REMOVED = enum.auto() + LAYOUT_CHANGED = enum.auto() + MODEL_RESET = enum.auto() + ROWS_INSERTED = enum.auto() + ROWS_MOVED = enum.auto() + ROWS_REMOVED = enum.auto() + + class ModelTester: """A tester for Qt's QAbstractItemModels.""" @@ -63,6 +76,7 @@ self._remove = None self._changing = [] self._qt_tester = None + self._change_in_flight = None def _debug(self, text): print("modeltest: " + text) @@ -131,10 +145,32 @@ # Special checks for changes self._model.layoutAboutToBeChanged.connect(self._on_layout_about_to_be_changed) self._model.layoutChanged.connect(self._on_layout_changed) + + # column operations + self._model.columnsAboutToBeInserted.connect( + self._on_columns_about_to_be_inserted + ) + self._model.columnsAboutToBeMoved.connect(self._on_columns_about_to_be_moved) + self._model.columnsAboutToBeRemoved.connect( + self._on_columns_about_to_be_removed + ) + self._model.columnsInserted.connect(self._on_columns_inserted) + self._model.columnsMoved.connect(self._on_columns_moved) + self._model.columnsRemoved.connect(self._on_columns_removed) + + # row operations self._model.rowsAboutToBeInserted.connect(self._on_rows_about_to_be_inserted) + self._model.rowsAboutToBeMoved.connect(self._on_rows_about_to_be_moved) self._model.rowsAboutToBeRemoved.connect(self._on_rows_about_to_be_removed) self._model.rowsInserted.connect(self._on_rows_inserted) + self._model.rowsMoved.connect(self._on_rows_moved) self._model.rowsRemoved.connect(self._on_rows_removed) + + # reset + self._model.modelAboutToBeReset.connect(self._on_model_about_to_be_reset) + self._model.modelReset.connect(self._on_model_reset) + + # data self._model.dataChanged.connect(self._on_data_changed) self._model.headerDataChanged.connect(self._on_header_data_changed) @@ -293,7 +329,7 @@ # QModelIndex when asked for the parent of an invalid index. assert not self._parent(qt_api.QtCore.QModelIndex()).isValid() - if not self._has_children(): + if self._model.rowCount() == 0 or self._column_count() == 0: return # Column 0 | Column 1 | @@ -304,11 +340,12 @@ # Common error test #1, make sure that a top level index has a parent # that is a invalid QModelIndex. top_index = self._model.index(0, 0, qt_api.QtCore.QModelIndex()) + assert top_index.isValid() assert not self._parent(top_index).isValid() # Common error test #2, make sure that a second level index has a # parent that is the first level index. - if self._has_children(top_index): + if self._model.rowCount(top_index) > 0 and self._column_count(top_index) > 0: child_index = self._model.index(0, 0, top_index) assert self._parent(child_index) == top_index @@ -317,7 +354,10 @@ # Usually the second column shouldn't have children. if self._model.hasIndex(0, 1): top_index_1 = self._model.index(0, 1, qt_api.QtCore.QModelIndex()) - if self._has_children(top_index) and self._has_children(top_index_1): + if ( + self._model.rowCount(top_index) > 0 + and self._model.rowCount(top_index_1) > 0 + ): child_index = self._model.index(0, 0, top_index) assert child_index.isValid() child_index_1 = self._model.index(0, 0, top_index_1) @@ -447,7 +487,7 @@ def _test_data(self): """Test model's implementation of data()""" - if not self._has_children(): + if self._model.rowCount() == 0 or self._column_count() == 0: return # A valid index should have a valid QVariant data @@ -511,11 +551,104 @@ qt_api.QtCore.Qt.CheckState.Checked, ] + def _on_columns_about_to_be_inserted(self, parent, start, end): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.COLUMNS_INSERTED + last_index = self._model.index(start - 1, 0, parent) + self._debug( + "columns about to be inserted: start {}, end {}, parent {}, " + "current count of parent {}, last before insertion {} {}".format( + start, + end, + self._modelindex_debug(parent), + self._model.rowCount(parent), + self._modelindex_debug(last_index), + self._model.data(last_index), + ) + ) + + def _on_columns_inserted(self, parent, start, end): + assert self._change_in_flight == _ChangeInFlight.COLUMNS_INSERTED + self._change_in_flight = None + self._debug( + "columns inserted: start {}, end {}, parent {}, " + "current count of parent {}, ".format( + start, + end, + self._modelindex_debug(parent), + self._model.rowCount(parent), + ) + ) + + def _on_columns_about_to_be_moved( + self, source_parent, source_start, source_end, dest_parent, dest_column + ): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.COLUMNS_MOVED + self._debug( + "columns about to be moved: source start {}, source end {}, " + "source parent {}, destination parent {}, " + "destination column {}".format( + source_start, + source_end, + self._modelindex_debug(source_parent), + self._modelindex_debug(dest_parent), + dest_column, + ) + ) + + def _on_columns_moved( + self, source_parent, source_start, source_end, dest_parent, dest_column + ): + assert self._change_in_flight == _ChangeInFlight.COLUMNS_MOVED + self._change_in_flight = None + self._debug( + "columns moved: source start {}, source end {}, " + "source parent {}, destination parent {}, " + "destination column {}".format( + source_start, + source_end, + self._modelindex_debug(source_parent), + self._modelindex_debug(dest_parent), + dest_column, + ) + ) + + def _on_columns_about_to_be_removed(self, parent, start, end): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.COLUMNS_REMOVED + last_index = self._model.index(start - 1, 0, parent) + self._debug( + "columns about to be removed: start {}, end {}, " + "parent {}, parent rowcount {}, last before removal {}".format( + start, + end, + self._modelindex_debug(parent), + self._model.rowCount(parent), + self._modelindex_debug(last_index), + ) + ) + + def _on_columns_removed(self, parent, start, end): + assert self._change_in_flight == _ChangeInFlight.COLUMNS_REMOVED + self._change_in_flight = None + self._debug( + "columns removed: start {}, end {}, parent {}, parent rowcount {}".format( + start, + end, + self._modelindex_debug(parent), + self._model.rowCount(parent), + ) + ) + def _on_rows_about_to_be_inserted(self, parent, start, end): """Store what is about to be inserted. This gets stored to make sure it actually happens in rowsInserted. """ + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.ROWS_INSERTED + last_index = self._model.index(start - 1, 0, parent) next_index = self._model.index(start, 0, parent) parent_rowcount = self._model.rowCount(parent) @@ -541,6 +674,9 @@ def _on_rows_inserted(self, parent, start, end): """Confirm that what was said was going to happen actually did.""" + assert self._change_in_flight == _ChangeInFlight.ROWS_INSERTED + self._change_in_flight = None + c = self._insert.pop() last_data = ( self._model.data(self._model.index(start - 1, 0, parent)) @@ -591,25 +727,82 @@ if next_data is not None: assert c.next == next_data + def _on_rows_about_to_be_moved( + self, source_parent, source_start, source_end, dest_parent, dest_row + ): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.ROWS_MOVED + self._debug( + "rows about to be moved: source start {}, source end {}, " + "source parent {}, destination parent {}, " + "destination row {}".format( + source_start, + source_end, + self._modelindex_debug(source_parent), + self._modelindex_debug(dest_parent), + dest_row, + ) + ) + + def _on_rows_moved( + self, source_parent, source_start, source_end, dest_parent, dest_row + ): + assert self._change_in_flight == _ChangeInFlight.ROWS_MOVED + self._change_in_flight = None + self._debug( + "rows moved: source start {}, source end {}, " + "source parent {}, destination parent {}, " + "destination row {}".format( + source_start, + source_end, + self._modelindex_debug(source_parent), + self._modelindex_debug(dest_parent), + dest_row, + ) + ) + def _on_layout_about_to_be_changed(self): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.LAYOUT_CHANGED + for i in range(max(self._model.rowCount(), 100)): idx = qt_api.QtCore.QPersistentModelIndex(self._model.index(i, 0)) self._changing.append(idx) def _on_layout_changed(self): + assert self._change_in_flight == _ChangeInFlight.LAYOUT_CHANGED + self._change_in_flight = None + for p in self._changing: assert p == self._model.index(p.row(), p.column(), p.parent()) self._changing = [] + def _on_model_about_to_be_reset(self): + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.MODEL_RESET + + def _on_model_reset(self): + assert self._change_in_flight == _ChangeInFlight.MODEL_RESET + self._change_in_flight = None + def _on_rows_about_to_be_removed(self, parent, start, end): """Store what is about to be removed to make sure it actually happens. This gets stored to make sure it actually happens in rowsRemoved. """ + assert self._change_in_flight is None + self._change_in_flight = _ChangeInFlight.ROWS_REMOVED + parent_rowcount = self._model.rowCount(parent) - last_index = self._model.index(start - 1, 0, parent) if start > 0 else None + last_index = ( + self._model.index(start - 1, 0, parent) + if start > 0 and self._column_count(parent) > 0 + else None + ) next_index = ( - self._model.index(end + 1, 0, parent) if end < parent_rowcount - 1 else None + self._model.index(end + 1, 0, parent) + if end < parent_rowcount - 1 and self._column_count(parent) > 0 + else None ) self._debug( @@ -638,6 +831,9 @@ def _on_rows_removed(self, parent, start, end): """Confirm that what was said was going to happen actually did.""" + assert self._change_in_flight == _ChangeInFlight.ROWS_REMOVED + self._change_in_flight = None + c = self._remove.pop() last_data = ( self._model.data(self._model.index(start - 1, 0, c.parent)) @@ -713,7 +909,7 @@ def _column_count(self, parent=qt_api.QtCore.QModelIndex()): """ Workaround for the fact that ``columnCount`` is a private method in - QAbstractListModel/QAbstractTableModel subclasses. + QAbstractListModel subclasses. """ if isinstance(self._model, qt_api.QtCore.QAbstractListModel): return 1 if parent == qt_api.QtCore.QModelIndex() else 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/plugin.py new/pytest-qt-4.1.0/src/pytestqt/plugin.py --- old/pytest-qt-4.0.2/src/pytestqt/plugin.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/plugin.py 2022-06-23 16:39:11.000000000 +0200 @@ -1,3 +1,5 @@ +import warnings + import pytest from pytestqt.exceptions import ( @@ -27,7 +29,30 @@ @pytest.fixture(scope="session") -def qapp(qapp_args, pytestconfig): +def qapp_cls(): + """ + Fixture that provides the QApplication subclass to use. + + You can override this fixture to use a custom QApplication subclass from + your application for tests: + + .. code-block:: python + + @pytest.fixture(scope="session") + def qapp_cls(): + return myapp.Application + + Or use a ``QCoreApplication`` if you want to test a non-gui Qt application: + + @pytest.fixture(scope="session") + def qapp_cls(): + return qt_api.QtCore.QCoreApplication + """ + return qt_api.QtWidgets.QApplication + + +@pytest.fixture(scope="session") +def qapp(qapp_args, qapp_cls, pytestconfig): """ Fixture that instantiates the QApplication instance that will be used by the tests. @@ -38,12 +63,17 @@ app = qt_api.QtWidgets.QApplication.instance() if app is None: global _qapp_instance - _qapp_instance = qt_api.QtWidgets.QApplication(qapp_args) + _qapp_instance = qapp_cls(qapp_args) name = pytestconfig.getini("qt_qapp_name") _qapp_instance.setApplicationName(name) return _qapp_instance else: - return app # pragma: no cover + if not isinstance(app, qapp_cls): + warnings.warn( + f"Existing QApplication {app} is not an instance of qapp_cls: " + f"{qapp_cls}" + ) + return app # holds a global QApplication instance created in the qapp fixture; keeping diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/qtbot.py new/pytest-qt-4.1.0/src/pytestqt/qtbot.py --- old/pytest-qt-4.0.2/src/pytestqt/qtbot.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/qtbot.py 2022-06-23 16:39:11.000000000 +0200 @@ -2,7 +2,7 @@ import weakref import warnings -from pytestqt.exceptions import TimeoutError +from pytestqt.exceptions import TimeoutError, ScreenshotError from pytestqt.qt_compat import qt_api from pytestqt.wait_signal import ( SignalBlocker, @@ -39,6 +39,7 @@ .. automethod:: waitExposed .. automethod:: waitForWindowShown .. automethod:: stop + .. automethod:: screenshot .. automethod:: wait **Signals and Events** @@ -88,7 +89,7 @@ .. staticmethod:: keyToAscii (key) - Auxiliary method that converts the given constant ot its equivalent ascii. + Auxiliary method that converts the given constant to its equivalent ascii. :param Qt.Key_* key: one of the constants for keys in the Qt namespace. @@ -138,6 +139,17 @@ def __init__(self, request): self._request = request + # pep8 aliases. Set here to automatically use implementations defined in sub-classes for alias creation + self.add_widget = self.addWidget + self.capture_exceptions = self.captureExceptions + self.wait_active = self.waitActive + self.wait_exposed = self.waitExposed + self.wait_for_window_shown = self.waitForWindowShown + self.wait_signal = self.waitSignal + self.wait_signals = self.waitSignals + self.assert_not_emitted = self.assertNotEmitted + self.wait_until = self.waitUntil + self.wait_callback = self.waitCallback def _should_raise(self, raising_arg): ini_val = self._request.config.getini("qt_default_raising") @@ -593,6 +605,56 @@ capture_exceptions = captureExceptions + def screenshot(self, widget, suffix="", region=None): + """ + .. versionadded:: 4.1 + + Take a screenshot of the given widget and save it. + + The file is saved in a test-specific directory using pytest's ``tmp_path`` + fixture. The filename is ensured to be unique using a counter, and contains the + ``objectName()`` of the widget if set, as well as its class name. A custom + ``suffix`` can be given to add to the generated name. + + :param QWidget widget: + The widget to take a screenshot of. + :param str suffix: + An optional suffix to add to the filename. + :param QRect region: + The region of the widget to screeshot. By default, the entire widget is + contained. + :returns: + A ``pathlib.Path`` object with the taken screenshot. + :raises ScreenshotError: if taking the screenshot or saving the file failed. + """ + pixmap = widget.grab() if region is None else widget.grab(region) + if pixmap.isNull(): + raise ScreenshotError("Got null pixmap from Qt") + + tmp_path = self._request.getfixturevalue("tmp_path") + + parts = ["screenshot", widget.__class__.__name__] + name = widget.objectName() + if name: + parts.append(name) + if suffix: + parts.append(suffix) + + for i in range(1, 500): + counter = [] if i == 1 else [str(i)] + + path = tmp_path / ("_".join(parts + counter) + ".png") + if path.exists(): + continue + + ok = pixmap.save(str(path)) + if not ok: + raise ScreenshotError(f"Saving to {path} failed") + + return path + + raise ScreenshotError(f"Failed to find unique filename, last try: {path}") + @staticmethod def keyClick(*args, **kwargs): qt_api.QtTest.QTest.keyClick(*args, **kwargs) @@ -645,35 +707,6 @@ def mouseRelease(*args, **kwargs): qt_api.QtTest.QTest.mouseRelease(*args, **kwargs) - # pep-8 aliases - - def add_widget(self, *args, **kwargs): - return self.addWidget(*args, **kwargs) - - def wait_active(self, *args, **kwargs): - return self.waitActive(*args, **kwargs) - - def wait_exposed(self, *args, **kwargs): - return self.waitExposed(*args, **kwargs) - - def wait_for_window_shown(self, *args, **kwargs): - return self.waitForWindowShown(*args, **kwargs) - - def wait_signal(self, *args, **kwargs): - return self.waitSignal(*args, **kwargs) - - def wait_signals(self, *args, **kwargs): - return self.waitSignals(*args, **kwargs) - - def assert_not_emitted(self, *args, **kwargs): - return self.assertNotEmitted(*args, **kwargs) - - def wait_until(self, *args, **kwargs): - return self.waitUntil(*args, **kwargs) - - def wait_callback(self, *args, **kwargs): - return self.waitCallback(*args, **kwargs) - # provide easy access to exceptions to qtbot fixtures QtBot.SignalEmittedError = SignalEmittedError @@ -721,7 +754,7 @@ def __init__(self, method_name, adjective_name, widget, timeout): """ - :param str method_name: name ot the ``QtTest`` method to call to check if widget is active/exposed. + :param str method_name: name to the ``QtTest`` method to call to check if widget is active/exposed. :param str adjective_name: "activated" or "exposed". :param widget: :param timeout: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/wait_signal.py new/pytest-qt-4.1.0/src/pytestqt/wait_signal.py --- old/pytest-qt-4.0.2/src/pytestqt/wait_signal.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/src/pytestqt/wait_signal.py 2022-06-23 16:39:11.000000000 +0200 @@ -626,7 +626,7 @@ :ivar int timeout: maximum time to wait for the callback to be called. :ivar bool raising: - If :class:`TimeoutError` should be raised if a timeout occured. + If :class:`TimeoutError` should be raised if a timeout occurred. .. note:: contrary to the parameter of same name in :meth:`pytestqt.qtbot.QtBot.waitCallback`, this parameter does not diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_basics.py new/pytest-qt-4.1.0/tests/test_basics.py --- old/pytest-qt-4.0.2/tests/test_basics.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/tests/test_basics.py 2022-06-23 16:39:11.000000000 +0200 @@ -42,6 +42,90 @@ res.stdout.fnmatch_lines("*1 passed*") +def test_qapp_cls(testdir): + testdir.makepyfile( + app=""" + from pytestqt.qt_compat import qt_api + + # Gets run before the plugin via conftest.py + qt_api.set_qt_api(None) + + class CustomQApp(qt_api.QtWidgets.QApplication): + pass + """ + ) + testdir.makeconftest( + """ + import pytest + from app import CustomQApp + + @pytest.fixture(scope="session") + def qapp_cls(): + return CustomQApp + """ + ) + testdir.makepyfile( + """ + from app import CustomQApp + + def test_cls(qapp): + assert isinstance(qapp, CustomQApp) + """ + ) + res = testdir.runpytest_subprocess() + res.stdout.fnmatch_lines("*1 passed*") + + +def test_qapp_reuse_existing(testdir): + testdir.makepyfile( + """ + from pytestqt.qt_compat import qt_api + + app_instance = qt_api.QtWidgets.QApplication([]) + + def test_instances(qapp): + assert qapp is app_instance + assert qapp is qt_api.QtWidgets.QApplication.instance() + """ + ) + res = testdir.runpytest_subprocess() + res.stdout.fnmatch_lines("*1 passed*") + + +def test_qapp_reuse_wrong_type(testdir): + testdir.makeconftest( + """ + import pytest + from pytestqt.qt_compat import qt_api + + # Gets run before the plugin + qt_api.set_qt_api(None) + + class CustomQApp(qt_api.QtWidgets.QApplication): + pass + + @pytest.fixture(scope="session") + def qapp_cls(): + return CustomQApp + """ + ) + testdir.makepyfile( + """ + from pytestqt.qt_compat import qt_api + + app_instance = qt_api.QtWidgets.QApplication([]) + + def test_wrong_type(qapp): + pass + """ + ) + res = testdir.runpytest_subprocess() + res.stdout.fnmatch_lines( + "*Existing QApplication <*.QtWidgets.QApplication* at 0x*> is not an " + "instance of qapp_cls: <class 'conftest.CustomQApp'>" + ) + + def test_key_events(qtbot, event_recorder): """ Basic key events test. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_modeltest.py new/pytest-qt-4.1.0/tests/test_modeltest.py --- old/pytest-qt-4.0.2/tests/test_modeltest.py 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/tests/test_modeltest.py 2022-06-23 16:39:11.000000000 +0200 @@ -1,3 +1,5 @@ +import sys + import pytest from pytestqt.qt_compat import qt_api @@ -54,6 +56,23 @@ qtmodeltester.check(proxy, force_py=True) +def test_standard_item_model_zero_columns(qtmodeltester): + model = qt_api.QtGui.QStandardItemModel() + qtmodeltester.check(model, force_py=True) + + # QTBUG-92220 + model.insertRows(0, 5) + model.removeRows(0, 5) + + # QTBUG-92886 + model.insertRows(0, 5) + model.removeRows(1, 2) + + parent_index = model.index(0, 0) + model.insertRows(0, 5, parent_index) + model.insertRows(1, 2, parent_index) + + @pytest.mark.parametrize( "broken_role", [ @@ -94,12 +113,22 @@ check_model(BrokenTypeModel(), should_pass=False) +xfail_py311_pyside2 = pytest.mark.xfail( + sys.version_info[:2] == (3, 11) and qt_api.pytest_qt_api == "pyside2", + reason="Fails to OR mask flags", +) + + @pytest.mark.parametrize( "role_value, should_pass", [ - (qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True), - (qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True), - (0xFFFFFF, False), + pytest.param( + qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True, marks=xfail_py311_pyside2 + ), + pytest.param( + qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True, marks=xfail_py311_pyside2 + ), + pytest.param(0xFFFFFF, False, marks=xfail_py311_pyside2), ("foo", False), (object(), False), ], @@ -242,7 +271,7 @@ def test_overridden_methods(qtmodeltester): - """Make sure overriden methods of a model are actually run. + """Make sure overridden methods of a model are actually run. With a previous implementation of the modeltester using sip.cast, the custom implementations did never actually run. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_qtbot_pep8_aliases.py new/pytest-qt-4.1.0/tests/test_qtbot_pep8_aliases.py --- old/pytest-qt-4.0.2/tests/test_qtbot_pep8_aliases.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-qt-4.1.0/tests/test_qtbot_pep8_aliases.py 2022-06-23 16:39:11.000000000 +0200 @@ -0,0 +1,92 @@ +import inspect +from unittest.mock import MagicMock + +import pytest + +from pytestqt.qtbot import QtBot + + +def _format_pep_8(camel_case_name: str) -> str: + """ + Helper that creates a pep8_compliant_method_name + from a given camelCaseMethodName. + """ + return camel_case_name[0].lower() + "".join( + f"_{letter.lower()}" if letter.isupper() else letter.lower() + for letter in camel_case_name[1:] + ) + + +@pytest.mark.parametrize( + "expected, camel_case_input", + [ + ("add_widget", "addWidget"), + ("wait_active", "waitActive"), + ("wait_exposed", "waitExposed"), + ("wait_for_window_shown", "waitForWindowShown"), + ("wait_signal", "waitSignal"), + ("wait_signals", "waitSignals"), + ("assert_not_emitted", "assertNotEmitted"), + ("wait_until", "waitUntil"), + ("wait_callback", "waitCallback"), + ], +) +def test_format_pep8(expected: str, camel_case_input: str): + assert _format_pep_8(camel_case_input) == expected + + +def test_pep8_aliases(qtbot): + """ + Test that defined PEP8 aliases actually refer to the correct implementation. + Only check methods that have such an alias defined. + """ + for name, func in inspect.getmembers(qtbot, inspect.ismethod): + if name != name.lower(): + pep8_name = _format_pep_8(name) + if hasattr(qtbot, pep8_name): + # Found a PEP8 alias. + assert ( + getattr(qtbot, name).__func__ is getattr(qtbot, pep8_name).__func__ + ) + + +def generate_test_cases_for_test_subclass_of_qtbot_has_overwritten_pep8_aliases(): + """ + For each PEP8 alias found in QtBot, yields a test case consisting of + a QtBot subclass that has the alias pair???s camelCase implementation + overwritten with a MagicMock. + + Yields tuples (subclass, camelCaseMethodName, pep8_method_name_alias) + """ + for name, func in inspect.getmembers(QtBot, inspect.isfunction): + if name != name.lower(): + subclass_logic_mock = MagicMock() + pep8_name = _format_pep_8(name) + if hasattr(QtBot, pep8_name): + # Found a PEP8 alias. + methods = QtBot.__dict__.copy() + # Only overwrite the camelCase method + methods[name] = subclass_logic_mock + sub_class = type("QtBotSubclass", (QtBot,), methods) + yield sub_class, name, pep8_name + + +@pytest.mark.parametrize( + "qtbot_subclass, method_name, pep8_name", + generate_test_cases_for_test_subclass_of_qtbot_has_overwritten_pep8_aliases(), +) +def test_subclass_of_qtbot_has_overwritten_pep8_aliases( + qtbot_subclass, method_name: str, pep8_name: str +): + """ + Test that subclassing QtBot does not create surprises, + by checking that the PEP8 aliases follow overwritten + camelCase methods. + """ + instance: QtBot = qtbot_subclass(MagicMock()) + assert isinstance(getattr(instance, method_name), MagicMock) + assert isinstance(getattr(instance, pep8_name), MagicMock) + # Now call the pep8_name_method and check that the subclass???s + # camelCaseMethod implementation was actually called. + getattr(instance, pep8_name)() + getattr(instance, method_name).assert_called() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_screenshot.py new/pytest-qt-4.1.0/tests/test_screenshot.py --- old/pytest-qt-4.0.2/tests/test_screenshot.py 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-qt-4.1.0/tests/test_screenshot.py 2022-06-23 16:39:11.000000000 +0200 @@ -0,0 +1,92 @@ +import pathlib + +import pytest + +from pytestqt.qt_compat import qt_api +from pytestqt.exceptions import ScreenshotError + + +@pytest.fixture +def widget(qtbot): + w = qt_api.QtWidgets.QWidget() + qtbot.addWidget(w) + w.setAttribute(qt_api.QtCore.Qt.WidgetAttribute.WA_StyledBackground) + w.setStyleSheet("background-color: magenta") + return w + + +def test_basic(qtbot, widget): + path = qtbot.screenshot(widget) + assert path.exists() + + pixmap = qt_api.QtGui.QPixmap() + assert pixmap.load(str(path)) + + image = pixmap.toImage() + color = image.pixelColor(image.rect().center()) + assert (color.red(), color.green(), color.blue()) == (255, 0, 255) + + +def test_region(qtbot, widget): + region = qt_api.QtCore.QRect(0, 0, 25, 25) + path = qtbot.screenshot(widget, region=region) + + pixmap = qt_api.QtGui.QPixmap() + assert pixmap.load(str(path)) + assert pixmap.rect() == region + + +def test_filename_class(qtbot, widget): + path = qtbot.screenshot(widget) + assert path.name == "screenshot_QWidget.png" + + +def test_filename_objectname(qtbot, widget): + widget.setObjectName("shotgun") + path = qtbot.screenshot(widget) + assert path.name == "screenshot_QWidget_shotgun.png" + + +def test_filename_suffix(qtbot, widget): + path = qtbot.screenshot(widget, suffix="before") + assert path.name == "screenshot_QWidget_before.png" + + +def test_filename_both(qtbot, widget): + widget.setObjectName("shotgun") + path = qtbot.screenshot(widget, suffix="before") + assert path.name == "screenshot_QWidget_shotgun_before.png" + + +def test_filename_endless(qtbot, widget, monkeypatch): + monkeypatch.setattr(pathlib.Path, "exists", lambda _self: True) + with pytest.raises(ScreenshotError, match="Failed to find unique filename"): + qtbot.screenshot(widget, suffix="before") + + +def test_filename_invalid(qtbot, widget): + with pytest.raises(ScreenshotError, match="Saving to .* failed"): + qtbot.screenshot(widget, suffix=r"invalid/path\everywhere") + + +def test_folder(qtbot, tmp_path, widget): + path = qtbot.screenshot(widget) + assert path.parent == tmp_path + + +@pytest.mark.parametrize( + "existing, expected", + [ + (["QLineEdit"], "QWidget"), + (["QWidget"], "QWidget_2"), + (["QWidget_2"], "QWidget"), + (["QWidget", "QWidget_2"], "QWidget_3"), + ], +) +def test_filename_dedup(qtbot, widget, tmp_path, existing, expected): + for name in existing: + path = tmp_path / f"screenshot_{name}.png" + path.touch() + + path = qtbot.screenshot(widget) + assert path.name == f"screenshot_{expected}.png" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-qt-4.0.2/tox.ini new/pytest-qt-4.1.0/tox.ini --- old/pytest-qt-4.0.2/tox.ini 2021-06-14 00:54:48.000000000 +0200 +++ new/pytest-qt-4.1.0/tox.ini 2022-06-23 16:39:11.000000000 +0200 @@ -1,5 +1,5 @@ [tox] -envlist = py{36,37,38}-pyqt5, py{36,37,38}-pyside2, py{37,38}-pyside6, py{36,37,38}-pyqt6, linting +envlist = py{37,38,39,310}-{pyqt5,pyside2,pyside6,pyqt6}, linting [testenv] deps=