From a42530a09d657f3a6b22ad8a45ac5d29068dd8b4 Mon Sep 17 00:00:00 2001 From: Simon Blanchard Date: Wed, 22 Nov 2023 21:05:00 +0100 Subject: [PATCH 01/23] Fix for operation on closed file in faulthandler teardown (#11584) --- changelog/11572.bugfix.rst | 1 + src/_pytest/faulthandler.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 changelog/11572.bugfix.rst diff --git a/changelog/11572.bugfix.rst b/changelog/11572.bugfix.rst new file mode 100644 index 000000000..7a235a071 --- /dev/null +++ b/changelog/11572.bugfix.rst @@ -0,0 +1 @@ +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/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index a9418f34e..1bccd18c6 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -9,8 +9,8 @@ from _pytest.nodes import Item from _pytest.stash import StashKey +fault_handler_original_stderr_fd_key = StashKey[int]() fault_handler_stderr_fd_key = StashKey[int]() -fault_handler_originally_enabled_key = StashKey[bool]() def pytest_addoption(parser: Parser) -> None: @@ -24,8 +24,15 @@ def pytest_addoption(parser: Parser) -> None: def pytest_configure(config: Config) -> None: import faulthandler - config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno()) - config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() + # at teardown we want to restore the original faulthandler fileno + # but faulthandler has no api to return the original fileno + # so here we stash the stderr fileno to be used at teardown + # sys.stderr and sys.__stderr__ may be closed or patched during the session + # so we can't rely on their values being good at that point (#11572). + stderr_fileno = get_stderr_fileno() + if faulthandler.is_enabled(): + config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno + config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno) faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) @@ -37,9 +44,10 @@ def pytest_unconfigure(config: Config) -> None: if fault_handler_stderr_fd_key in config.stash: os.close(config.stash[fault_handler_stderr_fd_key]) del config.stash[fault_handler_stderr_fd_key] - if config.stash.get(fault_handler_originally_enabled_key, False): - # Re-enable the faulthandler if it was originally enabled. - faulthandler.enable(file=get_stderr_fileno()) + # Re-enable the faulthandler if it was originally enabled. + if fault_handler_original_stderr_fd_key in config.stash: + faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key]) + del config.stash[fault_handler_original_stderr_fd_key] def get_stderr_fileno() -> int: From acab13fcc9f04940721b0a4aa39701100e376b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Tr=C3=B6ger?= Date: Fri, 24 Nov 2023 22:38:34 +1000 Subject: [PATCH 02/23] Add new filtering() method to LogCaptureFixture class (#11625) Fixes #11610 --- AUTHORS | 1 + changelog/11610.feature.rst | 2 ++ src/_pytest/logging.py | 16 ++++++++++++++++ testing/logging/test_fixture.py | 22 ++++++++++++++++++++-- 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 changelog/11610.feature.rst diff --git a/AUTHORS b/AUTHORS index e30131d1a..669ec537e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -188,6 +188,7 @@ Javier Romero Jeff Rackauckas Jeff Widman Jenni Rinker +Jens Tröger John Eddie Ayson John Litborn John Towler diff --git a/changelog/11610.feature.rst b/changelog/11610.feature.rst new file mode 100644 index 000000000..34df34705 --- /dev/null +++ b/changelog/11610.feature.rst @@ -0,0 +1,2 @@ +Added :func:`LogCaptureFixture.filtering() ` context manager that +adds a given :class:`logging.Filter` object to the caplog fixture. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index bacca4b2a..246a4ab6e 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -564,6 +564,22 @@ class LogCaptureFixture: self.handler.setLevel(handler_orig_level) logging.disable(original_disable_level) + @contextmanager + def filtering(self, filter_: logging.Filter) -> Generator[None, None, None]: + """Context manager that temporarily adds the given filter to the caplog's + :meth:`handler` for the 'with' statement block, and removes that filter at the + end of the block. + + :param filter_: A custom :class:`logging.Filter` object. + + .. versionadded:: 7.5 + """ + self.handler.addFilter(filter_) + try: + yield + finally: + self.handler.removeFilter(filter_) + @fixture def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index 753cf5fcd..f4912aecc 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -144,7 +144,7 @@ def test_change_level_undos_handler_level(pytester: Pytester) -> None: result.assert_outcomes(passed=3) -def test_with_statement(caplog: pytest.LogCaptureFixture) -> None: +def test_with_statement_at_level(caplog: pytest.LogCaptureFixture) -> None: with caplog.at_level(logging.INFO): logger.debug("handler DEBUG level") logger.info("handler INFO level") @@ -159,7 +159,9 @@ def test_with_statement(caplog: pytest.LogCaptureFixture) -> None: assert "CRITICAL" in caplog.text -def test_with_statement_logging_disabled(caplog: pytest.LogCaptureFixture) -> None: +def test_with_statement_at_level_logging_disabled( + caplog: pytest.LogCaptureFixture, +) -> None: logging.disable(logging.CRITICAL) assert logging.root.manager.disable == logging.CRITICAL with caplog.at_level(logging.WARNING): @@ -185,6 +187,22 @@ def test_with_statement_logging_disabled(caplog: pytest.LogCaptureFixture) -> No assert logging.root.manager.disable == logging.CRITICAL +def test_with_statement_filtering(caplog: pytest.LogCaptureFixture) -> None: + class TestFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + record.msg = "filtered handler call" + return True + + with caplog.at_level(logging.INFO): + with caplog.filtering(TestFilter()): + logger.info("handler call") + logger.info("handler call") + + filtered_tuple, unfiltered_tuple = caplog.record_tuples + assert filtered_tuple == ("test_fixture", 20, "filtered handler call") + assert unfiltered_tuple == ("test_fixture", 20, "handler call") + + @pytest.mark.parametrize( "level_str,expected_disable_level", [ From e0d5754d5d85267be2de5eceb1ed1a7b331361ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:09:03 -0300 Subject: [PATCH 03/23] [automated] Update plugin list (#11640) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 160 +++++++++++++++++++------------ 1 file changed, 100 insertions(+), 60 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 2e39b4153..6284177b9 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.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 @@ -379,7 +379,7 @@ This list contains 1346 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 Aug 04, 2023 N/A N/A + :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Nov 20, 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) @@ -410,14 +410,14 @@ This list contains 1346 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 07, 2023 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Nov 07, 2023 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Nov 07, 2023 5 - Production/Stable N/A + :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-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) @@ -527,15 +527,16 @@ This list contains 1346 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 15, 2023 1 - Planning pytest >=7.1.2 + :pypi:`pytest-fzf` fzf-based test selector for pytest Nov 20, 2023 4 - Beta pytest >=7.1.2 :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-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 08, 2023 N/A N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Nov 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 @@ -570,9 +571,10 @@ This list contains 1346 plugins. :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest + :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 11, 2023 3 - Alpha pytest ==7.4.3 + :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-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 @@ -611,7 +613,7 @@ This list contains 1346 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 16, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Nov 21, 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 @@ -697,7 +699,7 @@ This list contains 1346 plugins. :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-litter` Pytest plugin which verifies that tests do not modify file trees. Aug 08, 2023 4 - Beta pytest >=6.1 + :pypi:`pytest-litter` Pytest plugin which verifies that tests do not modify file trees. Nov 23, 2023 4 - Beta pytest >=6.1 :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest :pypi:`pytest-local-badge` Generate local badges (shields) reporting your test suite status. Jan 15, 2023 N/A pytest (>=6.1.0) :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Oct 14, 2023 5 - Production/Stable pytest @@ -747,7 +749,7 @@ This list contains 1346 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 Sep 27, 2023 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Nov 21, 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) @@ -838,7 +840,7 @@ This list contains 1346 plugins. :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A - :pypi:`pytest-order` pytest plugin to run your tests in a specific order Mar 10, 2023 4 - Beta pytest (>=5.0) ; python_version < "3.10" + :pypi:`pytest-order` pytest plugin to run your tests in a specific order Nov 18, 2023 4 - Beta pytest >=5.0 ; python_version < "3.10" :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest :pypi:`pytest-order-modify` 新增run_marker 来自定义用例的执行顺序 Nov 04, 2022 N/A N/A :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A @@ -863,6 +865,7 @@ This list contains 1346 plugins. :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A :pypi:`pytest-patch` An automagic \`patch\` fixture that can patch objects directly or by name. Apr 29, 2023 3 - Alpha pytest (>=7.0.0) :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Nov 17, 2023 4 - Beta N/A :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A @@ -939,7 +942,7 @@ This list contains 1346 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 04, 2023 N/A pytest + :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Nov 18, 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) @@ -979,7 +982,7 @@ This list contains 1346 plugins. :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-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Mar 30, 2023 N/A 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 :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Apr 19, 2023 5 - Production/Stable pytest (>=6.2) @@ -1020,10 +1023,10 @@ This list contains 1346 plugins. :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Sep 20, 2022 5 - Production/Stable pytest (>=4.6) :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) :pypi:`pytest-rerun-all` Rerun testsuite for a certain time or iterations Nov 16, 2023 3 - Alpha pytest (>=7.0.0) - :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Jul 05, 2023 5 - Production/Stable pytest (>=6.2) + :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 Oct 05, 2023 N/A pytest ~=4.6 ; python_version == "2.7" + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Nov 22, 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 @@ -1077,7 +1080,7 @@ This list contains 1346 plugins. :pypi:`pytest-screenshot-on-failure` Saves a screenshot when a test case from a pytest execution fails Jul 21, 2023 4 - Beta N/A :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A :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 May 28, 2023 5 - Production/Stable pytest>=6.0.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-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A @@ -1111,6 +1114,7 @@ This list contains 1346 plugins. :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 + :pypi:`pytest-skipuntil` A simple pytest plugin to skip flapping test with deadline Nov 25, 2023 4 - Beta pytest >=3.8.0 :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A :pypi:`pytest-slowest-first` Sort tests by their last duration, slowest first Dec 11, 2022 4 - Beta N/A @@ -1139,7 +1143,7 @@ This list contains 1346 plugins. :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A - :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Sep 15, 2023 N/A pytest (>7.0) + :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 @@ -1150,7 +1154,7 @@ This list contains 1346 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 Oct 23, 2023 N/A pytest (>5.4.0,<8) + :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-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 @@ -1178,6 +1182,7 @@ This list contains 1346 plugins. :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) + :pypi:`pytest-subinterpreter` Run pytest in a subinterpreter Nov 25, 2023 N/A pytest>=7.0.0 :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Jan 28, 2023 5 - Production/Stable pytest (>=4.0.0) :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Jul 16, 2022 N/A N/A :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 15, 2023 4 - Beta pytest (>=7.0) @@ -1217,7 +1222,7 @@ This list contains 1346 plugins. :pypi:`pytest-testinfra-jpic` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A :pypi:`pytest-testinfra-winrm-transport` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) - :pypi:`pytest-testmon` selects tests affected by changed files and methods Nov 07, 2023 4 - Beta pytest <8,>=5 + :pypi:`pytest-testmon` selects tests affected by changed files and methods Nov 23, 2023 4 - Beta pytest <8,>=5 :pypi:`pytest-testmon-dev` selects tests affected by changed files and methods Mar 30, 2023 4 - Beta pytest (<8,>=5) :pypi:`pytest-testmon-oc` nOly selects tests affected by changed files and methods Jun 01, 2022 4 - Beta pytest (<8,>=5) :pypi:`pytest-testmon-skip-libraries` selects tests affected by changed files and methods Mar 03, 2023 4 - Beta pytest (<8,>=5) @@ -1315,8 +1320,8 @@ This list contains 1346 plugins. :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Jun 20, 2022 5 - Production/Stable pytest (>=6.2.2) :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 Aug 04, 2020 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Nov 18, 2023 4 - Beta pytest + :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-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 @@ -1346,7 +1351,7 @@ This list contains 1346 plugins. :pypi:`pytest-wiremock` A pytest plugin for programmatically using wiremock in integration tests Mar 27, 2022 N/A pytest (>=7.1.1,<8.0.0) :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Jan 13, 2023 5 - Production/Stable pytest (>=7.0.0) - :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Nov 11, 2023 5 - Production/Stable pytest >=6.2.0 + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Nov 21, 2023 5 - Production/Stable pytest >=6.2.0 :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) @@ -1694,7 +1699,7 @@ This list contains 1346 plugins. Pytest fixtures to assert anything and something :pypi:`pytest-aoc` - *last release*: Sep 19, 2023, + *last release*: Nov 21, 2023, *status*: 5 - Production/Stable, *requires*: pytest ; extra == 'test' @@ -3801,7 +3806,7 @@ This list contains 1346 plugins. pytest plugin for dogu report :pypi:`pytest-dogu-sdk` - *last release*: Aug 04, 2023, + *last release*: Nov 20, 2023, *status*: N/A, *requires*: N/A @@ -4018,56 +4023,56 @@ This list contains 1346 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4837,8 +4842,8 @@ This list contains 1346 plugins. :pypi:`pytest-fzf` - *last release*: Nov 15, 2023, - *status*: 1 - Planning, + *last release*: Nov 20, 2023, + *status*: 4 - Beta, *requires*: pytest >=7.1.2 fzf-based test selector for pytest @@ -4864,6 +4869,13 @@ This list contains 1346 plugins. Uses gcov to measure test coverage of a C library + :pypi:`pytest-gee` + *last release*: Nov 22, 2023, + *status*: 3 - Alpha, + *requires*: pytest; extra == 'test' + + The Python plugin for your GEE based packages. + :pypi:`pytest-gevent` *last release*: Feb 25, 2020, *status*: N/A, @@ -4893,7 +4905,7 @@ This list contains 1346 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Nov 08, 2023, + *last release*: Nov 20, 2023, *status*: N/A, *requires*: N/A @@ -5137,6 +5149,13 @@ This list contains 1346 plugins. Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report + :pypi:`pytest-history` + *last release*: Nov 20, 2023, + *status*: N/A, + *requires*: pytest (>=7.4.3,<8.0.0) + + Pytest plugin to keep a history of your pytest runs + :pypi:`pytest-home` *last release*: Oct 09, 2023, *status*: 5 - Production/Stable, @@ -5152,7 +5171,7 @@ This list contains 1346 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Nov 11, 2023, + *last release*: Nov 23, 2023, *status*: 3 - Alpha, *requires*: pytest ==7.4.3 @@ -5425,7 +5444,7 @@ This list contains 1346 plugins. :pypi:`pytest-image-snapshot` - *last release*: Nov 16, 2023, + *last release*: Nov 21, 2023, *status*: 4 - Beta, *requires*: pytest >=3.5.0 @@ -6027,7 +6046,7 @@ This list contains 1346 plugins. A pytest plugin that stream output in LITF format :pypi:`pytest-litter` - *last release*: Aug 08, 2023, + *last release*: Nov 23, 2023, *status*: 4 - Beta, *requires*: pytest >=6.1 @@ -6377,7 +6396,7 @@ This list contains 1346 plugins. Custom metrics report for pytest :pypi:`pytest-mh` - *last release*: Sep 27, 2023, + *last release*: Nov 21, 2023, *status*: N/A, *requires*: pytest @@ -7014,9 +7033,9 @@ This list contains 1346 plugins. A pytest plugin for orchestrating tests :pypi:`pytest-order` - *last release*: Mar 10, 2023, + *last release*: Nov 18, 2023, *status*: 4 - Beta, - *requires*: pytest (>=5.0) ; python_version < "3.10" + *requires*: pytest >=5.0 ; python_version < "3.10" pytest plugin to run your tests in a specific order @@ -7188,6 +7207,13 @@ This list contains 1346 plugins. A contextmanager pytest fixture for handling multiple mock patches + :pypi:`pytest-patterns` + *last release*: Nov 17, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin to make testing complicated long string output easy to write and easy to debug + :pypi:`pytest-pdb` *last release*: Jul 31, 2018, *status*: N/A, @@ -7721,7 +7747,7 @@ This list contains 1346 plugins. Record PyMySQL queries and mock with the stored data. :pypi:`pytest-pyodide` - *last release*: Nov 04, 2023, + *last release*: Nov 18, 2023, *status*: N/A, *requires*: pytest @@ -8001,7 +8027,7 @@ This list contains 1346 plugins. Pytest fixtures for REANA. :pypi:`pytest-recorder` - *last release*: Mar 30, 2023, + *last release*: Nov 21, 2023, *status*: N/A, *requires*: N/A @@ -8288,9 +8314,9 @@ This list contains 1346 plugins. Rerun testsuite for a certain time or iterations :pypi:`pytest-rerunfailures` - *last release*: Jul 05, 2023, + *last release*: Nov 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2) + *requires*: pytest >=7 pytest plugin to re-run tests to eliminate flaky failures @@ -8309,7 +8335,7 @@ This list contains 1346 plugins. Pytest fixture for recording and replaying serial port traffic. :pypi:`pytest-resilient-circuits` - *last release*: Oct 05, 2023, + *last release*: Nov 22, 2023, *status*: N/A, *requires*: pytest ~=4.6 ; python_version == "2.7" @@ -8687,7 +8713,7 @@ This list contains 1346 plugins. A pytest plugin which allows to (de-)select tests from a file. :pypi:`pytest-selenium` - *last release*: May 28, 2023, + *last release*: Nov 20, 2023, *status*: 5 - Production/Stable, *requires*: pytest>=6.0.0 @@ -8924,6 +8950,13 @@ This list contains 1346 plugins. A pytest plugin to skip \`@pytest.mark.slow\` tests by default. + :pypi:`pytest-skipuntil` + *last release*: Nov 25, 2023, + *status*: 4 - Beta, + *requires*: pytest >=3.8.0 + + A simple pytest plugin to skip flapping test with deadline + :pypi:`pytest-slack` *last release*: Dec 15, 2020, *status*: 5 - Production/Stable, @@ -9121,7 +9154,7 @@ This list contains 1346 plugins. Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. :pypi:`pytest-spec2md` - *last release*: Sep 15, 2023, + *last release*: Nov 21, 2023, *status*: N/A, *requires*: pytest (>7.0) @@ -9198,7 +9231,7 @@ This list contains 1346 plugins. :pypi:`pytest-splunk-addon` - *last release*: Oct 23, 2023, + *last release*: Nov 25, 2023, *status*: N/A, *requires*: pytest (>5.4.0,<8) @@ -9393,6 +9426,13 @@ This list contains 1346 plugins. A pytest plugin to organize long run tests (named studies) without interfering the regular tests + :pypi:`pytest-subinterpreter` + *last release*: Nov 25, 2023, + *status*: N/A, + *requires*: pytest>=7.0.0 + + Run pytest in a subinterpreter + :pypi:`pytest-subprocess` *last release*: Jan 28, 2023, *status*: 5 - Production/Stable, @@ -9667,7 +9707,7 @@ This list contains 1346 plugins. pytest reporting plugin for testlink :pypi:`pytest-testmon` - *last release*: Nov 07, 2023, + *last release*: Nov 23, 2023, *status*: 4 - Beta, *requires*: pytest <8,>=5 @@ -10353,14 +10393,14 @@ This list contains 1346 plugins. :pypi:`pytest-venv` - *last release*: Aug 04, 2020, + *last release*: Nov 23, 2023, *status*: 4 - Beta, *requires*: pytest py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Nov 18, 2023, + *last release*: Nov 23, 2023, *status*: 4 - Beta, *requires*: pytest @@ -10570,7 +10610,7 @@ This list contains 1346 plugins. A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Nov 11, 2023, + *last release*: Nov 21, 2023, *status*: 5 - Production/Stable, *requires*: pytest >=6.2.0 From 85e0f676c5a545f71cfedb143a75268cda0aadaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sun, 26 Nov 2023 15:09:18 +0100 Subject: [PATCH 04/23] Reset color-related envvars for testing (#11638) Reset color-related environment variables in a fixture to prevent them from affecting test results. Otherwise, some of the tests fail e.g. if NO_COLOR is set in the calling environment. --- changelog/11638.trivial.rst | 1 + testing/conftest.py | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 changelog/11638.trivial.rst diff --git a/changelog/11638.trivial.rst b/changelog/11638.trivial.rst new file mode 100644 index 000000000..374960b89 --- /dev/null +++ b/changelog/11638.trivial.rst @@ -0,0 +1 @@ +Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment. diff --git a/testing/conftest.py b/testing/conftest.py index 6a50e810f..bcb05339b 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -31,6 +31,17 @@ def set_column_width(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("COLUMNS", "80") +@pytest.fixture(autouse=True) +def reset_colors(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Reset all color-related variables to prevent them from affecting internal pytest output + in tests that depend on it. + """ + monkeypatch.delenv("PY_COLORS", raising=False) + monkeypatch.delenv("NO_COLOR", raising=False) + monkeypatch.delenv("FORCE_COLOR", raising=False) + + @pytest.hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems(items) -> Generator[None, None, None]: """Prefer faster tests. From 03220f7c9e1035772bcdb9fd983d165c374d7881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:05:20 +0000 Subject: [PATCH 05/23] build(deps): Bump pytest-rerunfailures in /testing/plugins_integration Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 12.0 to 13.0. - [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/12.0...13.0) --- updated-dependencies: - dependency-name: pytest-rerunfailures dependency-type: direct:production update-type: version-update:semver-major ... 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 fcbe3f168..6224d0b34 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -7,7 +7,7 @@ pytest-django==4.7.0 pytest-flakes==4.0.5 pytest-html==4.1.1 pytest-mock==3.12.0 -pytest-rerunfailures==12.0 +pytest-rerunfailures==13.0 pytest-sugar==0.9.7 pytest-trio==0.7.0 pytest-twisted==1.14.0 From 6053bb8d6a005ea3bd0bd534056f37d073595f83 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:05:25 +0000 Subject: [PATCH 06/23] build(deps): Bump anyio[curio,trio] in /testing/plugins_integration Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 4.0.0 to 4.1.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.0.0...4.1.0) --- updated-dependencies: - dependency-name: anyio[curio,trio] dependency-type: direct:production update-type: version-update:semver-minor ... 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 fcbe3f168..93ead0d3f 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,4 +1,4 @@ -anyio[curio,trio]==4.0.0 +anyio[curio,trio]==4.1.0 django==4.2.7 pytest-asyncio==0.21.1 pytest-bdd==7.0.0 From 2d1710e0e93983134e83e26eacff6a147164baf1 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Mon, 27 Nov 2023 14:47:18 +0000 Subject: [PATCH 07/23] Improve the full diff by having more consistent indentation in the PrettyPrinter (#11571) The normal default pretty printer is not great when objects are nested and it can get hard to read the diff. Instead, provide a pretty printer that behaves more like when json get indented, which allows for smaller, more meaningful differences, at the expense of a slightly longer diff. This does not touch the other places where the pretty printer is used, and only updated the full diff one. --- changelog/1531.improvement.rst | 4 + src/_pytest/_io/pprint.py | 208 +++++++++++------------------- src/_pytest/assertion/util.py | 28 +---- testing/io/test_pprint.py | 222 ++++++++++++++++++++++----------- testing/test_assertion.py | 188 +++++++++++++++++++--------- testing/test_error_diffs.py | 71 +++++++---- 6 files changed, 402 insertions(+), 319 deletions(-) create mode 100644 changelog/1531.improvement.rst diff --git a/changelog/1531.improvement.rst b/changelog/1531.improvement.rst new file mode 100644 index 000000000..d444ea2e7 --- /dev/null +++ b/changelog/1531.improvement.rst @@ -0,0 +1,4 @@ +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/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 9923d0a62..bb59253f7 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -58,7 +58,7 @@ def _safe_tuple(t): class PrettyPrinter: def __init__( self, - indent=1, + indent=4, width=80, depth=None, stream=None, @@ -146,7 +146,6 @@ class PrettyPrinter: def _pprint_dataclass(self, object, stream, indent, allowance, context, level): cls_name = object.__class__.__name__ - indent += len(cls_name) + 1 items = [ (f.name, getattr(object, f.name)) for f in _dataclasses.fields(object) @@ -164,17 +163,11 @@ class PrettyPrinter: def _pprint_dict(self, object, stream, indent, allowance, context, level): write = stream.write write("{") - if self._indent_per_level > 1: - write((self._indent_per_level - 1) * " ") - length = len(object) - if length: - if self._sort_dicts: - items = sorted(object.items(), key=_safe_tuple) - else: - items = object.items() - self._format_dict_items( - items, stream, indent, allowance + 1, context, level - ) + if self._sort_dicts: + items = sorted(object.items(), key=_safe_tuple) + else: + items = object.items() + self._format_dict_items(items, stream, indent, allowance, context, level) write("}") _dispatch[dict.__repr__] = _pprint_dict @@ -185,32 +178,22 @@ class PrettyPrinter: return cls = object.__class__ stream.write(cls.__name__ + "(") - self._format( - list(object.items()), - stream, - indent + len(cls.__name__) + 1, - allowance + 1, - context, - level, - ) + self._pprint_dict(object, stream, indent, allowance, context, level) stream.write(")") _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict def _pprint_list(self, object, stream, indent, allowance, context, level): stream.write("[") - self._format_items(object, stream, indent, allowance + 1, context, level) + self._format_items(object, stream, indent, allowance, context, level) stream.write("]") _dispatch[list.__repr__] = _pprint_list def _pprint_tuple(self, object, stream, indent, allowance, context, level): stream.write("(") - endchar = ",)" if len(object) == 1 else ")" - self._format_items( - object, stream, indent, allowance + len(endchar), context, level - ) - stream.write(endchar) + self._format_items(object, stream, indent, allowance, context, level) + stream.write(")") _dispatch[tuple.__repr__] = _pprint_tuple @@ -225,11 +208,8 @@ class PrettyPrinter: else: stream.write(typ.__name__ + "({") endchar = "})" - indent += len(typ.__name__) + 1 object = sorted(object, key=_safe_key) - self._format_items( - object, stream, indent, allowance + len(endchar), context, level - ) + self._format_items(object, stream, indent, allowance, context, level) stream.write(endchar) _dispatch[set.__repr__] = _pprint_set @@ -319,7 +299,7 @@ class PrettyPrinter: def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level): stream.write("mappingproxy(") - self._format(object.copy(), stream, indent + 13, allowance + 1, context, level) + self._format(object.copy(), stream, indent, allowance, context, level) stream.write(")") _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy @@ -333,7 +313,6 @@ class PrettyPrinter: cls_name = "namespace" else: cls_name = object.__class__.__name__ - indent += len(cls_name) + 1 items = object.__dict__.items() stream.write(cls_name + "(") self._format_namespace_items(items, stream, indent, allowance, context, level) @@ -342,32 +321,30 @@ class PrettyPrinter: _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace def _format_dict_items(self, items, stream, indent, allowance, context, level): + if not items: + return + write = stream.write - indent += self._indent_per_level - delimnl = ",\n" + " " * indent - last_index = len(items) - 1 - for i, (key, ent) in enumerate(items): - last = i == last_index - rep = self._repr(key, context, level) - write(rep) + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) + write(self._repr(key, context, level)) write(": ") - self._format( - ent, - stream, - indent + len(rep) + 2, - allowance if last else 1, - context, - level, - ) - if not last: - write(delimnl) + self._format(ent, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) def _format_namespace_items(self, items, stream, indent, allowance, context, level): + if not items: + return + write = stream.write - delimnl = ",\n" + " " * indent - last_index = len(items) - 1 - for i, (key, ent) in enumerate(items): - last = i == last_index + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + for key, ent in items: + write(delimnl) write(key) write("=") if id(ent) in context: @@ -378,52 +355,30 @@ class PrettyPrinter: self._format( ent, stream, - indent + len(key) + 1, - allowance if last else 1, + item_indent + len(key) + 1, + 1, context, level, ) - if not last: - write(delimnl) + + write(",") + + write("\n" + " " * indent) def _format_items(self, items, stream, indent, allowance, context, level): - write = stream.write - indent += self._indent_per_level - if self._indent_per_level > 1: - write((self._indent_per_level - 1) * " ") - delimnl = ",\n" + " " * indent - delim = "" - width = max_width = self._width - indent + 1 - it = iter(items) - try: - next_ent = next(it) - except StopIteration: + if not items: return - last = False - while not last: - ent = next_ent - try: - next_ent = next(it) - except StopIteration: - last = True - max_width -= allowance - width -= allowance - if self._compact: - rep = self._repr(ent, context, level) - w = len(rep) + 2 - if width < w: - width = max_width - if delim: - delim = delimnl - if width >= w: - width -= w - write(delim) - delim = ", " - write(rep) - continue - write(delim) - delim = delimnl - self._format(ent, stream, indent, allowance if last else 1, context, level) + + write = stream.write + item_indent = indent + self._indent_per_level + delimnl = "\n" + " " * item_indent + + for item in items: + write(delimnl) + self._format(item, stream, item_indent, 1, context, level) + write(",") + + write("\n" + " " * indent) def _repr(self, object, context, level): repr, readable, recursive = self.format( @@ -443,66 +398,45 @@ class PrettyPrinter: return self._safe_repr(object, context, maxlevels, level) def _pprint_default_dict(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return rdf = self._repr(object.default_factory, context, level) - cls = object.__class__ - indent += len(cls.__name__) + 1 - stream.write(f"{cls.__name__}({rdf},\n{' ' * indent}") - self._pprint_dict(object, stream, indent, allowance + 1, context, level) + stream.write(f"{object.__class__.__name__}({rdf}, ") + self._pprint_dict(object, stream, indent, allowance, context, level) stream.write(")") _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict def _pprint_counter(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "({") - if self._indent_per_level > 1: - stream.write((self._indent_per_level - 1) * " ") - items = object.most_common() - self._format_dict_items( - items, stream, indent + len(cls.__name__) + 1, allowance + 2, context, level - ) - stream.write("})") + stream.write(object.__class__.__name__ + "(") + + if object: + stream.write("{") + items = object.most_common() + self._format_dict_items(items, stream, indent, allowance, context, level) + stream.write("}") + + stream.write(")") _dispatch[_collections.Counter.__repr__] = _pprint_counter def _pprint_chain_map(self, object, stream, indent, allowance, context, level): - if not len(object.maps): + if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): stream.write(repr(object)) return - cls = object.__class__ - stream.write(cls.__name__ + "(") - indent += len(cls.__name__) + 1 - for i, m in enumerate(object.maps): - if i == len(object.maps) - 1: - self._format(m, stream, indent, allowance + 1, context, level) - stream.write(")") - else: - self._format(m, stream, indent, 1, context, level) - stream.write(",\n" + " " * indent) + + stream.write(object.__class__.__name__ + "(") + self._format_items(object.maps, stream, indent, allowance, context, level) + stream.write(")") _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map def _pprint_deque(self, object, stream, indent, allowance, context, level): - if not len(object): - stream.write(repr(object)) - return - cls = object.__class__ - stream.write(cls.__name__ + "(") - indent += len(cls.__name__) + 1 + stream.write(object.__class__.__name__ + "(") + if object.maxlen is not None: + stream.write("maxlen=%d, " % object.maxlen) stream.write("[") - if object.maxlen is None: - self._format_items(object, stream, indent, allowance + 2, context, level) - stream.write("])") - else: - self._format_items(object, stream, indent, 2, context, level) - rml = self._repr(object.maxlen, context, level) - stream.write(f"],\n{' ' * indent}maxlen={rml})") + + self._format_items(object, stream, indent, allowance + 1, context, level) + stream.write("])") _dispatch[_collections.deque.__repr__] = _pprint_deque diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 4d9fd114b..214c321f0 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -318,18 +318,6 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: return explanation -def _surrounding_parens_on_own_lines(lines: List[str]) -> None: - """Move opening/closing parenthesis/bracket to own lines.""" - opening = lines[0][:1] - if opening in ["(", "[", "{"]: - lines[0] = " " + lines[0][1:] - lines[:] = [opening] + lines - closing = lines[-1][-1:] - if closing in [")", "]", "}"]: - lines[-1] = lines[-1][:-1] + "," - lines[:] = lines + [closing] - - def _compare_eq_iterable( left: Iterable[Any], right: Iterable[Any], @@ -341,20 +329,8 @@ def _compare_eq_iterable( # dynamic import to speedup pytest import difflib - left_formatting = pprint.pformat(left).splitlines() - right_formatting = pprint.pformat(right).splitlines() - - # Re-format for different output lengths. - lines_left = len(left_formatting) - lines_right = len(right_formatting) - if lines_left != lines_right: - printer = PrettyPrinter() - left_formatting = printer.pformat(left).splitlines() - right_formatting = printer.pformat(right).splitlines() - - if lines_left > 1 or lines_right > 1: - _surrounding_parens_on_own_lines(left_formatting) - _surrounding_parens_on_own_lines(right_formatting) + left_formatting = PrettyPrinter().pformat(left).splitlines() + right_formatting = PrettyPrinter().pformat(right).splitlines() explanation = ["Full diff:"] # "right" is the expected base against which we compare "left", diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 8c15740bd..3432c63f6 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -40,15 +40,19 @@ class DataclassWithTwoItems: pytest.param( DataclassWithOneItem(foo="bar"), """ - DataclassWithOneItem(foo='bar') + DataclassWithOneItem( + foo='bar', + ) """, id="dataclass-one-item", ), pytest.param( DataclassWithTwoItems(foo="foo", bar="bar"), """ - DataclassWithTwoItems(foo='foo', - bar='bar') + DataclassWithTwoItems( + foo='foo', + bar='bar', + ) """, id="dataclass-two-items", ), @@ -60,15 +64,19 @@ class DataclassWithTwoItems: pytest.param( {"one": 1}, """ - {'one': 1} + { + 'one': 1, + } """, id="dict-one-item", ), pytest.param( {"one": 1, "two": 2}, """ - {'one': 1, - 'two': 2} + { + 'one': 1, + 'two': 2, + } """, id="dict-two-items", ), @@ -76,18 +84,19 @@ class DataclassWithTwoItems: pytest.param( OrderedDict({"one": 1}), """ - OrderedDict([('one', - 1)]) + OrderedDict({ + 'one': 1, + }) """, id="ordereddict-one-item", ), pytest.param( OrderedDict({"one": 1, "two": 2}), """ - OrderedDict([('one', - 1), - ('two', - 2)]) + OrderedDict({ + 'one': 1, + 'two': 2, + }) """, id="ordereddict-two-items", ), @@ -99,15 +108,19 @@ class DataclassWithTwoItems: pytest.param( [1], """ - [1] + [ + 1, + ] """, id="list-one-item", ), pytest.param( [1, 2], """ - [1, - 2] + [ + 1, + 2, + ] """, id="list-two-items", ), @@ -119,15 +132,19 @@ class DataclassWithTwoItems: pytest.param( (1,), """ - (1,) + ( + 1, + ) """, id="tuple-one-item", ), pytest.param( (1, 2), """ - (1, - 2) + ( + 1, + 2, + ) """, id="tuple-two-items", ), @@ -139,15 +156,19 @@ class DataclassWithTwoItems: pytest.param( {1}, """ - {1} + { + 1, + } """, id="set-one-item", ), pytest.param( {1, 2}, """ - {1, - 2} + { + 1, + 2, + } """, id="set-two-items", ), @@ -159,15 +180,19 @@ class DataclassWithTwoItems: pytest.param( MappingProxyType({"one": 1}), """ - mappingproxy({'one': 1}) + mappingproxy({ + 'one': 1, + }) """, id="mappingproxy-one-item", ), pytest.param( MappingProxyType({"one": 1, "two": 2}), """ - mappingproxy({'one': 1, - 'two': 2}) + mappingproxy({ + 'one': 1, + 'two': 2, + }) """, id="mappingproxy-two-items", ), @@ -179,15 +204,19 @@ class DataclassWithTwoItems: pytest.param( SimpleNamespace(one=1), """ - namespace(one=1) + namespace( + one=1, + ) """, id="simplenamespace-one-item", ), pytest.param( SimpleNamespace(one=1, two=2), """ - namespace(one=1, - two=2) + namespace( + one=1, + two=2, + ) """, id="simplenamespace-two-items", ), @@ -197,17 +226,19 @@ class DataclassWithTwoItems: pytest.param( defaultdict(str, {"one": "1"}), """ - defaultdict(, - {'one': '1'}) + defaultdict(, { + 'one': '1', + }) """, id="defaultdict-one-item", ), pytest.param( defaultdict(str, {"one": "1", "two": "2"}), """ - defaultdict(, - {'one': '1', - 'two': '2'}) + defaultdict(, { + 'one': '1', + 'two': '2', + }) """, id="defaultdict-two-items", ), @@ -219,15 +250,19 @@ class DataclassWithTwoItems: pytest.param( Counter("1"), """ - Counter({'1': 1}) + Counter({ + '1': 1, + }) """, id="counter-one-item", ), pytest.param( Counter("121"), """ - Counter({'1': 2, - '2': 1}) + Counter({ + '1': 2, + '2': 1, + }) """, id="counter-two-items", ), @@ -235,16 +270,26 @@ class DataclassWithTwoItems: pytest.param( ChainMap({"one": 1, "two": 2}), """ - ChainMap({'one': 1, - 'two': 2}) + ChainMap( + { + 'one': 1, + 'two': 2, + }, + ) """, id="chainmap-one-item", ), pytest.param( ChainMap({"one": 1}, {"two": 2}), """ - ChainMap({'one': 1}, - {'two': 2}) + ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ) """, id="chainmap-two-items", ), @@ -256,24 +301,29 @@ class DataclassWithTwoItems: pytest.param( deque([1]), """ - deque([1]) + deque([ + 1, + ]) """, id="deque-one-item", ), pytest.param( deque([1, 2]), """ - deque([1, - 2]) + deque([ + 1, + 2, + ]) """, id="deque-two-items", ), pytest.param( deque([1, 2], maxlen=3), """ - deque([1, - 2], - maxlen=3) + deque(maxlen=3, [ + 1, + 2, + ]) """, id="deque-maxlen", ), @@ -293,34 +343,60 @@ class DataclassWithTwoItems: "tuple": (1, 2), }, """ - {'chainmap': ChainMap({'one': 1}, - {'two': 2}), - 'counter': Counter({'2': 2, - '1': 1}), - 'dataclass': DataclassWithTwoItems(foo='foo', - bar='bar'), - 'defaultdict': defaultdict(, - {'one': '1', - 'two': '2'}), - 'deque': deque([1, - 2], - maxlen=3), - 'dict': {'one': 1, - 'two': 2}, - 'list': [1, - 2], - 'mappingproxy': mappingproxy({'one': 1, - 'two': 2}), - 'ordereddict': OrderedDict([('one', - 1), - ('two', - 2)]), - 'set': {1, - 2}, - 'simplenamespace': namespace(one=1, - two=2), - 'tuple': (1, - 2)} + { + 'chainmap': ChainMap( + { + 'one': 1, + }, + { + 'two': 2, + }, + ), + 'counter': Counter({ + '2': 2, + '1': 1, + }), + 'dataclass': DataclassWithTwoItems( + foo='foo', + bar='bar', + ), + 'defaultdict': defaultdict(, { + 'one': '1', + 'two': '2', + }), + 'deque': deque(maxlen=3, [ + 1, + 2, + ]), + 'dict': { + 'one': 1, + 'two': 2, + }, + 'list': [ + 1, + 2, + ], + 'mappingproxy': mappingproxy({ + 'one': 1, + 'two': 2, + }), + 'ordereddict': OrderedDict({ + 'one': 1, + 'two': 2, + }), + 'set': { + 1, + 2, + }, + 'simplenamespace': namespace( + one=1, + two=2, + ), + 'tuple': ( + 1, + 2, + ), + } """, id="deep-example", ), diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 7c8c01556..ce10ed8c4 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -451,11 +451,14 @@ class TestAssert_reprcompare: [0, 2], """ Full diff: - - [0, 2] + [ + 0, + - 2, ? ^ - + [0, 1] + + 1, ? ^ - """, + ] + """, id="lists", ), pytest.param( @@ -463,10 +466,12 @@ class TestAssert_reprcompare: {0: 2}, """ Full diff: - - {0: 2} - ? ^ - + {0: 1} - ? ^ + { + - 0: 2, + ? ^ + + 0: 1, + ? ^ + } """, id="dicts", ), @@ -475,10 +480,13 @@ class TestAssert_reprcompare: {0, 2}, """ Full diff: - - {0, 2} + { + 0, + - 2, ? ^ - + {0, 1} + + 1, ? ^ + } """, id="sets", ), @@ -542,10 +550,10 @@ class TestAssert_reprcompare: "Right contains one more item: '" + long_d + "'", "Full diff:", " [", - " 'a',", - " 'b',", - " 'c',", - "- '" + long_d + "',", + " 'a',", + " 'b',", + " 'c',", + "- '" + long_d + "',", " ]", ] @@ -555,10 +563,10 @@ class TestAssert_reprcompare: "Left contains one more item: '" + long_d + "'", "Full diff:", " [", - " 'a',", - " 'b',", - " 'c',", - "+ '" + long_d + "',", + " 'a',", + " 'b',", + " 'c',", + "+ '" + long_d + "',", " ]", ] @@ -574,10 +582,10 @@ class TestAssert_reprcompare: "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", "Full diff:", " [", - "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", - " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", - " 'cccccccccccccccccccccccccccccc',", - "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", + " 'cccccccccccccccccccccccccccccc',", + "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", " ]", ] @@ -592,15 +600,15 @@ class TestAssert_reprcompare: "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", "Full diff:", " [", - "- 'should not get wrapped',", - "+ 'a',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", + "- 'should not get wrapped',", + "+ 'a',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", " ]", ] @@ -615,13 +623,17 @@ class TestAssert_reprcompare: "Differing items:", "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}", "Full diff:", - "- {'common': 1, 'env': {'env1': 1}}", - "+ {'common': 1, 'env': {'env1': 1, 'env2': 2}}", - "? +++++++++++", + " {", + " 'common': 1,", + " 'env': {", + " 'env1': 1,", + "+ 'env2': 2,", + " },", + " }", ] long_a = "a" * 80 - sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}} + sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 3}} d1 = {"env": {"sub": sub}} d2 = {"env": {"sub": sub}, "new": 1} diff = callequal(d1, d2, verbose=True) @@ -632,10 +644,16 @@ class TestAssert_reprcompare: "{'new': 1}", "Full diff:", " {", - " 'env': {'sub': {'long_a': '" + long_a + "',", - " 'sub1': {'long_a': 'substring that gets wrapped substring '", - " 'that gets wrapped '}}},", - "- 'new': 1,", + " 'env': {", + " 'sub': {", + f" 'long_a': '{long_a}',", + " 'sub1': {", + " 'long_a': 'substring that gets wrapped substring that gets wrapped '", + " 'substring that gets wrapped ',", + " },", + " },", + " },", + "- 'new': 1,", " }", ] @@ -677,8 +695,13 @@ class TestAssert_reprcompare: "Right contains 2 more items:", "{'b': 1, 'c': 2}", "Full diff:", - "- {'b': 1, 'c': 2}", - "+ {'a': 0}", + " {", + "- 'b': 1,", + "? ^ ^", + "+ 'a': 0,", + "? ^ ^", + "- 'c': 2,", + " }", ] lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) assert lines == [ @@ -688,8 +711,13 @@ class TestAssert_reprcompare: "Right contains 1 more item:", "{'a': 0}", "Full diff:", - "- {'a': 0}", - "+ {'b': 1, 'c': 2}", + " {", + "- 'a': 0,", + "? ^ ^", + "+ 'b': 1,", + "? ^ ^", + "+ 'c': 2,", + " }", ] def test_sequence_different_items(self) -> None: @@ -699,8 +727,17 @@ class TestAssert_reprcompare: "At index 0 diff: 1 != 3", "Right contains one more item: 5", "Full diff:", - "- (3, 4, 5)", - "+ (1, 2)", + " (", + "- 3,", + "? ^", + "+ 1,", + "? ^", + "- 4,", + "? ^", + "+ 2,", + "? ^", + "- 5,", + " )", ] lines = callequal((1, 2, 3), (4,), verbose=2) assert lines == [ @@ -708,8 +745,27 @@ class TestAssert_reprcompare: "At index 0 diff: 1 != 4", "Left contains 2 more items, first extra item: 2", "Full diff:", - "- (4,)", - "+ (1, 2, 3)", + " (", + "- 4,", + "? ^", + "+ 1,", + "? ^", + "+ 2,", + "+ 3,", + " )", + ] + 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,", + "- 20,", + "? -", + "+ 2,", + " 3,", + " )", ] def test_set(self) -> None: @@ -1844,8 +1900,8 @@ def test_reprcompare_verbose_long() -> None: assert [0, 1] == [0, 2] """, [ - "{bold}{red}E {light-red}- [0, 2]{hl-reset}{endline}{reset}", - "{bold}{red}E {light-green}+ [0, 1]{hl-reset}{endline}{reset}", + "{bold}{red}E {light-red}- 2,{hl-reset}{endline}{reset}", + "{bold}{red}E {light-green}+ 1,{hl-reset}{endline}{reset}", ], ), ( @@ -1857,8 +1913,8 @@ def test_reprcompare_verbose_long() -> None: """, [ "{bold}{red}E {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}", + "{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}", ], ), ), @@ -1917,14 +1973,32 @@ def test_fine_grained_assertion_verbosity(pytester: Pytester): f"{p.name} .FFF [100%]", "E At index 2 diff: 'grapes' != 'orange'", "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 ]", "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 }", f"E AssertionError: assert 'hello world' in '{long_text}'", ] ) diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index eb7812108..cad7a17c0 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -21,10 +21,14 @@ TESTCASES = [ E assert [1, 4, 3] == [1, 2, 3] E At index 1 diff: 4 != 2 E Full diff: - E - [1, 2, 3] + E [ + E 1, + E - 2, E ? ^ - E + [1, 4, 3] + E + 4, E ? ^ + E 3, + E ] """, id="Compare lists, one item differs", ), @@ -40,9 +44,11 @@ TESTCASES = [ E assert [1, 2, 3] == [1, 2] E Left contains one more item: 3 E Full diff: - E - [1, 2] - E + [1, 2, 3] - E ? +++ + E [ + E 1, + E 2, + E + 3, + E ] """, id="Compare lists, one extra item", ), @@ -59,9 +65,11 @@ TESTCASES = [ E At index 1 diff: 3 != 2 E Right contains one more item: 3 E Full diff: - E - [1, 2, 3] - E ? --- - E + [1, 3] + E [ + E 1, + E - 2, + E 3, + E ] """, id="Compare lists, one item missing", ), @@ -77,10 +85,14 @@ TESTCASES = [ E assert (1, 4, 3) == (1, 2, 3) E At index 1 diff: 4 != 2 E Full diff: - E - (1, 2, 3) + E ( + E 1, + E - 2, E ? ^ - E + (1, 4, 3) + E + 4, E ? ^ + E 3, + E ) """, id="Compare tuples", ), @@ -99,10 +111,12 @@ TESTCASES = [ E Extra items in the right set: E 2 E Full diff: - E - {1, 2, 3} - E ? ^ ^ - E + {1, 3, 4} - E ? ^ ^ + E { + E 1, + E - 2, + E 3, + E + 4, + E } """, id="Compare sets", ), @@ -123,10 +137,13 @@ TESTCASES = [ E Right contains 1 more item: E {2: 'eggs'} E Full diff: - E - {1: 'spam', 2: 'eggs'} - E ? ^ - E + {1: 'spam', 3: 'eggs'} - E ? ^ + E { + E 1: 'spam', + E - 2: 'eggs', + E ? ^ + E + 3: 'eggs', + E ? ^ + E } """, id="Compare dicts with differing keys", ), @@ -145,10 +162,11 @@ TESTCASES = [ E Differing items: E {2: 'eggs'} != {2: 'bacon'} E Full diff: - E - {1: 'spam', 2: 'bacon'} - E ? ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^^^^ + E { + E 1: 'spam', + E - 2: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing values", ), @@ -169,10 +187,11 @@ TESTCASES = [ E Right contains 1 more item: E {3: 'bacon'} E Full diff: - E - {1: 'spam', 3: 'bacon'} - E ? ^ ^^^^^ - E + {1: 'spam', 2: 'eggs'} - E ? ^ ^^^^ + E { + E 1: 'spam', + E - 3: 'bacon', + E + 2: 'eggs', + E } """, id="Compare dicts with differing items", ), From c4375f14b86e797446d44ee11d6335ffe9251c73 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:55:06 +0000 Subject: [PATCH 08/23] [pre-commit.ci] pre-commit autoupdate (#11645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1) 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 1fb026602..9b1ee9cda 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,7 +56,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.0 + rev: v1.7.1 hooks: - id: mypy files: ^(src/|testing/) From 64e72b79f6bd8eb0e6bf2b1a7456a026a88127fa Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 21 Nov 2023 21:35:27 +0000 Subject: [PATCH 09/23] pprint: Remove unused arguments on PrettyPrinter --- src/_pytest/_io/pprint.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index bb59253f7..8dc2dc028 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -15,7 +15,6 @@ import collections as _collections import dataclasses as _dataclasses import re -import sys as _sys import types as _types from io import StringIO as _StringIO from typing import Any @@ -61,9 +60,7 @@ class PrettyPrinter: indent=4, width=80, depth=None, - stream=None, *, - compact=False, sort_dicts=True, underscore_numbers=False, ): @@ -79,13 +76,6 @@ class PrettyPrinter: depth The maximum depth to print out nested structures. - stream - The desired output stream. If omitted (or false), the standard - output stream available at construction will be used. - - compact - If true, several items will be combined in one line. - sort_dicts If true, dict keys are sorted. @@ -101,11 +91,6 @@ class PrettyPrinter: self._depth = depth self._indent_per_level = indent self._width = width - if stream is not None: - self._stream = stream - else: - self._stream = _sys.stdout - self._compact = bool(compact) self._sort_dicts = sort_dicts self._underscore_numbers = underscore_numbers From e5a448cd5f36bb715e75efe01da89fe0fa52d6b1 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 21 Nov 2023 22:05:37 +0000 Subject: [PATCH 10/23] pprint: Type annotate the module This will make it easier to refactor --- src/_pytest/_io/pprint.py | 253 ++++++++++++++++++++++++++++++++------ 1 file changed, 217 insertions(+), 36 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 8dc2dc028..876723fbb 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -21,7 +21,10 @@ from typing import Any from typing import Callable from typing import Dict from typing import IO +from typing import Iterator from typing import List +from typing import Optional +from typing import Tuple class _safe_key: @@ -57,13 +60,13 @@ def _safe_tuple(t): class PrettyPrinter: def __init__( self, - indent=4, - width=80, - depth=None, + indent: int = 4, + width: int = 80, + depth: Optional[int] = None, *, - sort_dicts=True, - underscore_numbers=False, - ): + sort_dicts: bool = True, + underscore_numbers: bool = False, + ) -> None: """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -99,7 +102,15 @@ class PrettyPrinter: self._format(object, sio, 0, 0, {}, 0) return sio.getvalue() - def _format(self, object, stream, indent, allowance, context, level): + def _format( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: objid = id(object) if objid in context: stream.write(_recursion(object)) @@ -129,7 +140,15 @@ class PrettyPrinter: else: stream.write(self._repr(object, context, level)) - def _pprint_dataclass(self, object, stream, indent, allowance, context, level): + def _pprint_dataclass( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: cls_name = object.__class__.__name__ items = [ (f.name, getattr(object, f.name)) @@ -142,10 +161,18 @@ class PrettyPrinter: _dispatch: Dict[ Callable[..., str], - Callable[["PrettyPrinter", Any, IO[str], int, int, Dict[int, int], int], str], + Callable[["PrettyPrinter", Any, IO[str], int, int, Dict[int, int], int], None], ] = {} - def _pprint_dict(self, object, stream, indent, allowance, context, level): + def _pprint_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: write = stream.write write("{") if self._sort_dicts: @@ -157,7 +184,15 @@ class PrettyPrinter: _dispatch[dict.__repr__] = _pprint_dict - def _pprint_ordered_dict(self, object, stream, indent, allowance, context, level): + def _pprint_ordered_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not len(object): stream.write(repr(object)) return @@ -168,21 +203,45 @@ class PrettyPrinter: _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict - def _pprint_list(self, object, stream, indent, allowance, context, level): + def _pprint_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: stream.write("[") self._format_items(object, stream, indent, allowance, context, level) stream.write("]") _dispatch[list.__repr__] = _pprint_list - def _pprint_tuple(self, object, stream, indent, allowance, context, level): + def _pprint_tuple( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: stream.write("(") self._format_items(object, stream, indent, allowance, context, level) stream.write(")") _dispatch[tuple.__repr__] = _pprint_tuple - def _pprint_set(self, object, stream, indent, allowance, context, level): + def _pprint_set( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not len(object): stream.write(repr(object)) return @@ -200,7 +259,15 @@ class PrettyPrinter: _dispatch[set.__repr__] = _pprint_set _dispatch[frozenset.__repr__] = _pprint_set - def _pprint_str(self, object, stream, indent, allowance, context, level): + def _pprint_str( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: write = stream.write if not len(object): write(repr(object)) @@ -251,7 +318,15 @@ class PrettyPrinter: _dispatch[str.__repr__] = _pprint_str - def _pprint_bytes(self, object, stream, indent, allowance, context, level): + def _pprint_bytes( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: write = stream.write if len(object) <= 4: write(repr(object)) @@ -272,7 +347,15 @@ class PrettyPrinter: _dispatch[bytes.__repr__] = _pprint_bytes - def _pprint_bytearray(self, object, stream, indent, allowance, context, level): + def _pprint_bytearray( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: write = stream.write write("bytearray(") self._pprint_bytes( @@ -282,7 +365,15 @@ class PrettyPrinter: _dispatch[bytearray.__repr__] = _pprint_bytearray - def _pprint_mappingproxy(self, object, stream, indent, allowance, context, level): + def _pprint_mappingproxy( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: stream.write("mappingproxy(") self._format(object.copy(), stream, indent, allowance, context, level) stream.write(")") @@ -290,8 +381,14 @@ class PrettyPrinter: _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy def _pprint_simplenamespace( - self, object, stream, indent, allowance, context, level - ): + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if type(object) is _types.SimpleNamespace: # The SimpleNamespace repr is "namespace" instead of the class # name, so we do the same here. For subclasses; use the class name. @@ -305,7 +402,15 @@ class PrettyPrinter: _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace - def _format_dict_items(self, items, stream, indent, allowance, context, level): + def _format_dict_items( + self, + items: List[Tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not items: return @@ -321,7 +426,15 @@ class PrettyPrinter: write("\n" + " " * indent) - def _format_namespace_items(self, items, stream, indent, allowance, context, level): + def _format_namespace_items( + self, + items: List[Tuple[Any, Any]], + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not items: return @@ -350,7 +463,15 @@ class PrettyPrinter: write("\n" + " " * indent) - def _format_items(self, items, stream, indent, allowance, context, level): + def _format_items( + self, + items: List[Any], + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not items: return @@ -365,7 +486,7 @@ class PrettyPrinter: write("\n" + " " * indent) - def _repr(self, object, context, level): + def _repr(self, object: Any, context: Dict[int, int], level: int) -> str: repr, readable, recursive = self.format( object, context.copy(), self._depth, level ) @@ -375,14 +496,24 @@ class PrettyPrinter: self._recursive = True return repr - def format(self, object, context, maxlevels, level): + def format( + self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int + ) -> Tuple[str, bool, bool]: """Format object for a specific context, returning a string and flags indicating whether the representation is 'readable' and whether the object represents a recursive construct. """ return self._safe_repr(object, context, maxlevels, level) - def _pprint_default_dict(self, object, stream, indent, allowance, context, level): + def _pprint_default_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: rdf = self._repr(object.default_factory, context, level) stream.write(f"{object.__class__.__name__}({rdf}, ") self._pprint_dict(object, stream, indent, allowance, context, level) @@ -390,7 +521,15 @@ class PrettyPrinter: _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict - def _pprint_counter(self, object, stream, indent, allowance, context, level): + def _pprint_counter( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: stream.write(object.__class__.__name__ + "(") if object: @@ -403,7 +542,15 @@ class PrettyPrinter: _dispatch[_collections.Counter.__repr__] = _pprint_counter - def _pprint_chain_map(self, object, stream, indent, allowance, context, level): + def _pprint_chain_map( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): stream.write(repr(object)) return @@ -414,7 +561,15 @@ class PrettyPrinter: _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map - def _pprint_deque(self, object, stream, indent, allowance, context, level): + def _pprint_deque( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: stream.write(object.__class__.__name__ + "(") if object.maxlen is not None: stream.write("maxlen=%d, " % object.maxlen) @@ -425,22 +580,48 @@ class PrettyPrinter: _dispatch[_collections.deque.__repr__] = _pprint_deque - def _pprint_user_dict(self, object, stream, indent, allowance, context, level): + def _pprint_user_dict( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict - def _pprint_user_list(self, object, stream, indent, allowance, context, level): + def _pprint_user_list( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) _dispatch[_collections.UserList.__repr__] = _pprint_user_list - def _pprint_user_string(self, object, stream, indent, allowance, context, level): + def _pprint_user_string( + self, + object: Any, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, int], + level: int, + ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) _dispatch[_collections.UserString.__repr__] = _pprint_user_string - def _safe_repr(self, object, context, maxlevels, level): + def _safe_repr( + self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int + ) -> Tuple[str, bool, bool]: # Return triple (repr_string, isreadable, isrecursive). typ = type(object) if typ in _builtin_scalars: @@ -517,17 +698,17 @@ class PrettyPrinter: return format % ", ".join(components), readable, recursive rep = repr(object) - return rep, (rep and not rep.startswith("<")), False + return rep, bool(rep and not rep.startswith("<")), False _builtin_scalars = frozenset({str, bytes, bytearray, float, complex, bool, type(None)}) -def _recursion(object): +def _recursion(object: Any) -> str: return f"" -def _wrap_bytes_repr(object, width, allowance): +def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]: current = b"" last = len(object) // 4 * 4 for i in range(0, len(object), 4): From 767f08cecdc32b3ae11d40ab569d74f5f3fafad0 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 21 Nov 2023 22:08:15 +0000 Subject: [PATCH 11/23] pprint: Remove tracking of whether the object is recursive This information is not used anywhere, we can simplify by just not tracking it --- src/_pytest/_io/pprint.py | 54 +++++++++++++++------------------------ 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 876723fbb..02f43a0bd 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -114,7 +114,6 @@ class PrettyPrinter: objid = id(object) if objid in context: stream.write(_recursion(object)) - self._recursive = True self._readable = False return @@ -487,21 +486,16 @@ class PrettyPrinter: write("\n" + " " * indent) def _repr(self, object: Any, context: Dict[int, int], level: int) -> str: - repr, readable, recursive = self.format( - object, context.copy(), self._depth, level - ) + repr, readable = self.format(object, context.copy(), self._depth, level) if not readable: self._readable = False - if recursive: - self._recursive = True return repr def format( self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int - ) -> Tuple[str, bool, bool]: + ) -> Tuple[str, bool]: """Format object for a specific context, returning a string - and flags indicating whether the representation is 'readable' - and whether the object represents a recursive construct. + and a flag indicating whether the representation is 'readable'. """ return self._safe_repr(object, context, maxlevels, level) @@ -621,31 +615,30 @@ class PrettyPrinter: def _safe_repr( self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int - ) -> Tuple[str, bool, bool]: - # Return triple (repr_string, isreadable, isrecursive). + ) -> Tuple[str, bool]: + # Return pair (repr_string, isreadable). typ = type(object) if typ in _builtin_scalars: - return repr(object), True, False + return repr(object), True r = getattr(typ, "__repr__", None) if issubclass(typ, int) and r is int.__repr__: if self._underscore_numbers: - return f"{object:_d}", True, False + return f"{object:_d}", True else: - return repr(object), True, False + return repr(object), True if issubclass(typ, dict) and r is dict.__repr__: if not object: - return "{}", True, False + return "{}", True objid = id(object) if maxlevels and level >= maxlevels: - return "{...}", False, objid in context + return "{...}", False if objid in context: - return _recursion(object), False, True + return _recursion(object), False context[objid] = 1 readable = True - recursive = False components: List[str] = [] append = components.append level += 1 @@ -654,51 +647,46 @@ class PrettyPrinter: else: items = object.items() for k, v in items: - krepr, kreadable, krecur = self.format(k, context, maxlevels, level) - vrepr, vreadable, vrecur = self.format(v, context, maxlevels, level) + krepr, kreadable = self.format(k, context, maxlevels, level) + vrepr, vreadable = self.format(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") readable = readable and kreadable and vreadable - if krecur or vrecur: - recursive = True del context[objid] - return "{%s}" % ", ".join(components), readable, recursive + return "{%s}" % ", ".join(components), readable if (issubclass(typ, list) and r is list.__repr__) or ( issubclass(typ, tuple) and r is tuple.__repr__ ): if issubclass(typ, list): if not object: - return "[]", True, False + return "[]", True format = "[%s]" elif len(object) == 1: format = "(%s,)" else: if not object: - return "()", True, False + return "()", True format = "(%s)" objid = id(object) if maxlevels and level >= maxlevels: - return format % "...", False, objid in context + return format % "...", False if objid in context: - return _recursion(object), False, True + return _recursion(object), False context[objid] = 1 readable = True - recursive = False components = [] append = components.append level += 1 for o in object: - orepr, oreadable, orecur = self.format(o, context, maxlevels, level) + orepr, oreadable = self.format(o, context, maxlevels, level) append(orepr) if not oreadable: readable = False - if orecur: - recursive = True del context[objid] - return format % ", ".join(components), readable, recursive + return format % ", ".join(components), readable rep = repr(object) - return rep, bool(rep and not rep.startswith("<")), False + return rep, bool(rep and not rep.startswith("<")) _builtin_scalars = frozenset({str, bytes, bytearray, float, complex, bool, type(None)}) From 50607297f45bae1e786217bb6ad89fc2d1748d39 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 21 Nov 2023 22:12:33 +0000 Subject: [PATCH 12/23] pprint: Remove tracking of whether an object is readable This information is not used anywhere --- src/_pytest/_io/pprint.py | 52 ++++++++++++++------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 02f43a0bd..c21346035 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -114,7 +114,6 @@ class PrettyPrinter: objid = id(object) if objid in context: stream.write(_recursion(object)) - self._readable = False return p = self._dispatch.get(type(object).__repr__, None) @@ -486,17 +485,11 @@ class PrettyPrinter: write("\n" + " " * indent) def _repr(self, object: Any, context: Dict[int, int], level: int) -> str: - repr, readable = self.format(object, context.copy(), self._depth, level) - if not readable: - self._readable = False - return repr + return self.format(object, context.copy(), self._depth, level) def format( self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int - ) -> Tuple[str, bool]: - """Format object for a specific context, returning a string - and a flag indicating whether the representation is 'readable'. - """ + ) -> str: return self._safe_repr(object, context, maxlevels, level) def _pprint_default_dict( @@ -615,30 +608,28 @@ class PrettyPrinter: def _safe_repr( self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int - ) -> Tuple[str, bool]: - # Return pair (repr_string, isreadable). + ) -> str: typ = type(object) if typ in _builtin_scalars: - return repr(object), True + return repr(object) r = getattr(typ, "__repr__", None) if issubclass(typ, int) and r is int.__repr__: if self._underscore_numbers: - return f"{object:_d}", True + return f"{object:_d}" else: - return repr(object), True + return repr(object) if issubclass(typ, dict) and r is dict.__repr__: if not object: - return "{}", True + return "{}" objid = id(object) if maxlevels and level >= maxlevels: - return "{...}", False + return "{...}" if objid in context: - return _recursion(object), False + return _recursion(object) context[objid] = 1 - readable = True components: List[str] = [] append = components.append level += 1 @@ -647,46 +638,41 @@ class PrettyPrinter: else: items = object.items() for k, v in items: - krepr, kreadable = self.format(k, context, maxlevels, level) - vrepr, vreadable = self.format(v, context, maxlevels, level) + krepr = self.format(k, context, maxlevels, level) + vrepr = self.format(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") - readable = readable and kreadable and vreadable del context[objid] - return "{%s}" % ", ".join(components), readable + return "{%s}" % ", ".join(components) if (issubclass(typ, list) and r is list.__repr__) or ( issubclass(typ, tuple) and r is tuple.__repr__ ): if issubclass(typ, list): if not object: - return "[]", True + return "[]" format = "[%s]" elif len(object) == 1: format = "(%s,)" else: if not object: - return "()", True + return "()" format = "(%s)" objid = id(object) if maxlevels and level >= maxlevels: - return format % "...", False + return format % "..." if objid in context: - return _recursion(object), False + return _recursion(object) context[objid] = 1 - readable = True components = [] append = components.append level += 1 for o in object: - orepr, oreadable = self.format(o, context, maxlevels, level) + orepr = self.format(o, context, maxlevels, level) append(orepr) - if not oreadable: - readable = False del context[objid] - return format % ", ".join(components), readable + return format % ", ".join(components) - rep = repr(object) - return rep, bool(rep and not rep.startswith("<")) + return repr(object) _builtin_scalars = frozenset({str, bytes, bytearray, float, complex, bool, type(None)}) From 88c35460060d7e5c5531a4b47aef3a53583d9c10 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Tue, 21 Nov 2023 22:18:13 +0000 Subject: [PATCH 13/23] pprint: use a set instead of a dict for the context This is really what the context is doing, we don't need to use a dict for it --- src/_pytest/_io/pprint.py | 71 ++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index c21346035..ad1238709 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -24,6 +24,7 @@ from typing import IO from typing import Iterator from typing import List from typing import Optional +from typing import Set from typing import Tuple @@ -99,7 +100,7 @@ class PrettyPrinter: def pformat(self, object: Any) -> str: sio = _StringIO() - self._format(object, sio, 0, 0, {}, 0) + self._format(object, sio, 0, 0, set(), 0) return sio.getvalue() def _format( @@ -108,7 +109,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: objid = id(object) @@ -118,9 +119,9 @@ class PrettyPrinter: p = self._dispatch.get(type(object).__repr__, None) if p is not None: - context[objid] = 1 + context.add(objid) p(self, object, stream, indent, allowance, context, level + 1) - del context[objid] + context.remove(objid) elif ( _dataclasses.is_dataclass(object) and not isinstance(object, type) @@ -130,11 +131,11 @@ class PrettyPrinter: hasattr(object.__repr__, "__wrapped__") and "__create_fn__" in object.__repr__.__wrapped__.__qualname__ ): - context[objid] = 1 + context.add(objid) self._pprint_dataclass( object, stream, indent, allowance, context, level + 1 ) - del context[objid] + context.remove(objid) else: stream.write(self._repr(object, context, level)) @@ -144,7 +145,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: cls_name = object.__class__.__name__ @@ -159,7 +160,7 @@ class PrettyPrinter: _dispatch: Dict[ Callable[..., str], - Callable[["PrettyPrinter", Any, IO[str], int, int, Dict[int, int], int], None], + Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None], ] = {} def _pprint_dict( @@ -168,7 +169,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: write = stream.write @@ -188,7 +189,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not len(object): @@ -207,7 +208,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: stream.write("[") @@ -222,7 +223,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: stream.write("(") @@ -237,7 +238,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not len(object): @@ -263,7 +264,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: write = stream.write @@ -322,7 +323,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: write = stream.write @@ -351,7 +352,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: write = stream.write @@ -369,7 +370,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: stream.write("mappingproxy(") @@ -384,7 +385,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if type(object) is _types.SimpleNamespace: @@ -406,7 +407,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not items: @@ -430,7 +431,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not items: @@ -467,7 +468,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not items: @@ -484,11 +485,11 @@ class PrettyPrinter: write("\n" + " " * indent) - def _repr(self, object: Any, context: Dict[int, int], level: int) -> str: + 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: Dict[int, int], maxlevels: Optional[int], level: int + self, object: Any, context: Set[int], maxlevels: Optional[int], level: int ) -> str: return self._safe_repr(object, context, maxlevels, level) @@ -498,7 +499,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: rdf = self._repr(object.default_factory, context, level) @@ -514,7 +515,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -535,7 +536,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): @@ -554,7 +555,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -573,7 +574,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -586,7 +587,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -599,7 +600,7 @@ class PrettyPrinter: stream: IO[str], indent: int, allowance: int, - context: Dict[int, int], + context: Set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -607,7 +608,7 @@ class PrettyPrinter: _dispatch[_collections.UserString.__repr__] = _pprint_user_string def _safe_repr( - self, object: Any, context: Dict[int, int], maxlevels: Optional[int], level: int + self, object: Any, context: Set[int], maxlevels: Optional[int], level: int ) -> str: typ = type(object) if typ in _builtin_scalars: @@ -629,7 +630,7 @@ class PrettyPrinter: return "{...}" if objid in context: return _recursion(object) - context[objid] = 1 + context.add(objid) components: List[str] = [] append = components.append level += 1 @@ -641,7 +642,7 @@ class PrettyPrinter: krepr = self.format(k, context, maxlevels, level) vrepr = self.format(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") - del context[objid] + context.remove(objid) return "{%s}" % ", ".join(components) if (issubclass(typ, list) and r is list.__repr__) or ( @@ -662,14 +663,14 @@ class PrettyPrinter: return format % "..." if objid in context: return _recursion(object) - context[objid] = 1 + context.add(objid) components = [] append = components.append level += 1 for o in object: orepr = self.format(o, context, maxlevels, level) append(orepr) - del context[objid] + context.remove(objid) return format % ", ".join(components) return repr(object) From ad1bccdeade5472434fe77e9890472287cb0969f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 28 Nov 2023 17:36:05 +0200 Subject: [PATCH 14/23] pytester: avoid EncodingWarning from `locale.getpreferredencoding` When running `tox -e py-lsof` I get a deluge of this warning: ``` src/pytest/.tox/py-lsof-numpy-pexpect/lib/python3.11/site-packages/_pytest/pytester.py:130: EncodingWarning: UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead. ``` Use `locale.getencoding` instead. --- src/_pytest/pytester.py | 7 ++++++- testing/test_parseopt.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 99d33954d..f93e9c94a 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -121,13 +121,18 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: def get_open_files(self) -> List[Tuple[str, str]]: + if sys.version_info >= (3, 11): + # New in Python 3.11, ignores utf-8 mode + encoding = locale.getencoding() + else: + encoding = locale.getpreferredencoding(False) out = subprocess.run( ("lsof", "-Ffn0", "-p", str(os.getpid())), stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, text=True, - encoding=locale.getpreferredencoding(False), + encoding=encoding, ).stdout def isopen(line: str) -> bool: diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index b6df035aa..1b80883ee 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -290,10 +290,10 @@ class TestParser: def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: - try: + if sys.version_info >= (3, 11): # New in Python 3.11, ignores utf-8 mode - encoding = locale.getencoding() # type: ignore[attr-defined] - except AttributeError: + encoding = locale.getencoding() + else: encoding = locale.getpreferredencoding(False) try: bash_version = subprocess.run( From 968510b6aa69e2616828f643f6f2384e27f1ceb1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 28 Nov 2023 19:54:24 +0200 Subject: [PATCH 15/23] testing: fix isolation issue in `tr` fixture The default for `_prepareconfig` is to use `sys.argv`, which in this case are the flags passed to (top-level) `pytest`. This is not the intention, the tests themselves should not be affected by it. --- testing/test_terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 596c3c67e..264ab96d8 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1802,7 +1802,7 @@ def test_terminal_no_summary_warnings_header_once(pytester: Pytester) -> None: @pytest.fixture(scope="session") def tr() -> TerminalReporter: - config = _pytest.config._prepareconfig() + config = _pytest.config._prepareconfig([]) return TerminalReporter(config) From ef699f8c1719959dfa6c32f06fc705f70672308d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 30 Nov 2023 19:53:31 +0200 Subject: [PATCH 16/23] testing: remove a no longer necessary skip pytest-xdist was released at 2019-02-15, seems enough time. --- testing/test_config.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/testing/test_config.py b/testing/test_config.py index 58671e6ed..900cccee8 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1959,16 +1959,6 @@ def test_invocation_args(pytester: Pytester) -> None: ], ) def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None: - if plugin == "debugging": - # Fixed in xdist (after 1.27.0). - # https://github.com/pytest-dev/pytest-xdist/pull/422 - try: - import xdist # noqa: F401 - except ImportError: - pass - else: - pytest.skip("does not work with xdist currently") - p = pytester.makepyfile("def test(): pass") result = pytester.runpytest(str(p), "-pno:%s" % plugin) From 5689d806cffa394ae3e79aff9617784bb14533c2 Mon Sep 17 00:00:00 2001 From: touilleWoman <39769383+touilleWoman@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:25:01 +0100 Subject: [PATCH 17/23] Fix "Customizing Colors" documentation section (#11652) Fixes #11573 --- doc/en/how-to/logging.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index b9f522fa4..bdcfbe34f 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -241,7 +241,7 @@ through ``add_color_level()``. Example: .. code-block:: python - @pytest.hookimpl + @pytest.hookimpl(trylast=True) def pytest_configure(config): logging_plugin = config.pluginmanager.get_plugin("logging-plugin") 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 18/23] [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 19/23] 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 20/23] 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 21/23] 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 22/23] 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 23/23] 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'",