From 714ce2e872f0e1dc3b0363949d084e3e64f88c82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 11:49:36 -0300 Subject: [PATCH 001/104] [automated] Update plugin list (#11658) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 124 +++++++++++++++---------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 6284177b9..2ded9bf9d 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -78,7 +78,7 @@ This list contains 1351 plugins. :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Jul 05, 2022 5 - Production/Stable pytest :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest :pypi:`pytest-anything` Pytest fixtures to assert anything and something Oct 13, 2022 N/A pytest - :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Nov 21, 2023 5 - Production/Stable pytest ; extra == 'test' + :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 02, 2023 5 - Production/Stable pytest ; extra == 'test' :pypi:`pytest-aoreporter` pytest report Jun 27, 2022 N/A N/A :pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0) :pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A @@ -89,7 +89,7 @@ This list contains 1351 plugins. :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Sep 06, 2023 5 - Production/Stable pytest :pypi:`pytest-archon` Rule your architecture like a real developer Jul 11, 2023 5 - Production/Stable pytest (>=7.2) :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) - :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Nov 15, 2023 4 - Beta pytest >=4.6 + :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Nov 27, 2023 4 - Beta pytest >=4.6 :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) :pypi:`pytest-aspec` A rspec format reporter for pytest Oct 23, 2023 4 - Beta N/A :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A @@ -105,12 +105,12 @@ This list contains 1351 plugins. :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A - :pypi:`pytest-asyncio` Pytest support for asyncio Nov 16, 2023 4 - Beta pytest (>=7.0.0) - :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Aug 06, 2023 N/A N/A + :pypi:`pytest-asyncio` Pytest support for asyncio Nov 27, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Nov 30, 2023 N/A N/A :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-atf-allure` 基于allure-pytest进行自定义 Oct 22, 2023 N/A pytest (>=7.4.2,<8.0.0) + :pypi:`pytest-atf-allure` 基于allure-pytest进行自定义 Nov 29, 2023 N/A pytest (>=7.4.2,<8.0.0) :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A @@ -207,7 +207,7 @@ This list contains 1351 plugins. :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest :pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A - :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Nov 02, 2023 N/A pytest >=7.0.0 + :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Nov 30, 2023 N/A pytest >=7.0.0 :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Oct 20, 2022 N/A N/A @@ -410,14 +410,14 @@ This list contains 1351 plugins. :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Nov 23, 2023 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Nov 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Nov 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Nov 27, 2023 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Nov 27, 2023 5 - Production/Stable N/A :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) @@ -427,7 +427,7 @@ This list contains 1351 plugins. :pypi:`pytest-encoding` set your encoding and logger Aug 11, 2023 N/A pytest :pypi:`pytest-enhanced-reports` Enhanced test reports for pytest Dec 15, 2022 N/A N/A :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A - :pypi:`pytest-env` pytest plugin that allows you to add environment variables. Oct 30, 2023 5 - Production/Stable pytest>=7.4.3 + :pypi:`pytest-env` pytest plugin that allows you to add environment variables. Nov 28, 2023 5 - Production/Stable pytest>=7.4.3 :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) @@ -527,7 +527,7 @@ This list contains 1351 plugins. :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A - :pypi:`pytest-fzf` fzf-based test selector for pytest Nov 20, 2023 4 - Beta pytest >=7.1.2 + :pypi:`pytest-fzf` fzf-based test selector for pytest Nov 28, 2023 4 - Beta pytest >=6.0.0 :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A @@ -574,10 +574,10 @@ This list contains 1351 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Nov 20, 2023 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Nov 23, 2023 3 - Alpha pytest ==7.4.3 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 01, 2023 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hot-reloading` Jun 23, 2023 N/A N/A + :pypi:`pytest-hot-reloading` Dec 01, 2023 N/A N/A :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Nov 10, 2023 N/A pytest :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) @@ -613,7 +613,7 @@ This list contains 1351 plugins. :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Oct 11, 2023 5 - Production/Stable pytest >=6.0 :pypi:`pytest-ignore-test-results` A pytest plugin to ignore test results. Aug 17, 2023 2 - Pre-Alpha pytest>=7.0 :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest - :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Nov 21, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0 :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A @@ -622,9 +622,9 @@ This list contains 1351 plugins. :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Aug 03, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Nov 29, 2023 5 - Production/Stable N/A :pypi:`pytest-inmanta-extensions` Inmanta tests package Oct 13, 2023 5 - Production/Stable N/A - :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules May 17, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Nov 29, 2023 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Nov 02, 2022 N/A pytest (>=7.2.0,<8.0.0) @@ -634,7 +634,7 @@ This list contains 1351 plugins. :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest May 22, 2023 N/A pytest (>=5.2) :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Sep 14, 2023 4 - Beta pytest + :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Nov 29, 2023 4 - Beta pytest :pypi:`pytest-invenio` Pytest fixtures for Invenio. Oct 31, 2023 5 - Production/Stable pytest <7.2.0,>=6 :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Mar 20, 2013 2 - Pre-Alpha N/A @@ -960,7 +960,7 @@ This list contains 1351 plugins. :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration Sep 12, 2023 4 - Beta pytest (>=7.2.2,<8.0.0) :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 30, 2023 5 - Production/Stable pytest (>=6.2.5) + :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 29, 2023 5 - Production/Stable pytest >=6.0 :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A :pypi:`pytest-qt` pytest support for PyQt and PySide applications Oct 25, 2022 5 - Production/Stable pytest (>=3.0.0) @@ -981,7 +981,7 @@ This list contains 1351 plugins. :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Dec 03, 2022 5 - Production/Stable pytest (>=3.0.0) :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A - :pypi:`pytest-reana` Pytest fixtures for REANA. Sep 26, 2023 3 - Alpha N/A + :pypi:`pytest-reana` Pytest fixtures for REANA. Nov 30, 2023 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jul 31, 2023 4 - Beta pytest>=3.5.0 :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A @@ -1067,13 +1067,13 @@ This list contains 1351 plugins. :pypi:`pytest-saccharin` pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Oct 31, 2022 3 - Alpha N/A :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A - :pypi:`pytest-salt-factories` Pytest Salt Plugin Sep 27, 2023 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-salt-factories` Pytest Salt Plugin Nov 25, 2023 4 - Beta pytest (>=6.0.0) :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Nov 17, 2023 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 01, 2023 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) @@ -1082,7 +1082,7 @@ This list contains 1351 plugins. :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) :pypi:`pytest-selenium` pytest plugin for Selenium Nov 20, 2023 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Nov 17, 2023 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 01, 2023 5 - Production/Stable N/A :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A @@ -1155,7 +1155,7 @@ This list contains 1351 plugins. :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Nov 25, 2023 N/A pytest (>5.4.0,<8) - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Nov 15, 2023 N/A N/A + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Dec 01, 2023 N/A N/A :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A @@ -1295,7 +1295,7 @@ This list contains 1351 plugins. :pypi:`pytest-twisted` A twisted plugin for pytest. Oct 16, 2022 5 - Production/Stable pytest (>=2.3) :pypi:`pytest-typechecker` Run type checkers on specified test files Feb 04, 2022 N/A pytest (>=6.2.5,<7.0.0) :pypi:`pytest-typhoon-config` A Typhoon HIL plugin that facilitates test parameter configuration at runtime Apr 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-typhoon-polarion` Typhoontest plugin for Siemens Polarion Nov 17, 2023 4 - Beta N/A + :pypi:`pytest-typhoon-polarion` Typhoontest plugin for Siemens Polarion Dec 01, 2023 4 - Beta N/A :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Aug 15, 2023 4 - Beta N/A :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A @@ -1370,7 +1370,7 @@ This list contains 1351 plugins. :pypi:`pytest-xvfb` A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. May 29, 2023 4 - Beta pytest (>=2.8.1) :pypi:`pytest-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest - :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Jul 03, 2023 N/A pytest>=7.4.0 + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Nov 30, 2023 N/A pytest>=7.4.0 :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A :pypi:`pytest-yaml-yoyo` http/https API run by yaml Jun 19, 2023 N/A pytest (>=7.2.0) @@ -1699,7 +1699,7 @@ This list contains 1351 plugins. Pytest fixtures to assert anything and something :pypi:`pytest-aoc` - *last release*: Nov 21, 2023, + *last release*: Dec 02, 2023, *status*: 5 - Production/Stable, *requires*: pytest ; extra == 'test' @@ -1776,7 +1776,7 @@ This list contains 1351 plugins. pyest results colection plugin :pypi:`pytest-arraydiff` - *last release*: Nov 15, 2023, + *last release*: Nov 27, 2023, *status*: 4 - Beta, *requires*: pytest >=4.6 @@ -1888,14 +1888,14 @@ This list contains 1351 plugins. Pytest fixtures for async generators :pypi:`pytest-asyncio` - *last release*: Nov 16, 2023, + *last release*: Nov 27, 2023, *status*: 4 - Beta, *requires*: pytest (>=7.0.0) Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Aug 06, 2023, + *last release*: Nov 30, 2023, *status*: N/A, *requires*: N/A @@ -1923,7 +1923,7 @@ This list contains 1351 plugins. Database testing fixtures using the SQLAlchemy asyncio API :pypi:`pytest-atf-allure` - *last release*: Oct 22, 2023, + *last release*: Nov 29, 2023, *status*: N/A, *requires*: pytest (>=7.4.2,<8.0.0) @@ -2602,7 +2602,7 @@ This list contains 1351 plugins. A pytest plugin to send a report and printing summary of tests. :pypi:`pytest-choose` - *last release*: Nov 02, 2023, + *last release*: Nov 30, 2023, *status*: N/A, *requires*: pytest >=7.0.0 @@ -4023,56 +4023,56 @@ This list contains 1351 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Nov 23, 2023, + *last release*: Nov 27, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4142,7 +4142,7 @@ This list contains 1351 plugins. Improvements for pytest (rejected upstream) :pypi:`pytest-env` - *last release*: Oct 30, 2023, + *last release*: Nov 28, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=7.4.3 @@ -4842,9 +4842,9 @@ This list contains 1351 plugins. :pypi:`pytest-fzf` - *last release*: Nov 20, 2023, + *last release*: Nov 28, 2023, *status*: 4 - Beta, - *requires*: pytest >=7.1.2 + *requires*: pytest >=6.0.0 fzf-based test selector for pytest @@ -5171,7 +5171,7 @@ This list contains 1351 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Nov 23, 2023, + *last release*: Dec 01, 2023, *status*: 3 - Alpha, *requires*: pytest ==7.4.3 @@ -5192,7 +5192,7 @@ This list contains 1351 plugins. Report on tests that honor constraints, and guard against regressions :pypi:`pytest-hot-reloading` - *last release*: Jun 23, 2023, + *last release*: Dec 01, 2023, *status*: N/A, *requires*: N/A @@ -5444,7 +5444,7 @@ This list contains 1351 plugins. :pypi:`pytest-image-snapshot` - *last release*: Nov 21, 2023, + *last release*: Dec 01, 2023, *status*: 4 - Beta, *requires*: pytest >=3.5.0 @@ -5507,7 +5507,7 @@ This list contains 1351 plugins. A pytest plugin for writing inline tests. :pypi:`pytest-inmanta` - *last release*: Aug 03, 2023, + *last release*: Nov 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -5521,7 +5521,7 @@ This list contains 1351 plugins. Inmanta tests package :pypi:`pytest-inmanta-lsm` - *last release*: May 17, 2023, + *last release*: Nov 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -5591,7 +5591,7 @@ This list contains 1351 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. :pypi:`pytest-interface-tester` - *last release*: Sep 14, 2023, + *last release*: Nov 29, 2023, *status*: 4 - Beta, *requires*: pytest @@ -7873,9 +7873,9 @@ This list contains 1351 plugins. Pytest plugin for uploading test results to your QA Touch Testrun. :pypi:`pytest-qgis` - *last release*: Jun 30, 2023, + *last release*: Nov 29, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2.5) + *requires*: pytest >=6.0 A pytest plugin for testing QGIS python plugins @@ -8020,7 +8020,7 @@ This list contains 1351 plugins. Test your README.md file :pypi:`pytest-reana` - *last release*: Sep 26, 2023, + *last release*: Nov 30, 2023, *status*: 3 - Alpha, *requires*: N/A @@ -8622,7 +8622,7 @@ This list contains 1351 plugins. A Pytest plugin that builds and creates docker containers :pypi:`pytest-salt-factories` - *last release*: Sep 27, 2023, + *last release*: Nov 25, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) @@ -8664,7 +8664,7 @@ This list contains 1351 plugins. :pypi:`pytest-sbase` - *last release*: Nov 17, 2023, + *last release*: Dec 01, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -8727,7 +8727,7 @@ This list contains 1351 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Nov 17, 2023, + *last release*: Dec 01, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -9238,7 +9238,7 @@ This list contains 1351 plugins. A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Nov 15, 2023, + *last release*: Dec 01, 2023, *status*: N/A, *requires*: N/A @@ -10218,7 +10218,7 @@ This list contains 1351 plugins. A Typhoon HIL plugin that facilitates test parameter configuration at runtime :pypi:`pytest-typhoon-polarion` - *last release*: Nov 17, 2023, + *last release*: Dec 01, 2023, *status*: 4 - Beta, *requires*: N/A @@ -10743,7 +10743,7 @@ This list contains 1351 plugins. This plugin is used to load yaml output to your test using pytest framework. :pypi:`pytest-yaml-sanmu` - *last release*: Jul 03, 2023, + *last release*: Nov 30, 2023, *status*: N/A, *requires*: pytest>=7.4.0 From 3e14c4b3c40830b7dd9a15826f4db36c2f73716d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:41:20 -0300 Subject: [PATCH 002/104] build(deps): Bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 (#11663) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.10...v1.8.11) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 95ec0d174..e1b95efa7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -47,7 +47,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.10 + uses: pypa/gh-action-pypi-publish@v1.8.11 - name: Push tag run: | From db8c6f1da8112495123a4ff0e2fcfe12ac818f8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:41:34 -0300 Subject: [PATCH 003/104] build(deps): Bump pytest-asyncio in /testing/plugins_integration (#11664) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.21.1 to 0.23.1. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.21.1...v0.23.1) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index f692838f3..f1e97494f 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,6 +1,6 @@ anyio[curio,trio]==4.1.0 django==4.2.7 -pytest-asyncio==0.21.1 +pytest-asyncio==0.23.1 pytest-bdd==7.0.0 pytest-cov==4.1.0 pytest-django==4.7.0 From 022f1b4de546c8b3529e071965555888ecf01cb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 13:58:18 +0000 Subject: [PATCH 004/104] build(deps): Bump pytest-bdd in /testing/plugins_integration (#11665) Bumps [pytest-bdd](https://github.com/pytest-dev/pytest-bdd) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/pytest-dev/pytest-bdd/releases) - [Changelog](https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-bdd/compare/7.0.0...7.0.1) --- updated-dependencies: - dependency-name: pytest-bdd dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bruno Oliveira --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index f1e97494f..1166d14c9 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,7 +1,7 @@ anyio[curio,trio]==4.1.0 django==4.2.7 pytest-asyncio==0.23.1 -pytest-bdd==7.0.0 +pytest-bdd==7.0.1 pytest-cov==4.1.0 pytest-django==4.7.0 pytest-flakes==4.0.5 From b8118ab70d3e7cac1390241a67016f940e39b1c7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 4 Dec 2023 22:41:20 +0200 Subject: [PATCH 005/104] Remove `setup.py` Fix #11667. --- changelog/11667.breaking.rst | 3 +++ pyproject.toml | 1 - setup.py | 4 ---- 3 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 changelog/11667.breaking.rst delete mode 100644 setup.py diff --git a/changelog/11667.breaking.rst b/changelog/11667.breaking.rst new file mode 100644 index 000000000..7c05d39b2 --- /dev/null +++ b/changelog/11667.breaking.rst @@ -0,0 +1,3 @@ +pytest's ``setup.py`` file is removed. +If you relied on this file, e.g. to install pytest using ``setup.py install``, +please see `Why you shouldn't invoke setup.py directly `_ for alternatives. diff --git a/pyproject.toml b/pyproject.toml index cdbdd3880..d45597b77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] requires = [ - # sync with setup.py until we discard non-pep-517/518 "setuptools>=45.0", "setuptools-scm[toml]>=6.2.3", ] diff --git a/setup.py b/setup.py deleted file mode 100644 index 7f1a1763c..000000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from setuptools import setup - -if __name__ == "__main__": - setup() From a536f49d910eda84cbd0608e6edb1b70bb0868b2 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Wed, 6 Dec 2023 09:25:00 +0000 Subject: [PATCH 006/104] Separate the various parts of the error report with newlines (#11659) Previously the error report would have all sections glued together: - The assertion representation - The error explanation - The full diff This makes it hard to see at a glance where which one starts and ends. One of the representation (dataclasses, tuples, attrs) does display a newlines at the start already. Let's add a newlines before the error explanation and before the full diff, so we get an easier to read report. This has one disadvantage: we get one line less in the least verbose mode, where the output gets truncated. --- changelog/11520.improvement.rst | 2 ++ src/_pytest/assertion/util.py | 4 ++- testing/python/approx.py | 32 +++++++++++++----- testing/test_assertion.py | 59 +++++++++++++++++++++++++-------- 4 files changed, 74 insertions(+), 23 deletions(-) diff --git a/changelog/11520.improvement.rst b/changelog/11520.improvement.rst index 46e4992dd..d9b7b4933 100644 --- a/changelog/11520.improvement.rst +++ b/changelog/11520.improvement.rst @@ -1 +1,3 @@ Improved very verbose diff output to color it as a diff instead of only red. + +Improved the error reporting to better separate each section. diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 214c321f0..fe8904e15 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -230,6 +230,8 @@ def assertrepr_compare( if not explanation: return None + if explanation[0] != "": + explanation = [""] + explanation return [summary] + explanation @@ -332,7 +334,7 @@ def _compare_eq_iterable( left_formatting = PrettyPrinter().pformat(left).splitlines() right_formatting = PrettyPrinter().pformat(right).splitlines() - explanation = ["Full diff:"] + explanation = ["", "Full diff:"] # "right" is the expected base against which we compare "left", # see https://github.com/pytest-dev/pytest/issues/3333 explanation.extend( diff --git a/testing/python/approx.py b/testing/python/approx.py index 6ad411a3e..3b87e58f9 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -99,6 +99,7 @@ class TestApprox: 2.0, 1.0, [ + "", " comparison failed", f" Obtained: {SOME_FLOAT}", f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}", @@ -113,6 +114,7 @@ class TestApprox: "c": 3000000.0, }, [ + r"", r" comparison failed. Mismatched elements: 2 / 3:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -130,6 +132,7 @@ class TestApprox: "c": None, }, [ + r"", r" comparison failed. Mismatched elements: 2 / 3:", r" Max absolute difference: -inf", r" Max relative difference: -inf", @@ -143,6 +146,7 @@ class TestApprox: [1.0, 2.0, 3.0, 4.0], [1.0, 3.0, 3.0, 5.0], [ + r"", r" comparison failed. Mismatched elements: 2 / 4:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -156,6 +160,7 @@ class TestApprox: (1, 2.2, 4), (1, 3.2, 4), [ + r"", r" comparison failed. Mismatched elements: 1 / 3:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -169,6 +174,7 @@ class TestApprox: [0.0], [1.0], [ + r"", r" comparison failed. Mismatched elements: 1 / 1:", rf" Max absolute difference: {SOME_FLOAT}", r" Max relative difference: inf", @@ -187,6 +193,7 @@ class TestApprox: a, b, [ + r"", r" comparison failed. Mismatched elements: 1 / 20:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -209,6 +216,7 @@ class TestApprox: ] ), [ + r"", r" comparison failed. Mismatched elements: 3 / 8:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -224,6 +232,7 @@ class TestApprox: np.array([0.0]), np.array([1.0]), [ + r"", r" comparison failed. Mismatched elements: 1 / 1:", rf" Max absolute difference: {SOME_FLOAT}", r" Max relative difference: inf", @@ -241,6 +250,7 @@ class TestApprox: message = "\n".join(str(e.value).split("\n")[1:]) assert message == "\n".join( [ + " ", " Impossible to compare arrays with different shapes.", " Shapes: (2, 1) and (2, 2)", ] @@ -251,6 +261,7 @@ class TestApprox: message = "\n".join(str(e.value).split("\n")[1:]) assert message == "\n".join( [ + " ", " Impossible to compare lists with different sizes.", " Lengths: 2 and 3", ] @@ -264,6 +275,7 @@ class TestApprox: 2.0, 1.0, [ + "", " comparison failed", f" Obtained: {SOME_FLOAT}", f" Expected: {SOME_FLOAT} ± {SOME_FLOAT}", @@ -277,15 +289,15 @@ class TestApprox: a, b, [ - r" comparison failed. Mismatched elements: 20 / 20:", - rf" Max absolute difference: {SOME_FLOAT}", - rf" Max relative difference: {SOME_FLOAT}", - r" Index \| Obtained\s+\| Expected", - rf" \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - rf" \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", - rf" \(2,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}...", - "", - rf"\s*...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show", + r"^ $", + r"^ comparison failed. Mismatched elements: 20 / 20:$", + rf"^ Max absolute difference: {SOME_FLOAT}$", + rf"^ Max relative difference: {SOME_FLOAT}$", + r"^ Index \| Obtained\s+\| Expected\s+$", + rf"^ \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}$", + rf"^ \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}\.\.\.$", + "^ $", + rf"^ ...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show$", ], verbosity_level=0, ) @@ -294,6 +306,7 @@ class TestApprox: a, b, [ + r" ", r" comparison failed. Mismatched elements: 20 / 20:", rf" Max absolute difference: {SOME_FLOAT}", rf" Max relative difference: {SOME_FLOAT}", @@ -652,6 +665,7 @@ class TestApprox: {"foo": 42.0}, {"foo": 0.0}, [ + r"", r" comparison failed. Mismatched elements: 1 / 1:", rf" Max absolute difference: {SOME_FLOAT}", r" Max relative difference: inf", diff --git a/testing/test_assertion.py b/testing/test_assertion.py index ce10ed8c4..4d751f8db 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -392,6 +392,7 @@ class TestAssert_reprcompare: def test_text_diff(self) -> None: assert callequal("spam", "eggs") == [ "'spam' == 'eggs'", + "", "- eggs", "+ spam", ] @@ -399,7 +400,7 @@ class TestAssert_reprcompare: def test_text_skipping(self) -> None: lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs") assert lines is not None - assert "Skipping" in lines[1] + assert "Skipping" in lines[2] for line in lines: assert "a" * 50 not in line @@ -423,6 +424,7 @@ class TestAssert_reprcompare: assert diff == [ "b'spam' == b'eggs'", + "", "At index 0 diff: b's' != b'e'", "Use -v to get more diff", ] @@ -432,7 +434,9 @@ class TestAssert_reprcompare: diff = callequal(b"spam", b"eggs", verbose=1) assert diff == [ "b'spam' == b'eggs'", + "", "At index 0 diff: b's' != b'e'", + "", "Full diff:", "- b'eggs'", "+ b'spam'", @@ -509,6 +513,7 @@ class TestAssert_reprcompare: expl = callequal([1, 2], [10, 2], verbose=-1) assert expl == [ "[1, 2] == [10, 2]", + "", "At index 0 diff: 1 != 10", "Use -v to get more diff", ] @@ -547,7 +552,9 @@ class TestAssert_reprcompare: diff = callequal(l1, l2, verbose=True) assert diff == [ "['a', 'b', 'c'] == ['a', 'b', 'c...dddddddddddd']", + "", "Right contains one more item: '" + long_d + "'", + "", "Full diff:", " [", " 'a',", @@ -560,7 +567,9 @@ class TestAssert_reprcompare: diff = callequal(l2, l1, verbose=True) assert diff == [ "['a', 'b', 'c...dddddddddddd'] == ['a', 'b', 'c']", + "", "Left contains one more item: '" + long_d + "'", + "", "Full diff:", " [", " 'a',", @@ -579,7 +588,9 @@ class TestAssert_reprcompare: diff = callequal(l1, l2, verbose=True) assert diff == [ "['aaaaaaaaaaa...cccccccccccc'] == ['bbbbbbbbbbb...aaaaaaaaaaaa']", + "", "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "", "Full diff:", " [", "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", @@ -596,8 +607,10 @@ class TestAssert_reprcompare: diff = callequal(l1, l2, verbose=True) assert diff == [ "['a', 'aaaaaa...aaaaaaa', ...] == ['should not get wrapped']", + "", "At index 0 diff: 'a' != 'should not get wrapped'", "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", + "", "Full diff:", " [", "- 'should not get wrapped',", @@ -619,9 +632,11 @@ class TestAssert_reprcompare: diff = callequal(d1, d2, verbose=True) assert diff == [ "{'common': 1,...1, 'env2': 2}} == {'common': 1,...: {'env1': 1}}", + "", "Omitting 1 identical items, use -vv to show", "Differing items:", "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}", + "", "Full diff:", " {", " 'common': 1,", @@ -639,9 +654,11 @@ class TestAssert_reprcompare: diff = callequal(d1, d2, verbose=True) assert diff == [ "{'env': {'sub... wrapped '}}}} == {'env': {'sub...}}}, 'new': 1}", + "", "Omitting 1 identical items, use -vv to show", "Right contains 1 more item:", "{'new': 1}", + "", "Full diff:", " {", " 'env': {", @@ -665,7 +682,7 @@ class TestAssert_reprcompare: def test_dict_omitting(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}) assert lines is not None - assert lines[1].startswith("Omitting 1 identical item") + assert lines[2].startswith("Omitting 1 identical item") assert "Common items" not in lines for line in lines[1:]: assert "b" not in line @@ -674,26 +691,29 @@ class TestAssert_reprcompare: """Ensure differing items are visible for verbosity=1 (#1512).""" lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=1) assert lines is not None - assert lines[1].startswith("Omitting 1 identical item") - assert lines[2].startswith("Differing items") - assert lines[3] == "{'a': 0} != {'a': 1}" + assert lines[1] == "" + assert lines[2].startswith("Omitting 1 identical item") + assert lines[3].startswith("Differing items") + assert lines[4] == "{'a': 0} != {'a': 1}" assert "Common items" not in lines def test_dict_omitting_with_verbosity_2(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=2) assert lines is not None - assert lines[1].startswith("Common items:") - assert "Omitting" not in lines[1] - assert lines[2] == "{'b': 1}" + assert lines[2].startswith("Common items:") + assert "Omitting" not in lines[2] + assert lines[3] == "{'b': 1}" def test_dict_different_items(self) -> None: lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2) assert lines == [ "{'a': 0} == {'b': 1, 'c': 2}", + "", "Left contains 1 more item:", "{'a': 0}", "Right contains 2 more items:", "{'b': 1, 'c': 2}", + "", "Full diff:", " {", "- 'b': 1,", @@ -706,10 +726,12 @@ class TestAssert_reprcompare: lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) assert lines == [ "{'b': 1, 'c': 2} == {'a': 0}", + "", "Left contains 2 more items:", "{'b': 1, 'c': 2}", "Right contains 1 more item:", "{'a': 0}", + "", "Full diff:", " {", "- 'a': 0,", @@ -724,8 +746,10 @@ class TestAssert_reprcompare: lines = callequal((1, 2), (3, 4, 5), verbose=2) assert lines == [ "(1, 2) == (3, 4, 5)", + "", "At index 0 diff: 1 != 3", "Right contains one more item: 5", + "", "Full diff:", " (", "- 3,", @@ -742,8 +766,10 @@ class TestAssert_reprcompare: lines = callequal((1, 2, 3), (4,), verbose=2) assert lines == [ "(1, 2, 3) == (4,)", + "", "At index 0 diff: 1 != 4", "Left contains 2 more items, first extra item: 2", + "", "Full diff:", " (", "- 4,", @@ -757,7 +783,9 @@ class TestAssert_reprcompare: lines = callequal((1, 2, 3), (1, 20, 3), verbose=2) assert lines == [ "(1, 2, 3) == (1, 20, 3)", + "", "At index 1 diff: 2 != 20", + "", "Full diff:", " (", " 1,", @@ -823,7 +851,7 @@ class TestAssert_reprcompare: assert expl is not None assert expl[0].startswith("{} == <[ValueError") assert "raised in repr" in expl[0] - assert expl[1:] == [ + assert expl[2:] == [ "(pytest_assertion plugin: representation of details failed:" " {}:{}: ValueError: 42.".format( __file__, A.__repr__.__code__.co_firstlineno + 1 @@ -849,6 +877,7 @@ class TestAssert_reprcompare: def test_unicode(self) -> None: assert callequal("£€", "£") == [ "'£€' == '£'", + "", "- £", "+ £€", ] @@ -864,7 +893,7 @@ class TestAssert_reprcompare: return "\xff" expl = callequal(A(), "1") - assert expl == ["ÿ == '1'", "- 1"] + assert expl == ["ÿ == '1'", "", "- 1"] def test_format_nonascii_explanation(self) -> None: assert util.format_explanation("λ") @@ -887,6 +916,7 @@ class TestAssert_reprcompare: expl = callequal(left, right) assert expl == [ r"'hyv\xe4' == 'hyva\u0308'", + "", f"- {str(right)}", f"+ {str(left)}", ] @@ -894,6 +924,7 @@ class TestAssert_reprcompare: expl = callequal(left, right, verbose=2) assert expl == [ r"'hyv\xe4' == 'hyva\u0308'", + "", f"- {str(right)}", f"+ {str(left)}", ] @@ -1182,6 +1213,7 @@ class TestAssert_reprcompare_namedtuple: # Because the types are different, uses the generic sequence matcher. assert lines == [ "NT1(a=1, b='b') == NT2(a=2, b='b')", + "", "At index 0 diff: 1 != 2", "Use -v to get more diff", ] @@ -1369,7 +1401,7 @@ class TestTruncateExplanation: line_count = 7 line_len = 100 - expected_truncated_lines = 1 + expected_truncated_lines = 2 pytester.makepyfile( r""" def test_many_lines(): @@ -1389,8 +1421,7 @@ class TestTruncateExplanation: [ "*+ 1*", "*+ 3*", - "*+ 5*", - "*truncated (%d line hidden)*use*-vv*" % expected_truncated_lines, + "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) @@ -1433,6 +1464,7 @@ def test_rewritten(pytester: Pytester) -> None: def test_reprcompare_notin() -> None: assert callop("not in", "foo", "aaafoobbb") == [ "'foo' not in 'aaafoobbb'", + "", "'foo' is contained here:", " aaafoobbb", "? +++", @@ -1442,6 +1474,7 @@ def test_reprcompare_notin() -> None: def test_reprcompare_whitespaces() -> None: assert callequal("\r\n", "\n") == [ r"'\r\n' == '\n'", + "", r"Strings contain only whitespace, escaping them using repr()", r"- '\n'", r"+ '\r\n'", From a5ee9f2ecd22576ef6e91a00c957ec190ce87dc6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 6 Dec 2023 23:58:07 +0200 Subject: [PATCH 007/104] doc: document `pytest.{hookspec,hookimpl}` in the API Reference Makes sense for them to be there; also allows for Sphinx refs. --- doc/en/how-to/writing_hook_functions.rst | 5 +---- doc/en/reference/reference.rst | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/doc/en/how-to/writing_hook_functions.rst b/doc/en/how-to/writing_hook_functions.rst index 527aeec81..ec290349c 100644 --- a/doc/en/how-to/writing_hook_functions.rst +++ b/doc/en/how-to/writing_hook_functions.rst @@ -59,10 +59,6 @@ The remaining hook functions will not be called in this case. hook wrappers: executing around other hooks ------------------------------------------------- -.. currentmodule:: _pytest.core - - - pytest plugins can implement hook wrappers which wrap the execution of other hook implementations. A hook wrapper is a generator function which yields exactly once. When pytest invokes hooks it first executes @@ -165,6 +161,7 @@ Here is the order of execution: It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers in which case it will influence the ordering of hook wrappers among each other. +.. _`declaringhooks`: Declaring new hooks ------------------------ diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 254973709..2dcad7d58 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -612,10 +612,30 @@ Hooks **Tutorial**: :ref:`writing-plugins` -.. currentmodule:: _pytest.hookspec - Reference to all hooks which can be implemented by :ref:`conftest.py files ` and :ref:`plugins `. +@pytest.hookimpl +~~~~~~~~~~~~~~~~ + +.. function:: pytest.hookimpl + :decorator: + + pytest's decorator for marking functions as hook implementations. + + See :ref:`writinghooks` and :func:`pluggy.HookimplMarker`. + +@pytest.hookspec +~~~~~~~~~~~~~~~~ + +.. function:: pytest.hookspec + :decorator: + + pytest's decorator for marking functions as hook specifications. + + See :ref:`declaringhooks` and :func:`pluggy.HookspecMarker`. + +.. currentmodule:: _pytest.hookspec + Bootstrapping hooks ~~~~~~~~~~~~~~~~~~~ From 9056db4de56cd6a2bd061209d2ecd88783309ba0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 6 Dec 2023 23:37:52 +0200 Subject: [PATCH 008/104] doc: fix some broken Sphinx references --- changelog/9288.breaking.rst | 2 +- doc/en/backwards-compatibility.rst | 2 +- doc/en/changelog.rst | 58 ++++++++++++------------ doc/en/deprecations.rst | 8 ++-- doc/en/example/parametrize.rst | 4 +- doc/en/explanation/fixtures.rst | 2 +- doc/en/funcarg_compare.rst | 2 - doc/en/historical-notes.rst | 2 +- doc/en/how-to/capture-warnings.rst | 2 - doc/en/how-to/fixtures.rst | 2 +- doc/en/how-to/writing_hook_functions.rst | 4 +- doc/en/reference/fixtures.rst | 9 +--- src/_pytest/capture.py | 4 +- src/_pytest/config/argparsing.py | 4 +- src/_pytest/hookspec.py | 4 +- src/_pytest/legacypath.py | 7 ++- src/_pytest/logging.py | 4 +- src/_pytest/pytester.py | 4 +- src/_pytest/python_api.py | 2 - 19 files changed, 55 insertions(+), 71 deletions(-) diff --git a/changelog/9288.breaking.rst b/changelog/9288.breaking.rst index 053af8013..c344b83c7 100644 --- a/changelog/9288.breaking.rst +++ b/changelog/9288.breaking.rst @@ -1,4 +1,4 @@ -:func:`pytest.warns ` now re-emits unmatched warnings when the context +:func:`~pytest.warns` now re-emits unmatched warnings when the context closes -- previously it would consume all warnings, hiding those that were not matched by the function. diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 4ffb9fe97..e04e64a76 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -22,7 +22,7 @@ b) transitional: the old and new API don't conflict We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). - A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationwarning`). + A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`). When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 121d1708d..7c2162d62 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -413,7 +413,7 @@ Improvements - `#8508 `_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and - enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. + enhance match comparison for :py:func:`pytest.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. - `#8646 `_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing @@ -422,7 +422,7 @@ Improvements - `#9741 `_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. - :mod:`tomli` is no longer a dependency on Python 3.11. + `tomli` is no longer a dependency on Python 3.11. - `#9742 `_: Display assertion message without escaped newline characters with ``-vv``. @@ -457,7 +457,7 @@ Bug Fixes When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse. - When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead. + When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` instead. - `#9159 `_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``. @@ -710,7 +710,7 @@ Bug Fixes - `#9355 `_: Fixed error message prints function decorators when using assert in Python 3.8 and above. -- `#9396 `_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``). +- `#9396 `_: Ensure `pytest.Config.inifile` is available during the :hook:`pytest_cmdline_main` hook (regression during ``7.0.0rc1``). @@ -855,7 +855,7 @@ Deprecations - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. -- `#8447 `_: Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. +- `#8447 `_: Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning. It was never sanely supported and triggers hard to debug errors. See :ref:`the deprecation note ` for full details. @@ -897,7 +897,7 @@ Features - `#7132 `_: Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used. -- `#7259 `_: Added :meth:`cache.mkdir() `, which is similar to the existing :meth:`cache.makedir() `, +- `#7259 `_: Added :meth:`cache.mkdir() `, which is similar to the existing ``cache.makedir()``, but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. Added a ``paths`` type to :meth:`parser.addini() `, @@ -923,7 +923,7 @@ Features - ``pytest.HookRecorder`` for the :class:`HookRecorder ` type returned from :class:`~pytest.Pytester`. - ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall ` type returned from :class:`~pytest.HookRecorder`. - ``pytest.RunResult`` for the :class:`RunResult ` type returned from :class:`~pytest.Pytester`. - - ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. + - ``pytest.LineMatcher`` for the :class:`LineMatcher ` type used in :class:`~pytest.RunResult` and others. - ``pytest.TestReport`` for the :class:`TestReport ` type used in various hooks. - ``pytest.CollectReport`` for the :class:`CollectReport ` type used in various hooks. @@ -956,7 +956,7 @@ Features - `#8251 `_: Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet - due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`, we expect to deprecate it in a future release. + due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`, we expect to deprecate it in a future release. .. note:: The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the @@ -988,7 +988,7 @@ Features See :ref:`plugin-stash` for details. -- `#8953 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a +- `#8953 `_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a ``warnings`` argument to assert the total number of warnings captured. @@ -1000,7 +1000,7 @@ Features used. -- `#9113 `_: :class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a +- `#9113 `_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a ``deselected`` argument to assert the total number of deselected tests. @@ -1013,7 +1013,7 @@ Improvements - `#7480 `_: A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`, a subclass of :class:`~pytest.PytestDeprecationWarning`, - instead of :class:`PytestDeprecationWarning` directly. + instead of :class:`~pytest.PytestDeprecationWarning` directly. See :ref:`backwards-compatibility` for more details. @@ -1052,7 +1052,7 @@ Improvements - `#8803 `_: It is now possible to add colors to custom log levels on cli log. - By using :func:`add_color_level <_pytest.logging.add_color_level>` from a ``pytest_configure`` hook, colors can be added:: + By using ``add_color_level`` from a :hook:`pytest_configure` hook, colors can be added:: logging_plugin = config.pluginmanager.get_plugin('logging-plugin') logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan') @@ -1117,7 +1117,7 @@ Bug Fixes - `#8503 `_: :meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when ``setuptools`` is not installed. - It now only calls :func:`pkg_resources.fixup_namespace_packages` if + It now only calls ``pkg_resources.fixup_namespace_packages`` if ``pkg_resources`` was previously imported, because it is not needed otherwise. @@ -1344,7 +1344,7 @@ Features This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future. - Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface. + Internally, the old :class:`~pytest.Testdir` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface. - :issue:`7695`: A new hook was added, `pytest_markeval_namespace` which should return a dictionary. @@ -1382,7 +1382,7 @@ Features Improvements ------------ -- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. +- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method. - :issue:`2044`: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS". @@ -1446,7 +1446,7 @@ Bug Fixes - :issue:`7911`: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites. -- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved. +- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn ` when the :mod:`readline` module is involved. - :issue:`7951`: Fixed handling of recursive symlinks when collecting tests. @@ -1563,7 +1563,7 @@ Deprecations if you use this and want a replacement. -- :issue:`7255`: The :hook:`pytest_warning_captured` hook is deprecated in favor +- :issue:`7255`: The ``pytest_warning_captured`` hook is deprecated in favor of :hook:`pytest_warning_recorded`, and will be removed in a future version. @@ -1591,8 +1591,8 @@ Improvements - :issue:`7572`: When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace. -- :issue:`7685`: Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`. - These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes, +- :issue:`7685`: Added two new attributes :attr:`rootpath ` and :attr:`inipath ` to :class:`~pytest.Config`. + These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir ` and :attr:`inifile ` attributes, and should be preferred over them when possible. @@ -1663,7 +1663,7 @@ Trivial/Internal Changes - :issue:`7587`: The dependency on the ``more-itertools`` package has been removed. -- :issue:`7631`: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple, +- :issue:`7631`: The result type of :meth:`capfd.readouterr() ` (and similar) is no longer a namedtuple, but should behave like one in all respects. This was done for technical reasons. @@ -2041,10 +2041,10 @@ Improvements - :issue:`7128`: `pytest --version` now displays just the pytest version, while `pytest --version --version` displays more verbose information including plugins. This is more consistent with how other tools show `--version`. -- :issue:`7133`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file. +- :issue:`7133`: :meth:`caplog.set_level() ` will now override any :confval:`log_level` set via the CLI or configuration file. -- :issue:`7159`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <_pytest.logging.LogCaptureFixture.at_level>` no longer affect +- :issue:`7159`: :meth:`caplog.set_level() ` and :meth:`caplog.at_level() ` no longer affect the level of logs that are shown in the *Captured log report* report section. @@ -2390,7 +2390,7 @@ Improvements - :issue:`6384`: Make `--showlocals` work also with `--tb=short`. -- :issue:`6653`: Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`. +- :issue:`6653`: Add support for matching lines consecutively with :class:`~pytest.LineMatcher`'s :func:`~pytest.LineMatcher.fnmatch_lines` and :func:`~pytest.LineMatcher.re_match_lines`. - :issue:`6658`: Code is now highlighted in tracebacks when ``pygments`` is installed. @@ -2458,7 +2458,7 @@ Bug Fixes - :issue:`6597`: Fix node ids which contain a parametrized empty-string variable. -- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc. +- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest ` etc. - :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger. @@ -2524,7 +2524,7 @@ Bug Fixes ``multiprocessing`` module. -- :issue:`6436`: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and +- :issue:`6436`: :class:`~pytest.FixtureDef` objects now properly register their finalizers with autouse and parameterized fixtures that execute before them in the fixture stack so they are torn down at the right times, and in the right order. @@ -2580,7 +2580,7 @@ Improvements Bug Fixes --------- -- :issue:`5914`: pytester: fix :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` when used after positive matching. +- :issue:`5914`: pytester: fix :py:func:`~pytest.LineMatcher.no_fnmatch_line` when used after positive matching. - :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`. @@ -2644,8 +2644,8 @@ Features rather than implicitly. -- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and - :py:func:`~_pytest.pytester.LineMatcher.no_re_match_line`. +- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~pytest.LineMatcher.no_fnmatch_line` and + :py:func:`~pytest.LineMatcher.no_re_match_line`. The functions are used to ensure the captured text *does not* match the given pattern. @@ -6497,7 +6497,7 @@ Changes * fix :issue:`2013`: turn RecordedWarning into ``namedtuple``, to give it a comprehensible repr while preventing unwarranted modification. -* fix :issue:`2208`: ensure an iteration limit for _pytest.compat.get_real_func. +* fix :issue:`2208`: ensure an iteration limit for ``_pytest.compat.get_real_func``. Thanks :user:`RonnyPfannschmidt` for the report and PR. * Hooks are now verified after collection is complete, rather than right after loading installed plugins. This diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 0e47bdb09..ad051fc62 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -177,7 +177,7 @@ arguments they only pass on to the superclass. resolved in future versions as we slowly get rid of the :pypi:`py` dependency (see :issue:`9283` for a longer discussion). -Due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo` +Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo` which still is expected to return a ``py.path.local`` object, nodes still have both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes, no matter what argument was used in the constructor. We expect to deprecate the @@ -336,7 +336,7 @@ Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item` .. deprecated:: 7.0 -Defining a custom pytest node type which is both an :class:`pytest.Item ` and a :class:`pytest.Collector ` (e.g. :class:`pytest.File `) now issues a warning. +Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning. It was never sanely supported and triggers hard to debug errors. Some plugins providing linting/code analysis have been using this as a hack. @@ -348,8 +348,8 @@ Instead, a separate collector node should be used, which collects the item. See .. _uncooperative-constructors-deprecated: -Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Constructors of custom :class:`~_pytest.nodes.Node` subclasses should take ``**kwargs`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.0 diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 4ea6f6e65..8c129ddfe 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -4,8 +4,6 @@ Parametrizing tests ================================================= -.. currentmodule:: _pytest.python - ``pytest`` allows to easily parametrize test functions. For basic docs, see :ref:`parametrize-basics`. @@ -185,7 +183,7 @@ A quick port of "testscenarios" Here is a quick port to run tests configured with :pypi:`testscenarios`, an add-on from Robert Collins for the standard unittest framework. We only have to work a bit to construct the correct arguments for pytest's -:py:func:`Metafunc.parametrize`: +:py:func:`Metafunc.parametrize `: .. code-block:: python diff --git a/doc/en/explanation/fixtures.rst b/doc/en/explanation/fixtures.rst index 194e57649..322718873 100644 --- a/doc/en/explanation/fixtures.rst +++ b/doc/en/explanation/fixtures.rst @@ -162,7 +162,7 @@ A note about fixture cleanup ---------------------------- pytest does not do any special processing for :data:`SIGTERM ` and -:data:`SIGQUIT ` signals (:data:`SIGINT ` is handled naturally +``SIGQUIT`` signals (:data:`SIGINT ` is handled naturally by the Python runtime via :class:`KeyboardInterrupt`), so fixtures that manage external resources which are important to be cleared when the Python process is terminated (by those signals) might leak resources. diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 3bf4527cf..43260815b 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -11,8 +11,6 @@ funcarg mechanism, see :ref:`historical funcargs and pytest.funcargs`. If you are new to pytest, then you can simply ignore this section and read the other sections. -.. currentmodule:: _pytest - Shortcomings of the previous ``pytest_funcarg__`` mechanism -------------------------------------------------------------- diff --git a/doc/en/historical-notes.rst b/doc/en/historical-notes.rst index 29ebbd5d1..ae32c28f3 100644 --- a/doc/en/historical-notes.rst +++ b/doc/en/historical-notes.rst @@ -112,7 +112,7 @@ More details can be found in the :pull:`original PR <3317>`. .. note:: in a future major release of pytest we will introduce class based markers, - at which point markers will no longer be limited to instances of :py:class:`~_pytest.mark.Mark`. + at which point markers will no longer be limited to instances of :py:class:`~pytest.Mark`. cache plugin integrated into the core diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 0390230b8..ba6730587 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -382,8 +382,6 @@ warnings: a WarningsRecorder instance. To view the recorded warnings, you can iterate over this instance, call ``len`` on it to get the number of recorded warnings, or index into it to get a particular recorded warning. -.. currentmodule:: _pytest.warnings - Full API: :class:`~_pytest.recwarn.WarningsRecorder`. .. _`warns use cases`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index b2fb24b3f..35b06c519 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1271,7 +1271,7 @@ configured in multiple ways. Extending the previous example, we can flag the fixture to create two ``smtp_connection`` fixture instances which will cause all tests using the fixture to run twice. The fixture function gets access to each parameter -through the special :py:class:`request ` object: +through the special :py:class:`request ` object: .. code-block:: python diff --git a/doc/en/how-to/writing_hook_functions.rst b/doc/en/how-to/writing_hook_functions.rst index ec290349c..5d0a52f9d 100644 --- a/doc/en/how-to/writing_hook_functions.rst +++ b/doc/en/how-to/writing_hook_functions.rst @@ -171,13 +171,11 @@ Declaring new hooks This is a quick overview on how to add new hooks and how they work in general, but a more complete overview can be found in `the pluggy documentation `__. -.. currentmodule:: _pytest.hookspec - Plugins and ``conftest.py`` files may declare new hooks that can then be implemented by other plugins in order to alter behaviour or interact with the new plugin: -.. autofunction:: pytest_addhooks +.. autofunction:: _pytest.hookspec.pytest_addhooks :noindex: Hooks are usually declared as do-nothing functions that contain only diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index 01f825222..078933de6 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -11,9 +11,6 @@ Fixtures reference .. seealso:: :ref:`about-fixtures` .. seealso:: :ref:`how-to-fixtures` - -.. currentmodule:: _pytest.python - .. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection @@ -76,15 +73,13 @@ Built-in fixtures :class:`pathlib.Path` objects. :fixture:`tmpdir` - Provide a :class:`py.path.local` object to a temporary + Provide a `py.path.local `_ object to a temporary directory which is unique to each test function; replaced by :fixture:`tmp_path`. - .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html - :fixture:`tmpdir_factory` Make session-scoped temporary directories and return - :class:`py.path.local` objects; + ``py.path.local`` objects; replaced by :fixture:`tmp_path_factory`. diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 81b8bffbc..ebdcaedce 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -588,7 +588,7 @@ if sys.version_info >= (3, 11) or TYPE_CHECKING: @final class CaptureResult(NamedTuple, Generic[AnyStr]): - """The result of :method:`CaptureFixture.readouterr`.""" + """The result of :method:`caplog.readouterr() `.""" out: AnyStr err: AnyStr @@ -598,7 +598,7 @@ else: class CaptureResult( collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] ): - """The result of :method:`CaptureFixture.readouterr`.""" + """The result of :method:`caplog.readouterr() `.""" __slots__ = () diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 461b8afdf..331abb85d 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -98,7 +98,7 @@ class Parser: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :py:func:`add_argument() + Same attributes as the argparse library's :meth:`add_argument() ` function accepts. After command line parsing, options are available on the pytest config @@ -400,7 +400,7 @@ class OptionGroup: :param opts: Option names, can be short or long options. :param attrs: - Same attributes as the argparse library's :py:func:`add_argument() + Same attributes as the argparse library's :meth:`add_argument() ` function accepts. """ conflict = set(opts).intersection( diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 11878d1b0..014ce47ad 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -96,8 +96,8 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> `. :param pytest.PytestPluginManager pluginmanager: - The pytest plugin manager, which can be used to install :py:func:`hookspec`'s - or :py:func:`hookimpl`'s and allow one plugin to call another plugin's hooks + The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s + or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks to change how command line options are added. Options can later be accessed through the diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 8df0a5163..4876a083a 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -88,7 +88,6 @@ class Testdir: return self._pytester.chdir() def finalize(self) -> None: - """See :meth:`Pytester._finalize`.""" return self._pytester._finalize() def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: @@ -269,7 +268,7 @@ class LegacyTestdirPlugin: @final @dataclasses.dataclass class TempdirFactory: - """Backward compatibility wrapper that implements :class:`py.path.local` + """Backward compatibility wrapper that implements ``py.path.local`` for :class:`TempPathFactory`. .. note:: @@ -288,11 +287,11 @@ class TempdirFactory: self._tmppath_factory = tmppath_factory def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object.""" + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) def getbasetemp(self) -> LEGACY_PATH: - """Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object.""" + """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" return legacy_path(self._tmppath_factory.getbasetemp().resolve()) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 246a4ab6e..1c6bb923b 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -522,7 +522,7 @@ class LogCaptureFixture: The levels of the loggers changed by this function will be restored to their initial values at the end of the test. - Will enable the requested logging level if it was disabled via :meth:`logging.disable`. + Will enable the requested logging level if it was disabled via :func:`logging.disable`. :param level: The level. :param logger: The logger to update. If not given, the root logger. @@ -546,7 +546,7 @@ class LogCaptureFixture: the end of the 'with' statement the level is restored to its original value. - Will enable the requested logging level if it was disabled via :meth:`logging.disable`. + Will enable the requested logging level if it was disabled via :func:`logging.disable`. :param level: The level. :param logger: The logger to update. If not given, the root logger. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f93e9c94a..d388758a2 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1044,7 +1044,7 @@ class Pytester: The calling test instance (class containing the test method) must provide a ``.getrunner()`` method which should return a runner which can run the test protocol for a single item, e.g. - :py:func:`_pytest.runner.runtestprotocol`. + ``_pytest.runner.runtestprotocol``. """ # used from runner functional tests item = self.getitem(source) @@ -1395,7 +1395,7 @@ class Pytester: :param stdin: Optional standard input. - - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls + - If it is ``CLOSE_STDIN`` (Default), then this method calls :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and the standard input is closed immediately after the new command is started. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 07db0f234..53e23ebff 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -825,8 +825,6 @@ def raises( # noqa: F811 When using :py:func:`pytest.raises` as a function, you can use: ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - .. currentmodule:: _pytest._code - Use ``pytest.raises`` as a context manager, which will capture the exception of the given type, or any of its subclasses:: From 49188833367829edabb0e90ba4cc39e2dfacb70f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 7 Dec 2023 11:03:52 +0200 Subject: [PATCH 009/104] python_api: remove unused function `_non_numeric_type_error` --- src/_pytest/python_api.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 53e23ebff..643f04b97 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -30,15 +30,6 @@ if TYPE_CHECKING: from numpy import ndarray -def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: - at_str = f" at {at}" if at else "" - return TypeError( - "cannot make approximate comparisons to non-numeric values: {!r} {}".format( - value, at_str - ) - ) - - def _compare_approx( full_object: object, message_data: Sequence[Tuple[str, str, str]], From f76af423b08719a26a6505f61ced12f39a911d4d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 7 Dec 2023 10:41:51 +0200 Subject: [PATCH 010/104] doc/reference: document `FixtureLookupError` --- doc/en/example/simple.rst | 2 +- doc/en/reference/reference.rst | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index b798e711a..943342ff1 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -168,7 +168,7 @@ Now we'll get feedback on a bad argument: If you need to provide more detailed error messages, you can use the -``type`` parameter and raise ``pytest.UsageError``: +``type`` parameter and raise :exc:`pytest.UsageError`: .. code-block:: python diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 2dcad7d58..473d71e0e 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1157,7 +1157,10 @@ When set (regardless of value), pytest will use color in terminal output. Exceptions ---------- -.. autoclass:: pytest.UsageError() +.. autoexception:: pytest.UsageError() + :show-inheritance: + +.. autoexception:: pytest.FixtureLookupError() :show-inheritance: .. _`warnings ref`: From ee91d095f6121c119d0ea42b5ee34b46172779ec Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 7 Dec 2023 10:30:17 +0200 Subject: [PATCH 011/104] doc: fix more broken Sphinx references --- doc/en/changelog.rst | 8 ++++---- doc/en/funcarg_compare.rst | 4 ++-- src/_pytest/config/__init__.py | 2 ++ src/_pytest/hookspec.py | 10 +++++----- src/_pytest/nodes.py | 7 ++++--- src/_pytest/python_api.py | 10 +++++----- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 7c2162d62..35ea4fa32 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -1344,7 +1344,7 @@ Features This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future. - Internally, the old :class:`~pytest.Testdir` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface. + Internally, the old ``pytest.Testdir`` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface. - :issue:`7695`: A new hook was added, `pytest_markeval_namespace` which should return a dictionary. @@ -1592,7 +1592,7 @@ Improvements - :issue:`7685`: Added two new attributes :attr:`rootpath ` and :attr:`inipath ` to :class:`~pytest.Config`. - These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir ` and :attr:`inifile ` attributes, + These attributes are :class:`pathlib.Path` versions of the existing ``rootdir`` and ``inifile`` attributes, and should be preferred over them when possible. @@ -2139,7 +2139,7 @@ Bug Fixes parameter when Python is called with the ``-bb`` flag. -- :issue:`7143`: Fix :meth:`pytest.File.from_parent` so it forwards extra keyword arguments to the constructor. +- :issue:`7143`: Fix :meth:`pytest.File.from_parent <_pytest.nodes.Node.from_parent>` so it forwards extra keyword arguments to the constructor. - :issue:`7145`: Classes with broken ``__getattribute__`` methods are displayed correctly during failures. @@ -2458,7 +2458,7 @@ Bug Fixes - :issue:`6597`: Fix node ids which contain a parametrized empty-string variable. -- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest ` etc. +- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's ``testdir.runpytest`` etc. - :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger. diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 43260815b..27def534b 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -44,7 +44,7 @@ There are several limitations and difficulties with this approach: 2. parametrizing the "db" resource is not straight forward: you need to apply a "parametrize" decorator or implement a - :py:func:`~hookspec.pytest_generate_tests` hook + :hook:`pytest_generate_tests` hook calling :py:func:`~pytest.Metafunc.parametrize` which performs parametrization at the places where the resource is used. Moreover, you need to modify the factory to use an @@ -92,7 +92,7 @@ Direct parametrization of funcarg resource factories Previously, funcarg factories could not directly cause parametrization. You needed to specify a ``@parametrize`` decorator on your test function -or implement a ``pytest_generate_tests`` hook to perform +or implement a :hook:`pytest_generate_tests` hook to perform parametrization, i.e. calling a test multiple times with different value sets. pytest-2.3 introduces a decorator for use on the factory itself: diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index ea23c7742..adf1bfd9a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -449,6 +449,7 @@ class PytestPluginManager(PluginManager): def parse_hookimpl_opts( self, plugin: _PluggyPlugin, name: str ) -> Optional[HookimplOpts]: + """:meta private:""" # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes # (see issue #1073). @@ -472,6 +473,7 @@ class PytestPluginManager(PluginManager): ) def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]: + """:meta private:""" opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: method = getattr(module_or_class, name) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 014ce47ad..8a4e29e67 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -55,7 +55,7 @@ hookspec = HookspecMarker("pytest") @hookspec(historic=True) def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: """Called at plugin registration time to allow adding new hooks via a call to - ``pluginmanager.add_hookspecs(module_or_class, prefix)``. + :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. @@ -858,8 +858,8 @@ def pytest_warning_recorded( """Process a warning captured by the internal pytest warnings plugin. :param warning_message: - The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains - the same attributes as the parameters of :py:func:`warnings.showwarning`. + The captured warning. This is the same object produced by :class:`warnings.catch_warnings`, + and contains the same attributes as the parameters of :py:func:`warnings.showwarning`. :param when: Indicates when the warning was captured. Possible values: @@ -940,10 +940,10 @@ def pytest_exception_interact( interactively handled. May be called during collection (see :hook:`pytest_make_collect_report`), - in which case ``report`` is a :class:`CollectReport`. + in which case ``report`` is a :class:`~pytest.CollectReport`. May be called during runtest of an item (see :hook:`pytest_runtest_protocol`), - in which case ``report`` is a :class:`TestReport`. + in which case ``report`` is a :class:`~pytest.TestReport`. This hook is not called if the exception that was raised is an internal exception like ``skip.Exception``. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 4b94f413b..e4eb63996 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,4 +1,5 @@ import os +import pathlib import warnings from functools import cached_property from inspect import signature @@ -176,8 +177,8 @@ class Node(metaclass=NodeMeta): # Implemented in the legacypath plugin. #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage #: for methods not migrated to ``pathlib.Path`` yet, such as - #: :meth:`Item.reportinfo`. Will be deprecated in a future release, prefer - #: using :attr:`path` instead. + #: :meth:`Item.reportinfo `. Will be deprecated in + #: a future release, prefer using :attr:`path` instead. fspath: LEGACY_PATH # Use __slots__ to make attribute access faster. @@ -228,7 +229,7 @@ class Node(metaclass=NodeMeta): if path is None and fspath is None: path = getattr(parent, "path", None) #: Filesystem path where this node was collected from (can be None). - self.path: Path = _imply_path(type(self), path, fspath=fspath) + self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath) # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 643f04b97..f914d70e8 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -797,23 +797,23 @@ def raises( # noqa: F811 ) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: r"""Assert that a code block/function call raises an exception type, or one of its subclasses. - :param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception: + :param expected_exception: The expected exception type, or a tuple if one of multiple possible exception types are expected. Note that subclasses of the passed exceptions will also match. - :kwparam str | typing.Pattern[str] | None match: + :kwparam str | re.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string - representation of the exception and its `PEP-678 ` `__notes__` + representation of the exception and its :pep:`678` `__notes__` using :func:`re.search`. To match a literal string that may contain :ref:`special characters `, the pattern can first be escaped with :func:`re.escape`. - (This is only used when :py:func:`pytest.raises` is used as a context manager, + (This is only used when ``pytest.raises`` is used as a context manager, and passed through to the function otherwise. - When using :py:func:`pytest.raises` as a function, you can use: + When using ``pytest.raises`` as a function, you can use: ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) Use ``pytest.raises`` as a context manager, which will capture the exception of the given From 2aa8743bbeff8a983bc3e26e4f0cac155d112731 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 7 Dec 2023 10:30:21 +0200 Subject: [PATCH 012/104] doc: enable Sphinx nitpicky mode See: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-nitpicky This ensures we have no more broken references. --- doc/en/conf.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/doc/en/conf.py b/doc/en/conf.py index 92607a15a..d3a98015a 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -169,6 +169,50 @@ extlinks = { } +nitpicky = True +nitpick_ignore = [ + # TODO (fix in pluggy?) + ("py:class", "HookCaller"), + ("py:class", "HookspecMarker"), + ("py:exc", "PluginValidationError"), + # Might want to expose/TODO (https://github.com/pytest-dev/pytest/issues/7469) + ("py:class", "ExceptionRepr"), + ("py:class", "Exit"), + ("py:class", "SubRequest"), + ("py:class", "SubRequest"), + ("py:class", "TerminalReporter"), + ("py:class", "_pytest._code.code.TerminalRepr"), + ("py:class", "_pytest.fixtures.FixtureFunctionMarker"), + ("py:class", "_pytest.logging.LogCaptureHandler"), + ("py:class", "_pytest.mark.structures.ParameterSet"), + # Intentionally undocumented/private + ("py:class", "_pytest._code.code.Traceback"), + ("py:class", "_pytest._py.path.LocalPath"), + ("py:class", "_pytest.capture.CaptureResult"), + ("py:class", "_pytest.compat.NotSetType"), + ("py:class", "_pytest.python.PyCollector"), + ("py:class", "_pytest.python.PyobjMixin"), + ("py:class", "_pytest.python_api.RaisesContext"), + ("py:class", "_pytest.recwarn.WarningsChecker"), + ("py:class", "_pytest.reports.BaseReport"), + # Undocumented third parties + ("py:class", "_tracing.TagTracerSub"), + ("py:class", "warnings.WarningMessage"), + # Undocumented type aliases + ("py:class", "LEGACY_PATH"), + ("py:class", "_PluggyPlugin"), + # TypeVars + ("py:class", "_pytest._code.code.E"), + ("py:class", "_pytest.fixtures.FixtureFunction"), + ("py:class", "_pytest.nodes._NodeType"), + ("py:class", "_pytest.python_api.E"), + ("py:class", "_pytest.recwarn.T"), + ("py:class", "_pytest.runner.TResult"), + ("py:obj", "_pytest.fixtures.FixtureValue"), + ("py:obj", "_pytest.stash.T"), +] + + # -- Options for HTML output --------------------------------------------------- sys.path.append(os.path.abspath("_themes")) From 0ae02e2165264dc05d727906023fb21c036ecb0d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 6 Dec 2023 18:27:17 +0200 Subject: [PATCH 013/104] nodes,python: mark abstract node classes as ABCs Fixes #11676 --- changelog/11676.breaking.rst | 3 +++ doc/en/reference/reference.rst | 1 + src/_pytest/fixtures.py | 4 +++- src/_pytest/nodes.py | 15 +++++++++------ src/_pytest/python.py | 3 ++- testing/deprecated_test.py | 10 ++++++++-- .../issue88_initial_file_multinodes/conftest.py | 3 ++- testing/test_collection.py | 6 +++++- testing/test_nodes.py | 6 ++++++ 9 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 changelog/11676.breaking.rst diff --git a/changelog/11676.breaking.rst b/changelog/11676.breaking.rst new file mode 100644 index 000000000..f20efa80d --- /dev/null +++ b/changelog/11676.breaking.rst @@ -0,0 +1,3 @@ +The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`). + +We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 254973709..923ffc69f 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -801,6 +801,7 @@ Node .. autoclass:: _pytest.nodes.Node() :members: + :show-inheritance: Collector ~~~~~~~~~ diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d56274629..89046ddd0 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -135,7 +135,9 @@ def get_scope_node( import _pytest.python if scope is Scope.Function: - return node.getparent(nodes.Item) + # Type ignored because this is actually safe, see: + # https://github.com/python/mypy/issues/4717 + return node.getparent(nodes.Item) # type: ignore[type-abstract] elif scope is Scope.Class: return node.getparent(_pytest.python.Class) elif scope is Scope.Module: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 4b94f413b..6472c2ac6 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,3 +1,4 @@ +import abc import os import warnings from functools import cached_property @@ -121,7 +122,7 @@ def _imply_path( _NodeType = TypeVar("_NodeType", bound="Node") -class NodeMeta(type): +class NodeMeta(abc.ABCMeta): """Metaclass used by :class:`Node` to enforce that direct construction raises :class:`Failed`. @@ -165,7 +166,7 @@ class NodeMeta(type): return super().__call__(*k, **known_kw) -class Node(metaclass=NodeMeta): +class Node(abc.ABC, metaclass=NodeMeta): r"""Base class of :class:`Collector` and :class:`Item`, the components of the test collection tree. @@ -534,7 +535,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i return getattr(node, "fspath", "unknown location"), -1 -class Collector(Node): +class Collector(Node, abc.ABC): """Base class of all collectors. Collector create children through `collect()` and thus iteratively build @@ -544,6 +545,7 @@ class Collector(Node): class CollectError(Exception): """An error during collection, contains a custom message.""" + @abc.abstractmethod def collect(self) -> Iterable[Union["Item", "Collector"]]: """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") @@ -588,7 +590,7 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[ return None -class FSCollector(Collector): +class FSCollector(Collector, abc.ABC): """Base class for filesystem collectors.""" def __init__( @@ -666,14 +668,14 @@ class FSCollector(Collector): return self.session.isinitpath(path) -class File(FSCollector): +class File(FSCollector, abc.ABC): """Base class for collecting tests from a file. :ref:`non-python tests`. """ -class Item(Node): +class Item(Node, abc.ABC): """Base class of all test invocation items. Note that for a single function there might be multiple test invocation items. @@ -739,6 +741,7 @@ class Item(Node): PytestWarning, ) + @abc.abstractmethod def runtest(self) -> None: """Run the test case for this item. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 0985c871d..3dd3026fb 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,4 +1,5 @@ """Python test discovery, setup and run of test functions.""" +import abc import dataclasses import enum import fnmatch @@ -380,7 +381,7 @@ del _EmptyClass # fmt: on -class PyCollector(PyobjMixin, nodes.Collector): +class PyCollector(PyobjMixin, nodes.Collector, abc.ABC): def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 08e193b5c..fcd824d5f 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -257,11 +257,17 @@ def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None: def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: mod = pytester.getmodulecol("") + class MyFile(pytest.File): + def collect(self): + raise NotImplementedError() + with pytest.warns( pytest.PytestDeprecationWarning, - match=re.escape("The (fspath: py.path.local) argument to File is deprecated."), + match=re.escape( + "The (fspath: py.path.local) argument to MyFile is deprecated." + ), ): - pytest.File.from_parent( + MyFile.from_parent( parent=mod.parent, fspath=legacy_path("bla"), ) diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index cb8f5d671..0598eb841 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -11,4 +11,5 @@ def pytest_collect_file(file_path, parent): class MyItem(pytest.Item): - pass + def runtest(self): + raise NotImplementedError() diff --git a/testing/test_collection.py b/testing/test_collection.py index ca2e2b731..b2492f7f2 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -99,7 +99,8 @@ class TestCollector: conftest=""" import pytest class CustomFile(pytest.File): - pass + def collect(self): + return [] def pytest_collect_file(file_path, parent): if file_path.suffix == ".xxx": return CustomFile.from_parent(path=file_path, parent=parent) @@ -1509,6 +1510,9 @@ def test_fscollector_from_parent(pytester: Pytester, request: FixtureRequest) -> super().__init__(*k, **kw) self.x = x + def collect(self): + raise NotImplementedError() + collector = MyCollector.from_parent( parent=request.session, path=pytester.path / "foo", x=10 ) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index df1439e1c..84c377cf9 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -73,6 +73,12 @@ def test_subclassing_both_item_and_collector_deprecated( """Legacy ctor with legacy call # don't wana see""" super().__init__(fspath, parent) + def collect(self): + raise NotImplementedError() + + def runtest(self): + raise NotImplementedError() + with pytest.warns(PytestWarning) as rec: SoWrong.from_parent( request.session, fspath=legacy_path(tmp_path / "broken.txt") From 5381cd083f26b07f38adb41a9837142b0345402b Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 10 Dec 2023 00:20:38 +0000 Subject: [PATCH 014/104] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 186 +++++++++++++++++-------------- 1 file changed, 105 insertions(+), 81 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 2ded9bf9d..dea19eae6 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =3.5.0) - :pypi:`pytest-analyzer` this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system Nov 08, 2023 N/A pytest >=7.3.1 + :pypi:`pytest-analyzer` this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system Dec 06, 2023 N/A pytest >=7.3.1 :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) @@ -105,7 +105,7 @@ This list contains 1351 plugins. :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A - :pypi:`pytest-asyncio` Pytest support for asyncio Nov 27, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-asyncio` Pytest support for asyncio Dec 09, 2023 4 - Beta pytest >=7.0.0 :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Nov 30, 2023 N/A N/A :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) @@ -132,7 +132,7 @@ This list contains 1351 plugins. :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-bandit-xayon` A bandit plugin for pytest Oct 17, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-base-url` pytest plugin for URL based testing Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) - :pypi:`pytest-bdd` BDD for pytest Oct 10, 2023 6 - Mature pytest (>=6.2.0) + :pypi:`pytest-bdd` BDD for pytest Dec 02, 2023 6 - Mature pytest (>=6.2.0) :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Nov 22, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) :pypi:`pytest-bdd-ng` BDD for pytest Jul 01, 2023 4 - Beta pytest (>=5.0) :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports Nov 15, 2023 N/A pytest >=7.1.3 @@ -190,7 +190,7 @@ This list contains 1351 plugins. :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Dec 07, 2023 N/A N/A :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A @@ -200,7 +200,7 @@ This list contains 1351 plugins. :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Sep 22, 2023 N/A pytest :pypi:`pytest-checkdocs` check the README when running tests Jul 30, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) + :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Dec 04, 2023 5 - Production/Stable pytest >=2.9.2 :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A :pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest>=7.0 @@ -252,7 +252,7 @@ This list contains 1351 plugins. :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Nov 14, 2023 3 - Alpha pytest - :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Jun 23, 2023 4 - Beta pytest>=7.1.2 + :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 08, 2023 4 - Beta pytest>=7.3.2 :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A :pypi:`pytest-cov` Pytest plugin for measuring coverage. May 24, 2023 5 - Production/Stable pytest (>=4.6) @@ -328,7 +328,7 @@ This list contains 1351 plugins. :pypi:`pytest-diffeo` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' :pypi:`pytest-difido` PyTest plugin for generating Difido reports Oct 23, 2022 4 - Beta pytest (>=4.0.0) - :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Jun 23, 2023 4 - Beta pytest>=7.1.2 + :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 05, 2023 4 - Beta pytest>=7.3.2 :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 @@ -379,7 +379,7 @@ This list contains 1351 plugins. :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Aug 11, 2023 3 - Alpha pytest >=4.6 :pypi:`pytest-dogu-report` pytest plugin for dogu report Jul 07, 2023 N/A N/A - :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Nov 20, 2023 N/A N/A + :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Dec 05, 2023 N/A N/A :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) :pypi:`pytest-donde` record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script. Oct 01, 2023 4 - Beta pytest >=7.3.1 :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) @@ -403,21 +403,21 @@ This list contains 1351 plugins. :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" :pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A - :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A + :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Dec 05, 2023 5 - Production/Stable pytest >=2.2 :pypi:`pytest-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Sep 13, 2023 5 - Production/Stable pytest >=7.0 :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Nov 27, 2023 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Nov 27, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Nov 27, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Dec 04, 2023 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Dec 04, 2023 5 - Production/Stable N/A :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) @@ -531,12 +531,12 @@ This list contains 1351 plugins. :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A - :pypi:`pytest-gee` The Python plugin for your GEE based packages. Nov 22, 2023 3 - Alpha pytest; extra == 'test' + :pypi:`pytest-gee` The Python plugin for your GEE based packages. Dec 04, 2023 3 - Alpha pytest :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Nov 20, 2023 N/A N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Dec 05, 2023 N/A N/A :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A @@ -574,7 +574,7 @@ This list contains 1351 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Nov 20, 2023 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 01, 2023 3 - Alpha pytest ==7.4.3 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 09, 2023 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A :pypi:`pytest-hot-reloading` Dec 01, 2023 N/A N/A @@ -606,7 +606,7 @@ This list contains 1351 plugins. :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Aug 31, 2023 3 - Alpha pytest (>=7.0.0,<8.0.0) :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta pytest>=7.1 - :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Sep 20, 2023 4 - Beta pytest + :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Dec 05, 2023 4 - Beta pytest :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Jun 23, 2023 5 - Production/Stable N/A :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A @@ -634,7 +634,7 @@ This list contains 1351 plugins. :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest May 22, 2023 N/A pytest (>=5.2) :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Nov 29, 2023 4 - Beta pytest + :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Dec 05, 2023 4 - Beta pytest :pypi:`pytest-invenio` Pytest fixtures for Invenio. Oct 31, 2023 5 - Production/Stable pytest <7.2.0,>=6 :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Mar 20, 2013 2 - Pre-Alpha N/A @@ -661,7 +661,7 @@ This list contains 1351 plugins. :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0) :pypi:`pytest-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0 :pypi:`pytest-jtr` pytest plugin supporting json test report output Nov 29, 2022 N/A pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Mar 30, 2023 4 - Beta pytest + :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Dec 05, 2023 4 - Beta pytest :pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Jun 14, 2023 N/A pytest :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest @@ -690,7 +690,7 @@ This list contains 1351 plugins. :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Jul 11, 2022 4 - Beta N/A + :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Dec 06, 2023 4 - Beta N/A :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest @@ -738,7 +738,7 @@ This list contains 1351 plugins. :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. Oct 08, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 - :pypi:`pytest-meilisearch` Pytest helpers for testing projects using Meilisearch Nov 06, 2023 N/A pytest (>=7.4.3) + :pypi:`pytest-meilisearch` Pytest helpers for testing projects using Meilisearch Dec 07, 2023 N/A pytest (>=7.4.3) :pypi:`pytest-memlog` Log memory usage during tests May 03, 2023 N/A pytest (>=7.3.0,<8.0.0) :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A :pypi:`pytest-memray` A simple plugin to use with pytest Aug 23, 2023 N/A pytest>=7.2 @@ -749,10 +749,11 @@ This list contains 1351 plugins. :pypi:`pytest-messenger` Pytest to Slack reporting plugin Nov 24, 2022 5 - Production/Stable N/A :pypi:`pytest-metadata` pytest plugin for test session metadata May 27, 2023 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest - :pypi:`pytest-mh` Pytest multihost plugin Nov 21, 2023 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Dec 07, 2023 N/A pytest :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 06, 2023 N/A pytest >=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) @@ -832,6 +833,7 @@ This list contains 1351 plugins. :pypi:`pytest-ogsm-plugin` 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 May 16, 2023 N/A N/A :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jun 14, 2022 5 - Production/Stable pytest (<7.1); python_version <= "3.6" + :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 05, 2023 4 - Beta N/A :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Oct 01, 2023 N/A pytest @@ -942,7 +944,7 @@ This list contains 1351 plugins. :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Jan 05, 2023 3 - Alpha N/A :pypi:`pytest-pylint` pytest plugin to check source code with pylint Oct 06, 2023 5 - Production/Stable pytest >=7.0 :pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A - :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Nov 18, 2023 N/A pytest + :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Dec 09, 2023 N/A pytest :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0) @@ -983,7 +985,7 @@ This list contains 1351 plugins. :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Nov 30, 2023 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A - :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jul 31, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 2023 4 - Beta pytest>=3.5.0 :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Apr 19, 2023 5 - Production/Stable pytest (>=6.2) :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest @@ -1013,7 +1015,7 @@ This list contains 1351 plugins. :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility May 22, 2023 3 - Alpha pytest :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Oct 17, 2023 N/A pytest >=3.8.0 + :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Dec 06, 2023 N/A pytest >=3.8.0 :pypi:`pytest-report-stream` A pytest plugin which allows to stream test reports at runtime Oct 22, 2023 4 - Beta N/A :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) @@ -1059,7 +1061,6 @@ This list contains 1351 plugins. :pypi:`pytest-ruff` pytest plugin to check ruff requirements. Oct 31, 2023 4 - Beta N/A :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A - :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution Feb 25, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' :pypi:`pytest-run-subprocess` Pytest Plugin for running and testing subprocesses. Nov 12, 2022 5 - Production/Stable pytest :pypi:`pytest-runtime-types` Checks type annotations on runtime while running tests. Feb 09, 2023 N/A pytest :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A pytest>=5.0.0 @@ -1073,7 +1074,7 @@ This list contains 1351 plugins. :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 01, 2023 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 08, 2023 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) @@ -1082,7 +1083,7 @@ This list contains 1351 plugins. :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) :pypi:`pytest-selenium` pytest plugin for Selenium Nov 20, 2023 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 01, 2023 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 08, 2023 5 - Production/Stable N/A :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A @@ -1125,6 +1126,7 @@ This list contains 1351 plugins. :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest :pypi:`pytest-smtp4dev` Plugin for smtp4dev API Jun 27, 2023 5 - Production/Stable N/A :pypi:`pytest-smtpd` An SMTP server for testing built on aiosmtpd May 15, 2023 N/A pytest + :pypi:`pytest-smtp-test-server` pytest plugin for using \`smtp-test-server\` as a fixture Dec 03, 2023 2 - Pre-Alpha pytest (>=7.4.3,<8.0.0) :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Apr 23, 2022 4 - Beta pytest (>=3.0.0) @@ -1203,6 +1205,7 @@ This list contains 1351 plugins. :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) + :pypi:`pytest-tcp` A Pytest plugin for test prioritization Dec 04, 2023 4 - Beta pytest >=7.4.3 :pypi:`pytest-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) :pypi:`pytest-tdd` run pytest on a python module Aug 18, 2023 4 - Beta N/A :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A @@ -1264,7 +1267,7 @@ This list contains 1351 plugins. :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Oct 26, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest :pypi:`pytest-tmnet` A small example package Mar 01, 2022 N/A N/A - :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Apr 03, 2022 N/A pytest + :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Dec 08, 2023 N/A pytest :pypi:`pytest-tmpfs` A pytest plugin that helps you on using a temporary filesystem for testing. Aug 29, 2022 N/A pytest :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Aug 12, 2022 N/A N/A :pypi:`pytest-tmux` A pytest plugin that enables tmux driven tests Apr 22, 2023 4 - Beta N/A @@ -1289,7 +1292,7 @@ This list contains 1351 plugins. :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-tst` Customize pytest options, output and exit code to make it compatible with tst Apr 27, 2022 N/A pytest (>=5.0.0) :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-tui` Text User Interface (TUI) and HTML report for Pytest test runs Jun 12, 2023 4 - Beta N/A + :pypi:`pytest-tui` Text User Interface (TUI) and HTML report for Pytest test runs Dec 08, 2023 4 - Beta N/A :pypi:`pytest-tutorials` Mar 11, 2023 N/A N/A :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A :pypi:`pytest-twisted` A twisted plugin for pytest. Oct 16, 2022 5 - Production/Stable pytest (>=2.3) @@ -1622,7 +1625,7 @@ This list contains 1351 plugins. Static code checks used at Alphamoon :pypi:`pytest-analyzer` - *last release*: Nov 08, 2023, + *last release*: Dec 06, 2023, *status*: N/A, *requires*: pytest >=7.3.1 @@ -1888,9 +1891,9 @@ This list contains 1351 plugins. Pytest fixtures for async generators :pypi:`pytest-asyncio` - *last release*: Nov 27, 2023, + *last release*: Dec 09, 2023, *status*: 4 - Beta, - *requires*: pytest (>=7.0.0) + *requires*: pytest >=7.0.0 Pytest support for asyncio @@ -2077,7 +2080,7 @@ This list contains 1351 plugins. pytest plugin for URL based testing :pypi:`pytest-bdd` - *last release*: Oct 10, 2023, + *last release*: Dec 02, 2023, *status*: 6 - Mature, *requires*: pytest (>=6.2.0) @@ -2483,7 +2486,7 @@ This list contains 1351 plugins. Pytest plugin with server for catching HTTP requests. :pypi:`pytest-celery` - *last release*: May 06, 2021, + *last release*: Dec 07, 2023, *status*: N/A, *requires*: N/A @@ -2553,9 +2556,9 @@ This list contains 1351 plugins. check the README when running tests :pypi:`pytest-checkipdb` - *last release*: Jul 22, 2020, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.9.2) + *requires*: pytest >=2.9.2 plugin to check if there are ipdb debugs left @@ -2917,9 +2920,9 @@ This list contains 1351 plugins. The pytest plugin for your copier templates 📒 :pypi:`pytest-copier` - *last release*: Jun 23, 2023, + *last release*: Dec 08, 2023, *status*: 4 - Beta, - *requires*: pytest>=7.1.2 + *requires*: pytest>=7.3.2 A pytest plugin to help testing Copier templates @@ -3449,9 +3452,9 @@ This list contains 1351 plugins. PyTest plugin for generating Difido reports :pypi:`pytest-dir-equal` - *last release*: Jun 23, 2023, + *last release*: Dec 05, 2023, *status*: 4 - Beta, - *requires*: pytest>=7.1.2 + *requires*: pytest>=7.3.2 pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing @@ -3806,7 +3809,7 @@ This list contains 1351 plugins. pytest plugin for dogu report :pypi:`pytest-dogu-sdk` - *last release*: Nov 20, 2023, + *last release*: Dec 05, 2023, *status*: N/A, *requires*: N/A @@ -3974,9 +3977,9 @@ This list contains 1351 plugins. Pytest execution on EC2 instance :pypi:`pytest-echo` - *last release*: Jan 08, 2020, + *last release*: Dec 05, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest >=2.2 pytest plugin with mechanisms for echoing environment variables, package version and generic attributes @@ -4023,56 +4026,56 @@ This list contains 1351 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Nov 27, 2023, + *last release*: Dec 04, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4870,9 +4873,9 @@ This list contains 1351 plugins. Uses gcov to measure test coverage of a C library :pypi:`pytest-gee` - *last release*: Nov 22, 2023, + *last release*: Dec 04, 2023, *status*: 3 - Alpha, - *requires*: pytest; extra == 'test' + *requires*: pytest The Python plugin for your GEE based packages. @@ -4905,7 +4908,7 @@ This list contains 1351 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Nov 20, 2023, + *last release*: Dec 05, 2023, *status*: N/A, *requires*: N/A @@ -5171,7 +5174,7 @@ This list contains 1351 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Dec 01, 2023, + *last release*: Dec 09, 2023, *status*: 3 - Alpha, *requires*: pytest ==7.4.3 @@ -5395,7 +5398,7 @@ This list contains 1351 plugins. A plugin to sent pytest results to an Ibutsu server :pypi:`pytest-icdiff` - *last release*: Sep 20, 2023, + *last release*: Dec 05, 2023, *status*: 4 - Beta, *requires*: pytest @@ -5591,7 +5594,7 @@ This list contains 1351 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. :pypi:`pytest-interface-tester` - *last release*: Nov 29, 2023, + *last release*: Dec 05, 2023, *status*: 4 - Beta, *requires*: pytest @@ -5780,7 +5783,7 @@ This list contains 1351 plugins. pytest plugin supporting json test report output :pypi:`pytest-jupyter` - *last release*: Mar 30, 2023, + *last release*: Dec 05, 2023, *status*: 4 - Beta, *requires*: pytest @@ -5983,7 +5986,7 @@ This list contains 1351 plugins. A python-libfaketime plugin for pytest. :pypi:`pytest-libiio` - *last release*: Jul 11, 2022, + *last release*: Dec 06, 2023, *status*: 4 - Beta, *requires*: N/A @@ -6319,7 +6322,7 @@ This list contains 1351 plugins. A pytest plugin to make a test results report with Markdown table format. :pypi:`pytest-meilisearch` - *last release*: Nov 06, 2023, + *last release*: Dec 07, 2023, *status*: N/A, *requires*: pytest (>=7.4.3) @@ -6396,7 +6399,7 @@ This list contains 1351 plugins. Custom metrics report for pytest :pypi:`pytest-mh` - *last release*: Nov 21, 2023, + *last release*: Dec 07, 2023, *status*: N/A, *requires*: pytest @@ -6423,6 +6426,13 @@ This list contains 1351 plugins. A plugin to test mp + :pypi:`pytest-minio-mock` + *last release*: Dec 06, 2023, + *status*: N/A, + *requires*: pytest >=5.0.0 + + A pytest plugin for mocking Minio S3 interactions + :pypi:`pytest-missing-fixtures` *last release*: Oct 14, 2020, *status*: 4 - Beta, @@ -6976,6 +6986,13 @@ This list contains 1351 plugins. Use @pytest.mark.only to run a single test + :pypi:`pytest-oof` + *last release*: Dec 05, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A Pytest plugin providing structured, programmatic access to a test run's results + :pypi:`pytest-oot` *last release*: Sep 18, 2016, *status*: 4 - Beta, @@ -7747,7 +7764,7 @@ This list contains 1351 plugins. Record PyMySQL queries and mock with the stored data. :pypi:`pytest-pyodide` - *last release*: Nov 18, 2023, + *last release*: Dec 09, 2023, *status*: N/A, *requires*: pytest @@ -8034,7 +8051,7 @@ This list contains 1351 plugins. Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. :pypi:`pytest-recording` - *last release*: Jul 31, 2023, + *last release*: Dec 06, 2023, *status*: 4 - Beta, *requires*: pytest>=3.5.0 @@ -8244,7 +8261,7 @@ This list contains 1351 plugins. pytest plugin for adding tests' parameters to junit report :pypi:`pytest-reportportal` - *last release*: Oct 17, 2023, + *last release*: Dec 06, 2023, *status*: N/A, *requires*: pytest >=3.8.0 @@ -8565,13 +8582,6 @@ This list contains 1351 plugins. implement a --failed option for pytest - :pypi:`pytest-runner` - *last release*: Feb 25, 2022, - *status*: 5 - Production/Stable, - *requires*: pytest (>=6) ; extra == 'testing' - - Invoke py.test as distutils command with dependency resolution - :pypi:`pytest-run-subprocess` *last release*: Nov 12, 2022, *status*: 5 - Production/Stable, @@ -8664,7 +8674,7 @@ This list contains 1351 plugins. :pypi:`pytest-sbase` - *last release*: Dec 01, 2023, + *last release*: Dec 08, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -8727,7 +8737,7 @@ This list contains 1351 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Dec 01, 2023, + *last release*: Dec 08, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -9027,6 +9037,13 @@ This list contains 1351 plugins. An SMTP server for testing built on aiosmtpd + :pypi:`pytest-smtp-test-server` + *last release*: Dec 03, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest (>=7.4.3,<8.0.0) + + pytest plugin for using \`smtp-test-server\` as a fixture + :pypi:`pytest-snail` *last release*: Nov 04, 2019, *status*: 3 - Alpha, @@ -9573,6 +9590,13 @@ This list contains 1351 plugins. tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used + :pypi:`pytest-tcp` + *last release*: Dec 04, 2023, + *status*: 4 - Beta, + *requires*: pytest >=7.4.3 + + A Pytest plugin for test prioritization + :pypi:`pytest-tcpclient` *last release*: Nov 16, 2022, *status*: N/A, @@ -10001,7 +10025,7 @@ This list contains 1351 plugins. A small example package :pypi:`pytest-tmp-files` - *last release*: Apr 03, 2022, + *last release*: Dec 08, 2023, *status*: N/A, *requires*: pytest @@ -10176,7 +10200,7 @@ This list contains 1351 plugins. Test Class Base :pypi:`pytest-tui` - *last release*: Jun 12, 2023, + *last release*: Dec 08, 2023, *status*: 4 - Beta, *requires*: N/A From f411c8d6d786f6fe53e32eb5c132312da10e2182 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 30 Jun 2023 10:56:58 +0300 Subject: [PATCH 015/104] main: add `with_parents` parameter to `isinitpath` Will be used in upcoming commit. --- src/_pytest/main.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 5cee8e89b..f7b34ded8 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -499,6 +499,7 @@ class Session(nodes.FSCollector): self.shouldfail: Union[bool, str] = False self.trace = config.trace.root.get("collection") self._initialpaths: FrozenSet[Path] = frozenset() + self._initialpaths_with_parents: FrozenSet[Path] = frozenset() self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) @@ -549,10 +550,29 @@ class Session(nodes.FSCollector): pytest_collectreport = pytest_runtest_logreport - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: + def isinitpath( + self, + path: Union[str, "os.PathLike[str]"], + *, + with_parents: bool = False, + ) -> bool: + """Is path an initial path? + + An initial path is a path pytest starts with, e.g. given on the command + line. + + :param with_parents: + If set, also return True if the path is a parent of an initial path. + + .. versionchanged:: 8.0 + Added the ``with_parents`` parameter. + """ # Optimization: Path(Path(...)) is much slower than isinstance. path_ = path if isinstance(path, Path) else Path(path) - return path_ in self._initialpaths + if with_parents: + return path_ in self._initialpaths_with_parents + else: + return path_ in self._initialpaths def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: # Optimization: Path(Path(...)) is much slower than isinstance. @@ -667,6 +687,7 @@ class Session(nodes.FSCollector): items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: initialpaths: List[Path] = [] + initialpaths_with_parents: List[Path] = [] for arg in args: fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, @@ -675,7 +696,10 @@ class Session(nodes.FSCollector): ) self._initial_parts.append((fspath, parts)) initialpaths.append(fspath) + initialpaths_with_parents.append(fspath) + initialpaths_with_parents.extend(fspath.parents) self._initialpaths = frozenset(initialpaths) + self._initialpaths_with_parents = frozenset(initialpaths_with_parents) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 From 385796ba494e7ae65d55892d4a358b371ac7a6b6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 2 Jun 2023 16:03:39 +0300 Subject: [PATCH 016/104] Rework Session and Package collection Fix #7777. --- changelog/7777.breaking.rst | 90 ++++ doc/en/deprecations.rst | 85 ++++ doc/en/example/conftest.py | 2 +- doc/en/example/customdirectory.rst | 77 ++++ doc/en/example/customdirectory/conftest.py | 28 ++ doc/en/example/customdirectory/pytest.ini | 0 .../customdirectory/tests/manifest.json | 6 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + doc/en/example/index.rst | 1 + doc/en/reference/reference.rst | 14 + src/_pytest/cacheprovider.py | 4 +- src/_pytest/config/__init__.py | 2 - src/_pytest/hookspec.py | 24 ++ src/_pytest/main.py | 398 +++++++++++------- src/_pytest/mark/__init__.py | 13 +- src/_pytest/nodes.py | 18 + src/_pytest/pathlib.py | 10 +- src/_pytest/python.py | 115 +++-- src/pytest/__init__.py | 4 + testing/acceptance_test.py | 5 +- .../customdirectory/conftest.py | 22 + .../customdirectory/pytest.ini | 0 .../customdirectory/tests/manifest.json | 6 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + testing/python/collect.py | 105 +++++ testing/python/metafunc.py | 20 +- testing/test_assertion.py | 10 +- testing/test_cacheprovider.py | 36 +- testing/test_collection.py | 107 +++-- testing/test_config.py | 3 +- testing/test_mark.py | 35 +- testing/test_reports.py | 6 +- testing/test_runner.py | 2 +- testing/test_session.py | 23 +- testing/test_terminal.py | 31 +- 39 files changed, 985 insertions(+), 335 deletions(-) create mode 100644 changelog/7777.breaking.rst create mode 100644 doc/en/example/customdirectory.rst create mode 100644 doc/en/example/customdirectory/conftest.py create mode 100644 doc/en/example/customdirectory/pytest.ini create mode 100644 doc/en/example/customdirectory/tests/manifest.json create mode 100644 doc/en/example/customdirectory/tests/test_first.py create mode 100644 doc/en/example/customdirectory/tests/test_second.py create mode 100644 doc/en/example/customdirectory/tests/test_third.py create mode 100644 testing/example_scripts/customdirectory/conftest.py create mode 100644 testing/example_scripts/customdirectory/pytest.ini create mode 100644 testing/example_scripts/customdirectory/tests/manifest.json create mode 100644 testing/example_scripts/customdirectory/tests/test_first.py create mode 100644 testing/example_scripts/customdirectory/tests/test_second.py create mode 100644 testing/example_scripts/customdirectory/tests/test_third.py diff --git a/changelog/7777.breaking.rst b/changelog/7777.breaking.rst new file mode 100644 index 000000000..d38fea330 --- /dev/null +++ b/changelog/7777.breaking.rst @@ -0,0 +1,90 @@ +Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. +This is analogous to the existing :class:`pytest.File` for file nodes. + +Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. +A ``Package`` represents a filesystem directory which is a Python package, +i.e. contains an ``__init__.py`` file. + +:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. +Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy. + +Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. +This node represents a filesystem directory, which is not a :class:`pytest.Package`, +i.e. does not contain an ``__init__.py`` file. +Similarly to ``Package``, it only collects the files in its own directory, +while collecting sub-directories as sub-collector nodes. + +Added a new hook :hook:`pytest_collect_directory`, +which is called by filesystem-traversing collector nodes, +such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`, +to create a collector node for a sub-directory. +It is expected to return a subclass of :class:`pytest.Directory`. +This hook allows plugins to :ref:`customize the collection of directories `. + +:class:`pytest.Session` now only collects the initial arguments, without recursing into directories. +This work is now done by the :func:`recursive expansion process ` of directory collector nodes. + +:attr:`session.name ` is now ``""``; previously it was the rootdir directory name. +This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. + +Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. +Previously, files were collected before directories. + +The collection tree now contains directories/packages up to the :ref:`rootdir `, +for initial arguments that are found within the rootdir. +For files outside the rootdir, only the immediate directory/package is collected -- +note however that collecting from outside the rootdir is discouraged. + +As an example, given the following filesystem tree:: + + myroot/ + pytest.ini + top/ + ├── aaa + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── __init__.py + └── test_zzz.py + +the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, +is now the following:: + + + + + + + + + + + + + + + + + + +Previously, it was:: + + + + + + + + + + + + + + + +Code/plugins which rely on a specific shape of the collection tree might need to update. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index ad051fc62..caa7cb3e7 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -495,6 +495,91 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +Collection changes in pytest 8 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. +This is analogous to the existing :class:`pytest.File` for file nodes. + +Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. +A ``Package`` represents a filesystem directory which is a Python package, +i.e. contains an ``__init__.py`` file. + +:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. +Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy. + +:attr:`session.name ` is now ``""``; previously it was the rootdir directory name. +This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. + +Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. +This node represents a filesystem directory, which is not a :class:`pytest.Package`, +i.e. does not contain an ``__init__.py`` file. +Similarly to ``Package``, it only collects the files in its own directory, +while collecting sub-directories as sub-collector nodes. + +Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. +Previously, files were collected before directories. + +The collection tree now contains directories/packages up to the :ref:`rootdir `, +for initial arguments that are found within the rootdir. +For files outside the rootdir, only the immediate directory/package is collected -- +note however that collecting from outside the rootdir is discouraged. + +As an example, given the following filesystem tree:: + + myroot/ + pytest.ini + top/ + ├── aaa + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── __init__.py + └── test_zzz.py + +the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, +is now the following:: + + + + + + + + + + + + + + + + + + +Previously, it was:: + + + + + + + + + + + + + + + +Code/plugins which rely on a specific shape of the collection tree might need to update. + + :class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/example/conftest.py b/doc/en/example/conftest.py index f905738c4..66e70f14d 100644 --- a/doc/en/example/conftest.py +++ b/doc/en/example/conftest.py @@ -1 +1 @@ -collect_ignore = ["nonpython"] +collect_ignore = ["nonpython", "customdirectory"] diff --git a/doc/en/example/customdirectory.rst b/doc/en/example/customdirectory.rst new file mode 100644 index 000000000..1e4d7e370 --- /dev/null +++ b/doc/en/example/customdirectory.rst @@ -0,0 +1,77 @@ +.. _`custom directory collectors`: + +Using a custom directory collector +==================================================== + +By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files, +and :class:`pytest.Dir` for other directories. +If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector, +and use :hook:`pytest_collect_directory` to hook it up. + +.. _`directory manifest plugin`: + +A basic example for a directory manifest file +-------------------------------------------------------------- + +Suppose you want to customize how collection is done on a per-directory basis. +Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file, +which defines how the collection should be done for the directory. +In this example, only a simple list of files is supported, +however you can imagine adding other keys, such as exclusions and globs. + +.. include:: customdirectory/conftest.py + :literal: + +You can create a ``manifest.json`` file and some test files: + +.. include:: customdirectory/tests/manifest.json + :literal: + +.. include:: customdirectory/tests/test_first.py + :literal: + +.. include:: customdirectory/tests/test_second.py + :literal: + +.. include:: customdirectory/tests/test_third.py + :literal: + +An you can now execute the test specification: + +.. code-block:: pytest + + customdirectory $ pytest + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project/customdirectory + configfile: pytest.ini + collected 2 items + + tests/test_first.py . [ 50%] + tests/test_second.py . [100%] + + ============================ 2 passed in 0.12s ============================= + +.. regendoc:wipe + +Notice how ``test_three.py`` was not executed, because it is not listed in the manifest. + +You can verify that your custom collector appears in the collection tree: + +.. code-block:: pytest + + customdirectory $ pytest --collect-only + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y + rootdir: /home/sweet/project/customdirectory + configfile: pytest.ini + collected 2 items + + + + + + + + + ======================== 2 tests collected in 0.12s ======================== diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py new file mode 100644 index 000000000..350893cab --- /dev/null +++ b/doc/en/example/customdirectory/conftest.py @@ -0,0 +1,28 @@ +# content of conftest.py +import json + +import pytest + + +class ManifestDirectory(pytest.Directory): + def collect(self): + # The standard pytest behavior is to loop over all `test_*.py` files and + # call `pytest_collect_file` on each file. This collector instead reads + # the `manifest.json` file and only calls `pytest_collect_file` for the + # files defined there. + manifest_path = self.path / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + ihook = self.ihook + for file in manifest["files"]: + yield from ihook.pytest_collect_file( + file_path=self.path / file, parent=self + ) + + +@pytest.hookimpl +def pytest_collect_directory(path, parent): + # Use our custom collector for directories containing a `mainfest.json` file. + if path.joinpath("manifest.json").is_file(): + return ManifestDirectory.from_parent(parent=parent, path=path) + # Otherwise fallback to the standard behavior. + return None diff --git a/doc/en/example/customdirectory/pytest.ini b/doc/en/example/customdirectory/pytest.ini new file mode 100644 index 000000000..e69de29bb diff --git a/doc/en/example/customdirectory/tests/manifest.json b/doc/en/example/customdirectory/tests/manifest.json new file mode 100644 index 000000000..6ab6d0a52 --- /dev/null +++ b/doc/en/example/customdirectory/tests/manifest.json @@ -0,0 +1,6 @@ +{ + "files": [ + "test_first.py", + "test_second.py" + ] +} diff --git a/doc/en/example/customdirectory/tests/test_first.py b/doc/en/example/customdirectory/tests/test_first.py new file mode 100644 index 000000000..0a78de599 --- /dev/null +++ b/doc/en/example/customdirectory/tests/test_first.py @@ -0,0 +1,3 @@ +# content of test_first.py +def test_1(): + pass diff --git a/doc/en/example/customdirectory/tests/test_second.py b/doc/en/example/customdirectory/tests/test_second.py new file mode 100644 index 000000000..eed724a7d --- /dev/null +++ b/doc/en/example/customdirectory/tests/test_second.py @@ -0,0 +1,3 @@ +# content of test_second.py +def test_2(): + pass diff --git a/doc/en/example/customdirectory/tests/test_third.py b/doc/en/example/customdirectory/tests/test_third.py new file mode 100644 index 000000000..61cf59dc1 --- /dev/null +++ b/doc/en/example/customdirectory/tests/test_third.py @@ -0,0 +1,3 @@ +# content of test_third.py +def test_3(): + pass diff --git a/doc/en/example/index.rst b/doc/en/example/index.rst index 71e855534..e8835aae9 100644 --- a/doc/en/example/index.rst +++ b/doc/en/example/index.rst @@ -32,3 +32,4 @@ The following examples aim at various use cases you might encounter. special pythoncollection nonpython + customdirectory diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index d6f942ad0..a360e1612 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -682,6 +682,8 @@ Collection hooks .. autofunction:: pytest_collection .. hook:: pytest_ignore_collect .. autofunction:: pytest_ignore_collect +.. hook:: pytest_collect_directory +.. autofunction:: pytest_collect_directory .. hook:: pytest_collect_file .. autofunction:: pytest_collect_file .. hook:: pytest_pycollect_makemodule @@ -921,6 +923,18 @@ Config .. autoclass:: pytest.Config() :members: +Dir +~~~ + +.. autoclass:: pytest.Dir() + :members: + +Directory +~~~~~~~~~ + +.. autoclass:: pytest.Directory() + :members: + ExceptionInfo ~~~~~~~~~~~~~ diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 50a474a29..793e796de 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -27,8 +27,8 @@ from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.main import Session +from _pytest.nodes import Directory from _pytest.nodes import File -from _pytest.python import Package from _pytest.reports import TestReport README_CONTENT = """\ @@ -222,7 +222,7 @@ class LFPluginCollWrapper: self, collector: nodes.Collector ) -> Generator[None, CollectReport, CollectReport]: res = yield - if isinstance(collector, (Session, Package)): + if isinstance(collector, (Session, Directory)): # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index adf1bfd9a..e5775546d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -415,8 +415,6 @@ class PytestPluginManager(PluginManager): # session (#9478), often with the same path, so cache it. self._get_directory = lru_cache(256)(_get_directory) - self._duplicatepaths: Set[Path] = set() - # plugins that were explicitly skipped with pytest.skip # list of (module name, skip reason) # previously we would issue a warning when a plugin was skipped, but diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 8a4e29e67..3c65234da 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -284,11 +284,35 @@ def pytest_ignore_collect( """ +@hookspec(firstresult=True) +def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]": + """Create a :class:`~pytest.Collector` for the given directory, or None if + not relevant. + + .. versionadded:: 8.0 + + For best results, the returned collector should be a subclass of + :class:`~pytest.Directory`, but this is not required. + + The new node needs to have the specified ``parent`` as a parent. + + Stops at first non-None result, see :ref:`firstresult`. + + :param path: The path to analyze. + + See :ref:`custom directory collectors` for a simple example of use of this + hook. + """ + + def pytest_collect_file( file_path: Path, path: "LEGACY_PATH", parent: "Collector" ) -> "Optional[Collector]": """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. + For best results, the returned collector should be a subclass of + :class:`~pytest.File`, but this is not required. + The new node needs to have the specified ``parent`` as a parent. :param file_path: The path to analyze. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index f7b34ded8..e4ca05aac 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -12,6 +12,8 @@ from typing import Callable from typing import Dict from typing import final from typing import FrozenSet +from typing import Generator +from typing import Iterable from typing import Iterator from typing import List from typing import Literal @@ -19,8 +21,6 @@ from typing import Optional from typing import overload from typing import Sequence from typing import Tuple -from typing import Type -from typing import TYPE_CHECKING from typing import Union import pluggy @@ -41,17 +41,13 @@ from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import safe_exists -from _pytest.pathlib import visit +from _pytest.pathlib import scandir from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.runner import collect_one_node from _pytest.runner import SetupState -if TYPE_CHECKING: - from _pytest.python import Package - - def pytest_addoption(parser: Parser) -> None: parser.addini( "norecursedirs", @@ -414,6 +410,12 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo return None +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> Optional[nodes.Collector]: + return Dir.from_parent(parent, path=path) + + def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: @@ -470,7 +472,61 @@ class _bestrelpath_cache(Dict[Path, str]): @final -class Session(nodes.FSCollector): +class Dir(nodes.Directory): + """Collector of files in a file system directory. + + .. versionadded:: 8.0 + + .. note:: + + Python directories with an `__init__.py` file are instead collected by + :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory` + collectors. + """ + + @classmethod + def from_parent( # type: ignore[override] + cls, + parent: nodes.Collector, # type: ignore[override] + *, + path: Path, + ) -> "Dir": + """The public constructor. + + :param parent: The parent collector of this Dir. + :param path: The directory's path. + """ + return super().from_parent(parent=parent, path=path) # type: ignore[no-any-return] + + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + config = self.config + col: Optional[nodes.Collector] + cols: Sequence[nodes.Collector] + for direntry in scandir(self.path): + if direntry.is_dir(): + if direntry.name == "__pycache__": + continue + ihook = self.ihook + path = Path(direntry.path) + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col + + elif direntry.is_file(): + ihook = self.ihook + path = Path(direntry.path) + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols + + +@final +class Session(nodes.Collector): """The root of the collection tree. ``Session`` collects the initial paths given as arguments to pytest. @@ -486,6 +542,7 @@ class Session(nodes.FSCollector): def __init__(self, config: Config) -> None: super().__init__( + name="", path=config.rootpath, fspath=None, parent=None, @@ -500,6 +557,11 @@ class Session(nodes.FSCollector): self.trace = config.trace.root.get("collection") self._initialpaths: FrozenSet[Path] = frozenset() self._initialpaths_with_parents: FrozenSet[Path] = frozenset() + self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: List[Tuple[Path, List[str]]] = [] + self._in_genitems = False + self._collection_cache: Dict[nodes.Collector, CollectReport] = {} + self.items: List[nodes.Item] = [] self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) @@ -550,6 +612,29 @@ class Session(nodes.FSCollector): pytest_collectreport = pytest_runtest_logreport + @hookimpl(wrapper=True) + def pytest_collect_directory( + self, + ) -> Generator[None, Optional[nodes.Collector], Optional[nodes.Collector]]: + col = yield + + # Eagerly load conftests for the directory. + # This is needed because a conftest error needs to happen while + # collecting a collector, so it is caught by its CollectReport. + # Without this, the conftests are loaded inside of genitems itself + # which leads to an internal error. + # This should only be done for genitems; if done unconditionally, it + # will load conftests for non-selected directories which is to be + # avoided. + if self._in_genitems and col is not None: + self.config.pluginmanager._loadconftestmodules( + col.path, + self.config.getoption("importmode"), + rootpath=self.config.rootpath, + ) + + return col + def isinitpath( self, path: Union[str, "os.PathLike[str]"], @@ -558,7 +643,7 @@ class Session(nodes.FSCollector): ) -> bool: """Is path an initial path? - An initial path is a path pytest starts with, e.g. given on the command + An initial path is a path explicitly given to pytest on the command line. :param with_parents: @@ -600,49 +685,36 @@ class Session(nodes.FSCollector): proxy = self.config.hook return proxy - def _recurse(self, direntry: "os.DirEntry[str]") -> bool: - if direntry.name == "__pycache__": - return False - fspath = Path(direntry.path) - ihook = self.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - return True - - def _collectpackage(self, fspath: Path) -> Optional["Package"]: - from _pytest.python import Package - - ihook = self.gethookproxy(fspath) - if not self.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return None - - pkg: Package = Package.from_parent(self, path=fspath) - return pkg - - def _collectfile( - self, fspath: Path, handle_dupes: bool = True + def _collect_path( + self, + path: Path, + path_cache: Dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.gethookproxy(fspath) - if not self.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () + """Create a Collector for the given path. - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) + `path_cache` makes it so the same Collectors are returned for the same + path. + """ + if path in path_cache: + return path_cache[path] - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] + if path.is_dir(): + ihook = self.gethookproxy(path.parent) + col: Optional[nodes.Collector] = ihook.pytest_collect_directory( + path=path, parent=self + ) + cols: Sequence[nodes.Collector] = (col,) if col is not None else () + + elif path.is_file(): + ihook = self.gethookproxy(path) + cols = ihook.pytest_collect_file(file_path=path, parent=self) + + else: + # Broken symlink or invalid/missing file. + cols = () + + path_cache[path] = cols + return cols @overload def perform_collect( @@ -678,12 +750,13 @@ class Session(nodes.FSCollector): self.trace("perform_collect", self, args) self.trace.root.indent += 1 - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[Tuple[Path, List[str]]] = [] - self.items: List[nodes.Item] = [] - hook = self.config.hook + self._notfound = [] + self._initial_parts = [] + self._in_genitems = False + self._collection_cache = {} + self.items = [] items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: initialpaths: List[Path] = [] @@ -700,6 +773,7 @@ class Session(nodes.FSCollector): initialpaths_with_parents.extend(fspath.parents) self._initialpaths = frozenset(initialpaths) self._initialpaths_with_parents = frozenset(initialpaths_with_parents) + rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 @@ -708,12 +782,14 @@ class Session(nodes.FSCollector): for arg, collectors in self._notfound: if collectors: errors.append( - f"not found: {arg}\n(no name {arg!r} in any of {collectors!r})" + f"not found: {arg}\n(no match in any of {collectors!r})" ) else: errors.append(f"found no collectors for {arg}") raise UsageError(*errors) + + self._in_genitems = True if not genitems: items = rep.result else: @@ -726,22 +802,35 @@ class Session(nodes.FSCollector): session=self, config=self.config, items=items ) finally: + self._notfound = [] + self._initial_parts = [] + self._in_genitems = False + self._collection_cache = {} hook.pytest_collection_finish(session=self) - self.testscollected = len(items) + if genitems: + self.testscollected = len(items) + return items + def _collect_one_node( + self, + node: nodes.Collector, + handle_dupes: bool = True, + ) -> Tuple[CollectReport, bool]: + if node in self._collection_cache and handle_dupes: + rep = self._collection_cache[node] + return rep, True + else: + rep = collect_one_node(node) + self._collection_cache[node] = rep + return rep, False + def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: - # Keep track of any collected nodes in here, so we don't duplicate fixtures. - node_cache1: Dict[Path, Sequence[nodes.Collector]] = {} - node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {} - - # Keep track of any collected collectors in matchnodes paths, so they - # are not collected more than once. - matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} - - # Directories of pkgs with dunder-init files. - pkg_roots: Dict[Path, "Package"] = {} + # This is a cache for the root directories of the initial paths. + # We can't use collection_cache for Session because of its special + # role as the bootstrapping collector. + path_cache: Dict[Path, Sequence[nodes.Collector]] = {} pm = self.config.pluginmanager @@ -749,108 +838,87 @@ class Session(nodes.FSCollector): self.trace("processing argument", (argpath, names)) self.trace.root.indent += 1 - # Start with a Session root, and delve to argpath item (dir or file) - # and stack all Packages found on the way. - for parent in (argpath, *argpath.parents): - if not pm._is_in_confcutdir(argpath): - break - - if parent.is_dir(): - pkginit = parent / "__init__.py" - if pkginit.is_file() and parent not in node_cache1: - pkg = self._collectpackage(parent) - if pkg is not None: - pkg_roots[parent] = pkg - node_cache1[pkg.path] = [pkg] - - # If it's a directory argument, recurse and look for any Subpackages. - # Let the Package collector deal with subnodes, don't collect here. + # resolve_collection_argument() ensures this. if argpath.is_dir(): assert not names, f"invalid arg {(argpath, names)!r}" - if argpath in pkg_roots: - yield pkg_roots[argpath] + # Match the argpath from the root, e.g. + # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] + paths = [*reversed(argpath.parents), argpath] + # Paths outside of the confcutdir should not be considered, unless + # it's the argpath itself. + while len(paths) > 1 and not pm._is_in_confcutdir(paths[0]): + paths = paths[1:] - for direntry in visit(argpath, self._recurse): - path = Path(direntry.path) - if direntry.is_dir() and self._recurse(direntry): - pkginit = path / "__init__.py" - if pkginit.is_file(): - pkg = self._collectpackage(path) - if pkg is not None: - yield pkg - pkg_roots[path] = pkg + # Start going over the parts from the root, collecting each level + # and discarding all nodes which don't match the level's part. + any_matched_in_initial_part = False + notfound_collectors = [] + work: List[ + Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]] + ] = [(self, paths + names)] + while work: + matchnode, matchparts = work.pop() - elif direntry.is_file(): - if path.parent in pkg_roots: - # Package handles this file. - continue - for x in self._collectfile(path): - key2 = (type(x), x.path) - if key2 in node_cache2: - yield node_cache2[key2] - else: - node_cache2[key2] = x - yield x - else: - assert argpath.is_file() - - if argpath in node_cache1: - col = node_cache1[argpath] - else: - collect_root = pkg_roots.get(argpath.parent, self) - col = collect_root._collectfile(argpath, handle_dupes=False) - if col: - node_cache1[argpath] = col - - matching = [] - work: List[ - Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]] - ] = [(col, names)] - while work: - self.trace("matchnodes", col, names) - self.trace.root.indent += 1 - - matchnodes, matchnames = work.pop() - for node in matchnodes: - if not matchnames: - matching.append(node) - continue - if not isinstance(node, nodes.Collector): - continue - key = (type(node), node.nodeid) - if key in matchnodes_cache: - rep = matchnodes_cache[key] - else: - rep = collect_one_node(node) - matchnodes_cache[key] = rep - if rep.passed: - submatchnodes = [] - for r in rep.result: - # TODO: Remove parametrized workaround once collection structure contains - # parametrization. - if ( - r.name == matchnames[0] - or r.name.split("[")[0] == matchnames[0] - ): - submatchnodes.append(r) - if submatchnodes: - work.append((submatchnodes, matchnames[1:])) - else: - # Report collection failures here to avoid failing to run some test - # specified in the command line because the module could not be - # imported (#134). - node.ihook.pytest_collectreport(report=rep) - - self.trace("matchnodes finished -> ", len(matching), "nodes") - self.trace.root.indent -= 1 - - if not matching: - report_arg = "::".join((str(argpath), *names)) - self._notfound.append((report_arg, col)) + # Pop'd all of the parts, this is a match. + if not matchparts: + yield matchnode + any_matched_in_initial_part = True continue - yield from matching + # Should have been matched by now, discard. + if not isinstance(matchnode, nodes.Collector): + continue + + # Collect this level of matching. + # Collecting Session (self) is done directly to avoid endless + # recursion to this function. + subnodes: Sequence[Union[nodes.Collector, nodes.Item]] + if isinstance(matchnode, Session): + assert isinstance(matchparts[0], Path) + subnodes = matchnode._collect_path(matchparts[0], path_cache) + else: + # For backward compat, files given directly multiple + # times on the command line should not be deduplicated. + handle_dupes = not ( + len(matchparts) == 1 + and isinstance(matchparts[0], Path) + and matchparts[0].is_file() + ) + rep, duplicate = self._collect_one_node(matchnode, handle_dupes) + if not duplicate and not rep.passed: + # Report collection failures here to avoid failing to + # run some test specified in the command line because + # the module could not be imported (#134). + matchnode.ihook.pytest_collectreport(report=rep) + if not rep.passed: + continue + subnodes = rep.result + + # Prune this level. + any_matched_in_collector = False + for node in subnodes: + # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`. + if isinstance(matchparts[0], Path): + is_match = node.path == matchparts[0] + # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`. + else: + # TODO: Remove parametrized workaround once collection structure contains + # parametrization. + is_match = ( + node.name == matchparts[0] + or node.name.split("[")[0] == matchparts[0] + ) + if is_match: + work.append((node, matchparts[1:])) + any_matched_in_collector = True + + if not any_matched_in_collector: + notfound_collectors.append(matchnode) + + if not any_matched_in_initial_part: + report_arg = "::".join((str(argpath), *names)) + self._notfound.append((report_arg, notfound_collectors)) self.trace.root.indent -= 1 @@ -863,11 +931,17 @@ class Session(nodes.FSCollector): yield node else: assert isinstance(node, nodes.Collector) - rep = collect_one_node(node) + keepduplicates = self.config.getoption("keepduplicates") + # For backward compat, dedup only applies to files. + handle_dupes = not (keepduplicates and isinstance(node, nodes.File)) + rep, duplicate = self._collect_one_node(node, handle_dupes) + if duplicate and not keepduplicates: + return if rep.passed: for subnode in rep.result: yield from self.genitems(subnode) - node.ihook.pytest_collectreport(report=rep) + if not duplicate: + node.ihook.pytest_collectreport(report=rep) def search_pypath(module_name: str) -> str: diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index de46b4c8a..3f97299ea 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -152,12 +152,19 @@ class KeywordMatcher: def from_item(cls, item: "Item") -> "KeywordMatcher": mapped_names = set() - # Add the names of the current item and any parent items. + # Add the names of the current item and any parent items, + # except the Session and root Directory's which are not + # interesting for matching. import pytest for node in item.listchain(): - if not isinstance(node, pytest.Session): - mapped_names.add(node.name) + if isinstance(node, pytest.Session): + continue + if isinstance(node, pytest.Directory) and isinstance( + node.parent, pytest.Session + ): + continue + mapped_names.add(node.name) # Add the names added as extra keywords to current or parent items. mapped_names.update(item.listextrakeywords()) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 183f3c9d9..29efd56f4 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -676,6 +676,24 @@ class File(FSCollector, abc.ABC): """ +class Directory(FSCollector, abc.ABC): + """Base class for collecting files from a directory. + + A basic directory collector does the following: goes over the files and + sub-directories in the directory and creates collectors for them by calling + the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`, + after checking that they are not ignored using + :hook:`pytest_ignore_collect`. + + The default directory collectors are :class:`~pytest.Dir` and + :class:`~pytest.Package`. + + .. versionadded:: 8.0 + + :ref:`custom directory collectors`. + """ + + class Item(Node, abc.ABC): """Base class of all test invocation items. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index e39b3dc8e..4cd635ed7 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -689,10 +689,14 @@ def resolve_package_path(path: Path) -> Optional[Path]: return result -def scandir(path: Union[str, "os.PathLike[str]"]) -> List["os.DirEntry[str]"]: +def scandir( + path: Union[str, "os.PathLike[str]"], + sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name, +) -> List["os.DirEntry[str]"]: """Scan a directory recursively, in breadth-first order. - The returned entries are sorted. + The returned entries are sorted according to the given key. + The default is to sort by name. """ entries = [] with os.scandir(path) as s: @@ -706,7 +710,7 @@ def scandir(path: Union[str, "os.PathLike[str]"]) -> List["os.DirEntry[str]"]: continue raise entries.append(entry) - entries.sort(key=lambda entry: entry.name) + entries.sort(key=sort_key) # type: ignore[arg-type] return entries diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 3dd3026fb..09adb2b9c 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -76,8 +76,7 @@ from _pytest.pathlib import bestrelpath from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import import_path from _pytest.pathlib import ImportPathMismatchError -from _pytest.pathlib import parts -from _pytest.pathlib import visit +from _pytest.pathlib import scandir from _pytest.scope import _ScopeName from _pytest.scope import Scope from _pytest.stash import StashKey @@ -204,6 +203,16 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: return True +def pytest_collect_directory( + path: Path, parent: nodes.Collector +) -> Optional[nodes.Collector]: + pkginit = path / "__init__.py" + if pkginit.is_file(): + pkg: Package = Package.from_parent(parent, path=path) + return pkg + return None + + def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): @@ -659,9 +668,20 @@ class Module(nodes.File, PyCollector): self.obj.__pytest_setup_function = xunit_setup_function_fixture -class Package(nodes.FSCollector): +class Package(nodes.Directory): """Collector for files and directories in a Python packages -- directories - with an `__init__.py` file.""" + with an `__init__.py` file. + + .. note:: + + Directories without an `__init__.py` file are instead collected by + :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory` + collectors. + + .. versionchanged:: 8.0 + + Now inherits from :class:`~pytest.Directory`. + """ def __init__( self, @@ -674,10 +694,9 @@ class Package(nodes.FSCollector): path: Optional[Path] = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. - # nodes.FSCollector.__init__(self, fspath, parent=parent) + # super().__init__(self, fspath, parent=parent) session = parent.session - nodes.FSCollector.__init__( - self, + super().__init__( fspath=fspath, path=path, parent=parent, @@ -685,7 +704,6 @@ class Package(nodes.FSCollector): session=session, nodeid=nodeid, ) - self.name = self.path.name def setup(self) -> None: init_mod = importtestmodule(self.path / "__init__.py", self.config) @@ -705,66 +723,35 @@ class Package(nodes.FSCollector): func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def _recurse(self, direntry: "os.DirEntry[str]") -> bool: - if direntry.name == "__pycache__": - return False - fspath = Path(direntry.path) - ihook = self.session.gethookproxy(fspath.parent) - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return False - return True - - def _collectfile( - self, fspath: Path, handle_dupes: bool = True - ) -> Sequence[nodes.Collector]: - assert ( - fspath.is_file() - ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( - fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink() - ) - ihook = self.session.gethookproxy(fspath) - if not self.session.isinitpath(fspath): - if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): - return () - - if handle_dupes: - keepduplicates = self.config.getoption("keepduplicates") - if not keepduplicates: - duplicate_paths = self.config.pluginmanager._duplicatepaths - if fspath in duplicate_paths: - return () - else: - duplicate_paths.add(fspath) - - return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return] - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - # Always collect the __init__ first. - yield from self._collectfile(self.path / "__init__.py") + # Always collect __init__.py first. + def sort_key(entry: "os.DirEntry[str]") -> object: + return (entry.name != "__init__.py", entry.name) - pkg_prefixes: Set[Path] = set() - for direntry in visit(self.path, recurse=self._recurse): - path = Path(direntry.path) - - # Already handled above. - if direntry.is_file(): - if direntry.name == "__init__.py" and path.parent == self.path: + config = self.config + col: Optional[nodes.Collector] + cols: Sequence[nodes.Collector] + for direntry in scandir(self.path, sort_key): + if direntry.is_dir(): + if direntry.name == "__pycache__": continue + path = Path(direntry.path) + ihook = self.ihook + if not self.session.isinitpath(path, with_parents=True): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + col = ihook.pytest_collect_directory(path=path, parent=self) + if col is not None: + yield col - parts_ = parts(direntry.path) - if any( - str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path - for pkg_prefix in pkg_prefixes - ): - continue - - if direntry.is_file(): - yield from self._collectfile(path) - elif not direntry.is_dir(): - # Broken symlink or invalid/missing file. - continue - elif self._recurse(direntry) and path.joinpath("__init__.py").is_file(): - pkg_prefixes.add(path) + elif direntry.is_file(): + path = Path(direntry.path) + ihook = self.ihook + if not self.session.isinitpath(path): + if ihook.pytest_ignore_collect(collection_path=path, config=config): + continue + cols = ihook.pytest_collect_file(file_path=path, parent=self) + yield from cols def _call_with_optional_argument(func, arg) -> None: diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 0aa496a2f..4e0c23ddb 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -30,6 +30,7 @@ from _pytest.freeze_support import freeze_includes from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir from _pytest.logging import LogCaptureFixture +from _pytest.main import Dir from _pytest.main import Session from _pytest.mark import Mark from _pytest.mark import MARK_GEN as mark @@ -38,6 +39,7 @@ from _pytest.mark import MarkGenerator from _pytest.mark import param from _pytest.monkeypatch import MonkeyPatch from _pytest.nodes import Collector +from _pytest.nodes import Directory from _pytest.nodes import File from _pytest.nodes import Item from _pytest.outcomes import exit @@ -98,6 +100,8 @@ __all__ = [ "Config", "console_main", "deprecated_call", + "Dir", + "Directory", "DoctestItem", "exit", "ExceptionInfo", diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index d597311ae..43390ab83 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -185,7 +185,8 @@ class TestGeneralUsage: assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines( [ - f"ERROR: found no collectors for {p2}", + f"ERROR: not found: {p2}", + "(no match in any of *)", "", ] ) @@ -238,7 +239,7 @@ class TestGeneralUsage: pytester.copy_example("issue88_initial_file_multinodes") p = pytester.makepyfile("def test_hello(): pass") result = pytester.runpytest(p, "--collect-only") - result.stdout.fnmatch_lines(["*MyFile*test_issue88*", "*Module*test_issue88*"]) + result.stdout.fnmatch_lines(["*Module*test_issue88*", "*MyFile*test_issue88*"]) def test_issue93_initialnode_importing_capturing(self, pytester: Pytester) -> None: pytester.makeconftest( diff --git a/testing/example_scripts/customdirectory/conftest.py b/testing/example_scripts/customdirectory/conftest.py new file mode 100644 index 000000000..5357014d7 --- /dev/null +++ b/testing/example_scripts/customdirectory/conftest.py @@ -0,0 +1,22 @@ +# content of conftest.py +import json + +import pytest + + +class ManifestDirectory(pytest.Directory): + def collect(self): + manifest_path = self.path / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + ihook = self.ihook + for file in manifest["files"]: + yield from ihook.pytest_collect_file( + file_path=self.path / file, parent=self + ) + + +@pytest.hookimpl +def pytest_collect_directory(path, parent): + if path.joinpath("manifest.json").is_file(): + return ManifestDirectory.from_parent(parent=parent, path=path) + return None diff --git a/testing/example_scripts/customdirectory/pytest.ini b/testing/example_scripts/customdirectory/pytest.ini new file mode 100644 index 000000000..e69de29bb diff --git a/testing/example_scripts/customdirectory/tests/manifest.json b/testing/example_scripts/customdirectory/tests/manifest.json new file mode 100644 index 000000000..6ab6d0a52 --- /dev/null +++ b/testing/example_scripts/customdirectory/tests/manifest.json @@ -0,0 +1,6 @@ +{ + "files": [ + "test_first.py", + "test_second.py" + ] +} diff --git a/testing/example_scripts/customdirectory/tests/test_first.py b/testing/example_scripts/customdirectory/tests/test_first.py new file mode 100644 index 000000000..0a78de599 --- /dev/null +++ b/testing/example_scripts/customdirectory/tests/test_first.py @@ -0,0 +1,3 @@ +# content of test_first.py +def test_1(): + pass diff --git a/testing/example_scripts/customdirectory/tests/test_second.py b/testing/example_scripts/customdirectory/tests/test_second.py new file mode 100644 index 000000000..eed724a7d --- /dev/null +++ b/testing/example_scripts/customdirectory/tests/test_second.py @@ -0,0 +1,3 @@ +# content of test_second.py +def test_2(): + pass diff --git a/testing/example_scripts/customdirectory/tests/test_third.py b/testing/example_scripts/customdirectory/tests/test_third.py new file mode 100644 index 000000000..61cf59dc1 --- /dev/null +++ b/testing/example_scripts/customdirectory/tests/test_third.py @@ -0,0 +1,3 @@ +# content of test_third.py +def test_3(): + pass diff --git a/testing/python/collect.py b/testing/python/collect.py index 309d7e680..da11dd34a 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1514,3 +1514,108 @@ def test_package_ordering(pytester: Pytester) -> None: # Execute from . result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=3) + + +def test_collection_hierarchy(pytester: Pytester) -> None: + """A general test checking that a filesystem hierarchy is collected as + expected in various scenarios. + + top/ + ├── aaa + │ ├── pkg + │ │ ├── __init__.py + │ │ └── test_pkg.py + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── dir + │ └── test_dir.py + ├── __init__.py + └── test_zzz.py + """ + pytester.makepyfile( + **{ + "top/aaa/test_aaa.py": "def test_it(): pass", + "top/aaa/pkg/__init__.py": "", + "top/aaa/pkg/test_pkg.py": "def test_it(): pass", + "top/test_a.py": "def test_it(): pass", + "top/test_b/__init__.py": "", + "top/test_b/test_b.py": "def test_it(): pass", + "top/test_c.py": "def test_it(): pass", + "top/zzz/__init__.py": "", + "top/zzz/test_zzz.py": "def test_it(): pass", + "top/zzz/dir/test_dir.py": "def test_it(): pass", + } + ) + + full = [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ] + result = pytester.runpytest("--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + result = pytester.runpytest("top", "--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + result = pytester.runpytest("top", "top", "--collect-only") + result.stdout.fnmatch_lines(full, consecutive=True) + + result = pytester.runpytest( + "top/aaa", "top/aaa/pkg", "--collect-only", "--keep-duplicates" + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) + + result = pytester.runpytest( + "top/aaa/pkg", "top/aaa", "--collect-only", "--keep-duplicates" + ) + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + ], + consecutive=True, + ) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index e93363a78..9768c82ff 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1005,16 +1005,16 @@ class TestMetafunc: result = pytester.runpytest("--collect-only") result.stdout.re_match_lines( [ - r" ", - r" ", - r" ", - r" ", - r" ", - r" ", - r" ", - r" ", - r" ", - r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", + r" ", ] ) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 4d751f8db..c08c1cd82 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1574,12 +1574,12 @@ def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*def test_base():*", - "*E*assert 1 == 2*", "*def test_a():*", "*E*assert summary a*", "*def test_b():*", "*E*assert summary b*", + "*def test_base():*", + "*E*assert 1 == 2*", ] ) @@ -1744,9 +1744,9 @@ def test_recursion_source_decode(pytester: Pytester) -> None: ) result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( - """ - - """ + [ + " ", + ] ) diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index e2e195ca7..21c1957cf 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -422,7 +422,7 @@ class TestLastFailed: result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - @pytest.mark.parametrize("parent", ("session", "package")) + @pytest.mark.parametrize("parent", ("directory", "package")) def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None: if parent == "package": pytester.makepyfile( @@ -936,8 +936,10 @@ class TestLastFailed: "collected 1 item", "run-last-failure: rerun previous 1 failure (skipped 1 file)", "", - "", - " ", + "", + " ", + " ", + " ", ] ) @@ -966,8 +968,10 @@ class TestLastFailed: "*collected 1 item", "run-last-failure: 1 known failures not in selected tests", "", - "", - " ", + "", + " ", + " ", + " ", ], consecutive=True, ) @@ -981,8 +985,10 @@ class TestLastFailed: "collected 2 items / 1 deselected / 1 selected", "run-last-failure: rerun previous 1 failure", "", - "", - " ", + "", + " ", + " ", + " ", "*= 1/2 tests collected (1 deselected) in *", ], ) @@ -1011,10 +1017,12 @@ class TestLastFailed: "collected 3 items / 1 deselected / 2 selected", "run-last-failure: rerun previous 2 failures", "", - "", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", "", "*= 2/3 tests collected (1 deselected) in *", ], @@ -1048,8 +1056,10 @@ class TestLastFailed: "collected 1 item", "run-last-failure: 1 known failures not in selected tests", "", - "", - " ", + "", + " ", + " ", + " ", "", "*= 1 test collected in*", ], diff --git a/testing/test_collection.py b/testing/test_collection.py index b2492f7f2..deed4bda5 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -490,7 +490,7 @@ class TestSession: # assert root2 == rcol, rootid colitems = rcol.perform_collect([rcol.nodeid], genitems=False) assert len(colitems) == 1 - assert colitems[0].path == p + assert colitems[0].path == topdir def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" @@ -568,12 +568,12 @@ class TestSession: hookrec.assert_contains( [ ("pytest_collectstart", "collector.path == collector.session.path"), + ("pytest_collectstart", "collector.__class__.__name__ == 'Module'"), + ("pytest_pycollect_makeitem", "name == 'test_func'"), ( "pytest_collectstart", "collector.__class__.__name__ == 'SpecialFile'", ), - ("pytest_collectstart", "collector.__class__.__name__ == 'Module'"), - ("pytest_pycollect_makeitem", "name == 'test_func'"), ("pytest_collectreport", "report.nodeid.startswith(p.name)"), ] ) @@ -657,7 +657,8 @@ class Test_getinitialnodes: assert isinstance(col, pytest.Module) assert col.name == "x.py" assert col.parent is not None - assert col.parent.parent is None + assert col.parent.parent is not None + assert col.parent.parent.parent is None for parent in col.listchain(): assert parent.config is config @@ -937,6 +938,46 @@ class TestNodeKeywords: assert "baz" not in mod.keywords +class TestCollectDirectoryHook: + def test_custom_directory_example(self, pytester: Pytester) -> None: + """Verify the example from the customdirectory.rst doc.""" + pytester.copy_example("customdirectory") + + reprec = pytester.inline_run() + + reprec.assertoutcome(passed=2, failed=0) + calls = reprec.getcalls("pytest_collect_directory") + assert len(calls) == 2 + assert calls[0].path == pytester.path + assert isinstance(calls[0].parent, pytest.Session) + assert calls[1].path == pytester.path / "tests" + assert isinstance(calls[1].parent, pytest.Dir) + + def test_directory_ignored_if_none(self, pytester: Pytester) -> None: + """If the (entire) hook returns None, it's OK, the directory is ignored.""" + pytester.makeconftest( + """ + import pytest + + @pytest.hookimpl(wrapper=True) + def pytest_collect_directory(): + yield + return None + """, + ) + pytester.makepyfile( + **{ + "tests/test_it.py": """ + import pytest + + def test_it(): pass + """, + }, + ) + reprec = pytester.inline_run() + reprec.assertoutcome(passed=0, failed=0) + + COLLECTION_ERROR_PY_FILES = dict( test_01_failure=""" def test_1(): @@ -1098,22 +1139,24 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) result = pytester.runpytest("./tests", "--collect-only") result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) # Ignores duplicates with "." and pkginit (#4310). @@ -1121,11 +1164,12 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) # Same as before, but different order. @@ -1133,21 +1177,32 @@ def test_collect_init_tests(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "collected 2 items", - "", - " ", - " ", - " ", - " ", + "", + " ", + " ", + " ", + " ", + " ", ] ) result = pytester.runpytest("./tests/test_foo.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + [ + "", + " ", + " ", + " ", + ] ) result.stdout.no_fnmatch_line("*test_init*") result = pytester.runpytest("./tests/__init__.py", "--collect-only") result.stdout.fnmatch_lines( - ["", " ", " "] + [ + "", + " ", + " ", + " ", + ] ) result.stdout.no_fnmatch_line("*test_foo*") diff --git a/testing/test_config.py b/testing/test_config.py index 900cccee8..18022977c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1966,7 +1966,8 @@ def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines( [ - "ERROR: found no collectors for */test_config_blocked_default_plugins.py", + "ERROR: not found: */test_config_blocked_default_plugins.py", + "(no match in any of **", ] ) return diff --git a/testing/test_mark.py b/testing/test_mark.py index 7415b393e..609f73d68 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -871,17 +871,30 @@ class TestKeywordSelection: deselected_tests = dlist[0].items assert len(deselected_tests) == 1 - def test_no_match_directories_outside_the_suite(self, pytester: Pytester) -> None: + def test_no_match_directories_outside_the_suite( + self, + pytester: Pytester, + monkeypatch: pytest.MonkeyPatch, + ) -> None: """`-k` should not match against directories containing the test suite (#7040).""" - test_contents = """ - def test_aaa(): pass - def test_ddd(): pass - """ - pytester.makepyfile( - **{"ddd/tests/__init__.py": "", "ddd/tests/test_foo.py": test_contents} + pytester.makefile( + **{ + "suite/pytest": """[pytest]""", + }, + ext=".ini", ) + pytester.makepyfile( + **{ + "suite/ddd/tests/__init__.py": "", + "suite/ddd/tests/test_foo.py": """ + def test_aaa(): pass + def test_ddd(): pass + """, + } + ) + monkeypatch.chdir(pytester.path / "suite") - def get_collected_names(*args): + def get_collected_names(*args: str) -> List[str]: _, rec = pytester.inline_genitems(*args) calls = rec.getcalls("pytest_collection_finish") assert len(calls) == 1 @@ -893,12 +906,6 @@ class TestKeywordSelection: # do not collect anything based on names outside the collection tree assert get_collected_names("-k", pytester._name) == [] - # "-k ddd" should only collect "test_ddd", but not - # 'test_aaa' just because one of its parent directories is named "ddd"; - # this was matched previously because Package.name would contain the full path - # to the package - assert get_collected_names("-k", "ddd") == ["test_ddd"] - class TestMarkDecorator: @pytest.mark.parametrize( diff --git a/testing/test_reports.py b/testing/test_reports.py index 387d2e807..627ea1ed2 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -304,9 +304,9 @@ class TestReportSerialization: report = reports[1] else: assert report_class is CollectReport - # two collection reports: session and test file + # three collection reports: session, test file, directory reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 2 + assert len(reports) == 3 report = reports[1] def check_longrepr(longrepr: ExceptionChainRepr) -> None: @@ -471,7 +471,7 @@ class TestHooks: ) reprec = pytester.inline_run() reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 2 + assert len(reports) == 3 for rep in reports: data = pytestconfig.hook.pytest_report_to_serializable( config=pytestconfig, report=rep diff --git a/testing/test_runner.py b/testing/test_runner.py index cab631ee1..c8b646857 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1006,7 +1006,7 @@ class TestReportContents: ) rec = pytester.inline_run() calls = rec.getcalls("pytest_collectreport") - _, call = calls + _, call, _ = calls assert isinstance(call.report.longrepr, tuple) assert "Skipped" in call.report.longreprtext diff --git a/testing/test_session.py b/testing/test_session.py index 48dc08e8c..136e85eb6 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -172,8 +172,9 @@ class SessionTests: except pytest.skip.Exception: # pragma: no cover pytest.fail("wrong skipped caught") reports = reprec.getreports("pytest_collectreport") - assert len(reports) == 1 - assert reports[0].skipped + # Session, Dir + assert len(reports) == 2 + assert reports[1].skipped class TestNewSession(SessionTests): @@ -357,9 +358,10 @@ def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None: ) result.stdout.fnmatch_lines( [ - "", - " ", - " ", + " ", + " ", + " ", + " ", ], consecutive=True, ) @@ -373,11 +375,12 @@ def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None: ) result.stdout.fnmatch_lines( [ - "", - " ", - " ", - " ", - " ", + " ", + " ", + " ", + " ", + " ", + " ", ], consecutive=True, ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 264ab96d8..12c80f9b8 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -451,7 +451,11 @@ class TestCollectonly: ) result = pytester.runpytest("--collect-only") result.stdout.fnmatch_lines( - ["", " "] + [ + "", + " ", + " ", + ] ) def test_collectonly_skipped_module(self, pytester: Pytester) -> None: @@ -480,14 +484,15 @@ class TestCollectonly: result = pytester.runpytest("--collect-only", "--verbose") result.stdout.fnmatch_lines( [ - "", - " ", - "", - " ", - " This test has a description.", - " ", - " more1.", - " more2.", + "", + " ", + " ", + " ", + " ", + " This test has a description.", + " ", + " more1.", + " more2.", ], consecutive=True, ) @@ -2001,9 +2006,9 @@ class TestClassicOutputStyle: result = pytester.runpytest("-o", "console_output_style=classic") result.stdout.fnmatch_lines( [ + f"sub{os.sep}test_three.py .F.", "test_one.py .", "test_two.py F", - f"sub{os.sep}test_three.py .F.", "*2 failed, 3 passed in*", ] ) @@ -2012,18 +2017,18 @@ class TestClassicOutputStyle: result = pytester.runpytest("-o", "console_output_style=classic", "-v") result.stdout.fnmatch_lines( [ - "test_one.py::test_one PASSED", - "test_two.py::test_two FAILED", f"sub{os.sep}test_three.py::test_three_1 PASSED", f"sub{os.sep}test_three.py::test_three_2 FAILED", f"sub{os.sep}test_three.py::test_three_3 PASSED", + "test_one.py::test_one PASSED", + "test_two.py::test_two FAILED", "*2 failed, 3 passed in*", ] ) def test_quiet(self, pytester: Pytester, test_files) -> None: result = pytester.runpytest("-o", "console_output_style=classic", "-q") - result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"]) + result.stdout.fnmatch_lines([".F..F", "*2 failed, 3 passed in*"]) class TestProgressOutputStyle: From e1c66ab0ad8eda13e5552dfc939e07d7290ecd39 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 5 Dec 2023 23:07:06 +0200 Subject: [PATCH 017/104] Different fix for conftest loading --- Current main In current main (before pervious commit), calls to gethookproxy/ihook are the trigger for loading non-initial conftests. This basically means that conftest loading is done almost as a random side-effect, uncontrolled and very non-obvious. And it also dashes any hope of making gethookproxy faster (gethookproxy shows up prominently in pytest profiles). I've wanted to improve this for a while, #11268 was the latest step towards that. --- PR before change In this PR, I ran into a problem. Previously, Session and Package did all of the directory traversals inside of their collect, which loaded the conftests as a side effect. If the conftest loading failed, it will occur inside of the collect() and cause it to be reported as a failure. Now I've changed things such that Session.collect and Package.collect no longer recurse, but just collect their immediate descendants, and genitems does the recursive expansion work. The problem though is that genitems() doesn't run inside of specific collector's collect context. So when it loads a conftest, and the conftest loading fails, the exception isn't handled by any CollectReport and causes an internal error instead. The way I've fixed this problem is by loading the conftests eagerly in a pytest_collect_directory post-wrapper, but only during genitems to make sure the directory is actually selected. This solution in turn caused the conftests to be collected too early; specifically, the plugins are loaded during the parent's collect(), one after the other as the directory entries are collected. So when the ihook is hoisted out of the loop, new plugins are loaded inside the loop, and due to the way the hook proxy works, they are added to the ihook even though they're supposed to be scoped to the child collectors. So no hoisting. --- PR after change Now I've come up with a better solution: since now the collection tree actually reflects the filesystem tree, what we really want is to load the conftest of a directory right before we run its collect(). A conftest can affect a directory's collect() (e.g. with a pytest_ignore_collect hookimpl), but it cannot affect how the directory node itself is collected. So I just moved the conftest loading to be done right before calling collect, but still inside the CollectReport context. This allows the hoisting, and also removes conftest loading from gethookproxy since it's no longer necessary. And it will probably enable further cleanups. So I'm happy with it. --- src/_pytest/main.py | 40 +--------------------------------------- src/_pytest/python.py | 3 +-- src/_pytest/runner.py | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index e4ca05aac..3672df05a 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -12,7 +12,6 @@ from typing import Callable from typing import Dict from typing import final from typing import FrozenSet -from typing import Generator from typing import Iterable from typing import Iterator from typing import List @@ -502,11 +501,11 @@ class Dir(nodes.Directory): config = self.config col: Optional[nodes.Collector] cols: Sequence[nodes.Collector] + ihook = self.ihook for direntry in scandir(self.path): if direntry.is_dir(): if direntry.name == "__pycache__": continue - ihook = self.ihook path = Path(direntry.path) if not self.session.isinitpath(path, with_parents=True): if ihook.pytest_ignore_collect(collection_path=path, config=config): @@ -516,7 +515,6 @@ class Dir(nodes.Directory): yield col elif direntry.is_file(): - ihook = self.ihook path = Path(direntry.path) if not self.session.isinitpath(path): if ihook.pytest_ignore_collect(collection_path=path, config=config): @@ -559,7 +557,6 @@ class Session(nodes.Collector): self._initialpaths_with_parents: FrozenSet[Path] = frozenset() self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] self._initial_parts: List[Tuple[Path, List[str]]] = [] - self._in_genitems = False self._collection_cache: Dict[nodes.Collector, CollectReport] = {} self.items: List[nodes.Item] = [] @@ -612,29 +609,6 @@ class Session(nodes.Collector): pytest_collectreport = pytest_runtest_logreport - @hookimpl(wrapper=True) - def pytest_collect_directory( - self, - ) -> Generator[None, Optional[nodes.Collector], Optional[nodes.Collector]]: - col = yield - - # Eagerly load conftests for the directory. - # This is needed because a conftest error needs to happen while - # collecting a collector, so it is caught by its CollectReport. - # Without this, the conftests are loaded inside of genitems itself - # which leads to an internal error. - # This should only be done for genitems; if done unconditionally, it - # will load conftests for non-selected directories which is to be - # avoided. - if self._in_genitems and col is not None: - self.config.pluginmanager._loadconftestmodules( - col.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) - - return col - def isinitpath( self, path: Union[str, "os.PathLike[str]"], @@ -665,15 +639,6 @@ class Session(nodes.Collector): pm = self.config.pluginmanager # Check if we have the common case of running # hooks with all conftest.py files. - # - # TODO: pytest relies on this call to load non-initial conftests. This - # is incidental. It will be better to load conftests at a more - # well-defined place. - pm._loadconftestmodules( - path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, - ) my_conftestmodules = pm._getconftestmodules(path) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) proxy: pluggy.HookRelay @@ -754,7 +719,6 @@ class Session(nodes.Collector): self._notfound = [] self._initial_parts = [] - self._in_genitems = False self._collection_cache = {} self.items = [] items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items @@ -789,7 +753,6 @@ class Session(nodes.Collector): raise UsageError(*errors) - self._in_genitems = True if not genitems: items = rep.result else: @@ -804,7 +767,6 @@ class Session(nodes.Collector): finally: self._notfound = [] self._initial_parts = [] - self._in_genitems = False self._collection_cache = {} hook.pytest_collection_finish(session=self) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 09adb2b9c..e0f7a447a 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -731,12 +731,12 @@ class Package(nodes.Directory): config = self.config col: Optional[nodes.Collector] cols: Sequence[nodes.Collector] + ihook = self.ihook for direntry in scandir(self.path, sort_key): if direntry.is_dir(): if direntry.name == "__pycache__": continue path = Path(direntry.path) - ihook = self.ihook if not self.session.isinitpath(path, with_parents=True): if ihook.pytest_ignore_collect(collection_path=path, config=config): continue @@ -746,7 +746,6 @@ class Package(nodes.Directory): elif direntry.is_file(): path = Path(direntry.path) - ihook = self.ihook if not self.session.isinitpath(path): if ihook.pytest_ignore_collect(collection_path=path, config=config): continue diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 1b39f93cf..dcfc6b7d0 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -28,6 +28,7 @@ from _pytest._code.code import TerminalRepr from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest from _pytest.nodes import Collector +from _pytest.nodes import Directory from _pytest.nodes import Item from _pytest.nodes import Node from _pytest.outcomes import Exit @@ -368,7 +369,23 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - call = CallInfo.from_call(lambda: list(collector.collect()), "collect") + def collect() -> List[Union[Item, Collector]]: + # Before collecting, if this is a Directory, load the conftests. + # If a conftest import fails to load, it is considered a collection + # error of the Directory collector. This is why it's done inside of the + # CallInfo wrapper. + # + # Note: initial conftests are loaded early, not here. + if isinstance(collector, Directory): + collector.config.pluginmanager._loadconftestmodules( + collector.path, + collector.config.getoption("importmode"), + rootpath=collector.config.rootpath, + ) + + return list(collector.collect()) + + call = CallInfo.from_call(collect, "collect") longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" From ef8bf82a780c6a1ca69eae6dbc2cc251f1f59506 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:24:52 +0100 Subject: [PATCH 018/104] build(deps): Bump pytest-asyncio in /testing/plugins_integration (#11693) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.23.1 to 0.23.2. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.1...v0.23.2) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 1166d14c9..f65ed3c7f 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,6 +1,6 @@ anyio[curio,trio]==4.1.0 django==4.2.7 -pytest-asyncio==0.23.1 +pytest-asyncio==0.23.2 pytest-bdd==7.0.1 pytest-cov==4.1.0 pytest-django==4.7.0 From 8ce76c307e8e2a99ba6664dd95f8f37e0fc3bd8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:28:50 +0100 Subject: [PATCH 019/104] build(deps): Bump actions/stale from 8 to 9 (#11691) Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3f8ca186f..3f83839cd 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: permissions: issues: write steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 with: debug-only: false days-before-issue-stale: 14 From ab307b3402cf37e06123d5ff2758369f5cbada27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 09:46:13 +0100 Subject: [PATCH 020/104] build(deps): Bump actions/setup-python from 4 to 5 (#11690) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/prepare-release-pr.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/update-plugin-list.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e1b95efa7..ca65662c1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -73,7 +73,7 @@ jobs: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index ce8130c86..1bb23fab8 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -32,7 +32,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.8" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5f5832475..9f8e4eacb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -179,7 +179,7 @@ jobs: path: dist - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} check-latest: ${{ endsWith(matrix.python, '-dev') }} diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index ff148e108..349d5f529 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" cache: pip From dfc910ee90cbe70d8d8b079190fa75ca2a5ef067 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:18:43 +0000 Subject: [PATCH 021/104] build(deps): Bump django from 4.2.7 to 5.0 in /testing/plugins_integration (#11692) * build(deps): Bump django in /testing/plugins_integration Bumps [django](https://github.com/django/django) from 4.2.7 to 5.0. - [Commits](https://github.com/django/django/compare/4.2.7...5.0) --- updated-dependencies: - dependency-name: django dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump python version on the plugin job https://github.com/pytest-dev/pytest/pull/11692#issuecomment-1849963332 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- .github/workflows/test.yml | 2 +- testing/plugins_integration/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f8e4eacb..9fbd273bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -156,7 +156,7 @@ jobs: tox_env: "py312-xdist" - name: "plugins" - python: "3.9" + python: "3.12" os: ubuntu-latest tox_env: "plugins" diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index f65ed3c7f..9e5955d6a 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==4.1.0 -django==4.2.7 +django==5.0 pytest-asyncio==0.23.2 pytest-bdd==7.0.1 pytest-cov==4.1.0 From 2b86d2bddc3eda0a1b342ddb7d7d3aed7b29501f Mon Sep 17 00:00:00 2001 From: Tom Mortimer-Jones Date: Wed, 13 Dec 2023 12:24:43 +0000 Subject: [PATCH 022/104] Typo in fixtures.rst (#11699) --- doc/en/reference/fixtures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index 078933de6..8ba59395e 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -93,7 +93,7 @@ Fixture availability is determined from the perspective of the test. A fixture is only available for tests to request if they are in the scope that fixture is defined in. If a fixture is defined inside a class, it can only be requested by tests inside that class. But if a fixture is defined inside the global scope of -the module, than every test in that module, even if it's defined inside a class, +the module, then every test in that module, even if it's defined inside a class, can request it. Similarly, a test can also only be affected by an autouse fixture if that test From 047ba83dabe492af938104fe0058597f67a672be Mon Sep 17 00:00:00 2001 From: Arthur Richard Date: Thu, 14 Dec 2023 12:14:36 +0100 Subject: [PATCH 023/104] Improve pytest.exit docs (#11698) Fixes #11695 --- AUTHORS | 1 + doc/en/reference/reference.rst | 2 +- src/_pytest/outcomes.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 669ec537e..bb273edcc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ Ariel Pillemer Armin Rigo Aron Coyle Aron Curzon +Arthur Richard Ashish Kurmi Aviral Verma Aviv Palivoda diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index d6f942ad0..3054109ba 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -79,7 +79,7 @@ pytest.xfail pytest.exit ~~~~~~~~~~~ -.. autofunction:: pytest.exit(reason, [returncode=False, msg=None]) +.. autofunction:: pytest.exit(reason, [returncode=None, msg=None]) pytest.main ~~~~~~~~~~~ diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index a8984c5b9..0f64f91d9 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -112,7 +112,7 @@ def exit( only because `msg` is deprecated. :param returncode: - Return code to be used when exiting pytest. + Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. :param msg: Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. From 27f7cee23876dc3eb77a60dcc768d65ccc3020e8 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 17 Dec 2023 00:20:43 +0000 Subject: [PATCH 024/104] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 94 +++++++++++++++++--------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index dea19eae6..8317af7ca 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =7.0.1) - :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Sep 06, 2023 5 - Production/Stable pytest + :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Dec 12, 2023 5 - Production/Stable pytest :pypi:`pytest-archon` Rule your architecture like a real developer Jul 11, 2023 5 - Production/Stable pytest (>=7.2) :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Nov 27, 2023 4 - Beta pytest >=4.6 @@ -252,7 +252,7 @@ This list contains 1354 plugins. :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Nov 14, 2023 3 - Alpha pytest - :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 08, 2023 4 - Beta pytest>=7.3.2 + :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 11, 2023 4 - Beta pytest>=7.3.2 :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A :pypi:`pytest-cov` Pytest plugin for measuring coverage. May 24, 2023 5 - Production/Stable pytest (>=4.6) @@ -328,7 +328,7 @@ This list contains 1354 plugins. :pypi:`pytest-diffeo` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' :pypi:`pytest-difido` PyTest plugin for generating Difido reports Oct 23, 2022 4 - Beta pytest (>=4.0.0) - :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 05, 2023 4 - Beta pytest>=7.3.2 + :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 11, 2023 4 - Beta pytest>=7.3.2 :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 @@ -377,9 +377,9 @@ This list contains 1354 plugins. :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Aug 11, 2023 3 - Alpha pytest >=4.6 + :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Dec 13, 2023 5 - Production/Stable pytest >=4.6 :pypi:`pytest-dogu-report` pytest plugin for dogu report Jul 07, 2023 N/A N/A - :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Dec 05, 2023 N/A N/A + :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Dec 14, 2023 N/A N/A :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) :pypi:`pytest-donde` record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script. Oct 01, 2023 4 - Beta pytest >=7.3.1 :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) @@ -527,7 +527,7 @@ This list contains 1354 plugins. :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A - :pypi:`pytest-fzf` fzf-based test selector for pytest Nov 28, 2023 4 - Beta pytest >=6.0.0 + :pypi:`pytest-fzf` fzf-based test selector for pytest Dec 15, 2023 4 - Beta pytest >=6.0.0 :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A @@ -536,7 +536,7 @@ This list contains 1354 plugins. :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Dec 05, 2023 N/A N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Dec 14, 2023 N/A N/A :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A @@ -574,10 +574,10 @@ This list contains 1354 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Nov 20, 2023 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 09, 2023 3 - Alpha pytest ==7.4.3 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 15, 2023 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hot-reloading` Dec 01, 2023 N/A N/A + :pypi:`pytest-hot-reloading` Dec 13, 2023 N/A N/A :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Nov 10, 2023 N/A pytest :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) @@ -594,7 +594,7 @@ This list contains 1354 plugins. :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin May 08, 2023 5 - Production/Stable pytest ; extra == 'test' - :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace Nov 03, 2023 3 - Alpha pytest >=7.0.0 + :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace Dec 09, 2023 3 - Alpha pytest >=7.0.0 :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest May 22, 2023 3 - Alpha N/A @@ -604,11 +604,11 @@ This list contains 1354 plugins. :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A - :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Aug 31, 2023 3 - Alpha pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Dec 15, 2023 3 - Alpha pytest (>=7.0.0,<8.0.0) :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta pytest>=7.1 :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Dec 05, 2023 4 - Beta pytest :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A - :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Jun 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Dec 13, 2023 5 - Production/Stable N/A :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Oct 11, 2023 5 - Production/Stable pytest >=6.0 :pypi:`pytest-ignore-test-results` A pytest plugin to ignore test results. Aug 17, 2023 2 - Pre-Alpha pytest>=7.0 @@ -622,8 +622,8 @@ This list contains 1354 plugins. :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Nov 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-inmanta-extensions` Inmanta tests package Oct 13, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest + :pypi:`pytest-inmanta-extensions` Inmanta tests package Dec 11, 2023 5 - Production/Stable N/A :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Nov 29, 2023 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A @@ -833,7 +833,7 @@ This list contains 1354 plugins. :pypi:`pytest-ogsm-plugin` 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 May 16, 2023 N/A N/A :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jun 14, 2022 5 - Production/Stable pytest (<7.1); python_version <= "3.6" - :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 05, 2023 4 - Beta N/A + :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 11, 2023 4 - Beta N/A :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Oct 01, 2023 N/A pytest @@ -1028,7 +1028,7 @@ This list contains 1354 plugins. :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Nov 22, 2023 5 - Production/Stable pytest >=7 :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A :pypi:`pytest-reserial` Pytest fixture for recording and replaying serial port traffic. Aug 31, 2023 4 - Beta pytest - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Nov 22, 2023 N/A pytest ~=4.6 ; python_version == "2.7" + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Dec 11, 2023 N/A pytest ~=4.6 ; python_version == "2.7" :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) :pypi:`pytest-resource-usage` Pytest plugin for reporting running time and peak memory usage Nov 06, 2022 5 - Production/Stable pytest>=7.0.0 @@ -1091,7 +1091,7 @@ This list contains 1354 plugins. :pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A - :pypi:`pytest-servers` pytest servers Oct 31, 2023 3 - Alpha pytest >=6.2 + :pypi:`pytest-servers` pytest servers Dec 15, 2023 3 - Alpha pytest >=6.2 :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A @@ -1156,7 +1156,7 @@ This list contains 1354 plugins. :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Nov 25, 2023 N/A pytest (>5.4.0,<8) + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Dec 14, 2023 N/A pytest (>5.4.0,<8) :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Dec 01, 2023 N/A N/A :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A @@ -1205,7 +1205,7 @@ This list contains 1354 plugins. :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) - :pypi:`pytest-tcp` A Pytest plugin for test prioritization Dec 04, 2023 4 - Beta pytest >=7.4.3 + :pypi:`pytest-tcp` A Pytest plugin for test prioritization Dec 10, 2023 4 - Beta pytest >=7.4.3 :pypi:`pytest-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) :pypi:`pytest-tdd` run pytest on a python module Aug 18, 2023 4 - Beta N/A :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A @@ -1255,6 +1255,7 @@ This list contains 1354 plugins. :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) :pypi:`pytest-time` Jun 24, 2023 3 - Alpha pytest + :pypi:`pytest-timeassert-ethan` execution duration Dec 12, 2023 N/A pytest :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A :pypi:`pytest-timeout` pytest plugin to abort hanging tests Oct 08, 2023 5 - Production/Stable pytest >=5.0.0 :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A @@ -1324,7 +1325,7 @@ This list contains 1354 plugins. :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest :pypi:`pytest-vcs` Sep 22, 2022 4 - Beta N/A :pypi:`pytest-venv` py.test fixture for creating a virtual environment Nov 23, 2023 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Nov 23, 2023 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Dec 12, 2023 4 - Beta pytest :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest @@ -1758,7 +1759,7 @@ This list contains 1354 plugins. A plugin to use approvaltests with pytest :pypi:`pytest-approvaltests-geo` - *last release*: Sep 06, 2023, + *last release*: Dec 12, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -2920,7 +2921,7 @@ This list contains 1354 plugins. The pytest plugin for your copier templates 📒 :pypi:`pytest-copier` - *last release*: Dec 08, 2023, + *last release*: Dec 11, 2023, *status*: 4 - Beta, *requires*: pytest>=7.3.2 @@ -3452,7 +3453,7 @@ This list contains 1354 plugins. PyTest plugin for generating Difido reports :pypi:`pytest-dir-equal` - *last release*: Dec 05, 2023, + *last release*: Dec 11, 2023, *status*: 4 - Beta, *requires*: pytest>=7.3.2 @@ -3795,8 +3796,8 @@ This list contains 1354 plugins. A simple pytest plugin to import names and add them to the doctest namespace. :pypi:`pytest-doctestplus` - *last release*: Aug 11, 2023, - *status*: 3 - Alpha, + *last release*: Dec 13, 2023, + *status*: 5 - Production/Stable, *requires*: pytest >=4.6 Pytest plugin with advanced doctest features. @@ -3809,7 +3810,7 @@ This list contains 1354 plugins. pytest plugin for dogu report :pypi:`pytest-dogu-sdk` - *last release*: Dec 05, 2023, + *last release*: Dec 14, 2023, *status*: N/A, *requires*: N/A @@ -4845,7 +4846,7 @@ This list contains 1354 plugins. :pypi:`pytest-fzf` - *last release*: Nov 28, 2023, + *last release*: Dec 15, 2023, *status*: 4 - Beta, *requires*: pytest >=6.0.0 @@ -4908,7 +4909,7 @@ This list contains 1354 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Dec 05, 2023, + *last release*: Dec 14, 2023, *status*: N/A, *requires*: N/A @@ -5174,7 +5175,7 @@ This list contains 1354 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Dec 09, 2023, + *last release*: Dec 15, 2023, *status*: 3 - Alpha, *requires*: pytest ==7.4.3 @@ -5195,7 +5196,7 @@ This list contains 1354 plugins. Report on tests that honor constraints, and guard against regressions :pypi:`pytest-hot-reloading` - *last release*: Dec 01, 2023, + *last release*: Dec 13, 2023, *status*: N/A, *requires*: N/A @@ -5314,7 +5315,7 @@ This list contains 1354 plugins. Easily test your HTTP library against a local copy of httpbin :pypi:`pytest-httpdbg` - *last release*: Nov 03, 2023, + *last release*: Dec 09, 2023, *status*: 3 - Alpha, *requires*: pytest >=7.0.0 @@ -5384,7 +5385,7 @@ This list contains 1354 plugins. help hypo module for pytest :pypi:`pytest-iam` - *last release*: Aug 31, 2023, + *last release*: Dec 15, 2023, *status*: 3 - Alpha, *requires*: pytest (>=7.0.0,<8.0.0) @@ -5412,7 +5413,7 @@ This list contains 1354 plugins. A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api :pypi:`pytest-idem` - *last release*: Jun 23, 2023, + *last release*: Dec 13, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -5510,14 +5511,14 @@ This list contains 1354 plugins. A pytest plugin for writing inline tests. :pypi:`pytest-inmanta` - *last release*: Nov 29, 2023, + *last release*: Dec 13, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: Oct 13, 2023, + *last release*: Dec 11, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -6987,7 +6988,7 @@ This list contains 1354 plugins. Use @pytest.mark.only to run a single test :pypi:`pytest-oof` - *last release*: Dec 05, 2023, + *last release*: Dec 11, 2023, *status*: 4 - Beta, *requires*: N/A @@ -8352,7 +8353,7 @@ This list contains 1354 plugins. Pytest fixture for recording and replaying serial port traffic. :pypi:`pytest-resilient-circuits` - *last release*: Nov 22, 2023, + *last release*: Dec 11, 2023, *status*: N/A, *requires*: pytest ~=4.6 ; python_version == "2.7" @@ -8793,7 +8794,7 @@ This list contains 1354 plugins. Automatically mocks resources from serverless.yml in pytest using moto. :pypi:`pytest-servers` - *last release*: Oct 31, 2023, + *last release*: Dec 15, 2023, *status*: 3 - Alpha, *requires*: pytest >=6.2 @@ -9248,7 +9249,7 @@ This list contains 1354 plugins. :pypi:`pytest-splunk-addon` - *last release*: Nov 25, 2023, + *last release*: Dec 14, 2023, *status*: N/A, *requires*: pytest (>5.4.0,<8) @@ -9591,7 +9592,7 @@ This list contains 1354 plugins. tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used :pypi:`pytest-tcp` - *last release*: Dec 04, 2023, + *last release*: Dec 10, 2023, *status*: 4 - Beta, *requires*: pytest >=7.4.3 @@ -9940,6 +9941,13 @@ This list contains 1354 plugins. + :pypi:`pytest-timeassert-ethan` + *last release*: Dec 12, 2023, + *status*: N/A, + *requires*: pytest + + execution duration + :pypi:`pytest-timeit` *last release*: Oct 13, 2016, *status*: 4 - Beta, @@ -10424,7 +10432,7 @@ This list contains 1354 plugins. py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Nov 23, 2023, + *last release*: Dec 12, 2023, *status*: 4 - Beta, *requires*: pytest From 7541c5a999e17d7c5841a94fcc522f486c5179cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 06:33:59 +0100 Subject: [PATCH 025/104] build(deps): Bump anyio[curio,trio] in /testing/plugins_integration (#11717) Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 4.1.0 to 4.2.0. - [Release notes](https://github.com/agronholm/anyio/releases) - [Changelog](https://github.com/agronholm/anyio/blob/master/docs/versionhistory.rst) - [Commits](https://github.com/agronholm/anyio/compare/4.1.0...4.2.0) --- updated-dependencies: - dependency-name: anyio[curio,trio] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 9e5955d6a..dfdeeb7a8 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,4 +1,4 @@ -anyio[curio,trio]==4.1.0 +anyio[curio,trio]==4.2.0 django==5.0 pytest-asyncio==0.23.2 pytest-bdd==7.0.1 From 03b24e5b306e4f43edd4b1162a0bd7f94e5ae8fd Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 3 Dec 2023 13:23:42 +0000 Subject: [PATCH 026/104] pprint: Remove the `format` method, it's not used outside of pprint Let's reduce the API surface for the bundled PrettyPrinter to what we really need and use --- src/_pytest/_io/pprint.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index ad1238709..7b45ae95f 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -486,12 +486,7 @@ class PrettyPrinter: write("\n" + " " * indent) def _repr(self, object: Any, context: Set[int], level: int) -> str: - return self.format(object, context.copy(), self._depth, level) - - def format( - self, object: Any, context: Set[int], maxlevels: Optional[int], level: int - ) -> str: - return self._safe_repr(object, context, maxlevels, level) + return self._safe_repr(object, context.copy(), self._depth, level) def _pprint_default_dict( self, @@ -639,8 +634,8 @@ class PrettyPrinter: else: items = object.items() for k, v in items: - krepr = self.format(k, context, maxlevels, level) - vrepr = self.format(v, context, maxlevels, level) + krepr = self._safe_repr(k, context, maxlevels, level) + vrepr = self._safe_repr(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") context.remove(objid) return "{%s}" % ", ".join(components) @@ -668,7 +663,7 @@ class PrettyPrinter: append = components.append level += 1 for o in object: - orepr = self.format(o, context, maxlevels, level) + orepr = self._safe_repr(o, context, maxlevels, level) append(orepr) context.remove(objid) return format % ", ".join(components) From 6aa35f772fd62ad6c5dd9f761974ab44cd224d58 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 3 Dec 2023 13:48:12 +0000 Subject: [PATCH 027/104] pprint: Remove the option to sort dictionaries, we always do it --- src/_pytest/_io/pprint.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 7b45ae95f..4512d3937 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -65,7 +65,6 @@ class PrettyPrinter: width: int = 80, depth: Optional[int] = None, *, - sort_dicts: bool = True, underscore_numbers: bool = False, ) -> None: """Handle pretty printing operations onto a stream using a set of @@ -80,9 +79,6 @@ class PrettyPrinter: depth The maximum depth to print out nested structures. - sort_dicts - If true, dict keys are sorted. - """ indent = int(indent) width = int(width) @@ -95,7 +91,6 @@ class PrettyPrinter: self._depth = depth self._indent_per_level = indent self._width = width - self._sort_dicts = sort_dicts self._underscore_numbers = underscore_numbers def pformat(self, object: Any) -> str: @@ -174,10 +169,7 @@ class PrettyPrinter: ) -> None: write = stream.write write("{") - if self._sort_dicts: - items = sorted(object.items(), key=_safe_tuple) - else: - items = object.items() + items = sorted(object.items(), key=_safe_tuple) self._format_dict_items(items, stream, indent, allowance, context, level) write("}") @@ -629,11 +621,7 @@ class PrettyPrinter: components: List[str] = [] append = components.append level += 1 - if self._sort_dicts: - items = sorted(object.items(), key=_safe_tuple) - else: - items = object.items() - for k, v in items: + for k, v in sorted(object.items(), key=_safe_tuple): krepr = self._safe_repr(k, context, maxlevels, level) vrepr = self._safe_repr(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") From 64b5b665cf477a8e920e0e8b57f6b96a2d40f365 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 3 Dec 2023 13:49:46 +0000 Subject: [PATCH 028/104] pprint: Remove the option to add underscore for numbers This is never used, we can remove this. If we wanted instead, we could always enable it --- src/_pytest/_io/pprint.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 4512d3937..ea1073af6 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -64,8 +64,6 @@ class PrettyPrinter: indent: int = 4, width: int = 80, depth: Optional[int] = None, - *, - underscore_numbers: bool = False, ) -> None: """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -91,7 +89,6 @@ class PrettyPrinter: self._depth = depth self._indent_per_level = indent self._width = width - self._underscore_numbers = underscore_numbers def pformat(self, object: Any) -> str: sio = _StringIO() @@ -603,12 +600,6 @@ class PrettyPrinter: r = getattr(typ, "__repr__", None) - if issubclass(typ, int) and r is int.__repr__: - if self._underscore_numbers: - return f"{object:_d}" - else: - return repr(object) - if issubclass(typ, dict) and r is dict.__repr__: if not object: return "{}" @@ -659,7 +650,9 @@ class PrettyPrinter: return repr(object) -_builtin_scalars = frozenset({str, bytes, bytearray, float, complex, bool, type(None)}) +_builtin_scalars = frozenset( + {str, bytes, bytearray, float, complex, bool, type(None), int} +) def _recursion(object: Any) -> str: From 283a746dad7a3d88c35783bd4da595e507b855bd Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 3 Dec 2023 13:50:44 +0000 Subject: [PATCH 029/104] pprint: Remove conversion to int, we only accept those --- src/_pytest/_io/pprint.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index ea1073af6..7559c6778 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -78,8 +78,6 @@ class PrettyPrinter: The maximum depth to print out nested structures. """ - indent = int(indent) - width = int(width) if indent < 0: raise ValueError("indent must be >= 0") if depth is not None and depth <= 0: From 581762fcba64336aff6feaaaebe12387328f7f4e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:43:27 +0000 Subject: [PATCH 030/104] [pre-commit.ci] pre-commit autoupdate (#11722) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.11.0 → 23.12.0](https://github.com/psf/black/compare/23.11.0...23.12.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9b1ee9cda..c55c64947 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 23.11.0 + rev: 23.12.0 hooks: - id: black args: [--safe, --quiet] From 75f292d9df38827d40d444371428f092e7321c27 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 19 Dec 2023 23:29:27 +0200 Subject: [PATCH 031/104] Some minor typing tweaks --- src/_pytest/logging.py | 20 ++++++++++++++------ src/_pytest/runner.py | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 1c6bb923b..5426c3513 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -11,15 +11,18 @@ from datetime import timezone from io import StringIO from logging import LogRecord from pathlib import Path +from types import TracebackType from typing import AbstractSet from typing import Dict from typing import final from typing import Generator +from typing import Generic from typing import List from typing import Literal from typing import Mapping from typing import Optional from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -62,7 +65,7 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt=None) -> str: + def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: if datefmt and "%f" in datefmt: ct = self.converter(record.created) tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) @@ -331,7 +334,7 @@ _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) # Not using @contextmanager for performance reasons. -class catching_logs: +class catching_logs(Generic[_HandlerType]): """Context manager that prepares the whole logging machinery properly.""" __slots__ = ("handler", "level", "orig_level") @@ -340,7 +343,7 @@ class catching_logs: self.handler = handler self.level = level - def __enter__(self): + def __enter__(self) -> _HandlerType: root_logger = logging.getLogger() if self.level is not None: self.handler.setLevel(self.level) @@ -350,7 +353,12 @@ class catching_logs: root_logger.setLevel(min(self.orig_level, self.level)) return self.handler - def __exit__(self, type, value, traceback): + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: root_logger = logging.getLogger() if self.level is not None: root_logger.setLevel(self.orig_level) @@ -421,7 +429,7 @@ class LogCaptureFixture: return self._item.stash[caplog_handler_key] def get_records( - self, when: "Literal['setup', 'call', 'teardown']" + self, when: Literal["setup", "call", "teardown"] ) -> List[logging.LogRecord]: """Get the logging records for one of the possible test phases. @@ -742,7 +750,7 @@ class LoggingPlugin: if old_stream: old_stream.close() - def _log_cli_enabled(self): + def _log_cli_enabled(self) -> bool: """Return whether live logging is enabled.""" enabled = self._config.getoption( "--log-cli-level" diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 1b39f93cf..c03d707dc 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -317,7 +317,7 @@ class CallInfo(Generic[TResult]): @classmethod def from_call( cls, - func: "Callable[[], TResult]", + func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], reraise: Optional[ Union[Type[BaseException], Tuple[Type[BaseException], ...]] From 88ae27da085da7d59d34736234b57a280dcc9dfc Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Thu, 21 Dec 2023 17:11:56 +0000 Subject: [PATCH 032/104] Add syntactic highlights to the error explanations (#11661) * Put a 'reset' color in front of the highlighting When doing the highlighting, some lexers will not set the initial color explicitly, which may lead to the red from the errors being propagated to the start of the expression * Add syntactic highlighting to the error explanations This updates the various error reporting to highlight python code when displayed, to increase readability and make it easier to understand --- changelog/11520.improvement.rst | 2 + src/_pytest/_io/terminalwriter.py | 10 ++- src/_pytest/assertion/util.py | 103 ++++++++++++++++++++---------- testing/io/test_terminalwriter.py | 2 +- testing/test_assertion.py | 11 +++- testing/test_terminal.py | 14 ++-- 6 files changed, 96 insertions(+), 46 deletions(-) diff --git a/changelog/11520.improvement.rst b/changelog/11520.improvement.rst index d9b7b4933..548d52a12 100644 --- a/changelog/11520.improvement.rst +++ b/changelog/11520.improvement.rst @@ -1,3 +1,5 @@ Improved very verbose diff output to color it as a diff instead of only red. Improved the error reporting to better separate each section. + +Improved the error reporting to syntax-highlight Python code when Pygments is available. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 934278b93..2b2f49e9a 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -223,7 +223,15 @@ class TerminalWriter: style=os.getenv("PYTEST_THEME"), ), ) - return highlighted + # pygments terminal formatter may add a newline when there wasn't one. + # We don't want this, remove. + if highlighted[-1] == "\n" and source[-1] != "\n": + highlighted = highlighted[:-1] + + # Some lexers will not set the initial color explicitly + # which may lead to the previous color being propagated to the + # start of the expression, so reset first. + return "\x1b[0m" + highlighted except pygments.util.ClassNotFound: raise UsageError( "PYTEST_THEME environment variable had an invalid value: '{}'. " diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index fe8904e15..6f97101a9 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -192,12 +192,12 @@ def assertrepr_compare( right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii) summary = f"{left_repr} {op} {right_repr}" + highlighter = config.get_terminal_writer()._highlight explanation = None try: if op == "==": - writer = config.get_terminal_writer() - explanation = _compare_eq_any(left, right, writer._highlight, verbose) + explanation = _compare_eq_any(left, right, highlighter, verbose) elif op == "not in": if istext(left) and istext(right): explanation = _notin_text(left, right, verbose) @@ -206,16 +206,16 @@ def assertrepr_compare( explanation = ["Both sets are equal"] elif op == ">=": if isset(left) and isset(right): - explanation = _compare_gte_set(left, right, verbose) + explanation = _compare_gte_set(left, right, highlighter, verbose) elif op == "<=": if isset(left) and isset(right): - explanation = _compare_lte_set(left, right, verbose) + explanation = _compare_lte_set(left, right, highlighter, verbose) elif op == ">": if isset(left) and isset(right): - explanation = _compare_gt_set(left, right, verbose) + explanation = _compare_gt_set(left, right, highlighter, verbose) elif op == "<": if isset(left) and isset(right): - explanation = _compare_lt_set(left, right, verbose) + explanation = _compare_lt_set(left, right, highlighter, verbose) except outcomes.Exit: raise @@ -259,11 +259,11 @@ def _compare_eq_any( # used in older code bases before dataclasses/attrs were available. explanation = _compare_eq_cls(left, right, highlighter, verbose) elif issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right, verbose) + explanation = _compare_eq_sequence(left, right, highlighter, verbose) elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right, verbose) + explanation = _compare_eq_set(left, right, highlighter, verbose) elif isdict(left) and isdict(right): - explanation = _compare_eq_dict(left, right, verbose) + explanation = _compare_eq_dict(left, right, highlighter, verbose) if isiterable(left) and isiterable(right): expl = _compare_eq_iterable(left, right, highlighter, verbose) @@ -350,7 +350,10 @@ def _compare_eq_iterable( def _compare_eq_sequence( - left: Sequence[Any], right: Sequence[Any], verbose: int = 0 + left: Sequence[Any], + right: Sequence[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) explanation: List[str] = [] @@ -373,7 +376,10 @@ def _compare_eq_sequence( left_value = left[i] right_value = right[i] - explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"] + explanation.append( + f"At index {i} diff:" + f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}" + ) break if comparing_bytes: @@ -393,68 +399,91 @@ def _compare_eq_sequence( extra = saferepr(right[len_left]) if len_diff == 1: - explanation += [f"{dir_with_more} contains one more item: {extra}"] + explanation += [ + f"{dir_with_more} contains one more item: {highlighter(extra)}" + ] else: explanation += [ "%s contains %d more items, first extra item: %s" - % (dir_with_more, len_diff, extra) + % (dir_with_more, len_diff, highlighter(extra)) ] return explanation def _compare_eq_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: explanation = [] - explanation.extend(_set_one_sided_diff("left", left, right)) - explanation.extend(_set_one_sided_diff("right", right, left)) + explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) + explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) return explanation def _compare_gt_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: - explanation = _compare_gte_set(left, right, verbose) + explanation = _compare_gte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] return explanation def _compare_lt_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: - explanation = _compare_lte_set(left, right, verbose) + explanation = _compare_lte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] return explanation def _compare_gte_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: - return _set_one_sided_diff("right", right, left) + return _set_one_sided_diff("right", right, left, highlighter) def _compare_lte_set( - left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 + left: AbstractSet[Any], + right: AbstractSet[Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: - return _set_one_sided_diff("left", left, right) + return _set_one_sided_diff("left", left, right, highlighter) def _set_one_sided_diff( - posn: str, set1: AbstractSet[Any], set2: AbstractSet[Any] + posn: str, + set1: AbstractSet[Any], + set2: AbstractSet[Any], + highlighter: _HighlightFunc, ) -> List[str]: explanation = [] diff = set1 - set2 if diff: explanation.append(f"Extra items in the {posn} set:") for item in diff: - explanation.append(saferepr(item)) + explanation.append(highlighter(saferepr(item))) return explanation def _compare_eq_dict( - left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0 + left: Mapping[Any, Any], + right: Mapping[Any, Any], + highlighter: _HighlightFunc, + verbose: int = 0, ) -> List[str]: explanation: List[str] = [] set_left = set(left) @@ -465,12 +494,16 @@ def _compare_eq_dict( explanation += ["Omitting %s identical items, use -vv to show" % len(same)] elif same: explanation += ["Common items:"] - explanation += pprint.pformat(same).splitlines() + explanation += highlighter(pprint.pformat(same)).splitlines() diff = {k for k in common if left[k] != right[k]} if diff: explanation += ["Differing items:"] for k in diff: - explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] + explanation += [ + highlighter(saferepr({k: left[k]})) + + " != " + + highlighter(saferepr({k: right[k]})) + ] extra_left = set_left - set_right len_extra_left = len(extra_left) if len_extra_left: @@ -479,7 +512,7 @@ def _compare_eq_dict( % (len_extra_left, "" if len_extra_left == 1 else "s") ) explanation.extend( - pprint.pformat({k: left[k] for k in extra_left}).splitlines() + highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines() ) extra_right = set_right - set_left len_extra_right = len(extra_right) @@ -489,7 +522,7 @@ def _compare_eq_dict( % (len_extra_right, "" if len_extra_right == 1 else "s") ) explanation.extend( - pprint.pformat({k: right[k] for k in extra_right}).splitlines() + highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines() ) return explanation @@ -528,17 +561,17 @@ def _compare_eq_cls( explanation.append("Omitting %s identical items, use -vv to show" % len(same)) elif same: explanation += ["Matching attributes:"] - explanation += pprint.pformat(same).splitlines() + explanation += highlighter(pprint.pformat(same)).splitlines() if diff: explanation += ["Differing attributes:"] - explanation += pprint.pformat(diff).splitlines() + explanation += highlighter(pprint.pformat(diff)).splitlines() for field in diff: field_left = getattr(left, field) field_right = getattr(right, field) explanation += [ "", - "Drill down into differing attribute %s:" % field, - ("%s%s: %r != %r") % (indent, field, field_left, field_right), + f"Drill down into differing attribute {field}:", + f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}", ] explanation += [ indent + line diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index b5a04a99f..a2d730b07 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -254,7 +254,7 @@ class TestTerminalWriterLineWidth: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", + "{reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 4d751f8db..8a4b2c62e 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -20,7 +20,7 @@ from _pytest.pytester import Pytester def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): class TerminalWriter: - def _highlight(self, source, lexer): + def _highlight(self, source, lexer="python"): return source class Config: @@ -1933,6 +1933,7 @@ def test_reprcompare_verbose_long() -> None: assert [0, 1] == [0, 2] """, [ + "{bold}{red}E At index 1 diff: {reset}{number}1{hl-reset}{endline} != {reset}{number}2*", "{bold}{red}E {light-red}- 2,{hl-reset}{endline}{reset}", "{bold}{red}E {light-green}+ 1,{hl-reset}{endline}{reset}", ], @@ -1945,7 +1946,13 @@ def test_reprcompare_verbose_long() -> None: } """, [ - "{bold}{red}E {light-gray} {hl-reset} {{{endline}{reset}", + "{bold}{red}E Common items:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-1{hl-reset}{str}'{hl-reset}: {number}1*", + "{bold}{red}E Left contains 1 more item:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-5{hl-reset}{str}'{hl-reset}: {number}5*", + "{bold}{red}E Right contains 1 more item:{reset}", + "{bold}{red}E {reset}{{{str}'{hl-reset}{str}number-is-0{hl-reset}{str}'{hl-reset}: {number}0*", + "{bold}{red}E {reset}{light-gray} {hl-reset} {{{endline}{reset}", "{bold}{red}E {light-gray} {hl-reset} 'number-is-1': 1,{endline}{reset}", "{bold}{red}E {light-green}+ 'number-is-5': 5,{hl-reset}{endline}{reset}", ], diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 264ab96d8..80958f210 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1268,13 +1268,13 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None: "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "", - " {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + " {reset}{kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + " {reset}{kw}def{hl-reset} {function}fail{hl-reset}():{endline}", "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", @@ -1295,9 +1295,9 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None: "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "{bold}{red}test_color_yes.py{reset}:5: in test_this", - " fail(){endline}", + " {reset}fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}{endline}", + " {reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -2507,7 +2507,7 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] @@ -2529,7 +2529,7 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", @@ -2552,7 +2552,7 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + " {reset}{kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] From 52db918a27b2eb5043de6e80215076a98b0b9fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sat, 23 Dec 2023 09:12:13 +0100 Subject: [PATCH 033/104] Fix handling empty values of NO_COLOR and FORCE_COLOR (#11712) * Fix handling empty values of NO_COLOR and FORCE_COLOR Fix handling NO_COLOR and FORCE_COLOR environment variables to correctly be ignored when they are set to an empty value, as defined in the specification: > Command-line software which adds ANSI color to its output by default > should check for a NO_COLOR environment variable that, when present > *and not an empty string* (regardless of its value), prevents > the addition of ANSI color. (emphasis mine, https://no-color.org/) The same is true of FORCE_COLOR, https://force-color.org/. * Streamline testing for FORCE_COLOR and NO_COLOR Streamline the tests for FORCE_COLOR and NO_COLOR variables, and cover all possible cases (unset, set to empty, set to "1"). Combine the two assert functions into one taking boolean parameters. Mock file.isatty in all circumstances to ensure that the environment variables take precedence over the fallback value resulting from isatty check (or that the fallback is actually used, in the case of both FORCE_COLOR and NO_COLOR being unset). --- AUTHORS | 1 + changelog/11712.bugfix.rst | 1 + doc/en/reference/reference.rst | 4 +- src/_pytest/_io/terminalwriter.py | 4 +- testing/io/test_terminalwriter.py | 63 +++++++++++++++++++------------ 5 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 changelog/11712.bugfix.rst diff --git a/AUTHORS b/AUTHORS index bb273edcc..42cfd0be2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -266,6 +266,7 @@ Michael Goerz Michael Krebs Michael Seifert Michal Wajszczuk +Michał Górny Michał Zięba Mickey Pashov Mihai Capotă diff --git a/changelog/11712.bugfix.rst b/changelog/11712.bugfix.rst new file mode 100644 index 000000000..416d76149 --- /dev/null +++ b/changelog/11712.bugfix.rst @@ -0,0 +1 @@ +Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 3054109ba..b2b63a89e 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1146,13 +1146,13 @@ When set to ``0``, pytest will not use color. .. envvar:: NO_COLOR -When set (regardless of value), pytest will not use color in terminal output. +When set to a non-empty string (regardless of value), pytest will not use color in terminal output. ``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``. See `no-color.org `__ for other libraries supporting this community standard. .. envvar:: FORCE_COLOR -When set (regardless of value), pytest will use color in terminal output. +When set to a non-empty string (regardless of value), pytest will use color in terminal output. ``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``. Exceptions diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 2b2f49e9a..bf9b76651 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -29,9 +29,9 @@ def should_do_markup(file: TextIO) -> bool: return True if os.environ.get("PY_COLORS") == "0": return False - if "NO_COLOR" in os.environ: + if os.environ.get("NO_COLOR"): return False - if "FORCE_COLOR" in os.environ: + if os.environ.get("FORCE_COLOR"): return True return ( hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index a2d730b07..96e7366e5 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -5,6 +5,7 @@ import shutil import sys from pathlib import Path from typing import Generator +from typing import Optional from unittest import mock import pytest @@ -164,53 +165,67 @@ def test_attr_hasmarkup() -> None: assert "\x1b[0m" in s -def assert_color_set(): +def assert_color(expected: bool, default: Optional[bool] = None) -> None: file = io.StringIO() - tw = terminalwriter.TerminalWriter(file) - assert tw.hasmarkup + if default is None: + default = not expected + file.isatty = lambda: default # type: ignore + tw = terminalwriter.TerminalWriter(file=file) + assert tw.hasmarkup is expected tw.line("hello", bold=True) s = file.getvalue() - assert len(s) > len("hello\n") - assert "\x1b[1m" in s - assert "\x1b[0m" in s - - -def assert_color_not_set(): - f = io.StringIO() - f.isatty = lambda: True # type: ignore - tw = terminalwriter.TerminalWriter(file=f) - assert not tw.hasmarkup - tw.line("hello", bold=True) - s = f.getvalue() - assert s == "hello\n" + if expected: + assert len(s) > len("hello\n") + assert "\x1b[1m" in s + assert "\x1b[0m" in s + else: + assert s == "hello\n" def test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "PY_COLORS", "1") - assert_color_set() + assert_color(True) def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "PY_COLORS", "0") - assert_color_not_set() + assert_color(False) def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "NO_COLOR", "1") - assert_color_not_set() + assert_color(False) def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None: monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") - assert_color_set() + assert_color(True) -def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR( +@pytest.mark.parametrize( + ["NO_COLOR", "FORCE_COLOR", "expected"], + [ + ("1", "1", False), + ("", "1", True), + ("1", "", False), + ], +) +def test_NO_COLOR_and_FORCE_COLOR( monkeypatch: MonkeyPatch, + NO_COLOR: str, + FORCE_COLOR: str, + expected: bool, ) -> None: - monkeypatch.setitem(os.environ, "NO_COLOR", "1") - monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") - assert_color_not_set() + monkeypatch.setitem(os.environ, "NO_COLOR", NO_COLOR) + monkeypatch.setitem(os.environ, "FORCE_COLOR", FORCE_COLOR) + assert_color(expected) + + +def test_empty_NO_COLOR_and_FORCE_COLOR_ignored(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "NO_COLOR", "") + monkeypatch.setitem(os.environ, "FORCE_COLOR", "") + assert_color(True, True) + assert_color(False, False) class TestTerminalWriterLineWidth: From f93c0fc34eff371ecae3cd82aa362de2edf7be74 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 24 Dec 2023 00:20:08 +0000 Subject: [PATCH 034/104] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 86 +++++++++++++++++--------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 8317af7ca..7e766c41c 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =7.0.1) :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Dec 12, 2023 5 - Production/Stable pytest - :pypi:`pytest-archon` Rule your architecture like a real developer Jul 11, 2023 5 - Production/Stable pytest (>=7.2) + :pypi:`pytest-archon` Rule your architecture like a real developer Dec 18, 2023 5 - Production/Stable pytest >=7.2 :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Nov 27, 2023 4 - Beta pytest >=4.6 :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) - :pypi:`pytest-aspec` A rspec format reporter for pytest Oct 23, 2023 4 - Beta N/A + :pypi:`pytest-aspec` A rspec format reporter for pytest Dec 20, 2023 4 - Beta N/A :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A :pypi:`pytest-assertcount` Plugin to count actual number of asserts in pytest Oct 23, 2022 N/A pytest (>=5.0.0) :pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A @@ -207,7 +207,7 @@ This list contains 1355 plugins. :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest :pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A - :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Nov 30, 2023 N/A pytest >=7.0.0 + :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Dec 19, 2023 N/A pytest >=7.0.0 :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Oct 20, 2022 N/A N/A @@ -531,12 +531,12 @@ This list contains 1355 plugins. :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A - :pypi:`pytest-gee` The Python plugin for your GEE based packages. Dec 04, 2023 3 - Alpha pytest + :pypi:`pytest-gee` The Python plugin for your GEE based packages. Dec 18, 2023 3 - Alpha pytest :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Dec 14, 2023 N/A N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Dec 20, 2023 N/A N/A :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A @@ -599,12 +599,12 @@ This list contains 1355 plugins. :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest May 22, 2023 3 - Alpha N/A :pypi:`pytest-httptesting` http_testing framework on top of pytest Jul 24, 2023 N/A pytest (>=7.2.0,<8.0.0) - :pypi:`pytest-httpx` Send responses to httpx. Nov 13, 2023 5 - Production/Stable pytest ==7.* + :pypi:`pytest-httpx` Send responses to httpx. Dec 21, 2023 5 - Production/Stable pytest ==7.* :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A - :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Dec 15, 2023 3 - Alpha pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Dec 22, 2023 3 - Alpha pytest (>=7.0.0,<8.0.0) :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta pytest>=7.1 :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Dec 05, 2023 4 - Beta pytest :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A @@ -690,7 +690,7 @@ This list contains 1355 plugins. :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Dec 06, 2023 4 - Beta N/A + :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Dec 22, 2023 4 - Beta N/A :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest @@ -753,7 +753,7 @@ This list contains 1355 plugins. :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) - :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 06, 2023 N/A pytest >=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 20, 2023 N/A pytest >=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) @@ -911,6 +911,7 @@ This list contains 1355 plugins. :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A + :pypi:`pytest-pook` Pytest plugin for pook Dec 23, 2023 4 - Beta pytest :pypi:`pytest-pop` A pytest plugin to help with testing pop projects May 09, 2023 5 - Production/Stable pytest :pypi:`pytest-porringer` Oct 03, 2023 N/A pytest>=7.4.0 :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) @@ -965,7 +966,7 @@ This list contains 1355 plugins. :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Nov 29, 2023 5 - Production/Stable pytest >=6.0 :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A - :pypi:`pytest-qt` pytest support for PyQt and PySide applications Oct 25, 2022 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-qt` pytest support for PyQt and PySide applications Dec 22, 2023 5 - Production/Stable pytest >=3.0.0 :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 05, 2022 4 - Beta pytest (>=4.0) @@ -1074,7 +1075,7 @@ This list contains 1355 plugins. :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 08, 2023 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 23, 2023 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) @@ -1083,15 +1084,15 @@ This list contains 1355 plugins. :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) :pypi:`pytest-selenium` pytest plugin for Selenium Nov 20, 2023 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 08, 2023 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 23, 2023 5 - Production/Stable N/A :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Jan 05, 2023 N/A N/A :pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A - :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 5 - Production/Stable pytest :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A - :pypi:`pytest-servers` pytest servers Dec 15, 2023 3 - Alpha pytest >=6.2 + :pypi:`pytest-servers` pytest servers Dec 19, 2023 3 - Alpha pytest >=6.2 :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A @@ -1138,7 +1139,7 @@ This list contains 1355 plugins. :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-sort` Tools for sorting test cases Oct 06, 2023 N/A pytest >=7.4.0 + :pypi:`pytest-sort` Tools for sorting test cases Dec 22, 2023 N/A pytest >=7.4.0 :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Aug 04, 2023 2 - Pre-Alpha pytest :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest @@ -1156,7 +1157,7 @@ This list contains 1355 plugins. :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Dec 14, 2023 N/A pytest (>5.4.0,<8) + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Dec 21, 2023 N/A pytest (>5.4.0,<8) :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Dec 01, 2023 N/A N/A :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A @@ -1325,7 +1326,7 @@ This list contains 1355 plugins. :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest :pypi:`pytest-vcs` Sep 22, 2022 4 - Beta N/A :pypi:`pytest-venv` py.test fixture for creating a virtual environment Nov 23, 2023 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Dec 12, 2023 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Dec 19, 2023 4 - Beta pytest :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest @@ -1374,7 +1375,7 @@ This list contains 1355 plugins. :pypi:`pytest-xvfb` A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. May 29, 2023 4 - Beta pytest (>=2.8.1) :pypi:`pytest-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest - :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Nov 30, 2023 N/A pytest>=7.4.0 + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Dec 18, 2023 N/A pytest>=7.4.0 :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A :pypi:`pytest-yaml-yoyo` http/https API run by yaml Jun 19, 2023 N/A pytest (>=7.2.0) @@ -1766,9 +1767,9 @@ This list contains 1355 plugins. Extension for ApprovalTests.Python specific to geo data verification :pypi:`pytest-archon` - *last release*: Jul 11, 2023, + *last release*: Dec 18, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=7.2) + *requires*: pytest >=7.2 Rule your architecture like a real developer @@ -1794,7 +1795,7 @@ This list contains 1355 plugins. Convenient ASGI client/server fixtures for Pytest :pypi:`pytest-aspec` - *last release*: Oct 23, 2023, + *last release*: Dec 20, 2023, *status*: 4 - Beta, *requires*: N/A @@ -2606,7 +2607,7 @@ This list contains 1355 plugins. A pytest plugin to send a report and printing summary of tests. :pypi:`pytest-choose` - *last release*: Nov 30, 2023, + *last release*: Dec 19, 2023, *status*: N/A, *requires*: pytest >=7.0.0 @@ -4874,7 +4875,7 @@ This list contains 1355 plugins. Uses gcov to measure test coverage of a C library :pypi:`pytest-gee` - *last release*: Dec 04, 2023, + *last release*: Dec 18, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -4909,7 +4910,7 @@ This list contains 1355 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Dec 14, 2023, + *last release*: Dec 20, 2023, *status*: N/A, *requires*: N/A @@ -5350,7 +5351,7 @@ This list contains 1355 plugins. http_testing framework on top of pytest :pypi:`pytest-httpx` - *last release*: Nov 13, 2023, + *last release*: Dec 21, 2023, *status*: 5 - Production/Stable, *requires*: pytest ==7.* @@ -5385,7 +5386,7 @@ This list contains 1355 plugins. help hypo module for pytest :pypi:`pytest-iam` - *last release*: Dec 15, 2023, + *last release*: Dec 22, 2023, *status*: 3 - Alpha, *requires*: pytest (>=7.0.0,<8.0.0) @@ -5987,7 +5988,7 @@ This list contains 1355 plugins. A python-libfaketime plugin for pytest. :pypi:`pytest-libiio` - *last release*: Dec 06, 2023, + *last release*: Dec 22, 2023, *status*: 4 - Beta, *requires*: N/A @@ -6428,7 +6429,7 @@ This list contains 1355 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: Dec 06, 2023, + *last release*: Dec 20, 2023, *status*: N/A, *requires*: pytest >=5.0.0 @@ -7533,6 +7534,13 @@ This list contains 1355 plugins. Visualize your failed tests with poo + :pypi:`pytest-pook` + *last release*: Dec 23, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Pytest plugin for pook + :pypi:`pytest-pop` *last release*: May 09, 2023, *status*: 5 - Production/Stable, @@ -7912,9 +7920,9 @@ This list contains 1355 plugins. pytest plugin to generate test result QR codes :pypi:`pytest-qt` - *last release*: Oct 25, 2022, + *last release*: Dec 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest >=3.0.0 pytest support for PyQt and PySide applications @@ -8675,7 +8683,7 @@ This list contains 1355 plugins. :pypi:`pytest-sbase` - *last release*: Dec 08, 2023, + *last release*: Dec 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -8738,7 +8746,7 @@ This list contains 1355 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Dec 08, 2023, + *last release*: Dec 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -8780,7 +8788,7 @@ This list contains 1355 plugins. Pytest plugin for sequencing markers for execution of tests :pypi:`pytest-server-fixtures` - *last release*: May 28, 2019, + *last release*: Dec 19, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -8794,7 +8802,7 @@ This list contains 1355 plugins. Automatically mocks resources from serverless.yml in pytest using moto. :pypi:`pytest-servers` - *last release*: Dec 15, 2023, + *last release*: Dec 19, 2023, *status*: 3 - Alpha, *requires*: pytest >=6.2 @@ -9123,7 +9131,7 @@ This list contains 1355 plugins. Solr process and client fixtures for py.test. :pypi:`pytest-sort` - *last release*: Oct 06, 2023, + *last release*: Dec 22, 2023, *status*: N/A, *requires*: pytest >=7.4.0 @@ -9249,7 +9257,7 @@ This list contains 1355 plugins. :pypi:`pytest-splunk-addon` - *last release*: Dec 14, 2023, + *last release*: Dec 21, 2023, *status*: N/A, *requires*: pytest (>5.4.0,<8) @@ -10432,7 +10440,7 @@ This list contains 1355 plugins. py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Dec 12, 2023, + *last release*: Dec 19, 2023, *status*: 4 - Beta, *requires*: pytest @@ -10775,7 +10783,7 @@ This list contains 1355 plugins. This plugin is used to load yaml output to your test using pytest framework. :pypi:`pytest-yaml-sanmu` - *last release*: Nov 30, 2023, + *last release*: Dec 18, 2023, *status*: N/A, *requires*: pytest>=7.4.0 From 1e5aab1b2b8900ec6287ff168951184f9a7abecd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:43:18 +0000 Subject: [PATCH 035/104] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1) - [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c55c64947..7c328ad48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 23.12.0 + rev: 23.12.1 hooks: - id: black args: [--safe, --quiet] @@ -56,7 +56,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.1 + rev: v1.8.0 hooks: - id: mypy files: ^(src/|testing/) From a71a95b54cac6afd14ea36ede39b86b28155110e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 30 Dec 2023 22:12:45 +0200 Subject: [PATCH 036/104] Change "Marks applied to fixtures" removal from 8 to 9 The deprecation has only been added in 8.0, so can't be removed in 8. That will have to wait for 9. --- src/_pytest/deprecated.py | 3 ++- testing/deprecated_test.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 3fcf99ba4..77279d634 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -12,6 +12,7 @@ from warnings import warn from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestRemovedIn8Warning +from _pytest.warning_types import PytestRemovedIn9Warning from _pytest.warning_types import UnformattedWarning # set of plugins which have been integrated into the core; we use this list to ignore @@ -122,7 +123,7 @@ HOOK_LEGACY_MARKING = UnformattedWarning( "#configuring-hook-specs-impls-using-markers", ) -MARKED_FIXTURE = PytestRemovedIn8Warning( +MARKED_FIXTURE = PytestRemovedIn9Warning( "Marks applied to fixtures have no effect\n" "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function" ) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index fcd824d5f..0736ed1dc 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -290,7 +290,7 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None: def test_fixture_disallow_on_marked_functions(): """Test that applying @pytest.fixture to a marked function warns (#3364).""" with pytest.warns( - pytest.PytestRemovedIn8Warning, + pytest.PytestRemovedIn9Warning, match=r"Marks applied to fixtures have no effect", ) as record: @@ -309,7 +309,7 @@ def test_fixture_disallow_on_marked_functions(): def test_fixture_disallow_marks_on_fixtures(): """Test that applying a mark to a fixture warns (#3364).""" with pytest.warns( - pytest.PytestRemovedIn8Warning, + pytest.PytestRemovedIn9Warning, match=r"Marks applied to fixtures have no effect", ) as record: @@ -325,7 +325,7 @@ def test_fixture_disallow_marks_on_fixtures(): def test_fixture_disallowed_between_marks(): """Test that applying a mark to a fixture warns (#3364).""" with pytest.warns( - pytest.PytestRemovedIn8Warning, + pytest.PytestRemovedIn9Warning, match=r"Marks applied to fixtures have no effect", ) as record: From 460c38915d5bf5b52e8d6f99bb3fb2dc2c861bbb Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 31 Dec 2023 00:20:30 +0000 Subject: [PATCH 037/104] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 82 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 7e766c41c..a967a33bd 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =2.6) :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Dec 07, 2023 N/A N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Dec 28, 2023 N/A N/A :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A @@ -207,7 +207,7 @@ This list contains 1356 plugins. :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest :pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A - :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Dec 19, 2023 N/A pytest >=7.0.0 + :pypi:`pytest-choose` Provide the pytest with the ability to collect use cases based on rules in text files Dec 26, 2023 N/A pytest >=7.0.0 :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Oct 20, 2022 N/A N/A @@ -410,18 +410,18 @@ This list contains 1356 plugins. :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Dec 04, 2023 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Dec 04, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Dec 04, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Dec 29, 2023 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Dec 29, 2023 5 - Production/Stable N/A :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) - :pypi:`pytest-enabler` Enable installed pytest plugins Jul 14, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-enabler` Enable installed pytest plugins Dec 23, 2023 5 - Production/Stable pytest >=6 ; extra == 'testing' :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest :pypi:`pytest-encoding` set your encoding and logger Aug 11, 2023 N/A pytest @@ -577,9 +577,9 @@ This list contains 1356 plugins. :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 15, 2023 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hot-reloading` Dec 13, 2023 N/A N/A + :pypi:`pytest-hot-reloading` Dec 27, 2023 N/A N/A :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Nov 10, 2023 N/A pytest + :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Dec 25, 2023 N/A pytest :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 27, 2023 5 - Production/Stable pytest (>=3.7.0) :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Feb 28, 2023 4 - Beta pytest (>=6.2.4,<7.0.0) @@ -753,7 +753,7 @@ This list contains 1356 plugins. :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) - :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 20, 2023 N/A pytest >=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 24, 2023 N/A pytest >=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) @@ -1256,13 +1256,14 @@ This list contains 1356 plugins. :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) :pypi:`pytest-time` Jun 24, 2023 3 - Alpha pytest - :pypi:`pytest-timeassert-ethan` execution duration Dec 12, 2023 N/A pytest + :pypi:`pytest-timeassert-ethan` execution duration Dec 25, 2023 N/A pytest :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A :pypi:`pytest-timeout` pytest plugin to abort hanging tests Oct 08, 2023 5 - Production/Stable pytest >=5.0.0 :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A + :pypi:`pytest-timer` A timer plugin for pytest Dec 26, 2023 N/A pytest :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Sep 11, 2023 N/A pytest (>=7.3,<8.0) + :pypi:`pytest-tiny-api-client` The companion pytest plugin for tiny-api-client Dec 28, 2023 5 - Production/Stable pytest :pypi:`pytest-tinybird` A pytest plugin to report test results to tinybird Jun 26, 2023 4 - Beta pytest (>=3.8.0) :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) @@ -1365,7 +1366,7 @@ This list contains 1356 plugins. :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A :pypi:`pytest-xiuyu` This is a pytest plugin Jul 25, 2023 5 - Production/Stable N/A :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A - :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Jul 03, 2023 N/A pytest<8,>=7.4.0 + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Dec 28, 2023 N/A pytest<8,>=7.4.0 :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Sep 23, 2023 4 - Beta pytest (>=2.8) :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A @@ -2488,7 +2489,7 @@ This list contains 1356 plugins. Pytest plugin with server for catching HTTP requests. :pypi:`pytest-celery` - *last release*: Dec 07, 2023, + *last release*: Dec 28, 2023, *status*: N/A, *requires*: N/A @@ -2607,7 +2608,7 @@ This list contains 1356 plugins. A pytest plugin to send a report and printing summary of tests. :pypi:`pytest-choose` - *last release*: Dec 19, 2023, + *last release*: Dec 26, 2023, *status*: N/A, *requires*: pytest >=7.0.0 @@ -4028,56 +4029,56 @@ This list contains 1356 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Dec 04, 2023, + *last release*: Dec 29, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4105,9 +4106,9 @@ This list contains 1356 plugins. Pytest plugin to represent test output with emoji support :pypi:`pytest-enabler` - *last release*: Jul 14, 2023, + *last release*: Dec 23, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6) ; extra == 'testing' + *requires*: pytest >=6 ; extra == 'testing' Enable installed pytest plugins @@ -5197,7 +5198,7 @@ This list contains 1356 plugins. Report on tests that honor constraints, and guard against regressions :pypi:`pytest-hot-reloading` - *last release*: Dec 13, 2023, + *last release*: Dec 27, 2023, *status*: N/A, *requires*: N/A @@ -5211,7 +5212,7 @@ This list contains 1356 plugins. A plugin that tracks test changes :pypi:`pytest-houdini` - *last release*: Nov 10, 2023, + *last release*: Dec 25, 2023, *status*: N/A, *requires*: pytest @@ -6429,7 +6430,7 @@ This list contains 1356 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: Dec 20, 2023, + *last release*: Dec 24, 2023, *status*: N/A, *requires*: pytest >=5.0.0 @@ -9950,7 +9951,7 @@ This list contains 1356 plugins. :pypi:`pytest-timeassert-ethan` - *last release*: Dec 12, 2023, + *last release*: Dec 25, 2023, *status*: N/A, *requires*: pytest @@ -9978,9 +9979,9 @@ This list contains 1356 plugins. Linux-only Pytest plugin to control durations of various test case execution phases :pypi:`pytest-timer` - *last release*: Jun 02, 2021, + *last release*: Dec 26, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest A timer plugin for pytest @@ -9998,6 +9999,13 @@ This list contains 1356 plugins. A simple plugin to view timestamps for each test + :pypi:`pytest-tiny-api-client` + *last release*: Dec 28, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + The companion pytest plugin for tiny-api-client + :pypi:`pytest-tinybird` *last release*: Jun 26, 2023, *status*: 4 - Beta, @@ -10713,7 +10721,7 @@ This list contains 1356 plugins. Extended logging for test and decorators :pypi:`pytest-xlsx` - *last release*: Jul 03, 2023, + *last release*: Dec 28, 2023, *status*: N/A, *requires*: pytest<8,>=7.4.0 From d220880924c7ba827ff8b4d68f991fb3e67a1cd0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 31 Dec 2023 10:14:23 +0200 Subject: [PATCH 038/104] nodes: fix tracebacks from collection errors are not getting pruned (#11711) Fix #11710. --- changelog/11710.bugfix.rst | 1 + src/_pytest/nodes.py | 2 +- testing/test_collection.py | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelog/11710.bugfix.rst diff --git a/changelog/11710.bugfix.rst b/changelog/11710.bugfix.rst new file mode 100644 index 000000000..4bbf9fa2e --- /dev/null +++ b/changelog/11710.bugfix.rst @@ -0,0 +1 @@ +Fixed tracebacks from collection errors not getting pruned. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 29efd56f4..530714108 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -579,7 +579,7 @@ class Collector(Node, abc.ABC): ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - return excinfo.traceback.filter(excinfo) + return ntraceback.filter(excinfo) return excinfo.traceback diff --git a/testing/test_collection.py b/testing/test_collection.py index deed4bda5..be65169f7 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -345,6 +345,29 @@ class TestPrunetraceback: result = pytester.runpytest(p) result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"]) + def test_collection_error_traceback_is_clean(self, pytester: Pytester) -> None: + """When a collection error occurs, the report traceback doesn't contain + internal pytest stack entries. + + Issue #11710. + """ + pytester.makepyfile( + """ + raise Exception("LOUSY") + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*ERROR collecting*", + "test_*.py:1: in ", + ' raise Exception("LOUSY")', + "E Exception: LOUSY", + "*= short test summary info =*", + ], + consecutive=True, + ) + class TestCustomConftests: def test_ignore_collect_path(self, pytester: Pytester) -> None: From a1b6b7473b103b6dfa6c2b4bf8d680713d3f6047 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 31 Dec 2023 14:10:58 +0200 Subject: [PATCH 039/104] Merge pull request #11752 from pytest-dev/release-7.4.4 Prepare release 7.4.4 (cherry picked from commit 18dcd9d38d18fc01bf1f7f5f60db69f785957101) --- changelog/11091.doc.rst | 1 - changelog/11146.bugfix.rst | 1 - changelog/11572.bugfix.rst | 1 - changelog/11710.bugfix.rst | 1 - changelog/7966.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-7.4.4.rst | 20 ++++++++++++++++++++ doc/en/changelog.rst | 25 +++++++++++++++++++++++++ doc/en/getting-started.rst | 2 +- 9 files changed, 47 insertions(+), 6 deletions(-) delete mode 100644 changelog/11091.doc.rst delete mode 100644 changelog/11146.bugfix.rst delete mode 100644 changelog/11572.bugfix.rst delete mode 100644 changelog/11710.bugfix.rst delete mode 100644 changelog/7966.bugfix.rst create mode 100644 doc/en/announce/release-7.4.4.rst diff --git a/changelog/11091.doc.rst b/changelog/11091.doc.rst deleted file mode 100644 index 429f2ac28..000000000 --- a/changelog/11091.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Updated documentation and tests to refer to hyphonated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``. diff --git a/changelog/11146.bugfix.rst b/changelog/11146.bugfix.rst deleted file mode 100644 index 03b468f30..000000000 --- a/changelog/11146.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -- Prevent constants at the top of file from being detected as docstrings. diff --git a/changelog/11572.bugfix.rst b/changelog/11572.bugfix.rst deleted file mode 100644 index 7a235a071..000000000 --- a/changelog/11572.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down. diff --git a/changelog/11710.bugfix.rst b/changelog/11710.bugfix.rst deleted file mode 100644 index 4bbf9fa2e..000000000 --- a/changelog/11710.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed tracebacks from collection errors not getting pruned. diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst deleted file mode 100644 index de0557680..000000000 --- a/changelog/7966.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Removes unhelpful error message from assertion rewrite mechanism when exceptions raised in __iter__ methods, and instead treats them as un-iterable. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 854666f67..35fd2c814 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-7.4.4 release-7.4.3 release-7.4.2 release-7.4.1 diff --git a/doc/en/announce/release-7.4.4.rst b/doc/en/announce/release-7.4.4.rst new file mode 100644 index 000000000..c9633678d --- /dev/null +++ b/doc/en/announce/release-7.4.4.rst @@ -0,0 +1,20 @@ +pytest-7.4.4 +======================================= + +pytest 7.4.4 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Ran Benita +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 35ea4fa32..4a3b9cdf6 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,31 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.4.4 (2023-12-31) +========================= + +Bug Fixes +--------- + +- `#11140 `_: Fix non-string constants at the top of file being detected as docstrings on Python>=3.8. + + +- `#11572 `_: Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down. + + +- `#11710 `_: Fixed tracebacks from collection errors not getting pruned. + + +- `#7966 `_: Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead. + + + +Improved Documentation +---------------------- + +- `#11091 `_: Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``. + + pytest 7.4.3 (2023-10-24) ========================= diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 3b9d773b0..c1de6271e 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 7.4.3 + pytest 7.4.4 .. _`simpletest`: From d3c7ba310ceafe05f1115fa5b6770d1329504aae Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 2 Jan 2024 10:58:20 +0200 Subject: [PATCH 040/104] Merge pull request #11744 from pytest-dev/release-8.0.0rc1 Prepare release 8.0.0rc1 (cherry picked from commit 665e4e58d3fba8319e922674c286e92f070e6ec3) --- changelog/10441.feature.rst | 2 - changelog/10465.deprecation.rst | 1 - changelog/10617.feature.rst | 2 - changelog/10701.bugfix.rst | 2 - changelog/11011.doc.rst | 1 - changelog/11065.doc.rst | 3 - changelog/11122.improvement.rst | 6 - changelog/11137.breaking.rst | 11 - changelog/11151.breaking.rst | 1 - changelog/11208.trivial.rst | 2 - changelog/11216.improvement.rst | 1 - changelog/11218.trivial.rst | 5 - changelog/11227.improvement.rst | 1 - changelog/11255.bugfix.rst | 1 - changelog/11277.bugfix.rst | 2 - changelog/11282.breaking.rst | 11 - changelog/11314.improvement.rst | 2 - changelog/11315.trivial.rst | 3 - changelog/11333.trivial.rst | 2 - changelog/11353.trivial.rst | 1 - changelog/11387.feature.rst | 5 - changelog/11447.improvement.rst | 1 - changelog/11456.bugfix.rst | 4 - changelog/11520.improvement.rst | 5 - changelog/11563.bugfix.rst | 1 - changelog/11600.improvement.rst | 1 - changelog/11610.feature.rst | 2 - changelog/11638.trivial.rst | 1 - changelog/11667.breaking.rst | 3 - changelog/11676.breaking.rst | 3 - changelog/11712.bugfix.rst | 1 - changelog/1531.improvement.rst | 4 - changelog/3664.deprecation.rst | 3 - changelog/7363.breaking.rst | 22 -- changelog/7469.feature.rst | 1 - changelog/7777.breaking.rst | 90 ------ changelog/8976.breaking.rst | 5 - changelog/9036.bugfix.rst | 1 - changelog/9288.breaking.rst | 7 - doc/en/announce/index.rst | 1 + doc/en/announce/release-8.0.0rc1.rst | 82 ++++++ doc/en/builtin.rst | 26 +- doc/en/changelog.rst | 371 ++++++++++++++++++++++++ doc/en/example/markers.rst | 28 +- doc/en/example/nonpython.rst | 6 +- doc/en/example/parametrize.rst | 62 ++-- doc/en/example/pythoncollection.rst | 27 +- doc/en/example/reportingdemo.rst | 19 +- doc/en/example/simple.rst | 74 ++--- doc/en/getting-started.rst | 6 +- doc/en/how-to/assert.rst | 9 +- doc/en/how-to/cache.rst | 8 +- doc/en/how-to/capture-stdout-stderr.rst | 2 +- doc/en/how-to/capture-warnings.rst | 2 +- doc/en/how-to/doctest.rst | 4 +- doc/en/how-to/fixtures.rst | 49 ++-- doc/en/how-to/output.rst | 63 ++-- doc/en/how-to/parametrize.rst | 4 +- doc/en/how-to/tmp_path.rst | 2 +- doc/en/how-to/unittest.rst | 2 +- doc/en/how-to/writing_plugins.rst | 2 +- doc/en/index.rst | 2 +- doc/en/reference/reference.rst | 4 + 63 files changed, 681 insertions(+), 394 deletions(-) delete mode 100644 changelog/10441.feature.rst delete mode 100644 changelog/10465.deprecation.rst delete mode 100644 changelog/10617.feature.rst delete mode 100644 changelog/10701.bugfix.rst delete mode 100644 changelog/11011.doc.rst delete mode 100644 changelog/11065.doc.rst delete mode 100644 changelog/11122.improvement.rst delete mode 100644 changelog/11137.breaking.rst delete mode 100644 changelog/11151.breaking.rst delete mode 100644 changelog/11208.trivial.rst delete mode 100644 changelog/11216.improvement.rst delete mode 100644 changelog/11218.trivial.rst delete mode 100644 changelog/11227.improvement.rst delete mode 100644 changelog/11255.bugfix.rst delete mode 100644 changelog/11277.bugfix.rst delete mode 100644 changelog/11282.breaking.rst delete mode 100644 changelog/11314.improvement.rst delete mode 100644 changelog/11315.trivial.rst delete mode 100644 changelog/11333.trivial.rst delete mode 100644 changelog/11353.trivial.rst delete mode 100644 changelog/11387.feature.rst delete mode 100644 changelog/11447.improvement.rst delete mode 100644 changelog/11456.bugfix.rst delete mode 100644 changelog/11520.improvement.rst delete mode 100644 changelog/11563.bugfix.rst delete mode 100644 changelog/11600.improvement.rst delete mode 100644 changelog/11610.feature.rst delete mode 100644 changelog/11638.trivial.rst delete mode 100644 changelog/11667.breaking.rst delete mode 100644 changelog/11676.breaking.rst delete mode 100644 changelog/11712.bugfix.rst delete mode 100644 changelog/1531.improvement.rst delete mode 100644 changelog/3664.deprecation.rst delete mode 100644 changelog/7363.breaking.rst delete mode 100644 changelog/7469.feature.rst delete mode 100644 changelog/7777.breaking.rst delete mode 100644 changelog/8976.breaking.rst delete mode 100644 changelog/9036.bugfix.rst delete mode 100644 changelog/9288.breaking.rst create mode 100644 doc/en/announce/release-8.0.0rc1.rst diff --git a/changelog/10441.feature.rst b/changelog/10441.feature.rst deleted file mode 100644 index 0019926ac..000000000 --- a/changelog/10441.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added :func:`ExceptionInfo.group_contains() `, an assertion -helper that tests if an `ExceptionGroup` contains a matching exception. diff --git a/changelog/10465.deprecation.rst b/changelog/10465.deprecation.rst deleted file mode 100644 index a715af5e6..000000000 --- a/changelog/10465.deprecation.rst +++ /dev/null @@ -1 +0,0 @@ -Test functions returning a value other than None will now issue a :class:`pytest.PytestWarning` instead of :class:`pytest.PytestRemovedIn8Warning`, meaning this will stay a warning instead of becoming an error in the future. diff --git a/changelog/10617.feature.rst b/changelog/10617.feature.rst deleted file mode 100644 index c99ec4889..000000000 --- a/changelog/10617.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with -the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``. diff --git a/changelog/10701.bugfix.rst b/changelog/10701.bugfix.rst deleted file mode 100644 index f33fa7fb2..000000000 --- a/changelog/10701.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -:meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list, -rather than the first warning which is an instance of the requested type. diff --git a/changelog/11011.doc.rst b/changelog/11011.doc.rst deleted file mode 100644 index 5faabba9c..000000000 --- a/changelog/11011.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Added a warning about modifying the root logger during tests when using ``caplog``. diff --git a/changelog/11065.doc.rst b/changelog/11065.doc.rst deleted file mode 100644 index 70a3db92c..000000000 --- a/changelog/11065.doc.rst +++ /dev/null @@ -1,3 +0,0 @@ -Use pytestconfig instead of request.config in cache example - -to be consistent with the API documentation. diff --git a/changelog/11122.improvement.rst b/changelog/11122.improvement.rst deleted file mode 100644 index dedaa7d08..000000000 --- a/changelog/11122.improvement.rst +++ /dev/null @@ -1,6 +0,0 @@ -``pluggy>=1.2.0`` is now required. - -pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0. -See `pluggy's 1.2.0 changelog `_ and the :ref:`updated docs ` for details. - -Plugins which want to use new-style wrappers can do so if they require this version of pytest or later. diff --git a/changelog/11137.breaking.rst b/changelog/11137.breaking.rst deleted file mode 100644 index a92df326a..000000000 --- a/changelog/11137.breaking.rst +++ /dev/null @@ -1,11 +0,0 @@ -:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`. - -The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file. -Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module), -the module being the `__init__.py` file. -This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details). - -The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file. - -Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist, -if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files). diff --git a/changelog/11151.breaking.rst b/changelog/11151.breaking.rst deleted file mode 100644 index 114a7d8e2..000000000 --- a/changelog/11151.breaking.rst +++ /dev/null @@ -1 +0,0 @@ -Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 `__. diff --git a/changelog/11208.trivial.rst b/changelog/11208.trivial.rst deleted file mode 100644 index fced57b20..000000000 --- a/changelog/11208.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -The (internal) ``FixtureDef.cached_result`` type has changed. -Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet. diff --git a/changelog/11216.improvement.rst b/changelog/11216.improvement.rst deleted file mode 100644 index 80761de5c..000000000 --- a/changelog/11216.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -If a test is skipped from inside an :ref:`xunit setup fixture `, the test summary now shows the test location instead of the fixture location. diff --git a/changelog/11218.trivial.rst b/changelog/11218.trivial.rst deleted file mode 100644 index 772054856..000000000 --- a/changelog/11218.trivial.rst +++ /dev/null @@ -1,5 +0,0 @@ -(This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.) - -:class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly. -A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions, -as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions. diff --git a/changelog/11227.improvement.rst b/changelog/11227.improvement.rst deleted file mode 100644 index 3c6748c3d..000000000 --- a/changelog/11227.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 ` ``__notes__``. diff --git a/changelog/11255.bugfix.rst b/changelog/11255.bugfix.rst deleted file mode 100644 index 2a2a42667..000000000 --- a/changelog/11255.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed crash on `parametrize(..., scope="package")` without a package present. diff --git a/changelog/11277.bugfix.rst b/changelog/11277.bugfix.rst deleted file mode 100644 index 43370561e..000000000 --- a/changelog/11277.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed a bug that when there are multiple fixtures for an indirect parameter, -the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope. diff --git a/changelog/11282.breaking.rst b/changelog/11282.breaking.rst deleted file mode 100644 index cee9788ef..000000000 --- a/changelog/11282.breaking.rst +++ /dev/null @@ -1,11 +0,0 @@ -Sanitized the handling of the ``default`` parameter when defining configuration options. - -Previously if ``default`` was not supplied for :meth:`parser.addini ` and the configuration option value was not defined in a test session, then calls to :func:`config.getini ` returned an *empty list* or an *empty string* depending on whether ``type`` was supplied or not respectively, which is clearly incorrect. Also, ``None`` was not honored even if ``default=None`` was used explicitly while defining the option. - -Now the behavior of :meth:`parser.addini ` is as follows: - -* If ``default`` is NOT passed but ``type`` is provided, then a type-specific default will be returned. For example ``type=bool`` will return ``False``, ``type=str`` will return ``""``, etc. -* If ``default=None`` is passed and the option is not defined in a test session, then ``None`` will be returned, regardless of the ``type``. -* If neither ``default`` nor ``type`` are provided, assume ``type=str`` and return ``""`` as default (this is as per previous behavior). - -The team decided to not introduce a deprecation period for this change, as doing so would be complicated both in terms of communicating this to the community as well as implementing it, and also because the team believes this change should not break existing plugins except in rare cases. diff --git a/changelog/11314.improvement.rst b/changelog/11314.improvement.rst deleted file mode 100644 index 272af21f5..000000000 --- a/changelog/11314.improvement.rst +++ /dev/null @@ -1,2 +0,0 @@ -Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback -if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively. diff --git a/changelog/11315.trivial.rst b/changelog/11315.trivial.rst deleted file mode 100644 index 309dccd8b..000000000 --- a/changelog/11315.trivial.rst +++ /dev/null @@ -1,3 +0,0 @@ -The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory. -If you use ``pytester`` in combination with :func:`monkeypatch.undo() `, the CWD might get restored. -Use :func:`monkeypatch.context() ` instead. diff --git a/changelog/11333.trivial.rst b/changelog/11333.trivial.rst deleted file mode 100644 index 846f79e34..000000000 --- a/changelog/11333.trivial.rst +++ /dev/null @@ -1,2 +0,0 @@ -Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``. -The previous spelling ``INCOVATION_DIR`` remains as an alias. diff --git a/changelog/11353.trivial.rst b/changelog/11353.trivial.rst deleted file mode 100644 index 10a6b4692..000000000 --- a/changelog/11353.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -pluggy>=1.3.0 is now required. This adds typing to :class:`~pytest.PytestPluginManager`. diff --git a/changelog/11387.feature.rst b/changelog/11387.feature.rst deleted file mode 100644 index 90f20885b..000000000 --- a/changelog/11387.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -Added the new :confval:`verbosity_assertions` configuration option for fine-grained control of failed assertions verbosity. - -See :ref:`Fine-grained verbosity ` for more details. - -For plugin authors, :attr:`config.get_verbosity ` can be used to retrieve the verbosity level for a specific verbosity type. diff --git a/changelog/11447.improvement.rst b/changelog/11447.improvement.rst deleted file mode 100644 index 96be8dffe..000000000 --- a/changelog/11447.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -:func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`. diff --git a/changelog/11456.bugfix.rst b/changelog/11456.bugfix.rst deleted file mode 100644 index 77a2ccfb0..000000000 --- a/changelog/11456.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Parametrized tests now *really do* ensure that the ids given to each input are unique - for -example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``. -This necessarily means changing nodeids where these were previously colliding, and for -readability adds an underscore when non-unique ids end in a number. diff --git a/changelog/11520.improvement.rst b/changelog/11520.improvement.rst deleted file mode 100644 index 548d52a12..000000000 --- a/changelog/11520.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -Improved very verbose diff output to color it as a diff instead of only red. - -Improved the error reporting to better separate each section. - -Improved the error reporting to syntax-highlight Python code when Pygments is available. diff --git a/changelog/11563.bugfix.rst b/changelog/11563.bugfix.rst deleted file mode 100644 index 35b5e4f15..000000000 --- a/changelog/11563.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed crash when using an empty string for the same parametrized value more than once. diff --git a/changelog/11600.improvement.rst b/changelog/11600.improvement.rst deleted file mode 100644 index 7082e2c1e..000000000 --- a/changelog/11600.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Improved the documentation and type signature for :func:`pytest.mark.xfail `'s ``condition`` param to use ``False`` as the default value. diff --git a/changelog/11610.feature.rst b/changelog/11610.feature.rst deleted file mode 100644 index 34df34705..000000000 --- a/changelog/11610.feature.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added :func:`LogCaptureFixture.filtering() ` context manager that -adds a given :class:`logging.Filter` object to the caplog fixture. diff --git a/changelog/11638.trivial.rst b/changelog/11638.trivial.rst deleted file mode 100644 index 374960b89..000000000 --- a/changelog/11638.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment. diff --git a/changelog/11667.breaking.rst b/changelog/11667.breaking.rst deleted file mode 100644 index 7c05d39b2..000000000 --- a/changelog/11667.breaking.rst +++ /dev/null @@ -1,3 +0,0 @@ -pytest's ``setup.py`` file is removed. -If you relied on this file, e.g. to install pytest using ``setup.py install``, -please see `Why you shouldn't invoke setup.py directly `_ for alternatives. diff --git a/changelog/11676.breaking.rst b/changelog/11676.breaking.rst deleted file mode 100644 index f20efa80d..000000000 --- a/changelog/11676.breaking.rst +++ /dev/null @@ -1,3 +0,0 @@ -The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`). - -We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic. diff --git a/changelog/11712.bugfix.rst b/changelog/11712.bugfix.rst deleted file mode 100644 index 416d76149..000000000 --- a/changelog/11712.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value. diff --git a/changelog/1531.improvement.rst b/changelog/1531.improvement.rst deleted file mode 100644 index d444ea2e7..000000000 --- a/changelog/1531.improvement.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improved the very verbose diff for every standard library container types: the indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users. - -Previously, the default python pretty printer was used to generate the output, which puts opening and closing -markers on the same line as the first/last entry, in addition to not having consistent indentation. diff --git a/changelog/3664.deprecation.rst b/changelog/3664.deprecation.rst deleted file mode 100644 index 0a00e26c1..000000000 --- a/changelog/3664.deprecation.rst +++ /dev/null @@ -1,3 +0,0 @@ -Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work. - -This will become an error in the future. diff --git a/changelog/7363.breaking.rst b/changelog/7363.breaking.rst deleted file mode 100644 index 93d87b1b1..000000000 --- a/changelog/7363.breaking.rst +++ /dev/null @@ -1,22 +0,0 @@ -**PytestRemovedIn8Warning deprecation warnings are now errors by default.** - -Following our plan to remove deprecated features with as little disruption as -possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors -instead of warning messages by default. - -**The affected features will be effectively removed in pytest 8.1**, so please consult the -:ref:`deprecations` section in the docs for directions on how to update existing code. - -In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a -stopgap measure by adding this to your ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - filterwarnings = - ignore::pytest.PytestRemovedIn8Warning - -But this will stop working when pytest ``8.1`` is released. - -**If you have concerns** about the removal of a specific feature, please add a -comment to :issue:`7363`. diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst deleted file mode 100644 index 8e9df7269..000000000 --- a/changelog/7469.feature.rst +++ /dev/null @@ -1 +0,0 @@ -:class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes. diff --git a/changelog/7777.breaking.rst b/changelog/7777.breaking.rst deleted file mode 100644 index d38fea330..000000000 --- a/changelog/7777.breaking.rst +++ /dev/null @@ -1,90 +0,0 @@ -Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. -This is analogous to the existing :class:`pytest.File` for file nodes. - -Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. -A ``Package`` represents a filesystem directory which is a Python package, -i.e. contains an ``__init__.py`` file. - -:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. -Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy. - -Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. -This node represents a filesystem directory, which is not a :class:`pytest.Package`, -i.e. does not contain an ``__init__.py`` file. -Similarly to ``Package``, it only collects the files in its own directory, -while collecting sub-directories as sub-collector nodes. - -Added a new hook :hook:`pytest_collect_directory`, -which is called by filesystem-traversing collector nodes, -such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`, -to create a collector node for a sub-directory. -It is expected to return a subclass of :class:`pytest.Directory`. -This hook allows plugins to :ref:`customize the collection of directories `. - -:class:`pytest.Session` now only collects the initial arguments, without recursing into directories. -This work is now done by the :func:`recursive expansion process ` of directory collector nodes. - -:attr:`session.name ` is now ``""``; previously it was the rootdir directory name. -This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. - -Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. -Previously, files were collected before directories. - -The collection tree now contains directories/packages up to the :ref:`rootdir `, -for initial arguments that are found within the rootdir. -For files outside the rootdir, only the immediate directory/package is collected -- -note however that collecting from outside the rootdir is discouraged. - -As an example, given the following filesystem tree:: - - myroot/ - pytest.ini - top/ - ├── aaa - │ └── test_aaa.py - ├── test_a.py - ├── test_b - │ ├── __init__.py - │ └── test_b.py - ├── test_c.py - └── zzz - ├── __init__.py - └── test_zzz.py - -the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, -is now the following:: - - - - - - - - - - - - - - - - - - -Previously, it was:: - - - - - - - - - - - - - - - -Code/plugins which rely on a specific shape of the collection tree might need to update. diff --git a/changelog/8976.breaking.rst b/changelog/8976.breaking.rst deleted file mode 100644 index bd9a63982..000000000 --- a/changelog/8976.breaking.rst +++ /dev/null @@ -1,5 +0,0 @@ -Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. -Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself -(unless :confval:`python_files` was changed to allow `__init__.py` file). - -To collect the entire package, specify just the directory: `pytest pkg`. diff --git a/changelog/9036.bugfix.rst b/changelog/9036.bugfix.rst deleted file mode 100644 index 4f25f82e2..000000000 --- a/changelog/9036.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block. diff --git a/changelog/9288.breaking.rst b/changelog/9288.breaking.rst deleted file mode 100644 index c344b83c7..000000000 --- a/changelog/9288.breaking.rst +++ /dev/null @@ -1,7 +0,0 @@ -:func:`~pytest.warns` now re-emits unmatched warnings when the context -closes -- previously it would consume all warnings, hiding those that were not -matched by the function. - -While this is a new feature, we decided to announce this as a breaking change -because many test suites are configured to error-out on warnings, and will -therefore fail on the newly-re-emitted warnings. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 35fd2c814..740767c01 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.0.0rc1 release-7.4.4 release-7.4.3 release-7.4.2 diff --git a/doc/en/announce/release-8.0.0rc1.rst b/doc/en/announce/release-8.0.0rc1.rst new file mode 100644 index 000000000..547c8cbc5 --- /dev/null +++ b/doc/en/announce/release-8.0.0rc1.rst @@ -0,0 +1,82 @@ +pytest-8.0.0rc1 +======================================= + +The pytest team is proud to announce the 8.0.0rc1 release! + +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Akhilesh Ramakrishnan +* Aleksandr Brodin +* Anthony Sottile +* Arthur Richard +* Avasam +* Benjamin Schubert +* Bruno Oliveira +* Carsten Grohmann +* Cheukting +* Chris Mahoney +* Christoph Anton Mitterer +* DetachHead +* Erik Hasse +* Florian Bruhin +* Fraser Stark +* Ha Pam +* Hugo van Kemenade +* Isaac Virshup +* Israel Fruchter +* Jens Tröger +* Jon Parise +* Kenny Y +* Lesnek +* Marc Mueller +* Michał Górny +* Mihail Milushev +* Milan Lesnek +* Miro Hrončok +* Patrick Lannigan +* Ran Benita +* Reagan Lee +* Ronny Pfannschmidt +* Sadra Barikbin +* Sean Malloy +* Sean Patrick Malloy +* Sharad Nair +* Simon Blanchard +* Sourabh Beniwal +* Stefaan Lippens +* Tanya Agarwal +* Thomas Grainger +* Tom Mortimer-Jones +* Tushar Sadhwani +* Tyler Smart +* Uday Kumar +* Warren Markham +* WarrenTheRabbit +* Zac Hatfield-Dodds +* Ziad Kermadi +* akhilramkee +* antosikv +* bowugit +* mickeypash +* neilmartin2000 +* pomponchik +* ryanpudd +* touilleWoman +* ubaumann + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 405289444..2acbce966 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -18,11 +18,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a $ pytest --fixtures -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collected 0 items - cache -- .../_pytest/cacheprovider.py:532 + cache -- .../_pytest/cacheprovider.py:526 Return a cache object that can persist state between testing sessions. cache.get(key, default) @@ -33,7 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Values can be any object handled by the json stdlib module. - capsysbinary -- .../_pytest/capture.py:1001 + capsysbinary -- .../_pytest/capture.py:1008 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` @@ -51,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capsysbinary.readouterr() assert captured.out == b"hello\n" - capfd -- .../_pytest/capture.py:1029 + capfd -- .../_pytest/capture.py:1036 Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -69,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfd.readouterr() assert captured.out == "hello\n" - capfdbinary -- .../_pytest/capture.py:1057 + capfdbinary -- .../_pytest/capture.py:1064 Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -87,7 +87,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfdbinary.readouterr() assert captured.out == b"hello\n" - capsys -- .../_pytest/capture.py:973 + capsys -- .../_pytest/capture.py:980 Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method @@ -105,7 +105,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capsys.readouterr() assert captured.out == "hello\n" - doctest_namespace [session scope] -- .../_pytest/doctest.py:757 + doctest_namespace [session scope] -- .../_pytest/doctest.py:743 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. @@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a For more details: :ref:`doctest_namespace`. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1353 + pytestconfig [session scope] -- .../_pytest/fixtures.py:1365 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -174,10 +174,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a `pytest-xdist `__ plugin. See :issue:`7767` for details. - tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302 + tmpdir_factory [session scope] -- .../_pytest/legacypath.py:300 Return a :class:`pytest.TempdirFactory` instance for the test session. - tmpdir -- .../_pytest/legacypath.py:309 + tmpdir -- .../_pytest/legacypath.py:307 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. @@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - caplog -- .../_pytest/logging.py:570 + caplog -- .../_pytest/logging.py:593 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -237,10 +237,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. - tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:245 + tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:239 Return a :class:`pytest.TempPathFactory` instance for the test session. - tmp_path -- .../_pytest/tmpdir.py:260 + tmp_path -- .../_pytest/tmpdir.py:254 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 4a3b9cdf6..85ba6140a 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,377 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.0.0rc1 (2023-12-30) +============================ + +Breaking Changes +---------------- + +Old Deprecations Are Now Errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#7363 `_: **PytestRemovedIn8Warning deprecation warnings are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors + instead of warning messages by default. + + **The affected features will be effectively removed in pytest 8.1**, so please consult the + :ref:`deprecations` section in the docs for directions on how to update existing code. + + In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a + stopgap measure by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.PytestRemovedIn8Warning + + But this will stop working when pytest ``8.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to :issue:`7363`. + + +Version Compatibility +^^^^^^^^^^^^^^^^^^^^^ + +- `#11151 `_: Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 `__. + + +- ``pluggy>=1.3.0`` is now required. + + +Collection Changes +^^^^^^^^^^^^^^^^^^ + +In this version we've made several breaking changes to pytest's collection phase, +particularly around how filesystem directories and Python packages are collected, +fixing deficiencies and allowing for cleanups and improvements to pytest's internals. +A deprecation period for these changes was not possible. + + +- `#7777 `_: Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. + Previously, files were collected before directories. + See below for an example. + + +- `#8976 `_: Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only. + Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself + (unless :confval:`python_files` was changed to allow `__init__.py` file). + + To collect the entire package, specify just the directory: `pytest pkg`. + + +- `#11137 `_: :class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`. + + The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file. + Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module), + the module being the `__init__.py` file. + This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details). + + The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file. + + Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist, + if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files). + + +- `#7777 `_: Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. + This is analogous to the existing :class:`pytest.File` for file nodes. + + Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. + A ``Package`` represents a filesystem directory which is a Python package, + i.e. contains an ``__init__.py`` file. + + :class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. + Sub-directories are collected as their own collector nodes, which then collect themselves, thus creating a collection tree which mirrors the filesystem hierarchy. + + Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. + This node represents a filesystem directory, which is not a :class:`pytest.Package`, + that is, does not contain an ``__init__.py`` file. + Similarly to ``Package``, it only collects the files in its own directory. + + :class:`pytest.Session` now only collects the initial arguments, without recursing into directories. + This work is now done by the :func:`recursive expansion process ` of directory collector nodes. + + :attr:`session.name ` is now ``""``; previously it was the rootdir directory name. + This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. + + The collection tree now contains directories/packages up to the :ref:`rootdir `, + for initial arguments that are found within the rootdir. + For files outside the rootdir, only the immediate directory/package is collected -- + note however that collecting from outside the rootdir is discouraged. + + As an example, given the following filesystem tree:: + + myroot/ + pytest.ini + top/ + ├── aaa + │ └── test_aaa.py + ├── test_a.py + ├── test_b + │ ├── __init__.py + │ └── test_b.py + ├── test_c.py + └── zzz + ├── __init__.py + └── test_zzz.py + + the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, + is now the following:: + + + + + + + + + + + + + + + + + + + Previously, it was:: + + + + + + + + + + + + + + + + Code/plugins which rely on a specific shape of the collection tree might need to update. + + +- `#11676 `_: The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`). + + We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic. + + +Other breaking changes +^^^^^^^^^^^^^^^^^^^^^^ + +These are breaking changes where deprecation was not possible. + + +- `#11282 `_: Sanitized the handling of the ``default`` parameter when defining configuration options. + + Previously if ``default`` was not supplied for :meth:`parser.addini ` and the configuration option value was not defined in a test session, then calls to :func:`config.getini ` returned an *empty list* or an *empty string* depending on whether ``type`` was supplied or not respectively, which is clearly incorrect. Also, ``None`` was not honored even if ``default=None`` was used explicitly while defining the option. + + Now the behavior of :meth:`parser.addini ` is as follows: + + * If ``default`` is NOT passed but ``type`` is provided, then a type-specific default will be returned. For example ``type=bool`` will return ``False``, ``type=str`` will return ``""``, etc. + * If ``default=None`` is passed and the option is not defined in a test session, then ``None`` will be returned, regardless of the ``type``. + * If neither ``default`` nor ``type`` are provided, assume ``type=str`` and return ``""`` as default (this is as per previous behavior). + + The team decided to not introduce a deprecation period for this change, as doing so would be complicated both in terms of communicating this to the community as well as implementing it, and also because the team believes this change should not break existing plugins except in rare cases. + + +- `#11667 `_: pytest's ``setup.py`` file is removed. + If you relied on this file, e.g. to install pytest using ``setup.py install``, + please see `Why you shouldn't invoke setup.py directly `_ for alternatives. + + +- `#9288 `_: :func:`~pytest.warns` now re-emits unmatched warnings when the context + closes -- previously it would consume all warnings, hiding those that were not + matched by the function. + + While this is a new feature, we announce it as a breaking change + because many test suites are configured to error-out on warnings, and will + therefore fail on the newly-re-emitted warnings. + + + +Deprecations +------------ + +- `#10465 `_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of :class:`pytest.PytestRemovedIn8Warning`, meaning this will stay a warning instead of becoming an error in the future. + + +- `#3664 `_: Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work. + + This will become an error in pytest 9.0. + + + +Features and Improvements +------------------------- + +Improved Diffs +^^^^^^^^^^^^^^ + +These changes improve the diffs that pytest prints when an assertion fails. +Note that syntax highlighting requires the ``pygments`` package. + + +- `#11520 `_: The very verbose (``-vv``) diff output is now colored as a diff instead of a big chunk of red. + + Python code in error reports is now syntax-highlighted as Python. + + The sections in the error reports are now better separated. + + +- `#1531 `_: The very verbose diff (``-vv``) for every standard library container type is improved. The indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users. + + Previously, the standard Python pretty printer was used to generate the output, which puts opening and closing + markers on the same line as the first/last entry, in addition to not having consistent indentation. + + +- `#10617 `_: Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with + the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``. + + +Separate Control For Assertion Verbosity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#11387 `_: Added the new :confval:`verbosity_assertions` configuration option for fine-grained control of failed assertions verbosity. + + If you've ever wished that pytest always show you full diffs, but without making everything else verbose, this is for you. + + See :ref:`Fine-grained verbosity ` for more details. + + For plugin authors, :attr:`config.get_verbosity ` can be used to retrieve the verbosity level for a specific verbosity type. + + +Additional Support For Exception Groups and ``__notes__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These changes improve pytest's support for exception groups. + + +- `#10441 `_: Added :func:`ExceptionInfo.group_contains() `, an assertion helper that tests if an :class:`ExceptionGroup` contains a matching exception. + + See :ref:`assert-matching-exception-groups` for an example. + + +- `#11227 `_: Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 ` ``__notes__``. + + +Custom Directory collectors +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#7777 `_: Added a new hook :hook:`pytest_collect_directory`, + which is called by filesystem-traversing collector nodes, + such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`, + to create a collector node for a sub-directory. + It is expected to return a subclass of :class:`pytest.Directory`. + This hook allows plugins to :ref:`customize the collection of directories `. + + +"New-style" Hook Wrappers +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- `#11122 `_: pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0. + See `pluggy's 1.2.0 changelog `_ and the :ref:`updated docs ` for details. + + Plugins which want to use new-style wrappers can do so if they require ``pytest>=8``. + + +Other Improvements +^^^^^^^^^^^^^^^^^^ + +- `#11216 `_: If a test is skipped from inside an :ref:`xunit setup fixture `, the test summary now shows the test location instead of the fixture location. + + +- `#11314 `_: Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback + if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively. + + +- `#11610 `_: Added the :func:`LogCaptureFixture.filtering() ` context manager which + adds a given :class:`logging.Filter` object to the :fixture:`caplog` fixture. + + +- `#11447 `_: :func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`. + + +- `#11600 `_: Improved the documentation and type signature for :func:`pytest.mark.xfail `'s ``condition`` param to use ``False`` as the default value. + + +- `#7469 `_: :class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes. + + +- `#11353 `_: Added typing to :class:`~pytest.PytestPluginManager`. + + +Bug Fixes +--------- + +- `#10701 `_: :meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list, + rather than the first warning which is an instance of the requested type. + + +- `#11255 `_: Fixed crash on `parametrize(..., scope="package")` without a package present. + + +- `#11277 `_: Fixed a bug that when there are multiple fixtures for an indirect parameter, + the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope. + + +- `#11456 `_: Parametrized tests now *really do* ensure that the ids given to each input are unique - for + example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``. + This necessarily means changing nodeids where these were previously colliding, and for + readability adds an underscore when non-unique ids end in a number. + + +- `#11563 `_: Fixed a crash when using an empty string for the same parametrized value more than once. + + +- `#11712 `_: Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value. + + +- `#9036 `_: ``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block. + + + +Improved Documentation +---------------------- + +- `#11011 `_: Added a warning about modifying the root logger during tests when using ``caplog``. + + +- `#11065 `_: Use ``pytestconfig`` instead of ``request.config`` in cache example to be consistent with the API documentation. + + +Trivial/Internal Changes +------------------------ + +- `#11208 `_: The (internal) ``FixtureDef.cached_result`` type has changed. + Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet. + + +- `#11218 `_: (This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.) + + :class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly. + A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions, + as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions. + + +- `#11315 `_: The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory. + If you use ``pytester`` in combination with :func:`monkeypatch.undo() `, the CWD might get restored. + Use :func:`monkeypatch.context() ` instead. + + +- `#11333 `_: Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``. + The previous spelling ``INCOVATION_DIR`` remains as an alias. + + +- `#11638 `_: Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment. + pytest 7.4.4 (2023-12-31) ========================= diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 6cdf4eb42..c04d2a078 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -45,7 +45,7 @@ You can then restrict a test run to only run tests marked with ``webtest``: $ pytest -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -60,7 +60,7 @@ Or the inverse, running all tests except the webtest ones: $ pytest -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -82,7 +82,7 @@ tests based on their module, class, method, or function name: $ pytest -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -97,7 +97,7 @@ You can also select on the class: $ pytest -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -112,7 +112,7 @@ Or select multiple nodes: $ pytest -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -156,7 +156,7 @@ The expression matching is now case-insensitive. $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 3 deselected / 1 selected @@ -171,7 +171,7 @@ And you can also run all tests except the ones that match the keyword: $ pytest -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 1 deselected / 3 selected @@ -188,7 +188,7 @@ Or to select "http" and "quick" tests: $ pytest -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 4 items / 2 deselected / 2 selected @@ -397,7 +397,7 @@ the test needs: $ pytest -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -411,7 +411,7 @@ and here is one that specifies exactly the environment needed: $ pytest -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -604,7 +604,7 @@ then you will see two tests skipped and two executed tests as expected: $ pytest -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -620,7 +620,7 @@ Note that if you specify a platform via the marker-command line option like this $ pytest -m linux =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 3 deselected / 1 selected @@ -683,7 +683,7 @@ We can now use the ``-m option`` to select one set: $ pytest -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 2 deselected / 2 selected @@ -709,7 +709,7 @@ or to select both "event" and "interface" tests: $ pytest -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items / 1 deselected / 3 selected diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index efb701b1f..aa463e241 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -28,7 +28,7 @@ now execute the test specification: nonpython $ pytest test_simple.yaml =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items @@ -64,7 +64,7 @@ consulted when reporting in ``verbose`` mode: nonpython $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project/nonpython collecting ... collected 2 items @@ -90,7 +90,7 @@ interesting to just look at the collection tree: nonpython $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/nonpython collected 2 items diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 8c129ddfe..0426266e5 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -158,19 +158,20 @@ objects, they are still using the default pytest representation: $ pytest test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 8 items - - - - - - - - - + + + + + + + + + + ======================== 8 tests collected in 0.12s ======================== @@ -220,7 +221,7 @@ this is a fully self-contained example which you can run with: $ pytest test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -234,16 +235,17 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items - - - - - - + + + + + + + ======================== 4 tests collected in 0.12s ======================== @@ -312,13 +314,14 @@ Let's first see how it looks like at collection time: $ pytest test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items - - - + + + + ======================== 2 tests collected in 0.12s ======================== @@ -410,7 +413,7 @@ The result of this test will be successful: $ pytest -v test_indirect_list.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 1 item @@ -500,12 +503,11 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - sssssssssssssssssssssssssss [100%] + ssssssssssss...ssssssssssss [100%] ========================= short test summary info ========================== - SKIPPED [9] multipython.py:69: 'python3.5' not found - SKIPPED [9] multipython.py:69: 'python3.6' not found - SKIPPED [9] multipython.py:69: 'python3.7' not found - 27 skipped in 0.12s + SKIPPED [12] multipython.py:68: 'python3.9' not found + SKIPPED [12] multipython.py:68: 'python3.11' not found + 3 passed, 24 skipped in 0.12s Parametrization of optional implementations/imports --------------------------------------------------- @@ -565,7 +567,7 @@ If you run this with reporting for skips enabled: $ pytest -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -626,7 +628,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: $ pytest -v -m basic =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 24 items / 21 deselected / 3 selected diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 2451e3cab..dbc2c239f 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -147,15 +147,16 @@ The test collection would look like this: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project configfile: pytest.ini collected 2 items - - - - + + + + + ======================== 2 tests collected in 0.12s ======================== @@ -209,16 +210,18 @@ You can always peek at the collection tree without running tests like this: . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project configfile: pytest.ini collected 3 items - - - - - + + + + + + + ======================== 3 tests collected in 0.12s ======================== @@ -291,7 +294,7 @@ file will be left out: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project configfile: pytest.ini collected 0 items diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index cb59c4b42..2e8d4824c 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -9,7 +9,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: assertion $ pytest failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project/assertion collected 44 items @@ -80,6 +80,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_text(self): > assert "spam" == "eggs" E AssertionError: assert 'spam' == 'eggs' + E E - eggs E + spam @@ -91,6 +92,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_similar_text(self): > assert "foo 1 bar" == "foo 2 bar" E AssertionError: assert 'foo 1 bar' == 'foo 2 bar' + E E - foo 2 bar E ? ^ E + foo 1 bar @@ -104,6 +106,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_multiline_text(self): > assert "foo\nspam\nbar" == "foo\neggs\nbar" E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar' + E E foo E - eggs E + spam @@ -119,6 +122,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: b = "1" * 100 + "b" + "2" * 100 > assert a == b E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222' + E E Skipping 90 identical leading characters in diff, use -v to show E Skipping 91 identical trailing characters in diff, use -v to show E - 1111111111b222222222 @@ -136,15 +140,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: b = "1\n" * 100 + "b" + "2\n" * 100 > assert a == b E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n' + E E Skipping 190 identical leading characters in diff, use -v to show E Skipping 191 identical trailing characters in diff, use -v to show E 1 E 1 E 1 - E 1 E 1... E - E ...Full output truncated (6 lines hidden), use '-vv' to show + E ...Full output truncated (7 lines hidden), use '-vv' to show failure_demo.py:60: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ @@ -154,6 +158,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] E assert [0, 1, 2] == [0, 1, 3] + E E At index 2 diff: 2 != 3 E Use -v to get more diff @@ -167,6 +172,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: b = [0] * 100 + [2] + [3] * 100 > assert a == b E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...] + E E At index 100 diff: 1 != 2 E Use -v to get more diff @@ -178,6 +184,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_dict(self): > assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0} E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} + E E Omitting 1 identical items, use -vv to show E Differing items: E {'b': 1} != {'b': 2} @@ -195,6 +202,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_set(self): > assert {0, 10, 11, 12} == {0, 20, 21} E assert {0, 10, 11, 12} == {0, 20, 21} + E E Extra items in the left set: E 10 E 11 @@ -212,6 +220,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_longer_list(self): > assert [1, 2] == [1, 2, 3] E assert [1, 2] == [1, 2, 3] + E E Right contains one more item: 3 E Use -v to get more diff @@ -233,6 +242,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail" > assert "foo" not in text E AssertionError: assert 'foo' not in 'some multil...nand a\ntail' + E E 'foo' is contained here: E some multiline E text @@ -251,6 +261,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "single foo line" > assert "foo" not in text E AssertionError: assert 'foo' not in 'single foo line' + E E 'foo' is contained here: E single foo line E ? +++ @@ -264,6 +275,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "head " * 50 + "foo " + "tail " * 20 > assert "foo" not in text E AssertionError: assert 'foo' not in 'head head h...l tail tail ' + E E 'foo' is contained here: E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? +++ @@ -277,6 +289,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: text = "head " * 50 + "f" * 70 + "tail " * 20 > assert "f" * 70 not in text E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail ' + E E 'ffffffffffffffffff...fffffffffffffffffff' is contained here: E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 943342ff1..21e5f4a09 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -232,7 +232,7 @@ directory with the above conftest.py: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -296,7 +296,7 @@ and when running it will see a skipped "slow" test: $ pytest -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -312,7 +312,7 @@ Or run it including the ``slow`` marked test: $ pytest --runslow =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -456,7 +456,7 @@ which will add the string to the test header accordingly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y project deps: mylib-1.1 rootdir: /home/sweet/project collected 0 items @@ -484,7 +484,7 @@ which will add info only when run with "--v": $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache info1: did you know that ... did you? @@ -499,7 +499,7 @@ and nothing when run plainly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 0 items @@ -538,7 +538,7 @@ Now we can profile which test functions execute the slowest: $ pytest --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -644,7 +644,7 @@ If we run this: $ pytest -rx =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 4 items @@ -726,14 +726,14 @@ We can run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 7 items - test_step.py .Fx. [ 57%] - a/test_db.py F [ 71%] - a/test_db2.py F [ 85%] - b/test_error.py E [100%] + a/test_db.py F [ 14%] + a/test_db2.py F [ 28%] + b/test_error.py E [ 42%] + test_step.py .Fx. [100%] ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ @@ -745,39 +745,39 @@ We can run this: /home/sweet/project/b/test_error.py:1 ================================= FAILURES ================================= + _________________________________ test_a1 __________________________________ + + db = + + def test_a1(db): + > assert 0, db # to show value + E AssertionError: + E assert 0 + + a/test_db.py:2: AssertionError + _________________________________ test_a2 __________________________________ + + db = + + def test_a2(db): + > assert 0, db # to show value + E AssertionError: + E assert 0 + + a/test_db2.py:2: AssertionError ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 E assert 0 test_step.py:11: AssertionError - _________________________________ test_a1 __________________________________ - - db = - - def test_a1(db): - > assert 0, db # to show value - E AssertionError: - E assert 0 - - a/test_db.py:2: AssertionError - _________________________________ test_a2 __________________________________ - - db = - - def test_a2(db): - > assert 0, db # to show value - E AssertionError: - E assert 0 - - a/test_db2.py:2: AssertionError ========================= short test summary info ========================== - FAILED test_step.py::TestUserHandling::test_modification - assert 0 FAILED a/test_db.py::test_a1 - AssertionError: ` helper to assert that some code raises an e f() You can also use the context provided by :ref:`raises ` to -assert that an expected exception is part of a raised ``ExceptionGroup``: +assert that an expected exception is part of a raised :class:`ExceptionGroup`: .. code-block:: python diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst index 7d5076a50..5c7d125fe 100644 --- a/doc/en/how-to/assert.rst +++ b/doc/en/how-to/assert.rst @@ -29,7 +29,7 @@ you will see the return value of the function call: $ pytest test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -143,11 +143,13 @@ Notes: * The ``match`` parameter also matches against `PEP-678 `__ ``__notes__``. +.. _`assert-matching-exception-groups`: + Matching exception groups ~~~~~~~~~~~~~~~~~~~~~~~~~ You can also use the :func:`excinfo.group_contains() ` -method to test for exceptions returned as part of an ``ExceptionGroup``: +method to test for exceptions returned as part of an :class:`ExceptionGroup`: .. code-block:: python @@ -278,7 +280,7 @@ if you run this module: $ pytest test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -292,6 +294,7 @@ if you run this module: set2 = set("8035") > assert set1 == set2 E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} + E E Extra items in the left set: E '1' E Extra items in the right set: diff --git a/doc/en/how-to/cache.rst b/doc/en/how-to/cache.rst index 1b2a454cc..40cd3f00d 100644 --- a/doc/en/how-to/cache.rst +++ b/doc/en/how-to/cache.rst @@ -86,7 +86,7 @@ If you then run it with ``--lf``: $ pytest --lf =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items run-last-failure: rerun previous 2 failures @@ -132,7 +132,7 @@ of ``FF`` and dots): $ pytest --ff =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 50 items run-last-failure: rerun previous 2 failures first @@ -281,7 +281,7 @@ You can always peek at the content of the cache using the $ pytest --cache-show =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache --------------------------- cache values for '*' --------------------------- @@ -303,7 +303,7 @@ filtering: $ pytest --cache-show example/* =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project cachedir: /home/sweet/project/.pytest_cache ----------------------- cache values for 'example/*' ----------------------- diff --git a/doc/en/how-to/capture-stdout-stderr.rst b/doc/en/how-to/capture-stdout-stderr.rst index 9ccea719b..5e23f0c02 100644 --- a/doc/en/how-to/capture-stdout-stderr.rst +++ b/doc/en/how-to/capture-stdout-stderr.rst @@ -83,7 +83,7 @@ of the failing function and hide the other one: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index ba6730587..afabad5da 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -28,7 +28,7 @@ Running pytest now produces this output: $ pytest test_show_warnings.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item diff --git a/doc/en/how-to/doctest.rst b/doc/en/how-to/doctest.rst index 021ba274f..f70d28ce1 100644 --- a/doc/en/how-to/doctest.rst +++ b/doc/en/how-to/doctest.rst @@ -30,7 +30,7 @@ then you can just invoke ``pytest`` directly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -58,7 +58,7 @@ and functions, including from test modules: $ pytest --doctest-modules =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 35b06c519..a8fea574a 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -433,7 +433,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items @@ -771,7 +771,7 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur $ pytest -s test_finalizers.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -805,7 +805,7 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`. $ pytest -s test_finalizers.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item @@ -1414,27 +1414,28 @@ Running the above tests results in the following test IDs being used: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 12 items - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + ======================= 12 tests collected in 0.12s ======================== @@ -1468,7 +1469,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``: $ pytest test_fixture_marks.py -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 3 items @@ -1518,7 +1519,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 2 items @@ -1598,7 +1599,7 @@ Let's run the tests in verbose mode and with looking at the print-output: $ pytest -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache rootdir: /home/sweet/project collecting ... collected 8 items diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 8af9a38b7..95c3a89b5 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -100,6 +100,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' E Use -v to get more diff @@ -111,6 +112,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...} + E E Omitting 1 identical items, use -vv to show E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} @@ -162,12 +164,15 @@ Now we can increase pytest's verbosity: fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' + E E Full diff: - E - ['banana', 'apple', 'orange', 'melon', 'kiwi'] - E ? ^ ^^ - E + ['banana', 'apple', 'grapes', 'melon', 'kiwi'] - E ? ^ ^ + + E [ + E 'banana', + E 'apple',... + E + E ...Full output truncated (7 lines hidden), use '-vv' to show test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -177,15 +182,15 @@ Now we can increase pytest's verbosity: number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...} + E E Omitting 1 identical items, use -vv to show E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} - E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} - E ? - - - - - - - - - E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} + E ... + E + E ...Full output truncated (16 lines hidden), use '-vv' to show test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -231,12 +236,20 @@ Now if we increase verbosity even more: fruits2 = ["banana", "apple", "orange", "melon", "kiwi"] > assert fruits1 == fruits2 E AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi'] + E E At index 2 diff: 'grapes' != 'orange' + E E Full diff: - E - ['banana', 'apple', 'orange', 'melon', 'kiwi'] - E ? ^ ^^ - E + ['banana', 'apple', 'grapes', 'melon', 'kiwi'] - E ? ^ ^ + + E [ + E 'banana', + E 'apple', + E - 'orange', + E ? ^ ^^ + E + 'grapes', + E ? ^ ^ + + E 'melon', + E 'kiwi', + E ] test_verbosity_example.py:8: AssertionError ____________________________ test_numbers_fail _____________________________ @@ -246,16 +259,30 @@ Now if we increase verbosity even more: number_to_text2 = {str(x * 10): x * 10 for x in range(5)} > assert number_to_text1 == number_to_text2 E AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + E E Common items: E {'0': 0} E Left contains 4 more items: E {'1': 1, '2': 2, '3': 3, '4': 4} E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} + E E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} - E ? - - - - - - - - - E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} + E { + E '0': 0, + E - '10': 10, + E ? - - + E + '1': 1, + E - '20': 20, + E ? - - + E + '2': 2, + E - '30': 30, + E ? - - + E + '3': 3, + E - '40': 40, + E ? - - + E + '4': 4, + E } test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -354,7 +381,7 @@ Example: $ pytest -ra =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -410,7 +437,7 @@ More than one character can be used, so for example to only see failed and skipp $ pytest -rfs =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items @@ -445,7 +472,7 @@ captured output: $ pytest -rpP =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 6 items diff --git a/doc/en/how-to/parametrize.rst b/doc/en/how-to/parametrize.rst index a0c996842..b6466c491 100644 --- a/doc/en/how-to/parametrize.rst +++ b/doc/en/how-to/parametrize.rst @@ -56,7 +56,7 @@ them in turn: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items @@ -167,7 +167,7 @@ Let's run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 3 items diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index 3b49d63a5..b75fb5964 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -36,7 +36,7 @@ Running this would result in a passed test except for the last $ pytest test_tmp_path.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst index 7856c1a49..508aebde0 100644 --- a/doc/en/how-to/unittest.rst +++ b/doc/en/how-to/unittest.rst @@ -140,7 +140,7 @@ the ``self.db`` values in the traceback: $ pytest test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 2 items diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index 6f3211107..d907ae398 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -448,7 +448,7 @@ in our ``pytest.ini`` to tell pytest where to look for example files. $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project configfile: pytest.ini collected 2 items diff --git a/doc/en/index.rst b/doc/en/index.rst index b9331eb9a..50c84f6ae 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -42,7 +42,7 @@ To execute it: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y rootdir: /home/sweet/project collected 1 item diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 49459dd85..33aff0f7c 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -2141,6 +2141,10 @@ All the command-line flags can be obtained by running ``pytest --help``:: enable_assertion_pass_hook (bool): Enables the pytest_assertion_pass hook. Make sure to delete any previously generated pyc cache files. + verbosity_assertions (string): + Specify a verbosity level for assertions, overriding + the main level. Higher levels will provide more + detailed explanation when an assertion fails. junit_suite_name (string): Test suite name for JUnit report junit_logging (string): From f13724e2e3e7b66b09cca4608a57e68f7d303b6d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:10:56 +0200 Subject: [PATCH 041/104] Remove deprecated {FSCollector,Package}.{gethookproxy,isinitpath} --- src/_pytest/deprecated.py | 5 ----- src/_pytest/nodes.py | 9 --------- testing/deprecated_test.py | 26 -------------------------- 3 files changed, 40 deletions(-) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 77279d634..4750bec5d 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -51,11 +51,6 @@ WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( "Please use pytest_load_initial_conftests hook instead." ) -FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning( - "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; " - "use self.session.gethookproxy() and self.session.isinitpath() instead. " -) - STRICT_OPTION = PytestRemovedIn8Warning( "The --strict option is deprecated, use --strict-markers instead." ) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 530714108..eefe690de 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -32,7 +32,6 @@ from _pytest.compat import LEGACY_PATH from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.config.compat import _check_path -from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import NODE_CTOR_FSPATH_ARG from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator @@ -660,14 +659,6 @@ class FSCollector(Collector, abc.ABC): """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) - def gethookproxy(self, fspath: "os.PathLike[str]"): - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.gethookproxy(fspath) - - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.isinitpath(path) - class File(FSCollector, abc.ABC): """Base class for collecting tests from a file. diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 0736ed1dc..c1d8ad59d 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,6 +1,5 @@ import re import sys -import warnings from pathlib import Path import pytest @@ -68,31 +67,6 @@ def test_hookimpl_via_function_attributes_are_deprecated(): assert record.filename == __file__ -def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None: - module = pytester.getmodulecol( - """ - def test_foo(): pass - """, - withinit=True, - ) - assert isinstance(module, pytest.Module) - package = module.parent - assert isinstance(package, pytest.Package) - - with pytest.warns(pytest.PytestDeprecationWarning, match="gethookproxy"): - package.gethookproxy(pytester.path) - - with pytest.warns(pytest.PytestDeprecationWarning, match="isinitpath"): - package.isinitpath(pytester.path) - - # The methods on Session are *not* deprecated. - session = module.session - with warnings.catch_warnings(record=True) as rec: - session.gethookproxy(pytester.path) - session.isinitpath(pytester.path) - assert len(rec) == 0 - - def test_strict_option_is_deprecated(pytester: Pytester) -> None: """--strict is a deprecated alias to --strict-markers (#7530).""" pytester.makepyfile( From f4e7b0d6e00aff21d1c4aadfb51fb0f1f4c4bd94 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:14:06 +0200 Subject: [PATCH 042/104] Remove deprecated `pytest_cmdline_preparse` hook --- doc/en/changelog.rst | 2 +- doc/en/deprecations.rst | 51 +++++++++++++++++----------------- doc/en/reference/reference.rst | 2 -- src/_pytest/config/__init__.py | 2 -- src/_pytest/deprecated.py | 5 ---- src/_pytest/hookspec.py | 17 ------------ testing/deprecated_test.py | 17 ------------ testing/test_config.py | 11 -------- 8 files changed, 27 insertions(+), 80 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 85ba6140a..c097202ef 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -1257,7 +1257,7 @@ Deprecations See :ref:`the deprecation note ` for full details. -- `#8592 `_: :hook:`pytest_cmdline_preparse` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead. +- `#8592 `_: ``pytest_cmdline_preparse`` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead. See :ref:`the deprecation note ` for full details. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index caa7cb3e7..c8189b1bb 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -273,8 +273,6 @@ Directly constructing the following classes is now deprecated: These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. -.. _cmdline-preparse-deprecated: - Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -306,29 +304,6 @@ functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which # new pytest.exit(reason="bar") - -Implementing the ``pytest_cmdline_preparse`` hook -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated. -Implement the :hook:`pytest_load_initial_conftests` hook instead. - -.. code-block:: python - - def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: - ... - - - # becomes: - - - def pytest_load_initial_conftests( - early_config: Config, parser: Parser, args: List[str] - ) -> None: - ... - .. _diamond-inheritance-deprecated: Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item` @@ -495,6 +470,32 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +.. _cmdline-preparse-deprecated: + +Implementing the ``pytest_cmdline_preparse`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 +.. versionremoved:: 8.0 + +Implementing the ``pytest_cmdline_preparse`` hook has been officially deprecated. +Implement the :hook:`pytest_load_initial_conftests` hook instead. + +.. code-block:: python + + def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: + ... + + + # becomes: + + + def pytest_load_initial_conftests( + early_config: Config, parser: Parser, args: List[str] + ) -> None: + ... + + Collection changes in pytest 8 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 33aff0f7c..bd64bac41 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -643,8 +643,6 @@ Bootstrapping hooks called for plugins registered early enough (internal and set .. hook:: pytest_load_initial_conftests .. autofunction:: pytest_load_initial_conftests -.. hook:: pytest_cmdline_preparse -.. autofunction:: pytest_cmdline_preparse .. hook:: pytest_cmdline_parse .. autofunction:: pytest_cmdline_parse .. hook:: pytest_cmdline_main diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index e5775546d..9f25b67ab 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1432,8 +1432,6 @@ class Config: kwargs=dict(pluginmanager=self.pluginmanager) ) self._preparse(args, addopts=addopts) - # XXX deprecated hook: - self.hook.pytest_cmdline_preparse(config=self, args=args) self._parser.after_preparse = True # type: ignore try: args = self._parser.parse_setoption( diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 4750bec5d..b7beb3531 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -46,11 +46,6 @@ YIELD_FIXTURE = PytestDeprecationWarning( "Use @pytest.fixture instead; they are the same." ) -WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning( - "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n" - "Please use pytest_load_initial_conftests hook instead." -) - STRICT_OPTION = PytestRemovedIn8Warning( "The --strict option is deprecated, use --strict-markers instead." ) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 3c65234da..14f7f45fa 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -13,8 +13,6 @@ from typing import Union from pluggy import HookspecMarker -from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK - if TYPE_CHECKING: import pdb import warnings @@ -159,21 +157,6 @@ def pytest_cmdline_parse( """ -@hookspec(warn_on_impl=WARNING_CMDLINE_PREPARSE_HOOK) -def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: - """(**Deprecated**) modify command line arguments before option parsing. - - This hook is considered deprecated and will be removed in a future pytest version. Consider - using :hook:`pytest_load_initial_conftests` instead. - - .. note:: - This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - - :param config: The pytest config object. - :param args: Arguments passed on the command line. - """ - - @hookspec(firstresult=True) def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: """Called for performing the main command line action. The default diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index c1d8ad59d..042be8264 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -211,23 +211,6 @@ class TestSkipMsgArgumentDeprecated: result.assert_outcomes(warnings=1) -def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None: - pytester.makeconftest( - """ - def pytest_cmdline_preparse(config, args): - ... - - """ - ) - result = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*", - "*Please use pytest_load_initial_conftests hook instead.*", - ] - ) - - def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: mod = pytester.getmodulecol("") diff --git a/testing/test_config.py b/testing/test_config.py index 18022977c..2d95fb4cc 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1253,17 +1253,6 @@ def test_plugin_loading_order(pytester: Pytester) -> None: assert result.ret == 0 -def test_cmdline_processargs_simple(pytester: Pytester) -> None: - pytester.makeconftest( - """ - def pytest_cmdline_preparse(args): - args.append("-h") - """ - ) - result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines(["*pytest*", "*-h*"]) - - def test_invalid_options_show_extra_information(pytester: Pytester) -> None: """Display extra information when pytest exits due to unrecognized options in the command-line.""" From 1f8b39ed328a395c073db603bf4063fb3ec97218 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:17:58 +0200 Subject: [PATCH 043/104] Remove deprecated `--strict` option --- doc/en/deprecations.rst | 27 ++++++++++++++------------- src/_pytest/config/__init__.py | 5 ----- src/_pytest/deprecated.py | 4 ---- testing/deprecated_test.py | 19 ------------------- 4 files changed, 14 insertions(+), 41 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index c8189b1bb..7562f98eb 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -438,19 +438,6 @@ The proper fix is to change the `return` to an `assert`: assert foo(a, b) == result -The ``--strict`` command-line option -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 6.2 - -The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which -better conveys what the option does. - -We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing -flag for all strictness related options (``--strict-markers`` and ``--strict-config`` -at the moment, more might be introduced in the future). - - The ``yield_fixture`` function/decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -470,6 +457,20 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +The ``--strict`` command-line option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.2 +.. versionremoved:: 8.0 + +The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which +better conveys what the option does. + +We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing +flag for all strictness related options (``--strict-markers`` and ``--strict-config`` +at the moment, more might be introduced in the future). + + .. _cmdline-preparse-deprecated: Implementing the ``pytest_cmdline_preparse`` hook diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 9f25b67ab..2d5db1724 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1319,11 +1319,6 @@ class Config: self._validate_plugins() self._warn_about_skipped_plugins() - if self.known_args_namespace.strict: - self.issue_config_time_warning( - _pytest.deprecated.STRICT_OPTION, stacklevel=2 - ) - if self.known_args_namespace.confcutdir is None: if self.inipath is not None: confcutdir = str(self.inipath.parent) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index b7beb3531..1ead61e8c 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -46,10 +46,6 @@ YIELD_FIXTURE = PytestDeprecationWarning( "Use @pytest.fixture instead; they are the same." ) -STRICT_OPTION = PytestRemovedIn8Warning( - "The --strict option is deprecated, use --strict-markers instead." -) - # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 042be8264..dd6a6dfcf 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -67,25 +67,6 @@ def test_hookimpl_via_function_attributes_are_deprecated(): assert record.filename == __file__ -def test_strict_option_is_deprecated(pytester: Pytester) -> None: - """--strict is a deprecated alias to --strict-markers (#7530).""" - pytester.makepyfile( - """ - import pytest - - @pytest.mark.unknown - def test_foo(): pass - """ - ) - result = pytester.runpytest("--strict", "-Wdefault::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines( - [ - "'unknown' not found in `markers` configuration option", - "*PytestRemovedIn8Warning: The --strict option is deprecated, use --strict-markers instead.", - ] - ) - - def test_yield_fixture_is_deprecated() -> None: with pytest.warns(DeprecationWarning, match=r"yield_fixture is deprecated"): From 10fbb2325f66fb1834c02b04dd07089cca13985f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:22:02 +0200 Subject: [PATCH 044/104] Remove deprecated `Parser.addoption` backward compatibilities --- doc/en/deprecations.rst | 25 ++++++++++++----------- src/_pytest/config/argparsing.py | 35 +------------------------------- src/_pytest/deprecated.py | 19 ----------------- testing/test_parseopt.py | 3 --- 4 files changed, 14 insertions(+), 68 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 7562f98eb..982470cb2 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -374,18 +374,6 @@ Users expected in this case that the ``usefixtures`` mark would have its intende Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions. -Backward compatibilities in ``Parser.addoption`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 2.4 - -Several behaviors of :meth:`Parser.addoption ` are now -scheduled for removal in pytest 8 (deprecated since pytest 2.4.0): - -- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. -- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. - - Using ``pytest.warns(None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -457,6 +445,19 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +Backward compatibilities in ``Parser.addoption`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 2.4 +.. versionremoved:: 8.0 + +Several behaviors of :meth:`Parser.addoption ` are now +removed in pytest 8 (deprecated since pytest 2.4.0): + +- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead. +- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead. + + The ``--strict`` command-line option ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 331abb85d..39e417605 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,7 +1,6 @@ import argparse import os import sys -import warnings from gettext import gettext from typing import Any from typing import Callable @@ -19,9 +18,6 @@ from typing import Union import _pytest._io from _pytest.config.exceptions import UsageError -from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT -from _pytest.deprecated import ARGUMENT_TYPE_STR -from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE from _pytest.deprecated import check_ispytest FILE_OR_DIR = "file_or_dir" @@ -259,39 +255,15 @@ class Argument: https://docs.python.org/3/library/optparse.html#optparse-standard-option-types """ - _typ_map = {"int": int, "string": str, "float": float, "complex": complex} - def __init__(self, *names: str, **attrs: Any) -> None: """Store params in private vars for use in add_argument.""" self._attrs = attrs self._short_opts: List[str] = [] self._long_opts: List[str] = [] - if "%default" in (attrs.get("help") or ""): - warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3) try: - typ = attrs["type"] + self.type = attrs["type"] except KeyError: pass - else: - # This might raise a keyerror as well, don't want to catch that. - if isinstance(typ, str): - if typ == "choice": - warnings.warn( - ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names), - stacklevel=4, - ) - # argparse expects a type here take it from - # the type of the first element - attrs["type"] = type(attrs["choices"][0]) - else: - warnings.warn( - ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4 - ) - attrs["type"] = Argument._typ_map[typ] - # Used in test_parseopt -> test_parse_defaultgetter. - self.type = attrs["type"] - else: - self.type = typ try: # Attribute existence is tested in Config._processopt. self.default = attrs["default"] @@ -322,11 +294,6 @@ class Argument: self._attrs[attr] = getattr(self, attr) except AttributeError: pass - if self._attrs.get("help"): - a = self._attrs["help"] - a = a.replace("%default", "%(default)s") - # a = a.replace('%prog', '%(prog)s') - self._attrs["help"] = a return self._attrs def _set_opt_strings(self, opts: Sequence[str]) -> None: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 1ead61e8c..a41fb378f 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -49,25 +49,6 @@ YIELD_FIXTURE = PytestDeprecationWarning( # This deprecation is never really meant to be removed. PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning( - 'pytest now uses argparse. "%default" should be changed to "%(default)s"', -) - -ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}." - " For choices this is optional and can be omitted, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - -ARGUMENT_TYPE_STR = UnformattedWarning( - PytestRemovedIn8Warning, - "`type` argument to addoption() is the string {typ!r}, " - " but when supplied should be a type (for example `str` or `int`)." - " (options: {names})", -) - HOOK_LEGACY_PATH_ARG = UnformattedWarning( PytestRemovedIn8Warning, diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 1b80883ee..2a6291984 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -54,9 +54,6 @@ class TestParser: assert argument.type is str argument = parseopt.Argument("-t", dest="abc", type=float) assert argument.type is float - with pytest.warns(DeprecationWarning): - with pytest.raises(KeyError): - argument = parseopt.Argument("-t", dest="abc", type="choice") argument = parseopt.Argument( "-t", dest="abc", type=str, choices=["red", "blue"] ) From 4147c92b21c0fc393a59e42cab2b2511fcb60e06 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:32:01 +0200 Subject: [PATCH 045/104] Remove deprecated `pytest.warns(None)` --- doc/en/deprecations.rst | 25 +++++++++++++------------ src/_pytest/deprecated.py | 7 ------- src/_pytest/recwarn.py | 18 +++++------------- testing/deprecated_test.py | 14 -------------- testing/test_recwarn.py | 14 +++----------- testing/test_tmpdir.py | 12 +++++------- 6 files changed, 26 insertions(+), 64 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 982470cb2..70f8b09aa 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -374,18 +374,6 @@ Users expected in this case that the ``usefixtures`` mark would have its intende Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions. -Using ``pytest.warns(None)`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -:func:`pytest.warns(None) ` is now deprecated because it was frequently misused. -Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()`` -or ``pytest.warns(Warning)``. - -See :ref:`warns use cases` for examples. - - Returning non-None value in test functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -445,6 +433,19 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +Using ``pytest.warns(None)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 +.. versionremoved:: 8.0 + +:func:`pytest.warns(None) ` is now deprecated because it was frequently misused. +Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()`` +or ``pytest.warns(Warning)``. + +See :ref:`warns use cases` for examples. + + Backward compatibilities in ``Parser.addoption`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index a41fb378f..4ea2c39a2 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -65,13 +65,6 @@ NODE_CTOR_FSPATH_ARG = UnformattedWarning( "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", ) -WARNS_NONE_ARG = PytestRemovedIn8Warning( - "Passing None has been deprecated.\n" - "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" - "#additional-use-cases-of-warnings-in-tests" - " for alternatives in common use cases." -) - KEYWORD_MSG_ARG = UnformattedWarning( PytestRemovedIn8Warning, "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index d1d83ea2a..175d545b9 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -18,7 +18,6 @@ from typing import TypeVar from typing import Union from _pytest.deprecated import check_ispytest -from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture from _pytest.outcomes import fail @@ -264,9 +263,7 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg] class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Optional[ - Union[Type[Warning], Tuple[Type[Warning], ...]] - ] = Warning, + expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, match_expr: Optional[Union[str, Pattern[str]]] = None, *, _ispytest: bool = False, @@ -275,15 +272,14 @@ class WarningsChecker(WarningsRecorder): super().__init__(_ispytest=True) msg = "exceptions must be derived from Warning, not %s" - if expected_warning is None: - warnings.warn(WARNS_NONE_ARG, stacklevel=4) - expected_warning_tup = None - elif isinstance(expected_warning, tuple): + if isinstance(expected_warning, tuple): for exc in expected_warning: if not issubclass(exc, Warning): raise TypeError(msg % type(exc)) expected_warning_tup = expected_warning - elif issubclass(expected_warning, Warning): + elif isinstance(expected_warning, type) and issubclass( + expected_warning, Warning + ): expected_warning_tup = (expected_warning,) else: raise TypeError(msg % type(expected_warning)) @@ -307,10 +303,6 @@ class WarningsChecker(WarningsRecorder): __tracebackhide__ = True - if self.expected_warning is None: - # nothing to do in this deprecated case, see WARNS_NONE_ARG above - return - def found_str(): return pformat([record.message for record in self], indent=2) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index dd6a6dfcf..6fd592aa1 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -120,20 +120,6 @@ def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): ) -def test_warns_none_is_deprecated(): - with pytest.warns( - PytestDeprecationWarning, - match=re.escape( - "Passing None has been deprecated.\n" - "See https://docs.pytest.org/en/latest/how-to/capture-warnings.html" - "#additional-use-cases-of-warnings-in-tests" - " for alternatives in common use cases." - ), - ): - with pytest.warns(None): # type: ignore[call-overload] - pass - - class TestSkipMsgArgumentDeprecated: def test_skip_with_msg_is_deprecated(self, pytester: Pytester) -> None: p = pytester.makepyfile( diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 19a1cd534..2508e22a2 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -345,17 +345,9 @@ class TestWarns: assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" - def test_record_only_none_deprecated_warn(self) -> None: - # This should become an error when WARNS_NONE_ARG is removed in Pytest 8.0 - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with pytest.warns(None) as record: # type: ignore[call-overload] - warnings.warn("user", UserWarning) - warnings.warn("runtime", RuntimeWarning) - - assert len(record) == 2 - assert str(record[0].message) == "user" - assert str(record[1].message) == "runtime" + def test_record_only_none_type_error(self) -> None: + with pytest.raises(TypeError): + pytest.warns(None) # type: ignore[call-overload] def test_record_by_subclass(self) -> None: with pytest.warns(Warning) as record: diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 1e1446af1..2215e978a 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -530,13 +530,11 @@ class TestRmRf: assert fn.is_file() # ignored function - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - with pytest.warns(None) as warninfo: # type: ignore[call-overload] - exc_info4 = PermissionError() - on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) - assert fn.is_file() - assert not [x.message for x in warninfo] + with warnings.catch_warnings(record=True) as w: + exc_info4 = PermissionError() + on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) + assert fn.is_file() + assert not [x.message for x in w] exc_info5 = PermissionError() on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path) From 477959ef7dc9f8491a80ba4d585d737a68f0a34c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:37:04 +0200 Subject: [PATCH 046/104] Remove deprecated `pytest.Instance` backward compat --- doc/en/deprecations.rst | 36 ++++++++++++++++++------------------ src/_pytest/deprecated.py | 4 ---- src/_pytest/python.py | 15 --------------- src/pytest/__init__.py | 12 ------------ testing/deprecated_test.py | 14 -------------- 5 files changed, 18 insertions(+), 63 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 70f8b09aa..436a29e96 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -125,24 +125,6 @@ Will also need to be ported to a supported pytest style. One way to do it is usi .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup -.. _instance-collector-deprecation: - -The ``pytest.Instance`` collector -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionremoved:: 7.0 - -The ``pytest.Instance`` collector type has been removed. - -Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`. -Now :class:`~pytest.Class` collects the test methods directly. - -Most plugins which reference ``Instance`` do so in order to ignore or skip it, -using a check such as ``if isinstance(node, Instance): return``. -Such plugins should simply remove consideration of ``Instance`` on pytest>=7. -However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``, -and importing it emits a deprecation warning. This will be removed in pytest 8. - .. _node-ctor-fspath-deprecation: @@ -432,6 +414,24 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +.. _instance-collector-deprecation: + +The ``pytest.Instance`` collector +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 7.0 + +The ``pytest.Instance`` collector type has been removed. + +Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`. +Now :class:`~pytest.Class` collects the test methods directly. + +Most plugins which reference ``Instance`` do so in order to ignore or skip it, +using a check such as ``if isinstance(node, Instance): return``. +Such plugins should simply remove consideration of ``Instance`` on pytest>=7. +However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``, +and importing it emits a deprecation warning. This was removed in pytest 8. + Using ``pytest.warns(None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 4ea2c39a2..798cf7d34 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -70,10 +70,6 @@ KEYWORD_MSG_ARG = UnformattedWarning( "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", ) -INSTANCE_COLLECTOR = PytestRemovedIn8Warning( - "The pytest.Instance collector type is deprecated and is no longer used. " - "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector", -) HOOK_LEGACY_MARKING = UnformattedWarning( PytestDeprecationWarning, "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e0f7a447a..f7e0a30b5 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -57,7 +57,6 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import INSTANCE_COLLECTOR from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureRequest @@ -905,20 +904,6 @@ class Class(PyCollector): self.obj.__pytest_setup_method = xunit_setup_method_fixture -class InstanceDummy: - """Instance used to be a node type between Class and Function. It has been - removed in pytest 7.0. Some plugins exist which reference `pytest.Instance` - only to ignore it; this dummy class keeps them working. This will be removed - in pytest 8.""" - - -def __getattr__(name: str) -> object: - if name == "Instance": - warnings.warn(INSTANCE_COLLECTOR, 2) - return InstanceDummy - raise AttributeError(f"module {__name__} has no attribute {name}") - - def hasinit(obj: object) -> bool: init: object = getattr(obj, "__init__", None) if init: diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 4e0c23ddb..5fab295c6 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -1,7 +1,5 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" -from typing import TYPE_CHECKING - from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo @@ -170,13 +168,3 @@ __all__ = [ "xfail", "yield_fixture", ] - -if not TYPE_CHECKING: - - def __getattr__(name: str) -> object: - if name == "Instance": - # The import emits a deprecation warning. - from _pytest.python import Instance - - return Instance - raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 6fd592aa1..0fe0e8a7b 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -197,20 +197,6 @@ def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: ) -def test_importing_instance_is_deprecated(pytester: Pytester) -> None: - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape("The pytest.Instance collector type is deprecated"), - ): - pytest.Instance # type:ignore[attr-defined] - - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape("The pytest.Instance collector type is deprecated"), - ): - from _pytest.python import Instance # noqa: F401 - - def test_fixture_disallow_on_marked_functions(): """Test that applying @pytest.fixture to a marked function warns (#3364).""" with pytest.warns( From 0591569b4ba9bfb12e5b5307da621a83c4ceced6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 13:41:52 +0200 Subject: [PATCH 047/104] Remove deprecated pytest.{exit,fail,skip}(msg=...) argument --- doc/en/deprecations.rst | 64 +++++++++++++++++----------------- src/_pytest/deprecated.py | 5 --- src/_pytest/outcomes.py | 70 ++++---------------------------------- testing/deprecated_test.py | 58 ------------------------------- testing/test_skipping.py | 48 -------------------------- 5 files changed, 39 insertions(+), 206 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 436a29e96..1b661d4d7 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -255,37 +255,6 @@ Directly constructing the following classes is now deprecated: These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8. -Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit` -is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these -functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. - -.. code-block:: python - - def test_fail_example(): - # old - pytest.fail(msg="foo") - # new - pytest.fail(reason="bar") - - - def test_skip_example(): - # old - pytest.skip(msg="foo") - # new - pytest.skip(reason="bar") - - - def test_exit_example(): - # old - pytest.exit(msg="foo") - # new - pytest.exit(reason="bar") - .. _diamond-inheritance-deprecated: Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item` @@ -414,6 +383,39 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 +.. versionremoved:: 8.0 + +Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit` +is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these +functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument. + +.. code-block:: python + + def test_fail_example(): + # old + pytest.fail(msg="foo") + # new + pytest.fail(reason="bar") + + + def test_skip_example(): + # old + pytest.skip(msg="foo") + # new + pytest.skip(reason="bar") + + + def test_exit_example(): + # old + pytest.exit(msg="foo") + # new + pytest.exit(reason="bar") + + .. _instance-collector-deprecation: The ``pytest.Instance`` collector diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 798cf7d34..f728d7a55 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -65,11 +65,6 @@ NODE_CTOR_FSPATH_ARG = UnformattedWarning( "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", ) -KEYWORD_MSG_ARG = UnformattedWarning( - PytestRemovedIn8Warning, - "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead", -) - HOOK_LEGACY_MARKING = UnformattedWarning( PytestDeprecationWarning, "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 0f64f91d9..8710ba3e8 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -1,7 +1,6 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" import sys -import warnings from typing import Any from typing import Callable from typing import cast @@ -11,8 +10,6 @@ from typing import Protocol from typing import Type from typing import TypeVar -from _pytest.deprecated import KEYWORD_MSG_ARG - class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info @@ -103,7 +100,8 @@ def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _E @_with_exception(Exit) def exit( - reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None + reason: str = "", + returncode: Optional[int] = None, ) -> NoReturn: """Exit testing process. @@ -113,28 +111,16 @@ def exit( :param returncode: Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. - - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. """ __tracebackhide__ = True - from _pytest.config import UsageError - - if reason and msg: - raise UsageError( - "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`." - ) - if not reason: - if msg is None: - raise UsageError("exit() requires a reason argument") - warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2) - reason = msg raise Exit(reason, returncode) @_with_exception(Skipped) def skip( - reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None + reason: str = "", + *, + allow_module_level: bool = False, ) -> NoReturn: """Skip an executing test with the given message. @@ -153,9 +139,6 @@ def skip( Defaults to False. - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. - .. note:: It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be skipped under certain conditions @@ -164,12 +147,11 @@ def skip( to skip a doctest statically. """ __tracebackhide__ = True - reason = _resolve_msg_to_reason("skip", reason, msg) raise Skipped(msg=reason, allow_module_level=allow_module_level) @_with_exception(Failed) -def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> NoReturn: +def fail(reason: str = "", pytrace: bool = True) -> NoReturn: """Explicitly fail an executing test with the given message. :param reason: @@ -178,51 +160,11 @@ def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> N :param pytrace: If False, msg represents the full failure information and no python traceback will be reported. - - :param msg: - Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. """ __tracebackhide__ = True - reason = _resolve_msg_to_reason("fail", reason, msg) raise Failed(msg=reason, pytrace=pytrace) -def _resolve_msg_to_reason( - func_name: str, reason: str, msg: Optional[str] = None -) -> str: - """ - Handles converting the deprecated msg parameter if provided into - reason, raising a deprecation warning. This function will be removed - when the optional msg argument is removed from here in future. - - :param str func_name: - The name of the offending function, this is formatted into the deprecation message. - - :param str reason: - The reason= passed into either pytest.fail() or pytest.skip() - - :param str msg: - The msg= passed into either pytest.fail() or pytest.skip(). This will - be converted into reason if it is provided to allow pytest.skip(msg=) or - pytest.fail(msg=) to continue working in the interim period. - - :returns: - The value to use as reason. - - """ - __tracebackhide__ = True - if msg is not None: - if reason: - from pytest import UsageError - - raise UsageError( - f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted." - ) - warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3) - reason = msg - return reason - - class XFailed(Failed): """Raised from an explicit call to pytest.xfail().""" diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 0fe0e8a7b..284441f73 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -120,64 +120,6 @@ def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): ) -class TestSkipMsgArgumentDeprecated: - def test_skip_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_skipping_msg(): - pytest.skip(msg="skippedmsg") - """ - ) - result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, " - "use pytest.skip(reason=...) instead", - '*pytest.skip(msg="skippedmsg")*', - ] - ) - result.assert_outcomes(skipped=1, warnings=1) - - def test_fail_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_failing_msg(): - pytest.fail(msg="failedmsg") - """ - ) - result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, " - "use pytest.fail(reason=...) instead", - '*pytest.fail(msg="failedmsg")', - ] - ) - result.assert_outcomes(failed=1, warnings=1) - - def test_exit_with_msg_is_deprecated(self, pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_exit_msg(): - pytest.exit(msg="exitmsg") - """ - ) - result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines( - [ - "*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, " - "use pytest.exit(reason=...) instead", - ] - ) - result.assert_outcomes(warnings=1) - - def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: mod = pytester.getmodulecol("") diff --git a/testing/test_skipping.py b/testing/test_skipping.py index b7e448df3..a002ba6e8 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1494,54 +1494,6 @@ def test_fail_using_reason_works_ok(pytester: Pytester) -> None: result.assert_outcomes(failed=1) -def test_fail_fails_with_msg_and_reason(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_fail_both_arguments(): - pytest.fail(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: Passing both ``reason`` and ``msg`` to pytest.fail(...) is not permitted.*" - ) - result.assert_outcomes(failed=1) - - -def test_skip_fails_with_msg_and_reason(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_skip_both_arguments(): - pytest.skip(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: Passing both ``reason`` and ``msg`` to pytest.skip(...) is not permitted.*" - ) - result.assert_outcomes(failed=1) - - -def test_exit_with_msg_and_reason_fails(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import pytest - - def test_exit_both_arguments(): - pytest.exit(reason="foo", msg="bar") - """ - ) - result = pytester.runpytest(p) - result.stdout.fnmatch_lines( - "*UsageError: cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`.*" - ) - result.assert_outcomes(failed=1) - - def test_exit_with_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ From 0f18a7fe5e8911fe4b329931dcf8f23cb30130bd Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 15:20:38 +0200 Subject: [PATCH 048/104] Remove deprecated nose support --- README.rst | 4 +- doc/en/adopt.rst | 3 +- doc/en/contents.rst | 1 - doc/en/deprecations.rst | 216 ++++++------ doc/en/example/index.rst | 1 - doc/en/explanation/fixtures.rst | 2 +- doc/en/how-to/existingtestsuite.rst | 4 +- doc/en/how-to/index.rst | 1 - doc/en/how-to/nose.rst | 99 ------ doc/en/index.rst | 2 +- setup.cfg | 1 - src/_pytest/config/__init__.py | 1 - src/_pytest/deprecated.py | 15 - src/_pytest/nose.py | 50 --- src/_pytest/python.py | 37 -- testing/deprecated_test.py | 59 ---- testing/test_nose.py | 529 ---------------------------- 17 files changed, 116 insertions(+), 909 deletions(-) delete mode 100644 doc/en/how-to/nose.rst delete mode 100644 src/_pytest/nose.py delete mode 100644 testing/test_nose.py diff --git a/README.rst b/README.rst index bbf41a183..6e4772b04 100644 --- a/README.rst +++ b/README.rst @@ -97,8 +97,8 @@ Features - `Modular fixtures `_ for managing small or parametrized long-lived test resources -- Can run `unittest `_ (or trial), - `nose `_ test suites out of the box +- Can run `unittest `_ (or trial) + test suites out of the box - Python 3.8+ or PyPy3 diff --git a/doc/en/adopt.rst b/doc/en/adopt.rst index 13d82bf01..b95a117de 100644 --- a/doc/en/adopt.rst +++ b/doc/en/adopt.rst @@ -44,7 +44,7 @@ Partner projects, sign up here! (by 22 March) What does it mean to "adopt pytest"? ----------------------------------------- -There can be many different definitions of "success". Pytest can run many nose_ and unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right? +There can be many different definitions of "success". Pytest can run many unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right? Progressive success might look like: @@ -62,7 +62,6 @@ Progressive success might look like: It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies. -.. _nose: nose.html .. _unittest: unittest.html .. _assert: assert.html .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview diff --git a/doc/en/contents.rst b/doc/en/contents.rst index ae42884f6..181207203 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -44,7 +44,6 @@ How-to guides how-to/existingtestsuite how-to/unittest - how-to/nose how-to/xunit_setup how-to/bash-completion diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 1b661d4d7..f1007d977 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -19,113 +19,6 @@ Below is a complete list of all pytest features which are considered deprecated. :class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. -.. _nose-deprecation: - -Support for tests written for nose -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.2 - -Support for running tests written for `nose `__ is now deprecated. - -``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills -over the code base (see :issue:`9886` for more details). - -setup/teardown -^^^^^^^^^^^^^^ - -One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native, -they are in fact part of the ``nose`` support. - - -.. code-block:: python - - class Test: - def setup(self): - self.resource = make_resource() - - def teardown(self): - self.resource.close() - - def test_foo(self): - ... - - def test_bar(self): - ... - - - -Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to: - -.. code-block:: python - - class Test: - def setup_method(self): - self.resource = make_resource() - - def teardown_method(self): - self.resource.close() - - def test_foo(self): - ... - - def test_bar(self): - ... - - -This is easy to do in an entire code base by doing a simple find/replace. - -@with_setup -^^^^^^^^^^^ - -Code using `@with_setup `_ such as this: - -.. code-block:: python - - from nose.tools import with_setup - - - def setup_some_resource(): - ... - - - def teardown_some_resource(): - ... - - - @with_setup(setup_some_resource, teardown_some_resource) - def test_foo(): - ... - -Will also need to be ported to a supported pytest style. One way to do it is using a fixture: - -.. code-block:: python - - import pytest - - - def setup_some_resource(): - ... - - - def teardown_some_resource(): - ... - - - @pytest.fixture - def some_resource(): - setup_some_resource() - yield - teardown_some_resource() - - - def test_foo(some_resource): - ... - - -.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup - - .. _node-ctor-fspath-deprecation: ``fspath`` argument for Node constructors replaced with ``pathlib.Path`` @@ -383,6 +276,115 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +.. _nose-deprecation: + +Support for tests written for nose +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2 +.. versionremoved:: 8.0 + +Support for running tests written for `nose `__ is now deprecated. + +``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills +over the code base (see :issue:`9886` for more details). + +setup/teardown +^^^^^^^^^^^^^^ + +One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native, +they are in fact part of the ``nose`` support. + + +.. code-block:: python + + class Test: + def setup(self): + self.resource = make_resource() + + def teardown(self): + self.resource.close() + + def test_foo(self): + ... + + def test_bar(self): + ... + + + +Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to: + +.. code-block:: python + + class Test: + def setup_method(self): + self.resource = make_resource() + + def teardown_method(self): + self.resource.close() + + def test_foo(self): + ... + + def test_bar(self): + ... + + +This is easy to do in an entire code base by doing a simple find/replace. + +@with_setup +^^^^^^^^^^^ + +Code using `@with_setup `_ such as this: + +.. code-block:: python + + from nose.tools import with_setup + + + def setup_some_resource(): + ... + + + def teardown_some_resource(): + ... + + + @with_setup(setup_some_resource, teardown_some_resource) + def test_foo(): + ... + +Will also need to be ported to a supported pytest style. One way to do it is using a fixture: + +.. code-block:: python + + import pytest + + + def setup_some_resource(): + ... + + + def teardown_some_resource(): + ... + + + @pytest.fixture + def some_resource(): + setup_some_resource() + yield + teardown_some_resource() + + + def test_foo(some_resource): + ... + + +.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup + + + Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/example/index.rst b/doc/en/example/index.rst index e8835aae9..840819002 100644 --- a/doc/en/example/index.rst +++ b/doc/en/example/index.rst @@ -18,7 +18,6 @@ For basic examples, see - :ref:`Fixtures ` for basic fixture/setup examples - :ref:`parametrize` for basic test function parametrization - :ref:`unittest` for basic unittest integration -- :ref:`noseintegration` for basic nosetests integration The following examples aim at various use cases you might encounter. diff --git a/doc/en/explanation/fixtures.rst b/doc/en/explanation/fixtures.rst index 322718873..0bb3bf49f 100644 --- a/doc/en/explanation/fixtures.rst +++ b/doc/en/explanation/fixtures.rst @@ -85,7 +85,7 @@ style of setup/teardown functions: In addition, pytest continues to support :ref:`xunitsetup`. You can mix both styles, moving incrementally from classic to new style, as you prefer. You can also start out from existing :ref:`unittest.TestCase -style ` or :ref:`nose based ` projects. +style `. diff --git a/doc/en/how-to/existingtestsuite.rst b/doc/en/how-to/existingtestsuite.rst index 9909e7d11..1c37023c7 100644 --- a/doc/en/how-to/existingtestsuite.rst +++ b/doc/en/how-to/existingtestsuite.rst @@ -4,8 +4,8 @@ How to use pytest with an existing test suite ============================================== Pytest can be used with most existing test suites, but its -behavior differs from other test runners such as :ref:`nose ` or -Python's default unittest framework. +behavior differs from other test runners such as Python's +default unittest framework. Before using this section you will want to :ref:`install pytest `. diff --git a/doc/en/how-to/index.rst b/doc/en/how-to/index.rst index 6f52aaecd..225f28965 100644 --- a/doc/en/how-to/index.rst +++ b/doc/en/how-to/index.rst @@ -52,7 +52,6 @@ pytest and other test systems existingtestsuite unittest - nose xunit_setup pytest development environment diff --git a/doc/en/how-to/nose.rst b/doc/en/how-to/nose.rst deleted file mode 100644 index 45d3357cf..000000000 --- a/doc/en/how-to/nose.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. _`noseintegration`: - -How to run tests written for nose -======================================= - -``pytest`` has basic support for running tests written for nose_. - -.. warning:: - This functionality has been deprecated and is likely to be removed in ``pytest 8.x``. - -.. _nosestyle: - -Usage -------------- - -After :ref:`installation` type: - -.. code-block:: bash - - python setup.py develop # make sure tests can import our package - pytest # instead of 'nosetests' - -and you should be able to run your nose style tests and -make use of pytest's capabilities. - -Supported nose Idioms ----------------------- - -* ``setup()`` and ``teardown()`` at module/class/method level: any function or method called ``setup`` will be called during the setup phase for each test, same for ``teardown``. -* ``SkipTest`` exceptions and markers -* setup/teardown decorators -* ``__test__`` attribute on modules/classes/functions -* general usage of nose utilities - -Unsupported idioms / known issues ----------------------------------- - -- unittest-style ``setUp, tearDown, setUpClass, tearDownClass`` - are recognized only on ``unittest.TestCase`` classes but not - on plain classes. ``nose`` supports these methods also on plain - classes but pytest deliberately does not. As nose and pytest already - both support ``setup_class, teardown_class, setup_method, teardown_method`` - it doesn't seem useful to duplicate the unittest-API like nose does. - If you however rather think pytest should support the unittest-spelling on - plain classes please post to :issue:`377`. - -- nose imports test modules with the same import path (e.g. - ``tests.test_mode``) but different file system paths - (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``) - by extending sys.path/import semantics. pytest does not do that. Note that - `nose2 choose to avoid this sys.path/import hackery `_. - - If you place a conftest.py file in the root directory of your project - (as determined by pytest) pytest will run tests "nose style" against - the code below that directory by adding it to your ``sys.path`` instead of - running against your installed code. - - You may find yourself wanting to do this if you ran ``python setup.py install`` - to set up your project, as opposed to ``python setup.py develop`` or any of - the package manager equivalents. Installing with develop in a - virtual environment like tox is recommended over this pattern. - -- nose-style doctests are not collected and executed correctly, - also doctest fixtures don't work. - -- no nose-configuration is recognized. - -- ``yield``-based methods are - fundamentally incompatible with pytest because they don't support fixtures - properly since collection and test execution are separated. - -Here is a table comparing the default supported naming conventions for both -nose and pytest. - -========= ========================== ======= ===== -what default naming convention pytest nose -========= ========================== ======= ===== -module ``test*.py`` ✅ -module ``test_*.py`` ✅ ✅ -module ``*_test.py`` ✅ -module ``*_tests.py`` -class ``*(unittest.TestCase)`` ✅ ✅ -method ``test_*`` ✅ ✅ -class ``Test*`` ✅ -method ``test_*`` ✅ -function ``test_*`` ✅ -========= ========================== ======= ===== - - -Migrating from nose to pytest ------------------------------- - -`nose2pytest `_ is a Python script -and pytest plugin to help convert Nose-based tests into pytest-based tests. -Specifically, the script transforms ``nose.tools.assert_*`` function calls into -raw assert statements, while preserving format of original arguments -as much as possible. - -.. _nose: https://nose.readthedocs.io/en/latest/ diff --git a/doc/en/index.rst b/doc/en/index.rst index 50c84f6ae..bef42716f 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -74,7 +74,7 @@ Features - :ref:`Modular fixtures ` for managing small or parametrized long-lived test resources -- Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box +- Can run :ref:`unittest ` (including trial) test suites out of the box - Python 3.8+ or PyPy 3 diff --git a/setup.cfg b/setup.cfg index 3b1c627de..02f6031bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,7 +69,6 @@ testing = attrs>=19.2.0 hypothesis>=3.56 mock - nose pygments>=2.7.2 requests setuptools diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 2d5db1724..5d176c7ac 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -252,7 +252,6 @@ default_plugins = essential_plugins + ( "monkeypatch", "recwarn", "pastebin", - "nose", "assertion", "junitxml", "doctest", diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index f728d7a55..072da3211 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -23,21 +23,6 @@ DEPRECATED_EXTERNAL_PLUGINS = { "pytest_faulthandler", } -NOSE_SUPPORT = UnformattedWarning( - PytestRemovedIn8Warning, - "Support for nose tests is deprecated and will be removed in a future release.\n" - "{nodeid} is using nose method: `{method}` ({stage})\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", -) - -NOSE_SUPPORT_METHOD = UnformattedWarning( - PytestRemovedIn8Warning, - "Support for nose tests is deprecated and will be removed in a future release.\n" - "{nodeid} is using nose-specific method: `{method}(self)`\n" - "To remove this warning, rename it to `{method}_method(self)`\n" - "See docs: https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose", -) - # This can be* removed pytest 8, but it's harmless and common, so no rush to remove. # * If you're in the future: "could have been". diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py deleted file mode 100644 index 273bd045f..000000000 --- a/src/_pytest/nose.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Run testsuites written for nose.""" -import warnings - -from _pytest.config import hookimpl -from _pytest.deprecated import NOSE_SUPPORT -from _pytest.fixtures import getfixturemarker -from _pytest.nodes import Item -from _pytest.python import Function -from _pytest.unittest import TestCaseFunction - - -@hookimpl(trylast=True) -def pytest_runtest_setup(item: Item) -> None: - if not isinstance(item, Function): - return - # Don't do nose style setup/teardown on direct unittest style classes. - if isinstance(item, TestCaseFunction): - return - - # Capture the narrowed type of item for the teardown closure, - # see https://github.com/python/mypy/issues/2608 - func = item - - call_optional(func.obj, "setup", func.nodeid) - func.addfinalizer(lambda: call_optional(func.obj, "teardown", func.nodeid)) - - # NOTE: Module- and class-level fixtures are handled in python.py - # with `pluginmanager.has_plugin("nose")` checks. - # It would have been nicer to implement them outside of core, but - # it's not straightforward. - - -def call_optional(obj: object, name: str, nodeid: str) -> bool: - method = getattr(obj, name, None) - if method is None: - return False - is_fixture = getfixturemarker(method) is not None - if is_fixture: - return False - if not callable(method): - return False - # Warn about deprecation of this plugin. - method_name = getattr(method, "__name__", str(method)) - warnings.warn( - NOSE_SUPPORT.format(nodeid=nodeid, method=method_name, stage=name), stacklevel=2 - ) - # If there are any problems allow the exception to raise rather than - # silently ignoring it. - method() - return True diff --git a/src/_pytest/python.py b/src/_pytest/python.py index f7e0a30b5..969bb6765 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -57,7 +57,6 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FixtureDef from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FuncFixtureInfo @@ -596,23 +595,12 @@ class Module(nodes.File, PyCollector): Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_module = _get_first_non_fixture_func( self.obj, ("setUpModule", "setup_module") ) - if setup_module is None and has_nose: - # The name "setup" is too common - only treat as fixture if callable. - setup_module = _get_first_non_fixture_func(self.obj, ("setup",)) - if not callable(setup_module): - setup_module = None teardown_module = _get_first_non_fixture_func( self.obj, ("tearDownModule", "teardown_module") ) - if teardown_module is None and has_nose: - teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",)) - # Same as "setup" above - only treat as fixture if callable. - if not callable(teardown_module): - teardown_module = None if setup_module is None and teardown_module is None: return @@ -853,21 +841,10 @@ class Class(PyCollector): Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - has_nose = self.config.pluginmanager.has_plugin("nose") setup_name = "setup_method" setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) - emit_nose_setup_warning = False - if setup_method is None and has_nose: - setup_name = "setup" - emit_nose_setup_warning = True - setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) - emit_nose_teardown_warning = False - if teardown_method is None and has_nose: - teardown_name = "teardown" - emit_nose_teardown_warning = True - teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return @@ -882,24 +859,10 @@ class Class(PyCollector): if setup_method is not None: func = getattr(self, setup_name) _call_with_optional_argument(func, method) - if emit_nose_setup_warning: - warnings.warn( - NOSE_SUPPORT_METHOD.format( - nodeid=request.node.nodeid, method="setup" - ), - stacklevel=2, - ) yield if teardown_method is not None: func = getattr(self, teardown_name) _call_with_optional_argument(func, method) - if emit_nose_teardown_warning: - warnings.warn( - NOSE_SUPPORT_METHOD.format( - nodeid=request.node.nodeid, method="teardown" - ), - stacklevel=2, - ) self.obj.__pytest_setup_method = xunit_setup_method_fixture diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 284441f73..62314240f 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -188,62 +188,3 @@ def test_fixture_disallowed_between_marks(): raise NotImplementedError() assert len(record) == 2 # one for each mark decorator - - -@pytest.mark.filterwarnings("default") -def test_nose_deprecated_with_setup(pytester: Pytester) -> None: - pytest.importorskip("nose") - pytester.makepyfile( - """ - from nose.tools import with_setup - - def setup_fn_no_op(): - ... - - def teardown_fn_no_op(): - ... - - @with_setup(setup_fn_no_op, teardown_fn_no_op) - def test_omits_warnings(): - ... - """ - ) - output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") - message = [ - "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", - "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `teardown_fn_no_op` (teardown)", - ] - output.stdout.fnmatch_lines(message) - output.assert_outcomes(passed=1) - - -@pytest.mark.filterwarnings("default") -def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None: - pytest.importorskip("nose") - pytester.makepyfile( - """ - class Test: - - def setup(self): - ... - - def teardown(self): - ... - - def test(self): - ... - """ - ) - output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") - message = [ - "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`", - "*To remove this warning, rename it to `setup_method(self)`", - "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", - "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `teardown(self)`", - "*To remove this warning, rename it to `teardown_method(self)`", - ] - output.stdout.fnmatch_lines(message) - output.assert_outcomes(passed=1) diff --git a/testing/test_nose.py b/testing/test_nose.py deleted file mode 100644 index 7ec4026f2..000000000 --- a/testing/test_nose.py +++ /dev/null @@ -1,529 +0,0 @@ -import pytest -from _pytest.pytester import Pytester - - -def setup_module(mod): - mod.nose = pytest.importorskip("nose") - - -def test_nose_setup(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - values = [] - from nose.tools import with_setup - - @with_setup(lambda: values.append(1), lambda: values.append(2)) - def test_hello(): - assert values == [1] - - def test_world(): - assert values == [1,2] - - test_hello.setup = lambda: values.append(1) - test_hello.teardown = lambda: values.append(2) - """ - ) - result = pytester.runpytest( - p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" - ) - result.assert_outcomes(passed=2) - - -def test_setup_func_with_setup_decorator() -> None: - from _pytest.nose import call_optional - - values = [] - - class A: - @pytest.fixture(autouse=True) - def f(self): - values.append(1) - - call_optional(A(), "f", "A.f") - assert not values - - -def test_setup_func_not_callable() -> None: - from _pytest.nose import call_optional - - class A: - f = 1 - - call_optional(A(), "f", "A.f") - - -def test_nose_setup_func(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - from nose.tools import with_setup - - values = [] - - def my_setup(): - a = 1 - values.append(a) - - def my_teardown(): - b = 2 - values.append(b) - - @with_setup(my_setup, my_teardown) - def test_hello(): - print(values) - assert values == [1] - - def test_world(): - print(values) - assert values == [1,2] - - """ - ) - result = pytester.runpytest( - p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" - ) - result.assert_outcomes(passed=2) - - -def test_nose_setup_func_failure(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - from nose.tools import with_setup - - values = [] - my_setup = lambda x: 1 - my_teardown = lambda x: 2 - - @with_setup(my_setup, my_teardown) - def test_hello(): - print(values) - assert values == [1] - - def test_world(): - print(values) - assert values == [1,2] - - """ - ) - result = pytester.runpytest( - p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" - ) - result.stdout.fnmatch_lines(["*TypeError: ()*"]) - - -def test_nose_setup_func_failure_2(pytester: Pytester) -> None: - pytester.makepyfile( - """ - values = [] - - my_setup = 1 - my_teardown = 2 - - def test_hello(): - assert values == [] - - test_hello.setup = my_setup - test_hello.teardown = my_teardown - """ - ) - reprec = pytester.inline_run() - reprec.assertoutcome(passed=1) - - -def test_nose_setup_partial(pytester: Pytester) -> None: - pytest.importorskip("functools") - p = pytester.makepyfile( - """ - from functools import partial - - values = [] - - def my_setup(x): - a = x - values.append(a) - - def my_teardown(x): - b = x - values.append(b) - - my_setup_partial = partial(my_setup, 1) - my_teardown_partial = partial(my_teardown, 2) - - def test_hello(): - print(values) - assert values == [1] - - def test_world(): - print(values) - assert values == [1,2] - - test_hello.setup = my_setup_partial - test_hello.teardown = my_teardown_partial - """ - ) - result = pytester.runpytest( - p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" - ) - result.stdout.fnmatch_lines(["*2 passed*"]) - - -def test_module_level_setup(pytester: Pytester) -> None: - pytester.makepyfile( - """ - from nose.tools import with_setup - items = {} - - def setup(): - items.setdefault("setup", []).append("up") - - def teardown(): - items.setdefault("setup", []).append("down") - - def setup2(): - items.setdefault("setup2", []).append("up") - - def teardown2(): - items.setdefault("setup2", []).append("down") - - def test_setup_module_setup(): - assert items["setup"] == ["up"] - - def test_setup_module_setup_again(): - assert items["setup"] == ["up"] - - @with_setup(setup2, teardown2) - def test_local_setup(): - assert items["setup"] == ["up"] - assert items["setup2"] == ["up"] - - @with_setup(setup2, teardown2) - def test_local_setup_again(): - assert items["setup"] == ["up"] - assert items["setup2"] == ["up", "down", "up"] - """ - ) - result = pytester.runpytest( - "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" - ) - result.stdout.fnmatch_lines(["*4 passed*"]) - - -def test_nose_style_setup_teardown(pytester: Pytester) -> None: - pytester.makepyfile( - """ - values = [] - - def setup_module(): - values.append(1) - - def teardown_module(): - del values[0] - - def test_hello(): - assert values == [1] - - def test_world(): - assert values == [1] - """ - ) - result = pytester.runpytest("-p", "nose") - result.stdout.fnmatch_lines(["*2 passed*"]) - - -def test_fixtures_nose_setup_issue8394(pytester: Pytester) -> None: - pytester.makepyfile( - """ - def setup_module(): - pass - - def teardown_module(): - pass - - def setup_function(func): - pass - - def teardown_function(func): - pass - - def test_world(): - pass - - class Test(object): - def setup_class(cls): - pass - - def teardown_class(cls): - pass - - def setup_method(self, meth): - pass - - def teardown_method(self, meth): - pass - - def test_method(self): pass - """ - ) - match = "*no docstring available*" - result = pytester.runpytest("--fixtures") - assert result.ret == 0 - result.stdout.no_fnmatch_line(match) - - result = pytester.runpytest("--fixtures", "-v") - assert result.ret == 0 - result.stdout.fnmatch_lines([match, match, match, match]) - - -def test_nose_setup_ordering(pytester: Pytester) -> None: - pytester.makepyfile( - """ - def setup_module(mod): - mod.visited = True - - class TestClass(object): - def setup(self): - assert visited - self.visited_cls = True - def test_first(self): - assert visited - assert self.visited_cls - """ - ) - result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning") - result.stdout.fnmatch_lines(["*1 passed*"]) - - -def test_apiwrapper_problem_issue260(pytester: Pytester) -> None: - # this would end up trying a call an optional teardown on the class - # for plain unittests we don't want nose behaviour - pytester.makepyfile( - """ - import unittest - class TestCase(unittest.TestCase): - def setup(self): - #should not be called in unittest testcases - assert 0, 'setup' - def teardown(self): - #should not be called in unittest testcases - assert 0, 'teardown' - def setUp(self): - print('setup') - def tearDown(self): - print('teardown') - def test_fun(self): - pass - """ - ) - result = pytester.runpytest() - result.assert_outcomes(passed=1) - - -def test_setup_teardown_linking_issue265(pytester: Pytester) -> None: - # we accidentally didn't integrate nose setupstate with normal setupstate - # this test ensures that won't happen again - pytester.makepyfile( - ''' - import pytest - - class TestGeneric(object): - def test_nothing(self): - """Tests the API of the implementation (for generic and specialized).""" - - @pytest.mark.skipif("True", reason= - "Skip tests to check if teardown is skipped as well.") - class TestSkipTeardown(TestGeneric): - - def setup(self): - """Sets up my specialized implementation for $COOL_PLATFORM.""" - raise Exception("should not call setup for skipped tests") - - def teardown(self): - """Undoes the setup.""" - raise Exception("should not call teardown for skipped tests") - ''' - ) - reprec = pytester.runpytest() - reprec.assert_outcomes(passed=1, skipped=1) - - -def test_SkipTest_during_collection(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import nose - raise nose.SkipTest("during collection") - def test_failing(): - assert False - """ - ) - result = pytester.runpytest(p) - result.assert_outcomes(skipped=1, warnings=0) - - -def test_SkipTest_in_test(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import nose - - def test_skipping(): - raise nose.SkipTest("in test") - """ - ) - reprec = pytester.inline_run() - reprec.assertoutcome(skipped=1) - - -def test_istest_function_decorator(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import nose.tools - @nose.tools.istest - def not_test_prefix(): - pass - """ - ) - result = pytester.runpytest(p) - result.assert_outcomes(passed=1) - - -def test_nottest_function_decorator(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import nose.tools - @nose.tools.nottest - def test_prefix(): - pass - """ - ) - reprec = pytester.inline_run() - assert not reprec.getfailedcollections() - calls = reprec.getreports("pytest_runtest_logreport") - assert not calls - - -def test_istest_class_decorator(pytester: Pytester) -> None: - p = pytester.makepyfile( - """ - import nose.tools - @nose.tools.istest - class NotTestPrefix(object): - def test_method(self): - pass - """ - ) - result = pytester.runpytest(p) - result.assert_outcomes(passed=1) - - -def test_nottest_class_decorator(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import nose.tools - @nose.tools.nottest - class TestPrefix(object): - def test_method(self): - pass - """ - ) - reprec = pytester.inline_run() - assert not reprec.getfailedcollections() - calls = reprec.getreports("pytest_runtest_logreport") - assert not calls - - -def test_skip_test_with_unicode(pytester: Pytester) -> None: - pytester.makepyfile( - """\ - import unittest - class TestClass(): - def test_io(self): - raise unittest.SkipTest('😊') - """ - ) - result = pytester.runpytest() - result.stdout.fnmatch_lines(["* 1 skipped *"]) - - -def test_raises(pytester: Pytester) -> None: - pytester.makepyfile( - """ - from nose.tools import raises - - @raises(RuntimeError) - def test_raises_runtimeerror(): - raise RuntimeError - - @raises(Exception) - def test_raises_baseexception_not_caught(): - raise BaseException - - @raises(BaseException) - def test_raises_baseexception_caught(): - raise BaseException - """ - ) - result = pytester.runpytest("-vv") - result.stdout.fnmatch_lines( - [ - "test_raises.py::test_raises_runtimeerror PASSED*", - "test_raises.py::test_raises_baseexception_not_caught FAILED*", - "test_raises.py::test_raises_baseexception_caught PASSED*", - "*= FAILURES =*", - "*_ test_raises_baseexception_not_caught _*", - "", - "arg = (), kw = {}", - "", - " def newfunc(*arg, **kw):", - " try:", - "> func(*arg, **kw)", - "", - "*/nose/*: ", - "_ _ *", - "", - " @raises(Exception)", - " def test_raises_baseexception_not_caught():", - "> raise BaseException", - "E BaseException", - "", - "test_raises.py:9: BaseException", - "* 1 failed, 2 passed *", - ] - ) - - -def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None: - """Regression test for #9391.""" - p = pytester.makepyfile( - __init__="", - setup=""" - """, - teardown=""" - """, - test_it=""" - from . import setup, teardown - - def test_it(): - pass - """, - ) - result = pytester.runpytest(p.parent, "-p", "nose") - assert result.ret == 0 - - -@pytest.mark.parametrize("fixture_name", ("teardown", "teardown_class")) -def test_teardown_fixture_not_called_directly(fixture_name, pytester: Pytester) -> None: - """Regression test for #10597.""" - p = pytester.makepyfile( - f""" - import pytest - - class TestHello: - - @pytest.fixture - def {fixture_name}(self): - yield - - def test_hello(self, {fixture_name}): - assert True - """ - ) - result = pytester.runpytest(p, "-p", "nose") - assert result.ret == 0 From cb5a42c836744181229b522611084a70220e4e33 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 2 Jan 2024 19:31:37 +0200 Subject: [PATCH 049/104] terminalwriter: fix crash trying to highlight empty source For quick checking I don't know how we can reach here with an empty source, so test just checks the function directly. Fix #11758. --- changelog/11758.bugfix.rst | 2 ++ src/_pytest/_io/terminalwriter.py | 3 ++- testing/io/test_terminalwriter.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 changelog/11758.bugfix.rst diff --git a/changelog/11758.bugfix.rst b/changelog/11758.bugfix.rst new file mode 100644 index 000000000..af8a3f351 --- /dev/null +++ b/changelog/11758.bugfix.rst @@ -0,0 +1,2 @@ +Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``. +This bug was introduced in pytest 8.0.0rc1. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index bf9b76651..56107d566 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -200,8 +200,9 @@ class TerminalWriter: """Highlight the given source if we have markup support.""" from _pytest.config.exceptions import UsageError - if not self.hasmarkup or not self.code_highlight: + if not source or not self.hasmarkup or not self.code_highlight: return source + try: from pygments.formatters.terminal import TerminalFormatter diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index 96e7366e5..c7e63c672 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -306,3 +306,17 @@ def test_code_highlight(has_markup, code_highlight, expected, color_mapping): match=re.escape("indents size (2) should have same size as lines (1)"), ): tw._write_source(["assert 0"], [" ", " "]) + + +def test_highlight_empty_source() -> None: + """Don't crash trying to highlight empty source code. + + Issue #11758. + """ + f = io.StringIO() + tw = terminalwriter.TerminalWriter(f) + tw.hasmarkup = True + tw.code_highlight = True + tw._write_source([]) + + assert f.getvalue() == "" From effc2b05294b63e23162327a5704077dda6c60d4 Mon Sep 17 00:00:00 2001 From: Marc Bresson <50196352+MarcBresson@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:20:54 +0100 Subject: [PATCH 050/104] Clarified `markers` ini property. Fix #11738 (#11739) --- AUTHORS | 1 + doc/en/reference/reference.rst | 2 +- src/_pytest/mark/__init__.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 42cfd0be2..14a35c3d5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -242,6 +242,7 @@ Marc Mueller Marc Schlaich Marcelo Duarte Trevisani Marcin Bachry +Marc Bresson Marco Gorelli Mark Abramowitz Mark Dickinson diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 33aff0f7c..2796fa4f5 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -2100,7 +2100,7 @@ All the command-line flags can be obtained by running ``pytest --help``:: [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found: - markers (linelist): Markers for test functions + markers (linelist): Register new markers for test functions empty_parameter_set_mark (string): Default marker for empty parametersets norecursedirs (args): Directory patterns to avoid for recursion diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 3f97299ea..bcee802f3 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -105,7 +105,7 @@ def pytest_addoption(parser: Parser) -> None: help="show markers (builtin, plugin and per-project ones).", ) - parser.addini("markers", "Markers for test functions", "linelist") + parser.addini("markers", "Register new markers for test functions", "linelist") parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets") From a98f02d4238793300f1be01f75bf0fff5609a241 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 16:36:50 +0200 Subject: [PATCH 051/104] Remove deprecated py.path hook arguments --- doc/en/deprecations.rst | 52 +++++++++++++------------ src/_pytest/config/__init__.py | 4 +- src/_pytest/config/compat.py | 70 ---------------------------------- src/_pytest/deprecated.py | 7 ---- src/_pytest/hookspec.py | 43 +++++++++++---------- src/_pytest/main.py | 3 +- testing/deprecated_test.py | 33 ---------------- 7 files changed, 51 insertions(+), 161 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index f1007d977..c623f09ca 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -104,31 +104,6 @@ Changed ``hookwrapper`` attributes: * ``historic`` -``py.path.local`` arguments for hooks replaced with ``pathlib.Path`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments: - -* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) ` as equivalent to ``path`` -* :hook:`pytest_collect_file(file_path: pathlib.Path) ` as equivalent to ``path`` -* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) ` as equivalent to ``path`` -* :hook:`pytest_report_header(start_path: pathlib.Path) ` as equivalent to ``startdir`` -* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) ` as equivalent to ``startdir`` - -The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments. - -.. note:: - The name of the :class:`~_pytest.nodes.Node` arguments and attributes, - :ref:`outlined above ` (the new attribute - being ``path``) is **the opposite** of the situation for hooks (the old - argument being ``path``). - - This is an unfortunate artifact due to historical reasons, which should be - resolved in future versions as we slowly get rid of the :pypi:`py` - dependency (see :issue:`9283` for a longer discussion). - Directly constructing internal classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -276,6 +251,33 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +``py.path.local`` arguments for hooks replaced with ``pathlib.Path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 +.. versionremoved:: 8.0 + +In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments: + +* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_collect_file(file_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) ` as equivalent to ``path`` +* :hook:`pytest_report_header(start_path: pathlib.Path) ` as equivalent to ``startdir`` +* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) ` as equivalent to ``startdir`` + +The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments. + +.. note:: + The name of the :class:`~_pytest.nodes.Node` arguments and attributes, + :ref:`outlined above ` (the new attribute + being ``path``) is **the opposite** of the situation for hooks (the old + argument being ``path``). + + This is an unfortunate artifact due to historical reasons, which should be + resolved in future versions as we slowly get rid of the :pypi:`py` + dependency (see :issue:`9283` for a longer discussion). + + .. _nose-deprecation: Support for tests written for nose diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 5d176c7ac..49d63a357 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -38,7 +38,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union -import pluggy from pluggy import HookimplMarker from pluggy import HookimplOpts from pluggy import HookspecMarker @@ -48,7 +47,6 @@ from pluggy import PluginManager import _pytest._code import _pytest.deprecated import _pytest.hookspec -from .compat import PathAwareHookProxy from .exceptions import PrintHelp as PrintHelp from .exceptions import UsageError as UsageError from .findpaths import determine_setup @@ -1008,7 +1006,7 @@ class Config: self._store = self.stash self.trace = self.pluginmanager.trace.root.get("config") - self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] + self.hook = self.pluginmanager.hook # type: ignore[assignment] self._inicache: Dict[str, Any] = {} self._override_ini: Sequence[str] = () self._opt2dest: Dict[str, str] = {} diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py index afb38bbcc..9c61b4dac 100644 --- a/src/_pytest/config/compat.py +++ b/src/_pytest/config/compat.py @@ -1,24 +1,8 @@ from __future__ import annotations -import functools -import warnings from pathlib import Path -from typing import Mapping - -import pluggy from ..compat import LEGACY_PATH -from ..compat import legacy_path -from ..deprecated import HOOK_LEGACY_PATH_ARG - -# hookname: (Path, LEGACY_PATH) -imply_paths_hooks: Mapping[str, tuple[str, str]] = { - "pytest_ignore_collect": ("collection_path", "path"), - "pytest_collect_file": ("file_path", "path"), - "pytest_pycollect_makemodule": ("module_path", "path"), - "pytest_report_header": ("start_path", "startdir"), - "pytest_report_collectionfinish": ("start_path", "startdir"), -} def _check_path(path: Path, fspath: LEGACY_PATH) -> None: @@ -27,57 +11,3 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None: f"Path({fspath!r}) != {path!r}\n" "if both path and fspath are given they need to be equal" ) - - -class PathAwareHookProxy: - """ - this helper wraps around hook callers - until pluggy supports fixingcalls, this one will do - - it currently doesn't return full hook caller proxies for fixed hooks, - this may have to be changed later depending on bugs - """ - - def __init__(self, hook_relay: pluggy.HookRelay) -> None: - self._hook_relay = hook_relay - - def __dir__(self) -> list[str]: - return dir(self._hook_relay) - - def __getattr__(self, key: str) -> pluggy.HookCaller: - hook: pluggy.HookCaller = getattr(self._hook_relay, key) - if key not in imply_paths_hooks: - self.__dict__[key] = hook - return hook - else: - path_var, fspath_var = imply_paths_hooks[key] - - @functools.wraps(hook) - def fixed_hook(**kw): - path_value: Path | None = kw.pop(path_var, None) - fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None) - if fspath_value is not None: - warnings.warn( - HOOK_LEGACY_PATH_ARG.format( - pylib_path_arg=fspath_var, pathlib_path_arg=path_var - ), - stacklevel=2, - ) - if path_value is not None: - if fspath_value is not None: - _check_path(path_value, fspath_value) - else: - fspath_value = legacy_path(path_value) - else: - assert fspath_value is not None - path_value = Path(fspath_value) - - kw[path_var] = path_value - kw[fspath_var] = fspath_value - return hook(**kw) - - fixed_hook.name = hook.name # type: ignore[attr-defined] - fixed_hook.spec = hook.spec # type: ignore[attr-defined] - fixed_hook.__name__ = key - self.__dict__[key] = fixed_hook - return fixed_hook # type: ignore[return-value] diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 072da3211..5421f2320 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -35,13 +35,6 @@ YIELD_FIXTURE = PytestDeprecationWarning( PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -HOOK_LEGACY_PATH_ARG = UnformattedWarning( - PytestRemovedIn8Warning, - "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n" - "see https://docs.pytest.org/en/latest/deprecations.html" - "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", -) - NODE_CTOR_FSPATH_ARG = UnformattedWarning( PytestRemovedIn8Warning, "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 14f7f45fa..c1963b4d7 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -40,7 +40,6 @@ if TYPE_CHECKING: from _pytest.runner import CallInfo from _pytest.terminal import TerminalReporter from _pytest.terminal import TestShortLogReport - from _pytest.compat import LEGACY_PATH hookspec = HookspecMarker("pytest") @@ -246,9 +245,7 @@ def pytest_collection_finish(session: "Session") -> None: @hookspec(firstresult=True) -def pytest_ignore_collect( - collection_path: Path, path: "LEGACY_PATH", config: "Config" -) -> Optional[bool]: +def pytest_ignore_collect(collection_path: Path, config: "Config") -> Optional[bool]: """Return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling @@ -262,8 +259,10 @@ def pytest_ignore_collect( .. versionchanged:: 7.0.0 The ``collection_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``path`` parameter. The ``path`` parameter - has been deprecated. + equivalent of the ``path`` parameter. + + .. versionchanged:: 8.0.0 + The ``path`` parameter has been removed. """ @@ -288,9 +287,7 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle """ -def pytest_collect_file( - file_path: Path, path: "LEGACY_PATH", parent: "Collector" -) -> "Optional[Collector]": +def pytest_collect_file(file_path: Path, parent: "Collector") -> "Optional[Collector]": """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. For best results, the returned collector should be a subclass of @@ -303,8 +300,10 @@ def pytest_collect_file( .. versionchanged:: 7.0.0 The ``file_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``path`` parameter. The ``path`` parameter - has been deprecated. + equivalent of the ``path`` parameter. + + .. versionchanged:: 8.0.0 + The ``path`` parameter was removed. """ @@ -363,9 +362,7 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor @hookspec(firstresult=True) -def pytest_pycollect_makemodule( - module_path: Path, path: "LEGACY_PATH", parent -) -> Optional["Module"]: +def pytest_pycollect_makemodule(module_path: Path, parent) -> Optional["Module"]: """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. @@ -381,7 +378,8 @@ def pytest_pycollect_makemodule( The ``module_path`` parameter was added as a :class:`pathlib.Path` equivalent of the ``path`` parameter. - The ``path`` parameter has been deprecated in favor of ``fspath``. + .. versionchanged:: 8.0.0 + The ``path`` parameter has been removed in favor of ``module_path``. """ @@ -751,7 +749,7 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No def pytest_report_header( # type:ignore[empty-body] - config: "Config", start_path: Path, startdir: "LEGACY_PATH" + config: "Config", start_path: Path ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed as header info for terminal reporting. @@ -774,15 +772,16 @@ def pytest_report_header( # type:ignore[empty-body] .. versionchanged:: 7.0.0 The ``start_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``startdir`` parameter. The ``startdir`` parameter - has been deprecated. + equivalent of the ``startdir`` parameter. + + .. versionchanged:: 8.0.0 + The ``startdir`` parameter has been removed. """ def pytest_report_collectionfinish( # type:ignore[empty-body] config: "Config", start_path: Path, - startdir: "LEGACY_PATH", items: Sequence["Item"], ) -> Union[str, List[str]]: """Return a string or list of strings to be displayed after collection @@ -806,8 +805,10 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] .. versionchanged:: 7.0.0 The ``start_path`` parameter was added as a :class:`pathlib.Path` - equivalent of the ``startdir`` parameter. The ``startdir`` parameter - has been deprecated. + equivalent of the ``startdir`` parameter. + + .. versionchanged:: 8.0.0 + The ``startdir`` parameter has been removed. """ diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 3672df05a..83952c60c 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -33,7 +33,6 @@ from _pytest.config import hookimpl from _pytest.config import PytestPluginManager from _pytest.config import UsageError from _pytest.config.argparsing import Parser -from _pytest.config.compat import PathAwareHookProxy from _pytest.fixtures import FixtureManager from _pytest.outcomes import exit from _pytest.pathlib import absolutepath @@ -644,7 +643,7 @@ class Session(nodes.Collector): proxy: pluggy.HookRelay if remove_mods: # One or more conftests are not in use at this path. - proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods)) # type: ignore[arg-type,assignment] + proxy = FSHookProxy(pm, remove_mods) # type: ignore[arg-type,assignment] else: # All plugins are active for this fspath. proxy = self.config.hook diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 62314240f..5f7f4a2a6 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,6 +1,4 @@ import re -import sys -from pathlib import Path import pytest from _pytest import deprecated @@ -89,37 +87,6 @@ def test_private_is_deprecated() -> None: PrivateInit(10, _ispytest=True) -@pytest.mark.parametrize("hooktype", ["hook", "ihook"]) -def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request): - path = legacy_path(tmp_path) - - PATH_WARN_MATCH = r".*path: py\.path\.local\) argument is deprecated, please use \(collection_path: pathlib\.Path.*" - if hooktype == "ihook": - hooks = request.node.ihook - else: - hooks = request.config.hook - - with pytest.warns(PytestDeprecationWarning, match=PATH_WARN_MATCH) as r: - l1 = sys._getframe().f_lineno - hooks.pytest_ignore_collect( - config=request.config, path=path, collection_path=tmp_path - ) - l2 = sys._getframe().f_lineno - - (record,) = r - assert record.filename == __file__ - assert l1 < record.lineno < l2 - - hooks.pytest_ignore_collect(config=request.config, collection_path=tmp_path) - - # Passing entirely *different* paths is an outright error. - with pytest.raises(ValueError, match=r"path.*fspath.*need to be equal"): - with pytest.warns(PytestDeprecationWarning, match=PATH_WARN_MATCH) as r: - hooks.pytest_ignore_collect( - config=request.config, path=path, collection_path=Path("/bla/bla") - ) - - def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: mod = pytester.getmodulecol("") From 6c89f9261c6f5bde93bd116ef56b7ac96fc0ef21 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 16:45:17 +0200 Subject: [PATCH 052/104] Remove deprecated py.path (`fspath`) node constructor arguments --- doc/en/conf.py | 1 - doc/en/deprecations.rst | 79 ++++++++++++++++++------------------ src/_pytest/compat.py | 15 ------- src/_pytest/config/compat.py | 13 ------ src/_pytest/deprecated.py | 9 ---- src/_pytest/legacypath.py | 18 +++++++- src/_pytest/main.py | 1 - src/_pytest/nodes.py | 45 +++----------------- src/_pytest/python.py | 3 -- testing/deprecated_test.py | 22 ---------- testing/test_legacypath.py | 4 +- testing/test_nodes.py | 9 ++-- 12 files changed, 66 insertions(+), 153 deletions(-) delete mode 100644 src/_pytest/config/compat.py diff --git a/doc/en/conf.py b/doc/en/conf.py index d3a98015a..2bc18be58 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -199,7 +199,6 @@ nitpick_ignore = [ ("py:class", "_tracing.TagTracerSub"), ("py:class", "warnings.WarningMessage"), # Undocumented type aliases - ("py:class", "LEGACY_PATH"), ("py:class", "_PluggyPlugin"), # TypeVars ("py:class", "_pytest._code.code.E"), diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index c623f09ca..bcc195c60 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -19,45 +19,6 @@ Below is a complete list of all pytest features which are considered deprecated. :class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. -.. _node-ctor-fspath-deprecation: - -``fspath`` argument for Node constructors replaced with ``pathlib.Path`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 7.0 - -In order to support the transition from ``py.path.local`` to :mod:`pathlib`, -the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like -:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()` -is now deprecated. - -Plugins which construct nodes should pass the ``path`` argument, of type -:class:`pathlib.Path`, instead of the ``fspath`` argument. - -Plugins which implement custom items and collectors are encouraged to replace -``fspath`` parameters (``py.path.local``) with ``path`` parameters -(``pathlib.Path``), and drop any other usage of the ``py`` library if possible. - -If possible, plugins with custom items should use :ref:`cooperative -constructors ` to avoid hardcoding -arguments they only pass on to the superclass. - -.. note:: - The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the - new attribute being ``path``) is **the opposite** of the situation for - hooks, :ref:`outlined below ` (the old - argument being ``path``). - - This is an unfortunate artifact due to historical reasons, which should be - resolved in future versions as we slowly get rid of the :pypi:`py` - dependency (see :issue:`9283` for a longer discussion). - -Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo` -which still is expected to return a ``py.path.local`` object, nodes still have -both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes, -no matter what argument was used in the constructor. We expect to deprecate the -``fspath`` attribute in a future release. - .. _legacy-path-hooks-deprecated: Configuring hook specs/impls using markers @@ -251,6 +212,46 @@ an appropriate period of deprecation has passed. Some breaking changes which could not be deprecated are also listed. +.. _node-ctor-fspath-deprecation: + +``fspath`` argument for Node constructors replaced with ``pathlib.Path`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.0 + +In order to support the transition from ``py.path.local`` to :mod:`pathlib`, +the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like +:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()` +is now deprecated. + +Plugins which construct nodes should pass the ``path`` argument, of type +:class:`pathlib.Path`, instead of the ``fspath`` argument. + +Plugins which implement custom items and collectors are encouraged to replace +``fspath`` parameters (``py.path.local``) with ``path`` parameters +(``pathlib.Path``), and drop any other usage of the ``py`` library if possible. + +If possible, plugins with custom items should use :ref:`cooperative +constructors ` to avoid hardcoding +arguments they only pass on to the superclass. + +.. note:: + The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the + new attribute being ``path``) is **the opposite** of the situation for + hooks, :ref:`outlined below ` (the old + argument being ``path``). + + This is an unfortunate artifact due to historical reasons, which should be + resolved in future versions as we slowly get rid of the :pypi:`py` + dependency (see :issue:`9283` for a longer discussion). + +Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo` +which still is expected to return a ``py.path.local`` object, nodes still have +both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes, +no matter what argument was used in the constructor. We expect to deprecate the +``fspath`` attribute in a future release. + + ``py.path.local`` arguments for hooks replaced with ``pathlib.Path`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 73d77f978..1e9c38ca8 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -16,25 +16,10 @@ from typing import Final from typing import NoReturn from typing import TypeVar -import py - _T = TypeVar("_T") _S = TypeVar("_S") -#: constant to prepare valuing pylib path replacements/lazy proxies later on -# intended for removal in pytest 8.0 or 9.0 - -# fmt: off -# intentional space to create a fake difference for the verification -LEGACY_PATH = py.path. local -# fmt: on - - -def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: - """Internal wrapper to prepare lazy proxies for legacy_path instances""" - return LEGACY_PATH(path) - # fmt: off # Singleton type for NOTSET, as described in: diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py deleted file mode 100644 index 9c61b4dac..000000000 --- a/src/_pytest/config/compat.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from ..compat import LEGACY_PATH - - -def _check_path(path: Path, fspath: LEGACY_PATH) -> None: - if Path(fspath) != path: - raise ValueError( - f"Path({fspath!r}) != {path!r}\n" - "if both path and fspath are given they need to be equal" - ) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 5421f2320..1bc2cf57e 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -11,7 +11,6 @@ in case of warnings which need to format their messages. from warnings import warn from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import PytestRemovedIn9Warning from _pytest.warning_types import UnformattedWarning @@ -35,14 +34,6 @@ YIELD_FIXTURE = PytestDeprecationWarning( PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.") -NODE_CTOR_FSPATH_ARG = UnformattedWarning( - PytestRemovedIn8Warning, - "The (fspath: py.path.local) argument to {node_type_name} is deprecated. " - "Please use the (path: pathlib.Path) argument instead.\n" - "See https://docs.pytest.org/en/latest/deprecations.html" - "#fspath-argument-for-node-constructors-replaced-with-pathlib-path", -) - HOOK_LEGACY_MARKING = UnformattedWarning( PytestDeprecationWarning, "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n" diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 4876a083a..b2dd87436 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,5 +1,6 @@ """Add backward compatibility support for the legacy py path type.""" import dataclasses +import os import shlex import subprocess from pathlib import Path @@ -12,9 +13,8 @@ from typing import Union from iniconfig import SectionWrapper +import py from _pytest.cacheprovider import Cache -from _pytest.compat import LEGACY_PATH -from _pytest.compat import legacy_path from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config import PytestPluginManager @@ -36,6 +36,20 @@ if TYPE_CHECKING: import pexpect +#: constant to prepare valuing pylib path replacements/lazy proxies later on +# intended for removal in pytest 8.0 or 9.0 + +# fmt: off +# intentional space to create a fake difference for the verification +LEGACY_PATH = py.path. local +# fmt: on + + +def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: + """Internal wrapper to prepare lazy proxies for legacy_path instances""" + return LEGACY_PATH(path) + + @final class Testdir: """ diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 83952c60c..9fb96840e 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -541,7 +541,6 @@ class Session(nodes.Collector): super().__init__( name="", path=config.rootpath, - fspath=None, parent=None, config=config, session=self, diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index eefe690de..4cf6768e6 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,6 +1,5 @@ import abc import os -import pathlib import warnings from functools import cached_property from inspect import signature @@ -28,11 +27,8 @@ from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr from _pytest._code.code import Traceback -from _pytest.compat import LEGACY_PATH from _pytest.config import Config from _pytest.config import ConftestImportFailure -from _pytest.config.compat import _check_path -from _pytest.deprecated import NODE_CTOR_FSPATH_ARG from _pytest.mark.structures import Mark from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords @@ -98,27 +94,6 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]: yield nodeid -def _imply_path( - node_type: Type["Node"], - path: Optional[Path], - fspath: Optional[LEGACY_PATH], -) -> Path: - if fspath is not None: - warnings.warn( - NODE_CTOR_FSPATH_ARG.format( - node_type_name=node_type.__name__, - ), - stacklevel=6, - ) - if path is not None: - if fspath is not None: - _check_path(path, fspath) - return path - else: - assert fspath is not None - return Path(fspath) - - _NodeType = TypeVar("_NodeType", bound="Node") @@ -173,14 +148,6 @@ class Node(abc.ABC, metaclass=NodeMeta): ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the leaf nodes. """ - - # Implemented in the legacypath plugin. - #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage - #: for methods not migrated to ``pathlib.Path`` yet, such as - #: :meth:`Item.reportinfo `. Will be deprecated in - #: a future release, prefer using :attr:`path` instead. - fspath: LEGACY_PATH - # Use __slots__ to make attribute access faster. # Note that __dict__ is still available. __slots__ = ( @@ -200,7 +167,6 @@ class Node(abc.ABC, metaclass=NodeMeta): parent: "Optional[Node]" = None, config: Optional[Config] = None, session: "Optional[Session]" = None, - fspath: Optional[LEGACY_PATH] = None, path: Optional[Path] = None, nodeid: Optional[str] = None, ) -> None: @@ -226,10 +192,11 @@ class Node(abc.ABC, metaclass=NodeMeta): raise TypeError("session or parent must be provided") self.session = parent.session - if path is None and fspath is None: + if path is None: path = getattr(parent, "path", None) + assert path is not None #: Filesystem path where this node was collected from (can be None). - self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath) + self.path = path # The explicit annotation is to avoid publicly exposing NodeKeywords. #: Keywords/markers collected from all scopes. @@ -595,7 +562,6 @@ class FSCollector(Collector, abc.ABC): def __init__( self, - fspath: Optional[LEGACY_PATH] = None, path_or_parent: Optional[Union[Path, Node]] = None, path: Optional[Path] = None, name: Optional[str] = None, @@ -611,8 +577,8 @@ class FSCollector(Collector, abc.ABC): elif isinstance(path_or_parent, Path): assert path is None path = path_or_parent + assert path is not None - path = _imply_path(type(self), path, fspath=fspath) if name is None: name = path.name if parent is not None and parent.path != path: @@ -652,12 +618,11 @@ class FSCollector(Collector, abc.ABC): cls, parent, *, - fspath: Optional[LEGACY_PATH] = None, path: Optional[Path] = None, **kw, ): """The public constructor.""" - return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) + return super().from_parent(parent=parent, path=path, **kw) class File(FSCollector, abc.ABC): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 969bb6765..184399080 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -47,7 +47,6 @@ from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_async_function from _pytest.compat import is_generator -from _pytest.compat import LEGACY_PATH from _pytest.compat import NOTSET from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass @@ -672,7 +671,6 @@ class Package(nodes.Directory): def __init__( self, - fspath: Optional[LEGACY_PATH], parent: nodes.Collector, # NOTE: following args are unused: config=None, @@ -684,7 +682,6 @@ class Package(nodes.Directory): # super().__init__(self, fspath, parent=parent) session = parent.session super().__init__( - fspath=fspath, path=path, parent=parent, config=config, diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 5f7f4a2a6..ebff49ce6 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,8 +1,5 @@ -import re - import pytest from _pytest import deprecated -from _pytest.compat import legacy_path from _pytest.pytester import Pytester from pytest import PytestDeprecationWarning @@ -87,25 +84,6 @@ def test_private_is_deprecated() -> None: PrivateInit(10, _ispytest=True) -def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None: - mod = pytester.getmodulecol("") - - class MyFile(pytest.File): - def collect(self): - raise NotImplementedError() - - with pytest.warns( - pytest.PytestDeprecationWarning, - match=re.escape( - "The (fspath: py.path.local) argument to MyFile is deprecated." - ), - ): - MyFile.from_parent( - parent=mod.parent, - fspath=legacy_path("bla"), - ) - - def test_fixture_disallow_on_marked_functions(): """Test that applying @pytest.fixture to a marked function warns (#3364).""" with pytest.warns( diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index b4fd1bf2c..700499f24 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -1,8 +1,8 @@ from pathlib import Path import pytest -from _pytest.compat import LEGACY_PATH from _pytest.fixtures import TopRequest +from _pytest.legacypath import LEGACY_PATH from _pytest.legacypath import TempdirFactory from _pytest.legacypath import Testdir @@ -15,7 +15,7 @@ def test_item_fspath(pytester: pytest.Pytester) -> None: items2, hookrec = pytester.inline_genitems(item.nodeid) (item2,) = items2 assert item2.name == item.name - assert item2.fspath == item.fspath + assert item2.fspath == item.fspath # type: ignore[attr-defined] assert item2.path == item.path diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 84c377cf9..880e2a44f 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -7,7 +7,6 @@ from typing import Type import pytest from _pytest import nodes -from _pytest.compat import legacy_path from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester from _pytest.warning_types import PytestWarning @@ -69,9 +68,9 @@ def test_subclassing_both_item_and_collector_deprecated( warnings.simplefilter("error") class SoWrong(nodes.Item, nodes.File): - def __init__(self, fspath, parent): + def __init__(self, path, parent): """Legacy ctor with legacy call # don't wana see""" - super().__init__(fspath, parent) + super().__init__(parent, path) def collect(self): raise NotImplementedError() @@ -80,9 +79,7 @@ def test_subclassing_both_item_and_collector_deprecated( raise NotImplementedError() with pytest.warns(PytestWarning) as rec: - SoWrong.from_parent( - request.session, fspath=legacy_path(tmp_path / "broken.txt") - ) + SoWrong.from_parent(request.session, path=tmp_path / "broken.txt", wrong=10) messages = [str(x.message) for x in rec] assert any( re.search(".*SoWrong.* not using a cooperative constructor.*", x) From 215f4d1fab4fad11ea57fdd2b497d7cd3b46c7c1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 2 Jan 2024 12:21:08 +0200 Subject: [PATCH 053/104] Remove `PytestRemovedIn8Warning` Per our deprecation policy. --- doc/en/changelog.rst | 2 +- doc/en/reference/reference.rst | 3 --- src/_pytest/warning_types.py | 6 ------ src/_pytest/warnings.py | 3 ++- src/pytest/__init__.py | 2 -- testing/test_warnings.py | 9 ++++----- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index c097202ef..755f386c6 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -227,7 +227,7 @@ These are breaking changes where deprecation was not possible. Deprecations ------------ -- `#10465 `_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of :class:`pytest.PytestRemovedIn8Warning`, meaning this will stay a warning instead of becoming an error in the future. +- `#10465 `_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of ``pytest.PytestRemovedIn8Warning``, meaning this will stay a warning instead of becoming an error in the future. - `#3664 `_: Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index bd64bac41..3e1c5358d 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1207,9 +1207,6 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestReturnNotNoneWarning :show-inheritance: -.. autoclass:: pytest.PytestRemovedIn8Warning - :show-inheritance: - .. autoclass:: pytest.PytestRemovedIn9Warning :show-inheritance: diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 4219f1439..6c109b03f 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -49,12 +49,6 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): __module__ = "pytest" -class PytestRemovedIn8Warning(PytestDeprecationWarning): - """Warning class for features that will be removed in pytest 8.""" - - __module__ = "pytest" - - class PytestRemovedIn9Warning(PytestDeprecationWarning): """Warning class for features that will be removed in pytest 9.""" diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 6f20a872c..6ef4fafdc 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -46,7 +46,8 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.PytestRemovedIn8Warning) + # To be enabled in pytest 9.0.0. + # warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning) apply_warning_filters(config_filters, cmdline_filters) diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 5fab295c6..449cb39b8 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -73,7 +73,6 @@ from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning -from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import PytestRemovedIn9Warning from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning @@ -137,7 +136,6 @@ __all__ = [ "PytestConfigWarning", "PytestDeprecationWarning", "PytestExperimentalApiWarning", - "PytestRemovedIn8Warning", "PytestRemovedIn9Warning", "PytestReturnNotNoneWarning", "Pytester", diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 96ecad6f6..e7834dc4d 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -518,8 +518,7 @@ class TestDeprecationWarningsByDefault: assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -# In 8.1, uncomment below and change RemovedIn8 -> RemovedIn9. -# @pytest.mark.skip("not relevant until pytest 9.0") +@pytest.mark.skip("not relevant until pytest 9.0") @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None: """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors. @@ -531,7 +530,7 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No """ import warnings, pytest def test(): - warnings.warn(pytest.PytestRemovedIn8Warning("some warning")) + warnings.warn(pytest.PytestRemovedIn9Warning("some warning")) """ ) if change_default == "ini": @@ -539,12 +538,12 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No """ [pytest] filterwarnings = - ignore::pytest.PytestRemovedIn8Warning + ignore::pytest.PytestRemovedIn9Warning """ ) args = ( - ("-Wignore::pytest.PytestRemovedIn8Warning",) + ("-Wignore::pytest.PytestRemovedIn9Warning",) if change_default == "cmdline" else () ) From 1ba07450e427b7e291fd7c33278c0c087529a57d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 18:53:46 +0200 Subject: [PATCH 054/104] doc/deprecations: fix incorrect title level --- doc/en/deprecations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index bcc195c60..f5334ace5 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -136,7 +136,7 @@ conflicts (such as :class:`pytest.File` now taking ``path`` instead of deprecation warning is now raised. Applying a mark to a fixture function -------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 7.4 From 12b9bd580198edf88a795b692cbd6a1a05fe408a Mon Sep 17 00:00:00 2001 From: Ben Brown Date: Wed, 3 Jan 2024 12:39:24 -0500 Subject: [PATCH 055/104] Fix teardown error reporting when `--maxfail=1` (#11721) Co-authored-by: Ran Benita --- AUTHORS | 1 + changelog/11706.bugfix.rst | 1 + src/_pytest/main.py | 42 ++++++++++++++++++++++++-- src/_pytest/runner.py | 4 +++ testing/test_runner.py | 50 +++++++++++++++++++++++++++++++ testing/test_session.py | 60 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 changelog/11706.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 14a35c3d5..67e794527 100644 --- a/AUTHORS +++ b/AUTHORS @@ -54,6 +54,7 @@ Aviral Verma Aviv Palivoda Babak Keyvani Barney Gale +Ben Brown Ben Gartner Ben Webb Benjamin Peterson diff --git a/changelog/11706.bugfix.rst b/changelog/11706.bugfix.rst new file mode 100644 index 000000000..1b90d8f0b --- /dev/null +++ b/changelog/11706.bugfix.rst @@ -0,0 +1 @@ +Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 9fb96840e..51be84164 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -6,6 +6,7 @@ import functools import importlib import os import sys +import warnings from pathlib import Path from typing import AbstractSet from typing import Callable @@ -44,6 +45,7 @@ from _pytest.reports import CollectReport from _pytest.reports import TestReport from _pytest.runner import collect_one_node from _pytest.runner import SetupState +from _pytest.warning_types import PytestWarning def pytest_addoption(parser: Parser) -> None: @@ -548,8 +550,8 @@ class Session(nodes.Collector): ) self.testsfailed = 0 self.testscollected = 0 - self.shouldstop: Union[bool, str] = False - self.shouldfail: Union[bool, str] = False + self._shouldstop: Union[bool, str] = False + self._shouldfail: Union[bool, str] = False self.trace = config.trace.root.get("collection") self._initialpaths: FrozenSet[Path] = frozenset() self._initialpaths_with_parents: FrozenSet[Path] = frozenset() @@ -576,6 +578,42 @@ class Session(nodes.Collector): self.testscollected, ) + @property + def shouldstop(self) -> Union[bool, str]: + return self._shouldstop + + @shouldstop.setter + def shouldstop(self, value: Union[bool, str]) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldstop: + warnings.warn( + PytestWarning( + "session.shouldstop cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldstop = value + + @property + def shouldfail(self) -> Union[bool, str]: + return self._shouldfail + + @shouldfail.setter + def shouldfail(self, value: Union[bool, str]) -> None: + # The runner checks shouldfail and assumes that if it is set we are + # definitely stopping, so prevent unsetting it. + if value is False and self._shouldfail: + warnings.warn( + PytestWarning( + "session.shouldfail cannot be unset after it has been set; ignoring." + ), + stacklevel=2, + ) + return + self._shouldfail = value + @property def startpath(self) -> Path: """The path from which pytest was invoked. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e20338520..3e19f0de5 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -131,6 +131,10 @@ def runtestprotocol( show_test_item(item) if not item.config.getoption("setuponly", False): reports.append(call_and_report(item, "call", log)) + # If the session is about to fail or stop, teardown everything - this is + # necessary to correctly report fixture teardown errors (see #11706) + if item.session.shouldfail or item.session.shouldstop: + nextitem = None reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # After all teardown hooks have been called # want funcargs and request info to go away. diff --git a/testing/test_runner.py b/testing/test_runner.py index c8b646857..26f5b9a0b 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1087,3 +1087,53 @@ def test_outcome_exception_bad_msg() -> None: with pytest.raises(TypeError) as excinfo: OutcomeException(func) # type: ignore assert str(excinfo.value) == expected + + +def test_teardown_session_failed(pytester: Pytester) -> None: + """Test that higher-scoped fixture teardowns run in the context of the last + item after the test session bails early due to --maxfail. + + Regression test for #11706. + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def baz(): + yield + pytest.fail("This is a failing teardown") + + def test_foo(baz): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + result = pytester.runpytest("--maxfail=1") + result.assert_outcomes(failed=1, errors=1) + + +def test_teardown_session_stopped(pytester: Pytester) -> None: + """Test that higher-scoped fixture teardowns run in the context of the last + item after the test session bails early due to --stepwise. + + Regression test for #11706. + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def baz(): + yield + pytest.fail("This is a failing teardown") + + def test_foo(baz): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + result = pytester.runpytest("--stepwise") + result.assert_outcomes(failed=1, errors=1) diff --git a/testing/test_session.py b/testing/test_session.py index 136e85eb6..803bbed54 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -418,3 +418,63 @@ def test_rootdir_wrong_option_arg(pytester: Pytester) -> None: result.stderr.fnmatch_lines( ["*Directory *wrong_dir* not found. Check your '--rootdir' option.*"] ) + + +def test_shouldfail_is_sticky(pytester: Pytester) -> None: + """Test that session.shouldfail cannot be reset to False after being set. + + Issue #11706. + """ + pytester.makeconftest( + """ + def pytest_sessionfinish(session): + assert session.shouldfail + session.shouldfail = False + assert session.shouldfail + """ + ) + pytester.makepyfile( + """ + import pytest + + def test_foo(): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + + result = pytester.runpytest("--maxfail=1", "-Wall") + + result.assert_outcomes(failed=1, warnings=1) + result.stdout.fnmatch_lines("*session.shouldfail cannot be unset*") + + +def test_shouldstop_is_sticky(pytester: Pytester) -> None: + """Test that session.shouldstop cannot be reset to False after being set. + + Issue #11706. + """ + pytester.makeconftest( + """ + def pytest_sessionfinish(session): + assert session.shouldstop + session.shouldstop = False + assert session.shouldstop + """ + ) + pytester.makepyfile( + """ + import pytest + + def test_foo(): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + + result = pytester.runpytest("--stepwise", "-Wall") + + result.assert_outcomes(failed=1, warnings=1) + result.stdout.fnmatch_lines("*session.shouldstop cannot be unset*") From 5aa289e47865ab9c1ca4d1efbffa1fd1024b81f7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 31 Dec 2023 10:09:33 -0300 Subject: [PATCH 056/104] Improve GitHub release workflow This changes the existing script to just generate the release notes and delegate the actual publishing to the `softprops/action-gh-release@v1` action. This allows us to delete the custom code, which failed recently in https://github.com/pytest-dev/pytest/actions/runs/7370258570/job/20056477756. --- .github/workflows/deploy.yml | 13 +++-- scripts/.gitignore | 1 + ...-notes.py => generate-gh-release-notes.py} | 47 ++++--------------- tox.ini | 11 ++--- 4 files changed, 23 insertions(+), 49 deletions(-) create mode 100644 scripts/.gitignore rename scripts/{publish-gh-release-notes.py => generate-gh-release-notes.py} (56%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ca65662c1..fed725f0e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -82,9 +82,14 @@ jobs: python -m pip install --upgrade pip pip install --upgrade tox - - name: Publish GitHub release notes - env: - GH_RELEASE_NOTES_TOKEN: ${{ github.token }} + - name: Generate release notes run: | sudo apt-get install pandoc - tox -e publish-gh-release-notes + tox -e generate-gh-release-notes -- ${{ github.event.inputs.version }} scripts/latest-release-notes.md + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v1 + with: + body_path: scripts/latest-release-notes.md + files: dist/* + tag_name: ${{ github.event.inputs.version }} diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 000000000..50a75b629 --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +latest-release-notes.md diff --git a/scripts/publish-gh-release-notes.py b/scripts/generate-gh-release-notes.py similarity index 56% rename from scripts/publish-gh-release-notes.py rename to scripts/generate-gh-release-notes.py index 68cbd7adf..d30b8ef30 100644 --- a/scripts/publish-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -1,3 +1,4 @@ +# mypy:disallow-untyped-defs """ Script used to publish GitHub release notes extracted from CHANGELOG.rst. @@ -19,27 +20,18 @@ The script also requires ``pandoc`` to be previously installed in the system. Requires Python3.6+. """ -import os import re import sys from pathlib import Path -import github3 import pypandoc -def publish_github_release(slug, token, tag_name, body): - github = github3.login(token=token) - owner, repo = slug.split("/") - repo = github.repository(owner, repo) - return repo.create_release(tag_name=tag_name, body=body) - - def parse_changelog(tag_name): p = Path(__file__).parent.parent / "doc/en/changelog.rst" changelog_lines = p.read_text(encoding="UTF-8").splitlines() - title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)") + title_regex = re.compile(r"pytest (\d\.\d+\.\d+\w*) \(\d{4}-\d{2}-\d{2}\)") consuming_version = False version_lines = [] for line in changelog_lines: @@ -64,36 +56,17 @@ def convert_rst_to_md(text): def main(argv): - if len(argv) > 1: - tag_name = argv[1] - else: - tag_name = os.environ.get("GITHUB_REF") - if not tag_name: - print("tag_name not given and $GITHUB_REF not set", file=sys.stderr) - return 1 - if tag_name.startswith("refs/tags/"): - tag_name = tag_name[len("refs/tags/") :] + if len(argv) != 3: + print("Usage: generate-gh-release-notes VERSION FILE") + return 2 - token = os.environ.get("GH_RELEASE_NOTES_TOKEN") - if not token: - print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr) - return 1 - - slug = os.environ.get("GITHUB_REPOSITORY") - if not slug: - print("GITHUB_REPOSITORY not set", file=sys.stderr) - return 1 - - rst_body = parse_changelog(tag_name) + version, filename = argv[1:3] + print(f"Generating GitHub release notes for version {version}") + rst_body = parse_changelog(version) md_body = convert_rst_to_md(rst_body) - if not publish_github_release(slug, token, tag_name, md_body): - print("Could not publish release notes:", file=sys.stderr) - print(md_body, file=sys.stderr) - return 5 - + Path(filename).write_text(md_body, encoding="UTF-8") print() - print(f"Release notes for {tag_name} published successfully:") - print(f"https://github.com/{slug}/releases/tag/{tag_name}") + print(f"Done: {filename}") print() return 0 diff --git a/tox.ini b/tox.ini index c52a43fd7..e92f6c98b 100644 --- a/tox.ini +++ b/tox.ini @@ -177,18 +177,13 @@ passenv = {[testenv:release]passenv} deps = {[testenv:release]deps} commands = python scripts/prepare-release-pr.py {posargs} -[testenv:publish-gh-release-notes] -description = create GitHub release after deployment +[testenv:generate-gh-release-notes] +description = generate release notes that can be published as GitHub Release basepython = python3 usedevelop = True -passenv = - GH_RELEASE_NOTES_TOKEN - GITHUB_REF - GITHUB_REPOSITORY deps = - github3.py pypandoc -commands = python scripts/publish-gh-release-notes.py {posargs} +commands = python scripts/generate-gh-release-notes.py {posargs} [flake8] max-line-length = 120 From 6321b74faefef7c5dc267862eccc6a56f9a0ec61 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 31 Dec 2023 10:25:13 -0300 Subject: [PATCH 057/104] Enable type-checking in scripts/ --- .pre-commit-config.yaml | 3 ++- scripts/generate-gh-release-notes.py | 13 ++++++++----- scripts/prepare-release-pr.py | 1 + scripts/release.py | 25 ++++++++++++++----------- scripts/towncrier-draft-to-file.py | 7 ++++--- scripts/update-plugin-list.py | 27 +++++++++++++++++++++------ 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c328ad48..ce1cce6e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,7 +59,7 @@ repos: rev: v1.8.0 hooks: - id: mypy - files: ^(src/|testing/) + files: ^(src/|testing/|scripts/) args: [] additional_dependencies: - iniconfig>=1.1.0 @@ -67,6 +67,7 @@ repos: - packaging - tomli - types-pkg_resources + - types-tabulate # for mypy running on python>=3.11 since exceptiongroup is only a dependency # on <3.11 - exceptiongroup>=1.0.0rc8 diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py index d30b8ef30..8eddb8055 100644 --- a/scripts/generate-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -1,4 +1,4 @@ -# mypy:disallow-untyped-defs +# mypy: disallow-untyped-defs """ Script used to publish GitHub release notes extracted from CHANGELOG.rst. @@ -23,11 +23,12 @@ Requires Python3.6+. import re import sys from pathlib import Path +from typing import Sequence import pypandoc -def parse_changelog(tag_name): +def parse_changelog(tag_name: str) -> str: p = Path(__file__).parent.parent / "doc/en/changelog.rst" changelog_lines = p.read_text(encoding="UTF-8").splitlines() @@ -49,13 +50,15 @@ def parse_changelog(tag_name): return "\n".join(version_lines) -def convert_rst_to_md(text): - return pypandoc.convert_text( +def convert_rst_to_md(text: str) -> str: + result = pypandoc.convert_text( text, "md", format="rst", extra_args=["--wrap=preserve"] ) + assert isinstance(result, str), repr(result) + return result -def main(argv): +def main(argv: Sequence[str]) -> int: if len(argv) != 3: print("Usage: generate-gh-release-notes VERSION FILE") return 2 diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index 8ffa66964..ce8242a74 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -1,3 +1,4 @@ +# mypy: disallow-untyped-defs """ This script is part of the pytest release process which is triggered manually in the Actions tab of the repository. diff --git a/scripts/release.py b/scripts/release.py index 19fef4284..66617feb5 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,3 +1,4 @@ +# mypy: disallow-untyped-defs """Invoke development tasks.""" import argparse import os @@ -10,15 +11,15 @@ from colorama import Fore from colorama import init -def announce(version, template_name, doc_version): +def announce(version: str, template_name: str, doc_version: str) -> None: """Generates a new release announcement entry in the docs.""" # Get our list of authors - stdout = check_output(["git", "describe", "--abbrev=0", "--tags"]) - stdout = stdout.decode("utf-8") + stdout = check_output(["git", "describe", "--abbrev=0", "--tags"], encoding="UTF-8") last_version = stdout.strip() - stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"]) - stdout = stdout.decode("utf-8") + stdout = check_output( + ["git", "log", f"{last_version}..HEAD", "--format=%aN"], encoding="UTF-8" + ) contributors = { name @@ -61,7 +62,7 @@ def announce(version, template_name, doc_version): check_call(["git", "add", str(target)]) -def regen(version): +def regen(version: str) -> None: """Call regendoc tool to update examples and pytest output in the docs.""" print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs") check_call( @@ -70,7 +71,7 @@ def regen(version): ) -def fix_formatting(): +def fix_formatting() -> None: """Runs pre-commit in all files to ensure they are formatted correctly""" print( f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit" @@ -78,13 +79,15 @@ def fix_formatting(): call(["pre-commit", "run", "--all-files"]) -def check_links(): +def check_links() -> None: """Runs sphinx-build to check links""" print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links") check_call(["tox", "-e", "docs-checklinks"]) -def pre_release(version, template_name, doc_version, *, skip_check_links): +def pre_release( + version: str, template_name: str, doc_version: str, *, skip_check_links: bool +) -> None: """Generates new docs, release announcements and creates a local tag.""" announce(version, template_name, doc_version) regen(version) @@ -102,12 +105,12 @@ def pre_release(version, template_name, doc_version, *, skip_check_links): print("Please push your branch and open a PR.") -def changelog(version, write_out=False): +def changelog(version: str, write_out: bool = False) -> None: addopts = [] if write_out else ["--draft"] check_call(["towncrier", "--yes", "--version", version] + addopts) -def main(): +def main() -> None: init(autoreset=True) parser = argparse.ArgumentParser() parser.add_argument("version", help="Release version") diff --git a/scripts/towncrier-draft-to-file.py b/scripts/towncrier-draft-to-file.py index 1f1068689..7b2748aa8 100644 --- a/scripts/towncrier-draft-to-file.py +++ b/scripts/towncrier-draft-to-file.py @@ -1,11 +1,12 @@ +# mypy: disallow-untyped-defs import sys from subprocess import call -def main(): +def main() -> int: """ - Platform agnostic wrapper script for towncrier. - Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs. + Platform-agnostic wrapper script for towncrier. + Fixes the issue (#7251) where Windows users are unable to natively run tox -e docs to build pytest docs. """ with open( "doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8" diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index 46f22ad1e..0f811b778 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -1,8 +1,13 @@ +# mypy: disallow-untyped-defs import datetime import pathlib import re from textwrap import dedent from textwrap import indent +from typing import Any +from typing import Iterable +from typing import Iterator +from typing import TypedDict import packaging.version import platformdirs @@ -109,7 +114,17 @@ def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]: } -def iter_plugins(): +class PluginInfo(TypedDict): + """Relevant information about a plugin to generate the summary.""" + + name: str + summary: str + last_release: str + status: str + requires: str + + +def iter_plugins() -> Iterator[PluginInfo]: session = get_session() name_2_serial = pytest_plugin_projects_from_pypi(session) @@ -136,7 +151,7 @@ def iter_plugins(): requires = requirement break - def version_sort_key(version_string): + def version_sort_key(version_string: str) -> Any: """ Return the sort key for the given version string returned by the API. @@ -162,20 +177,20 @@ def iter_plugins(): yield { "name": name, "summary": summary.strip(), - "last release": last_release, + "last_release": last_release, "status": status, "requires": requires, } -def plugin_definitions(plugins): +def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]: """Return RST for the plugin list that fits better on a vertical page.""" for plugin in plugins: yield dedent( f""" {plugin['name']} - *last release*: {plugin["last release"]}, + *last release*: {plugin["last_release"]}, *status*: {plugin["status"]}, *requires*: {plugin["requires"]} @@ -184,7 +199,7 @@ def plugin_definitions(plugins): ) -def main(): +def main() -> None: plugins = [*iter_plugins()] reference_dir = pathlib.Path("doc", "en", "reference") From d38193646d872db966731234dcfc06ca995d07e4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 4 Jan 2024 07:29:20 -0300 Subject: [PATCH 058/104] Update docstring of scripts/generate-gh-release-notes.py (#11767) Follow up to #11754. --- scripts/generate-gh-release-notes.py | 30 +++++++++------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py index 8eddb8055..d22a5cf4c 100644 --- a/scripts/generate-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -1,22 +1,10 @@ # mypy: disallow-untyped-defs """ -Script used to publish GitHub release notes extracted from CHANGELOG.rst. +Script used to generate a Markdown file containing only the changelog entries of a specific pytest release, which +is then published as a GitHub Release during deploy (see workflows/deploy.yml). -This script is meant to be executed after a successful deployment in GitHub actions. - -Uses the following environment variables: - -* GIT_TAG: the name of the tag of the current commit. -* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. - - Create one at: - - https://github.com/settings/tokens - - This token should be set in a secret in the repository, which is exposed as an - environment variable in the main.yml workflow file. - -The script also requires ``pandoc`` to be previously installed in the system. +The script requires ``pandoc`` to be previously installed in the system -- we need to convert from RST (the format of +our CHANGELOG) into Markdown (which is required by GitHub Releases). Requires Python3.6+. """ @@ -28,7 +16,7 @@ from typing import Sequence import pypandoc -def parse_changelog(tag_name: str) -> str: +def extract_changelog_entries_for(version: str) -> str: p = Path(__file__).parent.parent / "doc/en/changelog.rst" changelog_lines = p.read_text(encoding="UTF-8").splitlines() @@ -38,10 +26,10 @@ def parse_changelog(tag_name: str) -> str: for line in changelog_lines: m = title_regex.match(line) if m: - # found the version we want: start to consume lines until we find the next version title - if m.group(1) == tag_name: + # Found the version we want: start to consume lines until we find the next version title. + if m.group(1) == version: consuming_version = True - # found a new version title while parsing the version we want: break out + # Found a new version title while parsing the version we want: break out. elif consuming_version: break if consuming_version: @@ -65,7 +53,7 @@ def main(argv: Sequence[str]) -> int: version, filename = argv[1:3] print(f"Generating GitHub release notes for version {version}") - rst_body = parse_changelog(version) + rst_body = extract_changelog_entries_for(version) md_body = convert_rst_to_md(rst_body) Path(filename).write_text(md_body, encoding="UTF-8") print() From ac96256272b6b806f1b036ed4476910b16dd7ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Thu, 4 Jan 2024 11:51:12 +0100 Subject: [PATCH 059/104] Fix a mistake in pytest.warns' docstring (expect_warning accepts tuples, not any sequence) --- src/_pytest/recwarn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 175d545b9..b3279dd31 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -113,7 +113,7 @@ def warns( # noqa: F811 ) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. - Specifically, the parameter ``expected_warning`` can be a warning class or sequence + Specifically, the parameter ``expected_warning`` can be a warning class or tuple of warning classes, and the code inside the ``with`` block must issue at least one warning of that class or classes. From 7b4ab8134ead2bd11d3e670b281d960c547e50f0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 23:03:24 +0200 Subject: [PATCH 060/104] fixtures: remove unnecessary `fspath` workaround --- src/_pytest/fixtures.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 89046ddd0..c5e466550 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1674,11 +1674,6 @@ class FixtureManager: self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): - # ugly workaround for one of the fspath deprecated property of node - # todo: safely generalize - if isinstance(holderobj, nodes.Node) and name == "fspath": - continue - # The attribute can be an arbitrary descriptor, so the attribute # access below can raise. safe_getatt() ignores such exceptions. obj = safe_getattr(holderobj, name, None) From 685e52ec30f87cd968fea28addf985925631b703 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 1 Jan 2024 23:09:33 +0200 Subject: [PATCH 061/104] nodes: fix attribute name `fspath` -> `path` in `get_fslocation_from_item` --- src/_pytest/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 4cf6768e6..112f9a149 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -488,7 +488,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i * "location": a pair (path, lineno) * "obj": a Python object that the node wraps. - * "fspath": just a path + * "path": just a path :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ @@ -499,7 +499,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i obj = getattr(node, "obj", None) if obj is not None: return getfslineno(obj) - return getattr(node, "fspath", "unknown location"), -1 + return getattr(node, "path", "unknown location"), -1 class Collector(Node, abc.ABC): From a616adf3aef6057203994852a8e0ae3cef54e0e3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 5 Jan 2024 13:18:10 +0200 Subject: [PATCH 062/104] unittest: inline `_make_xunit_fixture` The indirection makes things harder to follow in this case IMO. --- src/_pytest/unittest.py | 138 +++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 81 deletions(-) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 34845cec1..dee0c3e44 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -29,7 +29,6 @@ from _pytest.python import Class from _pytest.python import Function from _pytest.python import Module from _pytest.runner import CallInfo -from _pytest.scope import Scope if TYPE_CHECKING: import unittest @@ -71,7 +70,8 @@ class UnitTestCase(Class): skipped = _is_skipped(cls) if not skipped: - self._inject_setup_teardown_fixtures(cls) + self._inject_unittest_setup_method_fixture(cls) + self._inject_unittest_setup_class_fixture(cls) self._inject_setup_class_fixture() self.session._fixturemanager.parsefactories(self, unittest=True) @@ -93,91 +93,67 @@ class UnitTestCase(Class): if ut is None or runtest != ut.TestCase.runTest: # type: ignore yield TestCaseFunction.from_parent(self, name="runTest") - def _inject_setup_teardown_fixtures(self, cls: type) -> None: - """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding - teardown functions (#517).""" - class_fixture = _make_xunit_fixture( - cls, - "setUpClass", - "tearDownClass", - "doClassCleanups", - scope=Scope.Class, - pass_self=False, + def _inject_unittest_setup_class_fixture(self, cls: type) -> None: + """Injects a hidden auto-use fixture to invoke setUpClass and + tearDownClass (#517).""" + setup = getattr(cls, "setUpClass", None) + teardown = getattr(cls, "tearDownClass", None) + if setup is None and teardown is None: + return None + cleanup = getattr(cls, "doClassCleanups", lambda: None) + + @pytest.fixture( + scope="class", + autouse=True, + # Use a unique name to speed up lookup. + name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", ) - if class_fixture: - cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] - - method_fixture = _make_xunit_fixture( - cls, - "setup_method", - "teardown_method", - None, - scope=Scope.Function, - pass_self=True, - ) - if method_fixture: - cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] - - -def _make_xunit_fixture( - obj: type, - setup_name: str, - teardown_name: str, - cleanup_name: Optional[str], - scope: Scope, - pass_self: bool, -): - setup = getattr(obj, setup_name, None) - teardown = getattr(obj, teardown_name, None) - if setup is None and teardown is None: - return None - - if cleanup_name: - cleanup = getattr(obj, cleanup_name, lambda *args: None) - else: - - def cleanup(*args): - pass - - @pytest.fixture( - scope=scope.value, - autouse=True, - # Use a unique name to speed up lookup. - name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}", - ) - def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: - if _is_skipped(self): - reason = self.__unittest_skip_why__ - raise pytest.skip.Exception(reason, _use_item_location=True) - if setup is not None: - try: - if pass_self: - setup(self, request.function) - else: + def fixture(self) -> Generator[None, None, None]: + if _is_skipped(self): + reason = self.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + try: setup() - # unittest does not call the cleanup function for every BaseException, so we - # follow this here. - except Exception: - if pass_self: - cleanup(self) - else: + # unittest does not call the cleanup function for every BaseException, so we + # follow this here. + except Exception: cleanup() - - raise - yield - try: - if teardown is not None: - if pass_self: - teardown(self, request.function) - else: + raise + yield + try: + if teardown is not None: teardown() - finally: - if pass_self: - cleanup(self) - else: + finally: cleanup() - return fixture + cls.__pytest_class_setup = fixture # type: ignore[attr-defined] + + def _inject_unittest_setup_method_fixture(self, cls: type) -> None: + """Injects a hidden auto-use fixture to invoke setup_method and + teardown_method (#517).""" + setup = getattr(cls, "setup_method", None) + teardown = getattr(cls, "teardown_method", None) + if setup is None and teardown is None: + return None + + @pytest.fixture( + scope="function", + autouse=True, + # Use a unique name to speed up lookup. + name=f"_unittest_setup_method_fixture_{cls.__qualname__}", + ) + def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: + if _is_skipped(self): + reason = self.__unittest_skip_why__ + raise pytest.skip.Exception(reason, _use_item_location=True) + if setup is not None: + setup(self, request.function) + yield + if teardown is not None: + teardown(self, request.function) + + cls.__pytest_method_setup = fixture # type: ignore[attr-defined] class TestCaseFunction(Function): From 13eacdad8ac434b4336cf1e6496869815a6457c7 Mon Sep 17 00:00:00 2001 From: Fabian Sturm Date: Fri, 5 Jan 2024 13:59:19 +0100 Subject: [PATCH 063/104] Add summary for xfails with -rxX option (#11574) Co-authored-by: Brian Okken <1568356+okken@users.noreply.github.com> --- AUTHORS | 1 + changelog/11233.feature.rst | 5 ++ src/_pytest/terminal.py | 58 ++++++++++++------ testing/test_skipping.py | 2 +- testing/test_terminal.py | 119 ++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 changelog/11233.feature.rst diff --git a/AUTHORS b/AUTHORS index 67e794527..353489b6c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -138,6 +138,7 @@ Erik Hasse Erik M. Bray Evan Kepner Evgeny Seliverstov +Fabian Sturm Fabien Zarifian Fabio Zadrozny Felix Hofstätter diff --git a/changelog/11233.feature.rst b/changelog/11233.feature.rst new file mode 100644 index 000000000..c465def84 --- /dev/null +++ b/changelog/11233.feature.rst @@ -0,0 +1,5 @@ +Improvements to how ``-r`` for xfailures and xpasses: + +* Report tracebacks for xfailures when ``-rx`` is set. +* Report captured output for xpasses when ``-rX`` is set. +* For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index ea26d9368..b91a97221 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -878,8 +878,10 @@ class TerminalReporter: def pytest_terminal_summary(self) -> Generator[None, None, None]: self.summary_errors() self.summary_failures() + self.summary_xfailures() self.summary_warnings() self.summary_passes() + self.summary_xpasses() try: return (yield) finally: @@ -1009,12 +1011,20 @@ class TerminalReporter: ) def summary_passes(self) -> None: + self.summary_passes_combined("passed", "PASSES", "P") + + def summary_xpasses(self) -> None: + self.summary_passes_combined("xpassed", "XPASSES", "X") + + def summary_passes_combined( + self, which_reports: str, sep_title: str, needed_opt: str + ) -> None: if self.config.option.tbstyle != "no": - if self.hasopt("P"): - reports: List[TestReport] = self.getreports("passed") + if self.hasopt(needed_opt): + reports: List[TestReport] = self.getreports(which_reports) if not reports: return - self.write_sep("=", "PASSES") + self.write_sep("=", sep_title) for rep in reports: if rep.sections: msg = self._getfailureheadline(rep) @@ -1048,21 +1058,30 @@ class TerminalReporter: self._tw.line(content) def summary_failures(self) -> None: + self.summary_failures_combined("failed", "FAILURES") + + def summary_xfailures(self) -> None: + self.summary_failures_combined("xfailed", "XFAILURES", "x") + + def summary_failures_combined( + self, which_reports: str, sep_title: str, needed_opt: Optional[str] = None + ) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("failed") - if not reports: - return - self.write_sep("=", "FAILURES") - if self.config.option.tbstyle == "line": - for rep in reports: - line = self._getcrashline(rep) - self.write_line(line) - else: - for rep in reports: - msg = self._getfailureheadline(rep) - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) + if not needed_opt or self.hasopt(needed_opt): + reports: List[BaseReport] = self.getreports(which_reports) + if not reports: + return + self.write_sep("=", sep_title) + if self.config.option.tbstyle == "line": + for rep in reports: + line = self._getcrashline(rep) + self.write_line(line) + else: + for rep in reports: + msg = self._getfailureheadline(rep) + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) def summary_errors(self) -> None: if self.config.option.tbstyle != "no": @@ -1168,8 +1187,11 @@ class TerminalReporter: verbose_word, **{_color_for_type["warnings"]: True} ) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" reason = rep.wasxfail - lines.append(f"{markup_word} {nodeid} {reason}") + if reason: + line += " - " + str(reason) + lines.append(line) def show_skipped(lines: List[str]) -> None: skipped: List[CollectReport] = self.stats.get("skipped", []) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index a002ba6e8..86940baa6 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -649,7 +649,7 @@ class TestXFail: result.stdout.fnmatch_lines( [ "*test_strict_xfail*", - "XPASS test_strict_xfail.py::test_foo unsupported feature", + "XPASS test_strict_xfail.py::test_foo - unsupported feature", ] ) assert result.ret == (1 if strict else 0) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 20392096e..b521deea7 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2619,3 +2619,122 @@ def test_format_trimmed() -> None: assert _format_trimmed(" ({}) ", msg, len(msg) + 4) == " (unconditional skip) " assert _format_trimmed(" ({}) ", msg, len(msg) + 3) == " (unconditional ...) " + + +def test_summary_xfail_reason(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + assert False + + @pytest.mark.xfail(reason="foo") + def test_xfail_reason(): + assert False + """ + ) + result = pytester.runpytest("-rx") + expect1 = "XFAIL test_summary_xfail_reason.py::test_xfail" + expect2 = "XFAIL test_summary_xfail_reason.py::test_xfail_reason - foo" + result.stdout.fnmatch_lines([expect1, expect2]) + assert result.stdout.lines.count(expect1) == 1 + assert result.stdout.lines.count(expect2) == 1 + + +def test_summary_xfail_tb(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + a, b = 1, 2 + assert a == b + """ + ) + result = pytester.runpytest("-rx") + result.stdout.fnmatch_lines( + [ + "*= XFAILURES =*", + "*_ test_xfail _*", + "* @pytest.mark.xfail*", + "* def test_xfail():*", + "* a, b = 1, 2*", + "> *assert a == b*", + "E *assert 1 == 2*", + "test_summary_xfail_tb.py:6: AssertionError*", + "*= short test summary info =*", + "XFAIL test_summary_xfail_tb.py::test_xfail", + "*= 1 xfailed in * =*", + ] + ) + + +def test_xfail_tb_line(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_xfail(): + a, b = 1, 2 + assert a == b + """ + ) + result = pytester.runpytest("-rx", "--tb=line") + result.stdout.fnmatch_lines( + [ + "*= XFAILURES =*", + "*test_xfail_tb_line.py:6: assert 1 == 2", + "*= short test summary info =*", + "XFAIL test_xfail_tb_line.py::test_xfail", + "*= 1 xfailed in * =*", + ] + ) + + +def test_summary_xpass_reason(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_pass(): + ... + + @pytest.mark.xfail(reason="foo") + def test_reason(): + ... + """ + ) + result = pytester.runpytest("-rX") + expect1 = "XPASS test_summary_xpass_reason.py::test_pass" + expect2 = "XPASS test_summary_xpass_reason.py::test_reason - foo" + result.stdout.fnmatch_lines([expect1, expect2]) + assert result.stdout.lines.count(expect1) == 1 + assert result.stdout.lines.count(expect2) == 1 + + +def test_xpass_output(pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.mark.xfail + def test_pass(): + print('hi there') + """ + ) + result = pytester.runpytest("-rX") + result.stdout.fnmatch_lines( + [ + "*= XPASSES =*", + "*_ test_pass _*", + "*- Captured stdout call -*", + "*= short test summary info =*", + "XPASS test_xpass_output.py::test_pass*", + "*= 1 xpassed in * =*", + ] + ) From 5747a6c06ebc4823a48a2b66d0644f36dda3eee6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 7 Jan 2024 10:20:38 -0300 Subject: [PATCH 064/104] [automated] Update plugin list (#11784) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 160 +++++++++++++++++-------------- 1 file changed, 88 insertions(+), 72 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index a967a33bd..f1b672ecd 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,12 +27,12 @@ please refer to `the update script =7.3.0,<8.0.0) @@ -105,7 +105,7 @@ This list contains 1357 plugins. :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A - :pypi:`pytest-asyncio` Pytest support for asyncio Dec 09, 2023 4 - Beta pytest >=7.0.0 + :pypi:`pytest-asyncio` Pytest support for asyncio Jan 01, 2024 4 - Beta pytest >=7.0.0 :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Nov 30, 2023 N/A N/A :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) @@ -134,7 +134,7 @@ This list contains 1357 plugins. :pypi:`pytest-base-url` pytest plugin for URL based testing Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) :pypi:`pytest-bdd` BDD for pytest Dec 02, 2023 6 - Mature pytest (>=6.2.0) :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Nov 22, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) - :pypi:`pytest-bdd-ng` BDD for pytest Jul 01, 2023 4 - Beta pytest (>=5.0) + :pypi:`pytest-bdd-ng` BDD for pytest Dec 31, 2023 4 - Beta pytest >=5.0 :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports Nov 15, 2023 N/A pytest >=7.1.3 :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) @@ -190,7 +190,7 @@ This list contains 1357 plugins. :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Dec 28, 2023 N/A N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Jan 03, 2024 N/A N/A :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A @@ -198,7 +198,7 @@ This list contains 1357 plugins. :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) - :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Sep 22, 2023 N/A pytest + :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Dec 31, 2023 N/A pytest :pypi:`pytest-checkdocs` check the README when running tests Jul 30, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Dec 04, 2023 5 - Production/Stable pytest >=2.9.2 :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A @@ -306,6 +306,7 @@ This list contains 1357 plugins. :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) :pypi:`pytest-dbt-conventions` A pytest plugin for linting a dbt project's conventions Mar 02, 2022 N/A pytest (>=6.2.5,<7.0.0) :pypi:`pytest-dbt-core` Pytest extension for dbt. Aug 25, 2023 N/A pytest >=6.2.5 ; extra == 'test' + :pypi:`pytest-dbt-postgres` Pytest tooling to unittest DBT & Postgres models Jan 02, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A :pypi:`pytest-dbx` Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code Nov 29, 2022 N/A pytest (>=7.1.3,<8.0.0) :pypi:`pytest-dc` Manages Docker containers during your integration tests Aug 16, 2023 5 - Production/Stable pytest >=3.3 @@ -314,7 +315,7 @@ This list contains 1357 plugins. :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A :pypi:`pytest-defer` Aug 24, 2021 N/A N/A :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A - :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A + :pypi:`pytest-dependency` Manage dependencies of tests Dec 31, 2023 4 - Beta N/A :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A :pypi:`pytest-describe` Describe-style plugin for pytest Apr 09, 2023 5 - Production/Stable pytest (<8,>=4.6) @@ -338,6 +339,7 @@ This list contains 1357 plugins. :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A :pypi:`pytest-django-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A + :pypi:`pytest-django-docker-pg` Jan 05, 2024 5 - Production/Stable pytest >=7.0.0 :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A :pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2 @@ -369,7 +371,7 @@ This list contains 1357 plugins. :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Apr 08, 2022 4 - Beta pytest - :pypi:`pytest-docker-service` pytest plugin to start docker container Feb 22, 2023 3 - Alpha pytest (>=7.1.3) + :pypi:`pytest-docker-service` pytest plugin to start docker container Jan 03, 2024 3 - Alpha pytest (>=7.1.3) :pypi:`pytest-docker-squid-fixtures` Pytest fixtures for testing with squid. Feb 09, 2022 4 - Beta pytest :pypi:`pytest-docker-tools` Docker integration tests for pytest Feb 17, 2022 4 - Beta pytest (>=6.0.1) :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) @@ -410,14 +412,14 @@ This list contains 1357 plugins. :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Dec 29, 2023 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Dec 29, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Dec 29, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Jan 04, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Jan 04, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Jan 04, 2024 5 - Production/Stable N/A :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) @@ -546,7 +548,7 @@ This list contains 1357 plugins. :pypi:`pytest-github-report` Generate a GitHub report using pytest in GitHub Workflows Jun 03, 2022 4 - Beta N/A :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A :pypi:`pytest-gitlabci-parallelized` Parallelize pytest across GitLab CI workers. Mar 08, 2023 N/A N/A - :pypi:`pytest-gitlab-fold` Folds output sections in GitLab CI build log Sep 15, 2023 4 - Beta pytest >=2.6.0 + :pypi:`pytest-gitlab-fold` Folds output sections in GitLab CI build log Dec 31, 2023 4 - Beta pytest >=2.6.0 :pypi:`pytest-git-selector` Utility to select tests that have had its dependencies modified (as identified by git diff) Nov 17, 2022 N/A N/A :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Jul 22, 2022 4 - Beta pytest :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest @@ -577,7 +579,7 @@ This list contains 1357 plugins. :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 15, 2023 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hot-reloading` Dec 27, 2023 N/A N/A + :pypi:`pytest-hot-reloading` Jan 06, 2024 N/A N/A :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Dec 25, 2023 N/A pytest :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) @@ -587,7 +589,7 @@ This list contains 1357 plugins. :pypi:`pytest-html-cn` pytest plugin for generating HTML reports Aug 01, 2023 5 - Production/Stable N/A :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) :pypi:`pytest-html-merger` Pytest HTML reports merging utility Nov 11, 2023 N/A N/A - :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Mar 04, 2022 5 - Production/Stable N/A + :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Jan 05, 2024 5 - Production/Stable N/A :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Feb 13, 2022 N/A N/A :pypi:`pytest-html-report-merger` Oct 23, 2023 N/A N/A @@ -601,6 +603,7 @@ This list contains 1357 plugins. :pypi:`pytest-httptesting` http_testing framework on top of pytest Jul 24, 2023 N/A pytest (>=7.2.0,<8.0.0) :pypi:`pytest-httpx` Send responses to httpx. Dec 21, 2023 5 - Production/Stable pytest ==7.* :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) + :pypi:`pytest-httpx-recorder` Recorder feature based on pytest_httpx, like recorder feature in responses. Jan 04, 2024 5 - Production/Stable pytest :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A @@ -623,7 +626,7 @@ This list contains 1357 plugins. :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest - :pypi:`pytest-inmanta-extensions` Inmanta tests package Dec 11, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-extensions` Inmanta tests package Jan 04, 2024 5 - Production/Stable N/A :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Nov 29, 2023 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A @@ -634,14 +637,13 @@ This list contains 1357 plugins. :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest May 22, 2023 N/A pytest (>=5.2) :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Dec 05, 2023 4 - Beta pytest + :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. Jan 03, 2024 4 - Beta pytest :pypi:`pytest-invenio` Pytest fixtures for Invenio. Oct 31, 2023 5 - Production/Stable pytest <7.2.0,>=6 :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Mar 20, 2013 2 - Pre-Alpha N/A :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest :pypi:`pytest-isort` py.test plugin to check import ordering using isort Oct 31, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-is-running` pytest plugin providing a function to check if pytest is running. Jul 10, 2023 5 - Production/Stable N/A :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A :pypi:`pytest-iters` A contextmanager pytest fixture for handling multiple mock iters May 24, 2022 N/A N/A @@ -753,7 +755,7 @@ This list contains 1357 plugins. :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) - :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Dec 24, 2023 N/A pytest >=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Jan 04, 2024 N/A pytest >=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) @@ -930,7 +932,7 @@ This list contains 1357 plugins. :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A :pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 27, 2022 5 - Production/Stable pytest :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A - :pypi:`pytest-prysk` Pytest plugin for prysk Jul 18, 2023 4 - Beta pytest (>=7.3.2,<8.0.0) + :pypi:`pytest-prysk` Pytest plugin for prysk Dec 30, 2023 4 - Beta pytest (>=7.3.2,<8.0.0) :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) @@ -954,8 +956,8 @@ This list contains 1357 plugins. :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-pyreport` PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report Nov 03, 2023 N/A pytest :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Aug 20, 2023 4 - Beta pytest >=7.0.0 - :pypi:`pytest-pyspec` A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". Mar 12, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.0) - :pypi:`pytest-pystack` Plugin to run pystack after a timeout for a test suite. May 07, 2023 N/A pytest (>=3.5.0) + :pypi:`pytest-pyspec` A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". Jan 02, 2024 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-pystack` Plugin to run pystack after a timeout for a test suite. Jan 04, 2024 N/A pytest >=3.5.0 :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Feb 10, 2022 5 - Production/Stable pytest (<7,>=2.5.2) :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest @@ -1041,7 +1043,7 @@ This list contains 1357 plugins. :pypi:`pytest-result-sender` Apr 20, 2023 N/A pytest>=7.3.1 :pypi:`pytest-resume` A Pytest plugin to resuming from the last run test Apr 22, 2023 4 - Beta pytest (>=7.0) :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A - :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Oct 04, 2023 N/A pytest >=7.0.0 + :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Jan 04, 2024 N/A pytest >=7.0.0 :pypi:`pytest-retry-class` A pytest plugin to rerun entire class on failure Mar 25, 2023 N/A pytest (>=5.3) :pypi:`pytest-reusable-testcases` Apr 28, 2023 N/A N/A :pypi:`pytest-reverse` Pytest plugin to reverse test order. Jul 10, 2023 5 - Production/Stable pytest @@ -1075,7 +1077,7 @@ This list contains 1357 plugins. :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Dec 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jan 04, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) @@ -1084,7 +1086,7 @@ This list contains 1357 plugins. :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) :pypi:`pytest-selenium` pytest plugin for Selenium Nov 20, 2023 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Dec 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jan 04, 2024 5 - Production/Stable N/A :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A @@ -1112,7 +1114,7 @@ This list contains 1357 plugins. :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-skip-markers` Pytest Salt Plugin Oct 20, 2023 5 - Production/Stable pytest >=7.1.0 + :pypi:`pytest-skip-markers` Pytest Salt Plugin Jan 04, 2024 5 - Production/Stable pytest >=7.1.0 :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Feb 09, 2023 N/A pytest>=6.2.0 @@ -1149,7 +1151,7 @@ This list contains 1357 plugins. :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Nov 21, 2023 N/A pytest (>7.0) :pypi:`pytest-speed` Modern benchmarking library for python with pytest integration. Jan 22, 2023 3 - Alpha pytest>=7 :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Sep 06, 2022 4 - Beta pytest (>=7.0.0) - :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Feb 08, 2022 N/A N/A + :pypi:`pytest-spiratest` Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) Jan 01, 2024 N/A N/A :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0) :pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Jun 11, 2022 6 - Mature pytest (<8.0,>=7.1.2) :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Apr 12, 2023 4 - Beta pytest (>=5,<8) @@ -1263,7 +1265,7 @@ This list contains 1357 plugins. :pypi:`pytest-timer` A timer plugin for pytest Dec 26, 2023 N/A pytest :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Sep 11, 2023 N/A pytest (>=7.3,<8.0) - :pypi:`pytest-tiny-api-client` The companion pytest plugin for tiny-api-client Dec 28, 2023 5 - Production/Stable pytest + :pypi:`pytest-tiny-api-client` The companion pytest plugin for tiny-api-client Jan 04, 2024 5 - Production/Stable pytest :pypi:`pytest-tinybird` A pytest plugin to report test results to tinybird Jun 26, 2023 4 - Beta pytest (>=3.8.0) :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) @@ -1894,7 +1896,7 @@ This list contains 1357 plugins. Pytest fixtures for async generators :pypi:`pytest-asyncio` - *last release*: Dec 09, 2023, + *last release*: Jan 01, 2024, *status*: 4 - Beta, *requires*: pytest >=7.0.0 @@ -2097,9 +2099,9 @@ This list contains 1357 plugins. pytest plugin to display BDD info in HTML test report :pypi:`pytest-bdd-ng` - *last release*: Jul 01, 2023, + *last release*: Dec 31, 2023, *status*: 4 - Beta, - *requires*: pytest (>=5.0) + *requires*: pytest >=5.0 BDD for pytest @@ -2489,7 +2491,7 @@ This list contains 1357 plugins. Pytest plugin with server for catching HTTP requests. :pypi:`pytest-celery` - *last release*: Dec 28, 2023, + *last release*: Jan 03, 2024, *status*: N/A, *requires*: N/A @@ -2545,7 +2547,7 @@ This list contains 1357 plugins. A pytest fixture for changing current working directory :pypi:`pytest-check` - *last release*: Sep 22, 2023, + *last release*: Dec 31, 2023, *status*: N/A, *requires*: pytest @@ -3300,6 +3302,13 @@ This list contains 1357 plugins. Pytest extension for dbt. + :pypi:`pytest-dbt-postgres` + *last release*: Jan 02, 2024, + *status*: N/A, + *requires*: pytest (>=7.4.3,<8.0.0) + + Pytest tooling to unittest DBT & Postgres models + :pypi:`pytest-dbus-notification` *last release*: Mar 05, 2014, *status*: 5 - Production/Stable, @@ -3357,7 +3366,7 @@ This list contains 1357 plugins. pytest示例插件 :pypi:`pytest-dependency` - *last release*: Feb 14, 2020, + *last release*: Dec 31, 2023, *status*: 4 - Beta, *requires*: N/A @@ -3524,6 +3533,13 @@ This list contains 1357 plugins. A pytest plugin for running django in class-scoped fixtures + :pypi:`pytest-django-docker-pg` + *last release*: Jan 05, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest >=7.0.0 + + + :pypi:`pytest-django-dotenv` *last release*: Nov 26, 2019, *status*: 4 - Beta, @@ -3742,7 +3758,7 @@ This list contains 1357 plugins. Pytest fixtures for testing with docker registries. :pypi:`pytest-docker-service` - *last release*: Feb 22, 2023, + *last release*: Jan 03, 2024, *status*: 3 - Alpha, *requires*: pytest (>=7.1.3) @@ -4029,56 +4045,56 @@ This list contains 1357 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Dec 29, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -4981,7 +4997,7 @@ This list contains 1357 plugins. Parallelize pytest across GitLab CI workers. :pypi:`pytest-gitlab-fold` - *last release*: Sep 15, 2023, + *last release*: Dec 31, 2023, *status*: 4 - Beta, *requires*: pytest >=2.6.0 @@ -5198,7 +5214,7 @@ This list contains 1357 plugins. Report on tests that honor constraints, and guard against regressions :pypi:`pytest-hot-reloading` - *last release*: Dec 27, 2023, + *last release*: Jan 06, 2024, *status*: N/A, *requires*: N/A @@ -5268,7 +5284,7 @@ This list contains 1357 plugins. Pytest HTML reports merging utility :pypi:`pytest-html-object-storage` - *last release*: Mar 04, 2022, + *last release*: Jan 05, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -5365,6 +5381,13 @@ This list contains 1357 plugins. Disable httpx requests during a test run + :pypi:`pytest-httpx-recorder` + *last release*: Jan 04, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest + + Recorder feature based on pytest_httpx, like recorder feature in responses. + :pypi:`pytest-hue` *last release*: May 09, 2019, *status*: N/A, @@ -5520,7 +5543,7 @@ This list contains 1357 plugins. A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: Dec 11, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -5597,7 +5620,7 @@ This list contains 1357 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. :pypi:`pytest-interface-tester` - *last release*: Dec 05, 2023, + *last release*: Jan 03, 2024, *status*: 4 - Beta, *requires*: pytest @@ -5645,13 +5668,6 @@ This list contains 1357 plugins. py.test plugin to check import ordering using isort - :pypi:`pytest-is-running` - *last release*: Jul 10, 2023, - *status*: 5 - Production/Stable, - *requires*: N/A - - pytest plugin providing a function to check if pytest is running. - :pypi:`pytest-it` *last release*: Jan 22, 2020, *status*: 4 - Beta, @@ -6430,7 +6446,7 @@ This list contains 1357 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: Dec 24, 2023, + *last release*: Jan 04, 2024, *status*: N/A, *requires*: pytest >=5.0.0 @@ -7669,7 +7685,7 @@ This list contains 1357 plugins. Test helpers for Prosper projects :pypi:`pytest-prysk` - *last release*: Jul 18, 2023, + *last release*: Dec 30, 2023, *status*: 4 - Beta, *requires*: pytest (>=7.3.2,<8.0.0) @@ -7837,16 +7853,16 @@ This list contains 1357 plugins. Pytest plugin for type checking code with Pyright :pypi:`pytest-pyspec` - *last release*: Mar 12, 2023, - *status*: 5 - Production/Stable, + *last release*: Jan 02, 2024, + *status*: N/A, *requires*: pytest (>=7.2.1,<8.0.0) A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". :pypi:`pytest-pystack` - *last release*: May 07, 2023, + *last release*: Jan 04, 2024, *status*: N/A, - *requires*: pytest (>=3.5.0) + *requires*: pytest >=3.5.0 Plugin to run pystack after a timeout for a test suite. @@ -8446,7 +8462,7 @@ This list contains 1357 plugins. A RethinkDB plugin for pytest. :pypi:`pytest-retry` - *last release*: Oct 04, 2023, + *last release*: Jan 04, 2024, *status*: N/A, *requires*: pytest >=7.0.0 @@ -8684,7 +8700,7 @@ This list contains 1357 plugins. :pypi:`pytest-sbase` - *last release*: Dec 23, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -8747,7 +8763,7 @@ This list contains 1357 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Dec 23, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -8943,7 +8959,7 @@ This list contains 1357 plugins. Allow for multiple processes to log to a single file :pypi:`pytest-skip-markers` - *last release*: Oct 20, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: pytest >=7.1.0 @@ -9202,11 +9218,11 @@ This list contains 1357 plugins. Doctest plugin for pytest with support for Sphinx-specific doctest-directives :pypi:`pytest-spiratest` - *last release*: Feb 08, 2022, + *last release*: Jan 01, 2024, *status*: N/A, *requires*: N/A - Exports unit tests as test runs in SpiraTest/Team/Plan + Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) :pypi:`pytest-splinter` *last release*: Sep 09, 2022, @@ -10000,7 +10016,7 @@ This list contains 1357 plugins. A simple plugin to view timestamps for each test :pypi:`pytest-tiny-api-client` - *last release*: Dec 28, 2023, + *last release*: Jan 04, 2024, *status*: 5 - Production/Stable, *requires*: pytest From 3234c79ee57feb7ec481811af1925bb0f81acdea Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 4 Jan 2024 23:48:03 +0200 Subject: [PATCH 065/104] fixtures: add an internal API for registering a fixture Add a function on the `FixtureManager` to register a fixture with pytest. Currently this can only be done through `parsefactories`. My aim is to eventually make something like this available to plugins, as it's a pretty common need. --- src/_pytest/fixtures.py | 93 ++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c5e466550..d4b780caf 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1621,6 +1621,69 @@ class FixtureManager: # Separate parametrized setups. items[:] = reorder_items(items) + def _register_fixture( + self, + *, + name: str, + func: "_FixtureFunc[object]", + nodeid: Optional[str], + scope: Union[ + Scope, _ScopeName, Callable[[str, Config], _ScopeName], None + ] = "function", + params: Optional[Sequence[object]] = None, + ids: Optional[ + Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] + ] = None, + autouse: bool = False, + unittest: bool = False, + ) -> None: + """Register a fixture + + :param name: + The fixture's name. + :param func: + The fixture's implementation function. + :param nodeid: + The visibility of the fixture. The fixture will be available to the + node with this nodeid and its children in the collection tree. + None means that the fixture is visible to the entire collection tree, + e.g. a fixture defined for general use in a plugin. + :param scope: + The fixture's scope. + :param params: + The fixture's parametrization params. + :param ids: + The fixture's IDs. + :param autouse: + Whether this is an autouse fixture. + :param unittest: + Set this if this is a unittest fixture. + """ + fixture_def = FixtureDef( + fixturemanager=self, + baseid=nodeid, + argname=name, + func=func, + scope=scope, + params=params, + unittest=unittest, + ids=ids, + _ispytest=True, + ) + + faclist = self._arg2fixturedefs.setdefault(name, []) + if fixture_def.has_location: + faclist.append(fixture_def) + else: + # fixturedefs with no location are at the front + # so this inserts the current fixturedef after the + # existing fixturedefs from external plugins but + # before the fixturedefs provided in conftests. + i = len([f for f in faclist if not f.has_location]) + faclist.insert(i, fixture_def) + if autouse: + self._nodeid_autousenames.setdefault(nodeid or "", []).append(name) + @overload def parsefactories( self, @@ -1672,7 +1735,6 @@ class FixtureManager: return self._holderobjseen.add(holderobj) - autousenames = [] for name in dir(holderobj): # The attribute can be an arbitrary descriptor, so the attribute # access below can raise. safe_getatt() ignores such exceptions. @@ -1690,36 +1752,19 @@ class FixtureManager: # to issue a warning if called directly, so here we unwrap it in # order to not emit the warning when pytest itself calls the # fixture function. - obj = get_real_method(obj, holderobj) + func = get_real_method(obj, holderobj) - fixture_def = FixtureDef( - fixturemanager=self, - baseid=nodeid, - argname=name, - func=obj, + self._register_fixture( + name=name, + nodeid=nodeid, + func=func, scope=marker.scope, params=marker.params, unittest=unittest, ids=marker.ids, - _ispytest=True, + autouse=marker.autouse, ) - faclist = self._arg2fixturedefs.setdefault(name, []) - if fixture_def.has_location: - faclist.append(fixture_def) - else: - # fixturedefs with no location are at the front - # so this inserts the current fixturedef after the - # existing fixturedefs from external plugins but - # before the fixturedefs provided in conftests. - i = len([f for f in faclist if not f.has_location]) - faclist.insert(i, fixture_def) - if marker.autouse: - autousenames.append(name) - - if autousenames: - self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames) - def getfixturedefs( self, argname: str, nodeid: str ) -> Optional[Sequence[FixtureDef[Any]]]: From c8792bd0800b8ffc536a6ce251f9eb3075b5f5fa Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 4 Jan 2024 23:51:58 +0200 Subject: [PATCH 066/104] python,unittest: replace obj fixture patching with `FixtureManager._register_fixture` Instead of modifying user objects like modules and classes that we really shouldn't be touching, use the new `_register_fixture` internal API to do it directly. --- src/_pytest/python.py | 108 +++++++++++++++++++++------------------- src/_pytest/unittest.py | 58 +++++++++++---------- 2 files changed, 91 insertions(+), 75 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 184399080..aa134020f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -582,13 +582,13 @@ class Module(nodes.File, PyCollector): return importtestmodule(self.path, self.config) def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: - self._inject_setup_module_fixture() - self._inject_setup_function_fixture() + self._register_setup_module_fixture() + self._register_setup_function_fixture() self.session._fixturemanager.parsefactories(self) return super().collect() - def _inject_setup_module_fixture(self) -> None: - """Inject a hidden autouse, module scoped fixture into the collected module object + def _register_setup_module_fixture(self) -> None: + """Register an autouse, module-scoped fixture for the collected module object that invokes setUpModule/tearDownModule if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with @@ -604,23 +604,25 @@ class Module(nodes.File, PyCollector): if setup_module is None and teardown_module is None: return - @fixtures.fixture( - autouse=True, - scope="module", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_module_fixture_{self.obj.__name__}", - ) def xunit_setup_module_fixture(request) -> Generator[None, None, None]: + module = request.module if setup_module is not None: - _call_with_optional_argument(setup_module, request.module) + _call_with_optional_argument(setup_module, module) yield if teardown_module is not None: - _call_with_optional_argument(teardown_module, request.module) + _call_with_optional_argument(teardown_module, module) - self.obj.__pytest_setup_module = xunit_setup_module_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_module_fixture_{self.obj.__name__}", + func=xunit_setup_module_fixture, + nodeid=self.nodeid, + scope="module", + autouse=True, + ) - def _inject_setup_function_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected module object + def _register_setup_function_fixture(self) -> None: + """Register an autouse, function-scoped fixture for the collected module object that invokes setup_function/teardown_function if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with @@ -633,25 +635,27 @@ class Module(nodes.File, PyCollector): if setup_function is None and teardown_function is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_function_fixture_{self.obj.__name__}", - ) def xunit_setup_function_fixture(request) -> Generator[None, None, None]: if request.instance is not None: # in this case we are bound to an instance, so we need to let # setup_method handle this yield return + function = request.function if setup_function is not None: - _call_with_optional_argument(setup_function, request.function) + _call_with_optional_argument(setup_function, function) yield if teardown_function is not None: - _call_with_optional_argument(teardown_function, request.function) + _call_with_optional_argument(teardown_function, function) - self.obj.__pytest_setup_function = xunit_setup_function_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_function_fixture_{self.obj.__name__}", + func=xunit_setup_function_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) class Package(nodes.Directory): @@ -795,15 +799,15 @@ class Class(PyCollector): ) return [] - self._inject_setup_class_fixture() - self._inject_setup_method_fixture() + self._register_setup_class_fixture() + self._register_setup_method_fixture() self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid) return super().collect() - def _inject_setup_class_fixture(self) -> None: - """Inject a hidden autouse, class scoped fixture into the collected class object + def _register_setup_class_fixture(self) -> None: + """Register an autouse, class scoped fixture into the collected class object that invokes setup_class/teardown_class if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with @@ -814,25 +818,27 @@ class Class(PyCollector): if setup_class is None and teardown_class is None: return - @fixtures.fixture( - autouse=True, - scope="class", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: + def xunit_setup_class_fixture(request) -> Generator[None, None, None]: + cls = request.cls if setup_class is not None: func = getimfunc(setup_class) - _call_with_optional_argument(func, self.obj) + _call_with_optional_argument(func, cls) yield if teardown_class is not None: func = getimfunc(teardown_class) - _call_with_optional_argument(func, self.obj) + _call_with_optional_argument(func, cls) - self.obj.__pytest_setup_class = xunit_setup_class_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}", + func=xunit_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) - def _inject_setup_method_fixture(self) -> None: - """Inject a hidden autouse, function scoped fixture into the collected class object + def _register_setup_method_fixture(self) -> None: + """Register an autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with @@ -845,23 +851,25 @@ class Class(PyCollector): if setup_method is None and teardown_method is None: return - @fixtures.fixture( - autouse=True, - scope="function", - # Use a unique name to speed up lookup. - name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", - ) - def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: + def xunit_setup_method_fixture(request) -> Generator[None, None, None]: + instance = request.instance method = request.function if setup_method is not None: - func = getattr(self, setup_name) + func = getattr(instance, setup_name) _call_with_optional_argument(func, method) yield if teardown_method is not None: - func = getattr(self, teardown_name) + func = getattr(instance, teardown_name) _call_with_optional_argument(func, method) - self.obj.__pytest_setup_method = xunit_setup_method_fixture + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}", + func=xunit_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) def hasinit(obj: object) -> bool: diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index dee0c3e44..6bf8f4f2f 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -70,9 +70,9 @@ class UnitTestCase(Class): skipped = _is_skipped(cls) if not skipped: - self._inject_unittest_setup_method_fixture(cls) - self._inject_unittest_setup_class_fixture(cls) - self._inject_setup_class_fixture() + self._register_unittest_setup_method_fixture(cls) + self._register_unittest_setup_class_fixture(cls) + self._register_setup_class_fixture() self.session._fixturemanager.parsefactories(self, unittest=True) loader = TestLoader() @@ -93,8 +93,8 @@ class UnitTestCase(Class): if ut is None or runtest != ut.TestCase.runTest: # type: ignore yield TestCaseFunction.from_parent(self, name="runTest") - def _inject_unittest_setup_class_fixture(self, cls: type) -> None: - """Injects a hidden auto-use fixture to invoke setUpClass and + def _register_unittest_setup_class_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setUpClass and tearDownClass (#517).""" setup = getattr(cls, "setUpClass", None) teardown = getattr(cls, "tearDownClass", None) @@ -102,15 +102,12 @@ class UnitTestCase(Class): return None cleanup = getattr(cls, "doClassCleanups", lambda: None) - @pytest.fixture( - scope="class", - autouse=True, - # Use a unique name to speed up lookup. - name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", - ) - def fixture(self) -> Generator[None, None, None]: - if _is_skipped(self): - reason = self.__unittest_skip_why__ + def unittest_setup_class_fixture( + request: FixtureRequest, + ) -> Generator[None, None, None]: + cls = request.cls + if _is_skipped(cls): + reason = cls.__unittest_skip_why__ raise pytest.skip.Exception(reason, _use_item_location=True) if setup is not None: try: @@ -127,23 +124,27 @@ class UnitTestCase(Class): finally: cleanup() - cls.__pytest_class_setup = fixture # type: ignore[attr-defined] + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setUpClass_fixture_{cls.__qualname__}", + func=unittest_setup_class_fixture, + nodeid=self.nodeid, + scope="class", + autouse=True, + ) - def _inject_unittest_setup_method_fixture(self, cls: type) -> None: - """Injects a hidden auto-use fixture to invoke setup_method and + def _register_unittest_setup_method_fixture(self, cls: type) -> None: + """Register an auto-use fixture to invoke setup_method and teardown_method (#517).""" setup = getattr(cls, "setup_method", None) teardown = getattr(cls, "teardown_method", None) if setup is None and teardown is None: return None - @pytest.fixture( - scope="function", - autouse=True, - # Use a unique name to speed up lookup. - name=f"_unittest_setup_method_fixture_{cls.__qualname__}", - ) - def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: + def unittest_setup_method_fixture( + request: FixtureRequest, + ) -> Generator[None, None, None]: + self = request.instance if _is_skipped(self): reason = self.__unittest_skip_why__ raise pytest.skip.Exception(reason, _use_item_location=True) @@ -153,7 +154,14 @@ class UnitTestCase(Class): if teardown is not None: teardown(self, request.function) - cls.__pytest_method_setup = fixture # type: ignore[attr-defined] + self.session._fixturemanager._register_fixture( + # Use a unique name to speed up lookup. + name=f"_unittest_setup_method_fixture_{cls.__qualname__}", + func=unittest_setup_method_fixture, + nodeid=self.nodeid, + scope="function", + autouse=True, + ) class TestCaseFunction(Function): From 992d0f082f43c06f6a50bf9c863eba2d221d2272 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 6 Jan 2024 13:12:02 +0200 Subject: [PATCH 067/104] fixtures: match fixtures based on actual node hierarchy, not textual nodeids Refs #11662. --- Problem Each fixture definition has a "visibility", the `FixtureDef.baseid` attribute. This is nodeid-like string. When a certain `node` requests a certain fixture name, we match node's nodeid against the fixture definitions with this name. The matching currently happens on the *textual* representation of the nodeid - we split `node.nodeid` to its "parent nodeids" and then check if the fixture's `baseid` is in there. While this has worked so far, we really should try to avoid textual manipulation of nodeids as much as possible. It has also caused problem in an odd case of a `Package` in the root directory: the `Package` gets nodeid `.`, while a `Module` in it gets nodeid `test_module.py`. And textually, `.` is not a parent of `test_module.py`. --- Solution Avoid this entirely by just checking the node hierarchy itself. This is made possible by the fact that we now have proper `Directory` nodes (`Dir` or `Package`) for the entire hierarchy. Also do the same for `_getautousenames` which is a similar deal. The `iterparentnodeids` function is no longer used and is removed. --- changelog/11785.trivial.rst | 7 +++ src/_pytest/fixtures.py | 37 +++++++-------- src/_pytest/nodes.py | 50 +++----------------- testing/plugins_integration/requirements.txt | 4 +- testing/python/fixtures.py | 6 +-- testing/test_nodes.py | 24 ---------- tox.ini | 4 +- 7 files changed, 40 insertions(+), 92 deletions(-) create mode 100644 changelog/11785.trivial.rst diff --git a/changelog/11785.trivial.rst b/changelog/11785.trivial.rst new file mode 100644 index 000000000..b6b74d0da --- /dev/null +++ b/changelog/11785.trivial.rst @@ -0,0 +1,7 @@ +Some changes were made to private functions which may affect plugins which access them: + +- ``FixtureManager._getautousenames()`` now takes a ``Node`` itself instead of the nodeid. +- ``FixtureManager.getfixturedefs()`` now takes the ``Node`` itself instead of the nodeid. +- The ``_pytest.nodes.iterparentnodeids()`` function is removed without replacement. + Prefer to traverse the node hierarchy itself instead. + If you really need to, copy the function from the previous pytest release. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 89046ddd0..a693867cb 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -424,9 +424,9 @@ class FixtureRequest(abc.ABC): # We arrive here because of a dynamic call to # getfixturevalue(argname) usage which was naturally # not known at parsing/collection time. - assert self._pyfuncitem.parent is not None - parentid = self._pyfuncitem.parent.nodeid - fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) + parent = self._pyfuncitem.parent + assert parent is not None + fixturedefs = self._fixturemanager.getfixturedefs(argname, parent) if fixturedefs is not None: self._arg2fixturedefs[argname] = fixturedefs # No fixtures defined with this name. @@ -846,9 +846,8 @@ class FixtureLookupError(LookupError): available = set() parent = self.request._pyfuncitem.parent assert parent is not None - parentid = parent.nodeid for name, fixturedefs in fm._arg2fixturedefs.items(): - faclist = list(fm._matchfactories(fixturedefs, parentid)) + faclist = list(fm._matchfactories(fixturedefs, parent)) if faclist: available.add(name) if self.argname in available: @@ -989,9 +988,8 @@ class FixtureDef(Generic[FixtureValue]): # The "base" node ID for the fixture. # # This is a node ID prefix. A fixture is only available to a node (e.g. - # a `Function` item) if the fixture's baseid is a parent of the node's - # nodeid (see the `iterparentnodeids` function for what constitutes a - # "parent" and a "prefix" in this context). + # a `Function` item) if the fixture's baseid is a nodeid of a parent of + # node. # # For a fixture found in a Collector's object (e.g. a `Module`s module, # a `Class`'s class), the baseid is the Collector's nodeid. @@ -1482,7 +1480,7 @@ class FixtureManager: else: argnames = () usefixturesnames = self._getusefixturesnames(node) - autousenames = self._getautousenames(node.nodeid) + autousenames = self._getautousenames(node) initialnames = deduplicate_names(autousenames, usefixturesnames, argnames) direct_parametrize_args = _get_direct_parametrize_args(node) @@ -1517,10 +1515,10 @@ class FixtureManager: self.parsefactories(plugin, nodeid) - def _getautousenames(self, nodeid: str) -> Iterator[str]: - """Return the names of autouse fixtures applicable to nodeid.""" - for parentnodeid in nodes.iterparentnodeids(nodeid): - basenames = self._nodeid_autousenames.get(parentnodeid) + def _getautousenames(self, node: nodes.Node) -> Iterator[str]: + """Return the names of autouse fixtures applicable to node.""" + for parentnode in reversed(list(nodes.iterparentnodes(node))): + basenames = self._nodeid_autousenames.get(parentnode.nodeid) if basenames: yield from basenames @@ -1542,7 +1540,6 @@ class FixtureManager: # to re-discover fixturedefs again for each fixturename # (discovering matching fixtures for a given name/node is expensive). - parentid = parentnode.nodeid fixturenames_closure = list(initialnames) arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} @@ -1554,7 +1551,7 @@ class FixtureManager: continue if argname in arg2fixturedefs: continue - fixturedefs = self.getfixturedefs(argname, parentid) + fixturedefs = self.getfixturedefs(argname, parentnode) if fixturedefs: arg2fixturedefs[argname] = fixturedefs for arg in fixturedefs[-1].argnames: @@ -1726,7 +1723,7 @@ class FixtureManager: self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames) def getfixturedefs( - self, argname: str, nodeid: str + self, argname: str, node: nodes.Node ) -> Optional[Sequence[FixtureDef[Any]]]: """Get FixtureDefs for a fixture name which are applicable to a given node. @@ -1737,18 +1734,18 @@ class FixtureManager: an empty result is returned). :param argname: Name of the fixture to search for. - :param nodeid: Full node id of the requesting test. + :param node: The requesting Node. """ try: fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None - return tuple(self._matchfactories(fixturedefs, nodeid)) + return tuple(self._matchfactories(fixturedefs, node)) def _matchfactories( - self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str + self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node ) -> Iterator[FixtureDef[Any]]: - parentnodeids = set(nodes.iterparentnodeids(nodeid)) + parentnodeids = {n.nodeid for n in nodes.iterparentnodes(node)} for fixturedef in fixturedefs: if fixturedef.baseid in parentnodeids: yield fixturedef diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 4cf6768e6..da220918c 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -49,49 +49,13 @@ SEP = "/" tracebackcutdir = Path(_pytest.__file__).parent -def iterparentnodeids(nodeid: str) -> Iterator[str]: - """Return the parent node IDs of a given node ID, inclusive. - - For the node ID - - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - the result would be - - "" - "testing" - "testing/code" - "testing/code/test_excinfo.py" - "testing/code/test_excinfo.py::TestFormattedExcinfo" - "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source" - - Note that / components are only considered until the first ::. - """ - pos = 0 - first_colons: Optional[int] = nodeid.find("::") - if first_colons == -1: - first_colons = None - # The root Session node - always present. - yield "" - # Eagerly consume SEP parts until first colons. - while True: - at = nodeid.find(SEP, pos, first_colons) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len(SEP) - # Eagerly consume :: parts. - while True: - at = nodeid.find("::", pos) - if at == -1: - break - if at > 0: - yield nodeid[:at] - pos = at + len("::") - # The node ID itself. - if nodeid: - yield nodeid +def iterparentnodes(node: "Node") -> Iterator["Node"]: + """Return the parent nodes, including the node itself, from the node + upwards.""" + parent: Optional[Node] = node + while parent is not None: + yield parent + parent = parent.parent _NodeType = TypeVar("_NodeType", bound="Node") diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index dfdeeb7a8..40abcfccd 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,7 +1,9 @@ anyio[curio,trio]==4.2.0 django==5.0 pytest-asyncio==0.23.2 -pytest-bdd==7.0.1 +# Temporarily not installed until pytest-bdd is fixed: +# https://github.com/pytest-dev/pytest/pull/11785 +# pytest-bdd==7.0.1 pytest-cov==4.1.0 pytest-django==4.7.0 pytest-flakes==4.0.5 diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 775056a8e..81aa2bcc7 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1574,7 +1574,7 @@ class TestFixtureManagerParseFactories: """ def test_hello(item, fm): for name in ("fm", "hello", "item"): - faclist = fm.getfixturedefs(name, item.nodeid) + faclist = fm.getfixturedefs(name, item) assert len(faclist) == 1 fac = faclist[0] assert fac.func.__name__ == name @@ -1598,7 +1598,7 @@ class TestFixtureManagerParseFactories: def hello(self, request): return "class" def test_hello(self, item, fm): - faclist = fm.getfixturedefs("hello", item.nodeid) + faclist = fm.getfixturedefs("hello", item) print(faclist) assert len(faclist) == 3 @@ -1804,7 +1804,7 @@ class TestAutouseDiscovery: """ from _pytest.pytester import get_public_names def test_check_setup(item, fm): - autousenames = list(fm._getautousenames(item.nodeid)) + autousenames = list(fm._getautousenames(item)) assert len(get_public_names(autousenames)) == 2 assert "perfunction2" in autousenames assert "perfunction" in autousenames diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 880e2a44f..1de0f995e 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -2,7 +2,6 @@ import re import warnings from pathlib import Path from typing import cast -from typing import List from typing import Type import pytest @@ -12,29 +11,6 @@ from _pytest.pytester import Pytester from _pytest.warning_types import PytestWarning -@pytest.mark.parametrize( - ("nodeid", "expected"), - ( - ("", [""]), - ("a", ["", "a"]), - ("aa/b", ["", "aa", "aa/b"]), - ("a/b/c", ["", "a", "a/b", "a/b/c"]), - ("a/bbb/c::D", ["", "a", "a/bbb", "a/bbb/c", "a/bbb/c::D"]), - ("a/b/c::D::eee", ["", "a", "a/b", "a/b/c", "a/b/c::D", "a/b/c::D::eee"]), - ("::xx", ["", "::xx"]), - # / only considered until first :: - ("a/b/c::D/d::e", ["", "a", "a/b", "a/b/c", "a/b/c::D/d", "a/b/c::D/d::e"]), - # : alone is not a separator. - ("a/b::D:e:f::g", ["", "a", "a/b", "a/b::D:e:f", "a/b::D:e:f::g"]), - # / not considered if a part of a test name - ("a/b::c/d::e[/test]", ["", "a", "a/b", "a/b::c/d", "a/b::c/d::e[/test]"]), - ), -) -def test_iterparentnodeids(nodeid: str, expected: List[str]) -> None: - result = list(nodes.iterparentnodeids(nodeid)) - assert result == expected - - def test_node_from_parent_disallowed_arguments() -> None: with pytest.raises(TypeError, match="session is"): nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] diff --git a/tox.ini b/tox.ini index e92f6c98b..e4ad300a9 100644 --- a/tox.ini +++ b/tox.ini @@ -134,9 +134,11 @@ changedir = testing/plugins_integration deps = -rtesting/plugins_integration/requirements.txt setenv = PYTHONPATH=. +# Command temporarily removed until pytest-bdd is fixed: +# https://github.com/pytest-dev/pytest/pull/11785 +# pytest bdd_wallet.py commands = pip check - pytest bdd_wallet.py pytest --cov=. simple_integration.py pytest --ds=django_settings simple_integration.py pytest --html=simple.html simple_integration.py From 913d93debb75a2614bae742e6dd568731c5f29c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:24:22 +0000 Subject: [PATCH 068/104] build(deps): Bump pytest-asyncio in /testing/plugins_integration Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.23.2 to 0.23.3. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.2...v0.23.3) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 40abcfccd..0839b18c9 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,6 +1,6 @@ anyio[curio,trio]==4.2.0 django==5.0 -pytest-asyncio==0.23.2 +pytest-asyncio==0.23.3 # Temporarily not installed until pytest-bdd is fixed: # https://github.com/pytest-dev/pytest/pull/11785 # pytest-bdd==7.0.1 From 7bc8385924f3688b4c425c52e45ac780b6858cd2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:19:35 +0000 Subject: [PATCH 069/104] [pre-commit.ci] pre-commit autoupdate (#11792) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 6.1.0 → 7.0.0](https://github.com/PyCQA/flake8/compare/6.1.0...7.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce1cce6e0..be0b4315b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: language: python files: \.py$ - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.0.0 hooks: - id: flake8 language_version: python3 From 956d0e5e9d9a10c7e7faa90909452890f6081e62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 03:29:54 +0000 Subject: [PATCH 070/104] build(deps): Bump hynek/build-and-inspect-python-package Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 1.5.4 to 2.0.0. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v1.5.4...v2.0.0) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fed725f0e..f8b9f5f84 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5.4 + uses: hynek/build-and-inspect-python-package@v2.0.0 deploy: if: github.repository == 'pytest-dev/pytest' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9fbd273bc..79f806c6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5.4 + uses: hynek/build-and-inspect-python-package@v2.0.0 build: needs: [package] From 2270cab1c2dfb34dcc60c0e458be33ecb653e92d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 03:29:58 +0000 Subject: [PATCH 071/104] build(deps): Bump actions/download-artifact from 3 to 4 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f8b9f5f84..585398ba3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v4 - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79f806c6c..3d6f00bb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -173,7 +173,7 @@ jobs: persist-credentials: false - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Packages path: dist From 372c17e22899e8e676e105dae714e897426bef86 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 9 Jan 2024 23:01:50 +0200 Subject: [PATCH 072/104] fixtures: avoid FixtureDef <-> FixtureManager reference cycle There is no need to store the FixtureManager on each FixtureDef. --- src/_pytest/fixtures.py | 7 +++---- src/_pytest/python.py | 2 +- src/_pytest/setuponly.py | 15 +++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c184d2f3c..1c94583e8 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -970,7 +970,7 @@ class FixtureDef(Generic[FixtureValue]): def __init__( self, - fixturemanager: "FixtureManager", + config: Config, baseid: Optional[str], argname: str, func: "_FixtureFunc[FixtureValue]", @@ -984,7 +984,6 @@ class FixtureDef(Generic[FixtureValue]): _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self._fixturemanager = fixturemanager # The "base" node ID for the fixture. # # This is a node ID prefix. A fixture is only available to a node (e.g. @@ -1010,7 +1009,7 @@ class FixtureDef(Generic[FixtureValue]): if scope is None: scope = Scope.Function elif callable(scope): - scope = _eval_scope_callable(scope, argname, fixturemanager.config) + scope = _eval_scope_callable(scope, argname, config) if isinstance(scope, str): scope = Scope.from_user( scope, descr=f"Fixture '{func.__name__}'", where=baseid @@ -1657,7 +1656,7 @@ class FixtureManager: Set this if this is a unittest fixture. """ fixture_def = FixtureDef( - fixturemanager=self, + config=self.config, baseid=nodeid, argname=name, func=func, diff --git a/src/_pytest/python.py b/src/_pytest/python.py index aa134020f..36d2eba03 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1323,7 +1323,7 @@ class Metafunc: fixturedef = name2pseudofixturedef[argname] else: fixturedef = FixtureDef( - fixturemanager=self.definition.session._fixturemanager, + config=self.config, baseid="", argname=argname, func=get_direct_param_fixture_func, diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 0f8be899a..0f1045806 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -47,20 +47,23 @@ def pytest_fixture_setup( else: param = request.param fixturedef.cached_param = param # type: ignore[attr-defined] - _show_fixture_action(fixturedef, "SETUP") + _show_fixture_action(fixturedef, request.config, "SETUP") -def pytest_fixture_post_finalizer(fixturedef: FixtureDef[object]) -> None: +def pytest_fixture_post_finalizer( + fixturedef: FixtureDef[object], request: SubRequest +) -> None: if fixturedef.cached_result is not None: - config = fixturedef._fixturemanager.config + config = request.config if config.option.setupshow: - _show_fixture_action(fixturedef, "TEARDOWN") + _show_fixture_action(fixturedef, request.config, "TEARDOWN") if hasattr(fixturedef, "cached_param"): del fixturedef.cached_param # type: ignore[attr-defined] -def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: - config = fixturedef._fixturemanager.config +def _show_fixture_action( + fixturedef: FixtureDef[object], config: Config, msg: str +) -> None: capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() From 368fa2c03e5be3eb00cfea45e168886d9a24a286 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 9 Jan 2024 23:31:35 +0200 Subject: [PATCH 073/104] fixtures: remove unhelpful FixtureManager.{FixtureLookupError,FixtureLookupErrorRepr} Couldn't find any reason for this indirection, nor any plugins which rely on it. Seems like historically it was done to avoid some imports... --- src/_pytest/fixtures.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 1c94583e8..13c1790be 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -525,7 +525,7 @@ class FixtureRequest(abc.ABC): :param msg: An optional custom error message. """ - raise self._fixturemanager.FixtureLookupError(None, self, msg) + raise FixtureLookupError(None, self, msg) def getfixturevalue(self, argname: str) -> Any: """Dynamically run a named fixture function. @@ -1438,9 +1438,6 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - FixtureLookupError = FixtureLookupError - FixtureLookupErrorRepr = FixtureLookupErrorRepr - def __init__(self, session: "Session") -> None: self.session = session self.config: Config = session.config From 35a3863b151aa97b52b4b612b4959090cab307d9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 25 Dec 2023 19:40:40 +0200 Subject: [PATCH 074/104] config: clarify a bit of code in `_importconftest` --- src/_pytest/config/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 49d63a357..1fd82f49d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -638,9 +638,16 @@ class PytestPluginManager(PluginManager): if existing is not None: return cast(types.ModuleType, existing) + # conftest.py files there are not in a Python package all have module + # name "conftest", and thus conflict with each other. Clear the existing + # before loading the new one, otherwise the existing one will be + # returned from the module cache. pkgpath = resolve_package_path(conftestpath) if pkgpath is None: - _ensure_removed_sysmodule(conftestpath.stem) + try: + del sys.modules[conftestpath.stem] + except KeyError: + pass try: mod = import_path(conftestpath, mode=importmode, root=rootpath) @@ -818,13 +825,6 @@ def _get_plugin_specs_as_list( ) -def _ensure_removed_sysmodule(modname: str) -> None: - try: - del sys.modules[modname] - except KeyError: - pass - - class Notset: def __repr__(self): return "" From c7d85c5dc668e4a9eb2867e7432d599cdde799bd Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 10 Jan 2024 18:56:36 +0200 Subject: [PATCH 075/104] python: remove support for nose's `compat_co_firstlineno` Since we're removing nose support, let's also drop support for this attribute. From doing a code search on github, this seems completely unused outside of nose, except for some projects which used to use it, but no longer do. --- doc/en/deprecations.rst | 7 +++++++ src/_pytest/python.py | 15 +-------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index f5334ace5..76cc3482a 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -387,6 +387,13 @@ Will also need to be ported to a supported pytest style. One way to do it is usi .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup +The ``compat_co_firstlineno`` attribute +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Nose inspects this attribute on function objects to allow overriding the function's inferred line number. +Pytest no longer respects this attribute. + + Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 184399080..c4a840be1 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -6,7 +6,6 @@ import fnmatch import inspect import itertools import os -import sys import types import warnings from collections import Counter @@ -350,20 +349,8 @@ class PyobjMixin(nodes.Node): def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: # XXX caching? - obj = self.obj - compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) - if isinstance(compat_co_firstlineno, int): - # nose compatibility - file_path = sys.modules[obj.__module__].__file__ - assert file_path is not None - if file_path.endswith(".pyc"): - file_path = file_path[:-1] - path: Union["os.PathLike[str]", str] = file_path - lineno = compat_co_firstlineno - else: - path, lineno = getfslineno(obj) + path, lineno = getfslineno(self.obj) modpath = self.getmodpath() - assert isinstance(lineno, int) return path, lineno, modpath From 996e45d66a8e4ec16562e2937a532e9a3afe9876 Mon Sep 17 00:00:00 2001 From: Faisal Fawad <76597599+faisal-fawad@users.noreply.github.com> Date: Thu, 11 Jan 2024 06:01:07 -0500 Subject: [PATCH 076/104] Slight change to tmp_path documentation to more clearly illustrate its behavior (#11800) --- doc/en/how-to/tmp_path.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index b75fb5964..3e680dcac 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -9,7 +9,7 @@ The ``tmp_path`` fixture ------------------------ You can use the ``tmp_path`` fixture which will -provide a temporary directory unique to the test invocation, +provide a temporary directory unique to the current test, created in the `base temporary directory`_. ``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage: From 5bd5b80afdec77349a50d3c95b0d85d50f1ec0a9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 11 Jan 2024 11:30:22 +0200 Subject: [PATCH 077/104] nodes: add `Node.iterparents()` function This is a useful addition to the existing `listchain`. While `listchain` returns top-to-bottom, `iterparents` is bottom-to-top and doesn't require an internal full iteration + `reverse`. --- changelog/11801.improvement.rst | 2 ++ src/_pytest/fixtures.py | 24 ++++++++------------- src/_pytest/nodes.py | 38 ++++++++++++++++----------------- src/_pytest/python.py | 4 +--- 4 files changed, 30 insertions(+), 38 deletions(-) create mode 100644 changelog/11801.improvement.rst diff --git a/changelog/11801.improvement.rst b/changelog/11801.improvement.rst new file mode 100644 index 000000000..3046edca0 --- /dev/null +++ b/changelog/11801.improvement.rst @@ -0,0 +1,2 @@ +Added the :func:`iterparents() <_pytest.nodes.Node.iterparents>` helper method on nodes. +It is similar to :func:`listchain <_pytest.nodes.Node.listchain>`, but goes from bottom to top, and returns an iterator, not a list. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 13c1790be..dd37f8ec3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -116,22 +116,16 @@ def pytest_sessionstart(session: "Session") -> None: def get_scope_package( node: nodes.Item, fixturedef: "FixtureDef[object]", -) -> Optional[Union[nodes.Item, nodes.Collector]]: +) -> Optional[nodes.Node]: from _pytest.python import Package - current: Optional[Union[nodes.Item, nodes.Collector]] = node - while current and ( - not isinstance(current, Package) or current.nodeid != fixturedef.baseid - ): - current = current.parent # type: ignore[assignment] - if current is None: - return node.session - return current + for parent in node.iterparents(): + if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid: + return parent + return node.session -def get_scope_node( - node: nodes.Node, scope: Scope -) -> Optional[Union[nodes.Item, nodes.Collector]]: +def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: import _pytest.python if scope is Scope.Function: @@ -738,7 +732,7 @@ class SubRequest(FixtureRequest): scope = self._scope if scope is Scope.Function: # This might also be a non-function Item despite its attribute name. - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem + node: Optional[nodes.Node] = self._pyfuncitem elif scope is Scope.Package: node = get_scope_package(self._pyfuncitem, self._fixturedef) else: @@ -1513,7 +1507,7 @@ class FixtureManager: def _getautousenames(self, node: nodes.Node) -> Iterator[str]: """Return the names of autouse fixtures applicable to node.""" - for parentnode in reversed(list(nodes.iterparentnodes(node))): + for parentnode in node.listchain(): basenames = self._nodeid_autousenames.get(parentnode.nodeid) if basenames: yield from basenames @@ -1781,7 +1775,7 @@ class FixtureManager: def _matchfactories( self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node ) -> Iterator[FixtureDef[Any]]: - parentnodeids = {n.nodeid for n in nodes.iterparentnodes(node)} + parentnodeids = {n.nodeid for n in node.iterparents()} for fixturedef in fixturedefs: if fixturedef.baseid in parentnodeids: yield fixturedef diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index bc6d6f4dd..e45a515b0 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -49,15 +49,6 @@ SEP = "/" tracebackcutdir = Path(_pytest.__file__).parent -def iterparentnodes(node: "Node") -> Iterator["Node"]: - """Return the parent nodes, including the node itself, from the node - upwards.""" - parent: Optional[Node] = node - while parent is not None: - yield parent - parent = parent.parent - - _NodeType = TypeVar("_NodeType", bound="Node") @@ -265,12 +256,20 @@ class Node(abc.ABC, metaclass=NodeMeta): def teardown(self) -> None: pass - def listchain(self) -> List["Node"]: - """Return list of all parent collectors up to self, starting from - the root of collection tree. + def iterparents(self) -> Iterator["Node"]: + """Iterate over all parent collectors starting from and including self + up to the root of the collection tree. - :returns: The nodes. + .. versionadded:: 8.1 """ + parent: Optional[Node] = self + while parent is not None: + yield parent + parent = parent.parent + + def listchain(self) -> List["Node"]: + """Return a list of all parent collectors starting from the root of the + collection tree down to and including self.""" chain = [] item: Optional[Node] = self while item is not None: @@ -319,7 +318,7 @@ class Node(abc.ABC, metaclass=NodeMeta): :param name: If given, filter the results by the name attribute. :returns: An iterator of (node, mark) tuples. """ - for node in reversed(self.listchain()): + for node in self.iterparents(): for mark in node.own_markers: if name is None or getattr(mark, "name", None) == name: yield node, mark @@ -363,17 +362,16 @@ class Node(abc.ABC, metaclass=NodeMeta): self.session._setupstate.addfinalizer(fin, self) def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: - """Get the next parent node (including self) which is an instance of + """Get the closest parent node (including self) which is an instance of the given class. :param cls: The node class to search for. :returns: The node, if found. """ - current: Optional[Node] = self - while current and not isinstance(current, cls): - current = current.parent - assert current is None or isinstance(current, cls) - return current + for node in self.iterparents(): + if isinstance(node, cls): + return node + return None def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: return excinfo.traceback diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 36d2eba03..7623a9748 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -333,10 +333,8 @@ class PyobjMixin(nodes.Node): def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """Return Python path relative to the containing module.""" - chain = self.listchain() - chain.reverse() parts = [] - for node in chain: + for node in self.iterparents(): name = node.name if isinstance(node, Module): name = os.path.splitext(name)[0] From 82fda31e99b8302c0a242ed33afa2557adb30d5b Mon Sep 17 00:00:00 2001 From: mrbean-bremen Date: Thu, 11 Jan 2024 19:02:26 +0100 Subject: [PATCH 078/104] Clarify package scope The behavior of package scope is surprising to some (as seen by related questions on SO), this should clarify it a bit. --- doc/en/how-to/fixtures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index a8fea574a..95c376fd3 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -494,7 +494,7 @@ Fixtures are created when first requested by a test, and are destroyed based on * ``function``: the default scope, the fixture is destroyed at the end of the test. * ``class``: the fixture is destroyed during teardown of the last test in the class. * ``module``: the fixture is destroyed during teardown of the last test in the module. -* ``package``: the fixture is destroyed during teardown of the last test in the package. +* ``package``: the fixture is destroyed during teardown of the last test in the package where the fixture is defined, including sub-packages and sub-directories within it. * ``session``: the fixture is destroyed at the end of the test session. .. note:: From 06dbd3c21ccdf1ac76e8fa264048133cb4660842 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 13 Jan 2024 11:15:05 +0200 Subject: [PATCH 079/104] doctest: remove special conftest handling (Diff better viewed ignoring whitespace) Since e1c66ab0ad8eda13e5552dfc939e07d7290ecd39, conftest loading is handled at the directory level before sub-nodes are collected, so there is no need for the doctest plugin to handle it specially. This was probably the case even before e1c66ab0ad8eda13e5552dfc939e07d7290ecd39, but I haven't verified this. --- src/_pytest/doctest.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index a0125e93c..4ce32a298 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -558,24 +558,18 @@ class DoctestModule(Module): else: # pragma: no cover pass - if self.path.name == "conftest.py": - module = self.config.pluginmanager._importconftest( + try: + module = import_path( self.path, - self.config.getoption("importmode"), - rootpath=self.config.rootpath, + root=self.config.rootpath, + mode=self.config.getoption("importmode"), ) - else: - try: - module = import_path( - self.path, - root=self.config.rootpath, - mode=self.config.getoption("importmode"), - ) - except ImportError: - if self.config.getvalue("doctest_ignore_import_errors"): - skip("unable to import module %r" % self.path) - else: - raise + except ImportError: + if self.config.getvalue("doctest_ignore_import_errors"): + skip("unable to import module %r" % self.path) + else: + raise + # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() optionflags = get_optionflags(self.config) From a7c2549321ea30b778653a00c5307df625407687 Mon Sep 17 00:00:00 2001 From: Franck Charras <29153872+fcharras@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:54:07 +0100 Subject: [PATCH 080/104] Fix `assert mod not in mods` crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #27806. Co-authored-by: Loïc Estève Co-authored-by: Ran Benita Co-authored-by: Bruno Oliveira --- changelog/9765.bugfix.rst | 3 ++ src/_pytest/config/__init__.py | 11 ++++--- testing/acceptance_test.py | 59 ++++++++++++++++++++++++++++++++++ testing/test_pluginmanager.py | 34 +++++++++++++++++++- 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 changelog/9765.bugfix.rst diff --git a/changelog/9765.bugfix.rst b/changelog/9765.bugfix.rst new file mode 100644 index 000000000..c726cbf8d --- /dev/null +++ b/changelog/9765.bugfix.rst @@ -0,0 +1,3 @@ +Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangably in some places in the code. + +This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 1fd82f49d..85ae6dddf 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -634,7 +634,8 @@ class PytestPluginManager(PluginManager): def _importconftest( self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path ) -> types.ModuleType: - existing = self.get_plugin(str(conftestpath)) + conftestpath_plugin_name = str(conftestpath) + existing = self.get_plugin(conftestpath_plugin_name) if existing is not None: return cast(types.ModuleType, existing) @@ -666,7 +667,7 @@ class PytestPluginManager(PluginManager): assert mod not in mods mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") - self.consider_conftest(mod) + self.consider_conftest(mod, registration_name=conftestpath_plugin_name) return mod def _check_non_top_pytest_plugins( @@ -746,9 +747,11 @@ class PytestPluginManager(PluginManager): del self._name2plugin["pytest_" + name] self.import_plugin(arg, consider_entry_points=True) - def consider_conftest(self, conftestmodule: types.ModuleType) -> None: + def consider_conftest( + self, conftestmodule: types.ModuleType, registration_name: str + ) -> None: """:meta private:""" - self.register(conftestmodule, name=conftestmodule.__file__) + self.register(conftestmodule, name=registration_name) def consider_env(self) -> None: """:meta private:""" diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 43390ab83..b875b8f66 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,6 +1,7 @@ import dataclasses import importlib.metadata import os +import subprocess import sys import types @@ -1390,3 +1391,61 @@ def test_doctest_and_normal_imports_with_importlib(pytester: Pytester) -> None: ) result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines("*1 passed*") + + +@pytest.mark.skip(reason="Test is not isolated") +def test_issue_9765(pytester: Pytester) -> None: + """Reproducer for issue #9765 on Windows + + https://github.com/pytest-dev/pytest/issues/9765 + """ + pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + addopts = "-p my_package.plugin.my_plugin" + """ + ) + pytester.makepyfile( + **{ + "setup.py": ( + """ + from setuptools import setup + + if __name__ == '__main__': + setup(name='my_package', packages=['my_package', 'my_package.plugin']) + """ + ), + "my_package/__init__.py": "", + "my_package/conftest.py": "", + "my_package/test_foo.py": "def test(): pass", + "my_package/plugin/__init__.py": "", + "my_package/plugin/my_plugin.py": ( + """ + import pytest + + def pytest_configure(config): + + class SimplePlugin: + @pytest.fixture(params=[1, 2, 3]) + def my_fixture(self, request): + yield request.param + + config.pluginmanager.register(SimplePlugin()) + """ + ), + } + ) + + subprocess.run([sys.executable, "setup.py", "develop"], check=True) + try: + # We are using subprocess.run rather than pytester.run on purpose. + # pytester.run is adding the current directory to PYTHONPATH which avoids + # the bug. We also use pytest rather than python -m pytest for the same + # PYTHONPATH reason. + subprocess.run( + ["pytest", "my_package"], capture_output=True, check=True, text=True + ) + except subprocess.CalledProcessError as exc: + raise AssertionError( + f"pytest command failed:\n{exc.stdout=!s}\n{exc.stderr=!s}" + ) from exc diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index e5773412f..8bafde338 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -98,6 +98,38 @@ class TestPytestPluginInteractions: config.pluginmanager.register(A()) assert len(values) == 2 + @pytest.mark.skipif( + not sys.platform.startswith("win"), + reason="requires a case-insensitive file system", + ) + def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None: + """Unit test for issue #9765.""" + config = pytester.parseconfig() + pytester.makepyfile(**{"tests/conftest.py": ""}) + + conftest = pytester.path.joinpath("tests/conftest.py") + conftest_upper_case = pytester.path.joinpath("TESTS/conftest.py") + + mod = config.pluginmanager._importconftest( + conftest, + importmode="prepend", + rootpath=pytester.path, + ) + plugin = config.pluginmanager.get_plugin(str(conftest)) + assert plugin is mod + + mod_uppercase = config.pluginmanager._importconftest( + conftest_upper_case, + importmode="prepend", + rootpath=pytester.path, + ) + plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case)) + assert plugin_uppercase is mod_uppercase + + # No str(conftestpath) normalization so conftest should be imported + # twice and modules should be different objects + assert mod is not mod_uppercase + def test_hook_tracing(self, _config_for_test: Config) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] @@ -368,7 +400,7 @@ class TestPytestPluginManager: pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path ) with pytest.raises(ImportError): - pytestpm.consider_conftest(mod) + pytestpm.consider_conftest(mod, registration_name="unused") class TestPytestPluginManagerBootstrapming: From c6ed86453fbfd4a1f9ef6814a7a4679b3ced778f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 14 Jan 2024 01:04:53 -0300 Subject: [PATCH 081/104] [automated] Update plugin list (#11811) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 102 +++++++++++++++---------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index f1b672ecd..dceb3cf15 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -71,7 +71,7 @@ This list contains 1359 plugins. :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) - :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Oct 11, 2023 5 - Production/Stable pytest <8.0.0,>=6 + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Jan 10, 2024 5 - Production/Stable pytest >=6 :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) :pypi:`pytest-ansible-units` A pytest plugin for running unit tests within an ansible collection Apr 14, 2022 N/A N/A @@ -105,7 +105,7 @@ This list contains 1359 plugins. :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A - :pypi:`pytest-asyncio` Pytest support for asyncio Jan 01, 2024 4 - Beta pytest >=7.0.0 + :pypi:`pytest-asyncio` Pytest support for asyncio Jan 10, 2024 4 - Beta pytest >=7.0.0 :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Nov 30, 2023 N/A N/A :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) @@ -186,11 +186,11 @@ This list contains 1359 plugins. :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A :pypi:`pytest-capture-warnings` pytest plugin to capture all warnings and put them in one file of your choice May 03, 2022 N/A pytest - :pypi:`pytest-cases` Separate test code from test cases in pytest. Nov 10, 2023 5 - Production/Stable N/A + :pypi:`pytest-cases` Separate test code from test cases in pytest. Jan 12, 2024 5 - Production/Stable N/A :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Jan 03, 2024 N/A N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest Jan 08, 2024 N/A N/A :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A @@ -198,7 +198,7 @@ This list contains 1359 plugins. :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) - :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Dec 31, 2023 N/A pytest + :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Jan 08, 2024 N/A pytest>=7.0.0 :pypi:`pytest-checkdocs` check the README when running tests Jul 30, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Dec 04, 2023 5 - Production/Stable pytest >=2.9.2 :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A @@ -576,7 +576,7 @@ This list contains 1359 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Nov 20, 2023 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable pytest :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Dec 15, 2023 3 - Alpha pytest ==7.4.3 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jan 13, 2024 3 - Alpha pytest ==7.4.3 :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A :pypi:`pytest-hot-reloading` Jan 06, 2024 N/A N/A @@ -596,7 +596,7 @@ This list contains 1359 plugins. :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin May 08, 2023 5 - Production/Stable pytest ; extra == 'test' - :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace Dec 09, 2023 3 - Alpha pytest >=7.0.0 + :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace Jan 10, 2024 3 - Alpha pytest >=7.0.0 :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest May 22, 2023 3 - Alpha N/A @@ -730,7 +730,7 @@ This list contains 1359 plugins. :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A - :pypi:`pytest-matcher` Match test output against patterns stored in files Dec 10, 2021 5 - Production/Stable N/A + :pypi:`pytest-matcher` Keep a ChangeLog Jan 11, 2024 5 - Production/Stable pytest :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) @@ -810,7 +810,7 @@ This list contains 1359 plugins. :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A :pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) - :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Sep 18, 2023 N/A pytest (==6.2.5) + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jan 11, 2024 N/A pytest (>=6.2.5,<7.0.0) :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A @@ -903,7 +903,7 @@ This list contains 1359 plugins. :pypi:`pytest-plone` Pytest plugin to test Plone addons Jan 05, 2023 3 - Alpha pytest :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Oct 18, 2023 5 - Production/Stable pytest >=7.4.2 + :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Jan 10, 2024 5 - Production/Stable pytest >=7.4.2 :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Dec 26, 2022 N/A N/A :pypi:`pytest-pokie` Pokie plugin for pytest Oct 19, 2023 5 - Production/Stable N/A @@ -915,7 +915,7 @@ This list contains 1359 plugins. :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A :pypi:`pytest-pook` Pytest plugin for pook Dec 23, 2023 4 - Beta pytest :pypi:`pytest-pop` A pytest plugin to help with testing pop projects May 09, 2023 5 - Production/Stable pytest - :pypi:`pytest-porringer` Oct 03, 2023 N/A pytest>=7.4.0 + :pypi:`pytest-porringer` Jan 12, 2024 N/A pytest>=7.4.4 :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. May 20, 2023 5 - Production/Stable pytest (>=6.2) @@ -998,7 +998,7 @@ This list contains 1359 plugins. :pypi:`pytest-regex` Select pytest tests with regular expressions May 29, 2023 4 - Beta pytest (>=3.5.0) :pypi:`pytest-regex-dependency` Management of Pytest dependencies via regex patterns Jun 12, 2022 N/A pytest :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Aug 31, 2023 5 - Production/Stable pytest >=6.2.0 - :pypi:`pytest-regtest` pytest plugin for regression tests Aug 17, 2023 N/A N/A + :pypi:`pytest-regtest` "pytest plugin for snapshot regression testing" Jan 06, 2024 N/A pytest>7.2 :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest May 23, 2023 5 - Production/Stable pytest (>=7) :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A @@ -1007,7 +1007,7 @@ This list contains 1359 plugins. :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Jul 07, 2023 4 - Beta pytest :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 09, 2023 5 - Production/Stable pytest - :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jan 11, 2024 5 - Production/Stable pytest :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Apr 17, 2023 3 - Alpha pytest :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest @@ -1031,7 +1031,7 @@ This list contains 1359 plugins. :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Nov 22, 2023 5 - Production/Stable pytest >=7 :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A :pypi:`pytest-reserial` Pytest fixture for recording and replaying serial port traffic. Aug 31, 2023 4 - Beta pytest - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Dec 11, 2023 N/A pytest ~=4.6 ; python_version == "2.7" + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Jan 10, 2024 N/A pytest ~=4.6 ; python_version == "2.7" :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) :pypi:`pytest-resource-usage` Pytest plugin for reporting running time and peak memory usage Nov 06, 2022 5 - Production/Stable pytest>=7.0.0 @@ -1039,7 +1039,7 @@ This list contains 1359 plugins. :pypi:`pytest-responses` py.test integration for responses Oct 11, 2022 N/A pytest (>=2.5) :pypi:`pytest-rest-api` Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed Jul 10, 2023 5 - Production/Stable pytest - :pypi:`pytest-result-log` A pytest plugin that records the start, end, and result information of each use case in a log file Oct 15, 2023 N/A pytest>=7.2.0 + :pypi:`pytest-result-log` A pytest plugin that records the start, end, and result information of each use case in a log file Jan 10, 2024 N/A pytest>=7.2.0 :pypi:`pytest-result-sender` Apr 20, 2023 N/A pytest>=7.3.1 :pypi:`pytest-resume` A Pytest plugin to resuming from the last run test Apr 22, 2023 4 - Beta pytest (>=7.0) :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A @@ -1141,7 +1141,7 @@ This list contains 1359 plugins. :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-sort` Tools for sorting test cases Dec 22, 2023 N/A pytest >=7.4.0 + :pypi:`pytest-sort` Tools for sorting test cases Jan 07, 2024 N/A pytest >=7.4.0 :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Aug 04, 2023 2 - Pre-Alpha pytest :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest @@ -1159,7 +1159,7 @@ This list contains 1359 plugins. :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Dec 21, 2023 N/A pytest (>5.4.0,<8) + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jan 12, 2024 N/A pytest (>5.4.0,<8) :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Dec 01, 2023 N/A N/A :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A @@ -1197,7 +1197,7 @@ This list contains 1359 plugins. :pypi:`pytest-supercov` Pytest plugin for measuring explicit test-file to source-file coverage Jul 02, 2023 N/A N/A :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A - :pypi:`pytest-synodic` Synodic Pytest utilities Aug 26, 2023 N/A pytest>=7.4.0 + :pypi:`pytest-synodic` Synodic Pytest utilities Jan 12, 2024 N/A pytest>=7.4.4 :pypi:`pytest-system-statistics` Pytest plugin to track and report system usage statistics Feb 16, 2022 5 - Production/Stable pytest (>=6.0.0) :pypi:`pytest-system-test-plugin` Pyst - Pytest System-Test Plugin Feb 03, 2022 N/A N/A :pypi:`pytest-tagging` a pytest plugin to tag tests Apr 01, 2023 N/A pytest (>=7.1.3,<8.0.0) @@ -1385,11 +1385,11 @@ This list contains 1359 plugins. :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) :pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7) :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A - :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Nov 03, 2023 N/A pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Jan 11, 2024 N/A pytest (>=7.2.2,<8.0.0) :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A pytest>=5.0.0 :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A - :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Oct 27, 2023 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jan 08, 2024 5 - Production/Stable pytest (>=4.5.0) :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) :pypi:`pytest-zulip` Pytest report plugin for Zulip May 07, 2022 5 - Production/Stable pytest @@ -1658,9 +1658,9 @@ This list contains 1359 plugins. pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: Oct 11, 2023, + *last release*: Jan 10, 2024, *status*: 5 - Production/Stable, - *requires*: pytest <8.0.0,>=6 + *requires*: pytest >=6 Plugin for pytest to simplify calling ansible modules from tests or fixtures @@ -1896,7 +1896,7 @@ This list contains 1359 plugins. Pytest fixtures for async generators :pypi:`pytest-asyncio` - *last release*: Jan 01, 2024, + *last release*: Jan 10, 2024, *status*: 4 - Beta, *requires*: pytest >=7.0.0 @@ -2463,7 +2463,7 @@ This list contains 1359 plugins. pytest plugin to capture all warnings and put them in one file of your choice :pypi:`pytest-cases` - *last release*: Nov 10, 2023, + *last release*: Jan 12, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -2491,7 +2491,7 @@ This list contains 1359 plugins. Pytest plugin with server for catching HTTP requests. :pypi:`pytest-celery` - *last release*: Jan 03, 2024, + *last release*: Jan 08, 2024, *status*: N/A, *requires*: N/A @@ -2547,9 +2547,9 @@ This list contains 1359 plugins. A pytest fixture for changing current working directory :pypi:`pytest-check` - *last release*: Dec 31, 2023, + *last release*: Jan 08, 2024, *status*: N/A, - *requires*: pytest + *requires*: pytest>=7.0.0 A pytest plugin that allows multiple failures per test. @@ -5193,7 +5193,7 @@ This list contains 1359 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Dec 15, 2023, + *last release*: Jan 13, 2024, *status*: 3 - Alpha, *requires*: pytest ==7.4.3 @@ -5333,7 +5333,7 @@ This list contains 1359 plugins. Easily test your HTTP library against a local copy of httpbin :pypi:`pytest-httpdbg` - *last release*: Dec 09, 2023, + *last release*: Jan 10, 2024, *status*: 3 - Alpha, *requires*: pytest >=7.0.0 @@ -6271,11 +6271,11 @@ This list contains 1359 plugins. UNKNOWN :pypi:`pytest-matcher` - *last release*: Dec 10, 2021, + *last release*: Jan 11, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest - Match test output against patterns stored in files + Keep a ChangeLog :pypi:`pytest-match-skip` *last release*: May 15, 2019, @@ -6831,9 +6831,9 @@ This list contains 1359 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: Sep 18, 2023, + *last release*: Jan 11, 2024, *status*: N/A, - *requires*: pytest (==6.2.5) + *requires*: pytest (>=6.2.5,<7.0.0) Pytest plugin accessing NHSDigital's APIM proxies @@ -7482,7 +7482,7 @@ This list contains 1359 plugins. A plugin to help developing and testing other plugins :pypi:`pytest-plus` - *last release*: Oct 18, 2023, + *last release*: Jan 10, 2024, *status*: 5 - Production/Stable, *requires*: pytest >=7.4.2 @@ -7566,9 +7566,9 @@ This list contains 1359 plugins. A pytest plugin to help with testing pop projects :pypi:`pytest-porringer` - *last release*: Oct 03, 2023, + *last release*: Jan 12, 2024, *status*: N/A, - *requires*: pytest>=7.4.0 + *requires*: pytest>=7.4.4 @@ -8147,11 +8147,11 @@ This list contains 1359 plugins. Easy to use fixtures to write regression tests. :pypi:`pytest-regtest` - *last release*: Aug 17, 2023, + *last release*: Jan 06, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest>7.2 - pytest plugin for regression tests + "pytest plugin for snapshot regression testing" :pypi:`pytest-relative-order` *last release*: May 17, 2021, @@ -8210,9 +8210,9 @@ This list contains 1359 plugins. pytest plugin for repeating tests :pypi:`pytest-replay` - *last release*: Jun 09, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=3.0.0) + *last release*: Jan 11, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests @@ -8378,7 +8378,7 @@ This list contains 1359 plugins. Pytest fixture for recording and replaying serial port traffic. :pypi:`pytest-resilient-circuits` - *last release*: Dec 11, 2023, + *last release*: Jan 10, 2024, *status*: N/A, *requires*: pytest ~=4.6 ; python_version == "2.7" @@ -8434,7 +8434,7 @@ This list contains 1359 plugins. Pytest plugin to restrict the test types allowed :pypi:`pytest-result-log` - *last release*: Oct 15, 2023, + *last release*: Jan 10, 2024, *status*: N/A, *requires*: pytest>=7.2.0 @@ -9148,7 +9148,7 @@ This list contains 1359 plugins. Solr process and client fixtures for py.test. :pypi:`pytest-sort` - *last release*: Dec 22, 2023, + *last release*: Jan 07, 2024, *status*: N/A, *requires*: pytest >=7.4.0 @@ -9274,7 +9274,7 @@ This list contains 1359 plugins. :pypi:`pytest-splunk-addon` - *last release*: Dec 21, 2023, + *last release*: Jan 12, 2024, *status*: N/A, *requires*: pytest (>5.4.0,<8) @@ -9540,9 +9540,9 @@ This list contains 1359 plugins. pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. :pypi:`pytest-synodic` - *last release*: Aug 26, 2023, + *last release*: Jan 12, 2024, *status*: N/A, - *requires*: pytest>=7.4.0 + *requires*: pytest>=7.4.4 Synodic Pytest utilities @@ -10856,7 +10856,7 @@ This list contains 1359 plugins. PyTest plugin to run tests concurrently, each \`yield\` switch context to other one :pypi:`pytest-yls` - *last release*: Nov 03, 2023, + *last release*: Jan 11, 2024, *status*: N/A, *requires*: pytest (>=7.2.2,<8.0.0) @@ -10884,7 +10884,7 @@ This list contains 1359 plugins. OWASP ZAP plugin for py.test. :pypi:`pytest-zebrunner` - *last release*: Oct 27, 2023, + *last release*: Jan 08, 2024, *status*: 5 - Production/Stable, *requires*: pytest (>=4.5.0) From 1c9d6834fdd7343397993703f3eb82e1460c56fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Wed, 10 Jan 2024 06:35:13 +0100 Subject: [PATCH 082/104] Improve assert mod not in mods error message [ran: tweaked message, made the formatting lazy] --- src/_pytest/config/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 85ae6dddf..dc018ce2a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -664,7 +664,12 @@ class PytestPluginManager(PluginManager): if dirpath in self._dirpath2confmods: for path, mods in self._dirpath2confmods.items(): if dirpath in path.parents or path == dirpath: - assert mod not in mods + if mod in mods: + raise AssertionError( + f"While trying to load conftest path {str(conftestpath)}, " + f"found that the module {mod} is already loaded with path {mod.__file__}. " + "This is not supposed to happen. Please report this issue to pytest." + ) mods.append(mod) self.trace(f"loading conftestmodule {mod!r}") self.consider_conftest(mod, registration_name=conftestpath_plugin_name) From 2413d1b214dd2f58d301043dada54ff153ac373a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 14 Jan 2024 15:05:15 +0200 Subject: [PATCH 083/104] main,python: move `__pycache__` ignore to `pytest_ignore_collect` This removes one thing that directory collectors need to worry about. This adds one hook dispatch per `__pycache__` file, but I think it's worth it for consistency. --- src/_pytest/main.py | 5 +++-- src/_pytest/python.py | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 51be84164..f7e47cece 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -376,6 +376,9 @@ def _in_venv(path: Path) -> bool: def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: + if collection_path.name == "__pycache__": + return True + ignore_paths = config._getconftest_pathlist( "collect_ignore", path=collection_path.parent ) @@ -505,8 +508,6 @@ class Dir(nodes.Directory): ihook = self.ihook for direntry in scandir(self.path): if direntry.is_dir(): - if direntry.name == "__pycache__": - continue path = Path(direntry.path) if not self.session.isinitpath(path, with_parents=True): if ihook.pytest_ignore_collect(collection_path=path, config=config): diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4ca77fbfa..1d7f0fd6f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -707,8 +707,6 @@ class Package(nodes.Directory): ihook = self.ihook for direntry in scandir(self.path, sort_key): if direntry.is_dir(): - if direntry.name == "__pycache__": - continue path = Path(direntry.path) if not self.session.isinitpath(path, with_parents=True): if ihook.pytest_ignore_collect(collection_path=path, config=config): From 707642ad357d6fa1cc394991c2f4770c20aa6ccf Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 14 Jan 2024 15:17:41 +0200 Subject: [PATCH 084/104] nodes: rename `iterparents()` -> `iter_parents()` After the fact I remembered there is `node.iter_markers()` so let's be consistent with that rather than with `listchain()`. --- changelog/11801.improvement.rst | 2 +- src/_pytest/fixtures.py | 4 ++-- src/_pytest/nodes.py | 6 +++--- src/_pytest/python.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/changelog/11801.improvement.rst b/changelog/11801.improvement.rst index 3046edca0..d9e5f8483 100644 --- a/changelog/11801.improvement.rst +++ b/changelog/11801.improvement.rst @@ -1,2 +1,2 @@ -Added the :func:`iterparents() <_pytest.nodes.Node.iterparents>` helper method on nodes. +Added the :func:`iter_parents() <_pytest.nodes.Node.iter_parents>` helper method on nodes. It is similar to :func:`listchain <_pytest.nodes.Node.listchain>`, but goes from bottom to top, and returns an iterator, not a list. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index dd37f8ec3..c294ec586 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -119,7 +119,7 @@ def get_scope_package( ) -> Optional[nodes.Node]: from _pytest.python import Package - for parent in node.iterparents(): + for parent in node.iter_parents(): if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid: return parent return node.session @@ -1775,7 +1775,7 @@ class FixtureManager: def _matchfactories( self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node ) -> Iterator[FixtureDef[Any]]: - parentnodeids = {n.nodeid for n in node.iterparents()} + parentnodeids = {n.nodeid for n in node.iter_parents()} for fixturedef in fixturedefs: if fixturedef.baseid in parentnodeids: yield fixturedef diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index e45a515b0..3f22b5871 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -256,7 +256,7 @@ class Node(abc.ABC, metaclass=NodeMeta): def teardown(self) -> None: pass - def iterparents(self) -> Iterator["Node"]: + def iter_parents(self) -> Iterator["Node"]: """Iterate over all parent collectors starting from and including self up to the root of the collection tree. @@ -318,7 +318,7 @@ class Node(abc.ABC, metaclass=NodeMeta): :param name: If given, filter the results by the name attribute. :returns: An iterator of (node, mark) tuples. """ - for node in self.iterparents(): + for node in self.iter_parents(): for mark in node.own_markers: if name is None or getattr(mark, "name", None) == name: yield node, mark @@ -368,7 +368,7 @@ class Node(abc.ABC, metaclass=NodeMeta): :param cls: The node class to search for. :returns: The node, if found. """ - for node in self.iterparents(): + for node in self.iter_parents(): if isinstance(node, cls): return node return None diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4ca77fbfa..10934705b 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -333,7 +333,7 @@ class PyobjMixin(nodes.Node): def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: """Return Python path relative to the containing module.""" parts = [] - for node in self.iterparents(): + for node in self.iter_parents(): name = node.name if isinstance(node, Module): name = os.path.splitext(name)[0] From e1074f9c3df36bac760552447eb20a2bc16dbe0e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 13 Jan 2024 23:35:16 +0200 Subject: [PATCH 085/104] config: stop using exception triplets in `ConftestImportError` In recent python versions all of the info is on the exception object itself so no reason to deal with the annoying tuple. --- src/_pytest/config/__init__.py | 16 ++++++---------- src/_pytest/debugging.py | 3 ++- src/_pytest/nodes.py | 2 +- testing/test_config.py | 4 +--- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index dc018ce2a..698616664 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -17,7 +17,6 @@ from functools import lru_cache from pathlib import Path from textwrap import dedent from types import FunctionType -from types import TracebackType from typing import Any from typing import Callable from typing import cast @@ -112,16 +111,14 @@ class ConftestImportFailure(Exception): def __init__( self, path: Path, - excinfo: Tuple[Type[Exception], Exception, TracebackType], + *, + cause: Exception, ) -> None: - super().__init__(path, excinfo) self.path = path - self.excinfo = excinfo + self.cause = cause def __str__(self) -> str: - return "{}: {} (from {})".format( - self.excinfo[0].__name__, self.excinfo[1], self.path - ) + return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" def filter_traceback_for_conftest_import_failure( @@ -152,7 +149,7 @@ def main( try: config = _prepareconfig(args, plugins) except ConftestImportFailure as e: - exc_info = ExceptionInfo.from_exc_info(e.excinfo) + exc_info = ExceptionInfo.from_exception(e.cause) tw = TerminalWriter(sys.stderr) tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) exc_info.traceback = exc_info.traceback.filter( @@ -654,8 +651,7 @@ class PytestPluginManager(PluginManager): mod = import_path(conftestpath, mode=importmode, root=rootpath) except Exception as e: assert e.__traceback__ is not None - exc_info = (type(e), e, e.__traceback__) - raise ConftestImportFailure(conftestpath, exc_info) from e + raise ConftestImportFailure(conftestpath, cause=e) from e self._check_non_top_pytest_plugins(mod, conftestpath) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 69ec58c5b..57f26d219 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -377,7 +377,8 @@ def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.Traceb elif isinstance(excinfo.value, ConftestImportFailure): # A config.ConftestImportFailure is not useful for post_mortem. # Use the underlying exception instead: - return excinfo.value.excinfo[2] + assert excinfo.value.cause.__traceback__ is not None + return excinfo.value.cause.__traceback__ else: assert excinfo._excinfo is not None return excinfo._excinfo[2] diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 3f22b5871..45bbb4b57 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -384,7 +384,7 @@ class Node(abc.ABC, metaclass=NodeMeta): from _pytest.fixtures import FixtureLookupError if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo) + excinfo = ExceptionInfo.from_exception(excinfo.value.cause) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: style = "value" diff --git a/testing/test_config.py b/testing/test_config.py index 2d95fb4cc..0f586c043 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2108,9 +2108,7 @@ def test_conftest_import_error_repr(tmp_path: Path) -> None: try: raise RuntimeError("some error") except Exception as exc: - assert exc.__traceback__ is not None - exc_info = (type(exc), exc, exc.__traceback__) - raise ConftestImportFailure(path, exc_info) from exc + raise ConftestImportFailure(path, cause=exc) from exc def test_strtobool() -> None: From 348e6de102c5a5ccdaab88c860d57b1f49f7b1d2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 15 Jan 2024 21:04:08 +0100 Subject: [PATCH 086/104] doc: Update training dates and add pytest sprint (#11819) --- doc/en/index.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index bef42716f..3fb095a34 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,8 +1,12 @@ :orphan: -.. sidebar:: Next Open Trainings +.. sidebar:: Next Open Trainings and Events - - `Professional Testing with Python `_, via `Python Academy `_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote** + - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): + * **March 5th to 7th 2024**, Leipzig, Germany / Remote + * **June 11th to 13th 2024**, Remote + * **March 4th to 6th 2025**, Leipzig, Germany / Remote + - `pytest development sprint `_, June 2024 (`date poll `_) Also see :doc:`previous talks and blogposts `. From 9ad8b9fc36f4f62e085b1ad9a8b8bdf041cd8fa9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 9 Jan 2022 23:17:48 +0200 Subject: [PATCH 087/104] hookspec: remove explicit `:param` types Duplicates info in the type annotations which sphinx understands. --- src/_pytest/hookspec.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index c1963b4d7..a5c9aff70 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -54,7 +54,7 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: """Called at plugin registration time to allow adding new hooks via a call to :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. - :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager. + :param pluginmanager: The pytest plugin manager. .. note:: This hook is incompatible with hook wrappers. @@ -68,7 +68,7 @@ def pytest_plugin_registered( """A new pytest plugin got registered. :param plugin: The plugin module or instance. - :param pytest.PytestPluginManager manager: pytest plugin manager. + :param manager: pytest plugin manager. .. note:: This hook is incompatible with hook wrappers. @@ -86,13 +86,13 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> files situated at the tests root directory due to how pytest :ref:`discovers plugins during startup `. - :param pytest.Parser parser: + :param parser: To add command line options, call :py:func:`parser.addoption(...) `. To add ini-file values call :py:func:`parser.addini(...) `. - :param pytest.PytestPluginManager pluginmanager: + :param pluginmanager: The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks to change how command line options are added. @@ -127,7 +127,7 @@ def pytest_configure(config: "Config") -> None: .. note:: This hook is incompatible with hook wrappers. - :param pytest.Config config: The pytest config object. + :param config: The pytest config object. """ @@ -435,7 +435,7 @@ def pytest_make_parametrize_id( :param config: The pytest config object. :param val: The parametrized value. - :param str argname: The automatic parameter name produced by pytest. + :param argname: The automatic parameter name produced by pytest. """ From dd1447cfe509fac989258e078a73609e0d122a73 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jan 2022 00:04:20 +0200 Subject: [PATCH 088/104] hookspec: move pytest_load_initial_conftests up Reflect the order in which the plugins are called. --- src/_pytest/hookspec.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index a5c9aff70..f0c579c7b 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -156,18 +156,6 @@ def pytest_cmdline_parse( """ -@hookspec(firstresult=True) -def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: - """Called for performing the main command line action. The default - implementation will invoke the configure hooks and runtest_mainloop. - - Stops at first non-None result, see :ref:`firstresult`. - - :param config: The pytest config object. - :returns: The exit code. - """ - - def pytest_load_initial_conftests( early_config: "Config", parser: "Parser", args: List[str] ) -> None: @@ -183,6 +171,18 @@ def pytest_load_initial_conftests( """ +@hookspec(firstresult=True) +def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: + """Called for performing the main command line action. The default + implementation will invoke the configure hooks and runtest_mainloop. + + Stops at first non-None result, see :ref:`firstresult`. + + :param config: The pytest config object. + :returns: The exit code. + """ + + # ------------------------------------------------------------------------- # collection hooks # ------------------------------------------------------------------------- From c973ccb622da11181bbb820fa5861e825982f271 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 15 Jan 2024 23:46:59 +0200 Subject: [PATCH 089/104] hookspec: modernize a reference --- src/_pytest/hookspec.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index f0c579c7b..404eb4e96 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -173,8 +173,10 @@ def pytest_load_initial_conftests( @hookspec(firstresult=True) def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: - """Called for performing the main command line action. The default - implementation will invoke the configure hooks and runtest_mainloop. + """Called for performing the main command line action. + + The default implementation will invoke the configure hooks and + :hook:`pytest_runtestloop`. Stops at first non-None result, see :ref:`firstresult`. From e895c9d38cf4e66912c60c9c8307efcb9f82df4c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 16 Jan 2024 14:53:10 +0100 Subject: [PATCH 090/104] doc: Remove sold out training (#11823) --- doc/en/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 3fb095a34..bc0bd56bf 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -3,7 +3,6 @@ .. sidebar:: Next Open Trainings and Events - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): - * **March 5th to 7th 2024**, Leipzig, Germany / Remote * **June 11th to 13th 2024**, Remote * **March 4th to 6th 2025**, Leipzig, Germany / Remote - `pytest development sprint `_, June 2024 (`date poll `_) From 6e9f566d7919b52be266c365ea65cbab79108469 Mon Sep 17 00:00:00 2001 From: woutdenolf Date: Tue, 16 Jan 2024 15:41:01 +0100 Subject: [PATCH 091/104] avoid using __file__ in pytest_plugin_registered as can be wrong on Windows --- src/_pytest/fixtures.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index c294ec586..46b201184 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1485,23 +1485,25 @@ class FixtureManager: def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: nodeid = None - try: - p = absolutepath(plugin.__file__) # type: ignore[attr-defined] - except AttributeError: - pass - else: - # Construct the base nodeid which is later used to check - # what fixtures are visible for particular tests (as denoted - # by their test id). - if p.name == "conftest.py": - try: - nodeid = str(p.parent.relative_to(self.config.rootpath)) - except ValueError: - nodeid = "" - if nodeid == ".": - nodeid = "" - if os.sep != nodes.SEP: - nodeid = nodeid.replace(os.sep, nodes.SEP) + plugin_name = self.config.pluginmanager.get_name(plugin) + + # Construct the base nodeid which is later used to check + # what fixtures are visible for particular tests (as denoted + # by their test id). + if plugin_name and plugin_name.endswith("conftest.py"): + # The plugin name is assumed to be equal to plugin.__file__ + # for conftest plugins. The difference is that plugin_name + # has the correct capitalization on capital-insensitive + # systems (Windows). + p = absolutepath(plugin_name) + try: + nodeid = str(p.parent.relative_to(self.config.rootpath)) + except ValueError: + nodeid = "" + if nodeid == ".": + nodeid = "" + if os.sep != nodes.SEP: + nodeid = nodeid.replace(os.sep, nodes.SEP) self.parsefactories(plugin, nodeid) From 6b9bba2edb12a1b6679476aefe20c85dfb652ea0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 17 Jan 2024 15:00:09 +0200 Subject: [PATCH 092/104] pre-commit: add pluggy to mypy deps Otherwise mypy doesn't fully recognize pluggy's typing for some reason or another. --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be0b4315b..fe6ed99ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -64,6 +64,7 @@ repos: additional_dependencies: - iniconfig>=1.1.0 - attrs>=19.2.0 + - pluggy - packaging - tomli - types-pkg_resources From 0f5ecd83c432bc56e39549fe7353d06a92455a2a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 17 Jan 2024 14:47:07 +0200 Subject: [PATCH 093/104] hookspecs: add `plugin_name` parameter to the `pytest_plugin_registered` hook We have a use case for this in the next commit. The name can be obtained by using `manager.get_name(plugin)`, however this is currently O(num plugins) in pluggy, which would be good to avoid. Besides, it seems generally useful. --- changelog/11825.improvement.rst | 1 + src/_pytest/config/__init__.py | 12 ++++++++---- src/_pytest/hookspec.py | 7 +++++-- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 changelog/11825.improvement.rst diff --git a/changelog/11825.improvement.rst b/changelog/11825.improvement.rst new file mode 100644 index 000000000..afd85a041 --- /dev/null +++ b/changelog/11825.improvement.rst @@ -0,0 +1 @@ +The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 698616664..157c36490 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -490,15 +490,19 @@ class PytestPluginManager(PluginManager): ) ) return None - ret: Optional[str] = super().register(plugin, name) - if ret: + plugin_name = super().register(plugin, name) + if plugin_name is not None: self.hook.pytest_plugin_registered.call_historic( - kwargs=dict(plugin=plugin, manager=self) + kwargs=dict( + plugin=plugin, + plugin_name=plugin_name, + manager=self, + ) ) if isinstance(plugin, types.ModuleType): self.consider_module(plugin) - return ret + return plugin_name def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 404eb4e96..c4cce2d83 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -63,12 +63,15 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: @hookspec(historic=True) def pytest_plugin_registered( - plugin: "_PluggyPlugin", manager: "PytestPluginManager" + plugin: "_PluggyPlugin", + plugin_name: str, + manager: "PytestPluginManager", ) -> None: """A new pytest plugin got registered. :param plugin: The plugin module or instance. - :param manager: pytest plugin manager. + :param plugin_name: The name by which the plugin is registered. + :param manager: The pytest plugin manager. .. note:: This hook is incompatible with hook wrappers. From 9ea2e0a79f8d45b801b4bff8e4ad0aefd09fab21 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 17 Jan 2024 14:49:17 +0200 Subject: [PATCH 094/104] fixtures: avoid slow `pm.get_name(plugin)` call by using the new `plugin_name` hook parameter --- src/_pytest/fixtures.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 46b201184..51c573575 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1483,27 +1483,27 @@ class FixtureManager: return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) - def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: - nodeid = None - plugin_name = self.config.pluginmanager.get_name(plugin) - - # Construct the base nodeid which is later used to check - # what fixtures are visible for particular tests (as denoted - # by their test id). + def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None: + # Fixtures defined in conftest plugins are only visible to within the + # conftest's directory. This is unlike fixtures in non-conftest plugins + # which have global visibility. So for conftests, construct the base + # nodeid from the plugin name (which is the conftest path). if plugin_name and plugin_name.endswith("conftest.py"): - # The plugin name is assumed to be equal to plugin.__file__ - # for conftest plugins. The difference is that plugin_name - # has the correct capitalization on capital-insensitive - # systems (Windows). - p = absolutepath(plugin_name) + # Note: we explicitly do *not* use `plugin.__file__` here -- The + # difference is that plugin_name has the correct capitalization on + # case-insensitive systems (Windows) and other normalization issues + # (issue #11816). + conftestpath = absolutepath(plugin_name) try: - nodeid = str(p.parent.relative_to(self.config.rootpath)) + nodeid = str(conftestpath.parent.relative_to(self.config.rootpath)) except ValueError: nodeid = "" if nodeid == ".": nodeid = "" if os.sep != nodes.SEP: nodeid = nodeid.replace(os.sep, nodes.SEP) + else: + nodeid = None self.parsefactories(plugin, nodeid) From 5cd0535395541d8940035418b368331318002199 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 17 Jan 2024 22:53:04 +0200 Subject: [PATCH 095/104] testing: temporarily disable test due to hypothesis issue (#11836) Ref: https://github.com/pytest-dev/pytest/pull/11825#issuecomment-1894094641 --- testing/python/metafunc.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 9768c82ff..9393c9727 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -13,9 +13,6 @@ from typing import Sequence from typing import Tuple from typing import Union -import hypothesis -from hypothesis import strategies - import pytest from _pytest import fixtures from _pytest import python @@ -27,6 +24,9 @@ from _pytest.python import Function from _pytest.python import IdMaker from _pytest.scope import Scope +# import hypothesis +# from hypothesis import strategies + class TestMetafunc: def Metafunc(self, func, config=None) -> python.Metafunc: @@ -292,14 +292,15 @@ class TestMetafunc: assert metafunc._calls[2].id == "x1-a" assert metafunc._calls[3].id == "x1-b" - @hypothesis.given(strategies.text() | strategies.binary()) - @hypothesis.settings( - deadline=400.0 - ) # very close to std deadline and CI boxes are not reliable in CPU power - def test_idval_hypothesis(self, value) -> None: - escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6) - assert isinstance(escaped, str) - escaped.encode("ascii") + # TODO: Uncomment - https://github.com/HypothesisWorks/hypothesis/pull/3849 + # @hypothesis.given(strategies.text() | strategies.binary()) + # @hypothesis.settings( + # deadline=400.0 + # ) # very close to std deadline and CI boxes are not reliable in CPU power + # def test_idval_hypothesis(self, value) -> None: + # escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6) + # assert isinstance(escaped, str) + # escaped.encode("ascii") def test_unicode_idval(self) -> None: """Test that Unicode strings outside the ASCII character set get From ca5bbd0a9f56c8f9b3f5a43c47ff11c360f33d0b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 17 Jan 2024 23:44:01 +0200 Subject: [PATCH 096/104] Merge pull request #11835 from pytest-dev/release-8.0.0rc2 Prepare release version 8.0.0rc2 (cherry picked from commit 97960bdd148972b2f26bd9b336163e590bbc4c6b) --- changelog/11233.feature.rst | 5 ----- changelog/11706.bugfix.rst | 1 - changelog/11758.bugfix.rst | 2 -- changelog/11825.improvement.rst | 1 - changelog/9765.bugfix.rst | 3 --- doc/en/announce/index.rst | 1 + doc/en/announce/release-8.0.0rc2.rst | 32 ++++++++++++++++++++++++++++ doc/en/changelog.rst | 31 +++++++++++++++++++++++++++ doc/en/example/parametrize.rst | 10 ++++----- doc/en/example/pythoncollection.rst | 4 ++-- doc/en/example/simple.rst | 25 ++++++++++++++++++++++ doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 2 +- doc/en/how-to/output.rst | 11 +++++++++- 14 files changed, 108 insertions(+), 22 deletions(-) delete mode 100644 changelog/11233.feature.rst delete mode 100644 changelog/11706.bugfix.rst delete mode 100644 changelog/11758.bugfix.rst delete mode 100644 changelog/11825.improvement.rst delete mode 100644 changelog/9765.bugfix.rst create mode 100644 doc/en/announce/release-8.0.0rc2.rst diff --git a/changelog/11233.feature.rst b/changelog/11233.feature.rst deleted file mode 100644 index c465def84..000000000 --- a/changelog/11233.feature.rst +++ /dev/null @@ -1,5 +0,0 @@ -Improvements to how ``-r`` for xfailures and xpasses: - -* Report tracebacks for xfailures when ``-rx`` is set. -* Report captured output for xpasses when ``-rX`` is set. -* For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed. diff --git a/changelog/11706.bugfix.rst b/changelog/11706.bugfix.rst deleted file mode 100644 index 1b90d8f0b..000000000 --- a/changelog/11706.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. diff --git a/changelog/11758.bugfix.rst b/changelog/11758.bugfix.rst deleted file mode 100644 index af8a3f351..000000000 --- a/changelog/11758.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``. -This bug was introduced in pytest 8.0.0rc1. diff --git a/changelog/11825.improvement.rst b/changelog/11825.improvement.rst deleted file mode 100644 index afd85a041..000000000 --- a/changelog/11825.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered. diff --git a/changelog/9765.bugfix.rst b/changelog/9765.bugfix.rst deleted file mode 100644 index c726cbf8d..000000000 --- a/changelog/9765.bugfix.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangably in some places in the code. - -This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 740767c01..1c413de7a 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.0.0rc2 release-8.0.0rc1 release-7.4.4 release-7.4.3 diff --git a/doc/en/announce/release-8.0.0rc2.rst b/doc/en/announce/release-8.0.0rc2.rst new file mode 100644 index 000000000..1a6444c52 --- /dev/null +++ b/doc/en/announce/release-8.0.0rc2.rst @@ -0,0 +1,32 @@ +pytest-8.0.0rc2 +======================================= + +The pytest team is proud to announce the 8.0.0rc2 prerelease! + +This is a prerelease, not intended for production use, but to test the upcoming features and improvements +in order to catch any major problems before the final version is released to the major public. + +We appreciate your help testing this out before the final release, making sure to report any +regressions to our issue tracker: + +https://github.com/pytest-dev/pytest/issues + +When doing so, please include the string ``[prerelease]`` in the title. + +You can upgrade from PyPI via: + + pip install pytest==8.0.0rc2 + +Users are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/release-8.0.0rc2/changelog.html + +Thanks to all the contributors to this release: + +* Ben Brown +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 755f386c6..da275b622 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,37 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.0.0rc2 (2024-01-17) +============================ + + +Improvements +------------ + +- `#11233 `_: Improvements to ``-r`` for xfailures and xpasses: + + * Report tracebacks for xfailures when ``-rx`` is set. + * Report captured output for xpasses when ``-rX`` is set. + * For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed. + +- `#11825 `_: The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered. + + +Bug Fixes +--------- + +- `#11706 `_: Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. + + +- `#11758 `_: Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``. + This bug was introduced in pytest 8.0.0rc1. + + +- `#9765 `_, `#11816 `_: Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangably in some places in the code. + + This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*. + + pytest 8.0.0rc1 (2023-12-30) ============================ diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 0426266e5..9d78fc403 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + @@ -503,10 +503,10 @@ Running it results in some skips if we don't have all the python interpreters in .. code-block:: pytest . $ pytest -rs -q multipython.py - ssssssssssss...ssssssssssss [100%] + ssssssssssssssssssssssss... [100%] ========================= short test summary info ========================== SKIPPED [12] multipython.py:68: 'python3.9' not found - SKIPPED [12] multipython.py:68: 'python3.11' not found + SKIPPED [12] multipython.py:68: 'python3.10' not found 3 passed, 24 skipped in 0.12s Parametrization of optional implementations/imports diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index dbc2c239f..e98f1a752 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 21e5f4a09..7064f61f0 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -660,6 +660,31 @@ If we run this: E assert 0 test_step.py:11: AssertionError + ================================ XFAILURES ================================= + ______________________ TestUserHandling.test_deletion ______________________ + + item = + + def pytest_runtest_setup(item): + if "incremental" in item.keywords: + # retrieve the class name of the test + cls_name = str(item.cls) + # check if a previous test has failed for this class + if cls_name in _test_failed_incremental: + # retrieve the index of the test (if parametrize is used in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the first test function to fail for this class name and index + test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) + # if name found, test has failed for the combination of class name & test name + if test_name is not None: + > pytest.xfail(f"previous test failed ({test_name})") + E _pytest.outcomes.XFailed: previous test failed (test_modification) + + conftest.py:47: XFailed ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) ================== 1 failed, 2 passed, 1 xfailed in 0.12s ================== diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 0f74a8ecf..90765df60 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 8.0.0rc1 + pytest 8.0.0rc2 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 95c376fd3..e3209c777 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - + diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 95c3a89b5..76b2a53dd 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -404,10 +404,19 @@ Example: E assert 0 test_example.py:14: AssertionError + ================================ XFAILURES ================================= + ________________________________ test_xfail ________________________________ + + def test_xfail(): + > pytest.xfail("xfailing this test") + E _pytest.outcomes.XFailed: xfailing this test + + test_example.py:26: XFailed + ================================= XPASSES ================================== ========================= short test summary info ========================== SKIPPED [1] test_example.py:22: skipping this test XFAIL test_example.py::test_xfail - reason: xfailing this test - XPASS test_example.py::test_xpass always xfail + XPASS test_example.py::test_xpass - always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s === From 7fd561e4ba5dd5ae7a04c7b92b83cef8492a358d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 17 Jan 2024 19:11:10 -0300 Subject: [PATCH 097/104] Properly attach packages to the GH release notes (#11839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up to https://github.com/pytest-dev/pytest/pull/11754, noticed that the latest GitHub release does not contain the attached files. Output log from the action: ``` 🤔 Pattern 'dist/*' does not match any files. ``` --- .github/workflows/deploy.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 585398ba3..5d5015f18 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -72,6 +72,12 @@ jobs: fetch-depth: 0 persist-credentials: false + - name: Download Package + uses: actions/download-artifact@v4 + with: + name: Packages + path: dist + - name: Set up Python uses: actions/setup-python@v5 with: From eefc9d47fc40a9d5971a6207689c50efb1555a95 Mon Sep 17 00:00:00 2001 From: faph Date: Thu, 18 Jan 2024 10:21:49 +0000 Subject: [PATCH 098/104] [DOCS] Clarify tmp_path directory location and retention (#11830) Fixes #11789 and #11790 --- AUTHORS | 1 + changelog/11790.doc.rst | 1 + doc/en/how-to/tmp_path.rst | 22 +++++++++++++--------- src/_pytest/legacypath.py | 4 ++-- src/_pytest/tmpdir.py | 4 ++-- 5 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 changelog/11790.doc.rst diff --git a/AUTHORS b/AUTHORS index 353489b6c..803bb2b18 100644 --- a/AUTHORS +++ b/AUTHORS @@ -141,6 +141,7 @@ Evgeny Seliverstov Fabian Sturm Fabien Zarifian Fabio Zadrozny +faph Felix Hofstätter Felix Nieuwenhuizen Feng Ma diff --git a/changelog/11790.doc.rst b/changelog/11790.doc.rst new file mode 100644 index 000000000..648b20b96 --- /dev/null +++ b/changelog/11790.doc.rst @@ -0,0 +1 @@ +Documented the retention of temporary directories created using the ``tmp_path`` fixture in more detail. diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index 3e680dcac..3cc5152e9 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -8,9 +8,8 @@ How to use temporary directories and files in tests The ``tmp_path`` fixture ------------------------ -You can use the ``tmp_path`` fixture which will -provide a temporary directory unique to the current test, -created in the `base temporary directory`_. +You can use the ``tmp_path`` fixture which will provide a temporary directory +unique to each test function. ``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage: @@ -62,6 +61,11 @@ Running this would result in a passed test except for the last FAILED test_tmp_path.py::test_create_file - assert 0 ============================ 1 failed in 0.12s ============================= +By default, ``pytest`` retains the temporary directory for the last 3 ``pytest`` +invocations. Concurrent invocations of the same test function are supported by +configuring the base temporary directory to be unique for each concurrent +run. See `temporary directory location and retention`_ for details. + .. _`tmp_path_factory example`: The ``tmp_path_factory`` fixture @@ -100,7 +104,7 @@ See :ref:`tmp_path_factory API ` for details. .. _tmpdir: The ``tmpdir`` and ``tmpdir_factory`` fixtures ---------------------------------------------------- +---------------------------------------------- The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path`` and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects @@ -124,10 +128,10 @@ See :fixture:`tmpdir ` :fixture:`tmpdir_factory ` API for details. -.. _`base temporary directory`: +.. _`temporary directory location and retention`: -The default base temporary directory ------------------------------------------------ +Temporary directory location and retention +------------------------------------------ Temporary directories are by default created as sub-directories of the system temporary directory. The base name will be ``pytest-NUM`` where @@ -152,7 +156,7 @@ You can override the default temporary directory setting like this: for that purpose only. When distributing tests on the local machine using ``pytest-xdist``, care is taken to -automatically configure a basetemp directory for the sub processes such that all temporary -data lands below a single per-test run basetemp directory. +automatically configure a `basetemp` directory for the sub processes such that all temporary +data lands below a single per-test run temporary directory. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index b2dd87436..f69315b5f 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -326,8 +326,8 @@ class LegacyTmpdirPlugin: By default, a new base temporary directory is created each test session, and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a `legacy_path`_ object. diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 6fa227760..21e5366a3 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -262,8 +262,8 @@ def tmp_path( and old bases are removed after 3 sessions, to aid in debugging. This behavior can be configured with :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy`. - If ``--basetemp`` is used then it is cleared each session. See :ref:`base - temporary directory`. + If ``--basetemp`` is used then it is cleared each session. See + :ref:`temporary directory location and retention`. The returned object is a :class:`pathlib.Path` object. """ From 34fafe4c6b9f03107b5b925d310e875799376679 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Jan 2024 20:42:09 +0200 Subject: [PATCH 099/104] config: avoid Path.cwd() call in `_set_initial_conftests` We should aim to remove all `cwd()` calls except one, otherwise things will go bad if the working directory changes. Use the invocation dir instead. --- src/_pytest/config/__init__.py | 13 +++++++++---- testing/test_conftest.py | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 157c36490..70fceef7b 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -541,6 +541,7 @@ class PytestPluginManager(PluginManager): noconftest: bool, rootpath: Path, confcutdir: Optional[Path], + invocation_dir: Path, importmode: Union[ImportMode, str], ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -550,8 +551,9 @@ class PytestPluginManager(PluginManager): All builtin and 3rd party plugins will have been loaded, however, so common options will not confuse our logic here. """ - current = Path.cwd() - self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None + self._confcutdir = ( + absolutepath(invocation_dir / confcutdir) if confcutdir else None + ) self._noconftest = noconftest self._using_pyargs = pyargs foundanchor = False @@ -561,7 +563,7 @@ class PytestPluginManager(PluginManager): i = path.find("::") if i != -1: path = path[:i] - anchor = absolutepath(current / path) + anchor = absolutepath(invocation_dir / path) # Ensure we do not break if what appears to be an anchor # is in fact a very long option (#10169, #11394). @@ -569,7 +571,7 @@ class PytestPluginManager(PluginManager): self._try_load_conftest(anchor, importmode, rootpath) foundanchor = True if not foundanchor: - self._try_load_conftest(current, importmode, rootpath) + self._try_load_conftest(invocation_dir, importmode, rootpath) def _is_in_confcutdir(self, path: Path) -> bool: """Whether a path is within the confcutdir. @@ -1168,6 +1170,7 @@ class Config: noconftest=early_config.known_args_namespace.noconftest, rootpath=early_config.rootpath, confcutdir=early_config.known_args_namespace.confcutdir, + invocation_dir=early_config.invocation_params.dir, importmode=early_config.known_args_namespace.importmode, ) @@ -1261,6 +1264,8 @@ class Config: """Decide the args (initial paths/nodeids) to use given the relevant inputs. :param warn: Whether can issue warnings. + + :returns: The args and the args source. Guaranteed to be non-empty. """ if args: source = Config.ArgsSource.ARGS diff --git a/testing/test_conftest.py b/testing/test_conftest.py index cfc2d577b..e74190727 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -35,6 +35,7 @@ def conftest_setinitial( noconftest=False, rootpath=Path(args[0]), confcutdir=confcutdir, + invocation_dir=Path.cwd(), importmode="prepend", ) From 212c55218b1b7406643ba4fe7002702ff6cf2c04 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Jan 2024 20:47:36 +0200 Subject: [PATCH 100/104] helpconfig: add invocation dir to debug output The current WD is not supposed to matter, the invocation dir is what should be relevant. But keep them both for debugging. --- src/_pytest/helpconfig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 364bf4c42..91ea76997 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -109,10 +109,11 @@ def pytest_cmdline_parse() -> Generator[None, Config, Config]: debugfile = open(path, "w", encoding="utf-8") debugfile.write( "versions pytest-%s, " - "python-%s\ncwd=%s\nargs=%s\n\n" + "python-%s\ninvocation_dir=%s\ncwd=%s\nargs=%s\n\n" % ( pytest.__version__, ".".join(map(str, sys.version_info)), + config.invocation_params.dir, os.getcwd(), config.invocation_params.args, ) From 676f38d04aa7fc96ecf06235d855820aba05b83a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Jan 2024 20:56:32 +0200 Subject: [PATCH 101/104] findpaths: rely on invocation_dir instead of cwd We should aim to remove all `cwd()` calls except one, otherwise things will go bad if the working directory changes. Use the invocation dir instead. --- src/_pytest/config/__init__.py | 4 +- src/_pytest/config/findpaths.py | 32 +++++----- testing/test_config.py | 105 ++++++++++++++++++++++++++------ testing/test_findpaths.py | 13 ++-- 4 files changed, 111 insertions(+), 43 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 70fceef7b..e4e8f700a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1179,8 +1179,8 @@ class Config: args, namespace=copy.copy(self.option) ) rootpath, inipath, inicfg = determine_setup( - ns.inifilename, - ns.file_or_dir + unknown_args, + inifile=ns.inifilename, + args=ns.file_or_dir + unknown_args, rootdir_cmd_arg=ns.rootdir or None, invocation_dir=self.invocation_params.dir, ) diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index fc30533b6..8e69a46fd 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -87,6 +87,7 @@ def load_config_dict_from_file( def locate_config( + invocation_dir: Path, args: Iterable[Path], ) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: """Search in the list of arguments for a valid ini-file for pytest, @@ -100,7 +101,7 @@ def locate_config( ] args = [x for x in args if not str(x).startswith("-")] if not args: - args = [Path.cwd()] + args = [invocation_dir] for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): @@ -113,7 +114,10 @@ def locate_config( return None, None, {} -def get_common_ancestor(paths: Iterable[Path]) -> Path: +def get_common_ancestor( + invocation_dir: Path, + paths: Iterable[Path], +) -> Path: common_ancestor: Optional[Path] = None for path in paths: if not path.exists(): @@ -130,7 +134,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path: if shared is not None: common_ancestor = shared if common_ancestor is None: - common_ancestor = Path.cwd() + common_ancestor = invocation_dir elif common_ancestor.is_file(): common_ancestor = common_ancestor.parent return common_ancestor @@ -162,10 +166,11 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte def determine_setup( + *, inifile: Optional[str], args: Sequence[str], - rootdir_cmd_arg: Optional[str] = None, - invocation_dir: Optional[Path] = None, + rootdir_cmd_arg: Optional[str], + invocation_dir: Path, ) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: """Determine the rootdir, inifile and ini configuration values from the command line arguments. @@ -177,8 +182,7 @@ def determine_setup( :param rootdir_cmd_arg: The `--rootdir` command line argument, if given. :param invocation_dir: - The working directory when pytest was invoked, if known. - If not known, the current working directory is used. + The working directory when pytest was invoked. """ rootdir = None dirs = get_dirs_from_args(args) @@ -189,8 +193,8 @@ def determine_setup( if rootdir_cmd_arg is None: rootdir = inipath_.parent else: - ancestor = get_common_ancestor(dirs) - rootdir, inipath, inicfg = locate_config([ancestor]) + ancestor = get_common_ancestor(invocation_dir, dirs) + rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor]) if rootdir is None and rootdir_cmd_arg is None: for possible_rootdir in (ancestor, *ancestor.parents): if (possible_rootdir / "setup.py").is_file(): @@ -198,13 +202,11 @@ def determine_setup( break else: if dirs != [ancestor]: - rootdir, inipath, inicfg = locate_config(dirs) + rootdir, inipath, inicfg = locate_config(invocation_dir, dirs) if rootdir is None: - if invocation_dir is not None: - cwd = invocation_dir - else: - cwd = Path.cwd() - rootdir = get_common_ancestor([cwd, ancestor]) + rootdir = get_common_ancestor( + invocation_dir, [invocation_dir, ancestor] + ) if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: diff --git a/testing/test_config.py b/testing/test_config.py index 0f586c043..1eb530ace 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -59,7 +59,7 @@ class TestParseIni: ), encoding="utf-8", ) - _, _, cfg = locate_config([sub]) + _, _, cfg = locate_config(Path.cwd(), [sub]) assert cfg["name"] == "value" config = pytester.parseconfigure(str(sub)) assert config.inicfg["name"] == "value" @@ -1436,16 +1436,16 @@ def test_collect_pytest_prefix_bug(pytestconfig): class TestRootdir: def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: - assert get_common_ancestor([tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path a = tmp_path / "a" a.mkdir() - assert get_common_ancestor([a, tmp_path]) == tmp_path - assert get_common_ancestor([tmp_path, a]) == tmp_path + assert get_common_ancestor(Path.cwd(), [a, tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [tmp_path, a]) == tmp_path monkeypatch.chdir(tmp_path) - assert get_common_ancestor([]) == tmp_path + assert get_common_ancestor(Path.cwd(), []) == tmp_path no_path = tmp_path / "does-not-exist" - assert get_common_ancestor([no_path]) == tmp_path - assert get_common_ancestor([no_path / "a"]) == tmp_path + assert get_common_ancestor(Path.cwd(), [no_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [no_path / "a"]) == tmp_path @pytest.mark.parametrize( "name, contents", @@ -1467,10 +1467,20 @@ class TestRootdir: b = a / "b" b.mkdir() for args in ([str(tmp_path)], [str(a)], [str(b)]): - rootpath, parsed_inipath, _ = determine_setup(None, args) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=args, + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath - rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)]) + rootpath, parsed_inipath, ini_config = determine_setup( + inifile=None, + args=[str(b), str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath assert ini_config == {"x": "10"} @@ -1482,7 +1492,12 @@ class TestRootdir: a = tmp_path / "a" a.mkdir() (a / name).touch() - rootpath, parsed_inipath, _ = determine_setup(None, [str(a)]) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=[str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert parsed_inipath == inipath @@ -1491,14 +1506,24 @@ class TestRootdir: a.mkdir() (a / "setup.cfg").touch() (tmp_path / "setup.py").touch() - rootpath, inipath, inicfg = determine_setup(None, [str(a)]) + rootpath, inipath, inicfg = determine_setup( + inifile=None, + args=[str(a)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None assert inicfg == {} def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) - rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)]) + rootpath, inipath, inicfg = determine_setup( + inifile=None, + args=[str(tmp_path)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None assert inicfg == {} @@ -1520,7 +1545,12 @@ class TestRootdir: p = tmp_path / name p.touch() p.write_text(contents, encoding="utf-8") - rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) + rootpath, inipath, ini_config = determine_setup( + inifile=str(p), + args=[str(tmp_path)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath == p assert ini_config == {"x": "10"} @@ -1534,14 +1564,24 @@ class TestRootdir: monkeypatch.chdir(tmp_path) # No config file is explicitly given: rootdir is determined to be cwd. - rootpath, found_inipath, *_ = determine_setup(None, [str(tests_dir)]) + rootpath, found_inipath, *_ = determine_setup( + inifile=None, + args=[str(tests_dir)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert found_inipath is None # Config file is explicitly given: rootdir is determined to be inifile's directory. inipath = tmp_path / "pytest.ini" inipath.touch() - rootpath, found_inipath, *_ = determine_setup(str(inipath), [str(tests_dir)]) + rootpath, found_inipath, *_ = determine_setup( + inifile=str(inipath), + args=[str(tests_dir)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert found_inipath == inipath @@ -1553,7 +1593,12 @@ class TestRootdir: a.mkdir() b = tmp_path / "b" b.mkdir() - rootpath, inifile, _ = determine_setup(None, [str(a), str(b)]) + rootpath, inifile, _ = determine_setup( + inifile=None, + args=[str(a), str(b)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inifile is None @@ -1564,7 +1609,12 @@ class TestRootdir: b.mkdir() inipath = a / "pytest.ini" inipath.touch() - rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)]) + rootpath, parsed_inipath, _ = determine_setup( + inifile=None, + args=[str(a), str(b)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == a assert inipath == parsed_inipath @@ -1573,7 +1623,12 @@ class TestRootdir: self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch ) -> None: monkeypatch.chdir(tmp_path) - rootpath, inipath, _ = determine_setup(None, dirs) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=dirs, + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None @@ -1584,7 +1639,12 @@ class TestRootdir: a.mkdir() (a / "exists").touch() monkeypatch.chdir(tmp_path) - rootpath, inipath, _ = determine_setup(None, ["a/exist"]) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=["a/exist"], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path assert inipath is None @@ -1598,7 +1658,12 @@ class TestRootdir: (tmp_path / "myproject" / "tests").mkdir() monkeypatch.chdir(tmp_path / "myproject") - rootpath, inipath, _ = determine_setup(None, ["tests/"]) + rootpath, inipath, _ = determine_setup( + inifile=None, + args=["tests/"], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) assert rootpath == tmp_path / "myproject" assert inipath == tmp_path / "myproject" / "setup.cfg" diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 8287de603..65370a0ba 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -109,18 +109,19 @@ class TestCommonAncestor: fn2 = tmp_path / "foo" / "zaz" / "test_2.py" fn2.parent.mkdir(parents=True) fn2.touch() - assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo" - assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo" - assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo" - assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo" + cwd = Path.cwd() + assert get_common_ancestor(cwd, [fn1, fn2]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1.parent, fn2]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1.parent, fn2.parent]) == tmp_path / "foo" + assert get_common_ancestor(cwd, [fn1, fn2.parent]) == tmp_path / "foo" def test_single_dir(self, tmp_path: Path) -> None: - assert get_common_ancestor([tmp_path]) == tmp_path + assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path def test_single_file(self, tmp_path: Path) -> None: fn = tmp_path / "foo.py" fn.touch() - assert get_common_ancestor([fn]) == tmp_path + assert get_common_ancestor(Path.cwd(), [fn]) == tmp_path def test_get_dirs_from_args(tmp_path): From a6dd90a41414173110ea143c301d3da9853449ea Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Jan 2024 21:52:23 +0200 Subject: [PATCH 102/104] python: use invocation dir instead of cwd in fixture-printing code We should aim to remove all `cwd()` calls except one, otherwise things will go bad if the working directory changes. Use the invocation dir instead. --- src/_pytest/python.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e55772d5e..64480c971 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1525,14 +1525,13 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) - return val if escape_option else ascii_escaped(val) # type: ignore -def _pretty_fixture_path(func) -> str: - cwd = Path.cwd() - loc = Path(getlocation(func, str(cwd))) +def _pretty_fixture_path(invocation_dir: Path, func) -> str: + loc = Path(getlocation(func, invocation_dir)) prefix = Path("...", "_pytest") try: return str(prefix / loc.relative_to(_PYTEST_DIR)) except ValueError: - return bestrelpath(cwd, loc) + return bestrelpath(invocation_dir, loc) def show_fixtures_per_test(config): @@ -1545,19 +1544,19 @@ def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() - curdir = Path.cwd() + invocation_dir = config.invocation_params.dir tw = _pytest.config.create_terminal_writer(config) verbose = config.getvalue("verbose") def get_best_relpath(func) -> str: - loc = getlocation(func, str(curdir)) - return bestrelpath(curdir, Path(loc)) + loc = getlocation(func, invocation_dir) + return bestrelpath(invocation_dir, Path(loc)) def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: argname = fixture_def.argname if verbose <= 0 and argname.startswith("_"): return - prettypath = _pretty_fixture_path(fixture_def.func) + prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func) tw.write(f"{argname}", green=True) tw.write(f" -- {prettypath}", yellow=True) tw.write("\n") @@ -1601,7 +1600,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() - curdir = Path.cwd() + invocation_dir = config.invocation_params.dir tw = _pytest.config.create_terminal_writer(config) verbose = config.getvalue("verbose") @@ -1615,7 +1614,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: if not fixturedefs: continue for fixturedef in fixturedefs: - loc = getlocation(fixturedef.func, str(curdir)) + loc = getlocation(fixturedef.func, invocation_dir) if (fixturedef.argname, loc) in seen: continue seen.add((fixturedef.argname, loc)) @@ -1623,7 +1622,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: ( len(fixturedef.baseid), fixturedef.func.__module__, - _pretty_fixture_path(fixturedef.func), + _pretty_fixture_path(invocation_dir, fixturedef.func), fixturedef.argname, fixturedef, ) From 111ad26f714b05166b473a01823be9f7a21d306d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 18 Jan 2024 21:37:42 +0200 Subject: [PATCH 103/104] doc/writing_plugins: correct inaccuracies re. initial conftest loading --- doc/en/how-to/writing_plugins.rst | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index d907ae398..4bb6d1833 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -46,24 +46,18 @@ Plugin discovery order at tool startup 5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable. -6. by loading all :file:`conftest.py` files as inferred by the command line - invocation: +6. by loading all "initial ":file:`conftest.py` files: - - if no test paths are specified, use the current dir as a test path - - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative - to the directory part of the first test path. After the ``conftest.py`` - file is loaded, load all plugins specified in its - :globalvar:`pytest_plugins` variable if present. - - Note that pytest does not find ``conftest.py`` files in deeper nested - sub directories at tool startup. It is usually a good idea to keep - your ``conftest.py`` file in the top level test or project root directory. - -7. by recursively loading all plugins specified by the - :globalvar:`pytest_plugins` variable in ``conftest.py`` files. + - determine the test paths: specified on the command line, otherwise in + :confval:`testpaths` if defined and running from the rootdir, otherwise the + current dir + - for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative + to the directory part of the test path, if exist. Before a ``conftest.py`` + file is loaded, load ``conftest.py`` files in all of its parent directories. + After a ``conftest.py`` file is loaded, recursively load all plugins specified + in its :globalvar:`pytest_plugins` variable if present. -.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ .. _`conftest.py plugins`: .. _`localplugin`: .. _`local conftest plugins`: @@ -108,9 +102,9 @@ Here is how you might run it:: See also: :ref:`pythonpath`. .. note:: - Some hooks should be implemented only in plugins or conftest.py files situated at the - tests root directory due to how pytest discovers plugins during startup, - see the documentation of each hook for details. + Some hooks cannot be implemented in conftest.py files which are not + :ref:`initial ` due to how pytest discovers plugins during + startup. See the documentation of each hook for details. Writing your own plugin ----------------------- From d71ef04f117faf97eab2d83ddba935e6ac77c3c2 Mon Sep 17 00:00:00 2001 From: clee2000 <44682903+clee2000@users.noreply.github.com> Date: Thu, 18 Jan 2024 17:08:26 -0800 Subject: [PATCH 104/104] Escape skip reason in junitxml (#11842) Co-authored-by: Bruno Oliveira --- changelog/11842.bugfix.rst | 1 + src/_pytest/junitxml.py | 4 +++- testing/test_junitxml.py | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 changelog/11842.bugfix.rst diff --git a/changelog/11842.bugfix.rst b/changelog/11842.bugfix.rst new file mode 100644 index 000000000..3a11b110f --- /dev/null +++ b/changelog/11842.bugfix.rst @@ -0,0 +1 @@ +Properly escape the ``reason`` of a :ref:`skip ` mark when writing JUnit XML files. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 9ee35b84e..c8032e158 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -248,7 +248,9 @@ class _NodeReporter: skipreason = skipreason[9:] details = f"{filename}:{lineno}: {skipreason}" - skipped = ET.Element("skipped", type="pytest.skip", message=skipreason) + skipped = ET.Element( + "skipped", type="pytest.skip", message=bin_xml_escape(skipreason) + ) skipped.text = bin_xml_escape(details) self.append(skipped) self.write_captured_output(report) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 3f88c21e2..b0c2d1c6d 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1653,6 +1653,23 @@ def test_escaped_skipreason_issue3533( snode.assert_attr(message="1 <> 2") +def test_bin_escaped_skipreason(pytester: Pytester, run_and_parse: RunAndParse) -> None: + """Escape special characters from mark.skip reason (#11842).""" + pytester.makepyfile( + """ + import pytest + @pytest.mark.skip("\33[31;1mred\33[0m") + def test_skip(): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("skipped") + assert "#x1B[31;1mred#x1B[0m" in snode.text + snode.assert_attr(message="#x1B[31;1mred#x1B[0m") + + def test_escaped_setup_teardown_error( pytester: Pytester, run_and_parse: RunAndParse ) -> None: