Merge branch 'main' into improve-high-scope-fixtures-teardown-issue-3806

This commit is contained in:
Sadra Barikbin 2023-06-29 10:53:51 +03:30 committed by GitHub
commit 4633fa50f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 1362 additions and 640 deletions

View File

@ -43,6 +43,7 @@ jobs:
"windows-py39", "windows-py39",
"windows-py310", "windows-py310",
"windows-py311", "windows-py311",
"windows-py312",
"ubuntu-py37", "ubuntu-py37",
"ubuntu-py37-pluggy", "ubuntu-py37-pluggy",
@ -51,12 +52,13 @@ jobs:
"ubuntu-py39", "ubuntu-py39",
"ubuntu-py310", "ubuntu-py310",
"ubuntu-py311", "ubuntu-py311",
"ubuntu-py312",
"ubuntu-pypy3", "ubuntu-pypy3",
"macos-py37", "macos-py37",
"macos-py38",
"macos-py39", "macos-py39",
"macos-py310", "macos-py310",
"macos-py312",
"docs", "docs",
"doctesting", "doctesting",
@ -86,9 +88,13 @@ jobs:
os: windows-latest os: windows-latest
tox_env: "py310-xdist" tox_env: "py310-xdist"
- name: "windows-py311" - name: "windows-py311"
python: "3.11-dev" python: "3.11"
os: windows-latest os: windows-latest
tox_env: "py311" tox_env: "py311"
- name: "windows-py312"
python: "3.12-dev"
os: windows-latest
tox_env: "py312"
- name: "ubuntu-py37" - name: "ubuntu-py37"
python: "3.7" python: "3.7"
@ -116,10 +122,15 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
tox_env: "py310-xdist" tox_env: "py310-xdist"
- name: "ubuntu-py311" - name: "ubuntu-py311"
python: "3.11-dev" python: "3.11"
os: ubuntu-latest os: ubuntu-latest
tox_env: "py311" tox_env: "py311"
use_coverage: true use_coverage: true
- name: "ubuntu-py312"
python: "3.12-dev"
os: ubuntu-latest
tox_env: "py312"
use_coverage: true
- name: "ubuntu-pypy3" - name: "ubuntu-pypy3"
python: "pypy-3.7" python: "pypy-3.7"
os: ubuntu-latest os: ubuntu-latest
@ -129,19 +140,19 @@ jobs:
python: "3.7" python: "3.7"
os: macos-latest os: macos-latest
tox_env: "py37-xdist" tox_env: "py37-xdist"
- name: "macos-py38"
python: "3.8"
os: macos-latest
tox_env: "py38-xdist"
use_coverage: true
- name: "macos-py39" - name: "macos-py39"
python: "3.9" python: "3.9"
os: macos-latest os: macos-latest
tox_env: "py39-xdist" tox_env: "py39-xdist"
use_coverage: true
- name: "macos-py310" - name: "macos-py310"
python: "3.10" python: "3.10"
os: macos-latest os: macos-latest
tox_env: "py310-xdist" tox_env: "py310-xdist"
- name: "macos-py312"
python: "3.12-dev"
os: macos-latest
tox_env: "py312-xdist"
- name: "plugins" - name: "plugins"
python: "3.9" python: "3.9"
@ -168,6 +179,7 @@ jobs:
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python }} python-version: ${{ matrix.python }}
check-latest: ${{ endsWith(matrix.python, '-dev') }}
- name: Install dependencies - name: Install dependencies
run: | run: |

View File

@ -38,7 +38,7 @@ jobs:
run: python scripts/update-plugin-list.py run: python scripts/update-plugin-list.py
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
with: with:
commit-message: '[automated] Update plugin list' commit-message: '[automated] Update plugin list'
author: 'pytest bot <pytestbot@users.noreply.github.com>' author: 'pytest bot <pytestbot@users.noreply.github.com>'

View File

@ -5,7 +5,7 @@ repos:
- id: black - id: black
args: [--safe, --quiet] args: [--safe, --quiet]
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: 1.13.0 rev: 1.14.0
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: [black==23.1.0] additional_dependencies: [black==23.1.0]
@ -21,7 +21,7 @@ repos:
exclude: _pytest/(debugging|hookspec).py exclude: _pytest/(debugging|hookspec).py
language_version: python3 language_version: python3
- repo: https://github.com/PyCQA/autoflake - repo: https://github.com/PyCQA/autoflake
rev: v2.1.1 rev: v2.2.0
hooks: hooks:
- id: autoflake - id: autoflake
name: autoflake name: autoflake
@ -37,26 +37,26 @@ repos:
- flake8-typing-imports==1.12.0 - flake8-typing-imports==1.12.0
- flake8-docstrings==1.5.0 - flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder-python-imports - repo: https://github.com/asottile/reorder-python-imports
rev: v3.9.0 rev: v3.10.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: ['--application-directories=.:src', --py37-plus] args: ['--application-directories=.:src', --py37-plus]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.4.0 rev: v3.7.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py37-plus] args: [--py37-plus]
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.2.0 rev: v2.3.0
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
args: ["--max-py-version=3.11", "--include-version-classifiers"] args: ["--max-py-version=3.12", "--include-version-classifiers"]
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0 rev: v1.10.0
hooks: hooks:
- id: python-use-type-annotations - id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0 rev: v1.4.1
hooks: hooks:
- id: mypy - id: mypy
files: ^(src/|testing/) files: ^(src/|testing/)

View File

@ -129,6 +129,7 @@ Eric Hunsberger
Eric Liu Eric Liu
Eric Siegerman Eric Siegerman
Erik Aronesty Erik Aronesty
Erik Hasse
Erik M. Bray Erik M. Bray
Evan Kepner Evan Kepner
Evgeny Seliverstov Evgeny Seliverstov
@ -378,6 +379,7 @@ Victor Maryama
Victor Rodriguez Victor Rodriguez
Victor Uriarte Victor Uriarte
Vidar T. Fauske Vidar T. Fauske
Vijay Arora
Virgil Dupras Virgil Dupras
Vitaly Lashmanov Vitaly Lashmanov
Vivaan Verma Vivaan Verma

View File

@ -1 +0,0 @@
Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems.

View File

@ -1 +0,0 @@
Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.

View File

@ -1 +0,0 @@
Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.

View File

@ -1,2 +0,0 @@
Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.

View File

@ -1,5 +0,0 @@
When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
"All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
Previously, the last frame of the traceback was shown, even though it was hidden.

View File

@ -1,3 +0,0 @@
Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
Added :func:`TerminalReporter.wrap_write() <pytest.TerminalReporter.wrap_write>` as a helper for that.

View File

@ -1 +0,0 @@
:confval:`testpaths` is now honored to load root ``conftests``.

View File

@ -1 +0,0 @@
Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.

View File

@ -1 +0,0 @@
The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments.

View File

@ -1 +0,0 @@
Added underlying exception to cache provider path creation and write warning messages.

1
changelog/11011.doc.rst Normal file
View File

@ -0,0 +1 @@
Added a warning about modifying the root logger during tests when using ``caplog``.

View File

@ -1 +0,0 @@
Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.

View File

@ -1 +0,0 @@
Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call.

View File

@ -1 +0,0 @@
Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.

View File

@ -1,3 +0,0 @@
When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir`.
Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.

View File

@ -1 +0,0 @@
Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files).

View File

@ -1 +0,0 @@
Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.

View File

@ -1 +0,0 @@
Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).

View File

@ -0,0 +1,3 @@
Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work.
This will become an error in the future.

View File

@ -0,0 +1,22 @@
**PytestRemovedIn8Warning deprecation warnings are now errors by default.**
Following our plan to remove deprecated features with as little disruption as
possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors
instead of warning messages by default.
**The affected features will be effectively removed in pytest 8.1**, so please consult the
:ref:`deprecations` section in the docs for directions on how to update existing code.
In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a
stopgap measure by adding this to your ``pytest.ini`` file:
.. code-block:: ini
[pytest]
filterwarnings =
ignore::pytest.PytestRemovedIn8Warning
But this will stop working when pytest ``8.1`` is released.
**If you have concerns** about the removal of a specific feature, please add a
comment to :issue:`7363`.

View File

@ -1,3 +0,0 @@
:func:`_pytest.logging.LogCaptureFixture.set_level` and :func:`_pytest.logging.LogCaptureFixture.at_level`
will temporarily enable the requested ``level`` if ``level`` was disabled globally via
``logging.disable(LEVEL)``.

View File

@ -0,0 +1,5 @@
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).
To collect the entire package, specify just the directory: `pytest pkg`.

View File

@ -6,6 +6,8 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-7.4.0
release-7.3.2
release-7.3.1 release-7.3.1
release-7.3.0 release-7.3.0
release-7.2.2 release-7.2.2

View File

@ -0,0 +1,21 @@
pytest-7.3.2
=======================================
pytest 7.3.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Adam J. Stewart
* Alessio Izzo
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@ -0,0 +1,49 @@
pytest-7.4.0
=======================================
The pytest team is proud to announce the 7.4.0 release!
This release contains new features, improvements, and bug fixes,
the full list of changes is available in the changelog:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Adam J. Stewart
* Alessio Izzo
* Alex
* Alex Lambson
* Brian Larsen
* Bruno Oliveira
* Bryan Ricker
* Chris Mahoney
* Facundo Batista
* Florian Bruhin
* Jarrett Keifer
* Kenny Y
* Miro Hrončok
* Ran Benita
* Roberto Aldera
* Ronny Pfannschmidt
* Sergey Kim
* Stefanie Molin
* Vijay Arora
* Ville Skyttä
* Zac Hatfield-Dodds
* bzoracler
* leeyueh
* nondescryptid
* theirix
Happy testing,
The pytest Development Team

View File

@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
cachedir: .pytest_cache cachedir: .pytest_cache
rootdir: /home/sweet/project rootdir: /home/sweet/project
collected 0 items collected 0 items
cache -- .../_pytest/cacheprovider.py:510 cache -- .../_pytest/cacheprovider.py:528
Return a cache object that can persist state between testing sessions. Return a cache object that can persist state between testing sessions.
cache.get(key, default) cache.get(key, default)
@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
For more details: :ref:`doctest_namespace`. For more details: :ref:`doctest_namespace`.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1360 pytestconfig [session scope] -- .../_pytest/fixtures.py:1353
Session-scoped fixture that returns the session's :class:`pytest.Config` Session-scoped fixture that returns the session's :class:`pytest.Config`
object. object.
@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
caplog -- .../_pytest/logging.py:498 caplog -- .../_pytest/logging.py:570
Access and control log capturing. Access and control log capturing.
Captured logs are available through the following properties/methods:: Captured logs are available through the following properties/methods::
@ -207,7 +207,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
* caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.record_tuples -> list of (logger_name, level, message) tuples
* caplog.clear() -> clear captured records and formatted log output string * caplog.clear() -> clear captured records and formatted log output string
monkeypatch -- .../_pytest/monkeypatch.py:29 monkeypatch -- .../_pytest/monkeypatch.py:30
A convenient fixture for monkey-patching. A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries, or The fixture provides these methods to modify objects, dictionaries, or

View File

@ -28,6 +28,122 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 7.4.0 (2023-06-23)
=========================
Features
--------
- `#10901 <https://github.com/pytest-dev/pytest/issues/10901>`_: Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
Improvements
------------
- `#10872 <https://github.com/pytest-dev/pytest/issues/10872>`_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
- `#10907 <https://github.com/pytest-dev/pytest/issues/10907>`_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
"All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
Previously, the last frame of the traceback was shown, even though it was hidden.
- `#10940 <https://github.com/pytest-dev/pytest/issues/10940>`_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
Added ``TerminalReporter.wrap_write()`` as a helper for that.
- `#10991 <https://github.com/pytest-dev/pytest/issues/10991>`_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
- `#11005 <https://github.com/pytest-dev/pytest/issues/11005>`_: Added the underlying exception to the cache provider's path creation and write warning messages.
- `#11013 <https://github.com/pytest-dev/pytest/issues/11013>`_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
- `#11043 <https://github.com/pytest-dev/pytest/issues/11043>`_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir <rootdir>`.
Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
- `#11081 <https://github.com/pytest-dev/pytest/issues/11081>`_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
If after updating to this version you see that your `norecursedirs` setting is not being respected,
it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
Most likely, your hook returns `False` for paths it does not want to ignore,
which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
- `#8711 <https://github.com/pytest-dev/pytest/issues/8711>`_: :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :func:`caplog.at_level() <pytest.LogCaptureFixture.at_level>`
will temporarily enable the requested ``level`` if ``level`` was disabled globally via
``logging.disable(LEVEL)``.
Bug Fixes
---------
- `#10831 <https://github.com/pytest-dev/pytest/issues/10831>`_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
- `#11068 <https://github.com/pytest-dev/pytest/issues/11068>`_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
- `#11104 <https://github.com/pytest-dev/pytest/issues/11104>`_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
even when it was not utilized (e.g. when explicit paths were given on the command line).
Now the ``testpaths`` are only considered when they are in use.
- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
- `#7781 <https://github.com/pytest-dev/pytest/issues/7781>`_: Fix writing non-encodable text to log file when using ``--debug``.
Improved Documentation
----------------------
- `#9146 <https://github.com/pytest-dev/pytest/issues/9146>`_: Improved documentation for :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>`.
Trivial/Internal Changes
------------------------
- `#11031 <https://github.com/pytest-dev/pytest/issues/11031>`_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
pytest 7.3.2 (2023-06-10)
=========================
Bug Fixes
---------
- `#10169 <https://github.com/pytest-dev/pytest/issues/10169>`_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems.
- `#10894 <https://github.com/pytest-dev/pytest/issues/10894>`_: Support for Python 3.12 (beta at the time of writing).
- `#10987 <https://github.com/pytest-dev/pytest/issues/10987>`_: :confval:`testpaths` is now honored to load root ``conftests``.
- `#10999 <https://github.com/pytest-dev/pytest/issues/10999>`_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments.
- `#11028 <https://github.com/pytest-dev/pytest/issues/11028>`_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call.
- `#11054 <https://github.com/pytest-dev/pytest/issues/11054>`_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files).
pytest 7.3.1 (2023-04-14) pytest 7.3.1 (2023-04-14)
========================= =========================

View File

@ -380,6 +380,25 @@ conflicts (such as :class:`pytest.File` now taking ``path`` instead of
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a ``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
deprecation warning is now raised. deprecation warning is now raised.
Applying a mark to a fixture function
-------------------------------------
.. deprecated:: 7.4
Applying a mark to a fixture function never had any effect, but it is a common user error.
.. code-block:: python
@pytest.mark.usefixtures("clean_database")
@pytest.fixture
def user() -> User:
...
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
Backward compatibilities in ``Parser.addoption`` Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -467,12 +486,26 @@ The ``yield_fixture`` function/decorator
It has been so for a very long time, so can be search/replaced safely. It has been so for a very long time, so can be search/replaced safely.
Removed Features Removed Features and Breaking Changes
---------------- -------------------------------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed. an appropriate period of deprecation has passed.
Some breaking changes which could not be deprecated are also listed.
Collecting ``__init__.py`` files no longer collects package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 8.0
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
(unless :confval:`python_files` was changed to allow `__init__.py` file).
To collect the entire package, specify just the directory: `pytest pkg`.
The ``pytest.collect`` module The ``pytest.collect`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -691,7 +691,7 @@ Here is an example for making a ``db`` fixture available in a directory:
pass pass
@pytest.fixture(scope="session") @pytest.fixture(scope="package")
def db(): def db():
return DB() return DB()

View File

@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash .. code-block:: bash
$ pytest --version $ pytest --version
pytest 7.3.1 pytest 7.4.0
.. _`simpletest`: .. _`simpletest`:

View File

@ -1752,8 +1752,7 @@ into an ini-file:
def my_fixture_that_sadly_wont_use_my_other_fixture(): def my_fixture_that_sadly_wont_use_my_other_fixture():
... ...
Currently this will not generate any error or warning, but this is intended This generates a deprecation warning, and will become an error in Pytest 8.
to be handled by :issue:`3664`.
.. _`override fixtures`: .. _`override fixtures`:

View File

@ -172,6 +172,13 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
The full API is available at :class:`pytest.LogCaptureFixture`. The full API is available at :class:`pytest.LogCaptureFixture`.
.. warning::
The ``caplog`` fixture adds a handler to the root logger to capture logs. If the root logger is
modified during a test, for example with ``logging.config.dictConfig``, this handler may be
removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration
only adds to the existing handlers.
.. _live_logs: .. _live_logs:

View File

@ -2,6 +2,7 @@
.. sidebar:: Next Open Trainings .. sidebar:: Next Open Trainings
- `pytest tips and tricks for a better testsuite <https://ep2023.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2023 <https://ep2023.europython.eu/>`_, July 18th (3h), Prague/Remote
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote
Also see :doc:`previous talks and blogposts <talks>`. Also see :doc:`previous talks and blogposts <talks>`.

File diff suppressed because it is too large Load Diff

View File

@ -1153,6 +1153,9 @@ Custom warnings generated in some situations such as improper usage or deprecate
.. autoclass:: pytest.PytestRemovedIn8Warning .. autoclass:: pytest.PytestRemovedIn8Warning
:show-inheritance: :show-inheritance:
.. autoclass:: pytest.PytestRemovedIn9Warning
:show-inheritance:
.. autoclass:: pytest.PytestUnhandledCoroutineWarning .. autoclass:: pytest.PytestUnhandledCoroutineWarning
:show-inheritance: :show-inheritance:
@ -1703,6 +1706,11 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest] [pytest]
pythonpath = src1 src2 pythonpath = src1 src2
.. note::
``pythonpath`` does not affect some imports that happen very early,
most notably plugins loaded using the ``-p`` command line option.
.. confval:: required_plugins .. confval:: required_plugins
@ -1918,9 +1926,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
--strict-markers Markers not registered in the `markers` section of --strict-markers Markers not registered in the `markers` section of
the configuration file raise errors the configuration file raise errors
--strict (Deprecated) alias to --strict-markers --strict (Deprecated) alias to --strict-markers
-c, --config-file FILE -c FILE, --config-file=FILE
Load configuration from `FILE` instead of trying to Load configuration from `FILE` instead of trying to
locate one of the implicit configuration files locate one of the implicit configuration files.
--continue-on-collection-errors --continue-on-collection-errors
Force test execution even if collection errors occur Force test execution even if collection errors occur
--rootdir=ROOTDIR Define root directory for tests. Can be relative --rootdir=ROOTDIR Define root directory for tests. Can be relative

View File

@ -7,7 +7,9 @@ def main():
Platform agnostic wrapper script for towncrier. Platform agnostic wrapper script for towncrier.
Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs. Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs.
""" """
with open("doc/en/_changelog_towncrier_draft.rst", "w") as draft_file: with open(
"doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8"
) as draft_file:
return call(("towncrier", "--draft"), stdout=draft_file) return call(("towncrier", "--draft"), stdout=draft_file)

View File

@ -17,7 +17,9 @@ Plugin List
=========== ===========
PyPI projects that match "pytest-\*" are considered plugins and are listed PyPI projects that match "pytest-\*" are considered plugins and are listed
automatically. Packages classified as inactive are excluded. automatically together with a manually-maintained list in `the source
code <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
Packages classified as inactive are excluded.
.. The following conditional uses a different format for this list when .. The following conditional uses a different format for this list when
creating a PDF, because otherwise the table gets far too wide for the creating a PDF, because otherwise the table gets far too wide for the
@ -33,6 +35,9 @@ DEVELOPMENT_STATUS_CLASSIFIERS = (
"Development Status :: 6 - Mature", "Development Status :: 6 - Mature",
"Development Status :: 7 - Inactive", "Development Status :: 7 - Inactive",
) )
ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins
"logassert",
}
def escape_rst(text: str) -> str: def escape_rst(text: str) -> str:
@ -52,18 +57,18 @@ def iter_plugins():
regex = r">([\d\w-]*)</a>" regex = r">([\d\w-]*)</a>"
response = requests.get("https://pypi.org/simple") response = requests.get("https://pypi.org/simple")
matches = list( match_names = (match.groups()[0] for match in re.finditer(regex, response.text))
match plugin_names = [
for match in re.finditer(regex, response.text) name
if match.groups()[0].startswith("pytest-") for name in match_names
) if name.startswith("pytest-") or name in ADDITIONAL_PROJECTS
]
for match in tqdm(matches, smoothing=0): for name in tqdm(plugin_names, smoothing=0):
name = match.groups()[0]
response = requests.get(f"https://pypi.org/pypi/{name}/json") response = requests.get(f"https://pypi.org/pypi/{name}/json")
if response.status_code == 404: if response.status_code == 404:
# Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple
# return 404 on the JSON API. Skip. # but return 404 on the JSON API. Skip.
continue continue
response.raise_for_status() response.raise_for_status()
info = response.json()["info"] info = response.json()["info"]

View File

@ -6,7 +6,7 @@ long_description_content_type = text/x-rst
url = https://docs.pytest.org/en/latest/ url = https://docs.pytest.org/en/latest/
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
license = MIT license = MIT
license_file = LICENSE license_files = LICENSE
platforms = unix, linux, osx, cygwin, win32 platforms = unix, linux, osx, cygwin, win32
classifiers = classifiers =
Development Status :: 6 - Mature Development Status :: 6 - Mature
@ -22,6 +22,7 @@ classifiers =
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries
Topic :: Software Development :: Testing Topic :: Software Development :: Testing
Topic :: Utilities Topic :: Utilities
@ -73,6 +74,7 @@ testing =
nose nose
pygments>=2.7.2 pygments>=2.7.2
requests requests
setuptools
xmlschema xmlschema
[options.package_data] [options.package_data]

View File

@ -46,8 +46,14 @@ if TYPE_CHECKING:
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
namedExpr = ast.NamedExpr namedExpr = ast.NamedExpr
astNameConstant = ast.Constant
astStr = ast.Constant
astNum = ast.Constant
else: else:
namedExpr = ast.Expr namedExpr = ast.Expr
astNameConstant = ast.NameConstant
astStr = ast.Str
astNum = ast.Num
assertstate_key = StashKey["AssertionState"]() assertstate_key = StashKey["AssertionState"]()
@ -680,8 +686,11 @@ class AssertionRewriter(ast.NodeVisitor):
if ( if (
expect_docstring expect_docstring
and isinstance(item, ast.Expr) and isinstance(item, ast.Expr)
and isinstance(item.value, ast.Str) and isinstance(item.value, astStr)
): ):
if sys.version_info >= (3, 8):
doc = item.value.value
else:
doc = item.value.s doc = item.value.s
if self.is_rewrite_disabled(doc): if self.is_rewrite_disabled(doc):
return return
@ -814,7 +823,7 @@ class AssertionRewriter(ast.NodeVisitor):
current = self.stack.pop() current = self.stack.pop()
if self.stack: if self.stack:
self.explanation_specifiers = self.stack[-1] self.explanation_specifiers = self.stack[-1]
keys = [ast.Str(key) for key in current.keys()] keys = [astStr(key) for key in current.keys()]
format_dict = ast.Dict(keys, list(current.values())) format_dict = ast.Dict(keys, list(current.values()))
form = ast.BinOp(expl_expr, ast.Mod(), format_dict) form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
name = "@py_format" + str(next(self.variable_counter)) name = "@py_format" + str(next(self.variable_counter))
@ -868,16 +877,16 @@ class AssertionRewriter(ast.NodeVisitor):
negation = ast.UnaryOp(ast.Not(), top_condition) negation = ast.UnaryOp(ast.Not(), top_condition)
if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook
msg = self.pop_format_context(ast.Str(explanation)) msg = self.pop_format_context(astStr(explanation))
# Failed # Failed
if assert_.msg: if assert_.msg:
assertmsg = self.helper("_format_assertmsg", assert_.msg) assertmsg = self.helper("_format_assertmsg", assert_.msg)
gluestr = "\n>assert " gluestr = "\n>assert "
else: else:
assertmsg = ast.Str("") assertmsg = astStr("")
gluestr = "assert " gluestr = "assert "
err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg)
err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation)
err_name = ast.Name("AssertionError", ast.Load()) err_name = ast.Name("AssertionError", ast.Load())
fmt = self.helper("_format_explanation", err_msg) fmt = self.helper("_format_explanation", err_msg)
@ -893,8 +902,8 @@ class AssertionRewriter(ast.NodeVisitor):
hook_call_pass = ast.Expr( hook_call_pass = ast.Expr(
self.helper( self.helper(
"_call_assertion_pass", "_call_assertion_pass",
ast.Num(assert_.lineno), astNum(assert_.lineno),
ast.Str(orig), astStr(orig),
fmt_pass, fmt_pass,
) )
) )
@ -913,7 +922,7 @@ class AssertionRewriter(ast.NodeVisitor):
variables = [ variables = [
ast.Name(name, ast.Store()) for name in self.format_variables ast.Name(name, ast.Store()) for name in self.format_variables
] ]
clear_format = ast.Assign(variables, ast.NameConstant(None)) clear_format = ast.Assign(variables, astNameConstant(None))
self.statements.append(clear_format) self.statements.append(clear_format)
else: # Original assertion rewriting else: # Original assertion rewriting
@ -924,9 +933,9 @@ class AssertionRewriter(ast.NodeVisitor):
assertmsg = self.helper("_format_assertmsg", assert_.msg) assertmsg = self.helper("_format_assertmsg", assert_.msg)
explanation = "\n>assert " + explanation explanation = "\n>assert " + explanation
else: else:
assertmsg = ast.Str("") assertmsg = astStr("")
explanation = "assert " + explanation explanation = "assert " + explanation
template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) template = ast.BinOp(assertmsg, ast.Add(), astStr(explanation))
msg = self.pop_format_context(template) msg = self.pop_format_context(template)
fmt = self.helper("_format_explanation", msg) fmt = self.helper("_format_explanation", msg)
err_name = ast.Name("AssertionError", ast.Load()) err_name = ast.Name("AssertionError", ast.Load())
@ -938,7 +947,7 @@ class AssertionRewriter(ast.NodeVisitor):
# Clear temporary variables by setting them to None. # Clear temporary variables by setting them to None.
if self.variables: if self.variables:
variables = [ast.Name(name, ast.Store()) for name in self.variables] variables = [ast.Name(name, ast.Store()) for name in self.variables]
clear = ast.Assign(variables, ast.NameConstant(None)) clear = ast.Assign(variables, astNameConstant(None))
self.statements.append(clear) self.statements.append(clear)
# Fix locations (line numbers/column offsets). # Fix locations (line numbers/column offsets).
for stmt in self.statements: for stmt in self.statements:
@ -952,20 +961,20 @@ class AssertionRewriter(ast.NodeVisitor):
# thinks it's acceptable. # thinks it's acceptable.
locs = ast.Call(self.builtin("locals"), [], []) locs = ast.Call(self.builtin("locals"), [], [])
target_id = name.target.id # type: ignore[attr-defined] target_id = name.target.id # type: ignore[attr-defined]
inlocs = ast.Compare(ast.Str(target_id), [ast.In()], [locs]) inlocs = ast.Compare(astStr(target_id), [ast.In()], [locs])
dorepr = self.helper("_should_repr_global_name", name) dorepr = self.helper("_should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
expr = ast.IfExp(test, self.display(name), ast.Str(target_id)) expr = ast.IfExp(test, self.display(name), astStr(target_id))
return name, self.explanation_param(expr) return name, self.explanation_param(expr)
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
# Display the repr of the name if it's a local variable or # Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable. # _should_repr_global_name() thinks it's acceptable.
locs = ast.Call(self.builtin("locals"), [], []) locs = ast.Call(self.builtin("locals"), [], [])
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) inlocs = ast.Compare(astStr(name.id), [ast.In()], [locs])
dorepr = self.helper("_should_repr_global_name", name) dorepr = self.helper("_should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) expr = ast.IfExp(test, self.display(name), astStr(name.id))
return name, self.explanation_param(expr) return name, self.explanation_param(expr)
def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
@ -1003,7 +1012,7 @@ class AssertionRewriter(ast.NodeVisitor):
self.push_format_context() self.push_format_context()
res, expl = self.visit(v) res, expl = self.visit(v)
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
expl_format = self.pop_format_context(ast.Str(expl)) expl_format = self.pop_format_context(astStr(expl))
call = ast.Call(app, [expl_format], []) call = ast.Call(app, [expl_format], [])
self.expl_stmts.append(ast.Expr(call)) self.expl_stmts.append(ast.Expr(call))
if i < levels: if i < levels:
@ -1015,7 +1024,7 @@ class AssertionRewriter(ast.NodeVisitor):
self.statements = body = inner self.statements = body = inner
self.statements = save self.statements = save
self.expl_stmts = fail_save self.expl_stmts = fail_save
expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) expl_template = self.helper("_format_boolop", expl_list, astNum(is_or))
expl = self.pop_format_context(expl_template) expl = self.pop_format_context(expl_template)
return ast.Name(res_var, ast.Load()), self.explanation_param(expl) return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
@ -1118,9 +1127,9 @@ class AssertionRewriter(ast.NodeVisitor):
next_expl = f"({next_expl})" next_expl = f"({next_expl})"
results.append(next_res) results.append(next_res)
sym = BINOP_MAP[op.__class__] sym = BINOP_MAP[op.__class__]
syms.append(ast.Str(sym)) syms.append(astStr(sym))
expl = f"{left_expl} {sym} {next_expl}" expl = f"{left_expl} {sym} {next_expl}"
expls.append(ast.Str(expl)) expls.append(astStr(expl))
res_expr = ast.Compare(left_res, [op], [next_res]) res_expr = ast.Compare(left_res, [op], [next_res])
self.statements.append(ast.Assign([store_names[i]], res_expr)) self.statements.append(ast.Assign([store_names[i]], res_expr))
left_res, left_expl = next_res, next_expl left_res, left_expl = next_res, next_expl

View File

@ -527,9 +527,12 @@ class PytestPluginManager(PluginManager):
# #
def _set_initial_conftests( def _set_initial_conftests(
self, self,
namespace: argparse.Namespace, args: Sequence[Union[str, Path]],
pyargs: bool,
noconftest: bool,
rootpath: Path, rootpath: Path,
testpaths_ini: Sequence[str], confcutdir: Optional[Path],
importmode: Union[ImportMode, str],
) -> None: ) -> None:
"""Load initial conftest files given a preparsed "namespace". """Load initial conftest files given a preparsed "namespace".
@ -539,17 +542,12 @@ class PytestPluginManager(PluginManager):
common options will not confuse our logic here. common options will not confuse our logic here.
""" """
current = Path.cwd() current = Path.cwd()
self._confcutdir = ( self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None
absolutepath(current / namespace.confcutdir) self._noconftest = noconftest
if namespace.confcutdir self._using_pyargs = pyargs
else None
)
self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir + testpaths_ini
foundanchor = False foundanchor = False
for testpath in testpaths: for intitial_path in args:
path = str(testpath) path = str(intitial_path)
# remove node-id syntax # remove node-id syntax
i = path.find("::") i = path.find("::")
if i != -1: if i != -1:
@ -563,10 +561,10 @@ class PytestPluginManager(PluginManager):
except OSError: # pragma: no cover except OSError: # pragma: no cover
anchor_exists = False anchor_exists = False
if anchor_exists: if anchor_exists:
self._try_load_conftest(anchor, namespace.importmode, rootpath) self._try_load_conftest(anchor, importmode, rootpath)
foundanchor = True foundanchor = True
if not foundanchor: if not foundanchor:
self._try_load_conftest(current, namespace.importmode, rootpath) self._try_load_conftest(current, importmode, rootpath)
def _is_in_confcutdir(self, path: Path) -> bool: def _is_in_confcutdir(self, path: Path) -> bool:
"""Whether a path is within the confcutdir. """Whether a path is within the confcutdir.
@ -1140,10 +1138,25 @@ class Config:
@hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config: "Config") -> None: def pytest_load_initial_conftests(self, early_config: "Config") -> None:
self.pluginmanager._set_initial_conftests( # We haven't fully parsed the command line arguments yet, so
early_config.known_args_namespace, # early_config.args it not set yet. But we need it for
# discovering the initial conftests. So "pre-run" the logic here.
# It will be done for real in `parse()`.
args, args_source = early_config._decide_args(
args=early_config.known_args_namespace.file_or_dir,
pyargs=early_config.known_args_namespace.pyargs,
testpaths=early_config.getini("testpaths"),
invocation_dir=early_config.invocation_params.dir,
rootpath=early_config.rootpath, rootpath=early_config.rootpath,
testpaths_ini=self.getini("testpaths"), warn=False,
)
self.pluginmanager._set_initial_conftests(
args=args,
pyargs=early_config.known_args_namespace.pyargs,
noconftest=early_config.known_args_namespace.noconftest,
rootpath=early_config.rootpath,
confcutdir=early_config.known_args_namespace.confcutdir,
importmode=early_config.known_args_namespace.importmode,
) )
def _initini(self, args: Sequence[str]) -> None: def _initini(self, args: Sequence[str]) -> None:
@ -1223,6 +1236,49 @@ class Config:
return args return args
def _decide_args(
self,
*,
args: List[str],
pyargs: List[str],
testpaths: List[str],
invocation_dir: Path,
rootpath: Path,
warn: bool,
) -> Tuple[List[str], ArgsSource]:
"""Decide the args (initial paths/nodeids) to use given the relevant inputs.
:param warn: Whether can issue warnings.
"""
if args:
source = Config.ArgsSource.ARGS
result = args
else:
if invocation_dir == rootpath:
source = Config.ArgsSource.TESTPATHS
if pyargs:
result = testpaths
else:
result = []
for path in testpaths:
result.extend(sorted(glob.iglob(path, recursive=True)))
if testpaths and not result:
if warn:
warning_text = (
"No files were found in testpaths; "
"consider removing or adjusting your testpaths configuration. "
"Searching recursively from the current directory instead."
)
self.issue_config_time_warning(
PytestConfigWarning(warning_text), stacklevel=3
)
else:
result = []
if not result:
source = Config.ArgsSource.INCOVATION_DIR
result = [str(invocation_dir)]
return result, source
def _preparse(self, args: List[str], addopts: bool = True) -> None: def _preparse(self, args: List[str], addopts: bool = True) -> None:
if addopts: if addopts:
env_addopts = os.environ.get("PYTEST_ADDOPTS", "") env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
@ -1371,34 +1427,17 @@ class Config:
self.hook.pytest_cmdline_preparse(config=self, args=args) self.hook.pytest_cmdline_preparse(config=self, args=args)
self._parser.after_preparse = True # type: ignore self._parser.after_preparse = True # type: ignore
try: try:
source = Config.ArgsSource.ARGS
args = self._parser.parse_setoption( args = self._parser.parse_setoption(
args, self.option, namespace=self.option args, self.option, namespace=self.option
) )
if not args: self.args, self.args_source = self._decide_args(
if self.invocation_params.dir == self.rootpath: args=args,
source = Config.ArgsSource.TESTPATHS pyargs=self.known_args_namespace.pyargs,
testpaths: List[str] = self.getini("testpaths") testpaths=self.getini("testpaths"),
if self.known_args_namespace.pyargs: invocation_dir=self.invocation_params.dir,
args = testpaths rootpath=self.rootpath,
else: warn=True,
args = []
for path in testpaths:
args.extend(sorted(glob.iglob(path, recursive=True)))
if testpaths and not args:
warning_text = (
"No files were found in testpaths; "
"consider removing or adjusting your testpaths configuration. "
"Searching recursively from the current directory instead."
) )
self.issue_config_time_warning(
PytestConfigWarning(warning_text), stacklevel=3
)
if not args:
source = Config.ArgsSource.INCOVATION_DIR
args = [str(self.invocation_params.dir)]
self.args = args
self.args_source = source
except PrintHelp: except PrintHelp:
pass pass

View File

@ -122,6 +122,11 @@ HOOK_LEGACY_MARKING = UnformattedWarning(
"#configuring-hook-specs-impls-using-markers", "#configuring-hook-specs-impls-using-markers",
) )
MARKED_FIXTURE = PytestRemovedIn8Warning(
"Marks applied to fixtures have no effect\n"
"See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
)
# You want to make some `__init__` or function "private". # You want to make some `__init__` or function "private".
# #
# def my_private_function(some, args): # def my_private_function(some, args):

View File

@ -54,6 +54,7 @@ from _pytest.config import _PluggyPlugin
from _pytest.config import Config from _pytest.config import Config
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.deprecated import YIELD_FIXTURE from _pytest.deprecated import YIELD_FIXTURE
from _pytest.mark import Mark from _pytest.mark import Mark
from _pytest.mark import ParameterSet from _pytest.mark import ParameterSet
@ -1271,6 +1272,9 @@ class FixtureFunctionMarker:
"fixture is being applied more than once to the same function" "fixture is being applied more than once to the same function"
) )
if hasattr(function, "pytestmark"):
warnings.warn(MARKED_FIXTURE, stacklevel=2)
function = wrap_function_to_error_out_if_called_directly(function, self) function = wrap_function_to_error_out_if_called_directly(function, self)
name = self.name or function.__name__ name = self.name or function.__name__

View File

@ -105,7 +105,7 @@ def pytest_cmdline_parse():
if config.option.debug: if config.option.debug:
# --debug | --debug <file.log> was provided. # --debug | --debug <file.log> was provided.
path = config.option.debug path = config.option.debug
debugfile = open(path, "w") debugfile = open(path, "w", encoding="utf-8")
debugfile.write( debugfile.write(
"versions pytest-%s, " "versions pytest-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" "python-%s\ncwd=%s\nargs=%s\n\n"

View File

@ -515,7 +515,9 @@ class LogCaptureFixture:
return original_disable_level return original_disable_level
def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None:
"""Set the level of a logger for the duration of a test. """Set the threshold level of a logger for the duration of a test.
Logging messages which are less severe than this level will not be captured.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
The levels of the loggers changed by this function will be The levels of the loggers changed by this function will be

View File

@ -400,6 +400,12 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
allow_in_venv = config.getoption("collect_in_virtualenv") allow_in_venv = config.getoption("collect_in_virtualenv")
if not allow_in_venv and _in_venv(collection_path): if not allow_in_venv and _in_venv(collection_path):
return True return True
if collection_path.is_dir():
norecursepatterns = config.getini("norecursedirs")
if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns):
return True
return None return None
@ -563,9 +569,6 @@ class Session(nodes.FSCollector):
ihook = self.gethookproxy(fspath.parent) ihook = self.gethookproxy(fspath.parent)
if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
return False return False
norecursepatterns = self.config.getini("norecursedirs")
if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
return False
return True return True
def _collectfile( def _collectfile(
@ -687,8 +690,8 @@ class Session(nodes.FSCollector):
# are not collected more than once. # are not collected more than once.
matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {}
# Dirnames of pkgs with dunder-init files. # Directories of pkgs with dunder-init files.
pkg_roots: Dict[str, Package] = {} pkg_roots: Dict[Path, Package] = {}
for argpath, names in self._initial_parts: for argpath, names in self._initial_parts:
self.trace("processing argument", (argpath, names)) self.trace("processing argument", (argpath, names))
@ -709,7 +712,7 @@ class Session(nodes.FSCollector):
col = self._collectfile(pkginit, handle_dupes=False) col = self._collectfile(pkginit, handle_dupes=False)
if col: if col:
if isinstance(col[0], Package): if isinstance(col[0], Package):
pkg_roots[str(parent)] = col[0] pkg_roots[parent] = col[0]
node_cache1[col[0].path] = [col[0]] node_cache1[col[0].path] = [col[0]]
# If it's a directory argument, recurse and look for any Subpackages. # If it's a directory argument, recurse and look for any Subpackages.
@ -718,7 +721,7 @@ class Session(nodes.FSCollector):
assert not names, f"invalid arg {(argpath, names)!r}" assert not names, f"invalid arg {(argpath, names)!r}"
seen_dirs: Set[Path] = set() seen_dirs: Set[Path] = set()
for direntry in visit(str(argpath), self._recurse): for direntry in visit(argpath, self._recurse):
if not direntry.is_file(): if not direntry.is_file():
continue continue
@ -733,8 +736,8 @@ class Session(nodes.FSCollector):
for x in self._collectfile(pkginit): for x in self._collectfile(pkginit):
yield x yield x
if isinstance(x, Package): if isinstance(x, Package):
pkg_roots[str(dirpath)] = x pkg_roots[dirpath] = x
if str(dirpath) in pkg_roots: if dirpath in pkg_roots:
# Do not collect packages here. # Do not collect packages here.
continue continue
@ -751,7 +754,7 @@ class Session(nodes.FSCollector):
if argpath in node_cache1: if argpath in node_cache1:
col = node_cache1[argpath] col = node_cache1[argpath]
else: else:
collect_root = pkg_roots.get(str(argpath.parent), self) collect_root = pkg_roots.get(argpath.parent, self)
col = collect_root._collectfile(argpath, handle_dupes=False) col = collect_root._collectfile(argpath, handle_dupes=False)
if col: if col:
node_cache1[argpath] = col node_cache1[argpath] = col

View File

@ -18,6 +18,7 @@ import ast
import dataclasses import dataclasses
import enum import enum
import re import re
import sys
import types import types
from typing import Callable from typing import Callable
from typing import Iterator from typing import Iterator
@ -26,6 +27,11 @@ from typing import NoReturn
from typing import Optional from typing import Optional
from typing import Sequence from typing import Sequence
if sys.version_info >= (3, 8):
astNameConstant = ast.Constant
else:
astNameConstant = ast.NameConstant
__all__ = [ __all__ = [
"Expression", "Expression",
@ -132,7 +138,7 @@ IDENT_PREFIX = "$"
def expression(s: Scanner) -> ast.Expression: def expression(s: Scanner) -> ast.Expression:
if s.accept(TokenType.EOF): if s.accept(TokenType.EOF):
ret: ast.expr = ast.NameConstant(False) ret: ast.expr = astNameConstant(False)
else: else:
ret = expr(s) ret = expr(s)
s.accept(TokenType.EOF, reject=True) s.accept(TokenType.EOF, reject=True)

View File

@ -28,6 +28,7 @@ from ..compat import NOTSET
from ..compat import NotSetType from ..compat import NotSetType
from _pytest.config import Config from _pytest.config import Config
from _pytest.deprecated import check_ispytest from _pytest.deprecated import check_ispytest
from _pytest.deprecated import MARKED_FIXTURE
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.warning_types import PytestUnknownMarkWarning from _pytest.warning_types import PytestUnknownMarkWarning
@ -412,6 +413,12 @@ def store_mark(obj, mark: Mark) -> None:
This is used to implement the Mark declarations/decorators correctly. This is used to implement the Mark declarations/decorators correctly.
""" """
assert isinstance(mark, Mark), mark assert isinstance(mark, Mark), mark
from ..fixtures import getfixturemarker
if getfixturemarker(obj) is not None:
warnings.warn(MARKED_FIXTURE, stacklevel=2)
# Always reassign name to avoid updating pytestmark in a reference that # Always reassign name to avoid updating pytestmark in a reference that
# was only borrowed. # was only borrowed.
obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark]

View File

@ -6,6 +6,7 @@ import collections.abc
import contextlib import contextlib
import gc import gc
import importlib import importlib
import locale
import os import os
import platform import platform
import re import re
@ -129,6 +130,7 @@ class LsofFdLeakChecker:
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True, check=True,
text=True, text=True,
encoding=locale.getpreferredencoding(False),
).stdout ).stdout
def isopen(line: str) -> bool: def isopen(line: str) -> bool:

View File

@ -734,9 +734,6 @@ class Package(Module):
ihook = self.session.gethookproxy(fspath.parent) ihook = self.session.gethookproxy(fspath.parent)
if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
return False return False
norecursepatterns = self.config.getini("norecursedirs")
if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
return False
return True return True
def _collectfile( def _collectfile(
@ -767,7 +764,9 @@ class Package(Module):
this_path = self.path.parent this_path = self.path.parent
# Always collect the __init__ first. # Always collect the __init__ first.
if path_matches_patterns(self.path, self.config.getini("python_files")): if self.session.isinitpath(self.path) or path_matches_patterns(
self.path, self.config.getini("python_files")
):
yield Module.from_parent(self, path=self.path) yield Module.from_parent(self, path=self.path)
pkg_prefixes: Set[Path] = set() pkg_prefixes: Set[Path] = set()

View File

@ -56,6 +56,12 @@ class PytestRemovedIn8Warning(PytestDeprecationWarning):
__module__ = "pytest" __module__ = "pytest"
class PytestRemovedIn9Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 9."""
__module__ = "pytest"
class PytestReturnNotNoneWarning(PytestRemovedIn8Warning): class PytestReturnNotNoneWarning(PytestRemovedIn8Warning):
"""Warning emitted when a test function is returning value other than None.""" """Warning emitted when a test function is returning value other than None."""

View File

@ -49,6 +49,8 @@ def catch_warnings_for_item(
warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning)
warnings.filterwarnings("error", category=pytest.PytestRemovedIn8Warning)
apply_warning_filters(config_filters, cmdline_filters) apply_warning_filters(config_filters, cmdline_filters)
# apply filters from "filterwarnings" marks # apply filters from "filterwarnings" marks

View File

@ -71,6 +71,7 @@ from _pytest.warning_types import PytestConfigWarning
from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestExperimentalApiWarning from _pytest.warning_types import PytestExperimentalApiWarning
from _pytest.warning_types import PytestRemovedIn8Warning from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import PytestRemovedIn9Warning
from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestReturnNotNoneWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
@ -131,6 +132,7 @@ __all__ = [
"PytestDeprecationWarning", "PytestDeprecationWarning",
"PytestExperimentalApiWarning", "PytestExperimentalApiWarning",
"PytestRemovedIn8Warning", "PytestRemovedIn8Warning",
"PytestRemovedIn9Warning",
"PytestReturnNotNoneWarning", "PytestReturnNotNoneWarning",
"Pytester", "Pytester",
"PytestPluginManager", "PytestPluginManager",

View File

@ -1,7 +1,9 @@
import contextlib
import multiprocessing import multiprocessing
import os import os
import sys import sys
import time import time
import warnings
from unittest import mock from unittest import mock
import pytest import pytest
@ -9,6 +11,14 @@ from py import error
from py.path import local from py.path import local
@contextlib.contextmanager
def ignore_encoding_warning():
with warnings.catch_warnings():
with contextlib.suppress(NameError): # new in 3.10
warnings.simplefilter("ignore", EncodingWarning)
yield
class CommonFSTests: class CommonFSTests:
def test_constructor_equality(self, path1): def test_constructor_equality(self, path1):
p = path1.__class__(path1) p = path1.__class__(path1)
@ -223,6 +233,7 @@ class CommonFSTests:
assert not (path1 < path1) assert not (path1 < path1)
def test_simple_read(self, path1): def test_simple_read(self, path1):
with ignore_encoding_warning():
x = path1.join("samplefile").read("r") x = path1.join("samplefile").read("r")
assert x == "samplefile\n" assert x == "samplefile\n"
@ -265,11 +276,13 @@ class CommonFSTests:
def test_readlines(self, path1): def test_readlines(self, path1):
fn = path1.join("samplefile") fn = path1.join("samplefile")
with ignore_encoding_warning():
contents = fn.readlines() contents = fn.readlines()
assert contents == ["samplefile\n"] assert contents == ["samplefile\n"]
def test_readlines_nocr(self, path1): def test_readlines_nocr(self, path1):
fn = path1.join("samplefile") fn = path1.join("samplefile")
with ignore_encoding_warning():
contents = fn.readlines(cr=0) contents = fn.readlines(cr=0)
assert contents == ["samplefile", ""] assert contents == ["samplefile", ""]
@ -362,8 +375,8 @@ class CommonFSTests:
initpy.copy(copied) initpy.copy(copied)
try: try:
assert copied.check() assert copied.check()
s1 = initpy.read() s1 = initpy.read_text(encoding="utf-8")
s2 = copied.read() s2 = copied.read_text(encoding="utf-8")
assert s1 == s2 assert s1 == s2
finally: finally:
if copied.check(): if copied.check():
@ -376,8 +389,8 @@ class CommonFSTests:
otherdir.copy(copied) otherdir.copy(copied)
assert copied.check(dir=1) assert copied.check(dir=1)
assert copied.join("__init__.py").check(file=1) assert copied.join("__init__.py").check(file=1)
s1 = otherdir.join("__init__.py").read() s1 = otherdir.join("__init__.py").read_text(encoding="utf-8")
s2 = copied.join("__init__.py").read() s2 = copied.join("__init__.py").read_text(encoding="utf-8")
assert s1 == s2 assert s1 == s2
finally: finally:
if copied.check(dir=1): if copied.check(dir=1):
@ -463,13 +476,13 @@ def setuptestfs(path):
return return
# print "setting up test fs for", repr(path) # print "setting up test fs for", repr(path)
samplefile = path.ensure("samplefile") samplefile = path.ensure("samplefile")
samplefile.write("samplefile\n") samplefile.write_text("samplefile\n", encoding="utf-8")
execfile = path.ensure("execfile") execfile = path.ensure("execfile")
execfile.write("x=42") execfile.write_text("x=42", encoding="utf-8")
execfilepy = path.ensure("execfile.py") execfilepy = path.ensure("execfile.py")
execfilepy.write("x=42") execfilepy.write_text("x=42", encoding="utf-8")
d = {1: 2, "hello": "world", "answer": 42} d = {1: 2, "hello": "world", "answer": 42}
path.ensure("samplepickle").dump(d) path.ensure("samplepickle").dump(d)
@ -481,22 +494,24 @@ def setuptestfs(path):
otherdir.ensure("__init__.py") otherdir.ensure("__init__.py")
module_a = otherdir.ensure("a.py") module_a = otherdir.ensure("a.py")
module_a.write("from .b import stuff as result\n") module_a.write_text("from .b import stuff as result\n", encoding="utf-8")
module_b = otherdir.ensure("b.py") module_b = otherdir.ensure("b.py")
module_b.write('stuff="got it"\n') module_b.write_text('stuff="got it"\n', encoding="utf-8")
module_c = otherdir.ensure("c.py") module_c = otherdir.ensure("c.py")
module_c.write( module_c.write_text(
"""import py; """import py;
import otherdir.a import otherdir.a
value = otherdir.a.result value = otherdir.a.result
""" """,
encoding="utf-8",
) )
module_d = otherdir.ensure("d.py") module_d = otherdir.ensure("d.py")
module_d.write( module_d.write_text(
"""import py; """import py;
from otherdir import a from otherdir import a
value2 = a.result value2 = a.result
""" """,
encoding="utf-8",
) )
@ -534,9 +549,11 @@ def batch_make_numbered_dirs(rootdir, repeats):
for i in range(repeats): for i in range(repeats):
dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir) dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir)
file_ = dir_.join("foo") file_ = dir_.join("foo")
file_.write("%s" % i) file_.write_text("%s" % i, encoding="utf-8")
actual = int(file_.read()) actual = int(file_.read_text(encoding="utf-8"))
assert actual == i, f"int(file_.read()) is {actual} instead of {i}" assert (
actual == i
), f"int(file_.read_text(encoding='utf-8')) is {actual} instead of {i}"
dir_.join(".lock").remove(ignore_errors=True) dir_.join(".lock").remove(ignore_errors=True)
return True return True
@ -692,14 +709,14 @@ class TestLocalPath(CommonFSTests):
def test_open_and_ensure(self, path1): def test_open_and_ensure(self, path1):
p = path1.join("sub1", "sub2", "file") p = path1.join("sub1", "sub2", "file")
with p.open("w", ensure=1) as f: with p.open("w", ensure=1, encoding="utf-8") as f:
f.write("hello") f.write("hello")
assert p.read() == "hello" assert p.read_text(encoding="utf-8") == "hello"
def test_write_and_ensure(self, path1): def test_write_and_ensure(self, path1):
p = path1.join("sub1", "sub2", "file") p = path1.join("sub1", "sub2", "file")
p.write("hello", ensure=1) p.write_text("hello", ensure=1, encoding="utf-8")
assert p.read() == "hello" assert p.read_text(encoding="utf-8") == "hello"
@pytest.mark.parametrize("bin", (False, True)) @pytest.mark.parametrize("bin", (False, True))
def test_dump(self, tmpdir, bin): def test_dump(self, tmpdir, bin):
@ -770,9 +787,9 @@ class TestLocalPath(CommonFSTests):
newfile = tmpdir.join("test1", "test") newfile = tmpdir.join("test1", "test")
newfile.ensure() newfile.ensure()
assert newfile.check(file=1) assert newfile.check(file=1)
newfile.write("42") newfile.write_text("42", encoding="utf-8")
newfile.ensure() newfile.ensure()
s = newfile.read() s = newfile.read_text(encoding="utf-8")
assert s == "42" assert s == "42"
def test_ensure_filepath_withoutdir(self, tmpdir): def test_ensure_filepath_withoutdir(self, tmpdir):
@ -806,9 +823,9 @@ class TestLocalPath(CommonFSTests):
newfilename = "/test" * 60 # type:ignore[unreachable] newfilename = "/test" * 60 # type:ignore[unreachable]
l1 = tmpdir.join(newfilename) l1 = tmpdir.join(newfilename)
l1.ensure(file=True) l1.ensure(file=True)
l1.write("foo") l1.write_text("foo", encoding="utf-8")
l2 = tmpdir.join(newfilename) l2 = tmpdir.join(newfilename)
assert l2.read() == "foo" assert l2.read_text(encoding="utf-8") == "foo"
def test_visit_depth_first(self, tmpdir): def test_visit_depth_first(self, tmpdir):
tmpdir.ensure("a", "1") tmpdir.ensure("a", "1")
@ -1278,14 +1295,14 @@ class TestPOSIXLocalPath:
def test_hardlink(self, tmpdir): def test_hardlink(self, tmpdir):
linkpath = tmpdir.join("test") linkpath = tmpdir.join("test")
filepath = tmpdir.join("file") filepath = tmpdir.join("file")
filepath.write("Hello") filepath.write_text("Hello", encoding="utf-8")
nlink = filepath.stat().nlink nlink = filepath.stat().nlink
linkpath.mklinkto(filepath) linkpath.mklinkto(filepath)
assert filepath.stat().nlink == nlink + 1 assert filepath.stat().nlink == nlink + 1
def test_symlink_are_identical(self, tmpdir): def test_symlink_are_identical(self, tmpdir):
filepath = tmpdir.join("file") filepath = tmpdir.join("file")
filepath.write("Hello") filepath.write_text("Hello", encoding="utf-8")
linkpath = tmpdir.join("test") linkpath = tmpdir.join("test")
linkpath.mksymlinkto(filepath) linkpath.mksymlinkto(filepath)
assert linkpath.readlink() == str(filepath) assert linkpath.readlink() == str(filepath)
@ -1293,7 +1310,7 @@ class TestPOSIXLocalPath:
def test_symlink_isfile(self, tmpdir): def test_symlink_isfile(self, tmpdir):
linkpath = tmpdir.join("test") linkpath = tmpdir.join("test")
filepath = tmpdir.join("file") filepath = tmpdir.join("file")
filepath.write("") filepath.write_text("", encoding="utf-8")
linkpath.mksymlinkto(filepath) linkpath.mksymlinkto(filepath)
assert linkpath.check(file=1) assert linkpath.check(file=1)
assert not linkpath.check(link=0, file=1) assert not linkpath.check(link=0, file=1)
@ -1302,10 +1319,12 @@ class TestPOSIXLocalPath:
def test_symlink_relative(self, tmpdir): def test_symlink_relative(self, tmpdir):
linkpath = tmpdir.join("test") linkpath = tmpdir.join("test")
filepath = tmpdir.join("file") filepath = tmpdir.join("file")
filepath.write("Hello") filepath.write_text("Hello", encoding="utf-8")
linkpath.mksymlinkto(filepath, absolute=False) linkpath.mksymlinkto(filepath, absolute=False)
assert linkpath.readlink() == "file" assert linkpath.readlink() == "file"
assert filepath.read() == linkpath.read() assert filepath.read_text(encoding="utf-8") == linkpath.read_text(
encoding="utf-8"
)
def test_symlink_not_existing(self, tmpdir): def test_symlink_not_existing(self, tmpdir):
linkpath = tmpdir.join("testnotexisting") linkpath = tmpdir.join("testnotexisting")
@ -1338,7 +1357,7 @@ class TestPOSIXLocalPath:
def test_realpath_file(self, tmpdir): def test_realpath_file(self, tmpdir):
linkpath = tmpdir.join("test") linkpath = tmpdir.join("test")
filepath = tmpdir.join("file") filepath = tmpdir.join("file")
filepath.write("") filepath.write_text("", encoding="utf-8")
linkpath.mksymlinkto(filepath) linkpath.mksymlinkto(filepath)
realpath = linkpath.realpath() realpath = linkpath.realpath()
assert realpath.basename == "file" assert realpath.basename == "file"
@ -1383,7 +1402,7 @@ class TestPOSIXLocalPath:
atime1 = path.atime() atime1 = path.atime()
# we could wait here but timer resolution is very # we could wait here but timer resolution is very
# system dependent # system dependent
path.read() path.read_binary()
time.sleep(ATIME_RESOLUTION) time.sleep(ATIME_RESOLUTION)
atime2 = path.atime() atime2 = path.atime()
time.sleep(ATIME_RESOLUTION) time.sleep(ATIME_RESOLUTION)
@ -1467,7 +1486,7 @@ class TestPOSIXLocalPath:
test_files = ["a", "b", "c"] test_files = ["a", "b", "c"]
src = tmpdir.join("src") src = tmpdir.join("src")
for f in test_files: for f in test_files:
src.join(f).write(f, ensure=True) src.join(f).write_text(f, ensure=True, encoding="utf-8")
dst = tmpdir.join("dst") dst = tmpdir.join("dst")
# a small delay before the copy # a small delay before the copy
time.sleep(ATIME_RESOLUTION) time.sleep(ATIME_RESOLUTION)
@ -1521,6 +1540,7 @@ class TestUnicodePy2Py3:
def test_read_write(self, tmpdir): def test_read_write(self, tmpdir):
x = tmpdir.join("hello") x = tmpdir.join("hello")
part = "hällo" part = "hällo"
with ignore_encoding_warning():
x.write(part) x.write(part)
assert x.read() == part assert x.read() == part
x.write(part.encode(sys.getdefaultencoding())) x.write(part.encode(sys.getdefaultencoding()))

View File

@ -267,7 +267,7 @@ class TestGeneralUsage:
def test_issue109_sibling_conftests_not_loaded(self, pytester: Pytester) -> None: def test_issue109_sibling_conftests_not_loaded(self, pytester: Pytester) -> None:
sub1 = pytester.mkdir("sub1") sub1 = pytester.mkdir("sub1")
sub2 = pytester.mkdir("sub2") sub2 = pytester.mkdir("sub2")
sub1.joinpath("conftest.py").write_text("assert 0") sub1.joinpath("conftest.py").write_text("assert 0", encoding="utf-8")
result = pytester.runpytest(sub2) result = pytester.runpytest(sub2)
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
sub2.joinpath("__init__.py").touch() sub2.joinpath("__init__.py").touch()
@ -467,7 +467,7 @@ class TestGeneralUsage:
assert "invalid" in str(excinfo.value) assert "invalid" in str(excinfo.value)
p = pytester.path.joinpath("test_test_plugins_given_as_strings.py") p = pytester.path.joinpath("test_test_plugins_given_as_strings.py")
p.write_text("def test_foo(): pass") p.write_text("def test_foo(): pass", encoding="utf-8")
mod = types.ModuleType("myplugin") mod = types.ModuleType("myplugin")
monkeypatch.setitem(sys.modules, "myplugin", mod) monkeypatch.setitem(sys.modules, "myplugin", mod)
assert pytest.main(args=[str(pytester.path)], plugins=["myplugin"]) == 0 assert pytest.main(args=[str(pytester.path)], plugins=["myplugin"]) == 0
@ -587,7 +587,7 @@ class TestInvocationVariants:
def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None: def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None:
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
path = pytester.mkpydir("tpkg") path = pytester.mkpydir("tpkg")
path.joinpath("test_hello.py").write_text("raise ImportError") path.joinpath("test_hello.py").write_text("raise ImportError", encoding="utf-8")
result = pytester.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True) result = pytester.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True)
assert result.ret != 0 assert result.ret != 0
@ -597,10 +597,10 @@ class TestInvocationVariants:
def test_pyargs_only_imported_once(self, pytester: Pytester) -> None: def test_pyargs_only_imported_once(self, pytester: Pytester) -> None:
pkg = pytester.mkpydir("foo") pkg = pytester.mkpydir("foo")
pkg.joinpath("test_foo.py").write_text( pkg.joinpath("test_foo.py").write_text(
"print('hello from test_foo')\ndef test(): pass" "print('hello from test_foo')\ndef test(): pass", encoding="utf-8"
) )
pkg.joinpath("conftest.py").write_text( pkg.joinpath("conftest.py").write_text(
"def pytest_configure(config): print('configuring')" "def pytest_configure(config): print('configuring')", encoding="utf-8"
) )
result = pytester.runpytest( result = pytester.runpytest(
@ -613,7 +613,7 @@ class TestInvocationVariants:
def test_pyargs_filename_looks_like_module(self, pytester: Pytester) -> None: def test_pyargs_filename_looks_like_module(self, pytester: Pytester) -> None:
pytester.path.joinpath("conftest.py").touch() pytester.path.joinpath("conftest.py").touch()
pytester.path.joinpath("t.py").write_text("def test(): pass") pytester.path.joinpath("t.py").write_text("def test(): pass", encoding="utf-8")
result = pytester.runpytest("--pyargs", "t.py") result = pytester.runpytest("--pyargs", "t.py")
assert result.ret == ExitCode.OK assert result.ret == ExitCode.OK
@ -622,8 +622,12 @@ class TestInvocationVariants:
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
path = pytester.mkpydir("tpkg") path = pytester.mkpydir("tpkg")
path.joinpath("test_hello.py").write_text("def test_hello(): pass") path.joinpath("test_hello.py").write_text(
path.joinpath("test_world.py").write_text("def test_world(): pass") "def test_hello(): pass", encoding="utf-8"
)
path.joinpath("test_world.py").write_text(
"def test_world(): pass", encoding="utf-8"
)
result = pytester.runpytest("--pyargs", "tpkg") result = pytester.runpytest("--pyargs", "tpkg")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines(["*2 passed*"]) result.stdout.fnmatch_lines(["*2 passed*"])
@ -662,13 +666,15 @@ class TestInvocationVariants:
ns = d.joinpath("ns_pkg") ns = d.joinpath("ns_pkg")
ns.mkdir() ns.mkdir()
ns.joinpath("__init__.py").write_text( ns.joinpath("__init__.py").write_text(
"__import__('pkg_resources').declare_namespace(__name__)" "__import__('pkg_resources').declare_namespace(__name__)",
encoding="utf-8",
) )
lib = ns.joinpath(dirname) lib = ns.joinpath(dirname)
lib.mkdir() lib.mkdir()
lib.joinpath("__init__.py").touch() lib.joinpath("__init__.py").touch()
lib.joinpath(f"test_{dirname}.py").write_text( lib.joinpath(f"test_{dirname}.py").write_text(
f"def test_{dirname}(): pass\ndef test_other():pass" f"def test_{dirname}(): pass\ndef test_other():pass",
encoding="utf-8",
) )
# The structure of the test directory is now: # The structure of the test directory is now:
@ -754,10 +760,10 @@ class TestInvocationVariants:
lib.mkdir() lib.mkdir()
lib.joinpath("__init__.py").touch() lib.joinpath("__init__.py").touch()
lib.joinpath("test_bar.py").write_text( lib.joinpath("test_bar.py").write_text(
"def test_bar(): pass\ndef test_other(a_fixture):pass" "def test_bar(): pass\ndef test_other(a_fixture):pass", encoding="utf-8"
) )
lib.joinpath("conftest.py").write_text( lib.joinpath("conftest.py").write_text(
"import pytest\n@pytest.fixture\ndef a_fixture():pass" "import pytest\n@pytest.fixture\ndef a_fixture():pass", encoding="utf-8"
) )
d_local = pytester.mkdir("symlink_root") d_local = pytester.mkdir("symlink_root")
@ -1158,7 +1164,6 @@ def test_usage_error_code(pytester: Pytester) -> None:
assert result.ret == ExitCode.USAGE_ERROR assert result.ret == ExitCode.USAGE_ERROR
@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning")
def test_warn_on_async_function(pytester: Pytester) -> None: def test_warn_on_async_function(pytester: Pytester) -> None:
# In the below we .close() the coroutine only to avoid # In the below we .close() the coroutine only to avoid
# "RuntimeWarning: coroutine 'test_2' was never awaited" # "RuntimeWarning: coroutine 'test_2' was never awaited"
@ -1175,7 +1180,7 @@ def test_warn_on_async_function(pytester: Pytester) -> None:
return coro return coro
""" """
) )
result = pytester.runpytest() result = pytester.runpytest("-Wdefault")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"test_async.py::test_1", "test_async.py::test_1",
@ -1191,7 +1196,6 @@ def test_warn_on_async_function(pytester: Pytester) -> None:
) )
@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning")
def test_warn_on_async_gen_function(pytester: Pytester) -> None: def test_warn_on_async_gen_function(pytester: Pytester) -> None:
pytester.makepyfile( pytester.makepyfile(
test_async=""" test_async="""
@ -1203,7 +1207,7 @@ def test_warn_on_async_gen_function(pytester: Pytester) -> None:
return test_2() return test_2()
""" """
) )
result = pytester.runpytest() result = pytester.runpytest("-Wdefault")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"test_async.py::test_1", "test_async.py::test_1",
@ -1276,8 +1280,7 @@ def test_tee_stdio_captures_and_live_prints(pytester: Pytester) -> None:
result.stderr.fnmatch_lines(["*@this is stderr@*"]) result.stderr.fnmatch_lines(["*@this is stderr@*"])
# now ensure the output is in the junitxml # now ensure the output is in the junitxml
with open(pytester.path.joinpath("output.xml")) as f: fullXml = pytester.path.joinpath("output.xml").read_text(encoding="utf-8")
fullXml = f.read()
assert "@this is stdout@\n" in fullXml assert "@this is stdout@\n" in fullXml
assert "@this is stderr@\n" in fullXml assert "@this is stderr@\n" in fullXml

View File

@ -374,7 +374,7 @@ def test_excinfo_no_sourcecode():
def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
# XXX: simplified locally testable version # XXX: simplified locally testable version
tmp_path.joinpath("test.txt").write_text("{{ h()}}:") tmp_path.joinpath("test.txt").write_text("{{ h()}}:", encoding="utf-8")
jinja2 = pytest.importorskip("jinja2") jinja2 = pytest.importorskip("jinja2")
loader = jinja2.FileSystemLoader(str(tmp_path)) loader = jinja2.FileSystemLoader(str(tmp_path))
@ -451,7 +451,7 @@ class TestFormattedExcinfo:
source = textwrap.dedent(source) source = textwrap.dedent(source)
modpath = tmp_path.joinpath("mod.py") modpath = tmp_path.joinpath("mod.py")
tmp_path.joinpath("__init__.py").touch() tmp_path.joinpath("__init__.py").touch()
modpath.write_text(source) modpath.write_text(source, encoding="utf-8")
importlib.invalidate_caches() importlib.invalidate_caches()
return import_path(modpath, root=tmp_path) return import_path(modpath, root=tmp_path)
@ -1023,7 +1023,7 @@ raise ValueError()
""" """
) )
excinfo = pytest.raises(ValueError, mod.f) excinfo = pytest.raises(ValueError, mod.f)
tmp_path.joinpath("mod.py").write_text("asdf") tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8")
excinfo.traceback = excinfo.traceback.filter(excinfo) excinfo.traceback = excinfo.traceback.filter(excinfo)
repr = excinfo.getrepr() repr = excinfo.getrepr()
repr.toterminal(tw_mock) repr.toterminal(tw_mock)

View File

@ -294,7 +294,7 @@ def test_source_of_class_at_eof_without_newline(_sys_snapshot, tmp_path: Path) -
""" """
) )
path = tmp_path.joinpath("a.py") path = tmp_path.joinpath("a.py")
path.write_text(str(source)) path.write_text(str(source), encoding="utf-8")
mod: Any = import_path(path, root=tmp_path) mod: Any = import_path(path, root=tmp_path)
s2 = Source(mod.A) s2 = Source(mod.A)
assert str(source).strip() == str(s2).strip() assert str(source).strip() == str(s2).strip()

View File

@ -103,7 +103,7 @@ def test_strict_option_is_deprecated(pytester: Pytester) -> None:
def test_foo(): pass def test_foo(): pass
""" """
) )
result = pytester.runpytest("--strict") result = pytester.runpytest("--strict", "-Wdefault::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"'unknown' not found in `markers` configuration option", "'unknown' not found in `markers` configuration option",
@ -189,7 +189,7 @@ class TestSkipMsgArgumentDeprecated:
pytest.skip(msg="skippedmsg") pytest.skip(msg="skippedmsg")
""" """
) )
result = pytester.runpytest(p) result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, " "*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, "
@ -208,7 +208,7 @@ class TestSkipMsgArgumentDeprecated:
pytest.fail(msg="failedmsg") pytest.fail(msg="failedmsg")
""" """
) )
result = pytester.runpytest(p) result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, " "*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, "
@ -227,7 +227,7 @@ class TestSkipMsgArgumentDeprecated:
pytest.exit(msg="exitmsg") pytest.exit(msg="exitmsg")
""" """
) )
result = pytester.runpytest(p) result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, " "*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, "
@ -245,7 +245,7 @@ def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None:
""" """
) )
result = pytester.runpytest() result = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*", "*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*",
@ -281,6 +281,57 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
from _pytest.python import Instance # noqa: F401 from _pytest.python import Instance # noqa: F401
def test_fixture_disallow_on_marked_functions():
"""Test that applying @pytest.fixture to a marked function warns (#3364)."""
with pytest.warns(
pytest.PytestRemovedIn8Warning,
match=r"Marks applied to fixtures have no effect",
) as record:
@pytest.fixture
@pytest.mark.parametrize("example", ["hello"])
@pytest.mark.usefixtures("tmp_path")
def foo():
raise NotImplementedError()
# it's only possible to get one warning here because you're already prevented
# from applying @fixture twice
# ValueError("fixture is being applied more than once to the same function")
assert len(record) == 1
def test_fixture_disallow_marks_on_fixtures():
"""Test that applying a mark to a fixture warns (#3364)."""
with pytest.warns(
pytest.PytestRemovedIn8Warning,
match=r"Marks applied to fixtures have no effect",
) as record:
@pytest.mark.parametrize("example", ["hello"])
@pytest.mark.usefixtures("tmp_path")
@pytest.fixture
def foo():
raise NotImplementedError()
assert len(record) == 2 # one for each mark decorator
def test_fixture_disallowed_between_marks():
"""Test that applying a mark to a fixture warns (#3364)."""
with pytest.warns(
pytest.PytestRemovedIn8Warning,
match=r"Marks applied to fixtures have no effect",
) as record:
@pytest.mark.parametrize("example", ["hello"])
@pytest.fixture
@pytest.mark.usefixtures("tmp_path")
def foo():
raise NotImplementedError()
assert len(record) == 2 # one for each mark decorator
@pytest.mark.filterwarnings("default") @pytest.mark.filterwarnings("default")
def test_nose_deprecated_with_setup(pytester: Pytester) -> None: def test_nose_deprecated_with_setup(pytester: Pytester) -> None:
pytest.importorskip("nose") pytest.importorskip("nose")
@ -299,7 +350,7 @@ def test_nose_deprecated_with_setup(pytester: Pytester) -> None:
... ...
""" """
) )
output = pytester.runpytest() output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning")
message = [ message = [
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
"*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)",
@ -327,7 +378,7 @@ def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None:
... ...
""" """
) )
output = pytester.runpytest() output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning")
message = [ message = [
"*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.",
"*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`", "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`",

View File

@ -0,0 +1,2 @@
def test_init():
pass

View File

@ -1,2 +1,2 @@
def test(): def test_foo():
pass pass

View File

@ -81,7 +81,7 @@ def test_root_logger_affected(pytester: Pytester) -> None:
# not the info one, because the default level of the root logger is # not the info one, because the default level of the root logger is
# WARNING. # WARNING.
assert os.path.isfile(log_file) assert os.path.isfile(log_file)
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "info text going to logger" not in contents assert "info text going to logger" not in contents
assert "warning text going to logger" in contents assert "warning text going to logger" in contents
@ -656,7 +656,7 @@ def test_log_file_cli(pytester: Pytester) -> None:
# make sure that we get a '0' exit code for the testsuite # make sure that we get a '0' exit code for the testsuite
assert result.ret == 0 assert result.ret == 0
assert os.path.isfile(log_file) assert os.path.isfile(log_file)
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "This log message will be shown" in contents assert "This log message will be shown" in contents
assert "This log message won't be shown" not in contents assert "This log message won't be shown" not in contents
@ -687,7 +687,7 @@ def test_log_file_cli_level(pytester: Pytester) -> None:
# make sure that we get a '0' exit code for the testsuite # make sure that we get a '0' exit code for the testsuite
assert result.ret == 0 assert result.ret == 0
assert os.path.isfile(log_file) assert os.path.isfile(log_file)
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "This log message will be shown" in contents assert "This log message will be shown" in contents
assert "This log message won't be shown" not in contents assert "This log message won't be shown" not in contents
@ -738,7 +738,7 @@ def test_log_file_ini(pytester: Pytester) -> None:
# make sure that we get a '0' exit code for the testsuite # make sure that we get a '0' exit code for the testsuite
assert result.ret == 0 assert result.ret == 0
assert os.path.isfile(log_file) assert os.path.isfile(log_file)
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "This log message will be shown" in contents assert "This log message will be shown" in contents
assert "This log message won't be shown" not in contents assert "This log message won't be shown" not in contents
@ -777,7 +777,7 @@ def test_log_file_ini_level(pytester: Pytester) -> None:
# make sure that we get a '0' exit code for the testsuite # make sure that we get a '0' exit code for the testsuite
assert result.ret == 0 assert result.ret == 0
assert os.path.isfile(log_file) assert os.path.isfile(log_file)
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "This log message will be shown" in contents assert "This log message will be shown" in contents
assert "This log message won't be shown" not in contents assert "This log message won't be shown" not in contents
@ -985,7 +985,7 @@ def test_log_in_hooks(pytester: Pytester) -> None:
) )
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"]) result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"])
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert "sessionstart" in contents assert "sessionstart" in contents
assert "runtestloop" in contents assert "runtestloop" in contents
@ -1021,7 +1021,7 @@ def test_log_in_runtest_logreport(pytester: Pytester) -> None:
""" """
) )
pytester.runpytest() pytester.runpytest()
with open(log_file) as rfh: with open(log_file, encoding="utf-8") as rfh:
contents = rfh.read() contents = rfh.read()
assert contents.count("logreport") == 3 assert contents.count("logreport") == 3
@ -1065,11 +1065,11 @@ def test_log_set_path(pytester: Pytester) -> None:
""" """
) )
pytester.runpytest() pytester.runpytest()
with open(os.path.join(report_dir_base, "test_first")) as rfh: with open(os.path.join(report_dir_base, "test_first"), encoding="utf-8") as rfh:
content = rfh.read() content = rfh.read()
assert "message from test 1" in content assert "message from test 1" in content
with open(os.path.join(report_dir_base, "test_second")) as rfh: with open(os.path.join(report_dir_base, "test_second"), encoding="utf-8") as rfh:
content = rfh.read() content = rfh.read()
assert "message from test 2" in content assert "message from test 2" in content

View File

@ -1,15 +1,15 @@
anyio[curio,trio]==3.7.0 anyio[curio,trio]==3.7.0
django==4.2.1 django==4.2.2
pytest-asyncio==0.21.0 pytest-asyncio==0.21.0
pytest-bdd==6.1.1 pytest-bdd==6.1.1
pytest-cov==4.1.0 pytest-cov==4.1.0
pytest-django==4.5.2 pytest-django==4.5.2
pytest-flakes==4.0.5 pytest-flakes==4.0.5
pytest-html==3.2.0 pytest-html==3.2.0
pytest-mock==3.10.0 pytest-mock==3.11.1
pytest-rerunfailures==11.1.2 pytest-rerunfailures==11.1.2
pytest-sugar==0.9.7 pytest-sugar==0.9.7
pytest-trio==0.7.0 pytest-trio==0.7.0
pytest-twisted==1.14.0 pytest-twisted==1.14.0
twisted==22.8.0 twisted==22.8.0
pytest-xvfb==2.0.0 pytest-xvfb==3.0.0

View File

@ -60,7 +60,8 @@ class TestModule:
""".format( """.format(
str(root2) str(root2)
) )
) ),
encoding="utf-8",
) )
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.chdir(root2) mp.chdir(root2)
@ -832,7 +833,8 @@ class TestConftestCustomization:
mod = outcome.get_result() mod = outcome.get_result()
mod.obj.hello = "world" mod.obj.hello = "world"
""" """
) ),
encoding="utf-8",
) )
b.joinpath("test_module.py").write_text( b.joinpath("test_module.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -840,7 +842,8 @@ class TestConftestCustomization:
def test_hello(): def test_hello():
assert hello == "world" assert hello == "world"
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
@ -861,7 +864,8 @@ class TestConftestCustomization:
for func in result: for func in result:
func._some123 = "world" func._some123 = "world"
""" """
) ),
encoding="utf-8",
) )
b.joinpath("test_module.py").write_text( b.joinpath("test_module.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -874,7 +878,8 @@ class TestConftestCustomization:
def test_hello(obj): def test_hello(obj):
assert obj == "world" assert obj == "world"
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
@ -897,25 +902,29 @@ class TestConftestCustomization:
def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None: def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None:
"""Ensure we can collect files with weird file extensions as Python """Ensure we can collect files with weird file extensions as Python
modules (#2369)""" modules (#2369)"""
# We'll implement a little finder and loader to import files containing # Implement a little meta path finder to import files containing
# Python source code whose file extension is ".narf". # Python source code whose file extension is ".narf".
pytester.makeconftest( pytester.makeconftest(
""" """
import sys, os, imp import sys
import os.path
from importlib.util import spec_from_loader
from importlib.machinery import SourceFileLoader
from _pytest.python import Module from _pytest.python import Module
class Loader(object): class MetaPathFinder:
def load_module(self, name): def find_spec(self, fullname, path, target=None):
return imp.load_source(name, name + ".narf") if os.path.exists(fullname + ".narf"):
class Finder(object): return spec_from_loader(
def find_module(self, name, path=None): fullname,
if os.path.exists(name + ".narf"): SourceFileLoader(fullname, fullname + ".narf"),
return Loader() )
sys.meta_path.append(Finder()) sys.meta_path.append(MetaPathFinder())
def pytest_collect_file(file_path, parent): def pytest_collect_file(file_path, parent):
if file_path.suffix == ".narf": if file_path.suffix == ".narf":
return Module.from_parent(path=file_path, parent=parent)""" return Module.from_parent(path=file_path, parent=parent)
"""
) )
pytester.makefile( pytester.makefile(
".narf", ".narf",
@ -970,7 +979,8 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None:
def pytest_runtest_teardown(item): def pytest_runtest_teardown(item):
assert item.path.stem == "test_in_sub1" assert item.path.stem == "test_in_sub1"
""" """
) ),
encoding="utf-8",
) )
sub2.joinpath("conftest.py").write_text( sub2.joinpath("conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -983,10 +993,11 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None:
def pytest_runtest_teardown(item): def pytest_runtest_teardown(item):
assert item.path.stem == "test_in_sub2" assert item.path.stem == "test_in_sub2"
""" """
),
encoding="utf-8",
) )
) sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass", encoding="utf-8")
sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass", encoding="utf-8")
sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass")
result = pytester.runpytest("-v", "-s") result = pytester.runpytest("-v", "-s")
result.assert_outcomes(passed=2) result.assert_outcomes(passed=2)
@ -1374,7 +1385,8 @@ def test_skip_duplicates_by_default(pytester: Pytester) -> None:
def test_real(): def test_real():
pass pass
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest(str(a), str(a)) result = pytester.runpytest(str(a), str(a))
result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*collected 1 item*"])
@ -1394,7 +1406,8 @@ def test_keep_duplicates(pytester: Pytester) -> None:
def test_real(): def test_real():
pass pass
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("--keep-duplicates", str(a), str(a)) result = pytester.runpytest("--keep-duplicates", str(a), str(a))
result.stdout.fnmatch_lines(["*collected 2 item*"]) result.stdout.fnmatch_lines(["*collected 2 item*"])
@ -1407,10 +1420,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None:
def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: def test_package_collection_init_given_as_argument(pytester: Pytester) -> None:
"""Regression test for #3749""" """Regression test for #3749, #8976, #9263, #9313.
Specifying an __init__.py file directly should collect only the __init__.py
Module, not the entire package.
"""
p = pytester.copy_example("collect/package_init_given_as_arg") p = pytester.copy_example("collect/package_init_given_as_arg")
result = pytester.runpytest(p / "pkg" / "__init__.py") items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py")
result.stdout.fnmatch_lines(["*1 passed*"]) assert len(items) == 1
assert items[0].name == "test_init"
def test_package_with_modules(pytester: Pytester) -> None: def test_package_with_modules(pytester: Pytester) -> None:
@ -1439,8 +1457,12 @@ def test_package_with_modules(pytester: Pytester) -> None:
sub2_test = sub2.joinpath("test") sub2_test = sub2.joinpath("test")
sub2_test.mkdir(parents=True) sub2_test.mkdir(parents=True)
sub1_test.joinpath("test_in_sub1.py").write_text("def test_1(): pass") sub1_test.joinpath("test_in_sub1.py").write_text(
sub2_test.joinpath("test_in_sub2.py").write_text("def test_2(): pass") "def test_1(): pass", encoding="utf-8"
)
sub2_test.joinpath("test_in_sub2.py").write_text(
"def test_2(): pass", encoding="utf-8"
)
# Execute from . # Execute from .
result = pytester.runpytest("-v", "-s") result = pytester.runpytest("-v", "-s")
@ -1484,9 +1506,11 @@ def test_package_ordering(pytester: Pytester) -> None:
sub2_test = sub2.joinpath("test") sub2_test = sub2.joinpath("test")
sub2_test.mkdir(parents=True) sub2_test.mkdir(parents=True)
root.joinpath("Test_root.py").write_text("def test_1(): pass") root.joinpath("Test_root.py").write_text("def test_1(): pass", encoding="utf-8")
sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass") sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass", encoding="utf-8")
sub2_test.joinpath("test_sub2.py").write_text("def test_3(): pass") sub2_test.joinpath("test_sub2.py").write_text(
"def test_3(): pass", encoding="utf-8"
)
# Execute from . # Execute from .
result = pytester.runpytest("-v", "-s") result = pytester.runpytest("-v", "-s")

View File

@ -289,7 +289,8 @@ class TestFillFixtures:
def spam(): def spam():
return 'spam' return 'spam'
""" """
) ),
encoding="utf-8",
) )
testfile = subdir.joinpath("test_spam.py") testfile = subdir.joinpath("test_spam.py")
testfile.write_text( testfile.write_text(
@ -298,7 +299,8 @@ class TestFillFixtures:
def test_spam(spam): def test_spam(spam):
assert spam == "spam" assert spam == "spam"
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
@ -361,7 +363,8 @@ class TestFillFixtures:
def spam(request): def spam(request):
return request.param return request.param
""" """
) ),
encoding="utf-8",
) )
testfile = subdir.joinpath("test_spam.py") testfile = subdir.joinpath("test_spam.py")
testfile.write_text( testfile.write_text(
@ -373,7 +376,8 @@ class TestFillFixtures:
assert spam == params['spam'] assert spam == params['spam']
params['spam'] += 1 params['spam'] += 1
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines(["*3 passed*"]) result.stdout.fnmatch_lines(["*3 passed*"])
@ -405,7 +409,8 @@ class TestFillFixtures:
def spam(request): def spam(request):
return request.param return request.param
""" """
) ),
encoding="utf-8",
) )
testfile = subdir.joinpath("test_spam.py") testfile = subdir.joinpath("test_spam.py")
testfile.write_text( testfile.write_text(
@ -417,7 +422,8 @@ class TestFillFixtures:
assert spam == params['spam'] assert spam == params['spam']
params['spam'] += 1 params['spam'] += 1
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines(["*3 passed*"]) result.stdout.fnmatch_lines(["*3 passed*"])
@ -1039,10 +1045,11 @@ class TestRequestBasic:
def arg1(): def arg1():
pass pass
""" """
) ),
encoding="utf-8",
) )
p = b.joinpath("test_module.py") p = b.joinpath("test_module.py")
p.write_text("def test_func(arg1): pass") p.write_text("def test_func(arg1): pass", encoding="utf-8")
result = pytester.runpytest(p, "--fixtures") result = pytester.runpytest(p, "--fixtures")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
@ -1619,7 +1626,8 @@ class TestFixtureManagerParseFactories:
def one(): def one():
return 1 return 1
""" """
) ),
encoding="utf-8",
) )
package.joinpath("test_x.py").write_text( package.joinpath("test_x.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1627,7 +1635,8 @@ class TestFixtureManagerParseFactories:
def test_x(one): def test_x(one):
assert one == 1 assert one == 1
""" """
) ),
encoding="utf-8",
) )
sub = package.joinpath("sub") sub = package.joinpath("sub")
sub.mkdir() sub.mkdir()
@ -1640,7 +1649,8 @@ class TestFixtureManagerParseFactories:
def one(): def one():
return 2 return 2
""" """
) ),
encoding="utf-8",
) )
sub.joinpath("test_y.py").write_text( sub.joinpath("test_y.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1648,7 +1658,8 @@ class TestFixtureManagerParseFactories:
def test_x(one): def test_x(one):
assert one == 2 assert one == 2
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
@ -1673,7 +1684,8 @@ class TestFixtureManagerParseFactories:
def teardown_module(): def teardown_module():
values[:] = [] values[:] = []
""" """
) ),
encoding="utf-8",
) )
package.joinpath("test_x.py").write_text( package.joinpath("test_x.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1682,7 +1694,8 @@ class TestFixtureManagerParseFactories:
def test_x(): def test_x():
assert values == ["package"] assert values == ["package"]
""" """
) ),
encoding="utf-8",
) )
package = pytester.mkdir("package2") package = pytester.mkdir("package2")
package.joinpath("__init__.py").write_text( package.joinpath("__init__.py").write_text(
@ -1694,7 +1707,8 @@ class TestFixtureManagerParseFactories:
def teardown_module(): def teardown_module():
values[:] = [] values[:] = []
""" """
) ),
encoding="utf-8",
) )
package.joinpath("test_x.py").write_text( package.joinpath("test_x.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1703,7 +1717,8 @@ class TestFixtureManagerParseFactories:
def test_x(): def test_x():
assert values == ["package2"] assert values == ["package2"]
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
@ -1716,7 +1731,7 @@ class TestFixtureManagerParseFactories:
) )
pytester.syspathinsert(pytester.path.name) pytester.syspathinsert(pytester.path.name)
package = pytester.mkdir("package") package = pytester.mkdir("package")
package.joinpath("__init__.py").write_text("") package.joinpath("__init__.py").write_text("", encoding="utf-8")
package.joinpath("conftest.py").write_text( package.joinpath("conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
"""\ """\
@ -1733,7 +1748,8 @@ class TestFixtureManagerParseFactories:
yield values yield values
values.pop() values.pop()
""" """
) ),
encoding="utf-8",
) )
package.joinpath("test_x.py").write_text( package.joinpath("test_x.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1744,7 +1760,8 @@ class TestFixtureManagerParseFactories:
def test_package(one): def test_package(one):
assert values == ["package-auto", "package"] assert values == ["package-auto", "package"]
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
@ -1894,8 +1911,12 @@ class TestAutouseDiscovery:
""" """
) )
conftest.rename(a.joinpath(conftest.name)) conftest.rename(a.joinpath(conftest.name))
a.joinpath("test_something.py").write_text("def test_func(): pass") a.joinpath("test_something.py").write_text(
b.joinpath("test_otherthing.py").write_text("def test_func(): pass") "def test_func(): pass", encoding="utf-8"
)
b.joinpath("test_otherthing.py").write_text(
"def test_func(): pass", encoding="utf-8"
)
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
""" """
@ -1941,7 +1962,8 @@ class TestAutouseManagement:
import sys import sys
sys._myapp = "hello" sys._myapp = "hello"
""" """
) ),
encoding="utf-8",
) )
sub = pkgdir.joinpath("tests") sub = pkgdir.joinpath("tests")
sub.mkdir() sub.mkdir()
@ -1954,7 +1976,8 @@ class TestAutouseManagement:
def test_app(): def test_app():
assert sys._myapp == "hello" assert sys._myapp == "hello"
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run("-s") reprec = pytester.inline_run("-s")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
@ -2887,7 +2910,7 @@ class TestFixtureMarker:
def browser(request): def browser(request):
def finalize(): def finalize():
sys.stdout.write_text('Finalized') sys.stdout.write_text('Finalized', encoding='utf-8')
request.addfinalizer(finalize) request.addfinalizer(finalize)
return {} return {}
""" """
@ -2905,7 +2928,8 @@ class TestFixtureMarker:
def test_browser(browser): def test_browser(browser):
assert browser['visited'] is True assert browser['visited'] is True
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.runpytest("-s") reprec = pytester.runpytest("-s")
for test in ["test_browser"]: for test in ["test_browser"]:
@ -3860,7 +3884,8 @@ class TestParameterizedSubRequest:
def fix_with_param(request): def fix_with_param(request):
return request.param return request.param
""" """
) ),
encoding="utf-8",
) )
testfile = tests_dir.joinpath("test_foos.py") testfile = tests_dir.joinpath("test_foos.py")
@ -3872,7 +3897,8 @@ class TestParameterizedSubRequest:
def test_foo(request): def test_foo(request):
request.getfixturevalue('fix_with_param') request.getfixturevalue('fix_with_param')
""" """
) ),
encoding="utf-8",
) )
os.chdir(tests_dir) os.chdir(tests_dir)
@ -4201,7 +4227,7 @@ class TestScopeOrdering:
test_2.py test_2.py
""" """
root = pytester.mkdir("root") root = pytester.mkdir("root")
root.joinpath("__init__.py").write_text("values = []") root.joinpath("__init__.py").write_text("values = []", encoding="utf-8")
sub1 = root.joinpath("sub1") sub1 = root.joinpath("sub1")
sub1.mkdir() sub1.mkdir()
sub1.joinpath("__init__.py").touch() sub1.joinpath("__init__.py").touch()
@ -4216,7 +4242,8 @@ class TestScopeOrdering:
yield values yield values
assert values.pop() == "pre-sub1" assert values.pop() == "pre-sub1"
""" """
) ),
encoding="utf-8",
) )
sub1.joinpath("test_1.py").write_text( sub1.joinpath("test_1.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -4225,7 +4252,8 @@ class TestScopeOrdering:
def test_1(fix): def test_1(fix):
assert values == ["pre-sub1"] assert values == ["pre-sub1"]
""" """
) ),
encoding="utf-8",
) )
sub2 = root.joinpath("sub2") sub2 = root.joinpath("sub2")
sub2.mkdir() sub2.mkdir()
@ -4241,7 +4269,8 @@ class TestScopeOrdering:
yield values yield values
assert values.pop() == "pre-sub2" assert values.pop() == "pre-sub2"
""" """
) ),
encoding="utf-8",
) )
sub2.joinpath("test_2.py").write_text( sub2.joinpath("test_2.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -4250,7 +4279,8 @@ class TestScopeOrdering:
def test_2(fix): def test_2(fix):
assert values == ["pre-sub2"] assert values == ["pre-sub2"]
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run() reprec = pytester.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)

View File

@ -1472,7 +1472,8 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
assert metafunc.function.__name__ == "test_1" assert metafunc.function.__name__ == "test_1"
""" """
) ),
encoding="utf-8",
) )
sub2.joinpath("conftest.py").write_text( sub2.joinpath("conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -1480,10 +1481,15 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
assert metafunc.function.__name__ == "test_2" assert metafunc.function.__name__ == "test_2"
""" """
),
encoding="utf-8",
) )
sub1.joinpath("test_in_sub1.py").write_text(
"def test_1(): pass", encoding="utf-8"
)
sub2.joinpath("test_in_sub2.py").write_text(
"def test_2(): pass", encoding="utf-8"
) )
sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass")
sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass")
result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1)
result.assert_outcomes(passed=3) result.assert_outcomes(passed=3)

View File

@ -1392,14 +1392,14 @@ def test_sequence_comparison_uses_repr(pytester: Pytester) -> None:
def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None:
pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"]) pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"])
a = pytester.mkdir("a") a = pytester.mkdir("a")
a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2") a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2", encoding="utf-8")
a.joinpath("conftest.py").write_text( a.joinpath("conftest.py").write_text(
'def pytest_assertrepr_compare(): return ["summary a"]' 'def pytest_assertrepr_compare(): return ["summary a"]', encoding="utf-8"
) )
b = pytester.mkdir("b") b = pytester.mkdir("b")
b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2") b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2", encoding="utf-8")
b.joinpath("conftest.py").write_text( b.joinpath("conftest.py").write_text(
'def pytest_assertrepr_compare(): return ["summary b"]' 'def pytest_assertrepr_compare(): return ["summary b"]', encoding="utf-8"
) )
result = pytester.runpytest() result = pytester.runpytest()

View File

@ -160,7 +160,8 @@ class TestAssertionRewrite:
"def special_asserter():\n" "def special_asserter():\n"
" def special_assert(x, y):\n" " def special_assert(x, y):\n"
" assert x == y\n" " assert x == y\n"
" return special_assert\n" " return special_assert\n",
encoding="utf-8",
) )
pytester.makeconftest('pytest_plugins = ["plugin"]') pytester.makeconftest('pytest_plugins = ["plugin"]')
pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n") pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n")
@ -173,7 +174,9 @@ class TestAssertionRewrite:
pytester.makepyfile(test_y="x = 1") pytester.makepyfile(test_y="x = 1")
xdir = pytester.mkdir("x") xdir = pytester.mkdir("x")
pytester.mkpydir(str(xdir.joinpath("test_Y"))) pytester.mkpydir(str(xdir.joinpath("test_Y")))
xdir.joinpath("test_Y").joinpath("__init__.py").write_text("x = 2") xdir.joinpath("test_Y").joinpath("__init__.py").write_text(
"x = 2", encoding="utf-8"
)
pytester.makepyfile( pytester.makepyfile(
"import test_y\n" "import test_y\n"
"import test_Y\n" "import test_Y\n"
@ -726,7 +729,7 @@ class TestAssertionRewrite:
class TestRewriteOnImport: class TestRewriteOnImport:
def test_pycache_is_a_file(self, pytester: Pytester) -> None: def test_pycache_is_a_file(self, pytester: Pytester) -> None:
pytester.path.joinpath("__pycache__").write_text("Hello") pytester.path.joinpath("__pycache__").write_text("Hello", encoding="utf-8")
pytester.makepyfile( pytester.makepyfile(
""" """
def test_rewritten(): def test_rewritten():
@ -903,7 +906,8 @@ def test_rewritten():
pkg.joinpath("test_blah.py").write_text( pkg.joinpath("test_blah.py").write_text(
""" """
def test_rewritten(): def test_rewritten():
assert "@py_builtins" in globals()""" assert "@py_builtins" in globals()""",
encoding="utf-8",
) )
assert pytester.runpytest().ret == 0 assert pytester.runpytest().ret == 0
@ -1066,7 +1070,7 @@ class TestAssertionRewriteHookDetails:
source = tmp_path / "source.py" source = tmp_path / "source.py"
pyc = Path(str(source) + "c") pyc = Path(str(source) + "c")
source.write_text("def test(): pass") source.write_text("def test(): pass", encoding="utf-8")
py_compile.compile(str(source), str(pyc)) py_compile.compile(str(source), str(pyc))
contents = pyc.read_bytes() contents = pyc.read_bytes()
@ -1092,7 +1096,7 @@ class TestAssertionRewriteHookDetails:
fn = tmp_path / "source.py" fn = tmp_path / "source.py"
pyc = Path(str(fn) + "c") pyc = Path(str(fn) + "c")
fn.write_text("def test(): assert True") fn.write_text("def test(): assert True", encoding="utf-8")
source_stat, co = _rewrite_test(fn, config) source_stat, co = _rewrite_test(fn, config)
_write_pyc(state, co, source_stat, pyc) _write_pyc(state, co, source_stat, pyc)
@ -1157,7 +1161,7 @@ class TestAssertionRewriteHookDetails:
return False return False
def rewrite_self(): def rewrite_self():
with open(__file__, 'w') as self: with open(__file__, 'w', encoding='utf-8') as self:
self.write('def reloaded(): return True') self.write('def reloaded(): return True')
""", """,
test_fun=""" test_fun="""
@ -1187,9 +1191,10 @@ class TestAssertionRewriteHookDetails:
data = pkgutil.get_data('foo.test_foo', 'data.txt') data = pkgutil.get_data('foo.test_foo', 'data.txt')
assert data == b'Hey' assert data == b'Hey'
""" """
),
encoding="utf-8",
) )
) path.joinpath("data.txt").write_text("Hey", encoding="utf-8")
path.joinpath("data.txt").write_text("Hey")
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])

View File

@ -38,7 +38,9 @@ class TestNewAPI:
@pytest.mark.filterwarnings("ignore:could not create cache path") @pytest.mark.filterwarnings("ignore:could not create cache path")
def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None:
pytester.makeini("[pytest]") pytester.makeini("[pytest]")
pytester.path.joinpath(".pytest_cache").write_text("gone wrong") pytester.path.joinpath(".pytest_cache").write_text(
"gone wrong", encoding="utf-8"
)
config = pytester.parseconfigure() config = pytester.parseconfigure()
cache = config.cache cache = config.cache
assert cache is not None assert cache is not None
@ -1134,7 +1136,9 @@ class TestNewFirst:
["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"]
) )
p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n") p1.write_text(
"def test_1(): assert 1\n" "def test_2(): assert 1\n", encoding="utf-8"
)
os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
result = pytester.runpytest("--nf", "--collect-only", "-q") result = pytester.runpytest("--nf", "--collect-only", "-q")
@ -1207,7 +1211,8 @@ class TestNewFirst:
p1.write_text( p1.write_text(
"import pytest\n" "import pytest\n"
"@pytest.mark.parametrize('num', [1, 2, 3])\n" "@pytest.mark.parametrize('num', [1, 2, 3])\n"
"def test_1(num): assert num\n" "def test_1(num): assert num\n",
encoding="utf-8",
) )
os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
@ -1259,7 +1264,7 @@ def test_gitignore(pytester: Pytester) -> None:
assert gitignore_path.read_text(encoding="UTF-8") == msg assert gitignore_path.read_text(encoding="UTF-8") == msg
# Does not overwrite existing/custom one. # Does not overwrite existing/custom one.
gitignore_path.write_text("custom") gitignore_path.write_text("custom", encoding="utf-8")
cache.set("something", "else") cache.set("something", "else")
assert gitignore_path.read_text(encoding="UTF-8") == "custom" assert gitignore_path.read_text(encoding="UTF-8") == "custom"

View File

@ -750,9 +750,10 @@ def test_setup_failure_does_not_kill_capturing(pytester: Pytester) -> None:
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
raise ValueError(42) raise ValueError(42)
""" """
),
encoding="utf-8",
) )
) sub1.joinpath("test_mod.py").write_text("def test_func1(): pass", encoding="utf-8")
sub1.joinpath("test_mod.py").write_text("def test_func1(): pass")
result = pytester.runpytest(pytester.path, "--traceconfig") result = pytester.runpytest(pytester.path, "--traceconfig")
result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"]) result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"])
@ -1523,9 +1524,9 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None:
def pytest_runtest_logreport(report): def pytest_runtest_logreport(report):
if "test_global" in report.nodeid: if "test_global" in report.nodeid:
if report.when == "teardown": if report.when == "teardown":
with open("caplog", "w") as f: with open("caplog", "w", encoding="utf-8") as f:
f.write(report.caplog) f.write(report.caplog)
with open("capstdout", "w") as f: with open("capstdout", "w", encoding="utf-8") as f:
f.write(report.capstdout) f.write(report.capstdout)
""" """
) )
@ -1555,14 +1556,14 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None:
result = pytester.runpytest_subprocess("--log-cli-level=INFO") result = pytester.runpytest_subprocess("--log-cli-level=INFO")
assert result.ret == 0 assert result.ret == 0
with open("caplog") as f: with open("caplog", encoding="utf-8") as f:
caplog = f.read() caplog = f.read()
assert "fix setup" in caplog assert "fix setup" in caplog
assert "something in test" in caplog assert "something in test" in caplog
assert "fix teardown" in caplog assert "fix teardown" in caplog
with open("capstdout") as f: with open("capstdout", encoding="utf-8") as f:
capstdout = f.read() capstdout = f.read()
assert "fix setup" in capstdout assert "fix setup" in capstdout

View File

@ -140,7 +140,7 @@ class TestCollectFS:
ensure_file(tmp_path / ".bzr" / "test_notfound.py") ensure_file(tmp_path / ".bzr" / "test_notfound.py")
ensure_file(tmp_path / "normal" / "test_found.py") ensure_file(tmp_path / "normal" / "test_found.py")
for x in tmp_path.rglob("test_*.py"): for x in tmp_path.rglob("test_*.py"):
x.write_text("def test_hello(): pass", "utf-8") x.write_text("def test_hello(): pass", encoding="utf-8")
result = pytester.runpytest("--collect-only") result = pytester.runpytest("--collect-only")
s = result.stdout.str() s = result.stdout.str()
@ -162,7 +162,7 @@ class TestCollectFS:
bindir = "Scripts" if sys.platform.startswith("win") else "bin" bindir = "Scripts" if sys.platform.startswith("win") else "bin"
ensure_file(pytester.path / "virtual" / bindir / fname) ensure_file(pytester.path / "virtual" / bindir / fname)
testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py") testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py")
testfile.write_text("def test_hello(): pass") testfile.write_text("def test_hello(): pass", encoding="utf-8")
# by default, ignore tests inside a virtualenv # by default, ignore tests inside a virtualenv
result = pytester.runpytest() result = pytester.runpytest()
@ -192,7 +192,7 @@ class TestCollectFS:
# norecursedirs takes priority # norecursedirs takes priority
ensure_file(pytester.path / ".virtual" / bindir / fname) ensure_file(pytester.path / ".virtual" / bindir / fname)
testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py") testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py")
testfile.write_text("def test_hello(): pass") testfile.write_text("def test_hello(): pass", encoding="utf-8")
result = pytester.runpytest("--collect-in-virtualenv") result = pytester.runpytest("--collect-in-virtualenv")
result.stdout.no_fnmatch_line("*test_invenv*") result.stdout.no_fnmatch_line("*test_invenv*")
# ...unless the virtualenv is explicitly given on the CLI # ...unless the virtualenv is explicitly given on the CLI
@ -231,10 +231,14 @@ class TestCollectFS:
) )
tmp_path = pytester.path tmp_path = pytester.path
ensure_file(tmp_path / "mydir" / "test_hello.py").write_text( ensure_file(tmp_path / "mydir" / "test_hello.py").write_text(
"def test_1(): pass" "def test_1(): pass", encoding="utf-8"
)
ensure_file(tmp_path / "xyz123" / "test_2.py").write_text(
"def test_2(): 0/0", encoding="utf-8"
)
ensure_file(tmp_path / "xy" / "test_ok.py").write_text(
"def test_3(): pass", encoding="utf-8"
) )
ensure_file(tmp_path / "xyz123" / "test_2.py").write_text("def test_2(): 0/0")
ensure_file(tmp_path / "xy" / "test_ok.py").write_text("def test_3(): pass")
rec = pytester.inline_run() rec = pytester.inline_run()
rec.assertoutcome(passed=1) rec.assertoutcome(passed=1)
rec = pytester.inline_run("xyz123/test_2.py") rec = pytester.inline_run("xyz123/test_2.py")
@ -248,12 +252,14 @@ class TestCollectFS:
""" """
) )
tmp_path = pytester.path tmp_path = pytester.path
ensure_file(tmp_path / "a" / "test_1.py").write_text("def test_a(): pass") ensure_file(tmp_path / "a" / "test_1.py").write_text(
"def test_a(): pass", encoding="utf-8"
)
ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text( ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text(
"def test_b(): pass" "def test_b(): pass", encoding="utf-8"
) )
ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text( ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text(
"def test_c(): pass" "def test_c(): pass", encoding="utf-8"
) )
# executing from rootdir only tests from `testpaths` directories # executing from rootdir only tests from `testpaths` directories
@ -349,8 +355,8 @@ class TestCustomConftests:
""" """
) )
sub = pytester.mkdir("xy123") sub = pytester.mkdir("xy123")
ensure_file(sub / "test_hello.py").write_text("syntax error") ensure_file(sub / "test_hello.py").write_text("syntax error", encoding="utf-8")
sub.joinpath("conftest.py").write_text("syntax error") sub.joinpath("conftest.py").write_text("syntax error", encoding="utf-8")
pytester.makepyfile("def test_hello(): pass") pytester.makepyfile("def test_hello(): pass")
pytester.makepyfile(test_one="syntax error") pytester.makepyfile(test_one="syntax error")
result = pytester.runpytest("--fulltrace") result = pytester.runpytest("--fulltrace")
@ -1060,13 +1066,18 @@ def test_fixture_scope_sibling_conftests(pytester: Pytester) -> None:
def fix(): def fix():
return 1 return 1
""" """
),
encoding="utf-8",
) )
foo_path.joinpath("test_foo.py").write_text(
"def test_foo(fix): assert fix == 1", encoding="utf-8"
) )
foo_path.joinpath("test_foo.py").write_text("def test_foo(fix): assert fix == 1")
# Tests in `food/` should not see the conftest fixture from `foo/` # Tests in `food/` should not see the conftest fixture from `foo/`
food_path = pytester.mkpydir("food") food_path = pytester.mkpydir("food")
food_path.joinpath("test_food.py").write_text("def test_food(fix): assert fix == 1") food_path.joinpath("test_food.py").write_text(
"def test_food(fix): assert fix == 1", encoding="utf-8"
)
res = pytester.runpytest() res = pytester.runpytest()
assert res.ret == 1 assert res.ret == 1
@ -1197,7 +1208,8 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
os.chdir(%r) os.chdir(%r)
""" """
% (str(subdir),) % (str(subdir),)
) ),
encoding="utf-8",
) )
pytester.makepyfile( pytester.makepyfile(
""" """
@ -1227,8 +1239,12 @@ def test_collect_pyargs_with_testpaths(
) -> None: ) -> None:
testmod = pytester.mkdir("testmod") testmod = pytester.mkdir("testmod")
# NOTE: __init__.py is not collected since it does not match python_files. # NOTE: __init__.py is not collected since it does not match python_files.
testmod.joinpath("__init__.py").write_text("def test_func(): pass") testmod.joinpath("__init__.py").write_text(
testmod.joinpath("test_file.py").write_text("def test_func(): pass") "def test_func(): pass", encoding="utf-8"
)
testmod.joinpath("test_file.py").write_text(
"def test_func(): pass", encoding="utf-8"
)
root = pytester.mkdir("root") root = pytester.mkdir("root")
root.joinpath("pytest.ini").write_text( root.joinpath("pytest.ini").write_text(
@ -1238,7 +1254,8 @@ def test_collect_pyargs_with_testpaths(
addopts = --pyargs addopts = --pyargs
testpaths = testmod testpaths = testmod
""" """
) ),
encoding="utf-8",
) )
monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep) monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@ -1256,7 +1273,8 @@ def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
def pytest_sessionstart(session): def pytest_sessionstart(session):
raise Exception("pytest_sessionstart hook successfully run") raise Exception("pytest_sessionstart hook successfully run")
""" """
) ),
encoding="utf-8",
) )
pytester.makeini( pytester.makeini(
""" """
@ -1264,11 +1282,18 @@ def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
testpaths = some_path testpaths = some_path
""" """
) )
# No command line args - falls back to testpaths.
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == ExitCode.INTERNAL_ERROR
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
"INTERNALERROR* Exception: pytest_sessionstart hook successfully run" "INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
) )
# No fallback.
result = pytester.runpytest(".")
assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
"""Long option values do not break initial conftests handling (#10169).""" """Long option values do not break initial conftests handling (#10169)."""
@ -1316,6 +1341,7 @@ def test_collect_symlink_out_of_tree(pytester: Pytester) -> None:
assert request.node.nodeid == "test_real.py::test_nodeid" assert request.node.nodeid == "test_real.py::test_nodeid"
""" """
), ),
encoding="utf-8",
) )
out_of_tree = pytester.mkdir("out_of_tree") out_of_tree = pytester.mkdir("out_of_tree")
@ -1344,12 +1370,16 @@ def test_collect_symlink_dir(pytester: Pytester) -> None:
def test_collectignore_via_conftest(pytester: Pytester) -> None: def test_collectignore_via_conftest(pytester: Pytester) -> None:
"""collect_ignore in parent conftest skips importing child (issue #4592).""" """collect_ignore in parent conftest skips importing child (issue #4592)."""
tests = pytester.mkpydir("tests") tests = pytester.mkpydir("tests")
tests.joinpath("conftest.py").write_text("collect_ignore = ['ignore_me']") tests.joinpath("conftest.py").write_text(
"collect_ignore = ['ignore_me']", encoding="utf-8"
)
ignore_me = tests.joinpath("ignore_me") ignore_me = tests.joinpath("ignore_me")
ignore_me.mkdir() ignore_me.mkdir()
ignore_me.joinpath("__init__.py").touch() ignore_me.joinpath("__init__.py").touch()
ignore_me.joinpath("conftest.py").write_text("assert 0, 'should_not_be_called'") ignore_me.joinpath("conftest.py").write_text(
"assert 0, 'should_not_be_called'", encoding="utf-8"
)
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
@ -1358,23 +1388,31 @@ def test_collectignore_via_conftest(pytester: Pytester) -> None:
def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
subdir = pytester.mkdir("sub") subdir = pytester.mkdir("sub")
init = subdir.joinpath("__init__.py") init = subdir.joinpath("__init__.py")
init.write_text("def test_init(): pass") init.write_text("def test_init(): pass", encoding="utf-8")
p = subdir.joinpath("test_file.py") p = subdir.joinpath("test_file.py")
p.write_text("def test_file(): pass") p.write_text("def test_file(): pass", encoding="utf-8")
# NOTE: without "-o python_files=*.py" this collects test_file.py twice. # Just the package directory, the __init__.py module is filtered out.
# This changed/broke with "Add package scoped fixtures #2283" (2b1410895) result = pytester.runpytest("-v", subdir)
# initially (causing a RecursionError).
result = pytester.runpytest("-v", str(init), str(p))
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"sub/test_file.py::test_file PASSED*", "sub/test_file.py::test_file PASSED*",
"*1 passed in*",
]
)
# But it's included if specified directly.
result = pytester.runpytest("-v", init, p)
result.stdout.fnmatch_lines(
[
"sub/__init__.py::test_init PASSED*",
"sub/test_file.py::test_file PASSED*", "sub/test_file.py::test_file PASSED*",
"*2 passed in*", "*2 passed in*",
] ]
) )
result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) # Or if the pattern allows it.
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"sub/__init__.py::test_init PASSED*", "sub/__init__.py::test_init PASSED*",
@ -1387,12 +1425,15 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
def test_collect_pkg_init_only(pytester: Pytester) -> None: def test_collect_pkg_init_only(pytester: Pytester) -> None:
subdir = pytester.mkdir("sub") subdir = pytester.mkdir("sub")
init = subdir.joinpath("__init__.py") init = subdir.joinpath("__init__.py")
init.write_text("def test_init(): pass") init.write_text("def test_init(): pass", encoding="utf-8")
result = pytester.runpytest(str(init)) result = pytester.runpytest(subdir)
result.stdout.fnmatch_lines(["*no tests ran in*"]) result.stdout.fnmatch_lines(["*no tests ran in*"])
result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init)) result = pytester.runpytest("-v", init)
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
@ -1402,7 +1443,7 @@ def test_collect_sub_with_symlinks(use_pkg: bool, pytester: Pytester) -> None:
sub = pytester.mkdir("sub") sub = pytester.mkdir("sub")
if use_pkg: if use_pkg:
sub.joinpath("__init__.py").touch() sub.joinpath("__init__.py").touch()
sub.joinpath("test_file.py").write_text("def test_file(): pass") sub.joinpath("test_file.py").write_text("def test_file(): pass", encoding="utf-8")
# Create a broken symlink. # Create a broken symlink.
symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py")) symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py"))
@ -1440,7 +1481,7 @@ def test_collector_respects_tbstyle(pytester: Pytester) -> None:
def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None: def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None:
pytester.makepyfile("def test(): pass") pytester.makepyfile("def test(): pass")
pydir = pytester.mkpydir("foopkg") pydir = pytester.mkpydir("foopkg")
pydir.joinpath("__init__.py").write_text("assert False") pydir.joinpath("__init__.py").write_text("assert False", encoding="utf-8")
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret == ExitCode.OK assert result.ret == ExitCode.OK

View File

@ -87,7 +87,8 @@ class TestParseIni:
[pytest] [pytest]
addopts = --verbose addopts = --verbose
""" """
) ),
encoding="utf-8",
) )
config = pytester.parseconfig(tmp_path) config = pytester.parseconfig(tmp_path)
assert config.option.color == "no" assert config.option.color == "no"
@ -127,7 +128,8 @@ class TestParseIni:
""".format( """.format(
section=section section=section
) )
) ),
encoding="utf-8",
) )
config = pytester.parseconfig() config = pytester.parseconfig()
assert config.getini("minversion") == "3.36" assert config.getini("minversion") == "3.36"
@ -150,7 +152,8 @@ class TestParseIni:
[pytest] [pytest]
minversion = 2.0 minversion = 2.0
""" """
) ),
encoding="utf-8",
) )
pytester.path.joinpath("pytest.ini").write_text( pytester.path.joinpath("pytest.ini").write_text(
textwrap.dedent( textwrap.dedent(
@ -158,13 +161,16 @@ class TestParseIni:
[pytest] [pytest]
minversion = 1.5 minversion = 1.5
""" """
) ),
encoding="utf-8",
) )
config = pytester.parseconfigure(sub) config = pytester.parseconfigure(sub)
assert config.getini("minversion") == "2.0" assert config.getini("minversion") == "2.0"
def test_ini_parse_error(self, pytester: Pytester) -> None: def test_ini_parse_error(self, pytester: Pytester) -> None:
pytester.path.joinpath("pytest.ini").write_text("addopts = -x") pytester.path.joinpath("pytest.ini").write_text(
"addopts = -x", encoding="utf-8"
)
result = pytester.runpytest() result = pytester.runpytest()
assert result.ret != 0 assert result.ret != 0
result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined") result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined")
@ -634,7 +640,7 @@ class TestConfigAPI:
def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None: def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None:
somepath = tmp_path.joinpath("x", "y", "z") somepath = tmp_path.joinpath("x", "y", "z")
p = tmp_path.joinpath("conftest.py") p = tmp_path.joinpath("conftest.py")
p.write_text(f"mylist = {['.', str(somepath)]}") p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
config = pytester.parseconfigure(p) config = pytester.parseconfigure(p)
assert ( assert (
config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path) config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
@ -910,7 +916,8 @@ class TestConfigFromdictargs:
[pytest] [pytest]
name = value name = value
""" """
) ),
encoding="utf-8",
) )
inifilename = "../../foo/bar.ini" inifilename = "../../foo/bar.ini"
@ -927,7 +934,8 @@ class TestConfigFromdictargs:
name = wrong-value name = wrong-value
should_not_be_set = true should_not_be_set = true
""" """
) ),
encoding="utf-8",
) )
with MonkeyPatch.context() as mp: with MonkeyPatch.context() as mp:
mp.chdir(cwd) mp.chdir(cwd)
@ -1176,7 +1184,7 @@ def test_cmdline_processargs_simple(pytester: Pytester) -> None:
args.append("-h") args.append("-h")
""" """
) )
result = pytester.runpytest() result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines(["*pytest*", "*-h*"]) result.stdout.fnmatch_lines(["*pytest*", "*-h*"])
@ -1387,7 +1395,7 @@ class TestRootdir:
) )
def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
inipath = tmp_path / name inipath = tmp_path / name
inipath.write_text(contents, "utf-8") inipath.write_text(contents, encoding="utf-8")
a = tmp_path / "a" a = tmp_path / "a"
a.mkdir() a.mkdir()
@ -1446,7 +1454,7 @@ class TestRootdir:
) -> None: ) -> None:
p = tmp_path / name p = tmp_path / name
p.touch() p.touch()
p.write_text(contents, "utf-8") p.write_text(contents, encoding="utf-8")
rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)])
assert rootpath == tmp_path assert rootpath == tmp_path
assert inipath == p assert inipath == p
@ -1542,7 +1550,8 @@ class TestOverrideIniArgs:
custom = 1.0""".format( custom = 1.0""".format(
section=section section=section
) )
) ),
encoding="utf-8",
) )
pytester.makeconftest( pytester.makeconftest(
""" """

View File

@ -1,4 +1,3 @@
import argparse
import os import os
import textwrap import textwrap
from pathlib import Path from pathlib import Path
@ -7,6 +6,8 @@ from typing import Dict
from typing import Generator from typing import Generator
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Union
import pytest import pytest
from _pytest.config import ExitCode from _pytest.config import ExitCode
@ -24,18 +25,18 @@ def ConftestWithSetinitial(path) -> PytestPluginManager:
def conftest_setinitial( def conftest_setinitial(
conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None conftest: PytestPluginManager,
args: Sequence[Union[str, Path]],
confcutdir: Optional[Path] = None,
) -> None: ) -> None:
class Namespace: conftest._set_initial_conftests(
def __init__(self) -> None: args=args,
self.file_or_dir = args pyargs=False,
self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None noconftest=False,
self.noconftest = False rootpath=Path(args[0]),
self.pyargs = False confcutdir=confcutdir,
self.importmode = "prepend" importmode="prepend",
)
namespace = cast(argparse.Namespace, Namespace())
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
@pytest.mark.usefixtures("_sys_snapshot") @pytest.mark.usefixtures("_sys_snapshot")
@ -46,8 +47,12 @@ class TestConftestValueAccessGlobal:
) -> Generator[Path, None, None]: ) -> Generator[Path, None, None]:
tmp_path = tmp_path_factory.mktemp("basedir", numbered=True) tmp_path = tmp_path_factory.mktemp("basedir", numbered=True)
tmp_path.joinpath("adir/b").mkdir(parents=True) tmp_path.joinpath("adir/b").mkdir(parents=True)
tmp_path.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") tmp_path.joinpath("adir/conftest.py").write_text(
tmp_path.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") "a=1 ; Directory = 3", encoding="utf-8"
)
tmp_path.joinpath("adir/b/conftest.py").write_text(
"b=2 ; a = 1.5", encoding="utf-8"
)
if request.param == "inpackage": if request.param == "inpackage":
tmp_path.joinpath("adir/__init__.py").touch() tmp_path.joinpath("adir/__init__.py").touch()
tmp_path.joinpath("adir/b/__init__.py").touch() tmp_path.joinpath("adir/b/__init__.py").touch()
@ -122,8 +127,12 @@ class TestConftestValueAccessGlobal:
def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None:
tmp_path.joinpath("adir-1.0/b").mkdir(parents=True) tmp_path.joinpath("adir-1.0/b").mkdir(parents=True)
tmp_path.joinpath("adir-1.0/conftest.py").write_text("a=1 ; Directory = 3") tmp_path.joinpath("adir-1.0/conftest.py").write_text(
tmp_path.joinpath("adir-1.0/b/conftest.py").write_text("b=2 ; a = 1.5") "a=1 ; Directory = 3", encoding="utf-8"
)
tmp_path.joinpath("adir-1.0/b/conftest.py").write_text(
"b=2 ; a = 1.5", encoding="utf-8"
)
tmp_path.joinpath("adir-1.0/b/__init__.py").touch() tmp_path.joinpath("adir-1.0/b/__init__.py").touch()
tmp_path.joinpath("adir-1.0/__init__.py").touch() tmp_path.joinpath("adir-1.0/__init__.py").touch()
ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b")) ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b"))
@ -166,7 +175,7 @@ def test_conftest_global_import(pytester: Pytester) -> None:
sub = Path("sub") sub = Path("sub")
sub.mkdir() sub.mkdir()
subconf = sub / "conftest.py" subconf = sub / "conftest.py"
subconf.write_text("y=4") subconf.write_text("y=4", encoding="utf-8")
mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd()) mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd())
assert mod != mod2 assert mod != mod2
assert mod2.y == 4 assert mod2.y == 4
@ -245,7 +254,8 @@ def test_conftest_confcutdir(pytester: Pytester) -> None:
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--xyz", action="store_true") parser.addoption("--xyz", action="store_true")
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("-h", "--confcutdir=%s" % x, x) result = pytester.runpytest("-h", "--confcutdir=%s" % x, x)
result.stdout.fnmatch_lines(["*--xyz*"]) result.stdout.fnmatch_lines(["*--xyz*"])
@ -273,9 +283,12 @@ def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) ->
@pytest.fixture @pytest.fixture
def fix(): return None def fix(): return None
""" """
),
encoding="utf-8",
) )
tmp_path.joinpath("foo", "test_it.py").write_text(
"def test_it(fix): pass", encoding="utf-8"
) )
tmp_path.joinpath("foo", "test_it.py").write_text("def test_it(fix): pass")
result = pytester.runpytest("--pyargs", "foo") result = pytester.runpytest("--pyargs", "foo")
assert result.ret == 0 assert result.ret == 0
@ -400,7 +413,8 @@ def test_conftest_existing_junitxml(pytester: Pytester) -> None:
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--xyz", action="store_true") parser.addoption("--xyz", action="store_true")
""" """
) ),
encoding="utf-8",
) )
pytester.makefile(ext=".xml", junit="") # Writes junit.xml pytester.makefile(ext=".xml", junit="") # Writes junit.xml
result = pytester.runpytest("-h", "--junitxml", "junit.xml") result = pytester.runpytest("-h", "--junitxml", "junit.xml")
@ -411,7 +425,7 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
ct1 = pytester.makeconftest("") ct1 = pytester.makeconftest("")
sub = pytester.mkdir("sub") sub = pytester.mkdir("sub")
ct2 = sub / "conftest.py" ct2 = sub / "conftest.py"
ct2.write_text("") ct2.write_text("", encoding="utf-8")
def impct(p, importmode, root): def impct(p, importmode, root):
return p return p
@ -449,7 +463,8 @@ def test_fixture_dependency(pytester: Pytester) -> None:
def bar(foo): def bar(foo):
return 'bar' return 'bar'
""" """
) ),
encoding="utf-8",
) )
subsub = sub.joinpath("subsub") subsub = sub.joinpath("subsub")
subsub.mkdir() subsub.mkdir()
@ -466,7 +481,8 @@ def test_fixture_dependency(pytester: Pytester) -> None:
def test_event_fixture(bar): def test_event_fixture(bar):
assert bar == 'sub bar' assert bar == 'sub bar'
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("sub") result = pytester.runpytest("sub")
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
@ -480,10 +496,11 @@ def test_conftest_found_with_double_dash(pytester: Pytester) -> None:
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--hello-world", action="store_true") parser.addoption("--hello-world", action="store_true")
""" """
) ),
encoding="utf-8",
) )
p = sub.joinpath("test_hello.py") p = sub.joinpath("test_hello.py")
p.write_text("def test_hello(): pass") p.write_text("def test_hello(): pass", encoding="utf-8")
result = pytester.runpytest(str(p) + "::test_hello", "-h") result = pytester.runpytest(str(p) + "::test_hello", "-h")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
""" """
@ -507,7 +524,8 @@ class TestConftestVisibility:
def fxtr(): def fxtr():
return "from-package" return "from-package"
""" """
) ),
encoding="utf-8",
) )
package.joinpath("test_pkgroot.py").write_text( package.joinpath("test_pkgroot.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -515,7 +533,8 @@ class TestConftestVisibility:
def test_pkgroot(fxtr): def test_pkgroot(fxtr):
assert fxtr == "from-package" assert fxtr == "from-package"
""" """
) ),
encoding="utf-8",
) )
swc = package.joinpath("swc") swc = package.joinpath("swc")
@ -529,7 +548,8 @@ class TestConftestVisibility:
def fxtr(): def fxtr():
return "from-swc" return "from-swc"
""" """
) ),
encoding="utf-8",
) )
swc.joinpath("test_with_conftest.py").write_text( swc.joinpath("test_with_conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -537,7 +557,8 @@ class TestConftestVisibility:
def test_with_conftest(fxtr): def test_with_conftest(fxtr):
assert fxtr == "from-swc" assert fxtr == "from-swc"
""" """
) ),
encoding="utf-8",
) )
snc = package.joinpath("snc") snc = package.joinpath("snc")
@ -550,7 +571,8 @@ class TestConftestVisibility:
assert fxtr == "from-package" # No local conftest.py, so should assert fxtr == "from-package" # No local conftest.py, so should
# use value from parent dir's # use value from parent dir's
""" """
) ),
encoding="utf-8",
) )
print("created directory structure:") print("created directory structure:")
for x in pytester.path.glob("**/"): for x in pytester.path.glob("**/"):
@ -616,7 +638,7 @@ def test_search_conftest_up_to_inifile(
root = pytester.path root = pytester.path
src = root.joinpath("src") src = root.joinpath("src")
src.mkdir() src.mkdir()
src.joinpath("pytest.ini").write_text("[pytest]") src.joinpath("pytest.ini").write_text("[pytest]", encoding="utf-8")
src.joinpath("conftest.py").write_text( src.joinpath("conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
"""\ """\
@ -624,7 +646,8 @@ def test_search_conftest_up_to_inifile(
@pytest.fixture @pytest.fixture
def fix1(): pass def fix1(): pass
""" """
) ),
encoding="utf-8",
) )
src.joinpath("test_foo.py").write_text( src.joinpath("test_foo.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -634,7 +657,8 @@ def test_search_conftest_up_to_inifile(
def test_2(out_of_reach): def test_2(out_of_reach):
pass pass
""" """
) ),
encoding="utf-8",
) )
root.joinpath("conftest.py").write_text( root.joinpath("conftest.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -643,7 +667,8 @@ def test_search_conftest_up_to_inifile(
@pytest.fixture @pytest.fixture
def out_of_reach(): pass def out_of_reach(): pass
""" """
) ),
encoding="utf-8",
) )
args = [str(src)] args = [str(src)]
@ -726,7 +751,8 @@ def test_required_option_help(pytester: Pytester) -> None:
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addoption("--xyz", action="store_true", required=True) parser.addoption("--xyz", action="store_true", required=True)
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("-h", x) result = pytester.runpytest("-h", x)
result.stdout.no_fnmatch_line("*argument --xyz is required*") result.stdout.no_fnmatch_line("*argument --xyz is required*")

View File

@ -114,7 +114,7 @@ class TestDoctests:
reprec.assertoutcome(failed=1) reprec.assertoutcome(failed=1)
def test_importmode(self, pytester: Pytester): def test_importmode(self, pytester: Pytester):
p = pytester.makepyfile( pytester.makepyfile(
**{ **{
"namespacepkg/innerpkg/__init__.py": "", "namespacepkg/innerpkg/__init__.py": "",
"namespacepkg/innerpkg/a.py": """ "namespacepkg/innerpkg/a.py": """
@ -132,7 +132,7 @@ class TestDoctests:
""", """,
} }
) )
reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib") reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_new_pattern(self, pytester: Pytester): def test_new_pattern(self, pytester: Pytester):
@ -357,7 +357,8 @@ class TestDoctests:
>>> 1/0 >>> 1/0
''' '''
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("--doctest-modules") result = pytester.runpytest("--doctest-modules")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
@ -448,7 +449,8 @@ class TestDoctests:
"""\ """\
import asdalsdkjaslkdjasd import asdalsdkjaslkdjasd
""" """
) ),
encoding="utf-8",
) )
pytester.maketxtfile( pytester.maketxtfile(
""" """
@ -492,7 +494,8 @@ class TestDoctests:
2 2
''' '''
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest(p, "--doctest-modules") result = pytester.runpytest(p, "--doctest-modules")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
@ -1566,7 +1569,9 @@ def test_warning_on_unwrap_of_broken_object(
def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None: def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None:
not_setup_py = tmp_path.joinpath("not_setup.py") not_setup_py = tmp_path.joinpath("not_setup.py")
not_setup_py.write_text('from setuptools import setup; setup(name="foo")') not_setup_py.write_text(
'from setuptools import setup; setup(name="foo")', encoding="utf-8"
)
assert not _is_setup_py(not_setup_py) assert not _is_setup_py(not_setup_py)

View File

@ -28,7 +28,7 @@ from _pytest.stash import Stash
def schema() -> xmlschema.XMLSchema: def schema() -> xmlschema.XMLSchema:
"""Return an xmlschema.XMLSchema object for the junit-10.xsd file.""" """Return an xmlschema.XMLSchema object for the junit-10.xsd file."""
fn = Path(__file__).parent / "example_scripts/junit-10.xsd" fn = Path(__file__).parent / "example_scripts/junit-10.xsd"
with fn.open() as f: with fn.open(encoding="utf-8") as f:
return xmlschema.XMLSchema(f) return xmlschema.XMLSchema(f)
@ -45,7 +45,7 @@ class RunAndParse:
xml_path = self.pytester.path.joinpath("junit.xml") xml_path = self.pytester.path.joinpath("junit.xml")
result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args)
if family == "xunit2": if family == "xunit2":
with xml_path.open() as f: with xml_path.open(encoding="utf-8") as f:
self.schema.validate(f) self.schema.validate(f)
xmldoc = minidom.parse(str(xml_path)) xmldoc = minidom.parse(str(xml_path))
return result, DomNode(xmldoc) return result, DomNode(xmldoc)
@ -469,7 +469,7 @@ class TestPython:
self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str
) -> None: ) -> None:
p = pytester.mkdir("sub").joinpath("test_hello.py") p = pytester.mkdir("sub").joinpath("test_hello.py")
p.write_text("def test_func(): 0/0") p.write_text("def test_func(): 0/0", encoding="utf-8")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.find_first_by_tag("testsuite")
@ -987,7 +987,7 @@ class TestNonPython:
return "custom item runtest failed" return "custom item runtest failed"
""" """
) )
pytester.path.joinpath("myfile.xyz").write_text("hello") pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8")
result, dom = run_and_parse(family=xunit_family) result, dom = run_and_parse(family=xunit_family)
assert result.ret assert result.ret
node = dom.find_first_by_tag("testsuite") node = dom.find_first_by_tag("testsuite")
@ -1013,7 +1013,7 @@ def test_nullbyte(pytester: Pytester, junit_logging: str) -> None:
) )
xmlf = pytester.path.joinpath("junit.xml") xmlf = pytester.path.joinpath("junit.xml")
pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
text = xmlf.read_text() text = xmlf.read_text(encoding="utf-8")
assert "\x00" not in text assert "\x00" not in text
if junit_logging == "system-out": if junit_logging == "system-out":
assert "#x00" in text assert "#x00" in text
@ -1035,7 +1035,7 @@ def test_nullbyte_replace(pytester: Pytester, junit_logging: str) -> None:
) )
xmlf = pytester.path.joinpath("junit.xml") xmlf = pytester.path.joinpath("junit.xml")
pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
text = xmlf.read_text() text = xmlf.read_text(encoding="utf-8")
if junit_logging == "system-out": if junit_logging == "system-out":
assert "#x0" in text assert "#x0" in text
if junit_logging == "no": if junit_logging == "no":

View File

@ -59,7 +59,8 @@ def test_link_resolve(pytester: Pytester) -> None:
def test_foo(): def test_foo():
raise AssertionError() raise AssertionError()
""" """
) ),
encoding="utf-8",
) )
subst = subst_path_linux subst = subst_path_linux

View File

@ -324,7 +324,8 @@ def test_importerror(pytester: Pytester) -> None:
x = 1 x = 1
""" """
) ),
encoding="utf-8",
) )
pytester.path.joinpath("test_importerror.py").write_text( pytester.path.joinpath("test_importerror.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -332,7 +333,8 @@ def test_importerror(pytester: Pytester) -> None:
def test_importerror(monkeypatch): def test_importerror(monkeypatch):
monkeypatch.setattr('package.a.x', 2) monkeypatch.setattr('package.a.x', 2)
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest() result = pytester.runpytest()
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
@ -434,11 +436,13 @@ def test_syspath_prepend_with_namespace_packages(
ns = d.joinpath("ns_pkg") ns = d.joinpath("ns_pkg")
ns.mkdir() ns.mkdir()
ns.joinpath("__init__.py").write_text( ns.joinpath("__init__.py").write_text(
"__import__('pkg_resources').declare_namespace(__name__)" "__import__('pkg_resources').declare_namespace(__name__)", encoding="utf-8"
) )
lib = ns.joinpath(dirname) lib = ns.joinpath(dirname)
lib.mkdir() lib.mkdir()
lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname) lib.joinpath("__init__.py").write_text(
"def check(): return %r" % dirname, encoding="utf-8"
)
monkeypatch.syspath_prepend("hello") monkeypatch.syspath_prepend("hello")
import ns_pkg.hello import ns_pkg.hello
@ -457,5 +461,5 @@ def test_syspath_prepend_with_namespace_packages(
# Should invalidate caches via importlib.invalidate_caches. # Should invalidate caches via importlib.invalidate_caches.
modules_tmpdir = pytester.mkdir("modules_tmpdir") modules_tmpdir = pytester.mkdir("modules_tmpdir")
monkeypatch.syspath_prepend(str(modules_tmpdir)) monkeypatch.syspath_prepend(str(modules_tmpdir))
modules_tmpdir.joinpath("main_app.py").write_text("app = True") modules_tmpdir.joinpath("main_app.py").write_text("app = True", encoding="utf-8")
from main_app import app # noqa: F401 from main_app import app # noqa: F401

View File

@ -23,7 +23,9 @@ def test_nose_setup(pytester: Pytester) -> None:
test_hello.teardown = lambda: values.append(2) test_hello.teardown = lambda: values.append(2)
""" """
) )
result = pytester.runpytest(p, "-p", "nose") result = pytester.runpytest(
p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning"
)
result.assert_outcomes(passed=2) result.assert_outcomes(passed=2)
@ -76,7 +78,9 @@ def test_nose_setup_func(pytester: Pytester) -> None:
""" """
) )
result = pytester.runpytest(p, "-p", "nose") result = pytester.runpytest(
p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning"
)
result.assert_outcomes(passed=2) result.assert_outcomes(passed=2)
@ -100,7 +104,9 @@ def test_nose_setup_func_failure(pytester: Pytester) -> None:
""" """
) )
result = pytester.runpytest(p, "-p", "nose") result = pytester.runpytest(
p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning"
)
result.stdout.fnmatch_lines(["*TypeError: <lambda>()*"]) result.stdout.fnmatch_lines(["*TypeError: <lambda>()*"])
@ -154,7 +160,9 @@ def test_nose_setup_partial(pytester: Pytester) -> None:
test_hello.teardown = my_teardown_partial test_hello.teardown = my_teardown_partial
""" """
) )
result = pytester.runpytest(p, "-p", "nose") result = pytester.runpytest(
p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning"
)
result.stdout.fnmatch_lines(["*2 passed*"]) result.stdout.fnmatch_lines(["*2 passed*"])
@ -193,7 +201,9 @@ def test_module_level_setup(pytester: Pytester) -> None:
assert items["setup2"] == ["up", "down", "up"] assert items["setup2"] == ["up", "down", "up"]
""" """
) )
result = pytester.runpytest("-p", "nose") result = pytester.runpytest(
"-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning"
)
result.stdout.fnmatch_lines(["*4 passed*"]) result.stdout.fnmatch_lines(["*4 passed*"])
@ -278,7 +288,7 @@ def test_nose_setup_ordering(pytester: Pytester) -> None:
assert self.visited_cls assert self.visited_cls
""" """
) )
result = pytester.runpytest() result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning")
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
@ -494,7 +504,7 @@ def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None:
pass pass
""", """,
) )
result = pytester.runpytest(p, "-p", "nose") result = pytester.runpytest(p.parent, "-p", "nose")
assert result.ret == 0 assert result.ret == 0

View File

@ -1,4 +1,5 @@
import argparse import argparse
import locale
import os import os
import shlex import shlex
import subprocess import subprocess
@ -289,6 +290,10 @@ class TestParser:
def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
try:
encoding = locale.getencoding() # New in Python 3.11, ignores utf-8 mode
except AttributeError:
encoding = locale.getpreferredencoding(False)
try: try:
bash_version = subprocess.run( bash_version = subprocess.run(
["bash", "--version"], ["bash", "--version"],
@ -296,6 +301,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
check=True, check=True,
text=True, text=True,
encoding=encoding,
).stdout ).stdout
except (OSError, subprocess.CalledProcessError): except (OSError, subprocess.CalledProcessError):
pytest.skip("bash is not available") pytest.skip("bash is not available")
@ -305,7 +311,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
script = str(pytester.path.joinpath("test_argcomplete")) script = str(pytester.path.joinpath("test_argcomplete"))
with open(str(script), "w") as fp: with open(str(script), "w", encoding="utf-8") as fp:
# redirect output from argcomplete to stdin and stderr is not trivial # redirect output from argcomplete to stdin and stderr is not trivial
# http://stackoverflow.com/q/12589419/1307905 # http://stackoverflow.com/q/12589419/1307905
# so we use bash # so we use bash

View File

@ -100,13 +100,13 @@ class TestImportPath:
def setuptestfs(self, path: Path) -> None: def setuptestfs(self, path: Path) -> None:
# print "setting up test fs for", repr(path) # print "setting up test fs for", repr(path)
samplefile = path / "samplefile" samplefile = path / "samplefile"
samplefile.write_text("samplefile\n") samplefile.write_text("samplefile\n", encoding="utf-8")
execfile = path / "execfile" execfile = path / "execfile"
execfile.write_text("x=42") execfile.write_text("x=42", encoding="utf-8")
execfilepy = path / "execfile.py" execfilepy = path / "execfile.py"
execfilepy.write_text("x=42") execfilepy.write_text("x=42", encoding="utf-8")
d = {1: 2, "hello": "world", "answer": 42} d = {1: 2, "hello": "world", "answer": 42}
path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1)) path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1))
@ -120,9 +120,9 @@ class TestImportPath:
otherdir.joinpath("__init__.py").touch() otherdir.joinpath("__init__.py").touch()
module_a = otherdir / "a.py" module_a = otherdir / "a.py"
module_a.write_text("from .b import stuff as result\n") module_a.write_text("from .b import stuff as result\n", encoding="utf-8")
module_b = otherdir / "b.py" module_b = otherdir / "b.py"
module_b.write_text('stuff="got it"\n') module_b.write_text('stuff="got it"\n', encoding="utf-8")
module_c = otherdir / "c.py" module_c = otherdir / "c.py"
module_c.write_text( module_c.write_text(
dedent( dedent(
@ -131,7 +131,8 @@ class TestImportPath:
import otherdir.a import otherdir.a
value = otherdir.a.result value = otherdir.a.result
""" """
) ),
encoding="utf-8",
) )
module_d = otherdir / "d.py" module_d = otherdir / "d.py"
module_d.write_text( module_d.write_text(
@ -141,7 +142,8 @@ class TestImportPath:
from otherdir import a from otherdir import a
value2 = a.result value2 = a.result
""" """
) ),
encoding="utf-8",
) )
def test_smoke_test(self, path1: Path) -> None: def test_smoke_test(self, path1: Path) -> None:
@ -283,7 +285,7 @@ class TestImportPath:
def simple_module(self, tmp_path: Path) -> Path: def simple_module(self, tmp_path: Path) -> Path:
fn = tmp_path / "_src/tests/mymod.py" fn = tmp_path / "_src/tests/mymod.py"
fn.parent.mkdir(parents=True) fn.parent.mkdir(parents=True)
fn.write_text("def foo(x): return 40 + x") fn.write_text("def foo(x): return 40 + x", encoding="utf-8")
return fn return fn
def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None: def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None:
@ -447,7 +449,7 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
return False, even when they are clearly equal. return False, even when they are clearly equal.
""" """
module_path = tmp_path.joinpath("my_module.py") module_path = tmp_path.joinpath("my_module.py")
module_path.write_text("def foo(): return 42") module_path.write_text("def foo(): return 42", encoding="utf-8")
monkeypatch.syspath_prepend(tmp_path) monkeypatch.syspath_prepend(tmp_path)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@ -473,7 +475,8 @@ class TestImportLibMode:
class Data: class Data:
value: str value: str
""" """
) ),
encoding="utf-8",
) )
module = import_path(fn, mode="importlib", root=tmp_path) module = import_path(fn, mode="importlib", root=tmp_path)
@ -498,7 +501,8 @@ class TestImportLibMode:
s = pickle.dumps(_action) s = pickle.dumps(_action)
return pickle.loads(s) return pickle.loads(s)
""" """
) ),
encoding="utf-8",
) )
module = import_path(fn, mode="importlib", root=tmp_path) module = import_path(fn, mode="importlib", root=tmp_path)
@ -525,7 +529,8 @@ class TestImportLibMode:
class Data: class Data:
x: int = 42 x: int = 42
""" """
) ),
encoding="utf-8",
) )
fn2 = tmp_path.joinpath("_src/m2/tests/test.py") fn2 = tmp_path.joinpath("_src/m2/tests/test.py")
@ -540,7 +545,8 @@ class TestImportLibMode:
class Data: class Data:
x: str = "" x: str = ""
""" """
) ),
encoding="utf-8",
) )
import pickle import pickle

View File

@ -347,7 +347,7 @@ class TestPytestPluginManager:
pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
pytester.syspathinsert() pytester.syspathinsert()
pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3") pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8")
pluginname = "pkg.plug" pluginname = "pkg.plug"
pytestpm.import_plugin(pluginname) pytestpm.import_plugin(pluginname)
mod = pytestpm.get_plugin("pkg.plug") mod = pytestpm.get_plugin("pkg.plug")

View File

@ -222,7 +222,7 @@ class TestInlineRunModulesCleanup:
result = pytester.inline_run(str(test_mod)) result = pytester.inline_run(str(test_mod))
assert result.ret == ExitCode.OK assert result.ret == ExitCode.OK
# rewrite module, now test should fail if module was re-imported # rewrite module, now test should fail if module was re-imported
test_mod.write_text("def test_foo(): assert False") test_mod.write_text("def test_foo(): assert False", encoding="utf-8")
result2 = pytester.inline_run(str(test_mod)) result2 = pytester.inline_run(str(test_mod))
assert result2.ret == ExitCode.TESTS_FAILED assert result2.ret == ExitCode.TESTS_FAILED

View File

@ -410,7 +410,7 @@ class TestReportSerialization:
) -> None: ) -> None:
sub_dir = pytester.path.joinpath("ns") sub_dir = pytester.path.joinpath("ns")
sub_dir.mkdir() sub_dir.mkdir()
sub_dir.joinpath("conftest.py").write_text("import unknown") sub_dir.joinpath("conftest.py").write_text("import unknown", encoding="utf-8")
result = pytester.runpytest_subprocess(".") result = pytester.runpytest_subprocess(".")
result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"])

View File

@ -265,9 +265,9 @@ def test_plugin_already_exists(pytester: Pytester) -> None:
def test_exclude(pytester: Pytester) -> None: def test_exclude(pytester: Pytester) -> None:
hellodir = pytester.mkdir("hello") hellodir = pytester.mkdir("hello")
hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8")
hello2dir = pytester.mkdir("hello2") hello2dir = pytester.mkdir("hello2")
hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8")
pytester.makepyfile(test_ok="def test_pass(): pass") pytester.makepyfile(test_ok="def test_pass(): pass")
result = pytester.runpytest("--ignore=hello", "--ignore=hello2") result = pytester.runpytest("--ignore=hello", "--ignore=hello2")
assert result.ret == 0 assert result.ret == 0
@ -276,13 +276,13 @@ def test_exclude(pytester: Pytester) -> None:
def test_exclude_glob(pytester: Pytester) -> None: def test_exclude_glob(pytester: Pytester) -> None:
hellodir = pytester.mkdir("hello") hellodir = pytester.mkdir("hello")
hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8")
hello2dir = pytester.mkdir("hello2") hello2dir = pytester.mkdir("hello2")
hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8")
hello3dir = pytester.mkdir("hallo3") hello3dir = pytester.mkdir("hallo3")
hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror") hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror", encoding="utf-8")
subdir = pytester.mkdir("sub") subdir = pytester.mkdir("sub")
subdir.joinpath("test_hello4.py").write_text("x y syntaxerror") subdir.joinpath("test_hello4.py").write_text("x y syntaxerror", encoding="utf-8")
pytester.makepyfile(test_ok="def test_pass(): pass") pytester.makepyfile(test_ok="def test_pass(): pass")
result = pytester.runpytest("--ignore-glob=*h[ea]llo*") result = pytester.runpytest("--ignore-glob=*h[ea]llo*")
assert result.ret == 0 assert result.ret == 0

View File

@ -195,7 +195,8 @@ class TestEvaluation:
def pytest_markeval_namespace(): def pytest_markeval_namespace():
return {"arg": "root"} return {"arg": "root"}
""" """
) ),
encoding="utf-8",
) )
root.joinpath("test_root.py").write_text( root.joinpath("test_root.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -206,7 +207,8 @@ class TestEvaluation:
def test_root(): def test_root():
assert False assert False
""" """
) ),
encoding="utf-8",
) )
foo = root.joinpath("foo") foo = root.joinpath("foo")
foo.mkdir() foo.mkdir()
@ -219,7 +221,8 @@ class TestEvaluation:
def pytest_markeval_namespace(): def pytest_markeval_namespace():
return {"arg": "foo"} return {"arg": "foo"}
""" """
) ),
encoding="utf-8",
) )
foo.joinpath("test_foo.py").write_text( foo.joinpath("test_foo.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -230,7 +233,8 @@ class TestEvaluation:
def test_foo(): def test_foo():
assert False assert False
""" """
) ),
encoding="utf-8",
) )
bar = root.joinpath("bar") bar = root.joinpath("bar")
bar.mkdir() bar.mkdir()
@ -243,7 +247,8 @@ class TestEvaluation:
def pytest_markeval_namespace(): def pytest_markeval_namespace():
return {"arg": "bar"} return {"arg": "bar"}
""" """
) ),
encoding="utf-8",
) )
bar.joinpath("test_bar.py").write_text( bar.joinpath("test_bar.py").write_text(
textwrap.dedent( textwrap.dedent(
@ -254,7 +259,8 @@ class TestEvaluation:
def test_bar(): def test_bar():
assert False assert False
""" """
) ),
encoding="utf-8",
) )
reprec = pytester.inline_run("-vs", "--capture=no") reprec = pytester.inline_run("-vs", "--capture=no")
@ -629,7 +635,8 @@ class TestXFail:
@pytest.mark.xfail(reason='unsupported feature', strict=%s) @pytest.mark.xfail(reason='unsupported feature', strict=%s)
def test_foo(): def test_foo():
with open('foo_executed', 'w'): pass # make sure test executes with open('foo_executed', 'w', encoding='utf-8'):
pass # make sure test executes
""" """
% strict % strict
) )

View File

@ -352,6 +352,6 @@ def test_one():
assert result.ret == 0 assert result.ret == 0
assert Path(stepwise_cache_file).exists() assert Path(stepwise_cache_file).exists()
with stepwise_cache_file.open() as file_handle: with stepwise_cache_file.open(encoding="utf-8") as file_handle:
observed_value = file_handle.readlines() observed_value = file_handle.readlines()
assert [expected_value] == observed_value assert [expected_value] == observed_value

View File

@ -244,7 +244,8 @@ class TestTerminal:
def test_method(self): def test_method(self):
pass pass
""" """
) ),
encoding="utf-8",
) )
result = pytester.runpytest("-vv") result = pytester.runpytest("-vv")
assert result.ret == 0 assert result.ret == 0
@ -1567,7 +1568,8 @@ class TestGenericReporting:
""" """
def pytest_report_header(config, start_path): def pytest_report_header(config, start_path):
return ["line1", str(start_path)] return ["line1", str(start_path)]
""" """,
encoding="utf-8",
) )
result = pytester.runpytest("a") result = pytester.runpytest("a")
result.stdout.fnmatch_lines(["*hello: 42*", "line1", str(pytester.path)]) result.stdout.fnmatch_lines(["*hello: 42*", "line1", str(pytester.path)])
@ -1671,7 +1673,7 @@ def test_fdopen_kept_alive_issue124(pytester: Pytester) -> None:
import os, sys import os, sys
k = [] k = []
def test_open_file_and_keep_alive(capfd): def test_open_file_and_keep_alive(capfd):
stdout = os.fdopen(1, 'w', 1) stdout = os.fdopen(1, 'w', buffering=1, encoding='utf-8')
k.append(stdout) k.append(stdout)
def test_close_kept_alive_file(): def test_close_kept_alive_file():

View File

@ -561,7 +561,7 @@ def test_basetemp_with_read_only_files(pytester: Pytester) -> None:
def test(tmp_path): def test(tmp_path):
fn = tmp_path / 'foo.txt' fn = tmp_path / 'foo.txt'
fn.write_text('hello') fn.write_text('hello', encoding='utf-8')
mode = os.stat(str(fn)).st_mode mode = os.stat(str(fn)).st_mode
os.chmod(str(fn), mode & ~stat.S_IREAD) os.chmod(str(fn), mode & ~stat.S_IREAD)
""" """

View File

@ -518,7 +518,8 @@ class TestDeprecationWarningsByDefault:
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skip("not relevant until pytest 8.0") # In 8.1, uncomment below and change RemovedIn8 -> RemovedIn9.
# @pytest.mark.skip("not relevant until pytest 9.0")
@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None: def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None:
"""This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors. """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors.
@ -810,12 +811,12 @@ def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -
pytester.makepyfile( pytester.makepyfile(
""" """
def open_file(p): def open_file(p):
f = p.open("r") f = p.open("r", encoding="utf-8")
assert p.read_text() == "hello" assert p.read_text() == "hello"
def test_resource_warning(tmp_path): def test_resource_warning(tmp_path):
p = tmp_path.joinpath("foo.txt") p = tmp_path.joinpath("foo.txt")
p.write_text("hello") p.write_text("hello", encoding="utf-8")
open_file(p) open_file(p)
""" """
) )

View File

@ -38,6 +38,10 @@ passenv =
setenv = setenv =
_PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:}
# See https://docs.python.org/3/library/io.html#io-encoding-warning
# If we don't enable this, neither can any of our downstream users!
PYTHONWARNDEFAULTENCODING=1
# Configuration to run with coverage similar to CI, e.g. # Configuration to run with coverage similar to CI, e.g.
# "tox -e py37-coverage". # "tox -e py37-coverage".
coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m