Merge master into features (#5374)

Merge master into features
This commit is contained in:
Bruno Oliveira 2019-06-03 13:14:31 -03:00 committed by GitHub
commit 4f57d40a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
231 changed files with 1361 additions and 2932 deletions

View File

@ -7,17 +7,18 @@ repos:
args: [--safe, --quiet] args: [--safe, --quiet]
language_version: python3 language_version: python3
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: v0.5.0 rev: v1.0.0
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: [black==19.3b0] additional_dependencies: [black==19.3b0]
language_version: python3 language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.2.2 rev: v2.2.3
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: fix-encoding-pragma - id: fix-encoding-pragma
args: [--remove]
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
exclude: _pytest/debugging.py exclude: _pytest/debugging.py
@ -31,14 +32,14 @@ repos:
rev: v1.4.0 rev: v1.4.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: ['--application-directories=.:src'] args: ['--application-directories=.:src', --py3-plus]
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v1.15.0 rev: v1.18.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--keep-percent-format] args: [--py3-plus]
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.3.0 rev: v1.4.0
hooks: hooks:
- id: rst-backticks - id: rst-backticks
- repo: local - repo: local

View File

@ -19,18 +19,13 @@ install:
jobs: jobs:
include: include:
# OSX tests - first (in test stage), since they are the slower ones. # OSX tests - first (in test stage), since they are the slower ones.
- &test-macos - os: osx
os: osx # NOTE: (tests with) pexpect appear to be buggy on Travis,
# at least with coverage.
# Log: https://travis-ci.org/pytest-dev/pytest/jobs/500358864
osx_image: xcode10.1 osx_image: xcode10.1
language: generic language: generic
# Coverage for: env: TOXENV=py37-xdist PYTEST_COVERAGE=1
# - py2 with symlink in test_cmdline_python_package_symlink.
env: TOXENV=py27-xdist PYTEST_COVERAGE=1
before_install:
- python -V
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 27
- <<: *test-macos
env: TOXENV=py37-pexpect,py37-xdist PYTEST_COVERAGE=1
before_install: before_install:
- which python3 - which python3
- python3 -V - python3 -V
@ -38,20 +33,14 @@ jobs:
- python -V - python -V
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37 - test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
# Full run of latest (major) supported versions, without xdist. # Full run of latest supported version, without xdist.
- env: TOXENV=py27 - env: TOXENV=py37 PYTEST_COVERAGE=1
python: '2.7'
- env: TOXENV=py37
python: '3.7' python: '3.7'
# Coverage tracking is slow with pypy, skip it. # Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy-xdist
python: 'pypy'
- env: TOXENV=pypy3-xdist - env: TOXENV=pypy3-xdist
python: 'pypy3' python: 'pypy3'
- env: TOXENV=py34-xdist
python: '3.4'
- env: TOXENV=py35-xdist - env: TOXENV=py35-xdist
python: '3.5' python: '3.5'
@ -62,12 +51,6 @@ jobs:
# Empty PYTEST_ADDOPTS to run this non-verbose. # Empty PYTEST_ADDOPTS to run this non-verbose.
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS= - env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
# Specialized factors for py27.
- env: TOXENV=py27-nobyte-numpy-xdist
python: '2.7'
- env: TOXENV=py27-pluggymaster-xdist
python: '2.7'
# Specialized factors for py37. # Specialized factors for py37.
# Coverage for: # Coverage for:
# - test_sys_breakpoint_interception (via pexpect). # - test_sys_breakpoint_interception (via pexpect).
@ -81,12 +64,7 @@ jobs:
if: type = cron if: type = cron
- stage: baseline - stage: baseline
# Coverage for: env: TOXENV=py36-xdist
# - _pytest.unittest._handle_skip (via pexpect).
env: TOXENV=py27-pexpect,py27-twisted PYTEST_COVERAGE=1
python: '2.7'
# Use py36 here for faster baseline.
- env: TOXENV=py36-xdist
python: '3.6' python: '3.6'
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1 - env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
cache: cache:
@ -112,9 +90,6 @@ matrix:
allow_failures: allow_failures:
- python: '3.8-dev' - python: '3.8-dev'
env: TOXENV=py38-xdist env: TOXENV=py38-xdist
# Temporary (https://github.com/pytest-dev/pytest/pull/5334).
- env: TOXENV=pypy3-xdist
python: 'pypy3'
before_script: before_script:
- | - |

View File

@ -18,6 +18,96 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 4.6.1 (2019-06-02)
=========================
Bug Fixes
---------
- `#5354 <https://github.com/pytest-dev/pytest/issues/5354>`_: Fix ``pytest.mark.parametrize`` when the argvalues is an iterator.
- `#5358 <https://github.com/pytest-dev/pytest/issues/5358>`_: Fix assertion rewriting of ``all()`` calls to deal with non-generators.
pytest 4.6.0 (2019-05-31)
=========================
Important
---------
The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/latest/py27-py34-deprecation.html>`__.
Features
--------
- `#4559 <https://github.com/pytest-dev/pytest/issues/4559>`_: Added the ``junit_log_passing_tests`` ini value which can be used to enable or disable logging of passing test output in the Junit XML file.
- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory.
- `#5062 <https://github.com/pytest-dev/pytest/issues/5062>`_: Unroll calls to ``all`` to full for-loops with assertion rewriting for better failure messages, especially when using Generator Expressions.
- `#5063 <https://github.com/pytest-dev/pytest/issues/5063>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time.
- `#5091 <https://github.com/pytest-dev/pytest/issues/5091>`_: The output for ini options in ``--help`` has been improved.
- `#5269 <https://github.com/pytest-dev/pytest/issues/5269>`_: ``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``.
- `#5311 <https://github.com/pytest-dev/pytest/issues/5311>`_: Captured logs that are output for each failing test are formatted using the
ColoredLevelFormatter.
- `#5312 <https://github.com/pytest-dev/pytest/issues/5312>`_: Improved formatting of multiline log messages in Python 3.
Bug Fixes
---------
- `#2064 <https://github.com/pytest-dev/pytest/issues/2064>`_: The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.
- `#4908 <https://github.com/pytest-dev/pytest/issues/4908>`_: The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).
- `#5036 <https://github.com/pytest-dev/pytest/issues/5036>`_: Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.
- `#5256 <https://github.com/pytest-dev/pytest/issues/5256>`_: Handle internal error due to a lone surrogate unicode character not being representable in Jython.
- `#5257 <https://github.com/pytest-dev/pytest/issues/5257>`_: Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.
- `#5278 <https://github.com/pytest-dev/pytest/issues/5278>`_: Pytest's internal python plugin can be disabled using ``-p no:python`` again.
- `#5286 <https://github.com/pytest-dev/pytest/issues/5286>`_: Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option not working when using a list of test IDs in parametrized tests.
- `#5330 <https://github.com/pytest-dev/pytest/issues/5330>`_: Show the test module being collected when emitting ``PytestCollectionWarning`` messages for
test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem.
- `#5333 <https://github.com/pytest-dev/pytest/issues/5333>`_: Fix regression in 4.5.0 with ``--lf`` not re-running all tests with known failures from non-selected tests.
Improved Documentation
----------------------
- `#5250 <https://github.com/pytest-dev/pytest/issues/5250>`_: Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.
pytest 4.5.0 (2019-05-11) pytest 4.5.0 (2019-05-11)
========================= =========================

View File

@ -12,6 +12,8 @@ taking a lot of time to make a new one.
#. Create a branch ``release-X.Y.Z`` with the version for the release. #. Create a branch ``release-X.Y.Z`` with the version for the release.
* **maintenance releases**: from ``4.6-maintenance``;
* **patch releases**: from the latest ``master``; * **patch releases**: from the latest ``master``;
* **minor releases**: from the latest ``features``; then merge with the latest ``master``; * **minor releases**: from the latest ``features``; then merge with the latest ``master``;
@ -24,7 +26,8 @@ taking a lot of time to make a new one.
This will generate a commit with all the changes ready for pushing. This will generate a commit with all the changes ready for pushing.
#. Open a PR for this branch targeting ``master``. #. Open a PR for this branch targeting ``master`` (or ``4.6-maintenance`` for
maintenance releases).
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag:: #. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
@ -33,7 +36,16 @@ taking a lot of time to make a new one.
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_. Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR into ``master``. #. Merge the PR.
#. If this is a maintenance release, cherry-pick the CHANGELOG / announce
files to the ``master`` branch::
git fetch --all --prune
git checkout origin/master -b cherry-pick-maintenance-release
git cherry-pick --no-commit -m1 origin/4.6-maintenance
git checkout origin/master -- changelog
git commit # no arguments
#. Send an email announcement with the contents from:: #. Send an email announcement with the contents from::

View File

@ -85,7 +85,7 @@ Features
- Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial), - Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box; `nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); - Python 3.5+ and PyPy3;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community; - Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

View File

@ -4,7 +4,6 @@ trigger:
variables: variables:
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv" PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
python.needs_vc: False
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage" COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc" COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc"
PYTEST_COVERAGE: '0' PYTEST_COVERAGE: '0'
@ -16,44 +15,10 @@ jobs:
vmImage: "vs2017-win2016" vmImage: "vs2017-win2016"
strategy: strategy:
matrix: matrix:
py27: # -- pypy3 disabled for now: #5279 --
python.version: '2.7'
tox.env: 'py27'
py27-nobyte-lsof-numpy:
python.version: '2.7'
tox.env: 'py27-lsof-nobyte-numpy'
# Coverage for:
# - test_supports_breakpoint_module_global
# - test_terminal_reporter_writer_attr (without xdist)
# - "if write" branch in _pytest.assertion.rewrite
# - numpy
# - pytester's LsofFdLeakChecker (being skipped)
PYTEST_COVERAGE: '1'
py27-twisted:
python.version: '2.7'
tox.env: 'py27-twisted'
python.needs_vc: True
py27-pluggymaster-xdist:
python.version: '2.7'
tox.env: 'py27-pluggymaster-xdist'
# Coverage for:
# - except-IOError in _attempt_to_close_capture_file for py2.
# Also seen with py27-nobyte (using xdist), and py27-xdist.
# But no exception with py27-pexpect,py27-twisted,py27-numpy.
PYTEST_COVERAGE: '1'
# -- pypy2 and pypy3 are disabled for now: #5279 --
# pypy:
# python.version: 'pypy2'
# tox.env: 'pypy'
# pypy3: # pypy3:
# python.version: 'pypy3' # python.version: 'pypy3'
# tox.env: 'pypy3' # tox.env: 'pypy3'
py34-xdist:
python.version: '3.4'
tox.env: 'py34-xdist'
# Coverage for:
# - _pytest.compat._bytes_to_ascii
PYTEST_COVERAGE: '1'
py35-xdist: py35-xdist:
python.version: '3.5' python.version: '3.5'
tox.env: 'py35-xdist' tox.env: 'py35-xdist'
@ -87,10 +52,6 @@ jobs:
versionSpec: '$(python.version)' versionSpec: '$(python.version)'
architecture: 'x64' architecture: 'x64'
- script: choco install vcpython27
condition: eq(variables['python.needs_vc'], True)
displayName: 'Install VC for py27'
- script: python -m pip install --upgrade pip && python -m pip install tox - script: python -m pip install --upgrade pip && python -m pip install tox
displayName: 'Install tox' displayName: 'Install tox'

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# 10000 iterations, just for relative comparison # 10000 iterations, just for relative comparison
# 2.7.5 3.3.2 # 2.7.5 3.3.2
# FilesCompleter 75.1109 69.2116 # FilesCompleter 75.1109 69.2116

View File

@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
for i in range(1000): for i in range(1000):
exec("def test_func_%d(): pass" % i) exec("def test_func_%d(): pass" % i)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest

View File

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from six.moves import range
import pytest import pytest
SKIP = True SKIP = True

View File

@ -1 +0,0 @@
The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.

View File

@ -1 +0,0 @@
Added the ``junit_log_passing_tests`` ini value which can be used to enable and disable logging passing test output in the Junit XML file.

View File

@ -1 +0,0 @@
The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).

View File

@ -1 +0,0 @@
pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory.

View File

@ -1 +0,0 @@
Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.

View File

@ -1 +0,0 @@
Unroll calls to ``all`` to full for-loops for better failure messages, especially when using Generator Expressions.

View File

@ -1 +0,0 @@
Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time.

View File

@ -1 +0,0 @@
The output for ini options in ``--help`` has been improved.

View File

@ -1 +0,0 @@
Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.

View File

@ -1 +0,0 @@
Handle internal error due to a lone surrogate unicode character not being representable in Jython.

View File

@ -1 +0,0 @@
Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.

View File

@ -1 +0,0 @@
``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``.

View File

@ -1 +0,0 @@
Pytest's internal python plugin can be disabled using ``-p no:python`` again.

View File

@ -1 +0,0 @@
Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option doesn't work when using a list of test IDs in parametrized tests.

View File

@ -1,2 +0,0 @@
Captured logs that are output for each failing test are formatted using the
ColoredLevelFormatter.

View File

@ -1 +0,0 @@
Improved formatting of multiline log messages in python3.

View File

@ -1,2 +0,0 @@
Show the test module being collected when emitting ``PytestCollectionWarning`` messages for
test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem.

View File

@ -1 +0,0 @@
Fix regression with ``--lf`` not re-running all tests with known failures from non-selected tests.

View File

@ -0,0 +1,2 @@
Colorize level names when the level in the logging format is formatted using
'%(levelname).Xs' (truncated fixed width alignment), where X is an integer.

View File

@ -0,0 +1 @@
Fix ``pytest.mark.parametrize`` when the argvalues is an iterator.

View File

@ -0,0 +1 @@
Fix assertion rewriting of ``all()`` calls to deal with non-generators.

View File

@ -10,6 +10,7 @@
<li><a href="{{ pathto('changelog') }}">Changelog</a></li> <li><a href="{{ pathto('changelog') }}">Changelog</a></li>
<li><a href="{{ pathto('contributing') }}">Contributing</a></li> <li><a href="{{ pathto('contributing') }}">Contributing</a></li>
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li> <li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
<li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li>
<li><a href="{{ pathto('license') }}">License</a></li> <li><a href="{{ pathto('license') }}">License</a></li>
<li><a href="{{ pathto('contact') }}">Contact Channels</a></li> <li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
</ul> </ul>

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# flasky extensions. flasky pygments style based on tango style # flasky extensions. flasky pygments style based on tango style
from pygments.style import Style from pygments.style import Style
from pygments.token import Comment from pygments.token import Comment

View File

@ -6,6 +6,8 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-4.6.1
release-4.6.0
release-4.5.0 release-4.5.0
release-4.4.2 release-4.4.2
release-4.4.1 release-4.4.1

View File

@ -0,0 +1,43 @@
pytest-4.6.0
=======================================
The pytest team is proud to announce the 4.6.0 release!
pytest is a mature Python testing tool with more than a 2000 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bugs fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
https://docs.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/latest/
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Akiomi Kamakura
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* David Röthlisberger
* Evan Kepner
* Jeffrey Rackauckas
* MyComputer
* Nikita Krokosh
* Raul Tambre
* Thomas Hisch
* Tim Hoffmann
* Tomer Keren
* Victor Maryama
* danielx123
* oleg-yegorov
Happy testing,
The Pytest Development Team

View File

@ -0,0 +1,19 @@
pytest-4.6.1
=======================================
pytest 4.6.1 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/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Bruno Oliveira
Happy testing,
The pytest Development Team

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# pytest documentation build configuration file, created by # pytest documentation build configuration file, created by
# sphinx-quickstart on Fri Oct 8 17:54:28 2010. # sphinx-quickstart on Fri Oct 8 17:54:28 2010.
@ -63,9 +62,9 @@ source_suffix = ".rst"
master_doc = "contents" master_doc = "contents"
# General information about the project. # General information about the project.
project = u"pytest" project = "pytest"
year = datetime.datetime.utcnow().year year = datetime.datetime.utcnow().year
copyright = u"20152019 , holger krekel and pytest-dev team" copyright = "20152019 , holger krekel and pytest-dev team"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
@ -233,8 +232,8 @@ latex_documents = [
( (
"contents", "contents",
"pytest.tex", "pytest.tex",
u"pytest Documentation", "pytest Documentation",
u"holger krekel, trainer and consultant, http://merlinux.eu", "holger krekel, trainer and consultant, http://merlinux.eu",
"manual", "manual",
) )
] ]
@ -266,16 +265,16 @@ latex_domain_indices = False
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [("usage", "pytest", u"pytest usage", [u"holger krekel at merlinux eu"], 1)] man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)]
# -- Options for Epub output --------------------------------------------------- # -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info. # Bibliographic Dublin Core info.
epub_title = u"pytest" epub_title = "pytest"
epub_author = u"holger krekel at merlinux eu" epub_author = "holger krekel at merlinux eu"
epub_publisher = u"holger krekel at merlinux eu" epub_publisher = "holger krekel at merlinux eu"
epub_copyright = u"2013, holger krekel et alii" epub_copyright = "2013, holger krekel et alii"
# The language of the text. It defaults to the language option # The language of the text. It defaults to the language option
# or en if the language is not set. # or en if the language is not set.

View File

@ -1,2 +1 @@
# -*- coding: utf-8 -*-
collect_ignore = ["conf.py"] collect_ignore = ["conf.py"]

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import _pytest._code import _pytest._code
import pytest import pytest
from pytest import raises from pytest import raises
@ -21,7 +20,7 @@ def test_generative(param1, param2):
assert param1 * 2 < param2 assert param1 * 2 < param2
class TestFailing(object): class TestFailing:
def test_simple(self): def test_simple(self):
def f(): def f():
return 42 return 42
@ -41,7 +40,7 @@ class TestFailing(object):
assert not f() assert not f()
class TestSpecialisedExplanations(object): class TestSpecialisedExplanations:
def test_eq_text(self): def test_eq_text(self):
assert "spam" == "eggs" assert "spam" == "eggs"
@ -101,7 +100,7 @@ class TestSpecialisedExplanations(object):
from dataclasses import dataclass from dataclasses import dataclass
@dataclass @dataclass
class Foo(object): class Foo:
a: int a: int
b: str b: str
@ -113,7 +112,7 @@ class TestSpecialisedExplanations(object):
import attr import attr
@attr.s @attr.s
class Foo(object): class Foo:
a = attr.ib() a = attr.ib()
b = attr.ib() b = attr.ib()
@ -123,7 +122,7 @@ class TestSpecialisedExplanations(object):
def test_attribute(): def test_attribute():
class Foo(object): class Foo:
b = 1 b = 1
i = Foo() i = Foo()
@ -131,14 +130,14 @@ def test_attribute():
def test_attribute_instance(): def test_attribute_instance():
class Foo(object): class Foo:
b = 1 b = 1
assert Foo().b == 2 assert Foo().b == 2
def test_attribute_failure(): def test_attribute_failure():
class Foo(object): class Foo:
def _get_b(self): def _get_b(self):
raise Exception("Failed to get attrib") raise Exception("Failed to get attrib")
@ -149,10 +148,10 @@ def test_attribute_failure():
def test_attribute_multiple(): def test_attribute_multiple():
class Foo(object): class Foo:
b = 1 b = 1
class Bar(object): class Bar:
b = 2 b = 2
assert Foo().b == Bar().b assert Foo().b == Bar().b
@ -162,7 +161,7 @@ def globf(x):
return x + 1 return x + 1
class TestRaises(object): class TestRaises:
def test_raises(self): def test_raises(self):
s = "qwe" s = "qwe"
raises(TypeError, int, s) raises(TypeError, int, s)
@ -203,7 +202,7 @@ def test_dynamic_compile_shows_nicely():
module.foo() module.foo()
class TestMoreErrors(object): class TestMoreErrors:
def test_complex_error(self): def test_complex_error(self):
def f(): def f():
return 44 return 44
@ -253,16 +252,16 @@ class TestMoreErrors(object):
x = 0 x = 0
class TestCustomAssertMsg(object): class TestCustomAssertMsg:
def test_single_line(self): def test_single_line(self):
class A(object): class A:
a = 1 a = 1
b = 2 b = 2
assert A.a == b, "A.a appears not to be b" assert A.a == b, "A.a appears not to be b"
def test_multiline(self): def test_multiline(self):
class A(object): class A:
a = 1 a = 1
b = 2 b = 2
@ -271,7 +270,7 @@ class TestCustomAssertMsg(object):
), "A.a appears not to be b\nor does not appear to be b\none of those" ), "A.a appears not to be b\nor does not appear to be b\none of those"
def test_custom_repr(self): def test_custom_repr(self):
class JSON(object): class JSON:
a = 1 a = 1
def __repr__(self): def __repr__(self):

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import py import py
import pytest import pytest

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
hello = "world" hello = "world"

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import py import py
failure_demo = py.path.local(__file__).dirpath("failure_demo.py") failure_demo = py.path.local(__file__).dirpath("failure_demo.py")

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
def setup_module(module): def setup_module(module):
module.TestStateFullThing.classcount = 0 module.TestStateFullThing.classcount = 0
class TestStateFullThing(object): class TestStateFullThing:
def setup_class(cls): def setup_class(cls):
cls.classcount += 1 cls.classcount += 1

View File

@ -1,2 +1 @@
# -*- coding: utf-8 -*-
collect_ignore = ["nonpython"] collect_ignore = ["nonpython"]

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
@ -9,7 +8,7 @@ def setup(request):
setup.finalize() setup.finalize()
class CostlySetup(object): class CostlySetup:
def __init__(self): def __init__(self):
import time import time

View File

@ -1,2 +1 @@
# -*- coding: utf-8 -*-
# #

View File

@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
def test_quick(setup): def test_quick(setup):
pass pass

View File

@ -1,2 +1 @@
# -*- coding: utf-8 -*-
# #

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
def test_something(setup): def test_something(setup):
assert setup.timecostly == 1 assert setup.timecostly == 1

View File

@ -288,8 +288,7 @@ its test methods:
This is equivalent to directly applying the decorator to the This is equivalent to directly applying the decorator to the
two test functions. two test functions.
To remain backward-compatible with Python 2.4 you can also set a Due to legacy reasons, it is possible to set the ``pytestmark`` attribute on a TestClass like this:
``pytestmark`` attribute on a TestClass like this:
.. code-block:: python .. code-block:: python

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
module containing a parametrized tests testing cross-python module containing a parametrized tests testing cross-python
serialization via the pickle module. serialization via the pickle module.
@ -9,7 +8,7 @@ import textwrap
import pytest import pytest
pythonlist = ["python2.7", "python3.4", "python3.5"] pythonlist = ["python3.5", "python3.6", "python3.7"]
@pytest.fixture(params=pythonlist) @pytest.fixture(params=pythonlist)
@ -23,7 +22,7 @@ def python2(request, python1):
return Python(request.param, python1.picklefile) return Python(request.param, python1.picklefile)
class Python(object): class Python:
def __init__(self, version, picklefile): def __init__(self, version, picklefile):
self.pythonpath = distutils.spawn.find_executable(version) self.pythonpath = distutils.spawn.find_executable(version)
if not self.pythonpath: if not self.pythonpath:

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# content of conftest.py # content of conftest.py
import pytest import pytest
@ -19,7 +18,7 @@ class YamlFile(pytest.File):
class YamlItem(pytest.Item): class YamlItem(pytest.Item):
def __init__(self, name, parent, spec): def __init__(self, name, parent, spec):
super(YamlItem, self).__init__(name, parent) super().__init__(name, parent)
self.spec = spec self.spec = spec
def runtest(self): def runtest(self):

View File

@ -429,14 +429,14 @@ is to be run with different sets of arguments for its three arguments:
.. literalinclude:: multipython.py .. literalinclude:: multipython.py
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize): Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (3 interpreters times 3 interpreters times 3 objects to serialize/deserialize):
.. code-block:: pytest .. code-block:: pytest
. $ pytest -rs -q multipython.py . $ pytest -rs -q multipython.py
...sss...sssssssss...sss... [100%] ...sss...sssssssss...sss... [100%]
========================= short test summary info ========================== ========================= short test summary info ==========================
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
12 passed, 15 skipped in 0.12 seconds 12 passed, 15 skipped in 0.12 seconds
Indirect parametrization of optional implementations/imports Indirect parametrization of optional implementations/imports
@ -494,7 +494,7 @@ If you run this with reporting for skips enabled:
test_module.py .s [100%] test_module.py .s [100%]
========================= short test summary info ========================== ========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2' SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2': No module named 'opt2'
=================== 1 passed, 1 skipped in 0.12 seconds ==================== =================== 1 passed, 1 skipped in 0.12 seconds ====================
You'll see that we don't have an ``opt2`` module and thus the second test run You'll see that we don't have an ``opt2`` module and thus the second test run

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import sys import sys
import pytest import pytest

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
def test_exception_syntax(): def test_exception_syntax():
try: try:
0 / 0 0 / 0

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# run this with $ pytest --collect-only test_collectonly.py # run this with $ pytest --collect-only test_collectonly.py
# #
@ -7,7 +6,7 @@ def test_function():
pass pass
class TestClass(object): class TestClass:
def test_method(self): def test_method(self):
pass pass

View File

@ -26,7 +26,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert param1 * 2 < param2 > assert param1 * 2 < param2
E assert (3 * 2) < 6 E assert (3 * 2) < 6
failure_demo.py:20: AssertionError failure_demo.py:21: AssertionError
_________________________ TestFailing.test_simple __________________________ _________________________ TestFailing.test_simple __________________________
self = <failure_demo.TestFailing object at 0xdeadbeef> self = <failure_demo.TestFailing object at 0xdeadbeef>
@ -43,7 +43,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>() E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>() E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
failure_demo.py:31: AssertionError failure_demo.py:32: AssertionError
____________________ TestFailing.test_simple_multiline _____________________ ____________________ TestFailing.test_simple_multiline _____________________
self = <failure_demo.TestFailing object at 0xdeadbeef> self = <failure_demo.TestFailing object at 0xdeadbeef>
@ -51,7 +51,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_simple_multiline(self): def test_simple_multiline(self):
> otherfunc_multi(42, 6 * 9) > otherfunc_multi(42, 6 * 9)
failure_demo.py:34: failure_demo.py:35:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = 42, b = 54 a = 42, b = 54
@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert a == b > assert a == b
E assert 42 == 54 E assert 42 == 54
failure_demo.py:15: AssertionError failure_demo.py:16: AssertionError
___________________________ TestFailing.test_not ___________________________ ___________________________ TestFailing.test_not ___________________________
self = <failure_demo.TestFailing object at 0xdeadbeef> self = <failure_demo.TestFailing object at 0xdeadbeef>
@ -73,7 +73,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert not 42 E assert not 42
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>() E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
failure_demo.py:40: AssertionError failure_demo.py:41: AssertionError
_________________ TestSpecialisedExplanations.test_eq_text _________________ _________________ TestSpecialisedExplanations.test_eq_text _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -84,7 +84,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E - spam E - spam
E + eggs E + eggs
failure_demo.py:45: AssertionError failure_demo.py:46: AssertionError
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________ _____________ TestSpecialisedExplanations.test_eq_similar_text _____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -97,7 +97,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + foo 2 bar E + foo 2 bar
E ? ^ E ? ^
failure_demo.py:48: AssertionError failure_demo.py:49: AssertionError
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -110,7 +110,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + eggs E + eggs
E bar E bar
failure_demo.py:51: AssertionError failure_demo.py:52: AssertionError
______________ TestSpecialisedExplanations.test_eq_long_text _______________ ______________ TestSpecialisedExplanations.test_eq_long_text _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -127,7 +127,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + 1111111111b222222222 E + 1111111111b222222222
E ? ^ E ? ^
failure_demo.py:56: AssertionError failure_demo.py:57: AssertionError
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -147,7 +147,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E E
E ...Full output truncated (7 lines hidden), use '-vv' to show E ...Full output truncated (7 lines hidden), use '-vv' to show
failure_demo.py:61: AssertionError failure_demo.py:62: AssertionError
_________________ TestSpecialisedExplanations.test_eq_list _________________ _________________ TestSpecialisedExplanations.test_eq_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -158,7 +158,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E At index 2 diff: 2 != 3 E At index 2 diff: 2 != 3
E Use -v to get the full diff E Use -v to get the full diff
failure_demo.py:64: AssertionError failure_demo.py:65: AssertionError
______________ TestSpecialisedExplanations.test_eq_list_long _______________ ______________ TestSpecialisedExplanations.test_eq_list_long _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -171,7 +171,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E At index 100 diff: 1 != 2 E At index 100 diff: 1 != 2
E Use -v to get the full diff E Use -v to get the full diff
failure_demo.py:69: AssertionError failure_demo.py:70: AssertionError
_________________ TestSpecialisedExplanations.test_eq_dict _________________ _________________ TestSpecialisedExplanations.test_eq_dict _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -189,7 +189,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E E
E ...Full output truncated (2 lines hidden), use '-vv' to show E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:72: AssertionError failure_demo.py:73: AssertionError
_________________ TestSpecialisedExplanations.test_eq_set __________________ _________________ TestSpecialisedExplanations.test_eq_set __________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -207,7 +207,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E E
E ...Full output truncated (2 lines hidden), use '-vv' to show E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:75: AssertionError failure_demo.py:76: AssertionError
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________ _____________ TestSpecialisedExplanations.test_eq_longer_list ______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -218,7 +218,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E Right contains one more item: 3 E Right contains one more item: 3
E Use -v to get the full diff E Use -v to get the full diff
failure_demo.py:78: AssertionError failure_demo.py:79: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________ _________________ TestSpecialisedExplanations.test_in_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -227,7 +227,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert 1 in [0, 2, 3, 4, 5] > assert 1 in [0, 2, 3, 4, 5]
E assert 1 in [0, 2, 3, 4, 5] E assert 1 in [0, 2, 3, 4, 5]
failure_demo.py:81: AssertionError failure_demo.py:82: AssertionError
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________ __________ TestSpecialisedExplanations.test_not_in_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -246,7 +246,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E E
E ...Full output truncated (2 lines hidden), use '-vv' to show E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:85: AssertionError failure_demo.py:86: AssertionError
___________ TestSpecialisedExplanations.test_not_in_text_single ____________ ___________ TestSpecialisedExplanations.test_not_in_text_single ____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -259,7 +259,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E single foo line E single foo line
E ? +++ E ? +++
failure_demo.py:89: AssertionError failure_demo.py:90: AssertionError
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________ _________ TestSpecialisedExplanations.test_not_in_text_single_long _________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -272,7 +272,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? +++ E ? +++
failure_demo.py:93: AssertionError failure_demo.py:94: AssertionError
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -285,7 +285,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:97: AssertionError failure_demo.py:98: AssertionError
______________ TestSpecialisedExplanations.test_eq_dataclass _______________ ______________ TestSpecialisedExplanations.test_eq_dataclass _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -306,7 +306,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E Differing attributes: E Differing attributes:
E b: 'b' != 'c' E b: 'b' != 'c'
failure_demo.py:109: AssertionError failure_demo.py:110: AssertionError
________________ TestSpecialisedExplanations.test_eq_attrs _________________ ________________ TestSpecialisedExplanations.test_eq_attrs _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@ -327,7 +327,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E Differing attributes: E Differing attributes:
E b: 'b' != 'c' E b: 'b' != 'c'
failure_demo.py:121: AssertionError failure_demo.py:122: AssertionError
______________________________ test_attribute ______________________________ ______________________________ test_attribute ______________________________
def test_attribute(): def test_attribute():
@ -339,7 +339,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 1 == 2 E assert 1 == 2
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
failure_demo.py:129: AssertionError failure_demo.py:130: AssertionError
_________________________ test_attribute_instance __________________________ _________________________ test_attribute_instance __________________________
def test_attribute_instance(): def test_attribute_instance():
@ -351,7 +351,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>() E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
failure_demo.py:136: AssertionError failure_demo.py:137: AssertionError
__________________________ test_attribute_failure __________________________ __________________________ test_attribute_failure __________________________
def test_attribute_failure(): def test_attribute_failure():
@ -364,7 +364,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
i = Foo() i = Foo()
> assert i.b == 2 > assert i.b == 2
failure_demo.py:147: failure_demo.py:148:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef> self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
@ -373,7 +373,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> raise Exception("Failed to get attrib") > raise Exception("Failed to get attrib")
E Exception: Failed to get attrib E Exception: Failed to get attrib
failure_demo.py:142: Exception failure_demo.py:143: Exception
_________________________ test_attribute_multiple __________________________ _________________________ test_attribute_multiple __________________________
def test_attribute_multiple(): def test_attribute_multiple():
@ -390,7 +390,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>() E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
failure_demo.py:157: AssertionError failure_demo.py:158: AssertionError
__________________________ TestRaises.test_raises __________________________ __________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -400,7 +400,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> raises(TypeError, int, s) > raises(TypeError, int, s)
E ValueError: invalid literal for int() with base 10: 'qwe' E ValueError: invalid literal for int() with base 10: 'qwe'
failure_demo.py:167: ValueError failure_demo.py:168: ValueError
______________________ TestRaises.test_raises_doesnt _______________________ ______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -409,7 +409,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> raises(IOError, int, "3") > raises(IOError, int, "3")
E Failed: DID NOT RAISE <class 'OSError'> E Failed: DID NOT RAISE <class 'OSError'>
failure_demo.py:170: Failed failure_demo.py:171: Failed
__________________________ TestRaises.test_raise ___________________________ __________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -418,7 +418,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> raise ValueError("demo error") > raise ValueError("demo error")
E ValueError: demo error E ValueError: demo error
failure_demo.py:173: ValueError failure_demo.py:174: ValueError
________________________ TestRaises.test_tupleerror ________________________ ________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -427,7 +427,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> a, b = [1] # NOQA > a, b = [1] # NOQA
E ValueError: not enough values to unpack (expected 2, got 1) E ValueError: not enough values to unpack (expected 2, got 1)
failure_demo.py:176: ValueError failure_demo.py:177: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -438,7 +438,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> a, b = items.pop() > a, b = items.pop()
E TypeError: 'int' object is not iterable E TypeError: 'int' object is not iterable
failure_demo.py:181: TypeError failure_demo.py:182: TypeError
--------------------------- Captured stdout call --------------------------- --------------------------- Captured stdout call ---------------------------
items is [1, 2, 3] items is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________ ________________________ TestRaises.test_some_error ________________________
@ -449,7 +449,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> if namenotexi: # NOQA > if namenotexi: # NOQA
E NameError: name 'namenotexi' is not defined E NameError: name 'namenotexi' is not defined
failure_demo.py:184: NameError failure_demo.py:185: NameError
____________________ test_dynamic_compile_shows_nicely _____________________ ____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely(): def test_dynamic_compile_shows_nicely():
@ -464,14 +464,14 @@ Here is a nice run of several failures and how ``pytest`` presents things:
sys.modules[name] = module sys.modules[name] = module
> module.foo() > module.foo()
failure_demo.py:202: failure_demo.py:203:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def foo(): def foo():
> assert 1 == 0 > assert 1 == 0
E AssertionError E AssertionError
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:199>:2: AssertionError <0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:200>:2: AssertionError
____________________ TestMoreErrors.test_complex_error _____________________ ____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -485,9 +485,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> somefunc(f(), g()) > somefunc(f(), g())
failure_demo.py:213: failure_demo.py:214:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:11: in somefunc failure_demo.py:12: in somefunc
otherfunc(x, y) otherfunc(x, y)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@ -497,7 +497,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert a == b > assert a == b
E assert 44 == 43 E assert 44 == 43
failure_demo.py:7: AssertionError failure_demo.py:8: AssertionError
___________________ TestMoreErrors.test_z1_unpack_error ____________________ ___________________ TestMoreErrors.test_z1_unpack_error ____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -507,7 +507,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> a, b = items > a, b = items
E ValueError: not enough values to unpack (expected 2, got 0) E ValueError: not enough values to unpack (expected 2, got 0)
failure_demo.py:217: ValueError failure_demo.py:218: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________ ____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -517,7 +517,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> a, b = items > a, b = items
E TypeError: 'int' object is not iterable E TypeError: 'int' object is not iterable
failure_demo.py:221: TypeError failure_demo.py:222: TypeError
______________________ TestMoreErrors.test_startswith ______________________ ______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -530,7 +530,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456') E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
failure_demo.py:226: AssertionError failure_demo.py:227: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________ __________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -549,7 +549,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>() E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>() E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
failure_demo.py:235: AssertionError failure_demo.py:236: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________ _____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -560,7 +560,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where False = isinstance(43, float) E + where False = isinstance(43, float)
E + where 43 = globf(42) E + where 43 = globf(42)
failure_demo.py:238: AssertionError failure_demo.py:239: AssertionError
_______________________ TestMoreErrors.test_instance _______________________ _______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -571,7 +571,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 42 != 42 E assert 42 != 42
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
failure_demo.py:242: AssertionError failure_demo.py:243: AssertionError
_______________________ TestMoreErrors.test_compare ________________________ _______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -581,7 +581,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 11 < 5 E assert 11 < 5
E + where 11 = globf(10) E + where 11 = globf(10)
failure_demo.py:245: AssertionError failure_demo.py:246: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________ _____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert x == 0 > assert x == 0
E assert 1 == 0 E assert 1 == 0
failure_demo.py:250: AssertionError failure_demo.py:251: AssertionError
___________________ TestCustomAssertMsg.test_single_line ___________________ ___________________ TestCustomAssertMsg.test_single_line ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -607,7 +607,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 1 == 2 E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
failure_demo.py:261: AssertionError failure_demo.py:262: AssertionError
____________________ TestCustomAssertMsg.test_multiline ____________________ ____________________ TestCustomAssertMsg.test_multiline ____________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -626,7 +626,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 1 == 2 E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
failure_demo.py:268: AssertionError failure_demo.py:269: AssertionError
___________________ TestCustomAssertMsg.test_custom_repr ___________________ ___________________ TestCustomAssertMsg.test_custom_repr ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -648,5 +648,5 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E assert 1 == 2 E assert 1 == 2
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
failure_demo.py:281: AssertionError failure_demo.py:282: AssertionError
======================== 44 failed in 0.12 seconds ========================= ======================== 44 failed in 0.12 seconds =========================

View File

@ -441,7 +441,7 @@ Now we can profile which test functions execute the slowest:
========================= slowest 3 test durations ========================= ========================= slowest 3 test durations =========================
0.30s call test_some_are_slow.py::test_funcslow2 0.30s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1 0.21s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast 0.10s call test_some_are_slow.py::test_funcfast
========================= 3 passed in 0.12 seconds ========================= ========================= 3 passed in 0.12 seconds =========================

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
xfail = pytest.mark.xfail xfail = pytest.mark.xfail

View File

@ -1,9 +1,9 @@
Installation and Getting Started Installation and Getting Started
=================================== ===================================
**Pythons**: Python 2.7, 3.4, 3.5, 3.6, 3.7, Jython, PyPy-2.3 **Pythons**: Python 3.5, 3.6, 3.7, PyPy3
**Platforms**: Unix/Posix and Windows **Platforms**: Linux and Windows
**PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_ **PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_

View File

@ -7,8 +7,7 @@ Good Integration Practices
Install package with pip Install package with pip
------------------------------------------------- -------------------------------------------------
For development, we recommend you use venv_ for virtual environments For development, we recommend you use venv_ for virtual environments and
(or virtualenv_ for Python 2.7) and
pip_ for installing your application and any dependencies, pip_ for installing your application and any dependencies,
as well as the ``pytest`` package itself. as well as the ``pytest`` package itself.
This ensures your code and dependencies are isolated from your system Python installation. This ensures your code and dependencies are isolated from your system Python installation.

View File

@ -61,7 +61,7 @@ Features
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box; - Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); - Python Python 3.5+ and PyPy 3;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community; - Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

View File

@ -23,4 +23,4 @@ back-porting patches to the ``4.6-maintenance`` branch that affect Python 2 user
branch will continue to exist so the community itself can contribute patches. The core team will branch will continue to exist so the community itself can contribute patches. The core team will
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**. be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires> .. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires

View File

@ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
============================= warnings summary ============================= ============================= warnings summary =============================
test_pytest_warnings.py:1 test_pytest_warnings.py:1
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py)
class Test: class Test:
-- Docs: https://docs.pytest.org/en/latest/warnings.html -- Docs: https://docs.pytest.org/en/latest/warnings.html

View File

@ -335,8 +335,6 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello
.. code-block:: python .. code-block:: python
# -*- coding: utf-8 -*-
import pytest import pytest

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import json import json
import py import py

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import sys import sys
from distutils.core import setup from distutils.core import setup

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
Invoke development tasks. Invoke development tasks.
""" """

View File

@ -1,5 +1,4 @@
[metadata] [metadata]
name = pytest name = pytest
description = pytest: simple powerful testing with Python description = pytest: simple powerful testing with Python
long_description = file: README.rst long_description = file: README.rst
@ -23,13 +22,11 @@ classifiers =
Topic :: Software Development :: Testing Topic :: Software Development :: Testing
Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries
Topic :: Utilities Topic :: Utilities
Programming Language :: Python :: 2 Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
platforms = unix, linux, osx, cygwin, win32 platforms = unix, linux, osx, cygwin, win32
[options] [options]
@ -43,8 +40,7 @@ packages =
_pytest.mark _pytest.mark
py_modules = pytest py_modules = pytest
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* python_requires = >=3.5
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
@ -59,13 +55,9 @@ all_files = 1
[upload_sphinx] [upload_sphinx]
upload-dir = doc/en/build/html upload-dir = doc/en/build/html
[bdist_wheel]
universal = 1
[check-manifest] [check-manifest]
ignore = ignore =
_pytest/_version.py _pytest/_version.py
[devpi:upload] [devpi:upload]
formats = sdist.tgz,bdist_wheel formats = sdist.tgz,bdist_wheel

View File

@ -1,17 +1,13 @@
# -*- coding: utf-8 -*-
from setuptools import setup from setuptools import setup
# TODO: if py gets upgrade to >=1.6, # TODO: if py gets upgrade to >=1.6,
# remove _width_of_current_line in terminal.py # remove _width_of_current_line in terminal.py
INSTALL_REQUIRES = [ INSTALL_REQUIRES = [
"py>=1.5.0", "py>=1.5.0",
"six>=1.10.0",
"packaging", "packaging",
"attrs>=17.4.0", "attrs>=17.4.0",
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"', "more-itertools>=4.0.0",
'more-itertools>=4.0.0;python_version>"2.7"',
"atomicwrites>=1.0", "atomicwrites>=1.0",
'funcsigs>=1.0;python_version<"3.0"',
'pathlib2>=2.2.0;python_version<"3.6"', 'pathlib2>=2.2.0;python_version<"3.6"',
'colorama;sys_platform=="win32"', 'colorama;sys_platform=="win32"',
"pluggy>=0.12,<1.0", "pluggy>=0.12,<1.0",
@ -30,9 +26,9 @@ def main():
"testing": [ "testing": [
"argcomplete", "argcomplete",
"hypothesis>=3.56", "hypothesis>=3.56",
"mock",
"nose", "nose",
"requests", "requests",
"mock;python_version=='2.7'",
], ],
}, },
# fmt: on # fmt: on

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
__all__ = ["__version__"] __all__ = ["__version__"]
try: try:

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""allow bash-completion for argparse with argcomplete if installed """allow bash-completion for argparse with argcomplete if installed
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
to find the magic string, so _ARGCOMPLETE env. var is never set, and to find the magic string, so _ARGCOMPLETE env. var is never set, and
@ -54,16 +53,12 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script). global argcomplete script).
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os import os
import sys import sys
from glob import glob from glob import glob
class FastFilesCompleter(object): class FastFilesCompleter:
"Fast file completer class" "Fast file completer class"
def __init__(self, directories=True): def __init__(self, directories=True):

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" python inspection/code generation API """ """ python inspection/code generation API """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from .code import Code # noqa from .code import Code # noqa
from .code import ExceptionInfo # noqa from .code import ExceptionInfo # noqa
from .code import filter_traceback # noqa from .code import filter_traceback # noqa

View File

@ -1,95 +0,0 @@
# -*- coding: utf-8 -*-
# copied from python-2.7.3's traceback.py
# CHANGES:
# - some_str is replaced, trying to create unicode strings
#
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import types
from six import text_type
def format_exception_only(etype, value):
"""Format the exception part of a traceback.
The arguments are the exception type and value such as given by
sys.last_type and sys.last_value. The return value is a list of
strings, each ending in a newline.
Normally, the list contains a single string; however, for
SyntaxError exceptions, it contains several lines that (when
printed) display detailed information about where the syntax
error occurred.
The message indicating which exception occurred is always the last
string in the list.
"""
# An instance should not have a meaningful value parameter, but
# sometimes does, particularly for string exceptions, such as
# >>> raise string1, string2 # deprecated
#
# Clear these out first because issubtype(string1, SyntaxError)
# would throw another exception and mask the original problem.
if (
isinstance(etype, BaseException)
or isinstance(etype, types.InstanceType)
or etype is None
or type(etype) is str
):
return [_format_final_exc_line(etype, value)]
stype = etype.__name__
if not issubclass(etype, SyntaxError):
return [_format_final_exc_line(stype, value)]
# It was a syntax error; show exactly where the problem was found.
lines = []
try:
msg, (filename, lineno, offset, badline) = value.args
except Exception:
pass
else:
filename = filename or "<string>"
lines.append(' File "{}", line {}\n'.format(filename, lineno))
if badline is not None:
if isinstance(badline, bytes): # python 2 only
badline = badline.decode("utf-8", "replace")
lines.append(" {}\n".format(badline.strip()))
if offset is not None:
caretspace = badline.rstrip("\n")[:offset].lstrip()
# non-space whitespace (likes tabs) must be kept for alignment
caretspace = ((c.isspace() and c or " ") for c in caretspace)
# only three spaces to account for offset1 == pos 0
lines.append(" {}^\n".format("".join(caretspace)))
value = msg
lines.append(_format_final_exc_line(stype, value))
return lines
def _format_final_exc_line(etype, value):
"""Return a list of a single line -- normal case for format_exception_only"""
valuestr = _some_str(value)
if value is None or not valuestr:
line = "{}\n".format(etype)
else:
line = "{}: {}\n".format(etype, valuestr)
return line
def _some_str(value):
try:
return text_type(value)
except Exception:
try:
return bytes(value).decode("UTF-8", "replace")
except Exception:
pass
return "<unprintable {} object>".format(type(value).__name__)

View File

@ -1,36 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import inspect import inspect
import re import re
import sys import sys
import traceback import traceback
from inspect import CO_VARARGS from inspect import CO_VARARGS
from inspect import CO_VARKEYWORDS from inspect import CO_VARKEYWORDS
from traceback import format_exception_only
from weakref import ref from weakref import ref
import attr import attr
import pluggy import pluggy
import py import py
from six import text_type
import _pytest import _pytest
from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.compat import _PY2
from _pytest.compat import _PY3
from _pytest.compat import PY35
from _pytest.compat import safe_str
if _PY3:
from traceback import format_exception_only
else:
from ._py2traceback import format_exception_only
class Code(object): class Code:
""" wrapper around Python code objects """ """ wrapper around Python code objects """
def __init__(self, rawcode): def __init__(self, rawcode):
@ -41,7 +27,7 @@ class Code(object):
self.firstlineno = rawcode.co_firstlineno - 1 self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name self.name = rawcode.co_name
except AttributeError: except AttributeError:
raise TypeError("not a code object: %r" % (rawcode,)) raise TypeError("not a code object: {!r}".format(rawcode))
self.raw = rawcode self.raw = rawcode
def __eq__(self, other): def __eq__(self, other):
@ -100,7 +86,7 @@ class Code(object):
return raw.co_varnames[:argcount] return raw.co_varnames[:argcount]
class Frame(object): class Frame:
"""Wrapper around a Python frame holding f_locals and f_globals """Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated.""" in which expressions can be evaluated."""
@ -163,7 +149,7 @@ class Frame(object):
return retval return retval
class TracebackEntry(object): class TracebackEntry:
""" a single entry in a traceback """ """ a single entry in a traceback """
_repr_style = None _repr_style = None
@ -208,8 +194,7 @@ class TracebackEntry(object):
locals = property(getlocals, None, None, "locals of underlaying frame") locals = property(getlocals, None, None, "locals of underlaying frame")
def getfirstlinesource(self): def getfirstlinesource(self):
# on Jython this firstlineno can be -1 apparently return self.frame.code.firstlineno
return max(self.frame.code.firstlineno, 0)
def getsource(self, astcache=None): def getsource(self, astcache=None):
""" return failing source code. """ """ return failing source code. """
@ -324,7 +309,7 @@ class Traceback(list):
return self return self
def __getitem__(self, key): def __getitem__(self, key):
val = super(Traceback, self).__getitem__(key) val = super().__getitem__(key)
if isinstance(key, type(slice(0))): if isinstance(key, type(slice(0))):
val = self.__class__(val) val = self.__class__(val)
return val return val
@ -386,14 +371,12 @@ co_equal = compile(
@attr.s(repr=False) @attr.s(repr=False)
class ExceptionInfo(object): class ExceptionInfo:
""" wraps sys.exc_info() objects and offers """ wraps sys.exc_info() objects and offers
help for navigating the traceback. help for navigating the traceback.
""" """
_assert_start_repr = ( _assert_start_repr = "AssertionError('assert "
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
)
_excinfo = attr.ib() _excinfo = attr.ib()
_striptext = attr.ib(default="") _striptext = attr.ib(default="")
@ -558,11 +541,6 @@ class ExceptionInfo(object):
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc) return str(loc)
def __unicode__(self):
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return text_type(loc)
def match(self, regexp): def match(self, regexp):
""" """
Check whether the regular expression 'regexp' is found in the string Check whether the regular expression 'regexp' is found in the string
@ -578,7 +556,7 @@ class ExceptionInfo(object):
@attr.s @attr.s
class FormattedExcinfo(object): class FormattedExcinfo:
""" presenting information about failing Functions and Generators. """ """ presenting information about failing Functions and Generators. """
# for traceback entries # for traceback entries
@ -677,7 +655,7 @@ class FormattedExcinfo(object):
str_repr = safeformat(value) str_repr = safeformat(value)
# if len(str_repr) < 70 or not isinstance(value, # if len(str_repr) < 70 or not isinstance(value,
# (list, tuple, dict)): # (list, tuple, dict)):
lines.append("%-10s = %s" % (name, str_repr)) lines.append("{:<10} = {}".format(name, str_repr))
# else: # else:
# self._line("%-10s =\\" % (name,)) # self._line("%-10s =\\" % (name,))
# # XXX # # XXX
@ -692,8 +670,7 @@ class FormattedExcinfo(object):
source = _pytest._code.Source("???") source = _pytest._code.Source("???")
line_index = 0 line_index = 0
else: else:
# entry.getfirstlinesource() can be -1, should be 0 on jython line_index = entry.lineno - entry.getfirstlinesource()
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
lines = [] lines = []
style = entry._repr_style style = entry._repr_style
@ -733,7 +710,7 @@ class FormattedExcinfo(object):
if self.tbfilter: if self.tbfilter:
traceback = traceback.filter() traceback = traceback.filter()
if is_recursion_error(excinfo): if excinfo.errisinstance(RecursionError):
traceback, extraline = self._truncate_recursive_traceback(traceback) traceback, extraline = self._truncate_recursive_traceback(traceback)
else: else:
extraline = None extraline = None
@ -769,7 +746,7 @@ class FormattedExcinfo(object):
" Displaying first and last {max_frames} stack frames out of {total}." " Displaying first and last {max_frames} stack frames out of {total}."
).format( ).format(
exc_type=type(e).__name__, exc_type=type(e).__name__,
exc_msg=safe_str(e), exc_msg=str(e),
max_frames=max_frames, max_frames=max_frames,
total=len(traceback), total=len(traceback),
) )
@ -784,64 +761,51 @@ class FormattedExcinfo(object):
return traceback, extraline return traceback, extraline
def repr_excinfo(self, excinfo): def repr_excinfo(self, excinfo):
if _PY2:
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
return ReprExceptionInfo(reprtraceback, reprcrash) repr_chain = []
else: e = excinfo.value
repr_chain = [] descr = None
e = excinfo.value seen = set()
descr = None while e is not None and id(e) not in seen:
seen = set() seen.add(id(e))
while e is not None and id(e) not in seen: if excinfo:
seen.add(id(e)) reprtraceback = self.repr_traceback(excinfo)
if excinfo: reprcrash = excinfo._getreprcrash()
reprtraceback = self.repr_traceback(excinfo) else:
reprcrash = excinfo._getreprcrash() # fallback to native repr if the exception doesn't have a traceback:
else: # ExceptionInfo objects require a full traceback to work
# fallback to native repr if the exception doesn't have a traceback: reprtraceback = ReprTracebackNative(
# ExceptionInfo objects require a full traceback to work traceback.format_exception(type(e), e, None)
reprtraceback = ReprTracebackNative( )
traceback.format_exception(type(e), e, None) reprcrash = None
)
reprcrash = None
repr_chain += [(reprtraceback, reprcrash, descr)] repr_chain += [(reprtraceback, reprcrash, descr)]
if e.__cause__ is not None and self.chain: if e.__cause__ is not None and self.chain:
e = e.__cause__ e = e.__cause__
excinfo = ( excinfo = (
ExceptionInfo((type(e), e, e.__traceback__)) ExceptionInfo((type(e), e, e.__traceback__))
if e.__traceback__ if e.__traceback__
else None else None
) )
descr = "The above exception was the direct cause of the following exception:" descr = "The above exception was the direct cause of the following exception:"
elif ( elif (
e.__context__ is not None e.__context__ is not None and not e.__suppress_context__ and self.chain
and not e.__suppress_context__ ):
and self.chain e = e.__context__
): excinfo = (
e = e.__context__ ExceptionInfo((type(e), e, e.__traceback__))
excinfo = ( if e.__traceback__
ExceptionInfo((type(e), e, e.__traceback__)) else None
if e.__traceback__ )
else None descr = "During handling of the above exception, another exception occurred:"
) else:
descr = "During handling of the above exception, another exception occurred:" e = None
else: repr_chain.reverse()
e = None return ExceptionChainRepr(repr_chain)
repr_chain.reverse()
return ExceptionChainRepr(repr_chain)
class TerminalRepr(object): class TerminalRepr:
def __str__(self): def __str__(self):
s = self.__unicode__()
if _PY2:
s = s.encode("utf-8")
return s
def __unicode__(self):
# FYI this is called from pytest-xdist's serialization of exception # FYI this is called from pytest-xdist's serialization of exception
# information. # information.
io = py.io.TextIO() io = py.io.TextIO()
@ -850,7 +814,7 @@ class TerminalRepr(object):
return io.getvalue().strip() return io.getvalue().strip()
def __repr__(self): def __repr__(self):
return "<%s instance at %0x>" % (self.__class__, id(self)) return "<{} instance at {:0x}>".format(self.__class__, id(self))
class ExceptionRepr(TerminalRepr): class ExceptionRepr(TerminalRepr):
@ -868,7 +832,7 @@ class ExceptionRepr(TerminalRepr):
class ExceptionChainRepr(ExceptionRepr): class ExceptionChainRepr(ExceptionRepr):
def __init__(self, chain): def __init__(self, chain):
super(ExceptionChainRepr, self).__init__() super().__init__()
self.chain = chain self.chain = chain
# reprcrash and reprtraceback of the outermost (the newest) exception # reprcrash and reprtraceback of the outermost (the newest) exception
# in the chain # in the chain
@ -881,18 +845,18 @@ class ExceptionChainRepr(ExceptionRepr):
if element[2] is not None: if element[2] is not None:
tw.line("") tw.line("")
tw.line(element[2], yellow=True) tw.line(element[2], yellow=True)
super(ExceptionChainRepr, self).toterminal(tw) super().toterminal(tw)
class ReprExceptionInfo(ExceptionRepr): class ReprExceptionInfo(ExceptionRepr):
def __init__(self, reprtraceback, reprcrash): def __init__(self, reprtraceback, reprcrash):
super(ReprExceptionInfo, self).__init__() super().__init__()
self.reprtraceback = reprtraceback self.reprtraceback = reprtraceback
self.reprcrash = reprcrash self.reprcrash = reprcrash
def toterminal(self, tw): def toterminal(self, tw):
self.reprtraceback.toterminal(tw) self.reprtraceback.toterminal(tw)
super(ReprExceptionInfo, self).toterminal(tw) super().toterminal(tw)
class ReprTraceback(TerminalRepr): class ReprTraceback(TerminalRepr):
@ -969,7 +933,9 @@ class ReprEntry(TerminalRepr):
self.reprfileloc.toterminal(tw) self.reprfileloc.toterminal(tw)
def __str__(self): def __str__(self):
return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc) return "{}\n{}\n{}".format(
"\n".join(self.lines), self.reprlocals, self.reprfileloc
)
class ReprFileLocation(TerminalRepr): class ReprFileLocation(TerminalRepr):
@ -986,7 +952,7 @@ class ReprFileLocation(TerminalRepr):
if i != -1: if i != -1:
msg = msg[:i] msg = msg[:i]
tw.write(self.path, bold=True, red=True) tw.write(self.path, bold=True, red=True)
tw.line(":%s: %s" % (self.lineno, msg)) tw.line(":{}: {}".format(self.lineno, msg))
class ReprLocals(TerminalRepr): class ReprLocals(TerminalRepr):
@ -1006,7 +972,7 @@ class ReprFuncArgs(TerminalRepr):
if self.args: if self.args:
linesofar = "" linesofar = ""
for name, value in self.args: for name, value in self.args:
ns = "%s = %s" % (safe_str(name), safe_str(value)) ns = "{} = {}".format(name, value)
if len(ns) + len(linesofar) + 2 > tw.fullwidth: if len(ns) + len(linesofar) + 2 > tw.fullwidth:
if linesofar: if linesofar:
tw.line(linesofar) tw.line(linesofar)
@ -1038,23 +1004,6 @@ def getrawcode(obj, trycall=True):
return obj return obj
if PY35: # RecursionError introduced in 3.5
def is_recursion_error(excinfo):
return excinfo.errisinstance(RecursionError) # noqa
else:
def is_recursion_error(excinfo):
if not excinfo.errisinstance(RuntimeError):
return False
try:
return "maximum recursion depth exceeded" in str(excinfo.value)
except UnicodeError:
return False
# relative paths that we use to filter traceback entries from appearing to the user; # relative paths that we use to filter traceback entries from appearing to the user;
# see filter_traceback # see filter_traceback
# note: if we need to add more paths than what we have now we should probably use a list # note: if we need to add more paths than what we have now we should probably use a list

View File

@ -1,8 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import ast import ast
import inspect import inspect
import linecache import linecache
@ -14,10 +9,9 @@ from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right from bisect import bisect_right
import py import py
import six
class Source(object): class Source:
""" an immutable object holding a source code fragment, """ an immutable object holding a source code fragment,
possibly deindenting it. possibly deindenting it.
""" """
@ -34,7 +28,7 @@ class Source(object):
partlines = part.lines partlines = part.lines
elif isinstance(part, (tuple, list)): elif isinstance(part, (tuple, list)):
partlines = [x.rstrip("\n") for x in part] partlines = [x.rstrip("\n") for x in part]
elif isinstance(part, six.string_types): elif isinstance(part, str):
partlines = part.split("\n") partlines = part.split("\n")
else: else:
partlines = getsource(part, deindent=de).lines partlines = getsource(part, deindent=de).lines

View File

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
import pprint import pprint
import reprlib
from six.moves import reprlib
def _call_and_format_exception(call, x, *args): def _call_and_format_exception(call, x, *args):
@ -14,11 +12,8 @@ def _call_and_format_exception(call, x, *args):
exc_info = str(exc) exc_info = str(exc)
except Exception: except Exception:
exc_info = "unknown" exc_info = "unknown"
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
exc_name, exc_name, exc_info, x.__class__.__name__, id(x)
exc_info,
x.__class__.__name__,
id(x),
) )
@ -34,11 +29,11 @@ class SafeRepr(reprlib.Repr):
# Strictly speaking wrong on narrow builds # Strictly speaking wrong on narrow builds
def repr(u): def repr(u):
if "'" not in u: if "'" not in u:
return u"'%s'" % u return "'%s'" % u
elif '"' not in u: elif '"' not in u:
return u'"%s"' % u return '"%s"' % u
else: else:
return u"'%s'" % u.replace("'", r"\'") return "'%s'" % u.replace("'", r"\'")
s = repr(x[: self.maxstring]) s = repr(x[: self.maxstring])
if len(s) > self.maxstring: if len(s) > self.maxstring:

View File

@ -1,15 +1,8 @@
# -*- coding: utf-8 -*-
""" """
support for presenting detailed information in failing assertions. support for presenting detailed information in failing assertions.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import sys import sys
import six
from _pytest.assertion import rewrite from _pytest.assertion import rewrite
from _pytest.assertion import truncate from _pytest.assertion import truncate
from _pytest.assertion import util from _pytest.assertion import util
@ -56,14 +49,14 @@ def register_assert_rewrite(*names):
importhook.mark_rewrite(*names) importhook.mark_rewrite(*names)
class DummyRewriteHook(object): class DummyRewriteHook:
"""A no-op import hook for when rewriting is disabled.""" """A no-op import hook for when rewriting is disabled."""
def mark_rewrite(self, *names): def mark_rewrite(self, *names):
pass pass
class AssertionState(object): class AssertionState:
"""State for the assertion plugin.""" """State for the assertion plugin."""
def __init__(self, config, mode): def __init__(self, config, mode):
@ -74,10 +67,6 @@ class AssertionState(object):
def install_importhook(config): def install_importhook(config):
"""Try to install the rewrite hook, raise SystemError if it fails.""" """Try to install the rewrite hook, raise SystemError if it fails."""
# Jython has an AST bug that make the assertion rewriting hook malfunction.
if sys.platform.startswith("java"):
raise SystemError("rewrite not supported")
config._assertstate = AssertionState(config, "rewrite") config._assertstate = AssertionState(config, "rewrite")
config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
sys.meta_path.insert(0, hook) sys.meta_path.insert(0, hook)
@ -133,7 +122,7 @@ def pytest_runtest_setup(item):
if new_expl: if new_expl:
new_expl = truncate.truncate_if_required(new_expl, item) new_expl = truncate.truncate_if_required(new_expl, item)
new_expl = [line.replace("\n", "\\n") for line in new_expl] new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = six.text_type("\n~").join(new_expl) res = "\n~".join(new_expl)
if item.config.getvalue("assertmode") == "rewrite": if item.config.getvalue("assertmode") == "rewrite":
res = res.replace("%", "%%") res = res.replace("%", "%%")
return res return res

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
"""Rewrite assertion AST to produce nice error messages""" """Rewrite assertion AST to produce nice error messages"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import ast import ast
import errno import errno
import imp import imp
@ -15,17 +10,16 @@ import string
import struct import struct
import sys import sys
import types import types
from importlib.util import spec_from_file_location
import atomicwrites import atomicwrites
import py import py
import six
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.assertion import util from _pytest.assertion import util
from _pytest.assertion.util import ( # noqa: F401 from _pytest.assertion.util import ( # noqa: F401
format_explanation as _format_explanation, format_explanation as _format_explanation,
) )
from _pytest.compat import spec_from_file_location
from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import PurePath from _pytest.pathlib import PurePath
@ -35,28 +29,17 @@ if hasattr(imp, "get_tag"):
else: else:
if hasattr(sys, "pypy_version_info"): if hasattr(sys, "pypy_version_info"):
impl = "pypy" impl = "pypy"
elif sys.platform == "java":
impl = "jython"
else: else:
impl = "cpython" impl = "cpython"
ver = sys.version_info ver = sys.version_info
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1]) PYTEST_TAG = "{}-{}{}-PYTEST".format(impl, ver[0], ver[1])
del ver, impl del ver, impl
PYC_EXT = ".py" + (__debug__ and "c" or "o") PYC_EXT = ".py" + (__debug__ and "c" or "o")
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
if sys.version_info >= (3, 5): class AssertionRewritingHook:
ast_Call = ast.Call
else:
def ast_Call(a, b, c):
return ast.Call(a, b, c, None, None)
class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts.""" """PEP302 Import hook which rewrites asserts."""
def __init__(self, config): def __init__(self, config):
@ -165,7 +148,7 @@ class AssertionRewritingHook(object):
# to check for a cached pyc. This may not be optimal... # to check for a cached pyc. This may not be optimal...
co = _read_pyc(fn_pypath, pyc, state.trace) co = _read_pyc(fn_pypath, pyc, state.trace)
if co is None: if co is None:
state.trace("rewriting %r" % (fn,)) state.trace("rewriting {!r}".format(fn))
source_stat, co = _rewrite_test(self.config, fn_pypath) source_stat, co = _rewrite_test(self.config, fn_pypath)
if co is None: if co is None:
# Probably a SyntaxError in the test. # Probably a SyntaxError in the test.
@ -177,7 +160,7 @@ class AssertionRewritingHook(object):
finally: finally:
self._writing_pyc = False self._writing_pyc = False
else: else:
state.trace("found cached rewritten pyc for %r" % (fn,)) state.trace("found cached rewritten pyc for {!r}".format(fn))
self.modules[name] = co, pyc self.modules[name] = co, pyc
return self return self
@ -216,26 +199,28 @@ class AssertionRewritingHook(object):
if self._is_marked_for_rewrite(name, state): if self._is_marked_for_rewrite(name, state):
return False return False
state.trace("early skip of rewriting module: %s" % (name,)) state.trace("early skip of rewriting module: {}".format(name))
return True return True
def _should_rewrite(self, name, fn_pypath, state): def _should_rewrite(self, name, fn_pypath, state):
# always rewrite conftest files # always rewrite conftest files
fn = str(fn_pypath) fn = str(fn_pypath)
if fn_pypath.basename == "conftest.py": if fn_pypath.basename == "conftest.py":
state.trace("rewriting conftest file: %r" % (fn,)) state.trace("rewriting conftest file: {!r}".format(fn))
return True return True
if self.session is not None: if self.session is not None:
if self.session.isinitpath(fn): if self.session.isinitpath(fn):
state.trace("matched test file (was specified on cmdline): %r" % (fn,)) state.trace(
"matched test file (was specified on cmdline): {!r}".format(fn)
)
return True return True
# modules not passed explicitly on the command line are only # modules not passed explicitly on the command line are only
# rewritten if they match the naming convention for test files # rewritten if they match the naming convention for test files
for pat in self.fnpats: for pat in self.fnpats:
if fn_pypath.fnmatch(pat): if fn_pypath.fnmatch(pat):
state.trace("matched test file %r" % (fn,)) state.trace("matched test file {!r}".format(fn))
return True return True
return self._is_marked_for_rewrite(name, state) return self._is_marked_for_rewrite(name, state)
@ -246,7 +231,9 @@ class AssertionRewritingHook(object):
except KeyError: except KeyError:
for marked in self._must_rewrite: for marked in self._must_rewrite:
if name == marked or name.startswith(marked + "."): if name == marked or name.startswith(marked + "."):
state.trace("matched marked file %r (from %r)" % (name, marked)) state.trace(
"matched marked file {!r} (from {!r})".format(name, marked)
)
self._marked_for_rewrite_cache[name] = True self._marked_for_rewrite_cache[name] = True
return True return True
@ -341,7 +328,7 @@ def _write_pyc(state, co, source_stat, pyc):
fp.write(struct.pack("<LL", mtime, size)) fp.write(struct.pack("<LL", mtime, size))
fp.write(marshal.dumps(co)) fp.write(marshal.dumps(co))
except EnvironmentError as e: except EnvironmentError as e:
state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno)) state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
# we ignore any failure to write the cache file # we ignore any failure to write the cache file
# there are many reasons, permission-denied, __pycache__ being a # there are many reasons, permission-denied, __pycache__ being a
# file etc. # file etc.
@ -349,8 +336,8 @@ def _write_pyc(state, co, source_stat, pyc):
return True return True
RN = "\r\n".encode("utf-8") RN = "\r\n".encode()
N = "\n".encode("utf-8") N = "\n".encode()
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+") cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
BOM_UTF8 = "\xef\xbb\xbf" BOM_UTF8 = "\xef\xbb\xbf"
@ -364,42 +351,11 @@ def _rewrite_test(config, fn):
source = fn.read("rb") source = fn.read("rb")
except EnvironmentError: except EnvironmentError:
return None, None return None, None
if ASCII_IS_DEFAULT_ENCODING:
# ASCII is the default encoding in Python 2. Without a coding
# declaration, Python 2 will complain about any bytes in the file
# outside the ASCII range. Sadly, this behavior does not extend to
# compile() or ast.parse(), which prefer to interpret the bytes as
# latin-1. (At least they properly handle explicit coding cookies.) To
# preserve this error behavior, we could force ast.parse() to use ASCII
# as the encoding by inserting a coding cookie. Unfortunately, that
# messes up line numbers. Thus, we have to check ourselves if anything
# is outside the ASCII range in the case no encoding is explicitly
# declared. For more context, see issue #269. Yay for Python 3 which
# gets this right.
end1 = source.find("\n")
end2 = source.find("\n", end1 + 1)
if (
not source.startswith(BOM_UTF8)
and cookie_re.match(source[0:end1]) is None
and cookie_re.match(source[end1 + 1 : end2]) is None
):
if hasattr(state, "_indecode"):
# encodings imported us again, so don't rewrite.
return None, None
state._indecode = True
try:
try:
source.decode("ascii")
except UnicodeDecodeError:
# Let it fail in real import.
return None, None
finally:
del state._indecode
try: try:
tree = ast.parse(source, filename=fn.strpath) tree = ast.parse(source, filename=fn.strpath)
except SyntaxError: except SyntaxError:
# Let this pop up again in the real import. # Let this pop up again in the real import.
state.trace("failed to parse: %r" % (fn,)) state.trace("failed to parse: {!r}".format(fn))
return None, None return None, None
rewrite_asserts(tree, fn, config) rewrite_asserts(tree, fn, config)
try: try:
@ -407,7 +363,7 @@ def _rewrite_test(config, fn):
except SyntaxError: except SyntaxError:
# It's possible that this error is from some bug in the # It's possible that this error is from some bug in the
# assertion rewriting, but I don't know of a fast way to tell. # assertion rewriting, but I don't know of a fast way to tell.
state.trace("failed to compile: %r" % (fn,)) state.trace("failed to compile: {!r}".format(fn))
return None, None return None, None
return stat, co return stat, co
@ -427,7 +383,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
size = source.size() size = source.size()
data = fp.read(12) data = fp.read(12)
except EnvironmentError as e: except EnvironmentError as e:
trace("_read_pyc(%s): EnvironmentError %s" % (source, e)) trace("_read_pyc({}): EnvironmentError {}".format(source, e))
return None return None
# Check for invalid or out of date pyc file. # Check for invalid or out of date pyc file.
if ( if (
@ -440,7 +396,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
try: try:
co = marshal.load(fp) co = marshal.load(fp)
except Exception as e: except Exception as e:
trace("_read_pyc(%s): marshal.load error %s" % (source, e)) trace("_read_pyc({}): marshal.load error {}".format(source, e))
return None return None
if not isinstance(co, types.CodeType): if not isinstance(co, types.CodeType):
trace("_read_pyc(%s): not a code object" % source) trace("_read_pyc(%s): not a code object" % source)
@ -468,11 +424,11 @@ def _saferepr(obj):
# only occurs in python2.x, repr must return text in python3+ # only occurs in python2.x, repr must return text in python3+
if isinstance(r, bytes): if isinstance(r, bytes):
# Represent unprintable bytes as `\x##` # Represent unprintable bytes as `\x##`
r = u"".join( r = "".join(
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode() "\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
for c in r for c in r
) )
return r.replace(u"\n", u"\\n") return r.replace("\n", "\\n")
def _format_assertmsg(obj): def _format_assertmsg(obj):
@ -487,10 +443,10 @@ def _format_assertmsg(obj):
# contains a newline it gets escaped, however if an object has a # contains a newline it gets escaped, however if an object has a
# .__repr__() which contains newlines it does not get escaped. # .__repr__() which contains newlines it does not get escaped.
# However in either case we want to preserve the newline. # However in either case we want to preserve the newline.
replaces = [(u"\n", u"\n~"), (u"%", u"%%")] replaces = [("\n", "\n~"), ("%", "%%")]
if not isinstance(obj, six.string_types): if not isinstance(obj, str):
obj = saferepr(obj) obj = saferepr(obj)
replaces.append((u"\\n", u"\n~")) replaces.append(("\\n", "\n~"))
if isinstance(obj, bytes): if isinstance(obj, bytes):
replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces] replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces]
@ -513,8 +469,8 @@ def _should_repr_global_name(obj):
def _format_boolop(explanations, is_or): def _format_boolop(explanations, is_or):
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
if isinstance(explanation, six.text_type): if isinstance(explanation, str):
return explanation.replace(u"%", u"%%") return explanation.replace("%", "%%")
else: else:
return explanation.replace(b"%", b"%%") return explanation.replace(b"%", b"%%")
@ -643,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor):
""" """
def __init__(self, module_path, config): def __init__(self, module_path, config):
super(AssertionRewriter, self).__init__() super().__init__()
self.module_path = module_path self.module_path = module_path
self.config = config self.config = config
@ -655,7 +611,7 @@ class AssertionRewriter(ast.NodeVisitor):
# Insert some special imports at the top of the module but after any # Insert some special imports at the top of the module but after any
# docstrings and __future__ imports. # docstrings and __future__ imports.
aliases = [ aliases = [
ast.alias(six.moves.builtins.__name__, "@py_builtins"), ast.alias("builtins", "@py_builtins"),
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"), ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
] ]
doc = getattr(mod, "docstring", None) doc = getattr(mod, "docstring", None)
@ -737,7 +693,7 @@ class AssertionRewriter(ast.NodeVisitor):
"""Call a helper in this module.""" """Call a helper in this module."""
py_name = ast.Name("@pytest_ar", ast.Load()) py_name = ast.Name("@pytest_ar", ast.Load())
attr = ast.Attribute(py_name, name, ast.Load()) attr = ast.Attribute(py_name, name, ast.Load())
return ast_Call(attr, list(args), []) return ast.Call(attr, list(args), [])
def builtin(self, name): def builtin(self, name):
"""Return the builtin called *name*.""" """Return the builtin called *name*."""
@ -847,11 +803,9 @@ class AssertionRewriter(ast.NodeVisitor):
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())
exc = ast_Call(err_name, [fmt], []) exc = ast.Call(err_name, [fmt], [])
if sys.version_info[0] >= 3: raise_ = ast.Raise(exc, None)
raise_ = ast.Raise(exc, None)
else:
raise_ = ast.Raise(exc, None, None)
body.append(raise_) body.append(raise_)
# Clear temporary variables by setting them to None. # Clear temporary variables by setting them to None.
if self.variables: if self.variables:
@ -893,7 +847,7 @@ warn_explicit(
def visit_Name(self, name): def visit_Name(self, name):
# 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(ast.Str(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])
@ -920,7 +874,7 @@ warn_explicit(
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(ast.Str(expl))
call = ast_Call(app, [expl_format], []) call = ast.Call(app, [expl_format], [])
self.on_failure.append(ast.Expr(call)) self.on_failure.append(ast.Expr(call))
if i < levels: if i < levels:
cond = res cond = res
@ -945,15 +899,25 @@ warn_explicit(
symbol = binop_map[binop.op.__class__] symbol = binop_map[binop.op.__class__]
left_expr, left_expl = self.visit(binop.left) left_expr, left_expl = self.visit(binop.left)
right_expr, right_expl = self.visit(binop.right) right_expr, right_expl = self.visit(binop.right)
explanation = "(%s %s %s)" % (left_expl, symbol, right_expl) explanation = "({} {} {})".format(left_expl, symbol, right_expl)
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation return res, explanation
def visit_Call_35(self, call): @staticmethod
def _is_any_call_with_generator_or_list_comprehension(call):
"""Return True if the Call node is an 'any' call with a generator or list comprehension"""
return (
isinstance(call.func, ast.Name)
and call.func.id == "all"
and len(call.args) == 1
and isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp))
)
def visit_Call(self, call):
""" """
visit `ast.Call` nodes on Python3.5 and after visit `ast.Call` nodes
""" """
if isinstance(call.func, ast.Name) and call.func.id == "all": if self._is_any_call_with_generator_or_list_comprehension(call):
return self._visit_all(call) return self._visit_all(call)
new_func, func_expl = self.visit(call.func) new_func, func_expl = self.visit(call.func)
arg_expls = [] arg_expls = []
@ -971,17 +935,15 @@ warn_explicit(
else: # **args have `arg` keywords with an .arg of None else: # **args have `arg` keywords with an .arg of None
arg_expls.append("**" + expl) arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ", ".join(arg_expls)) expl = "{}({})".format(func_expl, ", ".join(arg_expls))
new_call = ast.Call(new_func, new_args, new_kwargs) new_call = ast.Call(new_func, new_args, new_kwargs)
res = self.assign(new_call) res = self.assign(new_call)
res_expl = self.explanation_param(self.display(res)) res_expl = self.explanation_param(self.display(res))
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) outer_expl = "{}\n{{{} = {}\n}}".format(res_expl, res_expl, expl)
return res, outer_expl return res, outer_expl
def _visit_all(self, call): def _visit_all(self, call):
"""Special rewrite for the builtin all function, see #5062""" """Special rewrite for the builtin all function, see #5062"""
if not isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp)):
return
gen_exp = call.args[0] gen_exp = call.args[0]
assertion_module = ast.Module( assertion_module = ast.Module(
body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)] body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)]
@ -1005,46 +967,6 @@ warn_explicit(
new_starred = ast.Starred(res, starred.ctx) new_starred = ast.Starred(res, starred.ctx)
return new_starred, "*" + expl return new_starred, "*" + expl
def visit_Call_legacy(self, call):
"""
visit `ast.Call nodes on 3.4 and below`
"""
if isinstance(call.func, ast.Name) and call.func.id == "all":
return self._visit_all(call)
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
new_kwargs = []
new_star = new_kwarg = None
for arg in call.args:
res, expl = self.visit(arg)
new_args.append(res)
arg_expls.append(expl)
for keyword in call.keywords:
res, expl = self.visit(keyword.value)
new_kwargs.append(ast.keyword(keyword.arg, res))
arg_expls.append(keyword.arg + "=" + expl)
if call.starargs:
new_star, expl = self.visit(call.starargs)
arg_expls.append("*" + expl)
if call.kwargs:
new_kwarg, expl = self.visit(call.kwargs)
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
res = self.assign(new_call)
res_expl = self.explanation_param(self.display(res))
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
# ast.Call signature changed on 3.5,
# conditionally change which methods is named
# visit_Call depending on Python version
if sys.version_info >= (3, 5):
visit_Call = visit_Call_35
else:
visit_Call = visit_Call_legacy
def visit_Attribute(self, attr): def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load): if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr) return self.generic_visit(attr)
@ -1074,7 +996,7 @@ warn_explicit(
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(ast.Str(sym))
expl = "%s %s %s" % (left_expl, sym, next_expl) expl = "{} {} {}".format(left_expl, sym, next_expl)
expls.append(ast.Str(expl)) expls.append(ast.Str(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))

View File

@ -1,18 +1,11 @@
# -*- coding: utf-8 -*-
""" """
Utilities for truncating assertion output. Utilities for truncating assertion output.
Current default behaviour is to truncate assertion explanations at Current default behaviour is to truncate assertion explanations at
~8 terminal lines, unless running in "-vv" mode or running on CI. ~8 terminal lines, unless running in "-vv" mode or running on CI.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os import os
import six
DEFAULT_MAX_LINES = 8 DEFAULT_MAX_LINES = 8
DEFAULT_MAX_CHARS = 8 * 80 DEFAULT_MAX_CHARS = 8 * 80
USAGE_MSG = "use '-vv' to show" USAGE_MSG = "use '-vv' to show"
@ -76,7 +69,7 @@ def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
else: else:
msg += " ({} lines hidden)".format(truncated_line_count) msg += " ({} lines hidden)".format(truncated_line_count)
msg += ", {}".format(USAGE_MSG) msg += ", {}".format(USAGE_MSG)
truncated_explanation.extend([six.text_type(""), six.text_type(msg)]) truncated_explanation.extend(["", str(msg)])
return truncated_explanation return truncated_explanation

View File

@ -1,15 +1,8 @@
# -*- coding: utf-8 -*-
"""Utilities for assertion debugging""" """Utilities for assertion debugging"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import pprint import pprint
from collections.abc import Sequence
import six
import _pytest._code import _pytest._code
from ..compat import Sequence
from _pytest import outcomes from _pytest import outcomes
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
@ -42,7 +35,7 @@ def format_explanation(explanation):
explanation = ecu(explanation) explanation = ecu(explanation)
lines = _split_explanation(explanation) lines = _split_explanation(explanation)
result = _format_lines(lines) result = _format_lines(lines)
return u"\n".join(result) return "\n".join(result)
def _split_explanation(explanation): def _split_explanation(explanation):
@ -52,7 +45,7 @@ def _split_explanation(explanation):
Any other newlines will be escaped and appear in the line as the Any other newlines will be escaped and appear in the line as the
literal '\n' characters. literal '\n' characters.
""" """
raw_lines = (explanation or u"").split("\n") raw_lines = (explanation or "").split("\n")
lines = [raw_lines[0]] lines = [raw_lines[0]]
for values in raw_lines[1:]: for values in raw_lines[1:]:
if values and values[0] in ["{", "}", "~", ">"]: if values and values[0] in ["{", "}", "~", ">"]:
@ -77,13 +70,13 @@ def _format_lines(lines):
for line in lines[1:]: for line in lines[1:]:
if line.startswith("{"): if line.startswith("{"):
if stackcnt[-1]: if stackcnt[-1]:
s = u"and " s = "and "
else: else:
s = u"where " s = "where "
stack.append(len(result)) stack.append(len(result))
stackcnt[-1] += 1 stackcnt[-1] += 1
stackcnt.append(0) stackcnt.append(0)
result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:]) result.append(" +" + " " * (len(stack) - 1) + s + line[1:])
elif line.startswith("}"): elif line.startswith("}"):
stack.pop() stack.pop()
stackcnt.pop() stackcnt.pop()
@ -92,7 +85,7 @@ def _format_lines(lines):
assert line[0] in ["~", ">"] assert line[0] in ["~", ">"]
stack[-1] += 1 stack[-1] += 1
indent = len(stack) if line.startswith("~") else len(stack) - 1 indent = len(stack) if line.startswith("~") else len(stack) - 1
result.append(u" " * indent + line[1:]) result.append(" " * indent + line[1:])
assert len(stack) == 1 assert len(stack) == 1
return result return result
@ -142,7 +135,7 @@ def assertrepr_compare(config, op, left, right):
left_repr = saferepr(left, maxsize=int(width // 2)) left_repr = saferepr(left, maxsize=int(width // 2))
right_repr = saferepr(right, maxsize=width - len(left_repr)) right_repr = saferepr(right, maxsize=width - len(left_repr))
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr)) summary = "{} {} {}".format(ecu(left_repr), op, ecu(right_repr))
verbose = config.getoption("verbose") verbose = config.getoption("verbose")
explanation = None explanation = None
@ -175,9 +168,9 @@ def assertrepr_compare(config, op, left, right):
raise raise
except Exception: except Exception:
explanation = [ explanation = [
u"(pytest_assertion plugin: representation of details failed. " "(pytest_assertion plugin: representation of details failed. "
u"Probably an object has a faulty __repr__.)", "Probably an object has a faulty __repr__.)",
six.text_type(_pytest._code.ExceptionInfo.from_current()), str(_pytest._code.ExceptionInfo.from_current()),
] ]
if not explanation: if not explanation:
@ -204,7 +197,7 @@ def _diff_text(left, right, verbose=0):
This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
newlines and carriage returns (#429). newlines and carriage returns (#429).
""" """
r = six.text_type(repr(binary_text)[1:-1]) r = str(repr(binary_text)[1:-1])
r = r.replace(r"\n", "\n") r = r.replace(r"\n", "\n")
r = r.replace(r"\r", "\r") r = r.replace(r"\r", "\r")
return r return r
@ -221,7 +214,7 @@ def _diff_text(left, right, verbose=0):
if i > 42: if i > 42:
i -= 10 # Provide some context i -= 10 # Provide some context
explanation = [ explanation = [
u"Skipping %s identical leading characters in diff, use -v to show" % i "Skipping %s identical leading characters in diff, use -v to show" % i
] ]
left = left[i:] left = left[i:]
right = right[i:] right = right[i:]
@ -232,8 +225,8 @@ def _diff_text(left, right, verbose=0):
if i > 42: if i > 42:
i -= 10 # Provide some context i -= 10 # Provide some context
explanation += [ explanation += [
u"Skipping {} identical trailing " "Skipping {} identical trailing "
u"characters in diff, use -v to show".format(i) "characters in diff, use -v to show".format(i)
] ]
left = left[:-i] left = left[:-i]
right = right[:-i] right = right[:-i]
@ -241,7 +234,7 @@ def _diff_text(left, right, verbose=0):
if left.isspace() or right.isspace(): if left.isspace() or right.isspace():
left = repr(str(left)) left = repr(str(left))
right = repr(str(right)) right = repr(str(right))
explanation += [u"Strings contain only whitespace, escaping them using repr()"] explanation += ["Strings contain only whitespace, escaping them using repr()"]
explanation += [ explanation += [
line.strip("\n") line.strip("\n")
for line in ndiff(left.splitlines(keepends), right.splitlines(keepends)) for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
@ -255,29 +248,29 @@ def _compare_eq_verbose(left, right):
right_lines = repr(right).splitlines(keepends) right_lines = repr(right).splitlines(keepends)
explanation = [] explanation = []
explanation += [u"-" + line for line in left_lines] explanation += ["-" + line for line in left_lines]
explanation += [u"+" + line for line in right_lines] explanation += ["+" + line for line in right_lines]
return explanation return explanation
def _compare_eq_iterable(left, right, verbose=0): def _compare_eq_iterable(left, right, verbose=0):
if not verbose: if not verbose:
return [u"Use -v to get the full diff"] return ["Use -v to get the full diff"]
# dynamic import to speedup pytest # dynamic import to speedup pytest
import difflib import difflib
try: try:
left_formatting = pprint.pformat(left).splitlines() left_formatting = pprint.pformat(left).splitlines()
right_formatting = pprint.pformat(right).splitlines() right_formatting = pprint.pformat(right).splitlines()
explanation = [u"Full diff:"] explanation = ["Full diff:"]
except Exception: except Exception:
# hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
# sorted() on a list would raise. See issue #718. # sorted() on a list would raise. See issue #718.
# As a workaround, the full diff is generated by using the repr() string of each item of each container. # As a workaround, the full diff is generated by using the repr() string of each item of each container.
left_formatting = sorted(repr(x) for x in left) left_formatting = sorted(repr(x) for x in left)
right_formatting = sorted(repr(x) for x in right) right_formatting = sorted(repr(x) for x in right)
explanation = [u"Full diff (fallback to calling repr on each item):"] explanation = ["Full diff (fallback to calling repr on each item):"]
explanation.extend( explanation.extend(
line.strip() for line in difflib.ndiff(left_formatting, right_formatting) line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
) )
@ -290,7 +283,9 @@ def _compare_eq_sequence(left, right, verbose=0):
len_right = len(right) len_right = len(right)
for i in range(min(len_left, len_right)): for i in range(min(len_left, len_right)):
if left[i] != right[i]: if left[i] != right[i]:
explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])] explanation += [
"At index {} diff: {!r} != {!r}".format(i, left[i], right[i])
]
break break
len_diff = len_left - len_right len_diff = len_left - len_right
@ -304,10 +299,12 @@ def _compare_eq_sequence(left, right, verbose=0):
extra = saferepr(right[len_left]) extra = saferepr(right[len_left])
if len_diff == 1: if len_diff == 1:
explanation += [u"%s contains one more item: %s" % (dir_with_more, extra)] explanation += [
"{} contains one more item: {}".format(dir_with_more, extra)
]
else: else:
explanation += [ explanation += [
u"%s contains %d more items, first extra item: %s" "%s contains %d more items, first extra item: %s"
% (dir_with_more, len_diff, extra) % (dir_with_more, len_diff, extra)
] ]
return explanation return explanation
@ -318,11 +315,11 @@ def _compare_eq_set(left, right, verbose=0):
diff_left = left - right diff_left = left - right
diff_right = right - left diff_right = right - left
if diff_left: if diff_left:
explanation.append(u"Extra items in the left set:") explanation.append("Extra items in the left set:")
for item in diff_left: for item in diff_left:
explanation.append(saferepr(item)) explanation.append(saferepr(item))
if diff_right: if diff_right:
explanation.append(u"Extra items in the right set:") explanation.append("Extra items in the right set:")
for item in diff_right: for item in diff_right:
explanation.append(saferepr(item)) explanation.append(saferepr(item))
return explanation return explanation
@ -335,20 +332,20 @@ def _compare_eq_dict(left, right, verbose=0):
common = set_left.intersection(set_right) common = set_left.intersection(set_right)
same = {k: left[k] for k in common if left[k] == right[k]} same = {k: left[k] for k in common if left[k] == right[k]}
if same and verbose < 2: if same and verbose < 2:
explanation += [u"Omitting %s identical items, use -vv to show" % len(same)] explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
elif same: elif same:
explanation += [u"Common items:"] explanation += ["Common items:"]
explanation += pprint.pformat(same).splitlines() explanation += pprint.pformat(same).splitlines()
diff = {k for k in common if left[k] != right[k]} diff = {k for k in common if left[k] != right[k]}
if diff: if diff:
explanation += [u"Differing items:"] explanation += ["Differing items:"]
for k in diff: for k in diff:
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
extra_left = set_left - set_right extra_left = set_left - set_right
len_extra_left = len(extra_left) len_extra_left = len(extra_left)
if len_extra_left: if len_extra_left:
explanation.append( explanation.append(
u"Left contains %d more item%s:" "Left contains %d more item%s:"
% (len_extra_left, "" if len_extra_left == 1 else "s") % (len_extra_left, "" if len_extra_left == 1 else "s")
) )
explanation.extend( explanation.extend(
@ -358,7 +355,7 @@ def _compare_eq_dict(left, right, verbose=0):
len_extra_right = len(extra_right) len_extra_right = len(extra_right)
if len_extra_right: if len_extra_right:
explanation.append( explanation.append(
u"Right contains %d more item%s:" "Right contains %d more item%s:"
% (len_extra_right, "" if len_extra_right == 1 else "s") % (len_extra_right, "" if len_extra_right == 1 else "s")
) )
explanation.extend( explanation.extend(
@ -386,15 +383,15 @@ def _compare_eq_cls(left, right, verbose, type_fns):
explanation = [] explanation = []
if same and verbose < 2: if same and verbose < 2:
explanation.append(u"Omitting %s identical items, use -vv to show" % len(same)) explanation.append("Omitting %s identical items, use -vv to show" % len(same))
elif same: elif same:
explanation += [u"Matching attributes:"] explanation += ["Matching attributes:"]
explanation += pprint.pformat(same).splitlines() explanation += pprint.pformat(same).splitlines()
if diff: if diff:
explanation += [u"Differing attributes:"] explanation += ["Differing attributes:"]
for field in diff: for field in diff:
explanation += [ explanation += [
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field)) ("%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
] ]
return explanation return explanation
@ -405,14 +402,14 @@ def _notin_text(term, text, verbose=0):
tail = text[index + len(term) :] tail = text[index + len(term) :]
correct_text = head + tail correct_text = head + tail
diff = _diff_text(correct_text, text, verbose) diff = _diff_text(correct_text, text, verbose)
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)] newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)]
for line in diff: for line in diff:
if line.startswith(u"Skipping"): if line.startswith("Skipping"):
continue continue
if line.startswith(u"- "): if line.startswith("- "):
continue continue
if line.startswith(u"+ "): if line.startswith("+ "):
newdiff.append(u" " + line[2:]) newdiff.append(" " + line[2:])
else: else:
newdiff.append(line) newdiff.append(line)
return newdiff return newdiff

View File

@ -1,29 +1,22 @@
# -*- coding: utf-8 -*-
""" """
merged implementation of the cache provider merged implementation of the cache provider
the name cache was not chosen to ensure pluggy automatically the name cache was not chosen to ensure pluggy automatically
ignores the external pytest-cache ignores the external pytest-cache
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import json import json
import os import os
from collections import OrderedDict from collections import OrderedDict
import attr import attr
import py import py
import six
import pytest import pytest
from .compat import _PY2 as PY2
from .pathlib import Path from .pathlib import Path
from .pathlib import resolve_from_str from .pathlib import resolve_from_str
from .pathlib import rmtree from .pathlib import rmtree
README_CONTENT = u"""\ README_CONTENT = """\
# pytest cache directory # # pytest cache directory #
This directory contains data from the pytest's cache plugin, This directory contains data from the pytest's cache plugin,
@ -43,7 +36,7 @@ Signature: 8a477f597d28d172789f06886806bc55
@attr.s @attr.s
class Cache(object): class Cache:
_cachedir = attr.ib(repr=False) _cachedir = attr.ib(repr=False)
_config = attr.ib(repr=False) _config = attr.ib(repr=False)
@ -129,7 +122,7 @@ class Cache(object):
if not cache_dir_exists_already: if not cache_dir_exists_already:
self._ensure_supporting_files() self._ensure_supporting_files()
try: try:
f = path.open("wb" if PY2 else "w") f = path.open("w")
except (IOError, OSError): except (IOError, OSError):
self.warn("cache could not write path {path}", path=path) self.warn("cache could not write path {path}", path=path)
else: else:
@ -142,14 +135,14 @@ class Cache(object):
readme_path.write_text(README_CONTENT) readme_path.write_text(README_CONTENT)
gitignore_path = self._cachedir.joinpath(".gitignore") gitignore_path = self._cachedir.joinpath(".gitignore")
msg = u"# Created by pytest automatically.\n*" msg = "# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8") gitignore_path.write_text(msg, encoding="UTF-8")
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
class LFPlugin(object): class LFPlugin:
""" Plugin which implements the --lf (run last-failing) option """ """ Plugin which implements the --lf (run last-failing) option """
def __init__(self, config): def __init__(self, config):
@ -262,7 +255,7 @@ class LFPlugin(object):
config.cache.set("cache/lastfailed", self.lastfailed) config.cache.set("cache/lastfailed", self.lastfailed)
class NFPlugin(object): class NFPlugin:
""" Plugin which implements the --nf (run new-first) option """ """ Plugin which implements the --nf (run new-first) option """
def __init__(self, config): def __init__(self, config):
@ -281,8 +274,8 @@ class NFPlugin(object):
other_items[item.nodeid] = item other_items[item.nodeid] = item
items[:] = self._get_increasing_order( items[:] = self._get_increasing_order(
six.itervalues(new_items) new_items.values()
) + self._get_increasing_order(six.itervalues(other_items)) ) + self._get_increasing_order(other_items.values())
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)] self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
def _get_increasing_order(self, items): def _get_increasing_order(self, items):

View File

@ -1,12 +1,7 @@
# -*- coding: utf-8 -*-
""" """
per-test stdout/stderr capturing mechanism. per-test stdout/stderr capturing mechanism.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import collections import collections
import contextlib import contextlib
import io import io
@ -15,10 +10,7 @@ import sys
from io import UnsupportedOperation from io import UnsupportedOperation
from tempfile import TemporaryFile from tempfile import TemporaryFile
import six
import pytest import pytest
from _pytest.compat import _PY3
from _pytest.compat import CaptureIO from _pytest.compat import CaptureIO
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
@ -67,7 +59,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
sys.stderr.write(err) sys.stderr.write(err)
class CaptureManager(object): class CaptureManager:
""" """
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
test phase (setup, call, teardown). After each of those points, the captured output is obtained and test phase (setup, call, teardown). After each of those points, the captured output is obtained and
@ -86,10 +78,8 @@ class CaptureManager(object):
self._current_item = None self._current_item = None
def __repr__(self): def __repr__(self):
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % ( return "<CaptureManager _method={!r} _global_capturing={!r} _current_item={!r}>".format(
self._method, self._method, self._global_capturing, self._current_item
self._global_capturing,
self._current_item,
) )
def _getcapture(self, method): def _getcapture(self, method):
@ -283,10 +273,6 @@ def capsysbinary(request):
``out`` and ``err`` will be ``bytes`` objects. ``out`` and ``err`` will be ``bytes`` objects.
""" """
_ensure_only_one_capture_fixture(request, "capsysbinary") _ensure_only_one_capture_fixture(request, "capsysbinary")
# Currently, the implementation uses the python3 specific `.buffer`
# property of CaptureIO.
if sys.version_info < (3,):
raise request.raiseerror("capsysbinary is only supported on Python 3")
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture: with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
yield fixture yield fixture
@ -345,7 +331,7 @@ def _install_capture_fixture_on_item(request, capture_class):
del request.node._capture_fixture del request.node._capture_fixture
class CaptureFixture(object): class CaptureFixture:
""" """
Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary` Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
fixtures. fixtures.
@ -424,7 +410,7 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
return EncodedFile(f, encoding or default_encoding) return EncodedFile(f, encoding or default_encoding)
class EncodedFile(object): class EncodedFile:
errors = "strict" # possibly needed by py3 code (issue555) errors = "strict" # possibly needed by py3 code (issue555)
def __init__(self, buffer, encoding): def __init__(self, buffer, encoding):
@ -432,9 +418,9 @@ class EncodedFile(object):
self.encoding = encoding self.encoding = encoding
def write(self, obj): def write(self, obj):
if isinstance(obj, six.text_type): if isinstance(obj, str):
obj = obj.encode(self.encoding, "replace") obj = obj.encode(self.encoding, "replace")
elif _PY3: else:
raise TypeError( raise TypeError(
"write() argument must be str, not {}".format(type(obj).__name__) "write() argument must be str, not {}".format(type(obj).__name__)
) )
@ -460,7 +446,7 @@ class EncodedFile(object):
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"]) CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
class MultiCapture(object): class MultiCapture:
out = err = in_ = None out = err = in_ = None
_state = None _state = None
@ -473,7 +459,7 @@ class MultiCapture(object):
self.err = Capture(2) self.err = Capture(2)
def __repr__(self): def __repr__(self):
return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % ( return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
self.out, self.out,
self.err, self.err,
self.in_, self.in_,
@ -539,12 +525,12 @@ class MultiCapture(object):
) )
class NoCapture(object): class NoCapture:
EMPTY_BUFFER = None EMPTY_BUFFER = None
__init__ = start = done = suspend = resume = lambda *args: None __init__ = start = done = suspend = resume = lambda *args: None
class FDCaptureBinary(object): class FDCaptureBinary:
"""Capture IO to/from a given os-level filedescriptor. """Capture IO to/from a given os-level filedescriptor.
snap() produces `bytes` snap() produces `bytes`
@ -578,10 +564,8 @@ class FDCaptureBinary(object):
self.tmpfile_fd = tmpfile.fileno() self.tmpfile_fd = tmpfile.fileno()
def __repr__(self): def __repr__(self):
return "<FDCapture %s oldfd=%s _state=%r>" % ( return "<FDCapture {} oldfd={} _state={!r}>".format(
self.targetfd, self.targetfd, getattr(self, "targetfd_save", None), self._state
getattr(self, "targetfd_save", None),
self._state,
) )
def start(self): def start(self):
@ -608,7 +592,7 @@ class FDCaptureBinary(object):
os.dup2(targetfd_save, self.targetfd) os.dup2(targetfd_save, self.targetfd)
os.close(targetfd_save) os.close(targetfd_save)
self.syscapture.done() self.syscapture.done()
_attempt_to_close_capture_file(self.tmpfile) self.tmpfile.close()
self._state = "done" self._state = "done"
def suspend(self): def suspend(self):
@ -623,7 +607,7 @@ class FDCaptureBinary(object):
def writeorg(self, data): def writeorg(self, data):
""" write to original file descriptor. """ """ write to original file descriptor. """
if isinstance(data, six.text_type): if isinstance(data, str):
data = data.encode("utf8") # XXX use encoding of original stream data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data) os.write(self.targetfd_save, data)
@ -637,14 +621,14 @@ class FDCapture(FDCaptureBinary):
EMPTY_BUFFER = str() EMPTY_BUFFER = str()
def snap(self): def snap(self):
res = super(FDCapture, self).snap() res = super().snap()
enc = getattr(self.tmpfile, "encoding", None) enc = getattr(self.tmpfile, "encoding", None)
if enc and isinstance(res, bytes): if enc and isinstance(res, bytes):
res = six.text_type(res, enc, "replace") res = str(res, enc, "replace")
return res return res
class SysCapture(object): class SysCapture:
EMPTY_BUFFER = str() EMPTY_BUFFER = str()
_state = None _state = None
@ -661,11 +645,8 @@ class SysCapture(object):
self.tmpfile = tmpfile self.tmpfile = tmpfile
def __repr__(self): def __repr__(self):
return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % ( return "<SysCapture {} _old={!r}, tmpfile={!r} _state={!r}>".format(
self.name, self.name, self._old, self.tmpfile, self._state
self._old,
self.tmpfile,
self._state,
) )
def start(self): def start(self):
@ -681,7 +662,7 @@ class SysCapture(object):
def done(self): def done(self):
setattr(sys, self.name, self._old) setattr(sys, self.name, self._old)
del self._old del self._old
_attempt_to_close_capture_file(self.tmpfile) self.tmpfile.close()
self._state = "done" self._state = "done"
def suspend(self): def suspend(self):
@ -707,7 +688,7 @@ class SysCaptureBinary(SysCapture):
return res return res
class DontReadFromInput(six.Iterator): class DontReadFromInput:
"""Temporary stub class. Ideally when stdin is accessed, the """Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured capturing should be turned off, with possibly all data captured
so far sent to the screen. This should be configurable, though, so far sent to the screen. This should be configurable, though,
@ -738,10 +719,7 @@ class DontReadFromInput(six.Iterator):
@property @property
def buffer(self): def buffer(self):
if sys.version_info >= (3, 0): return self
return self
else:
raise AttributeError("redirected stdin has no attribute buffer")
def _colorama_workaround(): def _colorama_workaround():
@ -837,14 +815,3 @@ def _py36_windowsconsoleio_workaround(stream):
sys.stdin = _reopen_stdio(sys.stdin, "rb") sys.stdin = _reopen_stdio(sys.stdin, "rb")
sys.stdout = _reopen_stdio(sys.stdout, "wb") sys.stdout = _reopen_stdio(sys.stdout, "wb")
sys.stderr = _reopen_stdio(sys.stderr, "wb") sys.stderr = _reopen_stdio(sys.stderr, "wb")
def _attempt_to_close_capture_file(f):
"""Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
if six.PY2:
try:
f.close()
except IOError:
pass
else:
f.close()

View File

@ -1,64 +1,28 @@
# -*- coding: utf-8 -*-
""" """
python version compatibility code python version compatibility code
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import codecs
import functools import functools
import inspect import inspect
import io
import re import re
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
import py import py
import six
from six import text_type
import _pytest import _pytest
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME from _pytest.outcomes import TEST_OUTCOME
try:
import enum
except ImportError: # pragma: no cover
# Only available in Python 3.4+ or as a backport
enum = None
_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
if _PY3:
from inspect import signature, Parameter as Parameter
else:
from funcsigs import signature, Parameter as Parameter
NOTSET = object() NOTSET = object()
PY35 = sys.version_info[:2] >= (3, 5) MODULE_NOT_FOUND_ERROR = (
PY36 = sys.version_info[:2] >= (3, 6) "ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError"
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError" )
if _PY3:
from collections.abc import MutableMapping as MappingMixin
from collections.abc import Iterable, Mapping, Sequence, Sized
else:
# those raise DeprecationWarnings in Python >=3.7
from collections import MutableMapping as MappingMixin # noqa
from collections import Iterable, Mapping, Sequence, Sized # noqa
if sys.version_info >= (3, 4):
from importlib.util import spec_from_file_location
else:
def spec_from_file_location(*_, **__):
return None
def _format_args(func): def _format_args(func):
@ -184,10 +148,10 @@ def get_default_arg_names(function):
_non_printable_ascii_translate_table = { _non_printable_ascii_translate_table = {
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127) i: "\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
} }
_non_printable_ascii_translate_table.update( _non_printable_ascii_translate_table.update(
{ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"} {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"}
) )
@ -195,75 +159,39 @@ def _translate_non_printable(s):
return s.translate(_non_printable_ascii_translate_table) return s.translate(_non_printable_ascii_translate_table)
if _PY3: STRING_TYPES = bytes, str
STRING_TYPES = bytes, str
UNICODE_TYPES = six.text_type
if PY35:
def _bytes_to_ascii(val): def _bytes_to_ascii(val):
return val.decode("ascii", "backslashreplace") return val.decode("ascii", "backslashreplace")
def ascii_escaped(val):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
and escapes unicode objects into a sequence of escaped unicode
ids, e.g.:
'4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
note:
the obvious "v.decode('unicode-escape')" will return
valid utf-8 unicode if it finds them in bytes, but we
want to return escaped bytes for any byte, even if they match
a utf-8 string.
"""
if isinstance(val, bytes):
ret = _bytes_to_ascii(val)
else: else:
ret = val.encode("unicode_escape").decode("ascii")
def _bytes_to_ascii(val): return _translate_non_printable(ret)
if val:
# source: http://goo.gl/bGsnwC
encoded_bytes, _ = codecs.escape_encode(val)
return encoded_bytes.decode("ascii")
else:
# empty bytes crashes codecs.escape_encode (#1087)
return ""
def ascii_escaped(val):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
and escapes unicode objects into a sequence of escaped unicode
ids, e.g.:
'4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
note:
the obvious "v.decode('unicode-escape')" will return
valid utf-8 unicode if it finds them in bytes, but we
want to return escaped bytes for any byte, even if they match
a utf-8 string.
"""
if isinstance(val, bytes):
ret = _bytes_to_ascii(val)
else:
ret = val.encode("unicode_escape").decode("ascii")
return _translate_non_printable(ret)
else: class _PytestWrapper:
STRING_TYPES = six.string_types
UNICODE_TYPES = six.text_type
def ascii_escaped(val):
"""In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string,
otherwise escape it into its binary form.
If it's a unicode string, change the unicode characters into
unicode escapes.
"""
if isinstance(val, bytes):
try:
ret = val.decode("ascii")
except UnicodeDecodeError:
ret = val.encode("string-escape").decode("ascii")
else:
ret = val.encode("unicode-escape").decode("ascii")
return _translate_non_printable(ret)
class _PytestWrapper(object):
"""Dummy wrapper around a function object for internal use only. """Dummy wrapper around a function object for internal use only.
Used to correctly unwrap the underlying function object Used to correctly unwrap the underlying function object
@ -357,36 +285,6 @@ def safe_isclass(obj):
return False return False
def _is_unittest_unexpected_success_a_failure():
"""Return if the test suite should fail if an @expectedFailure unittest test PASSES.
From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
Changed in version 3.4: Returns False if there were any
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
"""
return sys.version_info >= (3, 4)
if _PY3:
def safe_str(v):
"""returns v as string"""
return str(v)
else:
def safe_str(v):
"""returns v as string, converting to ascii if necessary"""
try:
return str(v)
except UnicodeError:
if not isinstance(v, text_type):
v = text_type(v)
errors = "replace"
return v.encode("utf-8", errors)
COLLECT_FAKEMODULE_ATTRIBUTES = ( COLLECT_FAKEMODULE_ATTRIBUTES = (
"Collector", "Collector",
"Module", "Module",
@ -410,30 +308,15 @@ def _setup_collect_fakemodule():
setattr(pytest.collect, attr, getattr(pytest, attr)) setattr(pytest.collect, attr, getattr(pytest, attr))
if _PY2: class CaptureIO(io.TextIOWrapper):
# Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO. def __init__(self):
from py.io import TextIO super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
class CaptureIO(TextIO): def getvalue(self):
@property return self.buffer.getvalue().decode("UTF-8")
def encoding(self):
return getattr(self, "_encoding", "UTF-8")
else: class FuncargnamesCompatAttr:
import io
class CaptureIO(io.TextIOWrapper):
def __init__(self):
super(CaptureIO, self).__init__(
io.BytesIO(), encoding="UTF-8", newline="", write_through=True
)
def getvalue(self):
return self.buffer.getvalue().decode("UTF-8")
class FuncargnamesCompatAttr(object):
""" helper class so that Metafunc, Function and FixtureRequest """ helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute. don't need to each define the "funcargnames" compatibility attribute.
""" """
@ -442,16 +325,3 @@ class FuncargnamesCompatAttr(object):
def funcargnames(self): def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility""" """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
return self.fixturenames return self.fixturenames
if six.PY2:
def lru_cache(*_, **__):
def dec(fn):
return fn
return dec
else:
from functools import lru_cache # noqa: F401

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" command line options, ini-file and conftest.py processing. """ """ command line options, ini-file and conftest.py processing. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse import argparse
import copy import copy
import inspect import inspect
@ -12,10 +7,10 @@ import shlex
import sys import sys
import types import types
import warnings import warnings
from functools import lru_cache
import importlib_metadata import importlib_metadata
import py import py
import six
from packaging.version import Version from packaging.version import Version
from pluggy import HookimplMarker from pluggy import HookimplMarker
from pluggy import HookspecMarker from pluggy import HookspecMarker
@ -31,8 +26,6 @@ from .findpaths import exists
from _pytest import deprecated from _pytest import deprecated
from _pytest._code import ExceptionInfo from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback from _pytest._code import filter_traceback
from _pytest.compat import lru_cache
from _pytest.compat import safe_str
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestConfigWarning
@ -73,7 +66,7 @@ def main(args=None, plugins=None):
if exc_info.traceback if exc_info.traceback
else exc_info.exconly() else exc_info.exconly()
) )
formatted_tb = safe_str(exc_repr) formatted_tb = str(exc_repr)
for line in formatted_tb.splitlines(): for line in formatted_tb.splitlines():
tw.line(line.rstrip(), red=True) tw.line(line.rstrip(), red=True)
return 4 return 4
@ -89,7 +82,7 @@ def main(args=None, plugins=None):
return EXIT_USAGEERROR return EXIT_USAGEERROR
class cmdline(object): # compatibility namespace class cmdline: # compatibility namespace
main = staticmethod(main) main = staticmethod(main)
@ -195,7 +188,7 @@ def _prepareconfig(args=None, plugins=None):
try: try:
if plugins: if plugins:
for plugin in plugins: for plugin in plugins:
if isinstance(plugin, six.string_types): if isinstance(plugin, str):
pluginmanager.consider_pluginarg(plugin) pluginmanager.consider_pluginarg(plugin)
else: else:
pluginmanager.register(plugin) pluginmanager.register(plugin)
@ -222,7 +215,7 @@ class PytestPluginManager(PluginManager):
""" """
def __init__(self): def __init__(self):
super(PytestPluginManager, self).__init__("pytest") super().__init__("pytest")
self._conftest_plugins = set() self._conftest_plugins = set()
# state related to local conftest plugins # state related to local conftest plugins
@ -270,7 +263,7 @@ class PytestPluginManager(PluginManager):
return return
method = getattr(plugin, name) method = getattr(plugin, name)
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) opts = super().parse_hookimpl_opts(plugin, name)
# consider only actual functions for hooks (#3775) # consider only actual functions for hooks (#3775)
if not inspect.isroutine(method): if not inspect.isroutine(method):
@ -289,9 +282,7 @@ class PytestPluginManager(PluginManager):
return opts return opts
def parse_hookspec_opts(self, module_or_class, name): def parse_hookspec_opts(self, module_or_class, name):
opts = super(PytestPluginManager, self).parse_hookspec_opts( opts = super().parse_hookspec_opts(module_or_class, name)
module_or_class, name
)
if opts is None: if opts is None:
method = getattr(module_or_class, name) method = getattr(module_or_class, name)
@ -318,7 +309,7 @@ class PytestPluginManager(PluginManager):
) )
) )
return return
ret = super(PytestPluginManager, self).register(plugin, name) ret = super().register(plugin, name)
if ret: if ret:
self.hook.pytest_plugin_registered.call_historic( self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self) kwargs=dict(plugin=plugin, manager=self)
@ -403,12 +394,6 @@ class PytestPluginManager(PluginManager):
else: else:
directory = path directory = path
if six.PY2: # py2 is not using lru_cache.
try:
return self._dirpath2confmods[directory]
except KeyError:
pass
# XXX these days we may rather want to use config.rootdir # XXX these days we may rather want to use config.rootdir
# and allow users to opt into looking into the rootdir parent # and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir # directories instead of requiring to specify confcutdir
@ -485,7 +470,7 @@ class PytestPluginManager(PluginManager):
while i < n: while i < n:
opt = args[i] opt = args[i]
i += 1 i += 1
if isinstance(opt, six.string_types): if isinstance(opt, str):
if opt == "-p": if opt == "-p":
try: try:
parg = args[i] parg = args[i]
@ -546,7 +531,7 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their # "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the # basename for historic purposes but must be imported with the
# _pytest prefix. # _pytest prefix.
assert isinstance(modname, six.string_types), ( assert isinstance(modname, str), (
"module name as text required, got %r" % modname "module name as text required, got %r" % modname
) )
modname = str(modname) modname = str(modname)
@ -564,20 +549,19 @@ class PytestPluginManager(PluginManager):
try: try:
__import__(importspec) __import__(importspec)
except ImportError as e: except ImportError as e:
new_exc_message = 'Error importing plugin "%s": %s' % ( new_exc_message = 'Error importing plugin "{}": {}'.format(
modname, modname, str(e.args[0])
safe_str(e.args[0]),
) )
new_exc = ImportError(new_exc_message) new_exc = ImportError(new_exc_message)
tb = sys.exc_info()[2] tb = sys.exc_info()[2]
six.reraise(ImportError, new_exc, tb) raise new_exc.with_traceback(tb)
except Skipped as e: except Skipped as e:
from _pytest.warnings import _issue_warning_captured from _pytest.warnings import _issue_warning_captured
_issue_warning_captured( _issue_warning_captured(
PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)), PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
self.hook, self.hook,
stacklevel=1, stacklevel=1,
) )
@ -595,7 +579,7 @@ def _get_plugin_specs_as_list(specs):
empty list is returned. empty list is returned.
""" """
if specs is not None and not isinstance(specs, types.ModuleType): if specs is not None and not isinstance(specs, types.ModuleType):
if isinstance(specs, six.string_types): if isinstance(specs, str):
specs = specs.split(",") if specs else [] specs = specs.split(",") if specs else []
if not isinstance(specs, (list, tuple)): if not isinstance(specs, (list, tuple)):
raise UsageError( raise UsageError(
@ -613,7 +597,7 @@ def _ensure_removed_sysmodule(modname):
pass pass
class Notset(object): class Notset:
def __repr__(self): def __repr__(self):
return "<NOTSET>" return "<NOTSET>"
@ -633,7 +617,7 @@ def _iter_rewritable_modules(package_files):
yield package_name yield package_name
class Config(object): class Config:
""" access to configuration values, pluginmanager and plugin hooks. """ """ access to configuration values, pluginmanager and plugin hooks. """
def __init__(self, pluginmanager): def __init__(self, pluginmanager):
@ -644,7 +628,7 @@ class Config(object):
_a = FILE_OR_DIR _a = FILE_OR_DIR
self._parser = Parser( self._parser = Parser(
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a),
processopt=self._processopt, processopt=self._processopt,
) )
#: a pluginmanager instance #: a pluginmanager instance
@ -932,7 +916,7 @@ class Config(object):
try: try:
description, type, default = self._parser._inidict[name] description, type, default = self._parser._inidict[name]
except KeyError: except KeyError:
raise ValueError("unknown configuration value: %r" % (name,)) raise ValueError("unknown configuration value: {!r}".format(name))
value = self._get_override_ini_value(name) value = self._get_override_ini_value(name)
if value is None: if value is None:
try: try:
@ -1009,8 +993,8 @@ class Config(object):
if skip: if skip:
import pytest import pytest
pytest.skip("no %r option found" % (name,)) pytest.skip("no {!r} option found".format(name))
raise ValueError("no option named %r" % (name,)) raise ValueError("no option named {!r}".format(name))
def getvalue(self, name, path=None): def getvalue(self, name, path=None):
""" (deprecated, use getoption()) """ """ (deprecated, use getoption()) """
@ -1098,4 +1082,4 @@ def _strtobool(val):
elif val in ("n", "no", "f", "false", "off", "0"): elif val in ("n", "no", "f", "false", "off", "0"):
return 0 return 0
else: else:
raise ValueError("invalid truth value %r" % (val,)) raise ValueError("invalid truth value {!r}".format(val))

View File

@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
import argparse import argparse
import warnings import warnings
import py import py
import six
from _pytest.config.exceptions import UsageError from _pytest.config.exceptions import UsageError
FILE_OR_DIR = "file_or_dir" FILE_OR_DIR = "file_or_dir"
class Parser(object): class Parser:
""" Parser for command line arguments and ini-file values. """ Parser for command line arguments and ini-file values.
:ivar extra_info: dict of generic param -> value to display in case :ivar extra_info: dict of generic param -> value to display in case
@ -145,12 +143,12 @@ class ArgumentError(Exception):
def __str__(self): def __str__(self):
if self.option_id: if self.option_id:
return "option %s: %s" % (self.option_id, self.msg) return "option {}: {}".format(self.option_id, self.msg)
else: else:
return self.msg return self.msg
class Argument(object): class Argument:
"""class that mimics the necessary behaviour of optparse.Option """class that mimics the necessary behaviour of optparse.Option
it's currently a least effort implementation it's currently a least effort implementation
@ -179,7 +177,7 @@ class Argument(object):
pass pass
else: else:
# this might raise a keyerror as well, don't want to catch that # this might raise a keyerror as well, don't want to catch that
if isinstance(typ, six.string_types): if isinstance(typ, str):
if typ == "choice": if typ == "choice":
warnings.warn( warnings.warn(
"`type` argument to addoption() is the string %r." "`type` argument to addoption() is the string %r."
@ -282,7 +280,7 @@ class Argument(object):
return "Argument({})".format(", ".join(args)) return "Argument({})".format(", ".join(args))
class OptionGroup(object): class OptionGroup:
def __init__(self, name, description="", parser=None): def __init__(self, name, description="", parser=None):
self.name = name self.name = name
self.description = description self.description = description
@ -337,10 +335,10 @@ class MyOptionParser(argparse.ArgumentParser):
def error(self, message): def error(self, message):
"""Transform argparse error message into UsageError.""" """Transform argparse error message into UsageError."""
msg = "%s: error: %s" % (self.prog, message) msg = "{}: error: {}".format(self.prog, message)
if hasattr(self._parser, "_config_source_hint"): if hasattr(self._parser, "_config_source_hint"):
msg = "%s (%s)" % (msg, self._parser._config_source_hint) msg = "{} ({})".format(msg, self._parser._config_source_hint)
raise UsageError(self.format_usage() + msg) raise UsageError(self.format_usage() + msg)
@ -352,7 +350,7 @@ class MyOptionParser(argparse.ArgumentParser):
if arg and arg[0] == "-": if arg and arg[0] == "-":
lines = ["unrecognized arguments: %s" % (" ".join(argv))] lines = ["unrecognized arguments: %s" % (" ".join(argv))]
for k, v in sorted(self.extra_info.items()): for k, v in sorted(self.extra_info.items()):
lines.append(" %s: %s" % (k, v)) lines.append(" {}: {}".format(k, v))
self.error("\n".join(lines)) self.error("\n".join(lines))
getattr(args, FILE_OR_DIR).extend(argv) getattr(args, FILE_OR_DIR).extend(argv)
return args return args

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
class UsageError(Exception): class UsageError(Exception):
""" error in pytest usage or invocation""" """ error in pytest usage or invocation"""

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import os import os
import py import py

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" interactive debugging with PDB, the Python Debugger. """ """ interactive debugging with PDB, the Python Debugger. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse import argparse
import pdb import pdb
import sys import sys
@ -74,7 +69,7 @@ def pytest_configure(config):
config._cleanup.append(fin) config._cleanup.append(fin)
class pytestPDB(object): class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """ """ Pseudo PDB that defers to the real pdb. """
_pluginmanager = None _pluginmanager = None
@ -128,18 +123,18 @@ class pytestPDB(object):
def _get_pdb_wrapper_class(cls, pdb_cls, capman): def _get_pdb_wrapper_class(cls, pdb_cls, capman):
import _pytest.config import _pytest.config
class PytestPdbWrapper(pdb_cls, object): class PytestPdbWrapper(pdb_cls):
_pytest_capman = capman _pytest_capman = capman
_continued = False _continued = False
def do_debug(self, arg): def do_debug(self, arg):
cls._recursive_debug += 1 cls._recursive_debug += 1
ret = super(PytestPdbWrapper, self).do_debug(arg) ret = super().do_debug(arg)
cls._recursive_debug -= 1 cls._recursive_debug -= 1
return ret return ret
def do_continue(self, arg): def do_continue(self, arg):
ret = super(PytestPdbWrapper, self).do_continue(arg) ret = super().do_continue(arg)
if cls._recursive_debug == 0: if cls._recursive_debug == 0:
tw = _pytest.config.create_terminal_writer(cls._config) tw = _pytest.config.create_terminal_writer(cls._config)
tw.line() tw.line()
@ -171,7 +166,7 @@ class pytestPDB(object):
could be handled, but this would require to wrap the could be handled, but this would require to wrap the
whole pytest run, and adjust the report etc. whole pytest run, and adjust the report etc.
""" """
ret = super(PytestPdbWrapper, self).do_quit(arg) ret = super().do_quit(arg)
if cls._recursive_debug == 0: if cls._recursive_debug == 0:
outcomes.exit("Quitting debugger") outcomes.exit("Quitting debugger")
@ -187,7 +182,7 @@ class pytestPDB(object):
Needed after do_continue resumed, and entering another Needed after do_continue resumed, and entering another
breakpoint again. breakpoint again.
""" """
ret = super(PytestPdbWrapper, self).setup(f, tb) ret = super().setup(f, tb)
if not ret and self._continued: if not ret and self._continued:
# pdb.setup() returns True if the command wants to exit # pdb.setup() returns True if the command wants to exit
# from the interaction: do not suspend capturing then. # from the interaction: do not suspend capturing then.
@ -196,7 +191,7 @@ class pytestPDB(object):
return ret return ret
def get_stack(self, f, t): def get_stack(self, f, t):
stack, i = super(PytestPdbWrapper, self).get_stack(f, t) stack, i = super().get_stack(f, t)
if f is None: if f is None:
# Find last non-hidden frame. # Find last non-hidden frame.
i = max(0, len(stack) - 1) i = max(0, len(stack) - 1)
@ -230,7 +225,7 @@ class pytestPDB(object):
else: else:
capturing = cls._is_capturing(capman) capturing = cls._is_capturing(capman)
if capturing == "global": if capturing == "global":
tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,)) tw.sep(">", "PDB {} (IO-capturing turned off)".format(method))
elif capturing: elif capturing:
tw.sep( tw.sep(
">", ">",
@ -238,7 +233,7 @@ class pytestPDB(object):
% (method, capturing), % (method, capturing),
) )
else: else:
tw.sep(">", "PDB %s" % (method,)) tw.sep(">", "PDB {}".format(method))
_pdb = cls._import_pdb_cls(capman)(**kwargs) _pdb = cls._import_pdb_cls(capman)(**kwargs)
@ -254,7 +249,7 @@ class pytestPDB(object):
_pdb.set_trace(frame) _pdb.set_trace(frame)
class PdbInvoke(object): class PdbInvoke:
def pytest_exception_interact(self, node, call, report): def pytest_exception_interact(self, node, call, report):
capman = node.config.pluginmanager.getplugin("capturemanager") capman = node.config.pluginmanager.getplugin("capturemanager")
if capman: if capman:
@ -269,7 +264,7 @@ class PdbInvoke(object):
post_mortem(tb) post_mortem(tb)
class PdbTrace(object): class PdbTrace:
@hookimpl(hookwrapper=True) @hookimpl(hookwrapper=True)
def pytest_pyfunc_call(self, pyfuncitem): def pytest_pyfunc_call(self, pyfuncitem):
_test_pytest_function(pyfuncitem) _test_pytest_function(pyfuncitem)

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
This module contains deprecation messages and bits of code used elsewhere in the codebase This module contains deprecation messages and bits of code used elsewhere in the codebase
that is planned to be removed in the next pytest release. that is planned to be removed in the next pytest release.
@ -9,10 +8,6 @@ be removed when the time comes.
All constants defined in this module should be either PytestWarning instances or UnformattedWarning All constants defined in this module should be either PytestWarning instances or UnformattedWarning
in case of warnings which need to format their messages. in case of warnings which need to format their messages.
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import RemovedInPytest4Warning from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warning_types import UnformattedWarning from _pytest.warning_types import UnformattedWarning

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" discover and run doctests in modules and test files.""" """ discover and run doctests in modules and test files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import inspect import inspect
import platform import platform
import sys import sys
@ -126,7 +121,7 @@ class ReprFailDoctest(TerminalRepr):
class MultipleDoctestFailures(Exception): class MultipleDoctestFailures(Exception):
def __init__(self, failures): def __init__(self, failures):
super(MultipleDoctestFailures, self).__init__() super().__init__()
self.failures = failures self.failures = failures
@ -181,7 +176,7 @@ def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=T
class DoctestItem(pytest.Item): class DoctestItem(pytest.Item):
def __init__(self, name, parent, runner=None, dtest=None): def __init__(self, name, parent, runner=None, dtest=None):
super(DoctestItem, self).__init__(name, parent) super().__init__(name, parent)
self.runner = runner self.runner = runner
self.dtest = dtest self.dtest = dtest
self.obj = None self.obj = None
@ -258,7 +253,7 @@ class DoctestItem(pytest.Item):
] ]
indent = ">>>" indent = ">>>"
for line in example.source.splitlines(): for line in example.source.splitlines():
lines.append("??? %s %s" % (indent, line)) lines.append("??? {} {}".format(indent, line))
indent = "..." indent = "..."
if isinstance(failure, doctest.DocTestFailure): if isinstance(failure, doctest.DocTestFailure):
lines += checker.output_difference( lines += checker.output_difference(
@ -271,7 +266,7 @@ class DoctestItem(pytest.Item):
reprlocation_lines.append((reprlocation, lines)) reprlocation_lines.append((reprlocation, lines))
return ReprFailDoctest(reprlocation_lines) return ReprFailDoctest(reprlocation_lines)
else: else:
return super(DoctestItem, self).repr_failure(excinfo) return super().repr_failure(excinfo)
def reportinfo(self): def reportinfo(self):
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
@ -333,7 +328,6 @@ class DoctestTextfile(pytest.Module):
checker=_get_checker(), checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config), continue_on_failure=_get_continue_on_failure(self.config),
) )
_fix_spoof_python2(runner, encoding)
parser = doctest.DocTestParser() parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0) test = parser.get_doctest(text, globs, name, filename, 0)
@ -539,32 +533,6 @@ def _get_report_choice(key):
}[key] }[key]
def _fix_spoof_python2(runner, encoding):
"""
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
should patch only doctests for text files because they don't have a way to declare their
encoding. Doctests in docstrings from Python modules don't have the same problem given that
Python already decoded the strings.
This fixes the problem related in issue #2434.
"""
from _pytest.compat import _PY2
if not _PY2:
return
from doctest import _SpoofOut
class UnicodeSpoof(_SpoofOut):
def getvalue(self):
result = _SpoofOut.getvalue(self)
if encoding and isinstance(result, bytes):
result = result.decode(encoding)
return result
runner._fakeout = UnicodeSpoof()
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def doctest_namespace(): def doctest_namespace():
""" """

View File

@ -1,8 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import functools import functools
import inspect import inspect
import itertools import itertools
@ -14,7 +9,6 @@ from collections import OrderedDict
import attr import attr
import py import py
import six
import _pytest import _pytest
from _pytest import nodes from _pytest import nodes
@ -41,7 +35,7 @@ from _pytest.outcomes import TEST_OUTCOME
@attr.s(frozen=True) @attr.s(frozen=True)
class PseudoFixtureDef(object): class PseudoFixtureDef:
cached_result = attr.ib() cached_result = attr.ib()
scope = attr.ib() scope = attr.ib()
@ -81,7 +75,7 @@ def scopeproperty(name=None, doc=None):
if func.__name__ in scope2props[self.scope]: if func.__name__ in scope2props[self.scope]:
return func(self) return func(self)
raise AttributeError( raise AttributeError(
"%s not available in %s-scoped context" % (scopename, self.scope) "{} not available in {}-scoped context".format(scopename, self.scope)
) )
return property(provide, None, None, func.__doc__) return property(provide, None, None, func.__doc__)
@ -94,7 +88,7 @@ def get_scope_package(node, fixturedef):
cls = pytest.Package cls = pytest.Package
current = node current = node
fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py") fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
while current and ( while current and (
type(current) is not cls or fixture_package_name != current.nodeid type(current) is not cls or fixture_package_name != current.nodeid
): ):
@ -301,7 +295,7 @@ def get_direct_param_fixture_func(request):
@attr.s(slots=True) @attr.s(slots=True)
class FuncFixtureInfo(object): class FuncFixtureInfo:
# original function argument names # original function argument names
argnames = attr.ib(type=tuple) argnames = attr.ib(type=tuple)
# argnames that function immediately requires. These include argnames + # argnames that function immediately requires. These include argnames +
@ -657,7 +651,7 @@ class SubRequest(FixtureRequest):
self._fixturemanager = request._fixturemanager self._fixturemanager = request._fixturemanager
def __repr__(self): def __repr__(self):
return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem) return "<SubRequest {!r} for {!r}>".format(self.fixturename, self._pyfuncitem)
def addfinalizer(self, finalizer): def addfinalizer(self, finalizer):
self._fixturedef.addfinalizer(finalizer) self._fixturedef.addfinalizer(finalizer)
@ -670,7 +664,7 @@ class SubRequest(FixtureRequest):
fixturedef.addfinalizer( fixturedef.addfinalizer(
functools.partial(self._fixturedef.finish, request=self) functools.partial(self._fixturedef.finish, request=self)
) )
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest) super()._schedule_finalizers(fixturedef, subrequest)
scopes = "session package module class function".split() scopes = "session package module class function".split()
@ -723,7 +717,7 @@ class FixtureLookupError(LookupError):
error_msg = "file %s, line %s: source code not available" error_msg = "file %s, line %s: source code not available"
addline(error_msg % (fspath, lineno + 1)) addline(error_msg % (fspath, lineno + 1))
else: else:
addline("file %s, line %s" % (fspath, lineno + 1)) addline("file {}, line {}".format(fspath, lineno + 1))
for i, line in enumerate(lines): for i, line in enumerate(lines):
line = line.rstrip() line = line.rstrip()
addline(" " + line) addline(" " + line)
@ -779,7 +773,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
def fail_fixturefunc(fixturefunc, msg): def fail_fixturefunc(fixturefunc, msg):
fs, lineno = getfslineno(fixturefunc) fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno + 1) location = "{}:{}".format(fs, lineno + 1)
source = _pytest._code.Source(fixturefunc) source = _pytest._code.Source(fixturefunc)
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
@ -809,7 +803,7 @@ def _teardown_yield_fixture(fixturefunc, it):
) )
class FixtureDef(object): class FixtureDef:
""" A container for a factory definition. """ """ A container for a factory definition. """
def __init__( def __init__(
@ -853,10 +847,10 @@ class FixtureDef(object):
except: # noqa except: # noqa
exceptions.append(sys.exc_info()) exceptions.append(sys.exc_info())
if exceptions: if exceptions:
e = exceptions[0] _, val, tb = exceptions[0]
# Ensure to not keep frame references through traceback. # Ensure to not keep frame references through traceback.
del exceptions del exceptions
six.reraise(*e) raise val.with_traceback(tb)
finally: finally:
hook = self._fixturemanager.session.gethookproxy(request.node.fspath) hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request) hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
@ -882,7 +876,8 @@ class FixtureDef(object):
result, cache_key, err = cached_result result, cache_key, err = cached_result
if my_cache_key == cache_key: if my_cache_key == cache_key:
if err is not None: if err is not None:
six.reraise(*err) _, val, tb = err
raise val.with_traceback(tb)
else: else:
return result return result
# we have a previous but differently parametrized fixture instance # we have a previous but differently parametrized fixture instance
@ -894,10 +889,8 @@ class FixtureDef(object):
return hook.pytest_fixture_setup(fixturedef=self, request=request) return hook.pytest_fixture_setup(fixturedef=self, request=request)
def __repr__(self): def __repr__(self):
return "<FixtureDef argname=%r scope=%r baseid=%r>" % ( return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
self.argname, self.argname, self.scope, self.baseid
self.scope,
self.baseid,
) )
@ -957,7 +950,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
name=fixture_marker.name or function.__name__ name=fixture_marker.name or function.__name__
) )
@six.wraps(function) @functools.wraps(function)
def result(*args, **kwargs): def result(*args, **kwargs):
fail(message, pytrace=False) fail(message, pytrace=False)
@ -969,7 +962,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
@attr.s(frozen=True) @attr.s(frozen=True)
class FixtureFunctionMarker(object): class FixtureFunctionMarker:
scope = attr.ib() scope = attr.ib()
params = attr.ib(converter=attr.converters.optional(tuple)) params = attr.ib(converter=attr.converters.optional(tuple))
autouse = attr.ib(default=False) autouse = attr.ib(default=False)
@ -1083,7 +1076,7 @@ def pytest_addoption(parser):
) )
class FixtureManager(object): class FixtureManager:
""" """
pytest fixtures definitions and information is stored and managed pytest fixtures definitions and information is stored and managed
from this class. from this class.
@ -1303,11 +1296,7 @@ class FixtureManager(object):
# during fixture definition we wrap the original fixture function # during fixture definition we wrap the original fixture function
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning # to issue a warning if called directly, so here we unwrap it in order to not emit the warning
# when pytest itself calls the fixture function # when pytest itself calls the fixture function
if six.PY2 and unittest: obj = get_real_method(obj, holderobj)
# hack on Python 2 because of the unbound methods
obj = get_real_func(obj)
else:
obj = get_real_method(obj, holderobj)
fixture_def = FixtureDef( fixture_def = FixtureDef(
self, self,

View File

@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
""" """
Provides a function to report all internal modules for using freezing tools Provides a function to report all internal modules for using freezing tools
pytest pytest
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
def freeze_includes(): def freeze_includes():

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" version info, help messages, tracing configuration. """ """ version info, help messages, tracing configuration. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os import os
import sys import sys
from argparse import Action from argparse import Action
@ -24,7 +19,7 @@ class HelpAction(Action):
""" """
def __init__(self, option_strings, dest=None, default=False, help=None): def __init__(self, option_strings, dest=None, default=False, help=None):
super(HelpAction, self).__init__( super().__init__(
option_strings=option_strings, option_strings=option_strings,
dest=dest, dest=dest,
const=True, const=True,
@ -122,7 +117,7 @@ def pytest_cmdline_parse():
def showversion(config): def showversion(config):
p = py.path.local(pytest.__file__) p = py.path.local(pytest.__file__)
sys.stderr.write( sys.stderr.write(
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p) "This is pytest version {}, imported from {}\n".format(pytest.__version__, p)
) )
plugininfo = getpluginversioninfo(config) plugininfo = getpluginversioninfo(config)
if plugininfo: if plugininfo:
@ -160,7 +155,7 @@ def showhelp(config):
help, type, default = config._parser._inidict[name] help, type, default = config._parser._inidict[name]
if type is None: if type is None:
type = "string" type = "string"
spec = "%s (%s):" % (name, type) spec = "{} ({}):".format(name, type)
tw.write(" %s" % spec) tw.write(" %s" % spec)
spec_len = len(spec) spec_len = len(spec)
if spec_len > (indent_len - 3): if spec_len > (indent_len - 3):
@ -194,7 +189,7 @@ def showhelp(config):
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"), ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
] ]
for name, help in vars: for name, help in vars:
tw.line(" %-24s %s" % (name, help)) tw.line(" {:<24} {}".format(name, help))
tw.line() tw.line()
tw.line() tw.line()
@ -221,7 +216,7 @@ def getpluginversioninfo(config):
lines.append("setuptools registered plugins:") lines.append("setuptools registered plugins:")
for plugin, dist in plugininfo: for plugin, dist in plugininfo:
loc = getattr(plugin, "__file__", repr(plugin)) loc = getattr(plugin, "__file__", repr(plugin))
content = "%s-%s at %s" % (dist.project_name, dist.version, loc) content = "{}-{} at {}".format(dist.project_name, dist.version, loc)
lines.append(" " + content) lines.append(" " + content)
return lines return lines
@ -229,7 +224,9 @@ def getpluginversioninfo(config):
def pytest_report_header(config): def pytest_report_header(config):
lines = [] lines = []
if config.option.debug or config.option.traceconfig: if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" % (pytest.__version__, py.__version__)) lines.append(
"using: pytest-{} pylib-{}".format(pytest.__version__, py.__version__)
)
verinfo = getpluginversioninfo(config) verinfo = getpluginversioninfo(config)
if verinfo: if verinfo:
@ -243,5 +240,5 @@ def pytest_report_header(config):
r = plugin.__file__ r = plugin.__file__
else: else:
r = repr(plugin) r = repr(plugin)
lines.append(" %-20s: %s" % (name, r)) lines.append(" {:<20}: {}".format(name, r))
return lines return lines

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from pluggy import HookspecMarker from pluggy import HookspecMarker

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
report test results in JUnit-XML format, report test results in JUnit-XML format,
for use with Jenkins and build integration servers. for use with Jenkins and build integration servers.
@ -9,10 +8,6 @@ Based on initial code from Ross Lawley.
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
""" """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import functools import functools
import os import os
import re import re
@ -20,16 +15,11 @@ import sys
import time import time
import py import py
import six
import pytest import pytest
from _pytest import nodes from _pytest import nodes
from _pytest.config import filename_arg from _pytest.config import filename_arg
# Python 2.X and 3.X compatibility
if sys.version_info[0] < 3:
from codecs import open
class Junit(py.xml.Namespace): class Junit(py.xml.Namespace):
pass pass
@ -43,12 +33,12 @@ class Junit(py.xml.Namespace):
_legal_chars = (0x09, 0x0A, 0x0D) _legal_chars = (0x09, 0x0A, 0x0D)
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF)) _legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
_legal_xml_re = [ _legal_xml_re = [
u"%s-%s" % (six.unichr(low), six.unichr(high)) "{}-{}".format(chr(low), chr(high))
for (low, high) in _legal_ranges for (low, high) in _legal_ranges
if low < sys.maxunicode if low < sys.maxunicode
] ]
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re _legal_xml_re = [chr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re)) illegal_xml_re = re.compile("[^%s]" % "".join(_legal_xml_re))
del _legal_chars del _legal_chars
del _legal_ranges del _legal_ranges
del _legal_xml_re del _legal_xml_re
@ -60,9 +50,9 @@ def bin_xml_escape(arg):
def repl(matchobj): def repl(matchobj):
i = ord(matchobj.group()) i = ord(matchobj.group())
if i <= 0xFF: if i <= 0xFF:
return u"#x%02X" % i return "#x%02X" % i
else: else:
return u"#x%04X" % i return "#x%04X" % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
@ -89,7 +79,7 @@ merge_family(families["xunit1"], families["_base_legacy"])
families["xunit2"] = families["_base"] families["xunit2"] = families["_base"]
class _NodeReporter(object): class _NodeReporter:
def __init__(self, nodeid, xml): def __init__(self, nodeid, xml):
self.id = nodeid self.id = nodeid
self.xml = xml self.xml = xml
@ -229,7 +219,7 @@ class _NodeReporter(object):
else: else:
if hasattr(report.longrepr, "reprcrash"): if hasattr(report.longrepr, "reprcrash"):
message = report.longrepr.reprcrash.message message = report.longrepr.reprcrash.message
elif isinstance(report.longrepr, six.string_types): elif isinstance(report.longrepr, str):
message = report.longrepr message = report.longrepr
else: else:
message = str(report.longrepr) message = str(report.longrepr)
@ -268,7 +258,7 @@ class _NodeReporter(object):
filename, lineno, skipreason = report.longrepr filename, lineno, skipreason = report.longrepr
if skipreason.startswith("Skipped: "): if skipreason.startswith("Skipped: "):
skipreason = skipreason[9:] skipreason = skipreason[9:]
details = "%s:%s: %s" % (filename, lineno, skipreason) details = "{}:{}: {}".format(filename, lineno, skipreason)
self.append( self.append(
Junit.skipped( Junit.skipped(
@ -353,7 +343,7 @@ def _check_record_param_type(param, v):
"""Used by record_testsuite_property to check that the given parameter name is of the proper """Used by record_testsuite_property to check that the given parameter name is of the proper
type""" type"""
__tracebackhide__ = True __tracebackhide__ = True
if not isinstance(v, six.string_types): if not isinstance(v, str):
msg = "{param} parameter needs to be a string, but {g} given" msg = "{param} parameter needs to be a string, but {g} given"
raise TypeError(msg.format(param=param, g=type(v).__name__)) raise TypeError(msg.format(param=param, g=type(v).__name__))
@ -473,7 +463,7 @@ def mangle_test_address(address):
return names return names
class LogXML(object): class LogXML:
def __init__( def __init__(
self, self,
logfile, logfile,

View File

@ -1,15 +1,9 @@
# -*- coding: utf-8 -*-
""" Access and control log capturing. """ """ Access and control log capturing. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging import logging
import re import re
from contextlib import contextmanager from contextlib import contextmanager
import py import py
import six
import pytest import pytest
from _pytest.compat import dummy_context_manager from _pytest.compat import dummy_context_manager
@ -39,14 +33,11 @@ class ColoredLevelFormatter(logging.Formatter):
logging.DEBUG: {"purple"}, logging.DEBUG: {"purple"},
logging.NOTSET: set(), logging.NOTSET: set(),
} }
LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-]?\d*s)") LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*s)")
def __init__(self, terminalwriter, *args, **kwargs): def __init__(self, terminalwriter, *args, **kwargs):
super(ColoredLevelFormatter, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if six.PY2: self._original_fmt = self._style._fmt
self._original_fmt = self._fmt
else:
self._original_fmt = self._style._fmt
self._level_to_fmt_mapping = {} self._level_to_fmt_mapping = {}
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
@ -70,41 +61,35 @@ class ColoredLevelFormatter(logging.Formatter):
def format(self, record): def format(self, record):
fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt) fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
if six.PY2: self._style._fmt = fmt
self._fmt = fmt return super().format(record)
class PercentStyleMultiline(logging.PercentStyle):
"""A logging style with special support for multiline messages.
If the message of a record consists of multiple lines, this style
formats the message as if each line were logged separately.
"""
@staticmethod
def _update_message(record_dict, message):
tmp = record_dict.copy()
tmp["message"] = message
return tmp
def format(self, record):
if "\n" in record.message:
lines = record.message.splitlines()
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
# TODO optimize this by introducing an option that tells the
# logging framework that the indentation doesn't
# change. This allows to compute the indentation only once.
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
lines[0] = formatted
return ("\n" + " " * indentation).join(lines)
else: else:
self._style._fmt = fmt return self._fmt % record.__dict__
return super(ColoredLevelFormatter, self).format(record)
if not six.PY2:
# Formatter classes don't support format styles in PY2
class PercentStyleMultiline(logging.PercentStyle):
"""A logging style with special support for multiline messages.
If the message of a record consists of multiple lines, this style
formats the message as if each line were logged separately.
"""
@staticmethod
def _update_message(record_dict, message):
tmp = record_dict.copy()
tmp["message"] = message
return tmp
def format(self, record):
if "\n" in record.message:
lines = record.message.splitlines()
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
# TODO optimize this by introducing an option that tells the
# logging framework that the indentation doesn't
# change. This allows to compute the indentation only once.
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
lines[0] = formatted
return ("\n" + " " * indentation).join(lines)
else:
return self._fmt % record.__dict__
def get_option_ini(config, *names): def get_option_ini(config, *names):
@ -246,7 +231,7 @@ class LogCaptureHandler(logging.StreamHandler):
self.stream = py.io.TextIO() self.stream = py.io.TextIO()
class LogCaptureFixture(object): class LogCaptureFixture:
"""Provides access and control of log capturing.""" """Provides access and control of log capturing."""
def __init__(self, item): def __init__(self, item):
@ -393,7 +378,7 @@ def get_actual_log_level(config, *setting_names):
else: else:
return return
if isinstance(log_level, six.string_types): if isinstance(log_level, str):
log_level = log_level.upper() log_level = log_level.upper()
try: try:
return int(getattr(logging, log_level, log_level)) return int(getattr(logging, log_level, log_level))
@ -412,7 +397,7 @@ def pytest_configure(config):
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
class LoggingPlugin(object): class LoggingPlugin:
"""Attaches to the logging module and captures log messages for each test. """Attaches to the logging module and captures log messages for each test.
""" """
@ -475,8 +460,7 @@ class LoggingPlugin(object):
else: else:
formatter = logging.Formatter(log_format, log_date_format) formatter = logging.Formatter(log_format, log_date_format)
if not six.PY2: formatter._style = PercentStyleMultiline(formatter._style._fmt)
formatter._style = PercentStyleMultiline(formatter._style._fmt)
return formatter return formatter
def _setup_cli_logging(self): def _setup_cli_logging(self):

View File

@ -1,10 +1,4 @@
# -*- coding: utf-8 -*-
""" core implementation of testing process: init, session, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import contextlib
import fnmatch import fnmatch
import functools import functools
import os import os
@ -14,7 +8,6 @@ import warnings
import attr import attr
import py import py
import six
import _pytest._code import _pytest._code
from _pytest import nodes from _pytest import nodes
@ -172,7 +165,7 @@ def pytest_addoption(parser):
) )
class _ConfigDeprecated(object): class _ConfigDeprecated:
def __init__(self, config): def __init__(self, config):
self.__dict__["_config"] = config self.__dict__["_config"] = config
@ -311,10 +304,7 @@ def pytest_ignore_collect(path, config):
if excludeglobopt: if excludeglobopt:
ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
if any( if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
for glob in ignore_globs
):
return True return True
allow_in_venv = config.getoption("collect_in_virtualenv") allow_in_venv = config.getoption("collect_in_virtualenv")
@ -342,47 +332,7 @@ def pytest_collection_modifyitems(items, config):
items[:] = remaining items[:] = remaining
@contextlib.contextmanager class FSHookProxy:
def _patched_find_module():
"""Patch bug in pkgutil.ImpImporter.find_module
When using pkgutil.find_loader on python<3.4 it removes symlinks
from the path due to a call to os.path.realpath. This is not consistent
with actually doing the import (in these versions, pkgutil and __import__
did not share the same underlying code). This can break conftest
discovery for pytest where symlinks are involved.
The only supported python<3.4 by pytest is python 2.7.
"""
if six.PY2: # python 3.4+ uses importlib instead
def find_module_patched(self, fullname, path=None):
# Note: we ignore 'path' argument since it is only used via meta_path
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
# original: path = [os.path.realpath(self.path)]
path = [self.path]
try:
file, filename, etc = pkgutil.imp.find_module(subname, path)
except ImportError:
return None
return pkgutil.ImpLoader(fullname, file, filename, etc)
old_find_module = pkgutil.ImpImporter.find_module
pkgutil.ImpImporter.find_module = find_module_patched
try:
yield
finally:
pkgutil.ImpImporter.find_module = old_find_module
else:
yield
class FSHookProxy(object):
def __init__(self, fspath, pm, remove_mods): def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath self.fspath = fspath
self.pm = pm self.pm = pm
@ -522,8 +472,8 @@ class Session(nodes.FSCollector):
if self._notfound: if self._notfound:
errors = [] errors = []
for arg, exc in self._notfound: for arg, exc in self._notfound:
line = "(no name %r in any of %r)" % (arg, exc.args[0]) line = "(no name {!r} in any of {!r})".format(arg, exc.args[0])
errors.append("not found: %s\n%s" % (arg, line)) errors.append("not found: {}\n{}".format(arg, line))
# XXX: test this # XXX: test this
raise UsageError(*errors) raise UsageError(*errors)
if not genitems: if not genitems:
@ -540,8 +490,7 @@ class Session(nodes.FSCollector):
self.trace("processing argument", arg) self.trace("processing argument", arg)
self.trace.root.indent += 1 self.trace.root.indent += 1
try: try:
for x in self._collect(arg): yield from self._collect(arg)
yield x
except NoMatch: except NoMatch:
# we are inside a make_report hook so # we are inside a make_report hook so
# we cannot directly pass through the exception # we cannot directly pass through the exception
@ -578,7 +527,7 @@ class Session(nodes.FSCollector):
# If it's a directory argument, recurse and look for any Subpackages. # If it's a directory argument, recurse and look for any Subpackages.
# Let the Package collector deal with subnodes, don't collect here. # Let the Package collector deal with subnodes, don't collect here.
if argpath.check(dir=1): if argpath.check(dir=1):
assert not names, "invalid arg %r" % (arg,) assert not names, "invalid arg {!r}".format(arg)
seen_dirs = set() seen_dirs = set()
for path in argpath.visit( for path in argpath.visit(
@ -623,15 +572,13 @@ class Session(nodes.FSCollector):
if argpath.basename == "__init__.py": if argpath.basename == "__init__.py":
yield next(m[0].collect()) yield next(m[0].collect())
return return
for y in m: yield from m
yield y
def _collectfile(self, path, handle_dupes=True): def _collectfile(self, path, handle_dupes=True):
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % ( assert (
path, path.isfile()
path.isdir(), ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
path.exists(), path, path.isdir(), path.exists(), path.islink()
path.islink(),
) )
ihook = self.gethookproxy(path) ihook = self.gethookproxy(path)
if not self.isinitpath(path): if not self.isinitpath(path):
@ -662,23 +609,14 @@ class Session(nodes.FSCollector):
ihook.pytest_collect_directory(path=dirpath, parent=self) ihook.pytest_collect_directory(path=dirpath, parent=self)
return True return True
if six.PY2: @staticmethod
def _visit_filter(f):
@staticmethod return f.check(file=1)
def _visit_filter(f):
return f.check(file=1) and not f.strpath.endswith("*.pyc")
else:
@staticmethod
def _visit_filter(f):
return f.check(file=1)
def _tryconvertpyarg(self, x): def _tryconvertpyarg(self, x):
"""Convert a dotted module name to path.""" """Convert a dotted module name to path."""
try: try:
with _patched_find_module(): loader = pkgutil.find_loader(x)
loader = pkgutil.find_loader(x)
except ImportError: except ImportError:
return x return x
if loader is None: if loader is None:
@ -686,8 +624,7 @@ class Session(nodes.FSCollector):
# This method is sometimes invoked when AssertionRewritingHook, which # This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place: # does not define a get_filename method, is already in place:
try: try:
with _patched_find_module(): path = loader.get_filename(x)
path = loader.get_filename(x)
except AttributeError: except AttributeError:
# Retrieve path from AssertionRewritingHook: # Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename path = loader.modules[x][0].co_filename
@ -769,6 +706,5 @@ class Session(nodes.FSCollector):
rep = collect_one_node(node) rep = collect_one_node(node)
if rep.passed: if rep.passed:
for subnode in rep.result: for subnode in rep.result:
for x in self.genitems(subnode): yield from self.genitems(subnode)
yield x
node.ihook.pytest_collectreport(report=rep) node.ihook.pytest_collectreport(report=rep)

View File

@ -1,9 +1,4 @@
# -*- coding: utf-8 -*-
""" generic mechanism for marking and selecting python functions. """ """ generic mechanism for marking and selecting python functions. """
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from .legacy import matchkeyword from .legacy import matchkeyword
from .legacy import matchmark from .legacy import matchmark
from .structures import EMPTY_PARAMETERSET_OPTION from .structures import EMPTY_PARAMETERSET_OPTION

Some files were not shown because too many files have changed in this diff Show More