commit
4f57d40a43
|
@ -7,17 +7,18 @@ repos:
|
|||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.5.0
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==19.3b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.2.2
|
||||
rev: v2.2.3
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: fix-encoding-pragma
|
||||
args: [--remove]
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
exclude: _pytest/debugging.py
|
||||
|
@ -31,14 +32,14 @@ repos:
|
|||
rev: v1.4.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
args: ['--application-directories=.:src', --py3-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.15.0
|
||||
rev: v1.18.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
args: [--py3-plus]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.3.0
|
||||
rev: v1.4.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
|
|
41
.travis.yml
41
.travis.yml
|
@ -19,18 +19,13 @@ install:
|
|||
jobs:
|
||||
include:
|
||||
# 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
|
||||
language: generic
|
||||
# Coverage for:
|
||||
# - 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
|
||||
env: TOXENV=py37-xdist PYTEST_COVERAGE=1
|
||||
before_install:
|
||||
- which python3
|
||||
- python3 -V
|
||||
|
@ -38,20 +33,14 @@ jobs:
|
|||
- python -V
|
||||
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
|
||||
|
||||
# Full run of latest (major) supported versions, without xdist.
|
||||
- env: TOXENV=py27
|
||||
python: '2.7'
|
||||
- env: TOXENV=py37
|
||||
# Full run of latest supported version, without xdist.
|
||||
- env: TOXENV=py37 PYTEST_COVERAGE=1
|
||||
python: '3.7'
|
||||
|
||||
# Coverage tracking is slow with pypy, skip it.
|
||||
- env: TOXENV=pypy-xdist
|
||||
python: 'pypy'
|
||||
- env: TOXENV=pypy3-xdist
|
||||
python: 'pypy3'
|
||||
|
||||
- env: TOXENV=py34-xdist
|
||||
python: '3.4'
|
||||
- env: TOXENV=py35-xdist
|
||||
python: '3.5'
|
||||
|
||||
|
@ -62,12 +51,6 @@ jobs:
|
|||
# Empty PYTEST_ADDOPTS to run this non-verbose.
|
||||
- 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.
|
||||
# Coverage for:
|
||||
# - test_sys_breakpoint_interception (via pexpect).
|
||||
|
@ -81,12 +64,7 @@ jobs:
|
|||
if: type = cron
|
||||
|
||||
- stage: baseline
|
||||
# Coverage for:
|
||||
# - _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
|
||||
env: TOXENV=py36-xdist
|
||||
python: '3.6'
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
|
||||
cache:
|
||||
|
@ -112,9 +90,6 @@ matrix:
|
|||
allow_failures:
|
||||
- python: '3.8-dev'
|
||||
env: TOXENV=py38-xdist
|
||||
# Temporary (https://github.com/pytest-dev/pytest/pull/5334).
|
||||
- env: TOXENV=pypy3-xdist
|
||||
python: 'pypy3'
|
||||
|
||||
before_script:
|
||||
- |
|
||||
|
|
|
@ -18,6 +18,96 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. 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)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
* **maintenance releases**: from ``4.6-maintenance``;
|
||||
|
||||
* **patch releases**: from 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.
|
||||
|
||||
#. 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::
|
||||
|
||||
|
@ -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>`_.
|
||||
|
||||
#. 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::
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ Features
|
|||
- 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;
|
||||
|
||||
- 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;
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ trigger:
|
|||
|
||||
variables:
|
||||
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
|
||||
python.needs_vc: False
|
||||
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
|
||||
COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc"
|
||||
PYTEST_COVERAGE: '0'
|
||||
|
@ -16,44 +15,10 @@ jobs:
|
|||
vmImage: "vs2017-win2016"
|
||||
strategy:
|
||||
matrix:
|
||||
py27:
|
||||
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 disabled for now: #5279 --
|
||||
# pypy3:
|
||||
# python.version: '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:
|
||||
python.version: '3.5'
|
||||
tox.env: 'py35-xdist'
|
||||
|
@ -87,10 +52,6 @@ jobs:
|
|||
versionSpec: '$(python.version)'
|
||||
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
|
||||
displayName: 'Install tox'
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# 10000 iterations, just for relative comparison
|
||||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
for i in range(1000):
|
||||
exec("def test_func_%d(): pass" % i)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from six.moves import range
|
||||
|
||||
import pytest
|
||||
|
||||
SKIP = True
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.
|
|
@ -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.
|
|
@ -1 +0,0 @@
|
|||
The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).
|
|
@ -1 +0,0 @@
|
|||
pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory.
|
|
@ -1 +0,0 @@
|
|||
Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.
|
|
@ -1 +0,0 @@
|
|||
Unroll calls to ``all`` to full for-loops for better failure messages, especially when using Generator Expressions.
|
|
@ -1 +0,0 @@
|
|||
Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time.
|
|
@ -1 +0,0 @@
|
|||
The output for ini options in ``--help`` has been improved.
|
|
@ -1 +0,0 @@
|
|||
Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.
|
|
@ -1 +0,0 @@
|
|||
Handle internal error due to a lone surrogate unicode character not being representable in Jython.
|
|
@ -1 +0,0 @@
|
|||
Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.
|
|
@ -1 +0,0 @@
|
|||
``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``.
|
|
@ -1 +0,0 @@
|
|||
Pytest's internal python plugin can be disabled using ``-p no:python`` again.
|
|
@ -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.
|
|
@ -1,2 +0,0 @@
|
|||
Captured logs that are output for each failing test are formatted using the
|
||||
ColoredLevelFormatter.
|
|
@ -1 +0,0 @@
|
|||
Improved formatting of multiline log messages in python3.
|
|
@ -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.
|
|
@ -1 +0,0 @@
|
|||
Fix regression with ``--lf`` not re-running all tests with known failures from non-selected tests.
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
Fix ``pytest.mark.parametrize`` when the argvalues is an iterator.
|
|
@ -0,0 +1 @@
|
|||
Fix assertion rewriting of ``all()`` calls to deal with non-generators.
|
|
@ -10,6 +10,7 @@
|
|||
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
|
||||
<li><a href="{{ pathto('contributing') }}">Contributing</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('contact') }}">Contact Channels</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Comment
|
||||
|
|
|
@ -6,6 +6,8 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-4.6.1
|
||||
release-4.6.0
|
||||
release-4.5.0
|
||||
release-4.4.2
|
||||
release-4.4.1
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# pytest documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Oct 8 17:54:28 2010.
|
||||
|
@ -63,9 +62,9 @@ source_suffix = ".rst"
|
|||
master_doc = "contents"
|
||||
|
||||
# General information about the project.
|
||||
project = u"pytest"
|
||||
project = "pytest"
|
||||
year = datetime.datetime.utcnow().year
|
||||
copyright = u"2015–2019 , holger krekel and pytest-dev team"
|
||||
copyright = "2015–2019 , holger krekel and pytest-dev team"
|
||||
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
|
@ -233,8 +232,8 @@ latex_documents = [
|
|||
(
|
||||
"contents",
|
||||
"pytest.tex",
|
||||
u"pytest Documentation",
|
||||
u"holger krekel, trainer and consultant, http://merlinux.eu",
|
||||
"pytest Documentation",
|
||||
"holger krekel, trainer and consultant, http://merlinux.eu",
|
||||
"manual",
|
||||
)
|
||||
]
|
||||
|
@ -266,16 +265,16 @@ latex_domain_indices = False
|
|||
|
||||
# One entry per manual page. List of tuples
|
||||
# (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 ---------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u"pytest"
|
||||
epub_author = u"holger krekel at merlinux eu"
|
||||
epub_publisher = u"holger krekel at merlinux eu"
|
||||
epub_copyright = u"2013, holger krekel et alii"
|
||||
epub_title = "pytest"
|
||||
epub_author = "holger krekel at merlinux eu"
|
||||
epub_publisher = "holger krekel at merlinux eu"
|
||||
epub_copyright = "2013, holger krekel et alii"
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
collect_ignore = ["conf.py"]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
@ -21,7 +20,7 @@ def test_generative(param1, param2):
|
|||
assert param1 * 2 < param2
|
||||
|
||||
|
||||
class TestFailing(object):
|
||||
class TestFailing:
|
||||
def test_simple(self):
|
||||
def f():
|
||||
return 42
|
||||
|
@ -41,7 +40,7 @@ class TestFailing(object):
|
|||
assert not f()
|
||||
|
||||
|
||||
class TestSpecialisedExplanations(object):
|
||||
class TestSpecialisedExplanations:
|
||||
def test_eq_text(self):
|
||||
assert "spam" == "eggs"
|
||||
|
||||
|
@ -101,7 +100,7 @@ class TestSpecialisedExplanations(object):
|
|||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
a: int
|
||||
b: str
|
||||
|
||||
|
@ -113,7 +112,7 @@ class TestSpecialisedExplanations(object):
|
|||
import attr
|
||||
|
||||
@attr.s
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
a = attr.ib()
|
||||
b = attr.ib()
|
||||
|
||||
|
@ -123,7 +122,7 @@ class TestSpecialisedExplanations(object):
|
|||
|
||||
|
||||
def test_attribute():
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
b = 1
|
||||
|
||||
i = Foo()
|
||||
|
@ -131,14 +130,14 @@ def test_attribute():
|
|||
|
||||
|
||||
def test_attribute_instance():
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
b = 1
|
||||
|
||||
assert Foo().b == 2
|
||||
|
||||
|
||||
def test_attribute_failure():
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
def _get_b(self):
|
||||
raise Exception("Failed to get attrib")
|
||||
|
||||
|
@ -149,10 +148,10 @@ def test_attribute_failure():
|
|||
|
||||
|
||||
def test_attribute_multiple():
|
||||
class Foo(object):
|
||||
class Foo:
|
||||
b = 1
|
||||
|
||||
class Bar(object):
|
||||
class Bar:
|
||||
b = 2
|
||||
|
||||
assert Foo().b == Bar().b
|
||||
|
@ -162,7 +161,7 @@ def globf(x):
|
|||
return x + 1
|
||||
|
||||
|
||||
class TestRaises(object):
|
||||
class TestRaises:
|
||||
def test_raises(self):
|
||||
s = "qwe"
|
||||
raises(TypeError, int, s)
|
||||
|
@ -203,7 +202,7 @@ def test_dynamic_compile_shows_nicely():
|
|||
module.foo()
|
||||
|
||||
|
||||
class TestMoreErrors(object):
|
||||
class TestMoreErrors:
|
||||
def test_complex_error(self):
|
||||
def f():
|
||||
return 44
|
||||
|
@ -253,16 +252,16 @@ class TestMoreErrors(object):
|
|||
x = 0
|
||||
|
||||
|
||||
class TestCustomAssertMsg(object):
|
||||
class TestCustomAssertMsg:
|
||||
def test_single_line(self):
|
||||
class A(object):
|
||||
class A:
|
||||
a = 1
|
||||
|
||||
b = 2
|
||||
assert A.a == b, "A.a appears not to be b"
|
||||
|
||||
def test_multiline(self):
|
||||
class A(object):
|
||||
class A:
|
||||
a = 1
|
||||
|
||||
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"
|
||||
|
||||
def test_custom_repr(self):
|
||||
class JSON(object):
|
||||
class JSON:
|
||||
a = 1
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import py
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import py
|
||||
|
||||
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
def setup_module(module):
|
||||
module.TestStateFullThing.classcount = 0
|
||||
|
||||
|
||||
class TestStateFullThing(object):
|
||||
class TestStateFullThing:
|
||||
def setup_class(cls):
|
||||
cls.classcount += 1
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
collect_ignore = ["nonpython"]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
|
||||
|
@ -9,7 +8,7 @@ def setup(request):
|
|||
setup.finalize()
|
||||
|
||||
|
||||
class CostlySetup(object):
|
||||
class CostlySetup:
|
||||
def __init__(self):
|
||||
import time
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
def test_quick(setup):
|
||||
pass
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
def test_something(setup):
|
||||
assert setup.timecostly == 1
|
||||
|
||||
|
|
|
@ -288,8 +288,7 @@ its test methods:
|
|||
This is equivalent to directly applying the decorator to the
|
||||
two test functions.
|
||||
|
||||
To remain backward-compatible with Python 2.4 you can also set a
|
||||
``pytestmark`` attribute on a TestClass like this:
|
||||
Due to legacy reasons, it is possible to set the ``pytestmark`` attribute on a TestClass like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
module containing a parametrized tests testing cross-python
|
||||
serialization via the pickle module.
|
||||
|
@ -9,7 +8,7 @@ import textwrap
|
|||
|
||||
import pytest
|
||||
|
||||
pythonlist = ["python2.7", "python3.4", "python3.5"]
|
||||
pythonlist = ["python3.5", "python3.6", "python3.7"]
|
||||
|
||||
|
||||
@pytest.fixture(params=pythonlist)
|
||||
|
@ -23,7 +22,7 @@ def python2(request, python1):
|
|||
return Python(request.param, python1.picklefile)
|
||||
|
||||
|
||||
class Python(object):
|
||||
class Python:
|
||||
def __init__(self, version, picklefile):
|
||||
self.pythonpath = distutils.spawn.find_executable(version)
|
||||
if not self.pythonpath:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# content of conftest.py
|
||||
import pytest
|
||||
|
||||
|
@ -19,7 +18,7 @@ class YamlFile(pytest.File):
|
|||
|
||||
class YamlItem(pytest.Item):
|
||||
def __init__(self, name, parent, spec):
|
||||
super(YamlItem, self).__init__(name, parent)
|
||||
super().__init__(name, parent)
|
||||
self.spec = spec
|
||||
|
||||
def runtest(self):
|
||||
|
|
|
@ -429,14 +429,14 @@ is to be run with different sets of arguments for its three arguments:
|
|||
|
||||
.. 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
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= 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
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
|
@ -494,7 +494,7 @@ If you run this with reporting for skips enabled:
|
|||
test_module.py .s [100%]
|
||||
|
||||
========================= 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 ====================
|
||||
|
||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
def test_exception_syntax():
|
||||
try:
|
||||
0 / 0
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# run this with $ pytest --collect-only test_collectonly.py
|
||||
#
|
||||
|
||||
|
@ -7,7 +6,7 @@ def test_function():
|
|||
pass
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> assert param1 * 2 < param2
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:20: AssertionError
|
||||
failure_demo.py:21: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
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 + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:31: AssertionError
|
||||
failure_demo.py:32: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
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):
|
||||
> otherfunc_multi(42, 6 * 9)
|
||||
|
||||
failure_demo.py:34:
|
||||
failure_demo.py:35:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
|
@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> assert a == b
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:15: AssertionError
|
||||
failure_demo.py:16: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
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 + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:40: AssertionError
|
||||
failure_demo.py:41: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
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 + eggs
|
||||
|
||||
failure_demo.py:45: AssertionError
|
||||
failure_demo.py:46: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
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 ? ^
|
||||
|
||||
failure_demo.py:48: AssertionError
|
||||
failure_demo.py:49: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
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 bar
|
||||
|
||||
failure_demo.py:51: AssertionError
|
||||
failure_demo.py:52: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
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 ? ^
|
||||
|
||||
failure_demo.py:56: AssertionError
|
||||
failure_demo.py:57: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
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 ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:61: AssertionError
|
||||
failure_demo.py:62: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:64: AssertionError
|
||||
failure_demo.py:65: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:69: AssertionError
|
||||
failure_demo.py:70: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
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 ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:72: AssertionError
|
||||
failure_demo.py:73: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
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 ...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 ______________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:78: AssertionError
|
||||
failure_demo.py:79: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
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]
|
||||
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 __________
|
||||
|
||||
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 ...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 ____________
|
||||
|
||||
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 ? +++
|
||||
|
||||
failure_demo.py:89: AssertionError
|
||||
failure_demo.py:90: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
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 ? +++
|
||||
|
||||
failure_demo.py:93: AssertionError
|
||||
failure_demo.py:94: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
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 ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:97: AssertionError
|
||||
failure_demo.py:98: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||
|
||||
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 b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:109: AssertionError
|
||||
failure_demo.py:110: AssertionError
|
||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||
|
||||
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 b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:121: AssertionError
|
||||
failure_demo.py:122: AssertionError
|
||||
______________________________ 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 + 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 __________________________
|
||||
|
||||
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 <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 __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
|
@ -364,7 +364,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:147:
|
||||
failure_demo.py:148:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
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")
|
||||
E Exception: Failed to get attrib
|
||||
|
||||
failure_demo.py:142: Exception
|
||||
failure_demo.py:143: Exception
|
||||
_________________________ 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 + 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 __________________________
|
||||
|
||||
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)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
failure_demo.py:167: ValueError
|
||||
failure_demo.py:168: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
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")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:170: Failed
|
||||
failure_demo.py:171: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
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")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:173: ValueError
|
||||
failure_demo.py:174: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
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
|
||||
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 ______
|
||||
|
||||
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()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:181: TypeError
|
||||
failure_demo.py:182: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
@ -449,7 +449,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:184: NameError
|
||||
failure_demo.py:185: NameError
|
||||
____________________ 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
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:202:
|
||||
failure_demo.py:203:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
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 _____________________
|
||||
|
||||
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())
|
||||
|
||||
failure_demo.py:213:
|
||||
failure_demo.py:214:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:11: in somefunc
|
||||
failure_demo.py:12: in somefunc
|
||||
otherfunc(x, y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
|
@ -497,7 +497,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> assert a == b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:7: AssertionError
|
||||
failure_demo.py:8: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
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
|
||||
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 _____________________
|
||||
|
||||
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
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:221: TypeError
|
||||
failure_demo.py:222: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
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 <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 ___________________
|
||||
|
||||
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 + 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 ______________________
|
||||
|
||||
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 43 = globf(42)
|
||||
|
||||
failure_demo.py:238: AssertionError
|
||||
failure_demo.py:239: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
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 + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||
|
||||
failure_demo.py:242: AssertionError
|
||||
failure_demo.py:243: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
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 + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:245: AssertionError
|
||||
failure_demo.py:246: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
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
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:250: AssertionError
|
||||
failure_demo.py:251: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
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 + 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 ____________________
|
||||
|
||||
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 + 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 ___________________
|
||||
|
||||
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 + 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 =========================
|
||||
|
|
|
@ -441,7 +441,7 @@ Now we can profile which test functions execute the slowest:
|
|||
|
||||
========================= slowest 3 test durations =========================
|
||||
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
|
||||
========================= 3 passed in 0.12 seconds =========================
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
xfail = pytest.mark.xfail
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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/>`_
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ Good Integration Practices
|
|||
Install package with pip
|
||||
-------------------------------------------------
|
||||
|
||||
For development, we recommend you use venv_ for virtual environments
|
||||
(or virtualenv_ for Python 2.7) and
|
||||
For development, we recommend you use venv_ for virtual environments and
|
||||
pip_ for installing your application and any dependencies,
|
||||
as well as the ``pytest`` package itself.
|
||||
This ensures your code and dependencies are isolated from your system Python installation.
|
||||
|
|
|
@ -61,7 +61,7 @@ Features
|
|||
|
||||
- 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;
|
||||
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
|||
|
||||
============================= warnings summary =============================
|
||||
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:
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
|
|
|
@ -335,8 +335,6 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
import py
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
from distutils.core import setup
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Invoke development tasks.
|
||||
"""
|
||||
|
|
14
setup.cfg
14
setup.cfg
|
@ -1,5 +1,4 @@
|
|||
[metadata]
|
||||
|
||||
name = pytest
|
||||
description = pytest: simple powerful testing with Python
|
||||
long_description = file: README.rst
|
||||
|
@ -23,13 +22,11 @@ classifiers =
|
|||
Topic :: Software Development :: Testing
|
||||
Topic :: Software Development :: Libraries
|
||||
Topic :: Utilities
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.5
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
platforms = unix, linux, osx, cygwin, win32
|
||||
|
||||
[options]
|
||||
|
@ -43,8 +40,7 @@ packages =
|
|||
_pytest.mark
|
||||
|
||||
py_modules = pytest
|
||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
||||
python_requires = >=3.5
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
|
@ -59,13 +55,9 @@ all_files = 1
|
|||
[upload_sphinx]
|
||||
upload-dir = doc/en/build/html
|
||||
|
||||
[bdist_wheel]
|
||||
universal = 1
|
||||
|
||||
[check-manifest]
|
||||
ignore =
|
||||
_pytest/_version.py
|
||||
|
||||
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz,bdist_wheel
|
||||
|
|
8
setup.py
8
setup.py
|
@ -1,17 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup
|
||||
|
||||
# TODO: if py gets upgrade to >=1.6,
|
||||
# remove _width_of_current_line in terminal.py
|
||||
INSTALL_REQUIRES = [
|
||||
"py>=1.5.0",
|
||||
"six>=1.10.0",
|
||||
"packaging",
|
||||
"attrs>=17.4.0",
|
||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs>=1.0;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
"pluggy>=0.12,<1.0",
|
||||
|
@ -30,9 +26,9 @@ def main():
|
|||
"testing": [
|
||||
"argcomplete",
|
||||
"hypothesis>=3.56",
|
||||
"mock",
|
||||
"nose",
|
||||
"requests",
|
||||
"mock;python_version=='2.7'",
|
||||
],
|
||||
},
|
||||
# fmt: on
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
__all__ = ["__version__"]
|
||||
|
||||
try:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""allow bash-completion for argparse with argcomplete if installed
|
||||
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
|
||||
|
@ -54,16 +53,12 @@ If things do not work right away:
|
|||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||
global argcomplete script).
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
|
||||
class FastFilesCompleter(object):
|
||||
class FastFilesCompleter:
|
||||
"Fast file completer class"
|
||||
|
||||
def __init__(self, directories=True):
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" 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 ExceptionInfo # noqa
|
||||
from .code import filter_traceback # noqa
|
||||
|
|
|
@ -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__)
|
|
@ -1,36 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
from inspect import CO_VARARGS
|
||||
from inspect import CO_VARKEYWORDS
|
||||
from traceback import format_exception_only
|
||||
from weakref import ref
|
||||
|
||||
import attr
|
||||
import pluggy
|
||||
import py
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import safeformat
|
||||
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 """
|
||||
|
||||
def __init__(self, rawcode):
|
||||
|
@ -41,7 +27,7 @@ class Code(object):
|
|||
self.firstlineno = rawcode.co_firstlineno - 1
|
||||
self.name = rawcode.co_name
|
||||
except AttributeError:
|
||||
raise TypeError("not a code object: %r" % (rawcode,))
|
||||
raise TypeError("not a code object: {!r}".format(rawcode))
|
||||
self.raw = rawcode
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -100,7 +86,7 @@ class Code(object):
|
|||
return raw.co_varnames[:argcount]
|
||||
|
||||
|
||||
class Frame(object):
|
||||
class Frame:
|
||||
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||
in which expressions can be evaluated."""
|
||||
|
||||
|
@ -163,7 +149,7 @@ class Frame(object):
|
|||
return retval
|
||||
|
||||
|
||||
class TracebackEntry(object):
|
||||
class TracebackEntry:
|
||||
""" a single entry in a traceback """
|
||||
|
||||
_repr_style = None
|
||||
|
@ -208,8 +194,7 @@ class TracebackEntry(object):
|
|||
locals = property(getlocals, None, None, "locals of underlaying frame")
|
||||
|
||||
def getfirstlinesource(self):
|
||||
# on Jython this firstlineno can be -1 apparently
|
||||
return max(self.frame.code.firstlineno, 0)
|
||||
return self.frame.code.firstlineno
|
||||
|
||||
def getsource(self, astcache=None):
|
||||
""" return failing source code. """
|
||||
|
@ -324,7 +309,7 @@ class Traceback(list):
|
|||
return self
|
||||
|
||||
def __getitem__(self, key):
|
||||
val = super(Traceback, self).__getitem__(key)
|
||||
val = super().__getitem__(key)
|
||||
if isinstance(key, type(slice(0))):
|
||||
val = self.__class__(val)
|
||||
return val
|
||||
|
@ -386,14 +371,12 @@ co_equal = compile(
|
|||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class ExceptionInfo(object):
|
||||
class ExceptionInfo:
|
||||
""" wraps sys.exc_info() objects and offers
|
||||
help for navigating the traceback.
|
||||
"""
|
||||
|
||||
_assert_start_repr = (
|
||||
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
|
||||
)
|
||||
_assert_start_repr = "AssertionError('assert "
|
||||
|
||||
_excinfo = attr.ib()
|
||||
_striptext = attr.ib(default="")
|
||||
|
@ -558,11 +541,6 @@ class ExceptionInfo(object):
|
|||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
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):
|
||||
"""
|
||||
Check whether the regular expression 'regexp' is found in the string
|
||||
|
@ -578,7 +556,7 @@ class ExceptionInfo(object):
|
|||
|
||||
|
||||
@attr.s
|
||||
class FormattedExcinfo(object):
|
||||
class FormattedExcinfo:
|
||||
""" presenting information about failing Functions and Generators. """
|
||||
|
||||
# for traceback entries
|
||||
|
@ -677,7 +655,7 @@ class FormattedExcinfo(object):
|
|||
str_repr = safeformat(value)
|
||||
# if len(str_repr) < 70 or not isinstance(value,
|
||||
# (list, tuple, dict)):
|
||||
lines.append("%-10s = %s" % (name, str_repr))
|
||||
lines.append("{:<10} = {}".format(name, str_repr))
|
||||
# else:
|
||||
# self._line("%-10s =\\" % (name,))
|
||||
# # XXX
|
||||
|
@ -692,8 +670,7 @@ class FormattedExcinfo(object):
|
|||
source = _pytest._code.Source("???")
|
||||
line_index = 0
|
||||
else:
|
||||
# entry.getfirstlinesource() can be -1, should be 0 on jython
|
||||
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
|
||||
line_index = entry.lineno - entry.getfirstlinesource()
|
||||
|
||||
lines = []
|
||||
style = entry._repr_style
|
||||
|
@ -733,7 +710,7 @@ class FormattedExcinfo(object):
|
|||
if self.tbfilter:
|
||||
traceback = traceback.filter()
|
||||
|
||||
if is_recursion_error(excinfo):
|
||||
if excinfo.errisinstance(RecursionError):
|
||||
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
||||
else:
|
||||
extraline = None
|
||||
|
@ -769,7 +746,7 @@ class FormattedExcinfo(object):
|
|||
" Displaying first and last {max_frames} stack frames out of {total}."
|
||||
).format(
|
||||
exc_type=type(e).__name__,
|
||||
exc_msg=safe_str(e),
|
||||
exc_msg=str(e),
|
||||
max_frames=max_frames,
|
||||
total=len(traceback),
|
||||
)
|
||||
|
@ -784,64 +761,51 @@ class FormattedExcinfo(object):
|
|||
return traceback, extraline
|
||||
|
||||
def repr_excinfo(self, excinfo):
|
||||
if _PY2:
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
|
||||
return ReprExceptionInfo(reprtraceback, reprcrash)
|
||||
else:
|
||||
repr_chain = []
|
||||
e = excinfo.value
|
||||
descr = None
|
||||
seen = set()
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
if excinfo:
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
else:
|
||||
# fallback to native repr if the exception doesn't have a traceback:
|
||||
# ExceptionInfo objects require a full traceback to work
|
||||
reprtraceback = ReprTracebackNative(
|
||||
traceback.format_exception(type(e), e, None)
|
||||
)
|
||||
reprcrash = None
|
||||
repr_chain = []
|
||||
e = excinfo.value
|
||||
descr = None
|
||||
seen = set()
|
||||
while e is not None and id(e) not in seen:
|
||||
seen.add(id(e))
|
||||
if excinfo:
|
||||
reprtraceback = self.repr_traceback(excinfo)
|
||||
reprcrash = excinfo._getreprcrash()
|
||||
else:
|
||||
# fallback to native repr if the exception doesn't have a traceback:
|
||||
# ExceptionInfo objects require a full traceback to work
|
||||
reprtraceback = ReprTracebackNative(
|
||||
traceback.format_exception(type(e), e, None)
|
||||
)
|
||||
reprcrash = None
|
||||
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
if e.__cause__ is not None and self.chain:
|
||||
e = e.__cause__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
if e.__traceback__
|
||||
else None
|
||||
)
|
||||
descr = "The above exception was the direct cause of the following exception:"
|
||||
elif (
|
||||
e.__context__ is not None
|
||||
and not e.__suppress_context__
|
||||
and self.chain
|
||||
):
|
||||
e = e.__context__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
if e.__traceback__
|
||||
else None
|
||||
)
|
||||
descr = "During handling of the above exception, another exception occurred:"
|
||||
else:
|
||||
e = None
|
||||
repr_chain.reverse()
|
||||
return ExceptionChainRepr(repr_chain)
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
if e.__cause__ is not None and self.chain:
|
||||
e = e.__cause__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
if e.__traceback__
|
||||
else None
|
||||
)
|
||||
descr = "The above exception was the direct cause of the following exception:"
|
||||
elif (
|
||||
e.__context__ is not None and not e.__suppress_context__ and self.chain
|
||||
):
|
||||
e = e.__context__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
if e.__traceback__
|
||||
else None
|
||||
)
|
||||
descr = "During handling of the above exception, another exception occurred:"
|
||||
else:
|
||||
e = None
|
||||
repr_chain.reverse()
|
||||
return ExceptionChainRepr(repr_chain)
|
||||
|
||||
|
||||
class TerminalRepr(object):
|
||||
class TerminalRepr:
|
||||
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
|
||||
# information.
|
||||
io = py.io.TextIO()
|
||||
|
@ -850,7 +814,7 @@ class TerminalRepr(object):
|
|||
return io.getvalue().strip()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s instance at %0x>" % (self.__class__, id(self))
|
||||
return "<{} instance at {:0x}>".format(self.__class__, id(self))
|
||||
|
||||
|
||||
class ExceptionRepr(TerminalRepr):
|
||||
|
@ -868,7 +832,7 @@ class ExceptionRepr(TerminalRepr):
|
|||
|
||||
class ExceptionChainRepr(ExceptionRepr):
|
||||
def __init__(self, chain):
|
||||
super(ExceptionChainRepr, self).__init__()
|
||||
super().__init__()
|
||||
self.chain = chain
|
||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||
# in the chain
|
||||
|
@ -881,18 +845,18 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
if element[2] is not None:
|
||||
tw.line("")
|
||||
tw.line(element[2], yellow=True)
|
||||
super(ExceptionChainRepr, self).toterminal(tw)
|
||||
super().toterminal(tw)
|
||||
|
||||
|
||||
class ReprExceptionInfo(ExceptionRepr):
|
||||
def __init__(self, reprtraceback, reprcrash):
|
||||
super(ReprExceptionInfo, self).__init__()
|
||||
super().__init__()
|
||||
self.reprtraceback = reprtraceback
|
||||
self.reprcrash = reprcrash
|
||||
|
||||
def toterminal(self, tw):
|
||||
self.reprtraceback.toterminal(tw)
|
||||
super(ReprExceptionInfo, self).toterminal(tw)
|
||||
super().toterminal(tw)
|
||||
|
||||
|
||||
class ReprTraceback(TerminalRepr):
|
||||
|
@ -969,7 +933,9 @@ class ReprEntry(TerminalRepr):
|
|||
self.reprfileloc.toterminal(tw)
|
||||
|
||||
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):
|
||||
|
@ -986,7 +952,7 @@ class ReprFileLocation(TerminalRepr):
|
|||
if i != -1:
|
||||
msg = msg[:i]
|
||||
tw.write(self.path, bold=True, red=True)
|
||||
tw.line(":%s: %s" % (self.lineno, msg))
|
||||
tw.line(":{}: {}".format(self.lineno, msg))
|
||||
|
||||
|
||||
class ReprLocals(TerminalRepr):
|
||||
|
@ -1006,7 +972,7 @@ class ReprFuncArgs(TerminalRepr):
|
|||
if self.args:
|
||||
linesofar = ""
|
||||
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 linesofar:
|
||||
tw.line(linesofar)
|
||||
|
@ -1038,23 +1004,6 @@ def getrawcode(obj, trycall=True):
|
|||
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;
|
||||
# see filter_traceback
|
||||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import linecache
|
||||
|
@ -14,10 +9,9 @@ from ast import PyCF_ONLY_AST as _AST_FLAG
|
|||
from bisect import bisect_right
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
|
||||
class Source(object):
|
||||
class Source:
|
||||
""" an immutable object holding a source code fragment,
|
||||
possibly deindenting it.
|
||||
"""
|
||||
|
@ -34,7 +28,7 @@ class Source(object):
|
|||
partlines = part.lines
|
||||
elif isinstance(part, (tuple, list)):
|
||||
partlines = [x.rstrip("\n") for x in part]
|
||||
elif isinstance(part, six.string_types):
|
||||
elif isinstance(part, str):
|
||||
partlines = part.split("\n")
|
||||
else:
|
||||
partlines = getsource(part, deindent=de).lines
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import pprint
|
||||
|
||||
from six.moves import reprlib
|
||||
import reprlib
|
||||
|
||||
|
||||
def _call_and_format_exception(call, x, *args):
|
||||
|
@ -14,11 +12,8 @@ def _call_and_format_exception(call, x, *args):
|
|||
exc_info = str(exc)
|
||||
except Exception:
|
||||
exc_info = "unknown"
|
||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
||||
exc_name,
|
||||
exc_info,
|
||||
x.__class__.__name__,
|
||||
id(x),
|
||||
return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
|
||||
exc_name, exc_info, x.__class__.__name__, id(x)
|
||||
)
|
||||
|
||||
|
||||
|
@ -34,11 +29,11 @@ class SafeRepr(reprlib.Repr):
|
|||
# Strictly speaking wrong on narrow builds
|
||||
def repr(u):
|
||||
if "'" not in u:
|
||||
return u"'%s'" % u
|
||||
return "'%s'" % u
|
||||
elif '"' not in u:
|
||||
return u'"%s"' % u
|
||||
return '"%s"' % u
|
||||
else:
|
||||
return u"'%s'" % u.replace("'", r"\'")
|
||||
return "'%s'" % u.replace("'", r"\'")
|
||||
|
||||
s = repr(x[: self.maxstring])
|
||||
if len(s) > self.maxstring:
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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 six
|
||||
|
||||
from _pytest.assertion import rewrite
|
||||
from _pytest.assertion import truncate
|
||||
from _pytest.assertion import util
|
||||
|
@ -56,14 +49,14 @@ def register_assert_rewrite(*names):
|
|||
importhook.mark_rewrite(*names)
|
||||
|
||||
|
||||
class DummyRewriteHook(object):
|
||||
class DummyRewriteHook:
|
||||
"""A no-op import hook for when rewriting is disabled."""
|
||||
|
||||
def mark_rewrite(self, *names):
|
||||
pass
|
||||
|
||||
|
||||
class AssertionState(object):
|
||||
class AssertionState:
|
||||
"""State for the assertion plugin."""
|
||||
|
||||
def __init__(self, config, mode):
|
||||
|
@ -74,10 +67,6 @@ class AssertionState(object):
|
|||
|
||||
def install_importhook(config):
|
||||
"""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.hook = hook = rewrite.AssertionRewritingHook(config)
|
||||
sys.meta_path.insert(0, hook)
|
||||
|
@ -133,7 +122,7 @@ def pytest_runtest_setup(item):
|
|||
if new_expl:
|
||||
new_expl = truncate.truncate_if_required(new_expl, item)
|
||||
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":
|
||||
res = res.replace("%", "%%")
|
||||
return res
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""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 errno
|
||||
import imp
|
||||
|
@ -15,17 +10,16 @@ import string
|
|||
import struct
|
||||
import sys
|
||||
import types
|
||||
from importlib.util import spec_from_file_location
|
||||
|
||||
import atomicwrites
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.util import ( # noqa: F401
|
||||
format_explanation as _format_explanation,
|
||||
)
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
from _pytest.pathlib import PurePath
|
||||
|
||||
|
@ -35,28 +29,17 @@ if hasattr(imp, "get_tag"):
|
|||
else:
|
||||
if hasattr(sys, "pypy_version_info"):
|
||||
impl = "pypy"
|
||||
elif sys.platform == "java":
|
||||
impl = "jython"
|
||||
else:
|
||||
impl = "cpython"
|
||||
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
|
||||
|
||||
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
||||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||
|
||||
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
ast_Call = ast.Call
|
||||
else:
|
||||
|
||||
def ast_Call(a, b, c):
|
||||
return ast.Call(a, b, c, None, None)
|
||||
|
||||
|
||||
class AssertionRewritingHook(object):
|
||||
class AssertionRewritingHook:
|
||||
"""PEP302 Import hook which rewrites asserts."""
|
||||
|
||||
def __init__(self, config):
|
||||
|
@ -165,7 +148,7 @@ class AssertionRewritingHook(object):
|
|||
# to check for a cached pyc. This may not be optimal...
|
||||
co = _read_pyc(fn_pypath, pyc, state.trace)
|
||||
if co is None:
|
||||
state.trace("rewriting %r" % (fn,))
|
||||
state.trace("rewriting {!r}".format(fn))
|
||||
source_stat, co = _rewrite_test(self.config, fn_pypath)
|
||||
if co is None:
|
||||
# Probably a SyntaxError in the test.
|
||||
|
@ -177,7 +160,7 @@ class AssertionRewritingHook(object):
|
|||
finally:
|
||||
self._writing_pyc = False
|
||||
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
|
||||
return self
|
||||
|
||||
|
@ -216,26 +199,28 @@ class AssertionRewritingHook(object):
|
|||
if self._is_marked_for_rewrite(name, state):
|
||||
return False
|
||||
|
||||
state.trace("early skip of rewriting module: %s" % (name,))
|
||||
state.trace("early skip of rewriting module: {}".format(name))
|
||||
return True
|
||||
|
||||
def _should_rewrite(self, name, fn_pypath, state):
|
||||
# always rewrite conftest files
|
||||
fn = str(fn_pypath)
|
||||
if fn_pypath.basename == "conftest.py":
|
||||
state.trace("rewriting conftest file: %r" % (fn,))
|
||||
state.trace("rewriting conftest file: {!r}".format(fn))
|
||||
return True
|
||||
|
||||
if self.session is not None:
|
||||
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
|
||||
|
||||
# modules not passed explicitly on the command line are only
|
||||
# rewritten if they match the naming convention for test files
|
||||
for pat in self.fnpats:
|
||||
if fn_pypath.fnmatch(pat):
|
||||
state.trace("matched test file %r" % (fn,))
|
||||
state.trace("matched test file {!r}".format(fn))
|
||||
return True
|
||||
|
||||
return self._is_marked_for_rewrite(name, state)
|
||||
|
@ -246,7 +231,9 @@ class AssertionRewritingHook(object):
|
|||
except KeyError:
|
||||
for marked in self._must_rewrite:
|
||||
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
|
||||
return True
|
||||
|
||||
|
@ -341,7 +328,7 @@ def _write_pyc(state, co, source_stat, pyc):
|
|||
fp.write(struct.pack("<LL", mtime, size))
|
||||
fp.write(marshal.dumps(co))
|
||||
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
|
||||
# there are many reasons, permission-denied, __pycache__ being a
|
||||
# file etc.
|
||||
|
@ -349,8 +336,8 @@ def _write_pyc(state, co, source_stat, pyc):
|
|||
return True
|
||||
|
||||
|
||||
RN = "\r\n".encode("utf-8")
|
||||
N = "\n".encode("utf-8")
|
||||
RN = "\r\n".encode()
|
||||
N = "\n".encode()
|
||||
|
||||
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
|
||||
BOM_UTF8 = "\xef\xbb\xbf"
|
||||
|
@ -364,42 +351,11 @@ def _rewrite_test(config, fn):
|
|||
source = fn.read("rb")
|
||||
except EnvironmentError:
|
||||
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:
|
||||
tree = ast.parse(source, filename=fn.strpath)
|
||||
except SyntaxError:
|
||||
# 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
|
||||
rewrite_asserts(tree, fn, config)
|
||||
try:
|
||||
|
@ -407,7 +363,7 @@ def _rewrite_test(config, fn):
|
|||
except SyntaxError:
|
||||
# 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.
|
||||
state.trace("failed to compile: %r" % (fn,))
|
||||
state.trace("failed to compile: {!r}".format(fn))
|
||||
return None, None
|
||||
return stat, co
|
||||
|
||||
|
@ -427,7 +383,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
|
|||
size = source.size()
|
||||
data = fp.read(12)
|
||||
except EnvironmentError as e:
|
||||
trace("_read_pyc(%s): EnvironmentError %s" % (source, e))
|
||||
trace("_read_pyc({}): EnvironmentError {}".format(source, e))
|
||||
return None
|
||||
# Check for invalid or out of date pyc file.
|
||||
if (
|
||||
|
@ -440,7 +396,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
|
|||
try:
|
||||
co = marshal.load(fp)
|
||||
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
|
||||
if not isinstance(co, types.CodeType):
|
||||
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+
|
||||
if isinstance(r, bytes):
|
||||
# Represent unprintable bytes as `\x##`
|
||||
r = u"".join(
|
||||
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
|
||||
r = "".join(
|
||||
"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
|
||||
for c in r
|
||||
)
|
||||
return r.replace(u"\n", u"\\n")
|
||||
return r.replace("\n", "\\n")
|
||||
|
||||
|
||||
def _format_assertmsg(obj):
|
||||
|
@ -487,10 +443,10 @@ def _format_assertmsg(obj):
|
|||
# contains a newline it gets escaped, however if an object has a
|
||||
# .__repr__() which contains newlines it does not get escaped.
|
||||
# However in either case we want to preserve the newline.
|
||||
replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
|
||||
if not isinstance(obj, six.string_types):
|
||||
replaces = [("\n", "\n~"), ("%", "%%")]
|
||||
if not isinstance(obj, str):
|
||||
obj = saferepr(obj)
|
||||
replaces.append((u"\\n", u"\n~"))
|
||||
replaces.append(("\\n", "\n~"))
|
||||
|
||||
if isinstance(obj, bytes):
|
||||
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):
|
||||
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
|
||||
if isinstance(explanation, six.text_type):
|
||||
return explanation.replace(u"%", u"%%")
|
||||
if isinstance(explanation, str):
|
||||
return explanation.replace("%", "%%")
|
||||
else:
|
||||
return explanation.replace(b"%", b"%%")
|
||||
|
||||
|
@ -643,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
"""
|
||||
|
||||
def __init__(self, module_path, config):
|
||||
super(AssertionRewriter, self).__init__()
|
||||
super().__init__()
|
||||
self.module_path = module_path
|
||||
self.config = config
|
||||
|
||||
|
@ -655,7 +611,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# Insert some special imports at the top of the module but after any
|
||||
# docstrings and __future__ imports.
|
||||
aliases = [
|
||||
ast.alias(six.moves.builtins.__name__, "@py_builtins"),
|
||||
ast.alias("builtins", "@py_builtins"),
|
||||
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
|
||||
]
|
||||
doc = getattr(mod, "docstring", None)
|
||||
|
@ -737,7 +693,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
"""Call a helper in this module."""
|
||||
py_name = ast.Name("@pytest_ar", 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):
|
||||
"""Return the builtin called *name*."""
|
||||
|
@ -847,11 +803,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
msg = self.pop_format_context(template)
|
||||
fmt = self.helper("_format_explanation", msg)
|
||||
err_name = ast.Name("AssertionError", ast.Load())
|
||||
exc = ast_Call(err_name, [fmt], [])
|
||||
if sys.version_info[0] >= 3:
|
||||
raise_ = ast.Raise(exc, None)
|
||||
else:
|
||||
raise_ = ast.Raise(exc, None, None)
|
||||
exc = ast.Call(err_name, [fmt], [])
|
||||
raise_ = ast.Raise(exc, None)
|
||||
|
||||
body.append(raise_)
|
||||
# Clear temporary variables by setting them to None.
|
||||
if self.variables:
|
||||
|
@ -893,7 +847,7 @@ warn_explicit(
|
|||
def visit_Name(self, name):
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _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])
|
||||
dorepr = self.helper("_should_repr_global_name", name)
|
||||
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
|
||||
|
@ -920,7 +874,7 @@ warn_explicit(
|
|||
res, expl = self.visit(v)
|
||||
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
||||
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))
|
||||
if i < levels:
|
||||
cond = res
|
||||
|
@ -945,15 +899,25 @@ warn_explicit(
|
|||
symbol = binop_map[binop.op.__class__]
|
||||
left_expr, left_expl = self.visit(binop.left)
|
||||
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))
|
||||
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)
|
||||
new_func, func_expl = self.visit(call.func)
|
||||
arg_expls = []
|
||||
|
@ -971,17 +935,15 @@ warn_explicit(
|
|||
else: # **args have `arg` keywords with an .arg of None
|
||||
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)
|
||||
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)
|
||||
outer_expl = "{}\n{{{} = {}\n}}".format(res_expl, res_expl, expl)
|
||||
return res, outer_expl
|
||||
|
||||
def _visit_all(self, call):
|
||||
"""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]
|
||||
assertion_module = ast.Module(
|
||||
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)
|
||||
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):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
|
@ -1074,7 +996,7 @@ warn_explicit(
|
|||
results.append(next_res)
|
||||
sym = binop_map[op.__class__]
|
||||
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))
|
||||
res_expr = ast.Compare(left_res, [op], [next_res])
|
||||
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
||||
|
|
|
@ -1,18 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Utilities for truncating assertion output.
|
||||
|
||||
Current default behaviour is to truncate assertion explanations at
|
||||
~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 six
|
||||
|
||||
DEFAULT_MAX_LINES = 8
|
||||
DEFAULT_MAX_CHARS = 8 * 80
|
||||
USAGE_MSG = "use '-vv' to show"
|
||||
|
@ -76,7 +69,7 @@ def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
|
|||
else:
|
||||
msg += " ({} lines hidden)".format(truncated_line_count)
|
||||
msg += ", {}".format(USAGE_MSG)
|
||||
truncated_explanation.extend([six.text_type(""), six.text_type(msg)])
|
||||
truncated_explanation.extend(["", str(msg)])
|
||||
return truncated_explanation
|
||||
|
||||
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Utilities for assertion debugging"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pprint
|
||||
|
||||
import six
|
||||
from collections.abc import Sequence
|
||||
|
||||
import _pytest._code
|
||||
from ..compat import Sequence
|
||||
from _pytest import outcomes
|
||||
from _pytest._io.saferepr import saferepr
|
||||
|
||||
|
@ -42,7 +35,7 @@ def format_explanation(explanation):
|
|||
explanation = ecu(explanation)
|
||||
lines = _split_explanation(explanation)
|
||||
result = _format_lines(lines)
|
||||
return u"\n".join(result)
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
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
|
||||
literal '\n' characters.
|
||||
"""
|
||||
raw_lines = (explanation or u"").split("\n")
|
||||
raw_lines = (explanation or "").split("\n")
|
||||
lines = [raw_lines[0]]
|
||||
for values in raw_lines[1:]:
|
||||
if values and values[0] in ["{", "}", "~", ">"]:
|
||||
|
@ -77,13 +70,13 @@ def _format_lines(lines):
|
|||
for line in lines[1:]:
|
||||
if line.startswith("{"):
|
||||
if stackcnt[-1]:
|
||||
s = u"and "
|
||||
s = "and "
|
||||
else:
|
||||
s = u"where "
|
||||
s = "where "
|
||||
stack.append(len(result))
|
||||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:])
|
||||
result.append(" +" + " " * (len(stack) - 1) + s + line[1:])
|
||||
elif line.startswith("}"):
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
|
@ -92,7 +85,7 @@ def _format_lines(lines):
|
|||
assert line[0] in ["~", ">"]
|
||||
stack[-1] += 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
|
||||
return result
|
||||
|
||||
|
@ -142,7 +135,7 @@ def assertrepr_compare(config, op, left, right):
|
|||
left_repr = saferepr(left, maxsize=int(width // 2))
|
||||
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")
|
||||
explanation = None
|
||||
|
@ -175,9 +168,9 @@ def assertrepr_compare(config, op, left, right):
|
|||
raise
|
||||
except Exception:
|
||||
explanation = [
|
||||
u"(pytest_assertion plugin: representation of details failed. "
|
||||
u"Probably an object has a faulty __repr__.)",
|
||||
six.text_type(_pytest._code.ExceptionInfo.from_current()),
|
||||
"(pytest_assertion plugin: representation of details failed. "
|
||||
"Probably an object has a faulty __repr__.)",
|
||||
str(_pytest._code.ExceptionInfo.from_current()),
|
||||
]
|
||||
|
||||
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
|
||||
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"\r", "\r")
|
||||
return r
|
||||
|
@ -221,7 +214,7 @@ def _diff_text(left, right, verbose=0):
|
|||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
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:]
|
||||
right = right[i:]
|
||||
|
@ -232,8 +225,8 @@ def _diff_text(left, right, verbose=0):
|
|||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation += [
|
||||
u"Skipping {} identical trailing "
|
||||
u"characters in diff, use -v to show".format(i)
|
||||
"Skipping {} identical trailing "
|
||||
"characters in diff, use -v to show".format(i)
|
||||
]
|
||||
left = left[:-i]
|
||||
right = right[:-i]
|
||||
|
@ -241,7 +234,7 @@ def _diff_text(left, right, verbose=0):
|
|||
if left.isspace() or right.isspace():
|
||||
left = repr(str(left))
|
||||
right = repr(str(right))
|
||||
explanation += [u"Strings contain only whitespace, escaping them using repr()"]
|
||||
explanation += ["Strings contain only whitespace, escaping them using repr()"]
|
||||
explanation += [
|
||||
line.strip("\n")
|
||||
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)
|
||||
|
||||
explanation = []
|
||||
explanation += [u"-" + line for line in left_lines]
|
||||
explanation += [u"+" + line for line in right_lines]
|
||||
explanation += ["-" + line for line in left_lines]
|
||||
explanation += ["+" + line for line in right_lines]
|
||||
|
||||
return explanation
|
||||
|
||||
|
||||
def _compare_eq_iterable(left, right, verbose=0):
|
||||
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
|
||||
import difflib
|
||||
|
||||
try:
|
||||
left_formatting = pprint.pformat(left).splitlines()
|
||||
right_formatting = pprint.pformat(right).splitlines()
|
||||
explanation = [u"Full diff:"]
|
||||
explanation = ["Full diff:"]
|
||||
except Exception:
|
||||
# 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.
|
||||
# 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)
|
||||
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(
|
||||
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)
|
||||
for i in range(min(len_left, len_right)):
|
||||
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
|
||||
len_diff = len_left - len_right
|
||||
|
||||
|
@ -304,10 +299,12 @@ def _compare_eq_sequence(left, right, verbose=0):
|
|||
extra = saferepr(right[len_left])
|
||||
|
||||
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:
|
||||
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)
|
||||
]
|
||||
return explanation
|
||||
|
@ -318,11 +315,11 @@ def _compare_eq_set(left, right, verbose=0):
|
|||
diff_left = left - right
|
||||
diff_right = right - 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:
|
||||
explanation.append(saferepr(item))
|
||||
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:
|
||||
explanation.append(saferepr(item))
|
||||
return explanation
|
||||
|
@ -335,20 +332,20 @@ def _compare_eq_dict(left, right, verbose=0):
|
|||
common = set_left.intersection(set_right)
|
||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||
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:
|
||||
explanation += [u"Common items:"]
|
||||
explanation += ["Common items:"]
|
||||
explanation += pprint.pformat(same).splitlines()
|
||||
diff = {k for k in common if left[k] != right[k]}
|
||||
if diff:
|
||||
explanation += [u"Differing items:"]
|
||||
explanation += ["Differing items:"]
|
||||
for k in diff:
|
||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||
extra_left = set_left - set_right
|
||||
len_extra_left = len(extra_left)
|
||||
if len_extra_left:
|
||||
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")
|
||||
)
|
||||
explanation.extend(
|
||||
|
@ -358,7 +355,7 @@ def _compare_eq_dict(left, right, verbose=0):
|
|||
len_extra_right = len(extra_right)
|
||||
if len_extra_right:
|
||||
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")
|
||||
)
|
||||
explanation.extend(
|
||||
|
@ -386,15 +383,15 @@ def _compare_eq_cls(left, right, verbose, type_fns):
|
|||
|
||||
explanation = []
|
||||
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:
|
||||
explanation += [u"Matching attributes:"]
|
||||
explanation += ["Matching attributes:"]
|
||||
explanation += pprint.pformat(same).splitlines()
|
||||
if diff:
|
||||
explanation += [u"Differing attributes:"]
|
||||
explanation += ["Differing attributes:"]
|
||||
for field in diff:
|
||||
explanation += [
|
||||
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
|
||||
("%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
@ -405,14 +402,14 @@ def _notin_text(term, text, verbose=0):
|
|||
tail = text[index + len(term) :]
|
||||
correct_text = head + tail
|
||||
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:
|
||||
if line.startswith(u"Skipping"):
|
||||
if line.startswith("Skipping"):
|
||||
continue
|
||||
if line.startswith(u"- "):
|
||||
if line.startswith("- "):
|
||||
continue
|
||||
if line.startswith(u"+ "):
|
||||
newdiff.append(u" " + line[2:])
|
||||
if line.startswith("+ "):
|
||||
newdiff.append(" " + line[2:])
|
||||
else:
|
||||
newdiff.append(line)
|
||||
return newdiff
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
merged implementation of the cache provider
|
||||
|
||||
the name cache was not chosen to ensure pluggy automatically
|
||||
ignores the external pytest-cache
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from .compat import _PY2 as PY2
|
||||
from .pathlib import Path
|
||||
from .pathlib import resolve_from_str
|
||||
from .pathlib import rmtree
|
||||
|
||||
README_CONTENT = u"""\
|
||||
README_CONTENT = """\
|
||||
# pytest cache directory #
|
||||
|
||||
This directory contains data from the pytest's cache plugin,
|
||||
|
@ -43,7 +36,7 @@ Signature: 8a477f597d28d172789f06886806bc55
|
|||
|
||||
|
||||
@attr.s
|
||||
class Cache(object):
|
||||
class Cache:
|
||||
_cachedir = attr.ib(repr=False)
|
||||
_config = attr.ib(repr=False)
|
||||
|
||||
|
@ -129,7 +122,7 @@ class Cache(object):
|
|||
if not cache_dir_exists_already:
|
||||
self._ensure_supporting_files()
|
||||
try:
|
||||
f = path.open("wb" if PY2 else "w")
|
||||
f = path.open("w")
|
||||
except (IOError, OSError):
|
||||
self.warn("cache could not write path {path}", path=path)
|
||||
else:
|
||||
|
@ -142,14 +135,14 @@ class Cache(object):
|
|||
readme_path.write_text(README_CONTENT)
|
||||
|
||||
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")
|
||||
|
||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
||||
|
||||
|
||||
class LFPlugin(object):
|
||||
class LFPlugin:
|
||||
""" Plugin which implements the --lf (run last-failing) option """
|
||||
|
||||
def __init__(self, config):
|
||||
|
@ -262,7 +255,7 @@ class LFPlugin(object):
|
|||
config.cache.set("cache/lastfailed", self.lastfailed)
|
||||
|
||||
|
||||
class NFPlugin(object):
|
||||
class NFPlugin:
|
||||
""" Plugin which implements the --nf (run new-first) option """
|
||||
|
||||
def __init__(self, config):
|
||||
|
@ -281,8 +274,8 @@ class NFPlugin(object):
|
|||
other_items[item.nodeid] = item
|
||||
|
||||
items[:] = self._get_increasing_order(
|
||||
six.itervalues(new_items)
|
||||
) + self._get_increasing_order(six.itervalues(other_items))
|
||||
new_items.values()
|
||||
) + self._get_increasing_order(other_items.values())
|
||||
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
|
||||
|
||||
def _get_increasing_order(self, items):
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
per-test stdout/stderr capturing mechanism.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import io
|
||||
|
@ -15,10 +10,7 @@ import sys
|
|||
from io import UnsupportedOperation
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import _PY3
|
||||
from _pytest.compat import CaptureIO
|
||||
|
||||
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
||||
|
@ -67,7 +59,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
|||
sys.stderr.write(err)
|
||||
|
||||
|
||||
class CaptureManager(object):
|
||||
class CaptureManager:
|
||||
"""
|
||||
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
|
||||
|
@ -86,10 +78,8 @@ class CaptureManager(object):
|
|||
self._current_item = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
|
||||
self._method,
|
||||
self._global_capturing,
|
||||
self._current_item,
|
||||
return "<CaptureManager _method={!r} _global_capturing={!r} _current_item={!r}>".format(
|
||||
self._method, self._global_capturing, self._current_item
|
||||
)
|
||||
|
||||
def _getcapture(self, method):
|
||||
|
@ -283,10 +273,6 @@ def capsysbinary(request):
|
|||
``out`` and ``err`` will be ``bytes`` objects.
|
||||
"""
|
||||
_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:
|
||||
yield fixture
|
||||
|
||||
|
@ -345,7 +331,7 @@ def _install_capture_fixture_on_item(request, capture_class):
|
|||
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`
|
||||
fixtures.
|
||||
|
@ -424,7 +410,7 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
|||
return EncodedFile(f, encoding or default_encoding)
|
||||
|
||||
|
||||
class EncodedFile(object):
|
||||
class EncodedFile:
|
||||
errors = "strict" # possibly needed by py3 code (issue555)
|
||||
|
||||
def __init__(self, buffer, encoding):
|
||||
|
@ -432,9 +418,9 @@ class EncodedFile(object):
|
|||
self.encoding = encoding
|
||||
|
||||
def write(self, obj):
|
||||
if isinstance(obj, six.text_type):
|
||||
if isinstance(obj, str):
|
||||
obj = obj.encode(self.encoding, "replace")
|
||||
elif _PY3:
|
||||
else:
|
||||
raise TypeError(
|
||||
"write() argument must be str, not {}".format(type(obj).__name__)
|
||||
)
|
||||
|
@ -460,7 +446,7 @@ class EncodedFile(object):
|
|||
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
|
||||
|
||||
|
||||
class MultiCapture(object):
|
||||
class MultiCapture:
|
||||
out = err = in_ = None
|
||||
_state = None
|
||||
|
||||
|
@ -473,7 +459,7 @@ class MultiCapture(object):
|
|||
self.err = Capture(2)
|
||||
|
||||
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.err,
|
||||
self.in_,
|
||||
|
@ -539,12 +525,12 @@ class MultiCapture(object):
|
|||
)
|
||||
|
||||
|
||||
class NoCapture(object):
|
||||
class NoCapture:
|
||||
EMPTY_BUFFER = None
|
||||
__init__ = start = done = suspend = resume = lambda *args: None
|
||||
|
||||
|
||||
class FDCaptureBinary(object):
|
||||
class FDCaptureBinary:
|
||||
"""Capture IO to/from a given os-level filedescriptor.
|
||||
|
||||
snap() produces `bytes`
|
||||
|
@ -578,10 +564,8 @@ class FDCaptureBinary(object):
|
|||
self.tmpfile_fd = tmpfile.fileno()
|
||||
|
||||
def __repr__(self):
|
||||
return "<FDCapture %s oldfd=%s _state=%r>" % (
|
||||
self.targetfd,
|
||||
getattr(self, "targetfd_save", None),
|
||||
self._state,
|
||||
return "<FDCapture {} oldfd={} _state={!r}>".format(
|
||||
self.targetfd, getattr(self, "targetfd_save", None), self._state
|
||||
)
|
||||
|
||||
def start(self):
|
||||
|
@ -608,7 +592,7 @@ class FDCaptureBinary(object):
|
|||
os.dup2(targetfd_save, self.targetfd)
|
||||
os.close(targetfd_save)
|
||||
self.syscapture.done()
|
||||
_attempt_to_close_capture_file(self.tmpfile)
|
||||
self.tmpfile.close()
|
||||
self._state = "done"
|
||||
|
||||
def suspend(self):
|
||||
|
@ -623,7 +607,7 @@ class FDCaptureBinary(object):
|
|||
|
||||
def writeorg(self, data):
|
||||
""" 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
|
||||
os.write(self.targetfd_save, data)
|
||||
|
||||
|
@ -637,14 +621,14 @@ class FDCapture(FDCaptureBinary):
|
|||
EMPTY_BUFFER = str()
|
||||
|
||||
def snap(self):
|
||||
res = super(FDCapture, self).snap()
|
||||
res = super().snap()
|
||||
enc = getattr(self.tmpfile, "encoding", None)
|
||||
if enc and isinstance(res, bytes):
|
||||
res = six.text_type(res, enc, "replace")
|
||||
res = str(res, enc, "replace")
|
||||
return res
|
||||
|
||||
|
||||
class SysCapture(object):
|
||||
class SysCapture:
|
||||
|
||||
EMPTY_BUFFER = str()
|
||||
_state = None
|
||||
|
@ -661,11 +645,8 @@ class SysCapture(object):
|
|||
self.tmpfile = tmpfile
|
||||
|
||||
def __repr__(self):
|
||||
return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % (
|
||||
self.name,
|
||||
self._old,
|
||||
self.tmpfile,
|
||||
self._state,
|
||||
return "<SysCapture {} _old={!r}, tmpfile={!r} _state={!r}>".format(
|
||||
self.name, self._old, self.tmpfile, self._state
|
||||
)
|
||||
|
||||
def start(self):
|
||||
|
@ -681,7 +662,7 @@ class SysCapture(object):
|
|||
def done(self):
|
||||
setattr(sys, self.name, self._old)
|
||||
del self._old
|
||||
_attempt_to_close_capture_file(self.tmpfile)
|
||||
self.tmpfile.close()
|
||||
self._state = "done"
|
||||
|
||||
def suspend(self):
|
||||
|
@ -707,7 +688,7 @@ class SysCaptureBinary(SysCapture):
|
|||
return res
|
||||
|
||||
|
||||
class DontReadFromInput(six.Iterator):
|
||||
class DontReadFromInput:
|
||||
"""Temporary stub class. Ideally when stdin is accessed, the
|
||||
capturing should be turned off, with possibly all data captured
|
||||
so far sent to the screen. This should be configurable, though,
|
||||
|
@ -738,10 +719,7 @@ class DontReadFromInput(six.Iterator):
|
|||
|
||||
@property
|
||||
def buffer(self):
|
||||
if sys.version_info >= (3, 0):
|
||||
return self
|
||||
else:
|
||||
raise AttributeError("redirected stdin has no attribute buffer")
|
||||
return self
|
||||
|
||||
|
||||
def _colorama_workaround():
|
||||
|
@ -837,14 +815,3 @@ def _py36_windowsconsoleio_workaround(stream):
|
|||
sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||
sys.stdout = _reopen_stdio(sys.stdout, "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()
|
||||
|
|
|
@ -1,64 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
python version compatibility code
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import codecs
|
||||
import functools
|
||||
import inspect
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from inspect import Parameter
|
||||
from inspect import signature
|
||||
|
||||
import py
|
||||
import six
|
||||
from six import text_type
|
||||
|
||||
import _pytest
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.outcomes import fail
|
||||
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()
|
||||
|
||||
PY35 = sys.version_info[:2] >= (3, 5)
|
||||
PY36 = sys.version_info[:2] >= (3, 6)
|
||||
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
|
||||
MODULE_NOT_FOUND_ERROR = (
|
||||
"ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError"
|
||||
)
|
||||
|
||||
|
||||
def _format_args(func):
|
||||
|
@ -184,10 +148,10 @@ def get_default_arg_names(function):
|
|||
|
||||
|
||||
_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(
|
||||
{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)
|
||||
|
||||
|
||||
if _PY3:
|
||||
STRING_TYPES = bytes, str
|
||||
UNICODE_TYPES = six.text_type
|
||||
STRING_TYPES = bytes, str
|
||||
|
||||
if PY35:
|
||||
|
||||
def _bytes_to_ascii(val):
|
||||
return val.decode("ascii", "backslashreplace")
|
||||
def _bytes_to_ascii(val):
|
||||
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:
|
||||
|
||||
def _bytes_to_ascii(val):
|
||||
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)
|
||||
ret = val.encode("unicode_escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
|
||||
|
||||
else:
|
||||
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):
|
||||
class _PytestWrapper:
|
||||
"""Dummy wrapper around a function object for internal use only.
|
||||
|
||||
Used to correctly unwrap the underlying function object
|
||||
|
@ -357,36 +285,6 @@ def safe_isclass(obj):
|
|||
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 = (
|
||||
"Collector",
|
||||
"Module",
|
||||
|
@ -410,30 +308,15 @@ def _setup_collect_fakemodule():
|
|||
setattr(pytest.collect, attr, getattr(pytest, attr))
|
||||
|
||||
|
||||
if _PY2:
|
||||
# Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
|
||||
from py.io import TextIO
|
||||
class CaptureIO(io.TextIOWrapper):
|
||||
def __init__(self):
|
||||
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
|
||||
|
||||
class CaptureIO(TextIO):
|
||||
@property
|
||||
def encoding(self):
|
||||
return getattr(self, "_encoding", "UTF-8")
|
||||
def getvalue(self):
|
||||
return self.buffer.getvalue().decode("UTF-8")
|
||||
|
||||
|
||||
else:
|
||||
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):
|
||||
class FuncargnamesCompatAttr:
|
||||
""" helper class so that Metafunc, Function and FixtureRequest
|
||||
don't need to each define the "funcargnames" compatibility attribute.
|
||||
"""
|
||||
|
@ -442,16 +325,3 @@ class FuncargnamesCompatAttr(object):
|
|||
def funcargnames(self):
|
||||
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
|
||||
return self.fixturenames
|
||||
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def lru_cache(*_, **__):
|
||||
def dec(fn):
|
||||
return fn
|
||||
|
||||
return dec
|
||||
|
||||
|
||||
else:
|
||||
from functools import lru_cache # noqa: F401
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" 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 copy
|
||||
import inspect
|
||||
|
@ -12,10 +7,10 @@ import shlex
|
|||
import sys
|
||||
import types
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
|
||||
import importlib_metadata
|
||||
import py
|
||||
import six
|
||||
from packaging.version import Version
|
||||
from pluggy import HookimplMarker
|
||||
from pluggy import HookspecMarker
|
||||
|
@ -31,8 +26,6 @@ from .findpaths import exists
|
|||
from _pytest import deprecated
|
||||
from _pytest._code import ExceptionInfo
|
||||
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 Skipped
|
||||
from _pytest.warning_types import PytestConfigWarning
|
||||
|
@ -73,7 +66,7 @@ def main(args=None, plugins=None):
|
|||
if exc_info.traceback
|
||||
else exc_info.exconly()
|
||||
)
|
||||
formatted_tb = safe_str(exc_repr)
|
||||
formatted_tb = str(exc_repr)
|
||||
for line in formatted_tb.splitlines():
|
||||
tw.line(line.rstrip(), red=True)
|
||||
return 4
|
||||
|
@ -89,7 +82,7 @@ def main(args=None, plugins=None):
|
|||
return EXIT_USAGEERROR
|
||||
|
||||
|
||||
class cmdline(object): # compatibility namespace
|
||||
class cmdline: # compatibility namespace
|
||||
main = staticmethod(main)
|
||||
|
||||
|
||||
|
@ -195,7 +188,7 @@ def _prepareconfig(args=None, plugins=None):
|
|||
try:
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
if isinstance(plugin, six.string_types):
|
||||
if isinstance(plugin, str):
|
||||
pluginmanager.consider_pluginarg(plugin)
|
||||
else:
|
||||
pluginmanager.register(plugin)
|
||||
|
@ -222,7 +215,7 @@ class PytestPluginManager(PluginManager):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(PytestPluginManager, self).__init__("pytest")
|
||||
super().__init__("pytest")
|
||||
self._conftest_plugins = set()
|
||||
|
||||
# state related to local conftest plugins
|
||||
|
@ -270,7 +263,7 @@ class PytestPluginManager(PluginManager):
|
|||
return
|
||||
|
||||
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)
|
||||
if not inspect.isroutine(method):
|
||||
|
@ -289,9 +282,7 @@ class PytestPluginManager(PluginManager):
|
|||
return opts
|
||||
|
||||
def parse_hookspec_opts(self, module_or_class, name):
|
||||
opts = super(PytestPluginManager, self).parse_hookspec_opts(
|
||||
module_or_class, name
|
||||
)
|
||||
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||
if opts is None:
|
||||
method = getattr(module_or_class, name)
|
||||
|
||||
|
@ -318,7 +309,7 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
)
|
||||
return
|
||||
ret = super(PytestPluginManager, self).register(plugin, name)
|
||||
ret = super().register(plugin, name)
|
||||
if ret:
|
||||
self.hook.pytest_plugin_registered.call_historic(
|
||||
kwargs=dict(plugin=plugin, manager=self)
|
||||
|
@ -403,12 +394,6 @@ class PytestPluginManager(PluginManager):
|
|||
else:
|
||||
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
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
|
@ -485,7 +470,7 @@ class PytestPluginManager(PluginManager):
|
|||
while i < n:
|
||||
opt = args[i]
|
||||
i += 1
|
||||
if isinstance(opt, six.string_types):
|
||||
if isinstance(opt, str):
|
||||
if opt == "-p":
|
||||
try:
|
||||
parg = args[i]
|
||||
|
@ -546,7 +531,7 @@ class PytestPluginManager(PluginManager):
|
|||
# "terminal" or "capture". Those plugins are registered under their
|
||||
# basename for historic purposes but must be imported with the
|
||||
# _pytest prefix.
|
||||
assert isinstance(modname, six.string_types), (
|
||||
assert isinstance(modname, str), (
|
||||
"module name as text required, got %r" % modname
|
||||
)
|
||||
modname = str(modname)
|
||||
|
@ -564,20 +549,19 @@ class PytestPluginManager(PluginManager):
|
|||
try:
|
||||
__import__(importspec)
|
||||
except ImportError as e:
|
||||
new_exc_message = 'Error importing plugin "%s": %s' % (
|
||||
modname,
|
||||
safe_str(e.args[0]),
|
||||
new_exc_message = 'Error importing plugin "{}": {}'.format(
|
||||
modname, str(e.args[0])
|
||||
)
|
||||
new_exc = ImportError(new_exc_message)
|
||||
tb = sys.exc_info()[2]
|
||||
|
||||
six.reraise(ImportError, new_exc, tb)
|
||||
raise new_exc.with_traceback(tb)
|
||||
|
||||
except Skipped as e:
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
||||
_issue_warning_captured(
|
||||
PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
|
||||
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
|
||||
self.hook,
|
||||
stacklevel=1,
|
||||
)
|
||||
|
@ -595,7 +579,7 @@ def _get_plugin_specs_as_list(specs):
|
|||
empty list is returned.
|
||||
"""
|
||||
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 []
|
||||
if not isinstance(specs, (list, tuple)):
|
||||
raise UsageError(
|
||||
|
@ -613,7 +597,7 @@ def _ensure_removed_sysmodule(modname):
|
|||
pass
|
||||
|
||||
|
||||
class Notset(object):
|
||||
class Notset:
|
||||
def __repr__(self):
|
||||
return "<NOTSET>"
|
||||
|
||||
|
@ -633,7 +617,7 @@ def _iter_rewritable_modules(package_files):
|
|||
yield package_name
|
||||
|
||||
|
||||
class Config(object):
|
||||
class Config:
|
||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||
|
||||
def __init__(self, pluginmanager):
|
||||
|
@ -644,7 +628,7 @@ class Config(object):
|
|||
|
||||
_a = FILE_OR_DIR
|
||||
self._parser = Parser(
|
||||
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
|
||||
usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a),
|
||||
processopt=self._processopt,
|
||||
)
|
||||
#: a pluginmanager instance
|
||||
|
@ -932,7 +916,7 @@ class Config(object):
|
|||
try:
|
||||
description, type, default = self._parser._inidict[name]
|
||||
except KeyError:
|
||||
raise ValueError("unknown configuration value: %r" % (name,))
|
||||
raise ValueError("unknown configuration value: {!r}".format(name))
|
||||
value = self._get_override_ini_value(name)
|
||||
if value is None:
|
||||
try:
|
||||
|
@ -1009,8 +993,8 @@ class Config(object):
|
|||
if skip:
|
||||
import pytest
|
||||
|
||||
pytest.skip("no %r option found" % (name,))
|
||||
raise ValueError("no option named %r" % (name,))
|
||||
pytest.skip("no {!r} option found".format(name))
|
||||
raise ValueError("no option named {!r}".format(name))
|
||||
|
||||
def getvalue(self, name, path=None):
|
||||
""" (deprecated, use getoption()) """
|
||||
|
@ -1098,4 +1082,4 @@ def _strtobool(val):
|
|||
elif val in ("n", "no", "f", "false", "off", "0"):
|
||||
return 0
|
||||
else:
|
||||
raise ValueError("invalid truth value %r" % (val,))
|
||||
raise ValueError("invalid truth value {!r}".format(val))
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import argparse
|
||||
import warnings
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
FILE_OR_DIR = "file_or_dir"
|
||||
|
||||
|
||||
class Parser(object):
|
||||
class Parser:
|
||||
""" Parser for command line arguments and ini-file values.
|
||||
|
||||
:ivar extra_info: dict of generic param -> value to display in case
|
||||
|
@ -145,12 +143,12 @@ class ArgumentError(Exception):
|
|||
|
||||
def __str__(self):
|
||||
if self.option_id:
|
||||
return "option %s: %s" % (self.option_id, self.msg)
|
||||
return "option {}: {}".format(self.option_id, self.msg)
|
||||
else:
|
||||
return self.msg
|
||||
|
||||
|
||||
class Argument(object):
|
||||
class Argument:
|
||||
"""class that mimics the necessary behaviour of optparse.Option
|
||||
|
||||
it's currently a least effort implementation
|
||||
|
@ -179,7 +177,7 @@ class Argument(object):
|
|||
pass
|
||||
else:
|
||||
# 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":
|
||||
warnings.warn(
|
||||
"`type` argument to addoption() is the string %r."
|
||||
|
@ -282,7 +280,7 @@ class Argument(object):
|
|||
return "Argument({})".format(", ".join(args))
|
||||
|
||||
|
||||
class OptionGroup(object):
|
||||
class OptionGroup:
|
||||
def __init__(self, name, description="", parser=None):
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
@ -337,10 +335,10 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
|
||||
def error(self, message):
|
||||
"""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"):
|
||||
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
|
||||
msg = "{} ({})".format(msg, self._parser._config_source_hint)
|
||||
|
||||
raise UsageError(self.format_usage() + msg)
|
||||
|
||||
|
@ -352,7 +350,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
if arg and arg[0] == "-":
|
||||
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
|
||||
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))
|
||||
getattr(args, FILE_OR_DIR).extend(argv)
|
||||
return args
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
class UsageError(Exception):
|
||||
""" error in pytest usage or invocation"""
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
import py
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" interactive debugging with PDB, the Python Debugger. """
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import pdb
|
||||
import sys
|
||||
|
@ -74,7 +69,7 @@ def pytest_configure(config):
|
|||
config._cleanup.append(fin)
|
||||
|
||||
|
||||
class pytestPDB(object):
|
||||
class pytestPDB:
|
||||
""" Pseudo PDB that defers to the real pdb. """
|
||||
|
||||
_pluginmanager = None
|
||||
|
@ -128,18 +123,18 @@ class pytestPDB(object):
|
|||
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
|
||||
import _pytest.config
|
||||
|
||||
class PytestPdbWrapper(pdb_cls, object):
|
||||
class PytestPdbWrapper(pdb_cls):
|
||||
_pytest_capman = capman
|
||||
_continued = False
|
||||
|
||||
def do_debug(self, arg):
|
||||
cls._recursive_debug += 1
|
||||
ret = super(PytestPdbWrapper, self).do_debug(arg)
|
||||
ret = super().do_debug(arg)
|
||||
cls._recursive_debug -= 1
|
||||
return ret
|
||||
|
||||
def do_continue(self, arg):
|
||||
ret = super(PytestPdbWrapper, self).do_continue(arg)
|
||||
ret = super().do_continue(arg)
|
||||
if cls._recursive_debug == 0:
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
|
@ -171,7 +166,7 @@ class pytestPDB(object):
|
|||
could be handled, but this would require to wrap the
|
||||
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:
|
||||
outcomes.exit("Quitting debugger")
|
||||
|
@ -187,7 +182,7 @@ class pytestPDB(object):
|
|||
Needed after do_continue resumed, and entering another
|
||||
breakpoint again.
|
||||
"""
|
||||
ret = super(PytestPdbWrapper, self).setup(f, tb)
|
||||
ret = super().setup(f, tb)
|
||||
if not ret and self._continued:
|
||||
# pdb.setup() returns True if the command wants to exit
|
||||
# from the interaction: do not suspend capturing then.
|
||||
|
@ -196,7 +191,7 @@ class pytestPDB(object):
|
|||
return ret
|
||||
|
||||
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:
|
||||
# Find last non-hidden frame.
|
||||
i = max(0, len(stack) - 1)
|
||||
|
@ -230,7 +225,7 @@ class pytestPDB(object):
|
|||
else:
|
||||
capturing = cls._is_capturing(capman)
|
||||
if capturing == "global":
|
||||
tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,))
|
||||
tw.sep(">", "PDB {} (IO-capturing turned off)".format(method))
|
||||
elif capturing:
|
||||
tw.sep(
|
||||
">",
|
||||
|
@ -238,7 +233,7 @@ class pytestPDB(object):
|
|||
% (method, capturing),
|
||||
)
|
||||
else:
|
||||
tw.sep(">", "PDB %s" % (method,))
|
||||
tw.sep(">", "PDB {}".format(method))
|
||||
|
||||
_pdb = cls._import_pdb_cls(capman)(**kwargs)
|
||||
|
||||
|
@ -254,7 +249,7 @@ class pytestPDB(object):
|
|||
_pdb.set_trace(frame)
|
||||
|
||||
|
||||
class PdbInvoke(object):
|
||||
class PdbInvoke:
|
||||
def pytest_exception_interact(self, node, call, report):
|
||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
|
@ -269,7 +264,7 @@ class PdbInvoke(object):
|
|||
post_mortem(tb)
|
||||
|
||||
|
||||
class PdbTrace(object):
|
||||
class PdbTrace:
|
||||
@hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
_test_pytest_function(pyfuncitem)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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.
|
||||
|
@ -9,10 +8,6 @@ be removed when the time comes.
|
|||
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||
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 RemovedInPytest4Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" 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 platform
|
||||
import sys
|
||||
|
@ -126,7 +121,7 @@ class ReprFailDoctest(TerminalRepr):
|
|||
|
||||
class MultipleDoctestFailures(Exception):
|
||||
def __init__(self, failures):
|
||||
super(MultipleDoctestFailures, self).__init__()
|
||||
super().__init__()
|
||||
self.failures = failures
|
||||
|
||||
|
||||
|
@ -181,7 +176,7 @@ def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=T
|
|||
|
||||
class DoctestItem(pytest.Item):
|
||||
def __init__(self, name, parent, runner=None, dtest=None):
|
||||
super(DoctestItem, self).__init__(name, parent)
|
||||
super().__init__(name, parent)
|
||||
self.runner = runner
|
||||
self.dtest = dtest
|
||||
self.obj = None
|
||||
|
@ -258,7 +253,7 @@ class DoctestItem(pytest.Item):
|
|||
]
|
||||
indent = ">>>"
|
||||
for line in example.source.splitlines():
|
||||
lines.append("??? %s %s" % (indent, line))
|
||||
lines.append("??? {} {}".format(indent, line))
|
||||
indent = "..."
|
||||
if isinstance(failure, doctest.DocTestFailure):
|
||||
lines += checker.output_difference(
|
||||
|
@ -271,7 +266,7 @@ class DoctestItem(pytest.Item):
|
|||
reprlocation_lines.append((reprlocation, lines))
|
||||
return ReprFailDoctest(reprlocation_lines)
|
||||
else:
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
return super().repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
|
||||
|
@ -333,7 +328,6 @@ class DoctestTextfile(pytest.Module):
|
|||
checker=_get_checker(),
|
||||
continue_on_failure=_get_continue_on_failure(self.config),
|
||||
)
|
||||
_fix_spoof_python2(runner, encoding)
|
||||
|
||||
parser = doctest.DocTestParser()
|
||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||
|
@ -539,32 +533,6 @@ def _get_report_choice(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")
|
||||
def doctest_namespace():
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import inspect
|
||||
import itertools
|
||||
|
@ -14,7 +9,6 @@ from collections import OrderedDict
|
|||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest
|
||||
from _pytest import nodes
|
||||
|
@ -41,7 +35,7 @@ from _pytest.outcomes import TEST_OUTCOME
|
|||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class PseudoFixtureDef(object):
|
||||
class PseudoFixtureDef:
|
||||
cached_result = attr.ib()
|
||||
scope = attr.ib()
|
||||
|
||||
|
@ -81,7 +75,7 @@ def scopeproperty(name=None, doc=None):
|
|||
if func.__name__ in scope2props[self.scope]:
|
||||
return func(self)
|
||||
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__)
|
||||
|
@ -94,7 +88,7 @@ def get_scope_package(node, fixturedef):
|
|||
|
||||
cls = pytest.Package
|
||||
current = node
|
||||
fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py")
|
||||
fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
|
||||
while current and (
|
||||
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)
|
||||
class FuncFixtureInfo(object):
|
||||
class FuncFixtureInfo:
|
||||
# original function argument names
|
||||
argnames = attr.ib(type=tuple)
|
||||
# argnames that function immediately requires. These include argnames +
|
||||
|
@ -657,7 +651,7 @@ class SubRequest(FixtureRequest):
|
|||
self._fixturemanager = request._fixturemanager
|
||||
|
||||
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):
|
||||
self._fixturedef.addfinalizer(finalizer)
|
||||
|
@ -670,7 +664,7 @@ class SubRequest(FixtureRequest):
|
|||
fixturedef.addfinalizer(
|
||||
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()
|
||||
|
@ -723,7 +717,7 @@ class FixtureLookupError(LookupError):
|
|||
error_msg = "file %s, line %s: source code not available"
|
||||
addline(error_msg % (fspath, lineno + 1))
|
||||
else:
|
||||
addline("file %s, line %s" % (fspath, lineno + 1))
|
||||
addline("file {}, line {}".format(fspath, lineno + 1))
|
||||
for i, line in enumerate(lines):
|
||||
line = line.rstrip()
|
||||
addline(" " + line)
|
||||
|
@ -779,7 +773,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
|||
|
||||
def fail_fixturefunc(fixturefunc, msg):
|
||||
fs, lineno = getfslineno(fixturefunc)
|
||||
location = "%s:%s" % (fs, lineno + 1)
|
||||
location = "{}:{}".format(fs, lineno + 1)
|
||||
source = _pytest._code.Source(fixturefunc)
|
||||
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. """
|
||||
|
||||
def __init__(
|
||||
|
@ -853,10 +847,10 @@ class FixtureDef(object):
|
|||
except: # noqa
|
||||
exceptions.append(sys.exc_info())
|
||||
if exceptions:
|
||||
e = exceptions[0]
|
||||
_, val, tb = exceptions[0]
|
||||
# Ensure to not keep frame references through traceback.
|
||||
del exceptions
|
||||
six.reraise(*e)
|
||||
raise val.with_traceback(tb)
|
||||
finally:
|
||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
||||
|
@ -882,7 +876,8 @@ class FixtureDef(object):
|
|||
result, cache_key, err = cached_result
|
||||
if my_cache_key == cache_key:
|
||||
if err is not None:
|
||||
six.reraise(*err)
|
||||
_, val, tb = err
|
||||
raise val.with_traceback(tb)
|
||||
else:
|
||||
return result
|
||||
# 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)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef argname=%r scope=%r baseid=%r>" % (
|
||||
self.argname,
|
||||
self.scope,
|
||||
self.baseid,
|
||||
return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
|
||||
self.argname, 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__
|
||||
)
|
||||
|
||||
@six.wraps(function)
|
||||
@functools.wraps(function)
|
||||
def result(*args, **kwargs):
|
||||
fail(message, pytrace=False)
|
||||
|
||||
|
@ -969,7 +962,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
|||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class FixtureFunctionMarker(object):
|
||||
class FixtureFunctionMarker:
|
||||
scope = attr.ib()
|
||||
params = attr.ib(converter=attr.converters.optional(tuple))
|
||||
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
|
||||
from this class.
|
||||
|
@ -1303,11 +1296,7 @@ class FixtureManager(object):
|
|||
# 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
|
||||
# when pytest itself calls the fixture function
|
||||
if six.PY2 and unittest:
|
||||
# hack on Python 2 because of the unbound methods
|
||||
obj = get_real_func(obj)
|
||||
else:
|
||||
obj = get_real_method(obj, holderobj)
|
||||
obj = get_real_method(obj, holderobj)
|
||||
|
||||
fixture_def = FixtureDef(
|
||||
self,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Provides a function to report all internal modules for using freezing tools
|
||||
pytest
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
|
||||
def freeze_includes():
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" version info, help messages, tracing configuration. """
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from argparse import Action
|
||||
|
@ -24,7 +19,7 @@ class HelpAction(Action):
|
|||
"""
|
||||
|
||||
def __init__(self, option_strings, dest=None, default=False, help=None):
|
||||
super(HelpAction, self).__init__(
|
||||
super().__init__(
|
||||
option_strings=option_strings,
|
||||
dest=dest,
|
||||
const=True,
|
||||
|
@ -122,7 +117,7 @@ def pytest_cmdline_parse():
|
|||
def showversion(config):
|
||||
p = py.path.local(pytest.__file__)
|
||||
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)
|
||||
if plugininfo:
|
||||
|
@ -160,7 +155,7 @@ def showhelp(config):
|
|||
help, type, default = config._parser._inidict[name]
|
||||
if type is None:
|
||||
type = "string"
|
||||
spec = "%s (%s):" % (name, type)
|
||||
spec = "{} ({}):".format(name, type)
|
||||
tw.write(" %s" % spec)
|
||||
spec_len = len(spec)
|
||||
if spec_len > (indent_len - 3):
|
||||
|
@ -194,7 +189,7 @@ def showhelp(config):
|
|||
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
|
||||
]
|
||||
for name, help in vars:
|
||||
tw.line(" %-24s %s" % (name, help))
|
||||
tw.line(" {:<24} {}".format(name, help))
|
||||
tw.line()
|
||||
tw.line()
|
||||
|
||||
|
@ -221,7 +216,7 @@ def getpluginversioninfo(config):
|
|||
lines.append("setuptools registered plugins:")
|
||||
for plugin, dist in plugininfo:
|
||||
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)
|
||||
return lines
|
||||
|
||||
|
@ -229,7 +224,9 @@ def getpluginversioninfo(config):
|
|||
def pytest_report_header(config):
|
||||
lines = []
|
||||
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)
|
||||
if verinfo:
|
||||
|
@ -243,5 +240,5 @@ def pytest_report_header(config):
|
|||
r = plugin.__file__
|
||||
else:
|
||||
r = repr(plugin)
|
||||
lines.append(" %-20s: %s" % (name, r))
|
||||
lines.append(" {:<20}: {}".format(name, r))
|
||||
return lines
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
report test results in JUnit-XML format,
|
||||
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/
|
||||
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 os
|
||||
import re
|
||||
|
@ -20,16 +15,11 @@ import sys
|
|||
import time
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest import nodes
|
||||
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):
|
||||
pass
|
||||
|
@ -43,12 +33,12 @@ class Junit(py.xml.Namespace):
|
|||
_legal_chars = (0x09, 0x0A, 0x0D)
|
||||
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
|
||||
_legal_xml_re = [
|
||||
u"%s-%s" % (six.unichr(low), six.unichr(high))
|
||||
"{}-{}".format(chr(low), chr(high))
|
||||
for (low, high) in _legal_ranges
|
||||
if low < sys.maxunicode
|
||||
]
|
||||
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re
|
||||
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re))
|
||||
_legal_xml_re = [chr(x) for x in _legal_chars] + _legal_xml_re
|
||||
illegal_xml_re = re.compile("[^%s]" % "".join(_legal_xml_re))
|
||||
del _legal_chars
|
||||
del _legal_ranges
|
||||
del _legal_xml_re
|
||||
|
@ -60,9 +50,9 @@ def bin_xml_escape(arg):
|
|||
def repl(matchobj):
|
||||
i = ord(matchobj.group())
|
||||
if i <= 0xFF:
|
||||
return u"#x%02X" % i
|
||||
return "#x%02X" % i
|
||||
else:
|
||||
return u"#x%04X" % i
|
||||
return "#x%04X" % i
|
||||
|
||||
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"]
|
||||
|
||||
|
||||
class _NodeReporter(object):
|
||||
class _NodeReporter:
|
||||
def __init__(self, nodeid, xml):
|
||||
self.id = nodeid
|
||||
self.xml = xml
|
||||
|
@ -229,7 +219,7 @@ class _NodeReporter(object):
|
|||
else:
|
||||
if hasattr(report.longrepr, "reprcrash"):
|
||||
message = report.longrepr.reprcrash.message
|
||||
elif isinstance(report.longrepr, six.string_types):
|
||||
elif isinstance(report.longrepr, str):
|
||||
message = report.longrepr
|
||||
else:
|
||||
message = str(report.longrepr)
|
||||
|
@ -268,7 +258,7 @@ class _NodeReporter(object):
|
|||
filename, lineno, skipreason = report.longrepr
|
||||
if skipreason.startswith("Skipped: "):
|
||||
skipreason = skipreason[9:]
|
||||
details = "%s:%s: %s" % (filename, lineno, skipreason)
|
||||
details = "{}:{}: {}".format(filename, lineno, skipreason)
|
||||
|
||||
self.append(
|
||||
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
|
||||
type"""
|
||||
__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"
|
||||
raise TypeError(msg.format(param=param, g=type(v).__name__))
|
||||
|
||||
|
@ -473,7 +463,7 @@ def mangle_test_address(address):
|
|||
return names
|
||||
|
||||
|
||||
class LogXML(object):
|
||||
class LogXML:
|
||||
def __init__(
|
||||
self,
|
||||
logfile,
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" Access and control log capturing. """
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.compat import dummy_context_manager
|
||||
|
@ -39,14 +33,11 @@ class ColoredLevelFormatter(logging.Formatter):
|
|||
logging.DEBUG: {"purple"},
|
||||
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):
|
||||
super(ColoredLevelFormatter, self).__init__(*args, **kwargs)
|
||||
if six.PY2:
|
||||
self._original_fmt = self._fmt
|
||||
else:
|
||||
self._original_fmt = self._style._fmt
|
||||
super().__init__(*args, **kwargs)
|
||||
self._original_fmt = self._style._fmt
|
||||
self._level_to_fmt_mapping = {}
|
||||
|
||||
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
|
||||
|
@ -70,41 +61,35 @@ class ColoredLevelFormatter(logging.Formatter):
|
|||
|
||||
def format(self, record):
|
||||
fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
|
||||
if six.PY2:
|
||||
self._fmt = fmt
|
||||
self._style._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:
|
||||
self._style._fmt = fmt
|
||||
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__
|
||||
return self._fmt % record.__dict__
|
||||
|
||||
|
||||
def get_option_ini(config, *names):
|
||||
|
@ -246,7 +231,7 @@ class LogCaptureHandler(logging.StreamHandler):
|
|||
self.stream = py.io.TextIO()
|
||||
|
||||
|
||||
class LogCaptureFixture(object):
|
||||
class LogCaptureFixture:
|
||||
"""Provides access and control of log capturing."""
|
||||
|
||||
def __init__(self, item):
|
||||
|
@ -393,7 +378,7 @@ def get_actual_log_level(config, *setting_names):
|
|||
else:
|
||||
return
|
||||
|
||||
if isinstance(log_level, six.string_types):
|
||||
if isinstance(log_level, str):
|
||||
log_level = log_level.upper()
|
||||
try:
|
||||
return int(getattr(logging, log_level, log_level))
|
||||
|
@ -412,7 +397,7 @@ def pytest_configure(config):
|
|||
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
||||
|
||||
|
||||
class LoggingPlugin(object):
|
||||
class LoggingPlugin:
|
||||
"""Attaches to the logging module and captures log messages for each test.
|
||||
"""
|
||||
|
||||
|
@ -475,8 +460,7 @@ class LoggingPlugin(object):
|
|||
else:
|
||||
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
|
||||
|
||||
def _setup_cli_logging(self):
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" 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 functools
|
||||
import os
|
||||
|
@ -14,7 +8,6 @@ import warnings
|
|||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import nodes
|
||||
|
@ -172,7 +165,7 @@ def pytest_addoption(parser):
|
|||
)
|
||||
|
||||
|
||||
class _ConfigDeprecated(object):
|
||||
class _ConfigDeprecated:
|
||||
def __init__(self, config):
|
||||
self.__dict__["_config"] = config
|
||||
|
||||
|
@ -311,10 +304,7 @@ def pytest_ignore_collect(path, config):
|
|||
if excludeglobopt:
|
||||
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
|
||||
|
||||
if any(
|
||||
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
|
||||
for glob in ignore_globs
|
||||
):
|
||||
if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
|
||||
return True
|
||||
|
||||
allow_in_venv = config.getoption("collect_in_virtualenv")
|
||||
|
@ -342,47 +332,7 @@ def pytest_collection_modifyitems(items, config):
|
|||
items[:] = remaining
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
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):
|
||||
class FSHookProxy:
|
||||
def __init__(self, fspath, pm, remove_mods):
|
||||
self.fspath = fspath
|
||||
self.pm = pm
|
||||
|
@ -522,8 +472,8 @@ class Session(nodes.FSCollector):
|
|||
if self._notfound:
|
||||
errors = []
|
||||
for arg, exc in self._notfound:
|
||||
line = "(no name %r in any of %r)" % (arg, exc.args[0])
|
||||
errors.append("not found: %s\n%s" % (arg, line))
|
||||
line = "(no name {!r} in any of {!r})".format(arg, exc.args[0])
|
||||
errors.append("not found: {}\n{}".format(arg, line))
|
||||
# XXX: test this
|
||||
raise UsageError(*errors)
|
||||
if not genitems:
|
||||
|
@ -540,8 +490,7 @@ class Session(nodes.FSCollector):
|
|||
self.trace("processing argument", arg)
|
||||
self.trace.root.indent += 1
|
||||
try:
|
||||
for x in self._collect(arg):
|
||||
yield x
|
||||
yield from self._collect(arg)
|
||||
except NoMatch:
|
||||
# we are inside a make_report hook so
|
||||
# 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.
|
||||
# Let the Package collector deal with subnodes, don't collect here.
|
||||
if argpath.check(dir=1):
|
||||
assert not names, "invalid arg %r" % (arg,)
|
||||
assert not names, "invalid arg {!r}".format(arg)
|
||||
|
||||
seen_dirs = set()
|
||||
for path in argpath.visit(
|
||||
|
@ -623,15 +572,13 @@ class Session(nodes.FSCollector):
|
|||
if argpath.basename == "__init__.py":
|
||||
yield next(m[0].collect())
|
||||
return
|
||||
for y in m:
|
||||
yield y
|
||||
yield from m
|
||||
|
||||
def _collectfile(self, path, handle_dupes=True):
|
||||
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
||||
path,
|
||||
path.isdir(),
|
||||
path.exists(),
|
||||
path.islink(),
|
||||
assert (
|
||||
path.isfile()
|
||||
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
|
||||
path, path.isdir(), path.exists(), path.islink()
|
||||
)
|
||||
ihook = self.gethookproxy(path)
|
||||
if not self.isinitpath(path):
|
||||
|
@ -662,23 +609,14 @@ class Session(nodes.FSCollector):
|
|||
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
||||
return True
|
||||
|
||||
if six.PY2:
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
@staticmethod
|
||||
def _visit_filter(f):
|
||||
return f.check(file=1)
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
"""Convert a dotted module name to path."""
|
||||
try:
|
||||
with _patched_find_module():
|
||||
loader = pkgutil.find_loader(x)
|
||||
loader = pkgutil.find_loader(x)
|
||||
except ImportError:
|
||||
return x
|
||||
if loader is None:
|
||||
|
@ -686,8 +624,7 @@ class Session(nodes.FSCollector):
|
|||
# This method is sometimes invoked when AssertionRewritingHook, which
|
||||
# does not define a get_filename method, is already in place:
|
||||
try:
|
||||
with _patched_find_module():
|
||||
path = loader.get_filename(x)
|
||||
path = loader.get_filename(x)
|
||||
except AttributeError:
|
||||
# Retrieve path from AssertionRewritingHook:
|
||||
path = loader.modules[x][0].co_filename
|
||||
|
@ -769,6 +706,5 @@ class Session(nodes.FSCollector):
|
|||
rep = collect_one_node(node)
|
||||
if rep.passed:
|
||||
for subnode in rep.result:
|
||||
for x in self.genitems(subnode):
|
||||
yield x
|
||||
yield from self.genitems(subnode)
|
||||
node.ihook.pytest_collectreport(report=rep)
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
""" 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 matchmark
|
||||
from .structures import EMPTY_PARAMETERSET_OPTION
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue