commit
4f57d40a43
|
@ -7,17 +7,18 @@ repos:
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
language_version: python3
|
language_version: python3
|
||||||
- repo: https://github.com/asottile/blacken-docs
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
rev: v0.5.0
|
rev: v1.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==19.3b0]
|
additional_dependencies: [black==19.3b0]
|
||||||
language_version: python3
|
language_version: python3
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.2.2
|
rev: v2.2.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: fix-encoding-pragma
|
- id: fix-encoding-pragma
|
||||||
|
args: [--remove]
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
exclude: _pytest/debugging.py
|
exclude: _pytest/debugging.py
|
||||||
|
@ -31,14 +32,14 @@ repos:
|
||||||
rev: v1.4.0
|
rev: v1.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ['--application-directories=.:src']
|
args: ['--application-directories=.:src', --py3-plus]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v1.15.0
|
rev: v1.18.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--keep-percent-format]
|
args: [--py3-plus]
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.3.0
|
rev: v1.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
- repo: local
|
- repo: local
|
||||||
|
|
41
.travis.yml
41
.travis.yml
|
@ -19,18 +19,13 @@ install:
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
# OSX tests - first (in test stage), since they are the slower ones.
|
# OSX tests - first (in test stage), since they are the slower ones.
|
||||||
- &test-macos
|
- os: osx
|
||||||
os: osx
|
# NOTE: (tests with) pexpect appear to be buggy on Travis,
|
||||||
|
# at least with coverage.
|
||||||
|
# Log: https://travis-ci.org/pytest-dev/pytest/jobs/500358864
|
||||||
osx_image: xcode10.1
|
osx_image: xcode10.1
|
||||||
language: generic
|
language: generic
|
||||||
# Coverage for:
|
env: TOXENV=py37-xdist PYTEST_COVERAGE=1
|
||||||
# - py2 with symlink in test_cmdline_python_package_symlink.
|
|
||||||
env: TOXENV=py27-xdist PYTEST_COVERAGE=1
|
|
||||||
before_install:
|
|
||||||
- python -V
|
|
||||||
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 27
|
|
||||||
- <<: *test-macos
|
|
||||||
env: TOXENV=py37-pexpect,py37-xdist PYTEST_COVERAGE=1
|
|
||||||
before_install:
|
before_install:
|
||||||
- which python3
|
- which python3
|
||||||
- python3 -V
|
- python3 -V
|
||||||
|
@ -38,20 +33,14 @@ jobs:
|
||||||
- python -V
|
- python -V
|
||||||
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
|
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
|
||||||
|
|
||||||
# Full run of latest (major) supported versions, without xdist.
|
# Full run of latest supported version, without xdist.
|
||||||
- env: TOXENV=py27
|
- env: TOXENV=py37 PYTEST_COVERAGE=1
|
||||||
python: '2.7'
|
|
||||||
- env: TOXENV=py37
|
|
||||||
python: '3.7'
|
python: '3.7'
|
||||||
|
|
||||||
# Coverage tracking is slow with pypy, skip it.
|
# Coverage tracking is slow with pypy, skip it.
|
||||||
- env: TOXENV=pypy-xdist
|
|
||||||
python: 'pypy'
|
|
||||||
- env: TOXENV=pypy3-xdist
|
- env: TOXENV=pypy3-xdist
|
||||||
python: 'pypy3'
|
python: 'pypy3'
|
||||||
|
|
||||||
- env: TOXENV=py34-xdist
|
|
||||||
python: '3.4'
|
|
||||||
- env: TOXENV=py35-xdist
|
- env: TOXENV=py35-xdist
|
||||||
python: '3.5'
|
python: '3.5'
|
||||||
|
|
||||||
|
@ -62,12 +51,6 @@ jobs:
|
||||||
# Empty PYTEST_ADDOPTS to run this non-verbose.
|
# Empty PYTEST_ADDOPTS to run this non-verbose.
|
||||||
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
||||||
|
|
||||||
# Specialized factors for py27.
|
|
||||||
- env: TOXENV=py27-nobyte-numpy-xdist
|
|
||||||
python: '2.7'
|
|
||||||
- env: TOXENV=py27-pluggymaster-xdist
|
|
||||||
python: '2.7'
|
|
||||||
|
|
||||||
# Specialized factors for py37.
|
# Specialized factors for py37.
|
||||||
# Coverage for:
|
# Coverage for:
|
||||||
# - test_sys_breakpoint_interception (via pexpect).
|
# - test_sys_breakpoint_interception (via pexpect).
|
||||||
|
@ -81,12 +64,7 @@ jobs:
|
||||||
if: type = cron
|
if: type = cron
|
||||||
|
|
||||||
- stage: baseline
|
- stage: baseline
|
||||||
# Coverage for:
|
env: TOXENV=py36-xdist
|
||||||
# - _pytest.unittest._handle_skip (via pexpect).
|
|
||||||
env: TOXENV=py27-pexpect,py27-twisted PYTEST_COVERAGE=1
|
|
||||||
python: '2.7'
|
|
||||||
# Use py36 here for faster baseline.
|
|
||||||
- env: TOXENV=py36-xdist
|
|
||||||
python: '3.6'
|
python: '3.6'
|
||||||
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
|
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
|
||||||
cache:
|
cache:
|
||||||
|
@ -112,9 +90,6 @@ matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- python: '3.8-dev'
|
- python: '3.8-dev'
|
||||||
env: TOXENV=py38-xdist
|
env: TOXENV=py38-xdist
|
||||||
# Temporary (https://github.com/pytest-dev/pytest/pull/5334).
|
|
||||||
- env: TOXENV=pypy3-xdist
|
|
||||||
python: 'pypy3'
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- |
|
||||||
|
|
|
@ -18,6 +18,96 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 4.6.1 (2019-06-02)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#5354 <https://github.com/pytest-dev/pytest/issues/5354>`_: Fix ``pytest.mark.parametrize`` when the argvalues is an iterator.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5358 <https://github.com/pytest-dev/pytest/issues/5358>`_: Fix assertion rewriting of ``all()`` calls to deal with non-generators.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 4.6.0 (2019-05-31)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Important
|
||||||
|
---------
|
||||||
|
|
||||||
|
The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
|
||||||
|
|
||||||
|
For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/latest/py27-py34-deprecation.html>`__.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#4559 <https://github.com/pytest-dev/pytest/issues/4559>`_: Added the ``junit_log_passing_tests`` ini value which can be used to enable or disable logging of passing test output in the Junit XML file.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5062 <https://github.com/pytest-dev/pytest/issues/5062>`_: Unroll calls to ``all`` to full for-loops with assertion rewriting for better failure messages, especially when using Generator Expressions.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5063 <https://github.com/pytest-dev/pytest/issues/5063>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5091 <https://github.com/pytest-dev/pytest/issues/5091>`_: The output for ini options in ``--help`` has been improved.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5269 <https://github.com/pytest-dev/pytest/issues/5269>`_: ``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5311 <https://github.com/pytest-dev/pytest/issues/5311>`_: Captured logs that are output for each failing test are formatted using the
|
||||||
|
ColoredLevelFormatter.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5312 <https://github.com/pytest-dev/pytest/issues/5312>`_: Improved formatting of multiline log messages in Python 3.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#2064 <https://github.com/pytest-dev/pytest/issues/2064>`_: The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4908 <https://github.com/pytest-dev/pytest/issues/4908>`_: The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).
|
||||||
|
|
||||||
|
|
||||||
|
- `#5036 <https://github.com/pytest-dev/pytest/issues/5036>`_: Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5256 <https://github.com/pytest-dev/pytest/issues/5256>`_: Handle internal error due to a lone surrogate unicode character not being representable in Jython.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5257 <https://github.com/pytest-dev/pytest/issues/5257>`_: Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5278 <https://github.com/pytest-dev/pytest/issues/5278>`_: Pytest's internal python plugin can be disabled using ``-p no:python`` again.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5286 <https://github.com/pytest-dev/pytest/issues/5286>`_: Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option not working when using a list of test IDs in parametrized tests.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5330 <https://github.com/pytest-dev/pytest/issues/5330>`_: Show the test module being collected when emitting ``PytestCollectionWarning`` messages for
|
||||||
|
test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5333 <https://github.com/pytest-dev/pytest/issues/5333>`_: Fix regression in 4.5.0 with ``--lf`` not re-running all tests with known failures from non-selected tests.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#5250 <https://github.com/pytest-dev/pytest/issues/5250>`_: Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.
|
||||||
|
|
||||||
|
|
||||||
pytest 4.5.0 (2019-05-11)
|
pytest 4.5.0 (2019-05-11)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ taking a lot of time to make a new one.
|
||||||
|
|
||||||
#. Create a branch ``release-X.Y.Z`` with the version for the release.
|
#. Create a branch ``release-X.Y.Z`` with the version for the release.
|
||||||
|
|
||||||
|
* **maintenance releases**: from ``4.6-maintenance``;
|
||||||
|
|
||||||
* **patch releases**: from the latest ``master``;
|
* **patch releases**: from the latest ``master``;
|
||||||
|
|
||||||
* **minor releases**: from the latest ``features``; then merge with the latest ``master``;
|
* **minor releases**: from the latest ``features``; then merge with the latest ``master``;
|
||||||
|
@ -24,7 +26,8 @@ taking a lot of time to make a new one.
|
||||||
|
|
||||||
This will generate a commit with all the changes ready for pushing.
|
This will generate a commit with all the changes ready for pushing.
|
||||||
|
|
||||||
#. Open a PR for this branch targeting ``master``.
|
#. Open a PR for this branch targeting ``master`` (or ``4.6-maintenance`` for
|
||||||
|
maintenance releases).
|
||||||
|
|
||||||
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
|
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
|
||||||
|
|
||||||
|
@ -33,7 +36,16 @@ taking a lot of time to make a new one.
|
||||||
|
|
||||||
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
|
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
|
||||||
|
|
||||||
#. Merge the PR into ``master``.
|
#. Merge the PR.
|
||||||
|
|
||||||
|
#. If this is a maintenance release, cherry-pick the CHANGELOG / announce
|
||||||
|
files to the ``master`` branch::
|
||||||
|
|
||||||
|
git fetch --all --prune
|
||||||
|
git checkout origin/master -b cherry-pick-maintenance-release
|
||||||
|
git cherry-pick --no-commit -m1 origin/4.6-maintenance
|
||||||
|
git checkout origin/master -- changelog
|
||||||
|
git commit # no arguments
|
||||||
|
|
||||||
#. Send an email announcement with the contents from::
|
#. Send an email announcement with the contents from::
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ Features
|
||||||
- Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
|
- Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
|
||||||
`nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
|
`nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
|
||||||
|
|
||||||
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
|
- Python 3.5+ and PyPy3;
|
||||||
|
|
||||||
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ trigger:
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
|
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
|
||||||
python.needs_vc: False
|
|
||||||
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
|
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
|
||||||
COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc"
|
COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc"
|
||||||
PYTEST_COVERAGE: '0'
|
PYTEST_COVERAGE: '0'
|
||||||
|
@ -16,44 +15,10 @@ jobs:
|
||||||
vmImage: "vs2017-win2016"
|
vmImage: "vs2017-win2016"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
py27:
|
# -- pypy3 disabled for now: #5279 --
|
||||||
python.version: '2.7'
|
|
||||||
tox.env: 'py27'
|
|
||||||
py27-nobyte-lsof-numpy:
|
|
||||||
python.version: '2.7'
|
|
||||||
tox.env: 'py27-lsof-nobyte-numpy'
|
|
||||||
# Coverage for:
|
|
||||||
# - test_supports_breakpoint_module_global
|
|
||||||
# - test_terminal_reporter_writer_attr (without xdist)
|
|
||||||
# - "if write" branch in _pytest.assertion.rewrite
|
|
||||||
# - numpy
|
|
||||||
# - pytester's LsofFdLeakChecker (being skipped)
|
|
||||||
PYTEST_COVERAGE: '1'
|
|
||||||
py27-twisted:
|
|
||||||
python.version: '2.7'
|
|
||||||
tox.env: 'py27-twisted'
|
|
||||||
python.needs_vc: True
|
|
||||||
py27-pluggymaster-xdist:
|
|
||||||
python.version: '2.7'
|
|
||||||
tox.env: 'py27-pluggymaster-xdist'
|
|
||||||
# Coverage for:
|
|
||||||
# - except-IOError in _attempt_to_close_capture_file for py2.
|
|
||||||
# Also seen with py27-nobyte (using xdist), and py27-xdist.
|
|
||||||
# But no exception with py27-pexpect,py27-twisted,py27-numpy.
|
|
||||||
PYTEST_COVERAGE: '1'
|
|
||||||
# -- pypy2 and pypy3 are disabled for now: #5279 --
|
|
||||||
# pypy:
|
|
||||||
# python.version: 'pypy2'
|
|
||||||
# tox.env: 'pypy'
|
|
||||||
# pypy3:
|
# pypy3:
|
||||||
# python.version: 'pypy3'
|
# python.version: 'pypy3'
|
||||||
# tox.env: 'pypy3'
|
# tox.env: 'pypy3'
|
||||||
py34-xdist:
|
|
||||||
python.version: '3.4'
|
|
||||||
tox.env: 'py34-xdist'
|
|
||||||
# Coverage for:
|
|
||||||
# - _pytest.compat._bytes_to_ascii
|
|
||||||
PYTEST_COVERAGE: '1'
|
|
||||||
py35-xdist:
|
py35-xdist:
|
||||||
python.version: '3.5'
|
python.version: '3.5'
|
||||||
tox.env: 'py35-xdist'
|
tox.env: 'py35-xdist'
|
||||||
|
@ -87,10 +52,6 @@ jobs:
|
||||||
versionSpec: '$(python.version)'
|
versionSpec: '$(python.version)'
|
||||||
architecture: 'x64'
|
architecture: 'x64'
|
||||||
|
|
||||||
- script: choco install vcpython27
|
|
||||||
condition: eq(variables['python.needs_vc'], True)
|
|
||||||
displayName: 'Install VC for py27'
|
|
||||||
|
|
||||||
- script: python -m pip install --upgrade pip && python -m pip install tox
|
- script: python -m pip install --upgrade pip && python -m pip install tox
|
||||||
displayName: 'Install tox'
|
displayName: 'Install tox'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# 10000 iterations, just for relative comparison
|
# 10000 iterations, just for relative comparison
|
||||||
# 2.7.5 3.3.2
|
# 2.7.5 3.3.2
|
||||||
# FilesCompleter 75.1109 69.2116
|
# FilesCompleter 75.1109 69.2116
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
exec("def test_func_%d(): pass" % i)
|
exec("def test_func_%d(): pass" % i)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from six.moves import range
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
SKIP = True
|
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('changelog') }}">Changelog</a></li>
|
||||||
<li><a href="{{ pathto('contributing') }}">Contributing</a></li>
|
<li><a href="{{ pathto('contributing') }}">Contributing</a></li>
|
||||||
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
|
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
|
||||||
|
<li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li>
|
||||||
<li><a href="{{ pathto('license') }}">License</a></li>
|
<li><a href="{{ pathto('license') }}">License</a></li>
|
||||||
<li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
|
<li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# flasky extensions. flasky pygments style based on tango style
|
# flasky extensions. flasky pygments style based on tango style
|
||||||
from pygments.style import Style
|
from pygments.style import Style
|
||||||
from pygments.token import Comment
|
from pygments.token import Comment
|
||||||
|
|
|
@ -6,6 +6,8 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-4.6.1
|
||||||
|
release-4.6.0
|
||||||
release-4.5.0
|
release-4.5.0
|
||||||
release-4.4.2
|
release-4.4.2
|
||||||
release-4.4.1
|
release-4.4.1
|
||||||
|
|
|
@ -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
|
# pytest documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Fri Oct 8 17:54:28 2010.
|
# sphinx-quickstart on Fri Oct 8 17:54:28 2010.
|
||||||
|
@ -63,9 +62,9 @@ source_suffix = ".rst"
|
||||||
master_doc = "contents"
|
master_doc = "contents"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u"pytest"
|
project = "pytest"
|
||||||
year = datetime.datetime.utcnow().year
|
year = datetime.datetime.utcnow().year
|
||||||
copyright = u"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
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
@ -233,8 +232,8 @@ latex_documents = [
|
||||||
(
|
(
|
||||||
"contents",
|
"contents",
|
||||||
"pytest.tex",
|
"pytest.tex",
|
||||||
u"pytest Documentation",
|
"pytest Documentation",
|
||||||
u"holger krekel, trainer and consultant, http://merlinux.eu",
|
"holger krekel, trainer and consultant, http://merlinux.eu",
|
||||||
"manual",
|
"manual",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -266,16 +265,16 @@ latex_domain_indices = False
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [("usage", "pytest", u"pytest usage", [u"holger krekel at merlinux eu"], 1)]
|
man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)]
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Epub output ---------------------------------------------------
|
# -- Options for Epub output ---------------------------------------------------
|
||||||
|
|
||||||
# Bibliographic Dublin Core info.
|
# Bibliographic Dublin Core info.
|
||||||
epub_title = u"pytest"
|
epub_title = "pytest"
|
||||||
epub_author = u"holger krekel at merlinux eu"
|
epub_author = "holger krekel at merlinux eu"
|
||||||
epub_publisher = u"holger krekel at merlinux eu"
|
epub_publisher = "holger krekel at merlinux eu"
|
||||||
epub_copyright = u"2013, holger krekel et alii"
|
epub_copyright = "2013, holger krekel et alii"
|
||||||
|
|
||||||
# The language of the text. It defaults to the language option
|
# The language of the text. It defaults to the language option
|
||||||
# or en if the language is not set.
|
# or en if the language is not set.
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
collect_ignore = ["conf.py"]
|
collect_ignore = ["conf.py"]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import raises
|
from pytest import raises
|
||||||
|
@ -21,7 +20,7 @@ def test_generative(param1, param2):
|
||||||
assert param1 * 2 < param2
|
assert param1 * 2 < param2
|
||||||
|
|
||||||
|
|
||||||
class TestFailing(object):
|
class TestFailing:
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
def f():
|
def f():
|
||||||
return 42
|
return 42
|
||||||
|
@ -41,7 +40,7 @@ class TestFailing(object):
|
||||||
assert not f()
|
assert not f()
|
||||||
|
|
||||||
|
|
||||||
class TestSpecialisedExplanations(object):
|
class TestSpecialisedExplanations:
|
||||||
def test_eq_text(self):
|
def test_eq_text(self):
|
||||||
assert "spam" == "eggs"
|
assert "spam" == "eggs"
|
||||||
|
|
||||||
|
@ -101,7 +100,7 @@ class TestSpecialisedExplanations(object):
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Foo(object):
|
class Foo:
|
||||||
a: int
|
a: int
|
||||||
b: str
|
b: str
|
||||||
|
|
||||||
|
@ -113,7 +112,7 @@ class TestSpecialisedExplanations(object):
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class Foo(object):
|
class Foo:
|
||||||
a = attr.ib()
|
a = attr.ib()
|
||||||
b = attr.ib()
|
b = attr.ib()
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ class TestSpecialisedExplanations(object):
|
||||||
|
|
||||||
|
|
||||||
def test_attribute():
|
def test_attribute():
|
||||||
class Foo(object):
|
class Foo:
|
||||||
b = 1
|
b = 1
|
||||||
|
|
||||||
i = Foo()
|
i = Foo()
|
||||||
|
@ -131,14 +130,14 @@ def test_attribute():
|
||||||
|
|
||||||
|
|
||||||
def test_attribute_instance():
|
def test_attribute_instance():
|
||||||
class Foo(object):
|
class Foo:
|
||||||
b = 1
|
b = 1
|
||||||
|
|
||||||
assert Foo().b == 2
|
assert Foo().b == 2
|
||||||
|
|
||||||
|
|
||||||
def test_attribute_failure():
|
def test_attribute_failure():
|
||||||
class Foo(object):
|
class Foo:
|
||||||
def _get_b(self):
|
def _get_b(self):
|
||||||
raise Exception("Failed to get attrib")
|
raise Exception("Failed to get attrib")
|
||||||
|
|
||||||
|
@ -149,10 +148,10 @@ def test_attribute_failure():
|
||||||
|
|
||||||
|
|
||||||
def test_attribute_multiple():
|
def test_attribute_multiple():
|
||||||
class Foo(object):
|
class Foo:
|
||||||
b = 1
|
b = 1
|
||||||
|
|
||||||
class Bar(object):
|
class Bar:
|
||||||
b = 2
|
b = 2
|
||||||
|
|
||||||
assert Foo().b == Bar().b
|
assert Foo().b == Bar().b
|
||||||
|
@ -162,7 +161,7 @@ def globf(x):
|
||||||
return x + 1
|
return x + 1
|
||||||
|
|
||||||
|
|
||||||
class TestRaises(object):
|
class TestRaises:
|
||||||
def test_raises(self):
|
def test_raises(self):
|
||||||
s = "qwe"
|
s = "qwe"
|
||||||
raises(TypeError, int, s)
|
raises(TypeError, int, s)
|
||||||
|
@ -203,7 +202,7 @@ def test_dynamic_compile_shows_nicely():
|
||||||
module.foo()
|
module.foo()
|
||||||
|
|
||||||
|
|
||||||
class TestMoreErrors(object):
|
class TestMoreErrors:
|
||||||
def test_complex_error(self):
|
def test_complex_error(self):
|
||||||
def f():
|
def f():
|
||||||
return 44
|
return 44
|
||||||
|
@ -253,16 +252,16 @@ class TestMoreErrors(object):
|
||||||
x = 0
|
x = 0
|
||||||
|
|
||||||
|
|
||||||
class TestCustomAssertMsg(object):
|
class TestCustomAssertMsg:
|
||||||
def test_single_line(self):
|
def test_single_line(self):
|
||||||
class A(object):
|
class A:
|
||||||
a = 1
|
a = 1
|
||||||
|
|
||||||
b = 2
|
b = 2
|
||||||
assert A.a == b, "A.a appears not to be b"
|
assert A.a == b, "A.a appears not to be b"
|
||||||
|
|
||||||
def test_multiline(self):
|
def test_multiline(self):
|
||||||
class A(object):
|
class A:
|
||||||
a = 1
|
a = 1
|
||||||
|
|
||||||
b = 2
|
b = 2
|
||||||
|
@ -271,7 +270,7 @@ class TestCustomAssertMsg(object):
|
||||||
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
||||||
|
|
||||||
def test_custom_repr(self):
|
def test_custom_repr(self):
|
||||||
class JSON(object):
|
class JSON:
|
||||||
a = 1
|
a = 1
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
hello = "world"
|
hello = "world"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")
|
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
def setup_module(module):
|
def setup_module(module):
|
||||||
module.TestStateFullThing.classcount = 0
|
module.TestStateFullThing.classcount = 0
|
||||||
|
|
||||||
|
|
||||||
class TestStateFullThing(object):
|
class TestStateFullThing:
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
cls.classcount += 1
|
cls.classcount += 1
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
collect_ignore = ["nonpython"]
|
collect_ignore = ["nonpython"]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,7 +8,7 @@ def setup(request):
|
||||||
setup.finalize()
|
setup.finalize()
|
||||||
|
|
||||||
|
|
||||||
class CostlySetup(object):
|
class CostlySetup:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
def test_quick(setup):
|
def test_quick(setup):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
def test_something(setup):
|
def test_something(setup):
|
||||||
assert setup.timecostly == 1
|
assert setup.timecostly == 1
|
||||||
|
|
||||||
|
|
|
@ -288,8 +288,7 @@ its test methods:
|
||||||
This is equivalent to directly applying the decorator to the
|
This is equivalent to directly applying the decorator to the
|
||||||
two test functions.
|
two test functions.
|
||||||
|
|
||||||
To remain backward-compatible with Python 2.4 you can also set a
|
Due to legacy reasons, it is possible to set the ``pytestmark`` attribute on a TestClass like this:
|
||||||
``pytestmark`` attribute on a TestClass like this:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
module containing a parametrized tests testing cross-python
|
module containing a parametrized tests testing cross-python
|
||||||
serialization via the pickle module.
|
serialization via the pickle module.
|
||||||
|
@ -9,7 +8,7 @@ import textwrap
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
pythonlist = ["python2.7", "python3.4", "python3.5"]
|
pythonlist = ["python3.5", "python3.6", "python3.7"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=pythonlist)
|
@pytest.fixture(params=pythonlist)
|
||||||
|
@ -23,7 +22,7 @@ def python2(request, python1):
|
||||||
return Python(request.param, python1.picklefile)
|
return Python(request.param, python1.picklefile)
|
||||||
|
|
||||||
|
|
||||||
class Python(object):
|
class Python:
|
||||||
def __init__(self, version, picklefile):
|
def __init__(self, version, picklefile):
|
||||||
self.pythonpath = distutils.spawn.find_executable(version)
|
self.pythonpath = distutils.spawn.find_executable(version)
|
||||||
if not self.pythonpath:
|
if not self.pythonpath:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# content of conftest.py
|
# content of conftest.py
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ class YamlFile(pytest.File):
|
||||||
|
|
||||||
class YamlItem(pytest.Item):
|
class YamlItem(pytest.Item):
|
||||||
def __init__(self, name, parent, spec):
|
def __init__(self, name, parent, spec):
|
||||||
super(YamlItem, self).__init__(name, parent)
|
super().__init__(name, parent)
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
|
|
|
@ -429,14 +429,14 @@ is to be run with different sets of arguments for its three arguments:
|
||||||
|
|
||||||
.. literalinclude:: multipython.py
|
.. literalinclude:: multipython.py
|
||||||
|
|
||||||
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):
|
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (3 interpreters times 3 interpreters times 3 objects to serialize/deserialize):
|
||||||
|
|
||||||
.. code-block:: pytest
|
.. code-block:: pytest
|
||||||
|
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
...sss...sssssssss...sss... [100%]
|
...sss...sssssssss...sss... [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
|
||||||
12 passed, 15 skipped in 0.12 seconds
|
12 passed, 15 skipped in 0.12 seconds
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
|
@ -494,7 +494,7 @@ If you run this with reporting for skips enabled:
|
||||||
test_module.py .s [100%]
|
test_module.py .s [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
|
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2': No module named 'opt2'
|
||||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||||
|
|
||||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
def test_exception_syntax():
|
def test_exception_syntax():
|
||||||
try:
|
try:
|
||||||
0 / 0
|
0 / 0
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# run this with $ pytest --collect-only test_collectonly.py
|
# run this with $ pytest --collect-only test_collectonly.py
|
||||||
#
|
#
|
||||||
|
|
||||||
|
@ -7,7 +6,7 @@ def test_function():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestClass(object):
|
class TestClass:
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert param1 * 2 < param2
|
> assert param1 * 2 < param2
|
||||||
E assert (3 * 2) < 6
|
E assert (3 * 2) < 6
|
||||||
|
|
||||||
failure_demo.py:20: AssertionError
|
failure_demo.py:21: AssertionError
|
||||||
_________________________ TestFailing.test_simple __________________________
|
_________________________ TestFailing.test_simple __________________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -43,7 +43,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
||||||
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:31: AssertionError
|
failure_demo.py:32: AssertionError
|
||||||
____________________ TestFailing.test_simple_multiline _____________________
|
____________________ TestFailing.test_simple_multiline _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -51,7 +51,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
def test_simple_multiline(self):
|
def test_simple_multiline(self):
|
||||||
> otherfunc_multi(42, 6 * 9)
|
> otherfunc_multi(42, 6 * 9)
|
||||||
|
|
||||||
failure_demo.py:34:
|
failure_demo.py:35:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
a = 42, b = 54
|
a = 42, b = 54
|
||||||
|
@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert a == b
|
> assert a == b
|
||||||
E assert 42 == 54
|
E assert 42 == 54
|
||||||
|
|
||||||
failure_demo.py:15: AssertionError
|
failure_demo.py:16: AssertionError
|
||||||
___________________________ TestFailing.test_not ___________________________
|
___________________________ TestFailing.test_not ___________________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -73,7 +73,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert not 42
|
E assert not 42
|
||||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:40: AssertionError
|
failure_demo.py:41: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -84,7 +84,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E - spam
|
E - spam
|
||||||
E + eggs
|
E + eggs
|
||||||
|
|
||||||
failure_demo.py:45: AssertionError
|
failure_demo.py:46: AssertionError
|
||||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -97,7 +97,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + foo 2 bar
|
E + foo 2 bar
|
||||||
E ? ^
|
E ? ^
|
||||||
|
|
||||||
failure_demo.py:48: AssertionError
|
failure_demo.py:49: AssertionError
|
||||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -110,7 +110,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + eggs
|
E + eggs
|
||||||
E bar
|
E bar
|
||||||
|
|
||||||
failure_demo.py:51: AssertionError
|
failure_demo.py:52: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -127,7 +127,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + 1111111111b222222222
|
E + 1111111111b222222222
|
||||||
E ? ^
|
E ? ^
|
||||||
|
|
||||||
failure_demo.py:56: AssertionError
|
failure_demo.py:57: AssertionError
|
||||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -147,7 +147,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:61: AssertionError
|
failure_demo.py:62: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -158,7 +158,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E At index 2 diff: 2 != 3
|
E At index 2 diff: 2 != 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:64: AssertionError
|
failure_demo.py:65: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -171,7 +171,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E At index 100 diff: 1 != 2
|
E At index 100 diff: 1 != 2
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:69: AssertionError
|
failure_demo.py:70: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -189,7 +189,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:72: AssertionError
|
failure_demo.py:73: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -207,7 +207,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:75: AssertionError
|
failure_demo.py:76: AssertionError
|
||||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -218,7 +218,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Right contains one more item: 3
|
E Right contains one more item: 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:78: AssertionError
|
failure_demo.py:79: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -227,7 +227,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert 1 in [0, 2, 3, 4, 5]
|
> assert 1 in [0, 2, 3, 4, 5]
|
||||||
E assert 1 in [0, 2, 3, 4, 5]
|
E assert 1 in [0, 2, 3, 4, 5]
|
||||||
|
|
||||||
failure_demo.py:81: AssertionError
|
failure_demo.py:82: AssertionError
|
||||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -246,7 +246,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:85: AssertionError
|
failure_demo.py:86: AssertionError
|
||||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -259,7 +259,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E single foo line
|
E single foo line
|
||||||
E ? +++
|
E ? +++
|
||||||
|
|
||||||
failure_demo.py:89: AssertionError
|
failure_demo.py:90: AssertionError
|
||||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -272,7 +272,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||||
E ? +++
|
E ? +++
|
||||||
|
|
||||||
failure_demo.py:93: AssertionError
|
failure_demo.py:94: AssertionError
|
||||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -285,7 +285,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
failure_demo.py:97: AssertionError
|
failure_demo.py:98: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -306,7 +306,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Differing attributes:
|
E Differing attributes:
|
||||||
E b: 'b' != 'c'
|
E b: 'b' != 'c'
|
||||||
|
|
||||||
failure_demo.py:109: AssertionError
|
failure_demo.py:110: AssertionError
|
||||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -327,7 +327,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Differing attributes:
|
E Differing attributes:
|
||||||
E b: 'b' != 'c'
|
E b: 'b' != 'c'
|
||||||
|
|
||||||
failure_demo.py:121: AssertionError
|
failure_demo.py:122: AssertionError
|
||||||
______________________________ test_attribute ______________________________
|
______________________________ test_attribute ______________________________
|
||||||
|
|
||||||
def test_attribute():
|
def test_attribute():
|
||||||
|
@ -339,7 +339,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
|
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
|
||||||
|
|
||||||
failure_demo.py:129: AssertionError
|
failure_demo.py:130: AssertionError
|
||||||
_________________________ test_attribute_instance __________________________
|
_________________________ test_attribute_instance __________________________
|
||||||
|
|
||||||
def test_attribute_instance():
|
def test_attribute_instance():
|
||||||
|
@ -351,7 +351,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
|
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
|
||||||
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
||||||
|
|
||||||
failure_demo.py:136: AssertionError
|
failure_demo.py:137: AssertionError
|
||||||
__________________________ test_attribute_failure __________________________
|
__________________________ test_attribute_failure __________________________
|
||||||
|
|
||||||
def test_attribute_failure():
|
def test_attribute_failure():
|
||||||
|
@ -364,7 +364,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
i = Foo()
|
i = Foo()
|
||||||
> assert i.b == 2
|
> assert i.b == 2
|
||||||
|
|
||||||
failure_demo.py:147:
|
failure_demo.py:148:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||||
|
@ -373,7 +373,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raise Exception("Failed to get attrib")
|
> raise Exception("Failed to get attrib")
|
||||||
E Exception: Failed to get attrib
|
E Exception: Failed to get attrib
|
||||||
|
|
||||||
failure_demo.py:142: Exception
|
failure_demo.py:143: Exception
|
||||||
_________________________ test_attribute_multiple __________________________
|
_________________________ test_attribute_multiple __________________________
|
||||||
|
|
||||||
def test_attribute_multiple():
|
def test_attribute_multiple():
|
||||||
|
@ -390,7 +390,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
||||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||||
|
|
||||||
failure_demo.py:157: AssertionError
|
failure_demo.py:158: AssertionError
|
||||||
__________________________ TestRaises.test_raises __________________________
|
__________________________ TestRaises.test_raises __________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -400,7 +400,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raises(TypeError, int, s)
|
> raises(TypeError, int, s)
|
||||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||||
|
|
||||||
failure_demo.py:167: ValueError
|
failure_demo.py:168: ValueError
|
||||||
______________________ TestRaises.test_raises_doesnt _______________________
|
______________________ TestRaises.test_raises_doesnt _______________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -409,7 +409,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raises(IOError, int, "3")
|
> raises(IOError, int, "3")
|
||||||
E Failed: DID NOT RAISE <class 'OSError'>
|
E Failed: DID NOT RAISE <class 'OSError'>
|
||||||
|
|
||||||
failure_demo.py:170: Failed
|
failure_demo.py:171: Failed
|
||||||
__________________________ TestRaises.test_raise ___________________________
|
__________________________ TestRaises.test_raise ___________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -418,7 +418,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raise ValueError("demo error")
|
> raise ValueError("demo error")
|
||||||
E ValueError: demo error
|
E ValueError: demo error
|
||||||
|
|
||||||
failure_demo.py:173: ValueError
|
failure_demo.py:174: ValueError
|
||||||
________________________ TestRaises.test_tupleerror ________________________
|
________________________ TestRaises.test_tupleerror ________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -427,7 +427,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = [1] # NOQA
|
> a, b = [1] # NOQA
|
||||||
E ValueError: not enough values to unpack (expected 2, got 1)
|
E ValueError: not enough values to unpack (expected 2, got 1)
|
||||||
|
|
||||||
failure_demo.py:176: ValueError
|
failure_demo.py:177: ValueError
|
||||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -438,7 +438,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items.pop()
|
> a, b = items.pop()
|
||||||
E TypeError: 'int' object is not iterable
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:181: TypeError
|
failure_demo.py:182: TypeError
|
||||||
--------------------------- Captured stdout call ---------------------------
|
--------------------------- Captured stdout call ---------------------------
|
||||||
items is [1, 2, 3]
|
items is [1, 2, 3]
|
||||||
________________________ TestRaises.test_some_error ________________________
|
________________________ TestRaises.test_some_error ________________________
|
||||||
|
@ -449,7 +449,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> if namenotexi: # NOQA
|
> if namenotexi: # NOQA
|
||||||
E NameError: name 'namenotexi' is not defined
|
E NameError: name 'namenotexi' is not defined
|
||||||
|
|
||||||
failure_demo.py:184: NameError
|
failure_demo.py:185: NameError
|
||||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||||
|
|
||||||
def test_dynamic_compile_shows_nicely():
|
def test_dynamic_compile_shows_nicely():
|
||||||
|
@ -464,14 +464,14 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
sys.modules[name] = module
|
sys.modules[name] = module
|
||||||
> module.foo()
|
> module.foo()
|
||||||
|
|
||||||
failure_demo.py:202:
|
failure_demo.py:203:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
def foo():
|
def foo():
|
||||||
> assert 1 == 0
|
> assert 1 == 0
|
||||||
E AssertionError
|
E AssertionError
|
||||||
|
|
||||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:199>:2: AssertionError
|
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:200>:2: AssertionError
|
||||||
____________________ TestMoreErrors.test_complex_error _____________________
|
____________________ TestMoreErrors.test_complex_error _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -485,9 +485,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
|
|
||||||
> somefunc(f(), g())
|
> somefunc(f(), g())
|
||||||
|
|
||||||
failure_demo.py:213:
|
failure_demo.py:214:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
failure_demo.py:11: in somefunc
|
failure_demo.py:12: in somefunc
|
||||||
otherfunc(x, y)
|
otherfunc(x, y)
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
|
@ -497,7 +497,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert a == b
|
> assert a == b
|
||||||
E assert 44 == 43
|
E assert 44 == 43
|
||||||
|
|
||||||
failure_demo.py:7: AssertionError
|
failure_demo.py:8: AssertionError
|
||||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -507,7 +507,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items
|
> a, b = items
|
||||||
E ValueError: not enough values to unpack (expected 2, got 0)
|
E ValueError: not enough values to unpack (expected 2, got 0)
|
||||||
|
|
||||||
failure_demo.py:217: ValueError
|
failure_demo.py:218: ValueError
|
||||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -517,7 +517,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items
|
> a, b = items
|
||||||
E TypeError: 'int' object is not iterable
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:221: TypeError
|
failure_demo.py:222: TypeError
|
||||||
______________________ TestMoreErrors.test_startswith ______________________
|
______________________ TestMoreErrors.test_startswith ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -530,7 +530,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
|
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
|
||||||
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
|
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
|
||||||
|
|
||||||
failure_demo.py:226: AssertionError
|
failure_demo.py:227: AssertionError
|
||||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -549,7 +549,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
|
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
|
||||||
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
|
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:235: AssertionError
|
failure_demo.py:236: AssertionError
|
||||||
_____________________ TestMoreErrors.test_global_func ______________________
|
_____________________ TestMoreErrors.test_global_func ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -560,7 +560,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where False = isinstance(43, float)
|
E + where False = isinstance(43, float)
|
||||||
E + where 43 = globf(42)
|
E + where 43 = globf(42)
|
||||||
|
|
||||||
failure_demo.py:238: AssertionError
|
failure_demo.py:239: AssertionError
|
||||||
_______________________ TestMoreErrors.test_instance _______________________
|
_______________________ TestMoreErrors.test_instance _______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -571,7 +571,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 42 != 42
|
E assert 42 != 42
|
||||||
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||||
|
|
||||||
failure_demo.py:242: AssertionError
|
failure_demo.py:243: AssertionError
|
||||||
_______________________ TestMoreErrors.test_compare ________________________
|
_______________________ TestMoreErrors.test_compare ________________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -581,7 +581,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 11 < 5
|
E assert 11 < 5
|
||||||
E + where 11 = globf(10)
|
E + where 11 = globf(10)
|
||||||
|
|
||||||
failure_demo.py:245: AssertionError
|
failure_demo.py:246: AssertionError
|
||||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert x == 0
|
> assert x == 0
|
||||||
E assert 1 == 0
|
E assert 1 == 0
|
||||||
|
|
||||||
failure_demo.py:250: AssertionError
|
failure_demo.py:251: AssertionError
|
||||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -607,7 +607,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
|
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
|
||||||
|
|
||||||
failure_demo.py:261: AssertionError
|
failure_demo.py:262: AssertionError
|
||||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -626,7 +626,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
||||||
|
|
||||||
failure_demo.py:268: AssertionError
|
failure_demo.py:269: AssertionError
|
||||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -648,5 +648,5 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||||
|
|
||||||
failure_demo.py:281: AssertionError
|
failure_demo.py:282: AssertionError
|
||||||
======================== 44 failed in 0.12 seconds =========================
|
======================== 44 failed in 0.12 seconds =========================
|
||||||
|
|
|
@ -441,7 +441,7 @@ Now we can profile which test functions execute the slowest:
|
||||||
|
|
||||||
========================= slowest 3 test durations =========================
|
========================= slowest 3 test durations =========================
|
||||||
0.30s call test_some_are_slow.py::test_funcslow2
|
0.30s call test_some_are_slow.py::test_funcslow2
|
||||||
0.20s call test_some_are_slow.py::test_funcslow1
|
0.21s call test_some_are_slow.py::test_funcslow1
|
||||||
0.10s call test_some_are_slow.py::test_funcfast
|
0.10s call test_some_are_slow.py::test_funcfast
|
||||||
========================= 3 passed in 0.12 seconds =========================
|
========================= 3 passed in 0.12 seconds =========================
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
xfail = pytest.mark.xfail
|
xfail = pytest.mark.xfail
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
Installation and Getting Started
|
Installation and Getting Started
|
||||||
===================================
|
===================================
|
||||||
|
|
||||||
**Pythons**: Python 2.7, 3.4, 3.5, 3.6, 3.7, Jython, PyPy-2.3
|
**Pythons**: Python 3.5, 3.6, 3.7, PyPy3
|
||||||
|
|
||||||
**Platforms**: Unix/Posix and Windows
|
**Platforms**: Linux and Windows
|
||||||
|
|
||||||
**PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_
|
**PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ Good Integration Practices
|
||||||
Install package with pip
|
Install package with pip
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
|
||||||
For development, we recommend you use venv_ for virtual environments
|
For development, we recommend you use venv_ for virtual environments and
|
||||||
(or virtualenv_ for Python 2.7) and
|
|
||||||
pip_ for installing your application and any dependencies,
|
pip_ for installing your application and any dependencies,
|
||||||
as well as the ``pytest`` package itself.
|
as well as the ``pytest`` package itself.
|
||||||
This ensures your code and dependencies are isolated from your system Python installation.
|
This ensures your code and dependencies are isolated from your system Python installation.
|
||||||
|
|
|
@ -61,7 +61,7 @@ Features
|
||||||
|
|
||||||
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
|
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
|
||||||
|
|
||||||
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
|
- Python Python 3.5+ and PyPy 3;
|
||||||
|
|
||||||
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,4 @@ back-porting patches to the ``4.6-maintenance`` branch that affect Python 2 user
|
||||||
branch will continue to exist so the community itself can contribute patches. The core team will
|
branch will continue to exist so the community itself can contribute patches. The core team will
|
||||||
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.
|
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.
|
||||||
|
|
||||||
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires>
|
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||||
|
|
|
@ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
||||||
|
|
||||||
============================= warnings summary =============================
|
============================= warnings summary =============================
|
||||||
test_pytest_warnings.py:1
|
test_pytest_warnings.py:1
|
||||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py)
|
||||||
class Test:
|
class Test:
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||||
|
|
|
@ -335,8 +335,6 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import sys
|
import sys
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Invoke development tasks.
|
Invoke development tasks.
|
||||||
"""
|
"""
|
||||||
|
|
14
setup.cfg
14
setup.cfg
|
@ -1,5 +1,4 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
|
|
||||||
name = pytest
|
name = pytest
|
||||||
description = pytest: simple powerful testing with Python
|
description = pytest: simple powerful testing with Python
|
||||||
long_description = file: README.rst
|
long_description = file: README.rst
|
||||||
|
@ -23,13 +22,11 @@ classifiers =
|
||||||
Topic :: Software Development :: Testing
|
Topic :: Software Development :: Testing
|
||||||
Topic :: Software Development :: Libraries
|
Topic :: Software Development :: Libraries
|
||||||
Topic :: Utilities
|
Topic :: Utilities
|
||||||
Programming Language :: Python :: 2
|
Programming Language :: Python :: 3 :: Only
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
Programming Language :: Python :: 3.5
|
Programming Language :: Python :: 3.5
|
||||||
Programming Language :: Python :: 3.6
|
Programming Language :: Python :: 3.6
|
||||||
Programming Language :: Python :: 3.7
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
platforms = unix, linux, osx, cygwin, win32
|
platforms = unix, linux, osx, cygwin, win32
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
|
@ -43,8 +40,7 @@ packages =
|
||||||
_pytest.mark
|
_pytest.mark
|
||||||
|
|
||||||
py_modules = pytest
|
py_modules = pytest
|
||||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
python_requires = >=3.5
|
||||||
|
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
|
@ -59,13 +55,9 @@ all_files = 1
|
||||||
[upload_sphinx]
|
[upload_sphinx]
|
||||||
upload-dir = doc/en/build/html
|
upload-dir = doc/en/build/html
|
||||||
|
|
||||||
[bdist_wheel]
|
|
||||||
universal = 1
|
|
||||||
|
|
||||||
[check-manifest]
|
[check-manifest]
|
||||||
ignore =
|
ignore =
|
||||||
_pytest/_version.py
|
_pytest/_version.py
|
||||||
|
|
||||||
|
|
||||||
[devpi:upload]
|
[devpi:upload]
|
||||||
formats = sdist.tgz,bdist_wheel
|
formats = sdist.tgz,bdist_wheel
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -1,17 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
# TODO: if py gets upgrade to >=1.6,
|
# TODO: if py gets upgrade to >=1.6,
|
||||||
# remove _width_of_current_line in terminal.py
|
# remove _width_of_current_line in terminal.py
|
||||||
INSTALL_REQUIRES = [
|
INSTALL_REQUIRES = [
|
||||||
"py>=1.5.0",
|
"py>=1.5.0",
|
||||||
"six>=1.10.0",
|
|
||||||
"packaging",
|
"packaging",
|
||||||
"attrs>=17.4.0",
|
"attrs>=17.4.0",
|
||||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
"more-itertools>=4.0.0",
|
||||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
|
||||||
"atomicwrites>=1.0",
|
"atomicwrites>=1.0",
|
||||||
'funcsigs>=1.0;python_version<"3.0"',
|
|
||||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||||
'colorama;sys_platform=="win32"',
|
'colorama;sys_platform=="win32"',
|
||||||
"pluggy>=0.12,<1.0",
|
"pluggy>=0.12,<1.0",
|
||||||
|
@ -30,9 +26,9 @@ def main():
|
||||||
"testing": [
|
"testing": [
|
||||||
"argcomplete",
|
"argcomplete",
|
||||||
"hypothesis>=3.56",
|
"hypothesis>=3.56",
|
||||||
|
"mock",
|
||||||
"nose",
|
"nose",
|
||||||
"requests",
|
"requests",
|
||||||
"mock;python_version=='2.7'",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
__all__ = ["__version__"]
|
__all__ = ["__version__"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""allow bash-completion for argparse with argcomplete if installed
|
"""allow bash-completion for argparse with argcomplete if installed
|
||||||
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
|
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
|
||||||
to find the magic string, so _ARGCOMPLETE env. var is never set, and
|
to find the magic string, so _ARGCOMPLETE env. var is never set, and
|
||||||
|
@ -54,16 +53,12 @@ If things do not work right away:
|
||||||
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
which should throw a KeyError: 'COMPLINE' (which is properly set by the
|
||||||
global argcomplete script).
|
global argcomplete script).
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
|
|
||||||
class FastFilesCompleter(object):
|
class FastFilesCompleter:
|
||||||
"Fast file completer class"
|
"Fast file completer class"
|
||||||
|
|
||||||
def __init__(self, directories=True):
|
def __init__(self, directories=True):
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" python inspection/code generation API """
|
""" python inspection/code generation API """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from .code import Code # noqa
|
from .code import Code # noqa
|
||||||
from .code import ExceptionInfo # noqa
|
from .code import ExceptionInfo # noqa
|
||||||
from .code import filter_traceback # noqa
|
from .code import filter_traceback # noqa
|
||||||
|
|
|
@ -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 inspect
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from inspect import CO_VARARGS
|
from inspect import CO_VARARGS
|
||||||
from inspect import CO_VARKEYWORDS
|
from inspect import CO_VARKEYWORDS
|
||||||
|
from traceback import format_exception_only
|
||||||
from weakref import ref
|
from weakref import ref
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import pluggy
|
import pluggy
|
||||||
import py
|
import py
|
||||||
from six import text_type
|
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import _PY2
|
|
||||||
from _pytest.compat import _PY3
|
|
||||||
from _pytest.compat import PY35
|
|
||||||
from _pytest.compat import safe_str
|
|
||||||
|
|
||||||
if _PY3:
|
|
||||||
from traceback import format_exception_only
|
|
||||||
else:
|
|
||||||
from ._py2traceback import format_exception_only
|
|
||||||
|
|
||||||
|
|
||||||
class Code(object):
|
class Code:
|
||||||
""" wrapper around Python code objects """
|
""" wrapper around Python code objects """
|
||||||
|
|
||||||
def __init__(self, rawcode):
|
def __init__(self, rawcode):
|
||||||
|
@ -41,7 +27,7 @@ class Code(object):
|
||||||
self.firstlineno = rawcode.co_firstlineno - 1
|
self.firstlineno = rawcode.co_firstlineno - 1
|
||||||
self.name = rawcode.co_name
|
self.name = rawcode.co_name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise TypeError("not a code object: %r" % (rawcode,))
|
raise TypeError("not a code object: {!r}".format(rawcode))
|
||||||
self.raw = rawcode
|
self.raw = rawcode
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -100,7 +86,7 @@ class Code(object):
|
||||||
return raw.co_varnames[:argcount]
|
return raw.co_varnames[:argcount]
|
||||||
|
|
||||||
|
|
||||||
class Frame(object):
|
class Frame:
|
||||||
"""Wrapper around a Python frame holding f_locals and f_globals
|
"""Wrapper around a Python frame holding f_locals and f_globals
|
||||||
in which expressions can be evaluated."""
|
in which expressions can be evaluated."""
|
||||||
|
|
||||||
|
@ -163,7 +149,7 @@ class Frame(object):
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
class TracebackEntry(object):
|
class TracebackEntry:
|
||||||
""" a single entry in a traceback """
|
""" a single entry in a traceback """
|
||||||
|
|
||||||
_repr_style = None
|
_repr_style = None
|
||||||
|
@ -208,8 +194,7 @@ class TracebackEntry(object):
|
||||||
locals = property(getlocals, None, None, "locals of underlaying frame")
|
locals = property(getlocals, None, None, "locals of underlaying frame")
|
||||||
|
|
||||||
def getfirstlinesource(self):
|
def getfirstlinesource(self):
|
||||||
# on Jython this firstlineno can be -1 apparently
|
return self.frame.code.firstlineno
|
||||||
return max(self.frame.code.firstlineno, 0)
|
|
||||||
|
|
||||||
def getsource(self, astcache=None):
|
def getsource(self, astcache=None):
|
||||||
""" return failing source code. """
|
""" return failing source code. """
|
||||||
|
@ -324,7 +309,7 @@ class Traceback(list):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
val = super(Traceback, self).__getitem__(key)
|
val = super().__getitem__(key)
|
||||||
if isinstance(key, type(slice(0))):
|
if isinstance(key, type(slice(0))):
|
||||||
val = self.__class__(val)
|
val = self.__class__(val)
|
||||||
return val
|
return val
|
||||||
|
@ -386,14 +371,12 @@ co_equal = compile(
|
||||||
|
|
||||||
|
|
||||||
@attr.s(repr=False)
|
@attr.s(repr=False)
|
||||||
class ExceptionInfo(object):
|
class ExceptionInfo:
|
||||||
""" wraps sys.exc_info() objects and offers
|
""" wraps sys.exc_info() objects and offers
|
||||||
help for navigating the traceback.
|
help for navigating the traceback.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_assert_start_repr = (
|
_assert_start_repr = "AssertionError('assert "
|
||||||
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
|
|
||||||
)
|
|
||||||
|
|
||||||
_excinfo = attr.ib()
|
_excinfo = attr.ib()
|
||||||
_striptext = attr.ib(default="")
|
_striptext = attr.ib(default="")
|
||||||
|
@ -558,11 +541,6 @@ class ExceptionInfo(object):
|
||||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||||
return str(loc)
|
return str(loc)
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
entry = self.traceback[-1]
|
|
||||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
|
||||||
return text_type(loc)
|
|
||||||
|
|
||||||
def match(self, regexp):
|
def match(self, regexp):
|
||||||
"""
|
"""
|
||||||
Check whether the regular expression 'regexp' is found in the string
|
Check whether the regular expression 'regexp' is found in the string
|
||||||
|
@ -578,7 +556,7 @@ class ExceptionInfo(object):
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class FormattedExcinfo(object):
|
class FormattedExcinfo:
|
||||||
""" presenting information about failing Functions and Generators. """
|
""" presenting information about failing Functions and Generators. """
|
||||||
|
|
||||||
# for traceback entries
|
# for traceback entries
|
||||||
|
@ -677,7 +655,7 @@ class FormattedExcinfo(object):
|
||||||
str_repr = safeformat(value)
|
str_repr = safeformat(value)
|
||||||
# if len(str_repr) < 70 or not isinstance(value,
|
# if len(str_repr) < 70 or not isinstance(value,
|
||||||
# (list, tuple, dict)):
|
# (list, tuple, dict)):
|
||||||
lines.append("%-10s = %s" % (name, str_repr))
|
lines.append("{:<10} = {}".format(name, str_repr))
|
||||||
# else:
|
# else:
|
||||||
# self._line("%-10s =\\" % (name,))
|
# self._line("%-10s =\\" % (name,))
|
||||||
# # XXX
|
# # XXX
|
||||||
|
@ -692,8 +670,7 @@ class FormattedExcinfo(object):
|
||||||
source = _pytest._code.Source("???")
|
source = _pytest._code.Source("???")
|
||||||
line_index = 0
|
line_index = 0
|
||||||
else:
|
else:
|
||||||
# entry.getfirstlinesource() can be -1, should be 0 on jython
|
line_index = entry.lineno - entry.getfirstlinesource()
|
||||||
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
|
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
style = entry._repr_style
|
style = entry._repr_style
|
||||||
|
@ -733,7 +710,7 @@ class FormattedExcinfo(object):
|
||||||
if self.tbfilter:
|
if self.tbfilter:
|
||||||
traceback = traceback.filter()
|
traceback = traceback.filter()
|
||||||
|
|
||||||
if is_recursion_error(excinfo):
|
if excinfo.errisinstance(RecursionError):
|
||||||
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
||||||
else:
|
else:
|
||||||
extraline = None
|
extraline = None
|
||||||
|
@ -769,7 +746,7 @@ class FormattedExcinfo(object):
|
||||||
" Displaying first and last {max_frames} stack frames out of {total}."
|
" Displaying first and last {max_frames} stack frames out of {total}."
|
||||||
).format(
|
).format(
|
||||||
exc_type=type(e).__name__,
|
exc_type=type(e).__name__,
|
||||||
exc_msg=safe_str(e),
|
exc_msg=str(e),
|
||||||
max_frames=max_frames,
|
max_frames=max_frames,
|
||||||
total=len(traceback),
|
total=len(traceback),
|
||||||
)
|
)
|
||||||
|
@ -784,64 +761,51 @@ class FormattedExcinfo(object):
|
||||||
return traceback, extraline
|
return traceback, extraline
|
||||||
|
|
||||||
def repr_excinfo(self, excinfo):
|
def repr_excinfo(self, excinfo):
|
||||||
if _PY2:
|
|
||||||
reprtraceback = self.repr_traceback(excinfo)
|
|
||||||
reprcrash = excinfo._getreprcrash()
|
|
||||||
|
|
||||||
return ReprExceptionInfo(reprtraceback, reprcrash)
|
repr_chain = []
|
||||||
else:
|
e = excinfo.value
|
||||||
repr_chain = []
|
descr = None
|
||||||
e = excinfo.value
|
seen = set()
|
||||||
descr = None
|
while e is not None and id(e) not in seen:
|
||||||
seen = set()
|
seen.add(id(e))
|
||||||
while e is not None and id(e) not in seen:
|
if excinfo:
|
||||||
seen.add(id(e))
|
reprtraceback = self.repr_traceback(excinfo)
|
||||||
if excinfo:
|
reprcrash = excinfo._getreprcrash()
|
||||||
reprtraceback = self.repr_traceback(excinfo)
|
else:
|
||||||
reprcrash = excinfo._getreprcrash()
|
# fallback to native repr if the exception doesn't have a traceback:
|
||||||
else:
|
# ExceptionInfo objects require a full traceback to work
|
||||||
# fallback to native repr if the exception doesn't have a traceback:
|
reprtraceback = ReprTracebackNative(
|
||||||
# ExceptionInfo objects require a full traceback to work
|
traceback.format_exception(type(e), e, None)
|
||||||
reprtraceback = ReprTracebackNative(
|
)
|
||||||
traceback.format_exception(type(e), e, None)
|
reprcrash = None
|
||||||
)
|
|
||||||
reprcrash = None
|
|
||||||
|
|
||||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
if e.__cause__ is not None and self.chain:
|
if e.__cause__ is not None and self.chain:
|
||||||
e = e.__cause__
|
e = e.__cause__
|
||||||
excinfo = (
|
excinfo = (
|
||||||
ExceptionInfo((type(e), e, e.__traceback__))
|
ExceptionInfo((type(e), e, e.__traceback__))
|
||||||
if e.__traceback__
|
if e.__traceback__
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
descr = "The above exception was the direct cause of the following exception:"
|
descr = "The above exception was the direct cause of the following exception:"
|
||||||
elif (
|
elif (
|
||||||
e.__context__ is not None
|
e.__context__ is not None and not e.__suppress_context__ and self.chain
|
||||||
and not e.__suppress_context__
|
):
|
||||||
and self.chain
|
e = e.__context__
|
||||||
):
|
excinfo = (
|
||||||
e = e.__context__
|
ExceptionInfo((type(e), e, e.__traceback__))
|
||||||
excinfo = (
|
if e.__traceback__
|
||||||
ExceptionInfo((type(e), e, e.__traceback__))
|
else None
|
||||||
if e.__traceback__
|
)
|
||||||
else None
|
descr = "During handling of the above exception, another exception occurred:"
|
||||||
)
|
else:
|
||||||
descr = "During handling of the above exception, another exception occurred:"
|
e = None
|
||||||
else:
|
repr_chain.reverse()
|
||||||
e = None
|
return ExceptionChainRepr(repr_chain)
|
||||||
repr_chain.reverse()
|
|
||||||
return ExceptionChainRepr(repr_chain)
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalRepr(object):
|
class TerminalRepr:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
s = self.__unicode__()
|
|
||||||
if _PY2:
|
|
||||||
s = s.encode("utf-8")
|
|
||||||
return s
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
# FYI this is called from pytest-xdist's serialization of exception
|
# FYI this is called from pytest-xdist's serialization of exception
|
||||||
# information.
|
# information.
|
||||||
io = py.io.TextIO()
|
io = py.io.TextIO()
|
||||||
|
@ -850,7 +814,7 @@ class TerminalRepr(object):
|
||||||
return io.getvalue().strip()
|
return io.getvalue().strip()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s instance at %0x>" % (self.__class__, id(self))
|
return "<{} instance at {:0x}>".format(self.__class__, id(self))
|
||||||
|
|
||||||
|
|
||||||
class ExceptionRepr(TerminalRepr):
|
class ExceptionRepr(TerminalRepr):
|
||||||
|
@ -868,7 +832,7 @@ class ExceptionRepr(TerminalRepr):
|
||||||
|
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
def __init__(self, chain):
|
def __init__(self, chain):
|
||||||
super(ExceptionChainRepr, self).__init__()
|
super().__init__()
|
||||||
self.chain = chain
|
self.chain = chain
|
||||||
# reprcrash and reprtraceback of the outermost (the newest) exception
|
# reprcrash and reprtraceback of the outermost (the newest) exception
|
||||||
# in the chain
|
# in the chain
|
||||||
|
@ -881,18 +845,18 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
if element[2] is not None:
|
if element[2] is not None:
|
||||||
tw.line("")
|
tw.line("")
|
||||||
tw.line(element[2], yellow=True)
|
tw.line(element[2], yellow=True)
|
||||||
super(ExceptionChainRepr, self).toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
def __init__(self, reprtraceback, reprcrash):
|
def __init__(self, reprtraceback, reprcrash):
|
||||||
super(ReprExceptionInfo, self).__init__()
|
super().__init__()
|
||||||
self.reprtraceback = reprtraceback
|
self.reprtraceback = reprtraceback
|
||||||
self.reprcrash = reprcrash
|
self.reprcrash = reprcrash
|
||||||
|
|
||||||
def toterminal(self, tw):
|
def toterminal(self, tw):
|
||||||
self.reprtraceback.toterminal(tw)
|
self.reprtraceback.toterminal(tw)
|
||||||
super(ReprExceptionInfo, self).toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
|
@ -969,7 +933,9 @@ class ReprEntry(TerminalRepr):
|
||||||
self.reprfileloc.toterminal(tw)
|
self.reprfileloc.toterminal(tw)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc)
|
return "{}\n{}\n{}".format(
|
||||||
|
"\n".join(self.lines), self.reprlocals, self.reprfileloc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReprFileLocation(TerminalRepr):
|
class ReprFileLocation(TerminalRepr):
|
||||||
|
@ -986,7 +952,7 @@ class ReprFileLocation(TerminalRepr):
|
||||||
if i != -1:
|
if i != -1:
|
||||||
msg = msg[:i]
|
msg = msg[:i]
|
||||||
tw.write(self.path, bold=True, red=True)
|
tw.write(self.path, bold=True, red=True)
|
||||||
tw.line(":%s: %s" % (self.lineno, msg))
|
tw.line(":{}: {}".format(self.lineno, msg))
|
||||||
|
|
||||||
|
|
||||||
class ReprLocals(TerminalRepr):
|
class ReprLocals(TerminalRepr):
|
||||||
|
@ -1006,7 +972,7 @@ class ReprFuncArgs(TerminalRepr):
|
||||||
if self.args:
|
if self.args:
|
||||||
linesofar = ""
|
linesofar = ""
|
||||||
for name, value in self.args:
|
for name, value in self.args:
|
||||||
ns = "%s = %s" % (safe_str(name), safe_str(value))
|
ns = "{} = {}".format(name, value)
|
||||||
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
|
||||||
if linesofar:
|
if linesofar:
|
||||||
tw.line(linesofar)
|
tw.line(linesofar)
|
||||||
|
@ -1038,23 +1004,6 @@ def getrawcode(obj, trycall=True):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
if PY35: # RecursionError introduced in 3.5
|
|
||||||
|
|
||||||
def is_recursion_error(excinfo):
|
|
||||||
return excinfo.errisinstance(RecursionError) # noqa
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def is_recursion_error(excinfo):
|
|
||||||
if not excinfo.errisinstance(RuntimeError):
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
return "maximum recursion depth exceeded" in str(excinfo.value)
|
|
||||||
except UnicodeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||||
# see filter_traceback
|
# see filter_traceback
|
||||||
# note: if we need to add more paths than what we have now we should probably use a list
|
# note: if we need to add more paths than what we have now we should probably use a list
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import inspect
|
import inspect
|
||||||
import linecache
|
import linecache
|
||||||
|
@ -14,10 +9,9 @@ from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class Source(object):
|
class Source:
|
||||||
""" an immutable object holding a source code fragment,
|
""" an immutable object holding a source code fragment,
|
||||||
possibly deindenting it.
|
possibly deindenting it.
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +28,7 @@ class Source(object):
|
||||||
partlines = part.lines
|
partlines = part.lines
|
||||||
elif isinstance(part, (tuple, list)):
|
elif isinstance(part, (tuple, list)):
|
||||||
partlines = [x.rstrip("\n") for x in part]
|
partlines = [x.rstrip("\n") for x in part]
|
||||||
elif isinstance(part, six.string_types):
|
elif isinstance(part, str):
|
||||||
partlines = part.split("\n")
|
partlines = part.split("\n")
|
||||||
else:
|
else:
|
||||||
partlines = getsource(part, deindent=de).lines
|
partlines = getsource(part, deindent=de).lines
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import pprint
|
import pprint
|
||||||
|
import reprlib
|
||||||
from six.moves import reprlib
|
|
||||||
|
|
||||||
|
|
||||||
def _call_and_format_exception(call, x, *args):
|
def _call_and_format_exception(call, x, *args):
|
||||||
|
@ -14,11 +12,8 @@ def _call_and_format_exception(call, x, *args):
|
||||||
exc_info = str(exc)
|
exc_info = str(exc)
|
||||||
except Exception:
|
except Exception:
|
||||||
exc_info = "unknown"
|
exc_info = "unknown"
|
||||||
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
|
return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
|
||||||
exc_name,
|
exc_name, exc_info, x.__class__.__name__, id(x)
|
||||||
exc_info,
|
|
||||||
x.__class__.__name__,
|
|
||||||
id(x),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,11 +29,11 @@ class SafeRepr(reprlib.Repr):
|
||||||
# Strictly speaking wrong on narrow builds
|
# Strictly speaking wrong on narrow builds
|
||||||
def repr(u):
|
def repr(u):
|
||||||
if "'" not in u:
|
if "'" not in u:
|
||||||
return u"'%s'" % u
|
return "'%s'" % u
|
||||||
elif '"' not in u:
|
elif '"' not in u:
|
||||||
return u'"%s"' % u
|
return '"%s"' % u
|
||||||
else:
|
else:
|
||||||
return u"'%s'" % u.replace("'", r"\'")
|
return "'%s'" % u.replace("'", r"\'")
|
||||||
|
|
||||||
s = repr(x[: self.maxstring])
|
s = repr(x[: self.maxstring])
|
||||||
if len(s) > self.maxstring:
|
if len(s) > self.maxstring:
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
support for presenting detailed information in failing assertions.
|
support for presenting detailed information in failing assertions.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from _pytest.assertion import rewrite
|
from _pytest.assertion import rewrite
|
||||||
from _pytest.assertion import truncate
|
from _pytest.assertion import truncate
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
|
@ -56,14 +49,14 @@ def register_assert_rewrite(*names):
|
||||||
importhook.mark_rewrite(*names)
|
importhook.mark_rewrite(*names)
|
||||||
|
|
||||||
|
|
||||||
class DummyRewriteHook(object):
|
class DummyRewriteHook:
|
||||||
"""A no-op import hook for when rewriting is disabled."""
|
"""A no-op import hook for when rewriting is disabled."""
|
||||||
|
|
||||||
def mark_rewrite(self, *names):
|
def mark_rewrite(self, *names):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssertionState(object):
|
class AssertionState:
|
||||||
"""State for the assertion plugin."""
|
"""State for the assertion plugin."""
|
||||||
|
|
||||||
def __init__(self, config, mode):
|
def __init__(self, config, mode):
|
||||||
|
@ -74,10 +67,6 @@ class AssertionState(object):
|
||||||
|
|
||||||
def install_importhook(config):
|
def install_importhook(config):
|
||||||
"""Try to install the rewrite hook, raise SystemError if it fails."""
|
"""Try to install the rewrite hook, raise SystemError if it fails."""
|
||||||
# Jython has an AST bug that make the assertion rewriting hook malfunction.
|
|
||||||
if sys.platform.startswith("java"):
|
|
||||||
raise SystemError("rewrite not supported")
|
|
||||||
|
|
||||||
config._assertstate = AssertionState(config, "rewrite")
|
config._assertstate = AssertionState(config, "rewrite")
|
||||||
config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
|
config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
|
||||||
sys.meta_path.insert(0, hook)
|
sys.meta_path.insert(0, hook)
|
||||||
|
@ -133,7 +122,7 @@ def pytest_runtest_setup(item):
|
||||||
if new_expl:
|
if new_expl:
|
||||||
new_expl = truncate.truncate_if_required(new_expl, item)
|
new_expl = truncate.truncate_if_required(new_expl, item)
|
||||||
new_expl = [line.replace("\n", "\\n") for line in new_expl]
|
new_expl = [line.replace("\n", "\\n") for line in new_expl]
|
||||||
res = six.text_type("\n~").join(new_expl)
|
res = "\n~".join(new_expl)
|
||||||
if item.config.getvalue("assertmode") == "rewrite":
|
if item.config.getvalue("assertmode") == "rewrite":
|
||||||
res = res.replace("%", "%%")
|
res = res.replace("%", "%%")
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Rewrite assertion AST to produce nice error messages"""
|
"""Rewrite assertion AST to produce nice error messages"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import errno
|
import errno
|
||||||
import imp
|
import imp
|
||||||
|
@ -15,17 +10,16 @@ import string
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from importlib.util import spec_from_file_location
|
||||||
|
|
||||||
import atomicwrites
|
import atomicwrites
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
from _pytest.assertion.util import ( # noqa: F401
|
from _pytest.assertion.util import ( # noqa: F401
|
||||||
format_explanation as _format_explanation,
|
format_explanation as _format_explanation,
|
||||||
)
|
)
|
||||||
from _pytest.compat import spec_from_file_location
|
|
||||||
from _pytest.pathlib import fnmatch_ex
|
from _pytest.pathlib import fnmatch_ex
|
||||||
from _pytest.pathlib import PurePath
|
from _pytest.pathlib import PurePath
|
||||||
|
|
||||||
|
@ -35,28 +29,17 @@ if hasattr(imp, "get_tag"):
|
||||||
else:
|
else:
|
||||||
if hasattr(sys, "pypy_version_info"):
|
if hasattr(sys, "pypy_version_info"):
|
||||||
impl = "pypy"
|
impl = "pypy"
|
||||||
elif sys.platform == "java":
|
|
||||||
impl = "jython"
|
|
||||||
else:
|
else:
|
||||||
impl = "cpython"
|
impl = "cpython"
|
||||||
ver = sys.version_info
|
ver = sys.version_info
|
||||||
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
|
PYTEST_TAG = "{}-{}{}-PYTEST".format(impl, ver[0], ver[1])
|
||||||
del ver, impl
|
del ver, impl
|
||||||
|
|
||||||
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
||||||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||||
|
|
||||||
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 5):
|
class AssertionRewritingHook:
|
||||||
ast_Call = ast.Call
|
|
||||||
else:
|
|
||||||
|
|
||||||
def ast_Call(a, b, c):
|
|
||||||
return ast.Call(a, b, c, None, None)
|
|
||||||
|
|
||||||
|
|
||||||
class AssertionRewritingHook(object):
|
|
||||||
"""PEP302 Import hook which rewrites asserts."""
|
"""PEP302 Import hook which rewrites asserts."""
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -165,7 +148,7 @@ class AssertionRewritingHook(object):
|
||||||
# to check for a cached pyc. This may not be optimal...
|
# to check for a cached pyc. This may not be optimal...
|
||||||
co = _read_pyc(fn_pypath, pyc, state.trace)
|
co = _read_pyc(fn_pypath, pyc, state.trace)
|
||||||
if co is None:
|
if co is None:
|
||||||
state.trace("rewriting %r" % (fn,))
|
state.trace("rewriting {!r}".format(fn))
|
||||||
source_stat, co = _rewrite_test(self.config, fn_pypath)
|
source_stat, co = _rewrite_test(self.config, fn_pypath)
|
||||||
if co is None:
|
if co is None:
|
||||||
# Probably a SyntaxError in the test.
|
# Probably a SyntaxError in the test.
|
||||||
|
@ -177,7 +160,7 @@ class AssertionRewritingHook(object):
|
||||||
finally:
|
finally:
|
||||||
self._writing_pyc = False
|
self._writing_pyc = False
|
||||||
else:
|
else:
|
||||||
state.trace("found cached rewritten pyc for %r" % (fn,))
|
state.trace("found cached rewritten pyc for {!r}".format(fn))
|
||||||
self.modules[name] = co, pyc
|
self.modules[name] = co, pyc
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -216,26 +199,28 @@ class AssertionRewritingHook(object):
|
||||||
if self._is_marked_for_rewrite(name, state):
|
if self._is_marked_for_rewrite(name, state):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
state.trace("early skip of rewriting module: %s" % (name,))
|
state.trace("early skip of rewriting module: {}".format(name))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _should_rewrite(self, name, fn_pypath, state):
|
def _should_rewrite(self, name, fn_pypath, state):
|
||||||
# always rewrite conftest files
|
# always rewrite conftest files
|
||||||
fn = str(fn_pypath)
|
fn = str(fn_pypath)
|
||||||
if fn_pypath.basename == "conftest.py":
|
if fn_pypath.basename == "conftest.py":
|
||||||
state.trace("rewriting conftest file: %r" % (fn,))
|
state.trace("rewriting conftest file: {!r}".format(fn))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.session is not None:
|
if self.session is not None:
|
||||||
if self.session.isinitpath(fn):
|
if self.session.isinitpath(fn):
|
||||||
state.trace("matched test file (was specified on cmdline): %r" % (fn,))
|
state.trace(
|
||||||
|
"matched test file (was specified on cmdline): {!r}".format(fn)
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# modules not passed explicitly on the command line are only
|
# modules not passed explicitly on the command line are only
|
||||||
# rewritten if they match the naming convention for test files
|
# rewritten if they match the naming convention for test files
|
||||||
for pat in self.fnpats:
|
for pat in self.fnpats:
|
||||||
if fn_pypath.fnmatch(pat):
|
if fn_pypath.fnmatch(pat):
|
||||||
state.trace("matched test file %r" % (fn,))
|
state.trace("matched test file {!r}".format(fn))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return self._is_marked_for_rewrite(name, state)
|
return self._is_marked_for_rewrite(name, state)
|
||||||
|
@ -246,7 +231,9 @@ class AssertionRewritingHook(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
for marked in self._must_rewrite:
|
for marked in self._must_rewrite:
|
||||||
if name == marked or name.startswith(marked + "."):
|
if name == marked or name.startswith(marked + "."):
|
||||||
state.trace("matched marked file %r (from %r)" % (name, marked))
|
state.trace(
|
||||||
|
"matched marked file {!r} (from {!r})".format(name, marked)
|
||||||
|
)
|
||||||
self._marked_for_rewrite_cache[name] = True
|
self._marked_for_rewrite_cache[name] = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -341,7 +328,7 @@ def _write_pyc(state, co, source_stat, pyc):
|
||||||
fp.write(struct.pack("<LL", mtime, size))
|
fp.write(struct.pack("<LL", mtime, size))
|
||||||
fp.write(marshal.dumps(co))
|
fp.write(marshal.dumps(co))
|
||||||
except EnvironmentError as e:
|
except EnvironmentError as e:
|
||||||
state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno))
|
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
|
||||||
# we ignore any failure to write the cache file
|
# we ignore any failure to write the cache file
|
||||||
# there are many reasons, permission-denied, __pycache__ being a
|
# there are many reasons, permission-denied, __pycache__ being a
|
||||||
# file etc.
|
# file etc.
|
||||||
|
@ -349,8 +336,8 @@ def _write_pyc(state, co, source_stat, pyc):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
RN = "\r\n".encode("utf-8")
|
RN = "\r\n".encode()
|
||||||
N = "\n".encode("utf-8")
|
N = "\n".encode()
|
||||||
|
|
||||||
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
|
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
|
||||||
BOM_UTF8 = "\xef\xbb\xbf"
|
BOM_UTF8 = "\xef\xbb\xbf"
|
||||||
|
@ -364,42 +351,11 @@ def _rewrite_test(config, fn):
|
||||||
source = fn.read("rb")
|
source = fn.read("rb")
|
||||||
except EnvironmentError:
|
except EnvironmentError:
|
||||||
return None, None
|
return None, None
|
||||||
if ASCII_IS_DEFAULT_ENCODING:
|
|
||||||
# ASCII is the default encoding in Python 2. Without a coding
|
|
||||||
# declaration, Python 2 will complain about any bytes in the file
|
|
||||||
# outside the ASCII range. Sadly, this behavior does not extend to
|
|
||||||
# compile() or ast.parse(), which prefer to interpret the bytes as
|
|
||||||
# latin-1. (At least they properly handle explicit coding cookies.) To
|
|
||||||
# preserve this error behavior, we could force ast.parse() to use ASCII
|
|
||||||
# as the encoding by inserting a coding cookie. Unfortunately, that
|
|
||||||
# messes up line numbers. Thus, we have to check ourselves if anything
|
|
||||||
# is outside the ASCII range in the case no encoding is explicitly
|
|
||||||
# declared. For more context, see issue #269. Yay for Python 3 which
|
|
||||||
# gets this right.
|
|
||||||
end1 = source.find("\n")
|
|
||||||
end2 = source.find("\n", end1 + 1)
|
|
||||||
if (
|
|
||||||
not source.startswith(BOM_UTF8)
|
|
||||||
and cookie_re.match(source[0:end1]) is None
|
|
||||||
and cookie_re.match(source[end1 + 1 : end2]) is None
|
|
||||||
):
|
|
||||||
if hasattr(state, "_indecode"):
|
|
||||||
# encodings imported us again, so don't rewrite.
|
|
||||||
return None, None
|
|
||||||
state._indecode = True
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
source.decode("ascii")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
# Let it fail in real import.
|
|
||||||
return None, None
|
|
||||||
finally:
|
|
||||||
del state._indecode
|
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(source, filename=fn.strpath)
|
tree = ast.parse(source, filename=fn.strpath)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Let this pop up again in the real import.
|
# Let this pop up again in the real import.
|
||||||
state.trace("failed to parse: %r" % (fn,))
|
state.trace("failed to parse: {!r}".format(fn))
|
||||||
return None, None
|
return None, None
|
||||||
rewrite_asserts(tree, fn, config)
|
rewrite_asserts(tree, fn, config)
|
||||||
try:
|
try:
|
||||||
|
@ -407,7 +363,7 @@ def _rewrite_test(config, fn):
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# It's possible that this error is from some bug in the
|
# It's possible that this error is from some bug in the
|
||||||
# assertion rewriting, but I don't know of a fast way to tell.
|
# assertion rewriting, but I don't know of a fast way to tell.
|
||||||
state.trace("failed to compile: %r" % (fn,))
|
state.trace("failed to compile: {!r}".format(fn))
|
||||||
return None, None
|
return None, None
|
||||||
return stat, co
|
return stat, co
|
||||||
|
|
||||||
|
@ -427,7 +383,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
|
||||||
size = source.size()
|
size = source.size()
|
||||||
data = fp.read(12)
|
data = fp.read(12)
|
||||||
except EnvironmentError as e:
|
except EnvironmentError as e:
|
||||||
trace("_read_pyc(%s): EnvironmentError %s" % (source, e))
|
trace("_read_pyc({}): EnvironmentError {}".format(source, e))
|
||||||
return None
|
return None
|
||||||
# Check for invalid or out of date pyc file.
|
# Check for invalid or out of date pyc file.
|
||||||
if (
|
if (
|
||||||
|
@ -440,7 +396,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
|
||||||
try:
|
try:
|
||||||
co = marshal.load(fp)
|
co = marshal.load(fp)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
trace("_read_pyc(%s): marshal.load error %s" % (source, e))
|
trace("_read_pyc({}): marshal.load error {}".format(source, e))
|
||||||
return None
|
return None
|
||||||
if not isinstance(co, types.CodeType):
|
if not isinstance(co, types.CodeType):
|
||||||
trace("_read_pyc(%s): not a code object" % source)
|
trace("_read_pyc(%s): not a code object" % source)
|
||||||
|
@ -468,11 +424,11 @@ def _saferepr(obj):
|
||||||
# only occurs in python2.x, repr must return text in python3+
|
# only occurs in python2.x, repr must return text in python3+
|
||||||
if isinstance(r, bytes):
|
if isinstance(r, bytes):
|
||||||
# Represent unprintable bytes as `\x##`
|
# Represent unprintable bytes as `\x##`
|
||||||
r = u"".join(
|
r = "".join(
|
||||||
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
|
"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
|
||||||
for c in r
|
for c in r
|
||||||
)
|
)
|
||||||
return r.replace(u"\n", u"\\n")
|
return r.replace("\n", "\\n")
|
||||||
|
|
||||||
|
|
||||||
def _format_assertmsg(obj):
|
def _format_assertmsg(obj):
|
||||||
|
@ -487,10 +443,10 @@ def _format_assertmsg(obj):
|
||||||
# contains a newline it gets escaped, however if an object has a
|
# contains a newline it gets escaped, however if an object has a
|
||||||
# .__repr__() which contains newlines it does not get escaped.
|
# .__repr__() which contains newlines it does not get escaped.
|
||||||
# However in either case we want to preserve the newline.
|
# However in either case we want to preserve the newline.
|
||||||
replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
|
replaces = [("\n", "\n~"), ("%", "%%")]
|
||||||
if not isinstance(obj, six.string_types):
|
if not isinstance(obj, str):
|
||||||
obj = saferepr(obj)
|
obj = saferepr(obj)
|
||||||
replaces.append((u"\\n", u"\n~"))
|
replaces.append(("\\n", "\n~"))
|
||||||
|
|
||||||
if isinstance(obj, bytes):
|
if isinstance(obj, bytes):
|
||||||
replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces]
|
replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces]
|
||||||
|
@ -513,8 +469,8 @@ def _should_repr_global_name(obj):
|
||||||
|
|
||||||
def _format_boolop(explanations, is_or):
|
def _format_boolop(explanations, is_or):
|
||||||
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
|
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
|
||||||
if isinstance(explanation, six.text_type):
|
if isinstance(explanation, str):
|
||||||
return explanation.replace(u"%", u"%%")
|
return explanation.replace("%", "%%")
|
||||||
else:
|
else:
|
||||||
return explanation.replace(b"%", b"%%")
|
return explanation.replace(b"%", b"%%")
|
||||||
|
|
||||||
|
@ -643,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, module_path, config):
|
def __init__(self, module_path, config):
|
||||||
super(AssertionRewriter, self).__init__()
|
super().__init__()
|
||||||
self.module_path = module_path
|
self.module_path = module_path
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
@ -655,7 +611,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
# Insert some special imports at the top of the module but after any
|
# Insert some special imports at the top of the module but after any
|
||||||
# docstrings and __future__ imports.
|
# docstrings and __future__ imports.
|
||||||
aliases = [
|
aliases = [
|
||||||
ast.alias(six.moves.builtins.__name__, "@py_builtins"),
|
ast.alias("builtins", "@py_builtins"),
|
||||||
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
|
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
|
||||||
]
|
]
|
||||||
doc = getattr(mod, "docstring", None)
|
doc = getattr(mod, "docstring", None)
|
||||||
|
@ -737,7 +693,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
"""Call a helper in this module."""
|
"""Call a helper in this module."""
|
||||||
py_name = ast.Name("@pytest_ar", ast.Load())
|
py_name = ast.Name("@pytest_ar", ast.Load())
|
||||||
attr = ast.Attribute(py_name, name, ast.Load())
|
attr = ast.Attribute(py_name, name, ast.Load())
|
||||||
return ast_Call(attr, list(args), [])
|
return ast.Call(attr, list(args), [])
|
||||||
|
|
||||||
def builtin(self, name):
|
def builtin(self, name):
|
||||||
"""Return the builtin called *name*."""
|
"""Return the builtin called *name*."""
|
||||||
|
@ -847,11 +803,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
msg = self.pop_format_context(template)
|
msg = self.pop_format_context(template)
|
||||||
fmt = self.helper("_format_explanation", msg)
|
fmt = self.helper("_format_explanation", msg)
|
||||||
err_name = ast.Name("AssertionError", ast.Load())
|
err_name = ast.Name("AssertionError", ast.Load())
|
||||||
exc = ast_Call(err_name, [fmt], [])
|
exc = ast.Call(err_name, [fmt], [])
|
||||||
if sys.version_info[0] >= 3:
|
raise_ = ast.Raise(exc, None)
|
||||||
raise_ = ast.Raise(exc, None)
|
|
||||||
else:
|
|
||||||
raise_ = ast.Raise(exc, None, None)
|
|
||||||
body.append(raise_)
|
body.append(raise_)
|
||||||
# Clear temporary variables by setting them to None.
|
# Clear temporary variables by setting them to None.
|
||||||
if self.variables:
|
if self.variables:
|
||||||
|
@ -893,7 +847,7 @@ warn_explicit(
|
||||||
def visit_Name(self, name):
|
def visit_Name(self, name):
|
||||||
# Display the repr of the name if it's a local variable or
|
# Display the repr of the name if it's a local variable or
|
||||||
# _should_repr_global_name() thinks it's acceptable.
|
# _should_repr_global_name() thinks it's acceptable.
|
||||||
locs = ast_Call(self.builtin("locals"), [], [])
|
locs = ast.Call(self.builtin("locals"), [], [])
|
||||||
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
|
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
|
||||||
dorepr = self.helper("_should_repr_global_name", name)
|
dorepr = self.helper("_should_repr_global_name", name)
|
||||||
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
|
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
|
||||||
|
@ -920,7 +874,7 @@ warn_explicit(
|
||||||
res, expl = self.visit(v)
|
res, expl = self.visit(v)
|
||||||
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
||||||
expl_format = self.pop_format_context(ast.Str(expl))
|
expl_format = self.pop_format_context(ast.Str(expl))
|
||||||
call = ast_Call(app, [expl_format], [])
|
call = ast.Call(app, [expl_format], [])
|
||||||
self.on_failure.append(ast.Expr(call))
|
self.on_failure.append(ast.Expr(call))
|
||||||
if i < levels:
|
if i < levels:
|
||||||
cond = res
|
cond = res
|
||||||
|
@ -945,15 +899,25 @@ warn_explicit(
|
||||||
symbol = binop_map[binop.op.__class__]
|
symbol = binop_map[binop.op.__class__]
|
||||||
left_expr, left_expl = self.visit(binop.left)
|
left_expr, left_expl = self.visit(binop.left)
|
||||||
right_expr, right_expl = self.visit(binop.right)
|
right_expr, right_expl = self.visit(binop.right)
|
||||||
explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
|
explanation = "({} {} {})".format(left_expl, symbol, right_expl)
|
||||||
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
||||||
return res, explanation
|
return res, explanation
|
||||||
|
|
||||||
def visit_Call_35(self, call):
|
@staticmethod
|
||||||
|
def _is_any_call_with_generator_or_list_comprehension(call):
|
||||||
|
"""Return True if the Call node is an 'any' call with a generator or list comprehension"""
|
||||||
|
return (
|
||||||
|
isinstance(call.func, ast.Name)
|
||||||
|
and call.func.id == "all"
|
||||||
|
and len(call.args) == 1
|
||||||
|
and isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp))
|
||||||
|
)
|
||||||
|
|
||||||
|
def visit_Call(self, call):
|
||||||
"""
|
"""
|
||||||
visit `ast.Call` nodes on Python3.5 and after
|
visit `ast.Call` nodes
|
||||||
"""
|
"""
|
||||||
if isinstance(call.func, ast.Name) and call.func.id == "all":
|
if self._is_any_call_with_generator_or_list_comprehension(call):
|
||||||
return self._visit_all(call)
|
return self._visit_all(call)
|
||||||
new_func, func_expl = self.visit(call.func)
|
new_func, func_expl = self.visit(call.func)
|
||||||
arg_expls = []
|
arg_expls = []
|
||||||
|
@ -971,17 +935,15 @@ warn_explicit(
|
||||||
else: # **args have `arg` keywords with an .arg of None
|
else: # **args have `arg` keywords with an .arg of None
|
||||||
arg_expls.append("**" + expl)
|
arg_expls.append("**" + expl)
|
||||||
|
|
||||||
expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
|
expl = "{}({})".format(func_expl, ", ".join(arg_expls))
|
||||||
new_call = ast.Call(new_func, new_args, new_kwargs)
|
new_call = ast.Call(new_func, new_args, new_kwargs)
|
||||||
res = self.assign(new_call)
|
res = self.assign(new_call)
|
||||||
res_expl = self.explanation_param(self.display(res))
|
res_expl = self.explanation_param(self.display(res))
|
||||||
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
|
outer_expl = "{}\n{{{} = {}\n}}".format(res_expl, res_expl, expl)
|
||||||
return res, outer_expl
|
return res, outer_expl
|
||||||
|
|
||||||
def _visit_all(self, call):
|
def _visit_all(self, call):
|
||||||
"""Special rewrite for the builtin all function, see #5062"""
|
"""Special rewrite for the builtin all function, see #5062"""
|
||||||
if not isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp)):
|
|
||||||
return
|
|
||||||
gen_exp = call.args[0]
|
gen_exp = call.args[0]
|
||||||
assertion_module = ast.Module(
|
assertion_module = ast.Module(
|
||||||
body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)]
|
body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)]
|
||||||
|
@ -1005,46 +967,6 @@ warn_explicit(
|
||||||
new_starred = ast.Starred(res, starred.ctx)
|
new_starred = ast.Starred(res, starred.ctx)
|
||||||
return new_starred, "*" + expl
|
return new_starred, "*" + expl
|
||||||
|
|
||||||
def visit_Call_legacy(self, call):
|
|
||||||
"""
|
|
||||||
visit `ast.Call nodes on 3.4 and below`
|
|
||||||
"""
|
|
||||||
if isinstance(call.func, ast.Name) and call.func.id == "all":
|
|
||||||
return self._visit_all(call)
|
|
||||||
new_func, func_expl = self.visit(call.func)
|
|
||||||
arg_expls = []
|
|
||||||
new_args = []
|
|
||||||
new_kwargs = []
|
|
||||||
new_star = new_kwarg = None
|
|
||||||
for arg in call.args:
|
|
||||||
res, expl = self.visit(arg)
|
|
||||||
new_args.append(res)
|
|
||||||
arg_expls.append(expl)
|
|
||||||
for keyword in call.keywords:
|
|
||||||
res, expl = self.visit(keyword.value)
|
|
||||||
new_kwargs.append(ast.keyword(keyword.arg, res))
|
|
||||||
arg_expls.append(keyword.arg + "=" + expl)
|
|
||||||
if call.starargs:
|
|
||||||
new_star, expl = self.visit(call.starargs)
|
|
||||||
arg_expls.append("*" + expl)
|
|
||||||
if call.kwargs:
|
|
||||||
new_kwarg, expl = self.visit(call.kwargs)
|
|
||||||
arg_expls.append("**" + expl)
|
|
||||||
expl = "%s(%s)" % (func_expl, ", ".join(arg_expls))
|
|
||||||
new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
|
|
||||||
res = self.assign(new_call)
|
|
||||||
res_expl = self.explanation_param(self.display(res))
|
|
||||||
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
|
|
||||||
return res, outer_expl
|
|
||||||
|
|
||||||
# ast.Call signature changed on 3.5,
|
|
||||||
# conditionally change which methods is named
|
|
||||||
# visit_Call depending on Python version
|
|
||||||
if sys.version_info >= (3, 5):
|
|
||||||
visit_Call = visit_Call_35
|
|
||||||
else:
|
|
||||||
visit_Call = visit_Call_legacy
|
|
||||||
|
|
||||||
def visit_Attribute(self, attr):
|
def visit_Attribute(self, attr):
|
||||||
if not isinstance(attr.ctx, ast.Load):
|
if not isinstance(attr.ctx, ast.Load):
|
||||||
return self.generic_visit(attr)
|
return self.generic_visit(attr)
|
||||||
|
@ -1074,7 +996,7 @@ warn_explicit(
|
||||||
results.append(next_res)
|
results.append(next_res)
|
||||||
sym = binop_map[op.__class__]
|
sym = binop_map[op.__class__]
|
||||||
syms.append(ast.Str(sym))
|
syms.append(ast.Str(sym))
|
||||||
expl = "%s %s %s" % (left_expl, sym, next_expl)
|
expl = "{} {} {}".format(left_expl, sym, next_expl)
|
||||||
expls.append(ast.Str(expl))
|
expls.append(ast.Str(expl))
|
||||||
res_expr = ast.Compare(left_res, [op], [next_res])
|
res_expr = ast.Compare(left_res, [op], [next_res])
|
||||||
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Utilities for truncating assertion output.
|
Utilities for truncating assertion output.
|
||||||
|
|
||||||
Current default behaviour is to truncate assertion explanations at
|
Current default behaviour is to truncate assertion explanations at
|
||||||
~8 terminal lines, unless running in "-vv" mode or running on CI.
|
~8 terminal lines, unless running in "-vv" mode or running on CI.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
DEFAULT_MAX_LINES = 8
|
DEFAULT_MAX_LINES = 8
|
||||||
DEFAULT_MAX_CHARS = 8 * 80
|
DEFAULT_MAX_CHARS = 8 * 80
|
||||||
USAGE_MSG = "use '-vv' to show"
|
USAGE_MSG = "use '-vv' to show"
|
||||||
|
@ -76,7 +69,7 @@ def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
|
||||||
else:
|
else:
|
||||||
msg += " ({} lines hidden)".format(truncated_line_count)
|
msg += " ({} lines hidden)".format(truncated_line_count)
|
||||||
msg += ", {}".format(USAGE_MSG)
|
msg += ", {}".format(USAGE_MSG)
|
||||||
truncated_explanation.extend([six.text_type(""), six.text_type(msg)])
|
truncated_explanation.extend(["", str(msg)])
|
||||||
return truncated_explanation
|
return truncated_explanation
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Utilities for assertion debugging"""
|
"""Utilities for assertion debugging"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import pprint
|
import pprint
|
||||||
|
from collections.abc import Sequence
|
||||||
import six
|
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from ..compat import Sequence
|
|
||||||
from _pytest import outcomes
|
from _pytest import outcomes
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
|
||||||
|
@ -42,7 +35,7 @@ def format_explanation(explanation):
|
||||||
explanation = ecu(explanation)
|
explanation = ecu(explanation)
|
||||||
lines = _split_explanation(explanation)
|
lines = _split_explanation(explanation)
|
||||||
result = _format_lines(lines)
|
result = _format_lines(lines)
|
||||||
return u"\n".join(result)
|
return "\n".join(result)
|
||||||
|
|
||||||
|
|
||||||
def _split_explanation(explanation):
|
def _split_explanation(explanation):
|
||||||
|
@ -52,7 +45,7 @@ def _split_explanation(explanation):
|
||||||
Any other newlines will be escaped and appear in the line as the
|
Any other newlines will be escaped and appear in the line as the
|
||||||
literal '\n' characters.
|
literal '\n' characters.
|
||||||
"""
|
"""
|
||||||
raw_lines = (explanation or u"").split("\n")
|
raw_lines = (explanation or "").split("\n")
|
||||||
lines = [raw_lines[0]]
|
lines = [raw_lines[0]]
|
||||||
for values in raw_lines[1:]:
|
for values in raw_lines[1:]:
|
||||||
if values and values[0] in ["{", "}", "~", ">"]:
|
if values and values[0] in ["{", "}", "~", ">"]:
|
||||||
|
@ -77,13 +70,13 @@ def _format_lines(lines):
|
||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if line.startswith("{"):
|
if line.startswith("{"):
|
||||||
if stackcnt[-1]:
|
if stackcnt[-1]:
|
||||||
s = u"and "
|
s = "and "
|
||||||
else:
|
else:
|
||||||
s = u"where "
|
s = "where "
|
||||||
stack.append(len(result))
|
stack.append(len(result))
|
||||||
stackcnt[-1] += 1
|
stackcnt[-1] += 1
|
||||||
stackcnt.append(0)
|
stackcnt.append(0)
|
||||||
result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:])
|
result.append(" +" + " " * (len(stack) - 1) + s + line[1:])
|
||||||
elif line.startswith("}"):
|
elif line.startswith("}"):
|
||||||
stack.pop()
|
stack.pop()
|
||||||
stackcnt.pop()
|
stackcnt.pop()
|
||||||
|
@ -92,7 +85,7 @@ def _format_lines(lines):
|
||||||
assert line[0] in ["~", ">"]
|
assert line[0] in ["~", ">"]
|
||||||
stack[-1] += 1
|
stack[-1] += 1
|
||||||
indent = len(stack) if line.startswith("~") else len(stack) - 1
|
indent = len(stack) if line.startswith("~") else len(stack) - 1
|
||||||
result.append(u" " * indent + line[1:])
|
result.append(" " * indent + line[1:])
|
||||||
assert len(stack) == 1
|
assert len(stack) == 1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -142,7 +135,7 @@ def assertrepr_compare(config, op, left, right):
|
||||||
left_repr = saferepr(left, maxsize=int(width // 2))
|
left_repr = saferepr(left, maxsize=int(width // 2))
|
||||||
right_repr = saferepr(right, maxsize=width - len(left_repr))
|
right_repr = saferepr(right, maxsize=width - len(left_repr))
|
||||||
|
|
||||||
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
|
summary = "{} {} {}".format(ecu(left_repr), op, ecu(right_repr))
|
||||||
|
|
||||||
verbose = config.getoption("verbose")
|
verbose = config.getoption("verbose")
|
||||||
explanation = None
|
explanation = None
|
||||||
|
@ -175,9 +168,9 @@ def assertrepr_compare(config, op, left, right):
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
explanation = [
|
explanation = [
|
||||||
u"(pytest_assertion plugin: representation of details failed. "
|
"(pytest_assertion plugin: representation of details failed. "
|
||||||
u"Probably an object has a faulty __repr__.)",
|
"Probably an object has a faulty __repr__.)",
|
||||||
six.text_type(_pytest._code.ExceptionInfo.from_current()),
|
str(_pytest._code.ExceptionInfo.from_current()),
|
||||||
]
|
]
|
||||||
|
|
||||||
if not explanation:
|
if not explanation:
|
||||||
|
@ -204,7 +197,7 @@ def _diff_text(left, right, verbose=0):
|
||||||
This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
|
This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
|
||||||
newlines and carriage returns (#429).
|
newlines and carriage returns (#429).
|
||||||
"""
|
"""
|
||||||
r = six.text_type(repr(binary_text)[1:-1])
|
r = str(repr(binary_text)[1:-1])
|
||||||
r = r.replace(r"\n", "\n")
|
r = r.replace(r"\n", "\n")
|
||||||
r = r.replace(r"\r", "\r")
|
r = r.replace(r"\r", "\r")
|
||||||
return r
|
return r
|
||||||
|
@ -221,7 +214,7 @@ def _diff_text(left, right, verbose=0):
|
||||||
if i > 42:
|
if i > 42:
|
||||||
i -= 10 # Provide some context
|
i -= 10 # Provide some context
|
||||||
explanation = [
|
explanation = [
|
||||||
u"Skipping %s identical leading characters in diff, use -v to show" % i
|
"Skipping %s identical leading characters in diff, use -v to show" % i
|
||||||
]
|
]
|
||||||
left = left[i:]
|
left = left[i:]
|
||||||
right = right[i:]
|
right = right[i:]
|
||||||
|
@ -232,8 +225,8 @@ def _diff_text(left, right, verbose=0):
|
||||||
if i > 42:
|
if i > 42:
|
||||||
i -= 10 # Provide some context
|
i -= 10 # Provide some context
|
||||||
explanation += [
|
explanation += [
|
||||||
u"Skipping {} identical trailing "
|
"Skipping {} identical trailing "
|
||||||
u"characters in diff, use -v to show".format(i)
|
"characters in diff, use -v to show".format(i)
|
||||||
]
|
]
|
||||||
left = left[:-i]
|
left = left[:-i]
|
||||||
right = right[:-i]
|
right = right[:-i]
|
||||||
|
@ -241,7 +234,7 @@ def _diff_text(left, right, verbose=0):
|
||||||
if left.isspace() or right.isspace():
|
if left.isspace() or right.isspace():
|
||||||
left = repr(str(left))
|
left = repr(str(left))
|
||||||
right = repr(str(right))
|
right = repr(str(right))
|
||||||
explanation += [u"Strings contain only whitespace, escaping them using repr()"]
|
explanation += ["Strings contain only whitespace, escaping them using repr()"]
|
||||||
explanation += [
|
explanation += [
|
||||||
line.strip("\n")
|
line.strip("\n")
|
||||||
for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
|
for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
|
||||||
|
@ -255,29 +248,29 @@ def _compare_eq_verbose(left, right):
|
||||||
right_lines = repr(right).splitlines(keepends)
|
right_lines = repr(right).splitlines(keepends)
|
||||||
|
|
||||||
explanation = []
|
explanation = []
|
||||||
explanation += [u"-" + line for line in left_lines]
|
explanation += ["-" + line for line in left_lines]
|
||||||
explanation += [u"+" + line for line in right_lines]
|
explanation += ["+" + line for line in right_lines]
|
||||||
|
|
||||||
return explanation
|
return explanation
|
||||||
|
|
||||||
|
|
||||||
def _compare_eq_iterable(left, right, verbose=0):
|
def _compare_eq_iterable(left, right, verbose=0):
|
||||||
if not verbose:
|
if not verbose:
|
||||||
return [u"Use -v to get the full diff"]
|
return ["Use -v to get the full diff"]
|
||||||
# dynamic import to speedup pytest
|
# dynamic import to speedup pytest
|
||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
try:
|
try:
|
||||||
left_formatting = pprint.pformat(left).splitlines()
|
left_formatting = pprint.pformat(left).splitlines()
|
||||||
right_formatting = pprint.pformat(right).splitlines()
|
right_formatting = pprint.pformat(right).splitlines()
|
||||||
explanation = [u"Full diff:"]
|
explanation = ["Full diff:"]
|
||||||
except Exception:
|
except Exception:
|
||||||
# hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
|
# hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
|
||||||
# sorted() on a list would raise. See issue #718.
|
# sorted() on a list would raise. See issue #718.
|
||||||
# As a workaround, the full diff is generated by using the repr() string of each item of each container.
|
# As a workaround, the full diff is generated by using the repr() string of each item of each container.
|
||||||
left_formatting = sorted(repr(x) for x in left)
|
left_formatting = sorted(repr(x) for x in left)
|
||||||
right_formatting = sorted(repr(x) for x in right)
|
right_formatting = sorted(repr(x) for x in right)
|
||||||
explanation = [u"Full diff (fallback to calling repr on each item):"]
|
explanation = ["Full diff (fallback to calling repr on each item):"]
|
||||||
explanation.extend(
|
explanation.extend(
|
||||||
line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
|
line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
|
||||||
)
|
)
|
||||||
|
@ -290,7 +283,9 @@ def _compare_eq_sequence(left, right, verbose=0):
|
||||||
len_right = len(right)
|
len_right = len(right)
|
||||||
for i in range(min(len_left, len_right)):
|
for i in range(min(len_left, len_right)):
|
||||||
if left[i] != right[i]:
|
if left[i] != right[i]:
|
||||||
explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
|
explanation += [
|
||||||
|
"At index {} diff: {!r} != {!r}".format(i, left[i], right[i])
|
||||||
|
]
|
||||||
break
|
break
|
||||||
len_diff = len_left - len_right
|
len_diff = len_left - len_right
|
||||||
|
|
||||||
|
@ -304,10 +299,12 @@ def _compare_eq_sequence(left, right, verbose=0):
|
||||||
extra = saferepr(right[len_left])
|
extra = saferepr(right[len_left])
|
||||||
|
|
||||||
if len_diff == 1:
|
if len_diff == 1:
|
||||||
explanation += [u"%s contains one more item: %s" % (dir_with_more, extra)]
|
explanation += [
|
||||||
|
"{} contains one more item: {}".format(dir_with_more, extra)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
explanation += [
|
explanation += [
|
||||||
u"%s contains %d more items, first extra item: %s"
|
"%s contains %d more items, first extra item: %s"
|
||||||
% (dir_with_more, len_diff, extra)
|
% (dir_with_more, len_diff, extra)
|
||||||
]
|
]
|
||||||
return explanation
|
return explanation
|
||||||
|
@ -318,11 +315,11 @@ def _compare_eq_set(left, right, verbose=0):
|
||||||
diff_left = left - right
|
diff_left = left - right
|
||||||
diff_right = right - left
|
diff_right = right - left
|
||||||
if diff_left:
|
if diff_left:
|
||||||
explanation.append(u"Extra items in the left set:")
|
explanation.append("Extra items in the left set:")
|
||||||
for item in diff_left:
|
for item in diff_left:
|
||||||
explanation.append(saferepr(item))
|
explanation.append(saferepr(item))
|
||||||
if diff_right:
|
if diff_right:
|
||||||
explanation.append(u"Extra items in the right set:")
|
explanation.append("Extra items in the right set:")
|
||||||
for item in diff_right:
|
for item in diff_right:
|
||||||
explanation.append(saferepr(item))
|
explanation.append(saferepr(item))
|
||||||
return explanation
|
return explanation
|
||||||
|
@ -335,20 +332,20 @@ def _compare_eq_dict(left, right, verbose=0):
|
||||||
common = set_left.intersection(set_right)
|
common = set_left.intersection(set_right)
|
||||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||||
if same and verbose < 2:
|
if same and verbose < 2:
|
||||||
explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
|
explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
|
||||||
elif same:
|
elif same:
|
||||||
explanation += [u"Common items:"]
|
explanation += ["Common items:"]
|
||||||
explanation += pprint.pformat(same).splitlines()
|
explanation += pprint.pformat(same).splitlines()
|
||||||
diff = {k for k in common if left[k] != right[k]}
|
diff = {k for k in common if left[k] != right[k]}
|
||||||
if diff:
|
if diff:
|
||||||
explanation += [u"Differing items:"]
|
explanation += ["Differing items:"]
|
||||||
for k in diff:
|
for k in diff:
|
||||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||||
extra_left = set_left - set_right
|
extra_left = set_left - set_right
|
||||||
len_extra_left = len(extra_left)
|
len_extra_left = len(extra_left)
|
||||||
if len_extra_left:
|
if len_extra_left:
|
||||||
explanation.append(
|
explanation.append(
|
||||||
u"Left contains %d more item%s:"
|
"Left contains %d more item%s:"
|
||||||
% (len_extra_left, "" if len_extra_left == 1 else "s")
|
% (len_extra_left, "" if len_extra_left == 1 else "s")
|
||||||
)
|
)
|
||||||
explanation.extend(
|
explanation.extend(
|
||||||
|
@ -358,7 +355,7 @@ def _compare_eq_dict(left, right, verbose=0):
|
||||||
len_extra_right = len(extra_right)
|
len_extra_right = len(extra_right)
|
||||||
if len_extra_right:
|
if len_extra_right:
|
||||||
explanation.append(
|
explanation.append(
|
||||||
u"Right contains %d more item%s:"
|
"Right contains %d more item%s:"
|
||||||
% (len_extra_right, "" if len_extra_right == 1 else "s")
|
% (len_extra_right, "" if len_extra_right == 1 else "s")
|
||||||
)
|
)
|
||||||
explanation.extend(
|
explanation.extend(
|
||||||
|
@ -386,15 +383,15 @@ def _compare_eq_cls(left, right, verbose, type_fns):
|
||||||
|
|
||||||
explanation = []
|
explanation = []
|
||||||
if same and verbose < 2:
|
if same and verbose < 2:
|
||||||
explanation.append(u"Omitting %s identical items, use -vv to show" % len(same))
|
explanation.append("Omitting %s identical items, use -vv to show" % len(same))
|
||||||
elif same:
|
elif same:
|
||||||
explanation += [u"Matching attributes:"]
|
explanation += ["Matching attributes:"]
|
||||||
explanation += pprint.pformat(same).splitlines()
|
explanation += pprint.pformat(same).splitlines()
|
||||||
if diff:
|
if diff:
|
||||||
explanation += [u"Differing attributes:"]
|
explanation += ["Differing attributes:"]
|
||||||
for field in diff:
|
for field in diff:
|
||||||
explanation += [
|
explanation += [
|
||||||
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
|
("%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
|
||||||
]
|
]
|
||||||
return explanation
|
return explanation
|
||||||
|
|
||||||
|
@ -405,14 +402,14 @@ def _notin_text(term, text, verbose=0):
|
||||||
tail = text[index + len(term) :]
|
tail = text[index + len(term) :]
|
||||||
correct_text = head + tail
|
correct_text = head + tail
|
||||||
diff = _diff_text(correct_text, text, verbose)
|
diff = _diff_text(correct_text, text, verbose)
|
||||||
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)]
|
newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)]
|
||||||
for line in diff:
|
for line in diff:
|
||||||
if line.startswith(u"Skipping"):
|
if line.startswith("Skipping"):
|
||||||
continue
|
continue
|
||||||
if line.startswith(u"- "):
|
if line.startswith("- "):
|
||||||
continue
|
continue
|
||||||
if line.startswith(u"+ "):
|
if line.startswith("+ "):
|
||||||
newdiff.append(u" " + line[2:])
|
newdiff.append(" " + line[2:])
|
||||||
else:
|
else:
|
||||||
newdiff.append(line)
|
newdiff.append(line)
|
||||||
return newdiff
|
return newdiff
|
||||||
|
|
|
@ -1,29 +1,22 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
merged implementation of the cache provider
|
merged implementation of the cache provider
|
||||||
|
|
||||||
the name cache was not chosen to ensure pluggy automatically
|
the name cache was not chosen to ensure pluggy automatically
|
||||||
ignores the external pytest-cache
|
ignores the external pytest-cache
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from .compat import _PY2 as PY2
|
|
||||||
from .pathlib import Path
|
from .pathlib import Path
|
||||||
from .pathlib import resolve_from_str
|
from .pathlib import resolve_from_str
|
||||||
from .pathlib import rmtree
|
from .pathlib import rmtree
|
||||||
|
|
||||||
README_CONTENT = u"""\
|
README_CONTENT = """\
|
||||||
# pytest cache directory #
|
# pytest cache directory #
|
||||||
|
|
||||||
This directory contains data from the pytest's cache plugin,
|
This directory contains data from the pytest's cache plugin,
|
||||||
|
@ -43,7 +36,7 @@ Signature: 8a477f597d28d172789f06886806bc55
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class Cache(object):
|
class Cache:
|
||||||
_cachedir = attr.ib(repr=False)
|
_cachedir = attr.ib(repr=False)
|
||||||
_config = attr.ib(repr=False)
|
_config = attr.ib(repr=False)
|
||||||
|
|
||||||
|
@ -129,7 +122,7 @@ class Cache(object):
|
||||||
if not cache_dir_exists_already:
|
if not cache_dir_exists_already:
|
||||||
self._ensure_supporting_files()
|
self._ensure_supporting_files()
|
||||||
try:
|
try:
|
||||||
f = path.open("wb" if PY2 else "w")
|
f = path.open("w")
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.warn("cache could not write path {path}", path=path)
|
self.warn("cache could not write path {path}", path=path)
|
||||||
else:
|
else:
|
||||||
|
@ -142,14 +135,14 @@ class Cache(object):
|
||||||
readme_path.write_text(README_CONTENT)
|
readme_path.write_text(README_CONTENT)
|
||||||
|
|
||||||
gitignore_path = self._cachedir.joinpath(".gitignore")
|
gitignore_path = self._cachedir.joinpath(".gitignore")
|
||||||
msg = u"# Created by pytest automatically.\n*"
|
msg = "# Created by pytest automatically.\n*"
|
||||||
gitignore_path.write_text(msg, encoding="UTF-8")
|
gitignore_path.write_text(msg, encoding="UTF-8")
|
||||||
|
|
||||||
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
|
||||||
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class LFPlugin(object):
|
class LFPlugin:
|
||||||
""" Plugin which implements the --lf (run last-failing) option """
|
""" Plugin which implements the --lf (run last-failing) option """
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -262,7 +255,7 @@ class LFPlugin(object):
|
||||||
config.cache.set("cache/lastfailed", self.lastfailed)
|
config.cache.set("cache/lastfailed", self.lastfailed)
|
||||||
|
|
||||||
|
|
||||||
class NFPlugin(object):
|
class NFPlugin:
|
||||||
""" Plugin which implements the --nf (run new-first) option """
|
""" Plugin which implements the --nf (run new-first) option """
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
@ -281,8 +274,8 @@ class NFPlugin(object):
|
||||||
other_items[item.nodeid] = item
|
other_items[item.nodeid] = item
|
||||||
|
|
||||||
items[:] = self._get_increasing_order(
|
items[:] = self._get_increasing_order(
|
||||||
six.itervalues(new_items)
|
new_items.values()
|
||||||
) + self._get_increasing_order(six.itervalues(other_items))
|
) + self._get_increasing_order(other_items.values())
|
||||||
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
|
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
|
||||||
|
|
||||||
def _get_increasing_order(self, items):
|
def _get_increasing_order(self, items):
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
per-test stdout/stderr capturing mechanism.
|
per-test stdout/stderr capturing mechanism.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import contextlib
|
import contextlib
|
||||||
import io
|
import io
|
||||||
|
@ -15,10 +10,7 @@ import sys
|
||||||
from io import UnsupportedOperation
|
from io import UnsupportedOperation
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import _PY3
|
|
||||||
from _pytest.compat import CaptureIO
|
from _pytest.compat import CaptureIO
|
||||||
|
|
||||||
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
|
||||||
|
@ -67,7 +59,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
||||||
sys.stderr.write(err)
|
sys.stderr.write(err)
|
||||||
|
|
||||||
|
|
||||||
class CaptureManager(object):
|
class CaptureManager:
|
||||||
"""
|
"""
|
||||||
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
|
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
|
||||||
test phase (setup, call, teardown). After each of those points, the captured output is obtained and
|
test phase (setup, call, teardown). After each of those points, the captured output is obtained and
|
||||||
|
@ -86,10 +78,8 @@ class CaptureManager(object):
|
||||||
self._current_item = None
|
self._current_item = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
|
return "<CaptureManager _method={!r} _global_capturing={!r} _current_item={!r}>".format(
|
||||||
self._method,
|
self._method, self._global_capturing, self._current_item
|
||||||
self._global_capturing,
|
|
||||||
self._current_item,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _getcapture(self, method):
|
def _getcapture(self, method):
|
||||||
|
@ -283,10 +273,6 @@ def capsysbinary(request):
|
||||||
``out`` and ``err`` will be ``bytes`` objects.
|
``out`` and ``err`` will be ``bytes`` objects.
|
||||||
"""
|
"""
|
||||||
_ensure_only_one_capture_fixture(request, "capsysbinary")
|
_ensure_only_one_capture_fixture(request, "capsysbinary")
|
||||||
# Currently, the implementation uses the python3 specific `.buffer`
|
|
||||||
# property of CaptureIO.
|
|
||||||
if sys.version_info < (3,):
|
|
||||||
raise request.raiseerror("capsysbinary is only supported on Python 3")
|
|
||||||
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
|
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
|
||||||
yield fixture
|
yield fixture
|
||||||
|
|
||||||
|
@ -345,7 +331,7 @@ def _install_capture_fixture_on_item(request, capture_class):
|
||||||
del request.node._capture_fixture
|
del request.node._capture_fixture
|
||||||
|
|
||||||
|
|
||||||
class CaptureFixture(object):
|
class CaptureFixture:
|
||||||
"""
|
"""
|
||||||
Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
|
Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
|
||||||
fixtures.
|
fixtures.
|
||||||
|
@ -424,7 +410,7 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
||||||
return EncodedFile(f, encoding or default_encoding)
|
return EncodedFile(f, encoding or default_encoding)
|
||||||
|
|
||||||
|
|
||||||
class EncodedFile(object):
|
class EncodedFile:
|
||||||
errors = "strict" # possibly needed by py3 code (issue555)
|
errors = "strict" # possibly needed by py3 code (issue555)
|
||||||
|
|
||||||
def __init__(self, buffer, encoding):
|
def __init__(self, buffer, encoding):
|
||||||
|
@ -432,9 +418,9 @@ class EncodedFile(object):
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
||||||
def write(self, obj):
|
def write(self, obj):
|
||||||
if isinstance(obj, six.text_type):
|
if isinstance(obj, str):
|
||||||
obj = obj.encode(self.encoding, "replace")
|
obj = obj.encode(self.encoding, "replace")
|
||||||
elif _PY3:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"write() argument must be str, not {}".format(type(obj).__name__)
|
"write() argument must be str, not {}".format(type(obj).__name__)
|
||||||
)
|
)
|
||||||
|
@ -460,7 +446,7 @@ class EncodedFile(object):
|
||||||
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
|
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
|
||||||
|
|
||||||
|
|
||||||
class MultiCapture(object):
|
class MultiCapture:
|
||||||
out = err = in_ = None
|
out = err = in_ = None
|
||||||
_state = None
|
_state = None
|
||||||
|
|
||||||
|
@ -473,7 +459,7 @@ class MultiCapture(object):
|
||||||
self.err = Capture(2)
|
self.err = Capture(2)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % (
|
return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
|
||||||
self.out,
|
self.out,
|
||||||
self.err,
|
self.err,
|
||||||
self.in_,
|
self.in_,
|
||||||
|
@ -539,12 +525,12 @@ class MultiCapture(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NoCapture(object):
|
class NoCapture:
|
||||||
EMPTY_BUFFER = None
|
EMPTY_BUFFER = None
|
||||||
__init__ = start = done = suspend = resume = lambda *args: None
|
__init__ = start = done = suspend = resume = lambda *args: None
|
||||||
|
|
||||||
|
|
||||||
class FDCaptureBinary(object):
|
class FDCaptureBinary:
|
||||||
"""Capture IO to/from a given os-level filedescriptor.
|
"""Capture IO to/from a given os-level filedescriptor.
|
||||||
|
|
||||||
snap() produces `bytes`
|
snap() produces `bytes`
|
||||||
|
@ -578,10 +564,8 @@ class FDCaptureBinary(object):
|
||||||
self.tmpfile_fd = tmpfile.fileno()
|
self.tmpfile_fd = tmpfile.fileno()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<FDCapture %s oldfd=%s _state=%r>" % (
|
return "<FDCapture {} oldfd={} _state={!r}>".format(
|
||||||
self.targetfd,
|
self.targetfd, getattr(self, "targetfd_save", None), self._state
|
||||||
getattr(self, "targetfd_save", None),
|
|
||||||
self._state,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -608,7 +592,7 @@ class FDCaptureBinary(object):
|
||||||
os.dup2(targetfd_save, self.targetfd)
|
os.dup2(targetfd_save, self.targetfd)
|
||||||
os.close(targetfd_save)
|
os.close(targetfd_save)
|
||||||
self.syscapture.done()
|
self.syscapture.done()
|
||||||
_attempt_to_close_capture_file(self.tmpfile)
|
self.tmpfile.close()
|
||||||
self._state = "done"
|
self._state = "done"
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self):
|
||||||
|
@ -623,7 +607,7 @@ class FDCaptureBinary(object):
|
||||||
|
|
||||||
def writeorg(self, data):
|
def writeorg(self, data):
|
||||||
""" write to original file descriptor. """
|
""" write to original file descriptor. """
|
||||||
if isinstance(data, six.text_type):
|
if isinstance(data, str):
|
||||||
data = data.encode("utf8") # XXX use encoding of original stream
|
data = data.encode("utf8") # XXX use encoding of original stream
|
||||||
os.write(self.targetfd_save, data)
|
os.write(self.targetfd_save, data)
|
||||||
|
|
||||||
|
@ -637,14 +621,14 @@ class FDCapture(FDCaptureBinary):
|
||||||
EMPTY_BUFFER = str()
|
EMPTY_BUFFER = str()
|
||||||
|
|
||||||
def snap(self):
|
def snap(self):
|
||||||
res = super(FDCapture, self).snap()
|
res = super().snap()
|
||||||
enc = getattr(self.tmpfile, "encoding", None)
|
enc = getattr(self.tmpfile, "encoding", None)
|
||||||
if enc and isinstance(res, bytes):
|
if enc and isinstance(res, bytes):
|
||||||
res = six.text_type(res, enc, "replace")
|
res = str(res, enc, "replace")
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class SysCapture(object):
|
class SysCapture:
|
||||||
|
|
||||||
EMPTY_BUFFER = str()
|
EMPTY_BUFFER = str()
|
||||||
_state = None
|
_state = None
|
||||||
|
@ -661,11 +645,8 @@ class SysCapture(object):
|
||||||
self.tmpfile = tmpfile
|
self.tmpfile = tmpfile
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % (
|
return "<SysCapture {} _old={!r}, tmpfile={!r} _state={!r}>".format(
|
||||||
self.name,
|
self.name, self._old, self.tmpfile, self._state
|
||||||
self._old,
|
|
||||||
self.tmpfile,
|
|
||||||
self._state,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -681,7 +662,7 @@ class SysCapture(object):
|
||||||
def done(self):
|
def done(self):
|
||||||
setattr(sys, self.name, self._old)
|
setattr(sys, self.name, self._old)
|
||||||
del self._old
|
del self._old
|
||||||
_attempt_to_close_capture_file(self.tmpfile)
|
self.tmpfile.close()
|
||||||
self._state = "done"
|
self._state = "done"
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self):
|
||||||
|
@ -707,7 +688,7 @@ class SysCaptureBinary(SysCapture):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class DontReadFromInput(six.Iterator):
|
class DontReadFromInput:
|
||||||
"""Temporary stub class. Ideally when stdin is accessed, the
|
"""Temporary stub class. Ideally when stdin is accessed, the
|
||||||
capturing should be turned off, with possibly all data captured
|
capturing should be turned off, with possibly all data captured
|
||||||
so far sent to the screen. This should be configurable, though,
|
so far sent to the screen. This should be configurable, though,
|
||||||
|
@ -738,10 +719,7 @@ class DontReadFromInput(six.Iterator):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def buffer(self):
|
def buffer(self):
|
||||||
if sys.version_info >= (3, 0):
|
return self
|
||||||
return self
|
|
||||||
else:
|
|
||||||
raise AttributeError("redirected stdin has no attribute buffer")
|
|
||||||
|
|
||||||
|
|
||||||
def _colorama_workaround():
|
def _colorama_workaround():
|
||||||
|
@ -837,14 +815,3 @@ def _py36_windowsconsoleio_workaround(stream):
|
||||||
sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||||
sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
||||||
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||||
|
|
||||||
|
|
||||||
def _attempt_to_close_capture_file(f):
|
|
||||||
"""Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
|
|
||||||
if six.PY2:
|
|
||||||
try:
|
|
||||||
f.close()
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
f.close()
|
|
||||||
|
|
|
@ -1,64 +1,28 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
python version compatibility code
|
python version compatibility code
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import codecs
|
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
import io
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from inspect import Parameter
|
||||||
|
from inspect import signature
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
from six import text_type
|
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
|
||||||
try:
|
|
||||||
import enum
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
# Only available in Python 3.4+ or as a backport
|
|
||||||
enum = None
|
|
||||||
|
|
||||||
_PY3 = sys.version_info > (3, 0)
|
|
||||||
_PY2 = not _PY3
|
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
|
||||||
from inspect import signature, Parameter as Parameter
|
|
||||||
else:
|
|
||||||
from funcsigs import signature, Parameter as Parameter
|
|
||||||
|
|
||||||
NOTSET = object()
|
NOTSET = object()
|
||||||
|
|
||||||
PY35 = sys.version_info[:2] >= (3, 5)
|
MODULE_NOT_FOUND_ERROR = (
|
||||||
PY36 = sys.version_info[:2] >= (3, 6)
|
"ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError"
|
||||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
)
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
|
||||||
from collections.abc import MutableMapping as MappingMixin
|
|
||||||
from collections.abc import Iterable, Mapping, Sequence, Sized
|
|
||||||
else:
|
|
||||||
# those raise DeprecationWarnings in Python >=3.7
|
|
||||||
from collections import MutableMapping as MappingMixin # noqa
|
|
||||||
from collections import Iterable, Mapping, Sequence, Sized # noqa
|
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 4):
|
|
||||||
from importlib.util import spec_from_file_location
|
|
||||||
else:
|
|
||||||
|
|
||||||
def spec_from_file_location(*_, **__):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _format_args(func):
|
def _format_args(func):
|
||||||
|
@ -184,10 +148,10 @@ def get_default_arg_names(function):
|
||||||
|
|
||||||
|
|
||||||
_non_printable_ascii_translate_table = {
|
_non_printable_ascii_translate_table = {
|
||||||
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
|
i: "\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
|
||||||
}
|
}
|
||||||
_non_printable_ascii_translate_table.update(
|
_non_printable_ascii_translate_table.update(
|
||||||
{ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"}
|
{ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,75 +159,39 @@ def _translate_non_printable(s):
|
||||||
return s.translate(_non_printable_ascii_translate_table)
|
return s.translate(_non_printable_ascii_translate_table)
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
STRING_TYPES = bytes, str
|
||||||
STRING_TYPES = bytes, str
|
|
||||||
UNICODE_TYPES = six.text_type
|
|
||||||
|
|
||||||
if PY35:
|
|
||||||
|
|
||||||
def _bytes_to_ascii(val):
|
def _bytes_to_ascii(val):
|
||||||
return val.decode("ascii", "backslashreplace")
|
return val.decode("ascii", "backslashreplace")
|
||||||
|
|
||||||
|
|
||||||
|
def ascii_escaped(val):
|
||||||
|
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
||||||
|
bytes objects into a sequence of escaped bytes:
|
||||||
|
|
||||||
|
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
|
||||||
|
|
||||||
|
and escapes unicode objects into a sequence of escaped unicode
|
||||||
|
ids, e.g.:
|
||||||
|
|
||||||
|
'4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
|
||||||
|
|
||||||
|
note:
|
||||||
|
the obvious "v.decode('unicode-escape')" will return
|
||||||
|
valid utf-8 unicode if it finds them in bytes, but we
|
||||||
|
want to return escaped bytes for any byte, even if they match
|
||||||
|
a utf-8 string.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(val, bytes):
|
||||||
|
ret = _bytes_to_ascii(val)
|
||||||
else:
|
else:
|
||||||
|
ret = val.encode("unicode_escape").decode("ascii")
|
||||||
def _bytes_to_ascii(val):
|
return _translate_non_printable(ret)
|
||||||
if val:
|
|
||||||
# source: http://goo.gl/bGsnwC
|
|
||||||
encoded_bytes, _ = codecs.escape_encode(val)
|
|
||||||
return encoded_bytes.decode("ascii")
|
|
||||||
else:
|
|
||||||
# empty bytes crashes codecs.escape_encode (#1087)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def ascii_escaped(val):
|
|
||||||
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
|
||||||
bytes objects into a sequence of escaped bytes:
|
|
||||||
|
|
||||||
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
|
|
||||||
|
|
||||||
and escapes unicode objects into a sequence of escaped unicode
|
|
||||||
ids, e.g.:
|
|
||||||
|
|
||||||
'4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
|
|
||||||
|
|
||||||
note:
|
|
||||||
the obvious "v.decode('unicode-escape')" will return
|
|
||||||
valid utf-8 unicode if it finds them in bytes, but we
|
|
||||||
want to return escaped bytes for any byte, even if they match
|
|
||||||
a utf-8 string.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(val, bytes):
|
|
||||||
ret = _bytes_to_ascii(val)
|
|
||||||
else:
|
|
||||||
ret = val.encode("unicode_escape").decode("ascii")
|
|
||||||
return _translate_non_printable(ret)
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
class _PytestWrapper:
|
||||||
STRING_TYPES = six.string_types
|
|
||||||
UNICODE_TYPES = six.text_type
|
|
||||||
|
|
||||||
def ascii_escaped(val):
|
|
||||||
"""In py2 bytes and str are the same type, so return if it's a bytes
|
|
||||||
object, return it unchanged if it is a full ascii string,
|
|
||||||
otherwise escape it into its binary form.
|
|
||||||
|
|
||||||
If it's a unicode string, change the unicode characters into
|
|
||||||
unicode escapes.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if isinstance(val, bytes):
|
|
||||||
try:
|
|
||||||
ret = val.decode("ascii")
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
ret = val.encode("string-escape").decode("ascii")
|
|
||||||
else:
|
|
||||||
ret = val.encode("unicode-escape").decode("ascii")
|
|
||||||
return _translate_non_printable(ret)
|
|
||||||
|
|
||||||
|
|
||||||
class _PytestWrapper(object):
|
|
||||||
"""Dummy wrapper around a function object for internal use only.
|
"""Dummy wrapper around a function object for internal use only.
|
||||||
|
|
||||||
Used to correctly unwrap the underlying function object
|
Used to correctly unwrap the underlying function object
|
||||||
|
@ -357,36 +285,6 @@ def safe_isclass(obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _is_unittest_unexpected_success_a_failure():
|
|
||||||
"""Return if the test suite should fail if an @expectedFailure unittest test PASSES.
|
|
||||||
|
|
||||||
From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
|
|
||||||
Changed in version 3.4: Returns False if there were any
|
|
||||||
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
|
|
||||||
"""
|
|
||||||
return sys.version_info >= (3, 4)
|
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
|
||||||
|
|
||||||
def safe_str(v):
|
|
||||||
"""returns v as string"""
|
|
||||||
return str(v)
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def safe_str(v):
|
|
||||||
"""returns v as string, converting to ascii if necessary"""
|
|
||||||
try:
|
|
||||||
return str(v)
|
|
||||||
except UnicodeError:
|
|
||||||
if not isinstance(v, text_type):
|
|
||||||
v = text_type(v)
|
|
||||||
errors = "replace"
|
|
||||||
return v.encode("utf-8", errors)
|
|
||||||
|
|
||||||
|
|
||||||
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||||
"Collector",
|
"Collector",
|
||||||
"Module",
|
"Module",
|
||||||
|
@ -410,30 +308,15 @@ def _setup_collect_fakemodule():
|
||||||
setattr(pytest.collect, attr, getattr(pytest, attr))
|
setattr(pytest.collect, attr, getattr(pytest, attr))
|
||||||
|
|
||||||
|
|
||||||
if _PY2:
|
class CaptureIO(io.TextIOWrapper):
|
||||||
# Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
|
def __init__(self):
|
||||||
from py.io import TextIO
|
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
|
||||||
|
|
||||||
class CaptureIO(TextIO):
|
def getvalue(self):
|
||||||
@property
|
return self.buffer.getvalue().decode("UTF-8")
|
||||||
def encoding(self):
|
|
||||||
return getattr(self, "_encoding", "UTF-8")
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
class FuncargnamesCompatAttr:
|
||||||
import io
|
|
||||||
|
|
||||||
class CaptureIO(io.TextIOWrapper):
|
|
||||||
def __init__(self):
|
|
||||||
super(CaptureIO, self).__init__(
|
|
||||||
io.BytesIO(), encoding="UTF-8", newline="", write_through=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def getvalue(self):
|
|
||||||
return self.buffer.getvalue().decode("UTF-8")
|
|
||||||
|
|
||||||
|
|
||||||
class FuncargnamesCompatAttr(object):
|
|
||||||
""" helper class so that Metafunc, Function and FixtureRequest
|
""" helper class so that Metafunc, Function and FixtureRequest
|
||||||
don't need to each define the "funcargnames" compatibility attribute.
|
don't need to each define the "funcargnames" compatibility attribute.
|
||||||
"""
|
"""
|
||||||
|
@ -442,16 +325,3 @@ class FuncargnamesCompatAttr(object):
|
||||||
def funcargnames(self):
|
def funcargnames(self):
|
||||||
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
|
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
|
||||||
return self.fixturenames
|
return self.fixturenames
|
||||||
|
|
||||||
|
|
||||||
if six.PY2:
|
|
||||||
|
|
||||||
def lru_cache(*_, **__):
|
|
||||||
def dec(fn):
|
|
||||||
return fn
|
|
||||||
|
|
||||||
return dec
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
from functools import lru_cache # noqa: F401
|
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" command line options, ini-file and conftest.py processing. """
|
""" command line options, ini-file and conftest.py processing. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import copy
|
import copy
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -12,10 +7,10 @@ import shlex
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
import importlib_metadata
|
import importlib_metadata
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
from pluggy import HookimplMarker
|
from pluggy import HookimplMarker
|
||||||
from pluggy import HookspecMarker
|
from pluggy import HookspecMarker
|
||||||
|
@ -31,8 +26,6 @@ from .findpaths import exists
|
||||||
from _pytest import deprecated
|
from _pytest import deprecated
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code import filter_traceback
|
from _pytest._code import filter_traceback
|
||||||
from _pytest.compat import lru_cache
|
|
||||||
from _pytest.compat import safe_str
|
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
from _pytest.warning_types import PytestConfigWarning
|
from _pytest.warning_types import PytestConfigWarning
|
||||||
|
@ -73,7 +66,7 @@ def main(args=None, plugins=None):
|
||||||
if exc_info.traceback
|
if exc_info.traceback
|
||||||
else exc_info.exconly()
|
else exc_info.exconly()
|
||||||
)
|
)
|
||||||
formatted_tb = safe_str(exc_repr)
|
formatted_tb = str(exc_repr)
|
||||||
for line in formatted_tb.splitlines():
|
for line in formatted_tb.splitlines():
|
||||||
tw.line(line.rstrip(), red=True)
|
tw.line(line.rstrip(), red=True)
|
||||||
return 4
|
return 4
|
||||||
|
@ -89,7 +82,7 @@ def main(args=None, plugins=None):
|
||||||
return EXIT_USAGEERROR
|
return EXIT_USAGEERROR
|
||||||
|
|
||||||
|
|
||||||
class cmdline(object): # compatibility namespace
|
class cmdline: # compatibility namespace
|
||||||
main = staticmethod(main)
|
main = staticmethod(main)
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,7 +188,7 @@ def _prepareconfig(args=None, plugins=None):
|
||||||
try:
|
try:
|
||||||
if plugins:
|
if plugins:
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if isinstance(plugin, six.string_types):
|
if isinstance(plugin, str):
|
||||||
pluginmanager.consider_pluginarg(plugin)
|
pluginmanager.consider_pluginarg(plugin)
|
||||||
else:
|
else:
|
||||||
pluginmanager.register(plugin)
|
pluginmanager.register(plugin)
|
||||||
|
@ -222,7 +215,7 @@ class PytestPluginManager(PluginManager):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PytestPluginManager, self).__init__("pytest")
|
super().__init__("pytest")
|
||||||
self._conftest_plugins = set()
|
self._conftest_plugins = set()
|
||||||
|
|
||||||
# state related to local conftest plugins
|
# state related to local conftest plugins
|
||||||
|
@ -270,7 +263,7 @@ class PytestPluginManager(PluginManager):
|
||||||
return
|
return
|
||||||
|
|
||||||
method = getattr(plugin, name)
|
method = getattr(plugin, name)
|
||||||
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
|
opts = super().parse_hookimpl_opts(plugin, name)
|
||||||
|
|
||||||
# consider only actual functions for hooks (#3775)
|
# consider only actual functions for hooks (#3775)
|
||||||
if not inspect.isroutine(method):
|
if not inspect.isroutine(method):
|
||||||
|
@ -289,9 +282,7 @@ class PytestPluginManager(PluginManager):
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def parse_hookspec_opts(self, module_or_class, name):
|
def parse_hookspec_opts(self, module_or_class, name):
|
||||||
opts = super(PytestPluginManager, self).parse_hookspec_opts(
|
opts = super().parse_hookspec_opts(module_or_class, name)
|
||||||
module_or_class, name
|
|
||||||
)
|
|
||||||
if opts is None:
|
if opts is None:
|
||||||
method = getattr(module_or_class, name)
|
method = getattr(module_or_class, name)
|
||||||
|
|
||||||
|
@ -318,7 +309,7 @@ class PytestPluginManager(PluginManager):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
ret = super(PytestPluginManager, self).register(plugin, name)
|
ret = super().register(plugin, name)
|
||||||
if ret:
|
if ret:
|
||||||
self.hook.pytest_plugin_registered.call_historic(
|
self.hook.pytest_plugin_registered.call_historic(
|
||||||
kwargs=dict(plugin=plugin, manager=self)
|
kwargs=dict(plugin=plugin, manager=self)
|
||||||
|
@ -403,12 +394,6 @@ class PytestPluginManager(PluginManager):
|
||||||
else:
|
else:
|
||||||
directory = path
|
directory = path
|
||||||
|
|
||||||
if six.PY2: # py2 is not using lru_cache.
|
|
||||||
try:
|
|
||||||
return self._dirpath2confmods[directory]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# XXX these days we may rather want to use config.rootdir
|
# XXX these days we may rather want to use config.rootdir
|
||||||
# and allow users to opt into looking into the rootdir parent
|
# and allow users to opt into looking into the rootdir parent
|
||||||
# directories instead of requiring to specify confcutdir
|
# directories instead of requiring to specify confcutdir
|
||||||
|
@ -485,7 +470,7 @@ class PytestPluginManager(PluginManager):
|
||||||
while i < n:
|
while i < n:
|
||||||
opt = args[i]
|
opt = args[i]
|
||||||
i += 1
|
i += 1
|
||||||
if isinstance(opt, six.string_types):
|
if isinstance(opt, str):
|
||||||
if opt == "-p":
|
if opt == "-p":
|
||||||
try:
|
try:
|
||||||
parg = args[i]
|
parg = args[i]
|
||||||
|
@ -546,7 +531,7 @@ class PytestPluginManager(PluginManager):
|
||||||
# "terminal" or "capture". Those plugins are registered under their
|
# "terminal" or "capture". Those plugins are registered under their
|
||||||
# basename for historic purposes but must be imported with the
|
# basename for historic purposes but must be imported with the
|
||||||
# _pytest prefix.
|
# _pytest prefix.
|
||||||
assert isinstance(modname, six.string_types), (
|
assert isinstance(modname, str), (
|
||||||
"module name as text required, got %r" % modname
|
"module name as text required, got %r" % modname
|
||||||
)
|
)
|
||||||
modname = str(modname)
|
modname = str(modname)
|
||||||
|
@ -564,20 +549,19 @@ class PytestPluginManager(PluginManager):
|
||||||
try:
|
try:
|
||||||
__import__(importspec)
|
__import__(importspec)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
new_exc_message = 'Error importing plugin "%s": %s' % (
|
new_exc_message = 'Error importing plugin "{}": {}'.format(
|
||||||
modname,
|
modname, str(e.args[0])
|
||||||
safe_str(e.args[0]),
|
|
||||||
)
|
)
|
||||||
new_exc = ImportError(new_exc_message)
|
new_exc = ImportError(new_exc_message)
|
||||||
tb = sys.exc_info()[2]
|
tb = sys.exc_info()[2]
|
||||||
|
|
||||||
six.reraise(ImportError, new_exc, tb)
|
raise new_exc.with_traceback(tb)
|
||||||
|
|
||||||
except Skipped as e:
|
except Skipped as e:
|
||||||
from _pytest.warnings import _issue_warning_captured
|
from _pytest.warnings import _issue_warning_captured
|
||||||
|
|
||||||
_issue_warning_captured(
|
_issue_warning_captured(
|
||||||
PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
|
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
|
||||||
self.hook,
|
self.hook,
|
||||||
stacklevel=1,
|
stacklevel=1,
|
||||||
)
|
)
|
||||||
|
@ -595,7 +579,7 @@ def _get_plugin_specs_as_list(specs):
|
||||||
empty list is returned.
|
empty list is returned.
|
||||||
"""
|
"""
|
||||||
if specs is not None and not isinstance(specs, types.ModuleType):
|
if specs is not None and not isinstance(specs, types.ModuleType):
|
||||||
if isinstance(specs, six.string_types):
|
if isinstance(specs, str):
|
||||||
specs = specs.split(",") if specs else []
|
specs = specs.split(",") if specs else []
|
||||||
if not isinstance(specs, (list, tuple)):
|
if not isinstance(specs, (list, tuple)):
|
||||||
raise UsageError(
|
raise UsageError(
|
||||||
|
@ -613,7 +597,7 @@ def _ensure_removed_sysmodule(modname):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Notset(object):
|
class Notset:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<NOTSET>"
|
return "<NOTSET>"
|
||||||
|
|
||||||
|
@ -633,7 +617,7 @@ def _iter_rewritable_modules(package_files):
|
||||||
yield package_name
|
yield package_name
|
||||||
|
|
||||||
|
|
||||||
class Config(object):
|
class Config:
|
||||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||||
|
|
||||||
def __init__(self, pluginmanager):
|
def __init__(self, pluginmanager):
|
||||||
|
@ -644,7 +628,7 @@ class Config(object):
|
||||||
|
|
||||||
_a = FILE_OR_DIR
|
_a = FILE_OR_DIR
|
||||||
self._parser = Parser(
|
self._parser = Parser(
|
||||||
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
|
usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a),
|
||||||
processopt=self._processopt,
|
processopt=self._processopt,
|
||||||
)
|
)
|
||||||
#: a pluginmanager instance
|
#: a pluginmanager instance
|
||||||
|
@ -932,7 +916,7 @@ class Config(object):
|
||||||
try:
|
try:
|
||||||
description, type, default = self._parser._inidict[name]
|
description, type, default = self._parser._inidict[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("unknown configuration value: %r" % (name,))
|
raise ValueError("unknown configuration value: {!r}".format(name))
|
||||||
value = self._get_override_ini_value(name)
|
value = self._get_override_ini_value(name)
|
||||||
if value is None:
|
if value is None:
|
||||||
try:
|
try:
|
||||||
|
@ -1009,8 +993,8 @@ class Config(object):
|
||||||
if skip:
|
if skip:
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
pytest.skip("no %r option found" % (name,))
|
pytest.skip("no {!r} option found".format(name))
|
||||||
raise ValueError("no option named %r" % (name,))
|
raise ValueError("no option named {!r}".format(name))
|
||||||
|
|
||||||
def getvalue(self, name, path=None):
|
def getvalue(self, name, path=None):
|
||||||
""" (deprecated, use getoption()) """
|
""" (deprecated, use getoption()) """
|
||||||
|
@ -1098,4 +1082,4 @@ def _strtobool(val):
|
||||||
elif val in ("n", "no", "f", "false", "off", "0"):
|
elif val in ("n", "no", "f", "false", "off", "0"):
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
raise ValueError("invalid truth value %r" % (val,))
|
raise ValueError("invalid truth value {!r}".format(val))
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import argparse
|
import argparse
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
|
|
||||||
FILE_OR_DIR = "file_or_dir"
|
FILE_OR_DIR = "file_or_dir"
|
||||||
|
|
||||||
|
|
||||||
class Parser(object):
|
class Parser:
|
||||||
""" Parser for command line arguments and ini-file values.
|
""" Parser for command line arguments and ini-file values.
|
||||||
|
|
||||||
:ivar extra_info: dict of generic param -> value to display in case
|
:ivar extra_info: dict of generic param -> value to display in case
|
||||||
|
@ -145,12 +143,12 @@ class ArgumentError(Exception):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.option_id:
|
if self.option_id:
|
||||||
return "option %s: %s" % (self.option_id, self.msg)
|
return "option {}: {}".format(self.option_id, self.msg)
|
||||||
else:
|
else:
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
|
|
||||||
class Argument(object):
|
class Argument:
|
||||||
"""class that mimics the necessary behaviour of optparse.Option
|
"""class that mimics the necessary behaviour of optparse.Option
|
||||||
|
|
||||||
it's currently a least effort implementation
|
it's currently a least effort implementation
|
||||||
|
@ -179,7 +177,7 @@ class Argument(object):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# this might raise a keyerror as well, don't want to catch that
|
# this might raise a keyerror as well, don't want to catch that
|
||||||
if isinstance(typ, six.string_types):
|
if isinstance(typ, str):
|
||||||
if typ == "choice":
|
if typ == "choice":
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"`type` argument to addoption() is the string %r."
|
"`type` argument to addoption() is the string %r."
|
||||||
|
@ -282,7 +280,7 @@ class Argument(object):
|
||||||
return "Argument({})".format(", ".join(args))
|
return "Argument({})".format(", ".join(args))
|
||||||
|
|
||||||
|
|
||||||
class OptionGroup(object):
|
class OptionGroup:
|
||||||
def __init__(self, name, description="", parser=None):
|
def __init__(self, name, description="", parser=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
|
@ -337,10 +335,10 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
"""Transform argparse error message into UsageError."""
|
"""Transform argparse error message into UsageError."""
|
||||||
msg = "%s: error: %s" % (self.prog, message)
|
msg = "{}: error: {}".format(self.prog, message)
|
||||||
|
|
||||||
if hasattr(self._parser, "_config_source_hint"):
|
if hasattr(self._parser, "_config_source_hint"):
|
||||||
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
|
msg = "{} ({})".format(msg, self._parser._config_source_hint)
|
||||||
|
|
||||||
raise UsageError(self.format_usage() + msg)
|
raise UsageError(self.format_usage() + msg)
|
||||||
|
|
||||||
|
@ -352,7 +350,7 @@ class MyOptionParser(argparse.ArgumentParser):
|
||||||
if arg and arg[0] == "-":
|
if arg and arg[0] == "-":
|
||||||
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
|
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
|
||||||
for k, v in sorted(self.extra_info.items()):
|
for k, v in sorted(self.extra_info.items()):
|
||||||
lines.append(" %s: %s" % (k, v))
|
lines.append(" {}: {}".format(k, v))
|
||||||
self.error("\n".join(lines))
|
self.error("\n".join(lines))
|
||||||
getattr(args, FILE_OR_DIR).extend(argv)
|
getattr(args, FILE_OR_DIR).extend(argv)
|
||||||
return args
|
return args
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
class UsageError(Exception):
|
class UsageError(Exception):
|
||||||
""" error in pytest usage or invocation"""
|
""" error in pytest usage or invocation"""
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" interactive debugging with PDB, the Python Debugger. """
|
""" interactive debugging with PDB, the Python Debugger. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import pdb
|
import pdb
|
||||||
import sys
|
import sys
|
||||||
|
@ -74,7 +69,7 @@ def pytest_configure(config):
|
||||||
config._cleanup.append(fin)
|
config._cleanup.append(fin)
|
||||||
|
|
||||||
|
|
||||||
class pytestPDB(object):
|
class pytestPDB:
|
||||||
""" Pseudo PDB that defers to the real pdb. """
|
""" Pseudo PDB that defers to the real pdb. """
|
||||||
|
|
||||||
_pluginmanager = None
|
_pluginmanager = None
|
||||||
|
@ -128,18 +123,18 @@ class pytestPDB(object):
|
||||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
|
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
|
||||||
import _pytest.config
|
import _pytest.config
|
||||||
|
|
||||||
class PytestPdbWrapper(pdb_cls, object):
|
class PytestPdbWrapper(pdb_cls):
|
||||||
_pytest_capman = capman
|
_pytest_capman = capman
|
||||||
_continued = False
|
_continued = False
|
||||||
|
|
||||||
def do_debug(self, arg):
|
def do_debug(self, arg):
|
||||||
cls._recursive_debug += 1
|
cls._recursive_debug += 1
|
||||||
ret = super(PytestPdbWrapper, self).do_debug(arg)
|
ret = super().do_debug(arg)
|
||||||
cls._recursive_debug -= 1
|
cls._recursive_debug -= 1
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def do_continue(self, arg):
|
def do_continue(self, arg):
|
||||||
ret = super(PytestPdbWrapper, self).do_continue(arg)
|
ret = super().do_continue(arg)
|
||||||
if cls._recursive_debug == 0:
|
if cls._recursive_debug == 0:
|
||||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||||
tw.line()
|
tw.line()
|
||||||
|
@ -171,7 +166,7 @@ class pytestPDB(object):
|
||||||
could be handled, but this would require to wrap the
|
could be handled, but this would require to wrap the
|
||||||
whole pytest run, and adjust the report etc.
|
whole pytest run, and adjust the report etc.
|
||||||
"""
|
"""
|
||||||
ret = super(PytestPdbWrapper, self).do_quit(arg)
|
ret = super().do_quit(arg)
|
||||||
|
|
||||||
if cls._recursive_debug == 0:
|
if cls._recursive_debug == 0:
|
||||||
outcomes.exit("Quitting debugger")
|
outcomes.exit("Quitting debugger")
|
||||||
|
@ -187,7 +182,7 @@ class pytestPDB(object):
|
||||||
Needed after do_continue resumed, and entering another
|
Needed after do_continue resumed, and entering another
|
||||||
breakpoint again.
|
breakpoint again.
|
||||||
"""
|
"""
|
||||||
ret = super(PytestPdbWrapper, self).setup(f, tb)
|
ret = super().setup(f, tb)
|
||||||
if not ret and self._continued:
|
if not ret and self._continued:
|
||||||
# pdb.setup() returns True if the command wants to exit
|
# pdb.setup() returns True if the command wants to exit
|
||||||
# from the interaction: do not suspend capturing then.
|
# from the interaction: do not suspend capturing then.
|
||||||
|
@ -196,7 +191,7 @@ class pytestPDB(object):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_stack(self, f, t):
|
def get_stack(self, f, t):
|
||||||
stack, i = super(PytestPdbWrapper, self).get_stack(f, t)
|
stack, i = super().get_stack(f, t)
|
||||||
if f is None:
|
if f is None:
|
||||||
# Find last non-hidden frame.
|
# Find last non-hidden frame.
|
||||||
i = max(0, len(stack) - 1)
|
i = max(0, len(stack) - 1)
|
||||||
|
@ -230,7 +225,7 @@ class pytestPDB(object):
|
||||||
else:
|
else:
|
||||||
capturing = cls._is_capturing(capman)
|
capturing = cls._is_capturing(capman)
|
||||||
if capturing == "global":
|
if capturing == "global":
|
||||||
tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,))
|
tw.sep(">", "PDB {} (IO-capturing turned off)".format(method))
|
||||||
elif capturing:
|
elif capturing:
|
||||||
tw.sep(
|
tw.sep(
|
||||||
">",
|
">",
|
||||||
|
@ -238,7 +233,7 @@ class pytestPDB(object):
|
||||||
% (method, capturing),
|
% (method, capturing),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tw.sep(">", "PDB %s" % (method,))
|
tw.sep(">", "PDB {}".format(method))
|
||||||
|
|
||||||
_pdb = cls._import_pdb_cls(capman)(**kwargs)
|
_pdb = cls._import_pdb_cls(capman)(**kwargs)
|
||||||
|
|
||||||
|
@ -254,7 +249,7 @@ class pytestPDB(object):
|
||||||
_pdb.set_trace(frame)
|
_pdb.set_trace(frame)
|
||||||
|
|
||||||
|
|
||||||
class PdbInvoke(object):
|
class PdbInvoke:
|
||||||
def pytest_exception_interact(self, node, call, report):
|
def pytest_exception_interact(self, node, call, report):
|
||||||
capman = node.config.pluginmanager.getplugin("capturemanager")
|
capman = node.config.pluginmanager.getplugin("capturemanager")
|
||||||
if capman:
|
if capman:
|
||||||
|
@ -269,7 +264,7 @@ class PdbInvoke(object):
|
||||||
post_mortem(tb)
|
post_mortem(tb)
|
||||||
|
|
||||||
|
|
||||||
class PdbTrace(object):
|
class PdbTrace:
|
||||||
@hookimpl(hookwrapper=True)
|
@hookimpl(hookwrapper=True)
|
||||||
def pytest_pyfunc_call(self, pyfuncitem):
|
def pytest_pyfunc_call(self, pyfuncitem):
|
||||||
_test_pytest_function(pyfuncitem)
|
_test_pytest_function(pyfuncitem)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
This module contains deprecation messages and bits of code used elsewhere in the codebase
|
This module contains deprecation messages and bits of code used elsewhere in the codebase
|
||||||
that is planned to be removed in the next pytest release.
|
that is planned to be removed in the next pytest release.
|
||||||
|
@ -9,10 +8,6 @@ be removed when the time comes.
|
||||||
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||||
in case of warnings which need to format their messages.
|
in case of warnings which need to format their messages.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from _pytest.warning_types import PytestDeprecationWarning
|
from _pytest.warning_types import PytestDeprecationWarning
|
||||||
from _pytest.warning_types import RemovedInPytest4Warning
|
from _pytest.warning_types import RemovedInPytest4Warning
|
||||||
from _pytest.warning_types import UnformattedWarning
|
from _pytest.warning_types import UnformattedWarning
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" discover and run doctests in modules and test files."""
|
""" discover and run doctests in modules and test files."""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
@ -126,7 +121,7 @@ class ReprFailDoctest(TerminalRepr):
|
||||||
|
|
||||||
class MultipleDoctestFailures(Exception):
|
class MultipleDoctestFailures(Exception):
|
||||||
def __init__(self, failures):
|
def __init__(self, failures):
|
||||||
super(MultipleDoctestFailures, self).__init__()
|
super().__init__()
|
||||||
self.failures = failures
|
self.failures = failures
|
||||||
|
|
||||||
|
|
||||||
|
@ -181,7 +176,7 @@ def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=T
|
||||||
|
|
||||||
class DoctestItem(pytest.Item):
|
class DoctestItem(pytest.Item):
|
||||||
def __init__(self, name, parent, runner=None, dtest=None):
|
def __init__(self, name, parent, runner=None, dtest=None):
|
||||||
super(DoctestItem, self).__init__(name, parent)
|
super().__init__(name, parent)
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
self.dtest = dtest
|
self.dtest = dtest
|
||||||
self.obj = None
|
self.obj = None
|
||||||
|
@ -258,7 +253,7 @@ class DoctestItem(pytest.Item):
|
||||||
]
|
]
|
||||||
indent = ">>>"
|
indent = ">>>"
|
||||||
for line in example.source.splitlines():
|
for line in example.source.splitlines():
|
||||||
lines.append("??? %s %s" % (indent, line))
|
lines.append("??? {} {}".format(indent, line))
|
||||||
indent = "..."
|
indent = "..."
|
||||||
if isinstance(failure, doctest.DocTestFailure):
|
if isinstance(failure, doctest.DocTestFailure):
|
||||||
lines += checker.output_difference(
|
lines += checker.output_difference(
|
||||||
|
@ -271,7 +266,7 @@ class DoctestItem(pytest.Item):
|
||||||
reprlocation_lines.append((reprlocation, lines))
|
reprlocation_lines.append((reprlocation, lines))
|
||||||
return ReprFailDoctest(reprlocation_lines)
|
return ReprFailDoctest(reprlocation_lines)
|
||||||
else:
|
else:
|
||||||
return super(DoctestItem, self).repr_failure(excinfo)
|
return super().repr_failure(excinfo)
|
||||||
|
|
||||||
def reportinfo(self):
|
def reportinfo(self):
|
||||||
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
|
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
|
||||||
|
@ -333,7 +328,6 @@ class DoctestTextfile(pytest.Module):
|
||||||
checker=_get_checker(),
|
checker=_get_checker(),
|
||||||
continue_on_failure=_get_continue_on_failure(self.config),
|
continue_on_failure=_get_continue_on_failure(self.config),
|
||||||
)
|
)
|
||||||
_fix_spoof_python2(runner, encoding)
|
|
||||||
|
|
||||||
parser = doctest.DocTestParser()
|
parser = doctest.DocTestParser()
|
||||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||||
|
@ -539,32 +533,6 @@ def _get_report_choice(key):
|
||||||
}[key]
|
}[key]
|
||||||
|
|
||||||
|
|
||||||
def _fix_spoof_python2(runner, encoding):
|
|
||||||
"""
|
|
||||||
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
|
|
||||||
should patch only doctests for text files because they don't have a way to declare their
|
|
||||||
encoding. Doctests in docstrings from Python modules don't have the same problem given that
|
|
||||||
Python already decoded the strings.
|
|
||||||
|
|
||||||
This fixes the problem related in issue #2434.
|
|
||||||
"""
|
|
||||||
from _pytest.compat import _PY2
|
|
||||||
|
|
||||||
if not _PY2:
|
|
||||||
return
|
|
||||||
|
|
||||||
from doctest import _SpoofOut
|
|
||||||
|
|
||||||
class UnicodeSpoof(_SpoofOut):
|
|
||||||
def getvalue(self):
|
|
||||||
result = _SpoofOut.getvalue(self)
|
|
||||||
if encoding and isinstance(result, bytes):
|
|
||||||
result = result.decode(encoding)
|
|
||||||
return result
|
|
||||||
|
|
||||||
runner._fakeout = UnicodeSpoof()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def doctest_namespace():
|
def doctest_namespace():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -14,7 +9,6 @@ from collections import OrderedDict
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
@ -41,7 +35,7 @@ from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True)
|
@attr.s(frozen=True)
|
||||||
class PseudoFixtureDef(object):
|
class PseudoFixtureDef:
|
||||||
cached_result = attr.ib()
|
cached_result = attr.ib()
|
||||||
scope = attr.ib()
|
scope = attr.ib()
|
||||||
|
|
||||||
|
@ -81,7 +75,7 @@ def scopeproperty(name=None, doc=None):
|
||||||
if func.__name__ in scope2props[self.scope]:
|
if func.__name__ in scope2props[self.scope]:
|
||||||
return func(self)
|
return func(self)
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"%s not available in %s-scoped context" % (scopename, self.scope)
|
"{} not available in {}-scoped context".format(scopename, self.scope)
|
||||||
)
|
)
|
||||||
|
|
||||||
return property(provide, None, None, func.__doc__)
|
return property(provide, None, None, func.__doc__)
|
||||||
|
@ -94,7 +88,7 @@ def get_scope_package(node, fixturedef):
|
||||||
|
|
||||||
cls = pytest.Package
|
cls = pytest.Package
|
||||||
current = node
|
current = node
|
||||||
fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py")
|
fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
|
||||||
while current and (
|
while current and (
|
||||||
type(current) is not cls or fixture_package_name != current.nodeid
|
type(current) is not cls or fixture_package_name != current.nodeid
|
||||||
):
|
):
|
||||||
|
@ -301,7 +295,7 @@ def get_direct_param_fixture_func(request):
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True)
|
@attr.s(slots=True)
|
||||||
class FuncFixtureInfo(object):
|
class FuncFixtureInfo:
|
||||||
# original function argument names
|
# original function argument names
|
||||||
argnames = attr.ib(type=tuple)
|
argnames = attr.ib(type=tuple)
|
||||||
# argnames that function immediately requires. These include argnames +
|
# argnames that function immediately requires. These include argnames +
|
||||||
|
@ -657,7 +651,7 @@ class SubRequest(FixtureRequest):
|
||||||
self._fixturemanager = request._fixturemanager
|
self._fixturemanager = request._fixturemanager
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
|
return "<SubRequest {!r} for {!r}>".format(self.fixturename, self._pyfuncitem)
|
||||||
|
|
||||||
def addfinalizer(self, finalizer):
|
def addfinalizer(self, finalizer):
|
||||||
self._fixturedef.addfinalizer(finalizer)
|
self._fixturedef.addfinalizer(finalizer)
|
||||||
|
@ -670,7 +664,7 @@ class SubRequest(FixtureRequest):
|
||||||
fixturedef.addfinalizer(
|
fixturedef.addfinalizer(
|
||||||
functools.partial(self._fixturedef.finish, request=self)
|
functools.partial(self._fixturedef.finish, request=self)
|
||||||
)
|
)
|
||||||
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest)
|
super()._schedule_finalizers(fixturedef, subrequest)
|
||||||
|
|
||||||
|
|
||||||
scopes = "session package module class function".split()
|
scopes = "session package module class function".split()
|
||||||
|
@ -723,7 +717,7 @@ class FixtureLookupError(LookupError):
|
||||||
error_msg = "file %s, line %s: source code not available"
|
error_msg = "file %s, line %s: source code not available"
|
||||||
addline(error_msg % (fspath, lineno + 1))
|
addline(error_msg % (fspath, lineno + 1))
|
||||||
else:
|
else:
|
||||||
addline("file %s, line %s" % (fspath, lineno + 1))
|
addline("file {}, line {}".format(fspath, lineno + 1))
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
addline(" " + line)
|
addline(" " + line)
|
||||||
|
@ -779,7 +773,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
||||||
|
|
||||||
def fail_fixturefunc(fixturefunc, msg):
|
def fail_fixturefunc(fixturefunc, msg):
|
||||||
fs, lineno = getfslineno(fixturefunc)
|
fs, lineno = getfslineno(fixturefunc)
|
||||||
location = "%s:%s" % (fs, lineno + 1)
|
location = "{}:{}".format(fs, lineno + 1)
|
||||||
source = _pytest._code.Source(fixturefunc)
|
source = _pytest._code.Source(fixturefunc)
|
||||||
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
|
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
|
||||||
|
|
||||||
|
@ -809,7 +803,7 @@ def _teardown_yield_fixture(fixturefunc, it):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FixtureDef(object):
|
class FixtureDef:
|
||||||
""" A container for a factory definition. """
|
""" A container for a factory definition. """
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -853,10 +847,10 @@ class FixtureDef(object):
|
||||||
except: # noqa
|
except: # noqa
|
||||||
exceptions.append(sys.exc_info())
|
exceptions.append(sys.exc_info())
|
||||||
if exceptions:
|
if exceptions:
|
||||||
e = exceptions[0]
|
_, val, tb = exceptions[0]
|
||||||
# Ensure to not keep frame references through traceback.
|
# Ensure to not keep frame references through traceback.
|
||||||
del exceptions
|
del exceptions
|
||||||
six.reraise(*e)
|
raise val.with_traceback(tb)
|
||||||
finally:
|
finally:
|
||||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||||
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
||||||
|
@ -882,7 +876,8 @@ class FixtureDef(object):
|
||||||
result, cache_key, err = cached_result
|
result, cache_key, err = cached_result
|
||||||
if my_cache_key == cache_key:
|
if my_cache_key == cache_key:
|
||||||
if err is not None:
|
if err is not None:
|
||||||
six.reraise(*err)
|
_, val, tb = err
|
||||||
|
raise val.with_traceback(tb)
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
# we have a previous but differently parametrized fixture instance
|
# we have a previous but differently parametrized fixture instance
|
||||||
|
@ -894,10 +889,8 @@ class FixtureDef(object):
|
||||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<FixtureDef argname=%r scope=%r baseid=%r>" % (
|
return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
|
||||||
self.argname,
|
self.argname, self.scope, self.baseid
|
||||||
self.scope,
|
|
||||||
self.baseid,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -957,7 +950,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
||||||
name=fixture_marker.name or function.__name__
|
name=fixture_marker.name or function.__name__
|
||||||
)
|
)
|
||||||
|
|
||||||
@six.wraps(function)
|
@functools.wraps(function)
|
||||||
def result(*args, **kwargs):
|
def result(*args, **kwargs):
|
||||||
fail(message, pytrace=False)
|
fail(message, pytrace=False)
|
||||||
|
|
||||||
|
@ -969,7 +962,7 @@ def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
||||||
|
|
||||||
|
|
||||||
@attr.s(frozen=True)
|
@attr.s(frozen=True)
|
||||||
class FixtureFunctionMarker(object):
|
class FixtureFunctionMarker:
|
||||||
scope = attr.ib()
|
scope = attr.ib()
|
||||||
params = attr.ib(converter=attr.converters.optional(tuple))
|
params = attr.ib(converter=attr.converters.optional(tuple))
|
||||||
autouse = attr.ib(default=False)
|
autouse = attr.ib(default=False)
|
||||||
|
@ -1083,7 +1076,7 @@ def pytest_addoption(parser):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FixtureManager(object):
|
class FixtureManager:
|
||||||
"""
|
"""
|
||||||
pytest fixtures definitions and information is stored and managed
|
pytest fixtures definitions and information is stored and managed
|
||||||
from this class.
|
from this class.
|
||||||
|
@ -1303,11 +1296,7 @@ class FixtureManager(object):
|
||||||
# during fixture definition we wrap the original fixture function
|
# during fixture definition we wrap the original fixture function
|
||||||
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning
|
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning
|
||||||
# when pytest itself calls the fixture function
|
# when pytest itself calls the fixture function
|
||||||
if six.PY2 and unittest:
|
obj = get_real_method(obj, holderobj)
|
||||||
# hack on Python 2 because of the unbound methods
|
|
||||||
obj = get_real_func(obj)
|
|
||||||
else:
|
|
||||||
obj = get_real_method(obj, holderobj)
|
|
||||||
|
|
||||||
fixture_def = FixtureDef(
|
fixture_def = FixtureDef(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Provides a function to report all internal modules for using freezing tools
|
Provides a function to report all internal modules for using freezing tools
|
||||||
pytest
|
pytest
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
|
|
||||||
def freeze_includes():
|
def freeze_includes():
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" version info, help messages, tracing configuration. """
|
""" version info, help messages, tracing configuration. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from argparse import Action
|
from argparse import Action
|
||||||
|
@ -24,7 +19,7 @@ class HelpAction(Action):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, option_strings, dest=None, default=False, help=None):
|
def __init__(self, option_strings, dest=None, default=False, help=None):
|
||||||
super(HelpAction, self).__init__(
|
super().__init__(
|
||||||
option_strings=option_strings,
|
option_strings=option_strings,
|
||||||
dest=dest,
|
dest=dest,
|
||||||
const=True,
|
const=True,
|
||||||
|
@ -122,7 +117,7 @@ def pytest_cmdline_parse():
|
||||||
def showversion(config):
|
def showversion(config):
|
||||||
p = py.path.local(pytest.__file__)
|
p = py.path.local(pytest.__file__)
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
|
"This is pytest version {}, imported from {}\n".format(pytest.__version__, p)
|
||||||
)
|
)
|
||||||
plugininfo = getpluginversioninfo(config)
|
plugininfo = getpluginversioninfo(config)
|
||||||
if plugininfo:
|
if plugininfo:
|
||||||
|
@ -160,7 +155,7 @@ def showhelp(config):
|
||||||
help, type, default = config._parser._inidict[name]
|
help, type, default = config._parser._inidict[name]
|
||||||
if type is None:
|
if type is None:
|
||||||
type = "string"
|
type = "string"
|
||||||
spec = "%s (%s):" % (name, type)
|
spec = "{} ({}):".format(name, type)
|
||||||
tw.write(" %s" % spec)
|
tw.write(" %s" % spec)
|
||||||
spec_len = len(spec)
|
spec_len = len(spec)
|
||||||
if spec_len > (indent_len - 3):
|
if spec_len > (indent_len - 3):
|
||||||
|
@ -194,7 +189,7 @@ def showhelp(config):
|
||||||
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
|
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
|
||||||
]
|
]
|
||||||
for name, help in vars:
|
for name, help in vars:
|
||||||
tw.line(" %-24s %s" % (name, help))
|
tw.line(" {:<24} {}".format(name, help))
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
||||||
|
@ -221,7 +216,7 @@ def getpluginversioninfo(config):
|
||||||
lines.append("setuptools registered plugins:")
|
lines.append("setuptools registered plugins:")
|
||||||
for plugin, dist in plugininfo:
|
for plugin, dist in plugininfo:
|
||||||
loc = getattr(plugin, "__file__", repr(plugin))
|
loc = getattr(plugin, "__file__", repr(plugin))
|
||||||
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
|
content = "{}-{} at {}".format(dist.project_name, dist.version, loc)
|
||||||
lines.append(" " + content)
|
lines.append(" " + content)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
@ -229,7 +224,9 @@ def getpluginversioninfo(config):
|
||||||
def pytest_report_header(config):
|
def pytest_report_header(config):
|
||||||
lines = []
|
lines = []
|
||||||
if config.option.debug or config.option.traceconfig:
|
if config.option.debug or config.option.traceconfig:
|
||||||
lines.append("using: pytest-%s pylib-%s" % (pytest.__version__, py.__version__))
|
lines.append(
|
||||||
|
"using: pytest-{} pylib-{}".format(pytest.__version__, py.__version__)
|
||||||
|
)
|
||||||
|
|
||||||
verinfo = getpluginversioninfo(config)
|
verinfo = getpluginversioninfo(config)
|
||||||
if verinfo:
|
if verinfo:
|
||||||
|
@ -243,5 +240,5 @@ def pytest_report_header(config):
|
||||||
r = plugin.__file__
|
r = plugin.__file__
|
||||||
else:
|
else:
|
||||||
r = repr(plugin)
|
r = repr(plugin)
|
||||||
lines.append(" %-20s: %s" % (name, r))
|
lines.append(" {:<20}: {}".format(name, r))
|
||||||
return lines
|
return lines
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||||
from pluggy import HookspecMarker
|
from pluggy import HookspecMarker
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
report test results in JUnit-XML format,
|
report test results in JUnit-XML format,
|
||||||
for use with Jenkins and build integration servers.
|
for use with Jenkins and build integration servers.
|
||||||
|
@ -9,10 +8,6 @@ Based on initial code from Ross Lawley.
|
||||||
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
|
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
|
||||||
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -20,16 +15,11 @@ import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest.config import filename_arg
|
from _pytest.config import filename_arg
|
||||||
|
|
||||||
# Python 2.X and 3.X compatibility
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
from codecs import open
|
|
||||||
|
|
||||||
|
|
||||||
class Junit(py.xml.Namespace):
|
class Junit(py.xml.Namespace):
|
||||||
pass
|
pass
|
||||||
|
@ -43,12 +33,12 @@ class Junit(py.xml.Namespace):
|
||||||
_legal_chars = (0x09, 0x0A, 0x0D)
|
_legal_chars = (0x09, 0x0A, 0x0D)
|
||||||
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
|
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
|
||||||
_legal_xml_re = [
|
_legal_xml_re = [
|
||||||
u"%s-%s" % (six.unichr(low), six.unichr(high))
|
"{}-{}".format(chr(low), chr(high))
|
||||||
for (low, high) in _legal_ranges
|
for (low, high) in _legal_ranges
|
||||||
if low < sys.maxunicode
|
if low < sys.maxunicode
|
||||||
]
|
]
|
||||||
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re
|
_legal_xml_re = [chr(x) for x in _legal_chars] + _legal_xml_re
|
||||||
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re))
|
illegal_xml_re = re.compile("[^%s]" % "".join(_legal_xml_re))
|
||||||
del _legal_chars
|
del _legal_chars
|
||||||
del _legal_ranges
|
del _legal_ranges
|
||||||
del _legal_xml_re
|
del _legal_xml_re
|
||||||
|
@ -60,9 +50,9 @@ def bin_xml_escape(arg):
|
||||||
def repl(matchobj):
|
def repl(matchobj):
|
||||||
i = ord(matchobj.group())
|
i = ord(matchobj.group())
|
||||||
if i <= 0xFF:
|
if i <= 0xFF:
|
||||||
return u"#x%02X" % i
|
return "#x%02X" % i
|
||||||
else:
|
else:
|
||||||
return u"#x%04X" % i
|
return "#x%04X" % i
|
||||||
|
|
||||||
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
|
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
|
||||||
|
|
||||||
|
@ -89,7 +79,7 @@ merge_family(families["xunit1"], families["_base_legacy"])
|
||||||
families["xunit2"] = families["_base"]
|
families["xunit2"] = families["_base"]
|
||||||
|
|
||||||
|
|
||||||
class _NodeReporter(object):
|
class _NodeReporter:
|
||||||
def __init__(self, nodeid, xml):
|
def __init__(self, nodeid, xml):
|
||||||
self.id = nodeid
|
self.id = nodeid
|
||||||
self.xml = xml
|
self.xml = xml
|
||||||
|
@ -229,7 +219,7 @@ class _NodeReporter(object):
|
||||||
else:
|
else:
|
||||||
if hasattr(report.longrepr, "reprcrash"):
|
if hasattr(report.longrepr, "reprcrash"):
|
||||||
message = report.longrepr.reprcrash.message
|
message = report.longrepr.reprcrash.message
|
||||||
elif isinstance(report.longrepr, six.string_types):
|
elif isinstance(report.longrepr, str):
|
||||||
message = report.longrepr
|
message = report.longrepr
|
||||||
else:
|
else:
|
||||||
message = str(report.longrepr)
|
message = str(report.longrepr)
|
||||||
|
@ -268,7 +258,7 @@ class _NodeReporter(object):
|
||||||
filename, lineno, skipreason = report.longrepr
|
filename, lineno, skipreason = report.longrepr
|
||||||
if skipreason.startswith("Skipped: "):
|
if skipreason.startswith("Skipped: "):
|
||||||
skipreason = skipreason[9:]
|
skipreason = skipreason[9:]
|
||||||
details = "%s:%s: %s" % (filename, lineno, skipreason)
|
details = "{}:{}: {}".format(filename, lineno, skipreason)
|
||||||
|
|
||||||
self.append(
|
self.append(
|
||||||
Junit.skipped(
|
Junit.skipped(
|
||||||
|
@ -353,7 +343,7 @@ def _check_record_param_type(param, v):
|
||||||
"""Used by record_testsuite_property to check that the given parameter name is of the proper
|
"""Used by record_testsuite_property to check that the given parameter name is of the proper
|
||||||
type"""
|
type"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if not isinstance(v, six.string_types):
|
if not isinstance(v, str):
|
||||||
msg = "{param} parameter needs to be a string, but {g} given"
|
msg = "{param} parameter needs to be a string, but {g} given"
|
||||||
raise TypeError(msg.format(param=param, g=type(v).__name__))
|
raise TypeError(msg.format(param=param, g=type(v).__name__))
|
||||||
|
|
||||||
|
@ -473,7 +463,7 @@ def mangle_test_address(address):
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
class LogXML(object):
|
class LogXML:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
logfile,
|
logfile,
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" Access and control log capturing. """
|
""" Access and control log capturing. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import dummy_context_manager
|
from _pytest.compat import dummy_context_manager
|
||||||
|
@ -39,14 +33,11 @@ class ColoredLevelFormatter(logging.Formatter):
|
||||||
logging.DEBUG: {"purple"},
|
logging.DEBUG: {"purple"},
|
||||||
logging.NOTSET: set(),
|
logging.NOTSET: set(),
|
||||||
}
|
}
|
||||||
LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-]?\d*s)")
|
LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*s)")
|
||||||
|
|
||||||
def __init__(self, terminalwriter, *args, **kwargs):
|
def __init__(self, terminalwriter, *args, **kwargs):
|
||||||
super(ColoredLevelFormatter, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if six.PY2:
|
self._original_fmt = self._style._fmt
|
||||||
self._original_fmt = self._fmt
|
|
||||||
else:
|
|
||||||
self._original_fmt = self._style._fmt
|
|
||||||
self._level_to_fmt_mapping = {}
|
self._level_to_fmt_mapping = {}
|
||||||
|
|
||||||
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
|
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
|
||||||
|
@ -70,41 +61,35 @@ class ColoredLevelFormatter(logging.Formatter):
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
|
fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt)
|
||||||
if six.PY2:
|
self._style._fmt = fmt
|
||||||
self._fmt = fmt
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class PercentStyleMultiline(logging.PercentStyle):
|
||||||
|
"""A logging style with special support for multiline messages.
|
||||||
|
|
||||||
|
If the message of a record consists of multiple lines, this style
|
||||||
|
formats the message as if each line were logged separately.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_message(record_dict, message):
|
||||||
|
tmp = record_dict.copy()
|
||||||
|
tmp["message"] = message
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if "\n" in record.message:
|
||||||
|
lines = record.message.splitlines()
|
||||||
|
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
|
||||||
|
# TODO optimize this by introducing an option that tells the
|
||||||
|
# logging framework that the indentation doesn't
|
||||||
|
# change. This allows to compute the indentation only once.
|
||||||
|
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
|
||||||
|
lines[0] = formatted
|
||||||
|
return ("\n" + " " * indentation).join(lines)
|
||||||
else:
|
else:
|
||||||
self._style._fmt = fmt
|
return self._fmt % record.__dict__
|
||||||
return super(ColoredLevelFormatter, self).format(record)
|
|
||||||
|
|
||||||
|
|
||||||
if not six.PY2:
|
|
||||||
# Formatter classes don't support format styles in PY2
|
|
||||||
|
|
||||||
class PercentStyleMultiline(logging.PercentStyle):
|
|
||||||
"""A logging style with special support for multiline messages.
|
|
||||||
|
|
||||||
If the message of a record consists of multiple lines, this style
|
|
||||||
formats the message as if each line were logged separately.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _update_message(record_dict, message):
|
|
||||||
tmp = record_dict.copy()
|
|
||||||
tmp["message"] = message
|
|
||||||
return tmp
|
|
||||||
|
|
||||||
def format(self, record):
|
|
||||||
if "\n" in record.message:
|
|
||||||
lines = record.message.splitlines()
|
|
||||||
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
|
|
||||||
# TODO optimize this by introducing an option that tells the
|
|
||||||
# logging framework that the indentation doesn't
|
|
||||||
# change. This allows to compute the indentation only once.
|
|
||||||
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
|
|
||||||
lines[0] = formatted
|
|
||||||
return ("\n" + " " * indentation).join(lines)
|
|
||||||
else:
|
|
||||||
return self._fmt % record.__dict__
|
|
||||||
|
|
||||||
|
|
||||||
def get_option_ini(config, *names):
|
def get_option_ini(config, *names):
|
||||||
|
@ -246,7 +231,7 @@ class LogCaptureHandler(logging.StreamHandler):
|
||||||
self.stream = py.io.TextIO()
|
self.stream = py.io.TextIO()
|
||||||
|
|
||||||
|
|
||||||
class LogCaptureFixture(object):
|
class LogCaptureFixture:
|
||||||
"""Provides access and control of log capturing."""
|
"""Provides access and control of log capturing."""
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
@ -393,7 +378,7 @@ def get_actual_log_level(config, *setting_names):
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(log_level, six.string_types):
|
if isinstance(log_level, str):
|
||||||
log_level = log_level.upper()
|
log_level = log_level.upper()
|
||||||
try:
|
try:
|
||||||
return int(getattr(logging, log_level, log_level))
|
return int(getattr(logging, log_level, log_level))
|
||||||
|
@ -412,7 +397,7 @@ def pytest_configure(config):
|
||||||
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
config.pluginmanager.register(LoggingPlugin(config), "logging-plugin")
|
||||||
|
|
||||||
|
|
||||||
class LoggingPlugin(object):
|
class LoggingPlugin:
|
||||||
"""Attaches to the logging module and captures log messages for each test.
|
"""Attaches to the logging module and captures log messages for each test.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -475,8 +460,7 @@ class LoggingPlugin(object):
|
||||||
else:
|
else:
|
||||||
formatter = logging.Formatter(log_format, log_date_format)
|
formatter = logging.Formatter(log_format, log_date_format)
|
||||||
|
|
||||||
if not six.PY2:
|
formatter._style = PercentStyleMultiline(formatter._style._fmt)
|
||||||
formatter._style = PercentStyleMultiline(formatter._style._fmt)
|
|
||||||
return formatter
|
return formatter
|
||||||
|
|
||||||
def _setup_cli_logging(self):
|
def _setup_cli_logging(self):
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" core implementation of testing process: init, session, runtest loop. """
|
""" core implementation of testing process: init, session, runtest loop. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
|
@ -14,7 +8,6 @@ import warnings
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
import py
|
import py
|
||||||
import six
|
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
@ -172,7 +165,7 @@ def pytest_addoption(parser):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class _ConfigDeprecated(object):
|
class _ConfigDeprecated:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.__dict__["_config"] = config
|
self.__dict__["_config"] = config
|
||||||
|
|
||||||
|
@ -311,10 +304,7 @@ def pytest_ignore_collect(path, config):
|
||||||
if excludeglobopt:
|
if excludeglobopt:
|
||||||
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
|
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
|
||||||
|
|
||||||
if any(
|
if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
|
||||||
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
|
|
||||||
for glob in ignore_globs
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
allow_in_venv = config.getoption("collect_in_virtualenv")
|
allow_in_venv = config.getoption("collect_in_virtualenv")
|
||||||
|
@ -342,47 +332,7 @@ def pytest_collection_modifyitems(items, config):
|
||||||
items[:] = remaining
|
items[:] = remaining
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
class FSHookProxy:
|
||||||
def _patched_find_module():
|
|
||||||
"""Patch bug in pkgutil.ImpImporter.find_module
|
|
||||||
|
|
||||||
When using pkgutil.find_loader on python<3.4 it removes symlinks
|
|
||||||
from the path due to a call to os.path.realpath. This is not consistent
|
|
||||||
with actually doing the import (in these versions, pkgutil and __import__
|
|
||||||
did not share the same underlying code). This can break conftest
|
|
||||||
discovery for pytest where symlinks are involved.
|
|
||||||
|
|
||||||
The only supported python<3.4 by pytest is python 2.7.
|
|
||||||
"""
|
|
||||||
if six.PY2: # python 3.4+ uses importlib instead
|
|
||||||
|
|
||||||
def find_module_patched(self, fullname, path=None):
|
|
||||||
# Note: we ignore 'path' argument since it is only used via meta_path
|
|
||||||
subname = fullname.split(".")[-1]
|
|
||||||
if subname != fullname and self.path is None:
|
|
||||||
return None
|
|
||||||
if self.path is None:
|
|
||||||
path = None
|
|
||||||
else:
|
|
||||||
# original: path = [os.path.realpath(self.path)]
|
|
||||||
path = [self.path]
|
|
||||||
try:
|
|
||||||
file, filename, etc = pkgutil.imp.find_module(subname, path)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
return pkgutil.ImpLoader(fullname, file, filename, etc)
|
|
||||||
|
|
||||||
old_find_module = pkgutil.ImpImporter.find_module
|
|
||||||
pkgutil.ImpImporter.find_module = find_module_patched
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
pkgutil.ImpImporter.find_module = old_find_module
|
|
||||||
else:
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
class FSHookProxy(object):
|
|
||||||
def __init__(self, fspath, pm, remove_mods):
|
def __init__(self, fspath, pm, remove_mods):
|
||||||
self.fspath = fspath
|
self.fspath = fspath
|
||||||
self.pm = pm
|
self.pm = pm
|
||||||
|
@ -522,8 +472,8 @@ class Session(nodes.FSCollector):
|
||||||
if self._notfound:
|
if self._notfound:
|
||||||
errors = []
|
errors = []
|
||||||
for arg, exc in self._notfound:
|
for arg, exc in self._notfound:
|
||||||
line = "(no name %r in any of %r)" % (arg, exc.args[0])
|
line = "(no name {!r} in any of {!r})".format(arg, exc.args[0])
|
||||||
errors.append("not found: %s\n%s" % (arg, line))
|
errors.append("not found: {}\n{}".format(arg, line))
|
||||||
# XXX: test this
|
# XXX: test this
|
||||||
raise UsageError(*errors)
|
raise UsageError(*errors)
|
||||||
if not genitems:
|
if not genitems:
|
||||||
|
@ -540,8 +490,7 @@ class Session(nodes.FSCollector):
|
||||||
self.trace("processing argument", arg)
|
self.trace("processing argument", arg)
|
||||||
self.trace.root.indent += 1
|
self.trace.root.indent += 1
|
||||||
try:
|
try:
|
||||||
for x in self._collect(arg):
|
yield from self._collect(arg)
|
||||||
yield x
|
|
||||||
except NoMatch:
|
except NoMatch:
|
||||||
# we are inside a make_report hook so
|
# we are inside a make_report hook so
|
||||||
# we cannot directly pass through the exception
|
# we cannot directly pass through the exception
|
||||||
|
@ -578,7 +527,7 @@ class Session(nodes.FSCollector):
|
||||||
# If it's a directory argument, recurse and look for any Subpackages.
|
# If it's a directory argument, recurse and look for any Subpackages.
|
||||||
# Let the Package collector deal with subnodes, don't collect here.
|
# Let the Package collector deal with subnodes, don't collect here.
|
||||||
if argpath.check(dir=1):
|
if argpath.check(dir=1):
|
||||||
assert not names, "invalid arg %r" % (arg,)
|
assert not names, "invalid arg {!r}".format(arg)
|
||||||
|
|
||||||
seen_dirs = set()
|
seen_dirs = set()
|
||||||
for path in argpath.visit(
|
for path in argpath.visit(
|
||||||
|
@ -623,15 +572,13 @@ class Session(nodes.FSCollector):
|
||||||
if argpath.basename == "__init__.py":
|
if argpath.basename == "__init__.py":
|
||||||
yield next(m[0].collect())
|
yield next(m[0].collect())
|
||||||
return
|
return
|
||||||
for y in m:
|
yield from m
|
||||||
yield y
|
|
||||||
|
|
||||||
def _collectfile(self, path, handle_dupes=True):
|
def _collectfile(self, path, handle_dupes=True):
|
||||||
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
|
assert (
|
||||||
path,
|
path.isfile()
|
||||||
path.isdir(),
|
), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
|
||||||
path.exists(),
|
path, path.isdir(), path.exists(), path.islink()
|
||||||
path.islink(),
|
|
||||||
)
|
)
|
||||||
ihook = self.gethookproxy(path)
|
ihook = self.gethookproxy(path)
|
||||||
if not self.isinitpath(path):
|
if not self.isinitpath(path):
|
||||||
|
@ -662,23 +609,14 @@ class Session(nodes.FSCollector):
|
||||||
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
ihook.pytest_collect_directory(path=dirpath, parent=self)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if six.PY2:
|
@staticmethod
|
||||||
|
def _visit_filter(f):
|
||||||
@staticmethod
|
return f.check(file=1)
|
||||||
def _visit_filter(f):
|
|
||||||
return f.check(file=1) and not f.strpath.endswith("*.pyc")
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _visit_filter(f):
|
|
||||||
return f.check(file=1)
|
|
||||||
|
|
||||||
def _tryconvertpyarg(self, x):
|
def _tryconvertpyarg(self, x):
|
||||||
"""Convert a dotted module name to path."""
|
"""Convert a dotted module name to path."""
|
||||||
try:
|
try:
|
||||||
with _patched_find_module():
|
loader = pkgutil.find_loader(x)
|
||||||
loader = pkgutil.find_loader(x)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return x
|
return x
|
||||||
if loader is None:
|
if loader is None:
|
||||||
|
@ -686,8 +624,7 @@ class Session(nodes.FSCollector):
|
||||||
# This method is sometimes invoked when AssertionRewritingHook, which
|
# This method is sometimes invoked when AssertionRewritingHook, which
|
||||||
# does not define a get_filename method, is already in place:
|
# does not define a get_filename method, is already in place:
|
||||||
try:
|
try:
|
||||||
with _patched_find_module():
|
path = loader.get_filename(x)
|
||||||
path = loader.get_filename(x)
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Retrieve path from AssertionRewritingHook:
|
# Retrieve path from AssertionRewritingHook:
|
||||||
path = loader.modules[x][0].co_filename
|
path = loader.modules[x][0].co_filename
|
||||||
|
@ -769,6 +706,5 @@ class Session(nodes.FSCollector):
|
||||||
rep = collect_one_node(node)
|
rep = collect_one_node(node)
|
||||||
if rep.passed:
|
if rep.passed:
|
||||||
for subnode in rep.result:
|
for subnode in rep.result:
|
||||||
for x in self.genitems(subnode):
|
yield from self.genitems(subnode)
|
||||||
yield x
|
|
||||||
node.ihook.pytest_collectreport(report=rep)
|
node.ihook.pytest_collectreport(report=rep)
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
""" generic mechanism for marking and selecting python functions. """
|
""" generic mechanism for marking and selecting python functions. """
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from .legacy import matchkeyword
|
from .legacy import matchkeyword
|
||||||
from .legacy import matchmark
|
from .legacy import matchmark
|
||||||
from .structures import EMPTY_PARAMETERSET_OPTION
|
from .structures import EMPTY_PARAMETERSET_OPTION
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue