Compare commits

...

79 Commits
7.4.4 ... 4.6.6

Author SHA1 Message Date
Bruno Oliveira
5b499bafb2 Preparing release version 4.6.6 2019-10-11 17:02:08 -04:00
Bruno Oliveira
62c0d82d64 Use 'python3' instead of 'python3.6' on tox
This allows us to use python3.7+ to use tox
2019-10-11 16:58:40 -04:00
Bruno Oliveira
d526053af3 Add changelog entry for #5902 2019-10-11 16:57:32 -04:00
Bruno Oliveira
2c7614a0e1 [4.6] Replace importlib_metadata with importlib.metadata on Pyt… (#5945)
[4.6] Replace importlib_metadata with importlib.metadata on Python 3.8+ (#5539)
2019-10-11 12:54:28 -03:00
Bruno Oliveira
b9a8465ce4 Replace importlib_metadata with importlib.metadata on Python 3.8+ (#5539) 2019-10-11 17:10:01 +02:00
Bruno Oliveira
1cc974c95d [4.6] Fix warnings with attrs 19.2 and fix object assertions (#… (#5944)
[4.6] Fix warnings with attrs 19.2 and fix object assertions (#5902)
2019-10-11 11:13:37 -03:00
Bruno Oliveira
c03e46f1ad Fix warnings with attrs 19.2 and fix object assertions (#5902)
Fix warnings with attrs 19.2 and fix object assertions
2019-10-11 15:26:23 +02:00
Bruno Oliveira
f2d87dcf6c Merge pull request #5809 from goerz/pastebin
Fix "lexer" being used when uploading to bpaste.net
2019-09-01 09:34:16 -03:00
Michael Goerz
914441557c Fix "lexer" being used when uploading to bpaste.net
Closes #5806.
2019-09-01 00:38:11 -04:00
Anthony Sottile
8aba863a63 Merge pull request #5801 from asottile/flake8_2020
[4.6] fixes for python4
2019-08-29 10:30:54 -07:00
Anthony Sottile
aa79b1c00c [4.6] fixes for python4 2019-08-29 09:52:35 -07:00
Bruno Oliveira
117f52dcf3 Merge pull request #5767 from nicoddemus/backport-5723-5740-5750
[4.6] Publish GitHub release notes after deployment
2019-08-20 19:34:49 -03:00
Bruno Oliveira
9191857b5f Do not update pip on Azure
Avoid upgrading pip because it is giving this error on py34:

Requirement already up-to-date: pip in c:\hostedtoolcache\windows\python\3.4.4\x64\lib\site-packages (19.2.1)
ERROR: Package 'pip' requires a different Python: 3.4.4 not in '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*'
[error]Cmd.exe exited with code '1'.
2019-08-20 19:11:14 -03:00
Bruno Oliveira
7718d8c972 Fix linting 2019-08-19 16:58:47 -03:00
Bruno Oliveira
7a96f3f970 Merge pull request #5750 from nicoddemus/fix-gh-publish-notes
Forward $TRAVIS_REPO_SLUG for GH publish notes
2019-08-19 16:39:16 -03:00
Bruno Oliveira
2fbea0e5e4 Merge pull request #5740 from nicoddemus/use-repo-env-var
Use TRAVIS_REPO_SLUG instead of hard-coding pytest-dev/pytest
2019-08-19 16:39:07 -03:00
Bruno Oliveira
4910036b76 Publish GitHub release notes after deployment (#5723)
Publish GitHub release notes after deployment
2019-08-19 16:38:57 -03:00
Bruno Oliveira
0b039b14aa Preparing release version 4.6.5 (#5696)
Preparing release version 4.6.5
2019-08-05 15:05:28 -03:00
Bruno Oliveira
7807c263bc Preparing release version 4.6.5 2019-08-05 13:37:25 -03:00
Daniel Hahler
b71f873189 [4.6] Fix RuntimeError when trying to collect package with "__init__.py" only
Fixes https://github.com/pytest-dev/pytest/issues/4344.
2019-08-05 18:22:23 +02:00
Anthony Sottile
a19ae2af22 Merge pull request #5691 from nicoddemus/backport-5627
[4.6] Handle only known functions in rm_rf (#5627)
2019-08-04 19:08:01 -07:00
Bruno Oliveira
0274c08b8a Handle only known functions in rm_rf (#5627)
Backport of #5627

Conflicts:
- 	src/_pytest/pathlib.py

Also had to adapt:

- PermissionError into OSError with appropriate
- Change keyword-only argument to **kwargs
- Remove type annotations
2019-08-03 10:29:17 -03:00
Bruno Oliveira
829941a061 [4.6] Improve output when parsing an ini configuration fails (#… (#5652)
[4.6] Improve output when parsing an ini configuration fails (#5650)
2019-08-01 12:28:50 -03:00
Bruno Oliveira
2e345fd277 Disable shallow cloning because of setuptools-scm
setuptools-scm needs all tags to guess the version correctly
2019-08-01 10:20:14 -03:00
Bruno Oliveira
400393cfe4 Improve output when parsing an ini configuration fails (#5650)
Improve output when parsing an ini configuration fails
2019-07-23 11:18:16 -03:00
Anthony Sottile
459c5f4e49 Merge pull request #5637 from asottile/backport-5636
[4.6] #5636 Fix ordering of sys modules snapshot
2019-07-20 13:18:29 -07:00
Anthony Sottile
f06ae5297b Merge pull request #5636 from asottile/fixup_sysmodules_test
Fix ordering of sys modules snapshot
2019-07-20 12:31:22 -07:00
Bruno Oliveira
30de66944d [4.6] Fix rmtree to remove directories with read-only files (#5… (#5597)
[4.6] Fix rmtree to remove directories with read-only files (#5588)
2019-07-11 19:43:55 -03:00
Bruno Oliveira
02c737fe4e Fix rmtree to remove directories with read-only files (#5588)
Fix rmtree to remove directories with read-only files
2019-07-11 19:07:36 -03:00
Bruno Oliveira
01655b114e Merge pull request #5561 from nicoddemus/backport-5560
[4.6] Fix comment in stepwise (follow up to #5555) [skip ci] (#5560)
2019-07-05 10:29:02 -03:00
Bruno Oliveira
a92ac0d4f6 Fix comment in stepwise (follow up to #5555) [skip ci] (#5560)
Fix comment in stepwise (follow up to #5555) [skip ci]
2019-07-05 10:27:34 -03:00
Bruno Oliveira
802c77ad2f [4.6] Handle xfail(strict=True) properly in --step-wise mode (#… (#5556)
[4.6] Handle xfail(strict=True) properly in --step-wise mode (#5555)
2019-07-04 21:58:41 -03:00
Bruno Oliveira
acb62ba619 Fix test_stepwise::test_xfail_handling when byte code writing is disabled 2019-07-04 21:08:16 -03:00
Bruno Oliveira
df0cff18ac Handle xfail(strict=True) properly in --step-wise mode (#5555)
Handle xfail(strict=True) properly in --step-wise mode
2019-07-04 20:51:59 -03:00
Bruno Oliveira
46a0888352 Fix pytest.raises handling of unicode exceptions in Python 2 (#5479)
Fix pytest.raises handling of unicode exceptions in Python 2
2019-07-04 10:25:56 -03:00
Bruno Oliveira
34b4e21606 Include two more cases for non-ascii encoded bytes 2019-07-04 09:34:55 -03:00
Bruno Oliveira
a886015bfd Test various bytes <=> unicode cases as requested in review 2019-06-30 21:08:40 -03:00
Bruno Oliveira
09dee292ca Use unicode message if regex is also unicode in ExceptionInfo.match 2019-06-30 10:43:46 -03:00
Anthony Sottile
2301fa61de Merge pull request #5520 from asottile/release-4.6.4
Preparing release version 4.6.4
2019-06-28 19:05:34 -07:00
Anthony Sottile
d3549df5b9 Preparing release version 4.6.4 2019-06-28 18:23:53 -07:00
Anthony Sottile
b85d98edbb Merge pull request #5508 from asottile/backport_5506
[4.6] Fix crash when discovery fails while using `-p no:terminal`
2019-06-27 15:57:17 -07:00
Anthony Sottile
f4b1c1184f Merge pull request #5506 from asottile/fix_no_terminal
Fix crash when discovery fails while using `-p no:terminal`
2019-06-27 11:00:05 -07:00
Thomas Grainger
86a4eb6008 Update changelog/5478.bugfix.rst
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2019-06-27 07:35:02 +01:00
Thomas Grainger
013d0e66c7 use safe_str to serialize Exceptions Fixes #5478 2019-06-26 11:11:54 +01:00
Bruno Oliveira
554bff8cc1 Merge pull request #5489 from graingert/fix-safe-str-doc
fix safe_str docstring
2019-06-25 17:30:54 -03:00
Thomas Grainger
d2f74d342e fix safe_str docstring 2019-06-25 12:54:46 +01:00
Bruno Oliveira
430de12f35 Merge pull request #5486 from nicoddemus/backport-5483
[4.6] Pickup addition positional args passed to _parse_parametrize_ar… (#5483)
2019-06-24 22:59:54 -03:00
Bruno Oliveira
d5eed3bb9c Pickup addition positional args passed to _parse_parametrize_ar… (#5483)
Pickup addition positional args passed to _parse_parametrize_args
2019-06-24 22:07:53 -03:00
Bruno Oliveira
4b104ba222 Merge pull request #5454 from nicoddemus/backport-5446
Backport 5446
2019-06-17 09:46:01 -03:00
Zac Hatfield-Dodds
c765b83a2a Merge pull request #5453 from Zac-HD/backport-unwrapper
[4.6] Backport unwrapper PR
2019-06-17 15:40:40 +10:00
Daniel Hahler
443af11861 Merge pull request #5404 from Zac-HD/helpful-mock-unwrapper
Emit warning for broken object
2019-06-17 14:48:40 +10:00
Bruno Oliveira
4e02248b84 Fix test docstring 2019-06-16 10:46:07 -03:00
Bruno Oliveira
43a499e6fa Remove handling of collection errors by --sw
Since then pytest itself adopted the behavior of interrupting
the test session on collection errors, so --sw no longer needs
to handle this.

The --sw behavior seems have been implemented when pytest
would continue execution even if there were collection errors.
2019-06-16 10:46:07 -03:00
Bruno Oliveira
e2fa2b621c Fix --sw crash when first file in cmdline fails to collect
Fix #5444
2019-06-16 10:46:07 -03:00
Thomas Grainger
0fc11b6f3c add test for stepwise attribute error Refs: #5444 2019-06-16 10:46:07 -03:00
Anthony Sottile
d2c1a04532 Merge pull request #5435 from asottile/release-4.6.3
Preparing release version 4.6.3
2019-06-11 09:58:51 -07:00
Anthony Sottile
b8e65d03bf Preparing release version 4.6.3 2019-06-11 08:48:11 -07:00
Anthony Sottile
f37ea715d8 Merge pull request #5425 from asottile/backport-5421
[4.6] Link deprecation docs pytest.raises 'message' warning
2019-06-08 15:05:01 -07:00
Anthony Sottile
45d36ddb47 Merge pull request #5421 from nicoddemus/raises-warning-message
Link deprecation docs pytest.raises 'message' warning
2019-06-08 12:45:16 -07:00
Anthony Sottile
355954df5d Merge pull request #5411 from nicoddemus/backport-5391
[4.6] Fix verbosity bug in --collect-only (backport of #5391)
2019-06-05 17:32:15 -07:00
Bruno Oliveira
a93c50ccb9 Fix verbosity bug in --collect-only (#5391)
Fix verbosity bug in --collect-only
2019-06-05 20:50:21 -03:00
Bruno Oliveira
1cae76b0fe [4.6] tests: restore tracing function (#5408)
[4.6] tests: restore tracing function
2019-06-05 20:01:55 -03:00
Daniel Hahler
1b7597ac91 [4.6] tests: restore tracing function
Without this, `testing/test_pdb.py` (already without pexpect) will cause
missing test coverage afterwards (for the same process).
2019-06-05 12:44:30 +02:00
Anthony Sottile
21680ffa77 Merge pull request #5401 from nicoddemus/backport-5389
[4.6] Backport #5389
2019-06-04 19:35:42 -07:00
Bruno Oliveira
8076f48eae [4.6] Merge pull request #5393 from nicoddemus/unittest-self-5390 (#5399)
[4.6] Merge pull request #5393 from nicoddemus/unittest-self-5390
2019-06-04 22:56:09 -03:00
Dirk Thomas
0ae27714d1 Backport of #5389: fix for 'files' = None in broken metadata 2019-06-04 22:21:25 -03:00
Anthony Sottile
92432ac45c Merge pull request #5393 from nicoddemus/unittest-self-5390
item.obj is again a bound method on TestCase function items
2019-06-04 17:49:53 -07:00
Anthony Sottile
937f945946 Merge pull request #5386 from asottile/backport_5384
[4.6] Remove --recreate from .travis.yml (#5384)
2019-06-03 19:54:30 -07:00
Bruno Oliveira
829a5986e8 Remove --recreate from .travis.yml (#5384)
Remove --recreate from .travis.yml
2019-06-03 19:26:04 -07:00
Anthony Sottile
54dbfb5167 Merge pull request #5379 from asottile/release-4.6.2
Preparing release version 4.6.2
2019-06-03 12:19:07 -07:00
Anthony Sottile
70f0b77c72 Preparing release version 4.6.2 2019-06-03 10:43:09 -07:00
Anthony Sottile
2a8b463b38 Merge pull request #5376 from asottile/backport_5373
[4.6] Merge pull request #5373 from asottile/revert_all_handling
2019-06-03 10:18:37 -07:00
Anthony Sottile
12bf458719 Merge pull request #5373 from asottile/revert_all_handling
Revert unrolling of `all()`
2019-06-03 09:20:48 -07:00
Anthony Sottile
114dba56f8 Merge pull request #5362 from asottile/release-4.6.1
Preparing release version 4.6.1
2019-06-02 11:43:41 -07:00
Anthony Sottile
abb853f482 Preparing release version 4.6.1 2019-06-02 10:09:51 -07:00
Anthony Sottile
8208a376cc Merge pull request #5361 from asottile/backport_5360
[4.6] Fix all() unroll for non-generators/non-list comprehensions (#5360)
2019-06-02 10:04:31 -07:00
Bruno Oliveira
f078984c2e Fix all() unroll for non-generators/non-list comprehensions (#5360)
Fix all() unroll for non-generators/non-list comprehensions
2019-06-02 09:12:39 -07:00
Bruno Oliveira
dba62f8a46 [4.6] Fix pytest.mark.parametrize when the argvalue is an iterator (#5357)
[4.6] Fix `pytest.mark.parametrize` when the argvalue is an iterator
2019-06-01 19:58:06 -03:00
Anthony Sottile
f7bf914108 Fix pytest.mark.parametrize when the argvalue is an iterator 2019-06-01 15:10:33 -07:00
54 changed files with 924 additions and 191 deletions

View File

@@ -13,6 +13,10 @@ env:
global:
- PYTEST_ADDOPTS=-vv
# setuptools-scm needs all tags in order to obtain a proper version
git:
depth: false
install:
- python -m pip install --upgrade --pre tox
@@ -95,8 +99,17 @@ jobs:
- stage: deploy
python: '3.6'
install: pip install -U setuptools setuptools_scm
install: pip install -U setuptools setuptools_scm tox
script: skip
# token to upload github release notes: GH_RELEASE_NOTES_TOKEN
env:
- secure: "OjOeL7/0JUDkV00SsTs732e8vQjHynpbG9FKTNtZZJ+1Zn4Cib+hAlwmlBnvVukML0X60YpcfjnC4quDOIGLPsh5zeXnvJmYtAIIUNQXjWz8NhcGYrhyzuP1rqV22U68RTCdmOq3lMYU/W2acwHP7T49PwJtOiUM5kF120UAQ0Zi5EmkqkIvH8oM5mO9Dlver+/U7Htpz9rhKrHBXQNCMZI6yj2aUyukqB2PN2fjAlDbCF//+FmvYw9NjT4GeFOSkTCf4ER9yfqs7yglRfwiLtOCZ2qKQhWZNsSJDB89rxIRXWavJUjJKeY2EW2/NkomYJDpqJLIF4JeFRw/HhA47CYPeo6BJqyyNV+0CovL1frpWfi9UQw2cMbgFUkUIUk3F6DD59PHNIOX2R/HX56dQsw7WKl3QuHlCOkICXYg8F7Ta684IoKjeTX03/6QNOkURfDBwfGszY0FpbxrjCSWKom6RyZdyidnESaxv9RzjcIRZVh1rp8KMrwS1OrwRSdG0zjlsPr49hWMenN/8fKgcHTV4/r1Tj6mip0dorSRCrgUNIeRBKgmui6FS8642ab5JNKOxMteVPVR2sFuhjOQ0Jy+PmvceYY9ZMWc3+/B/KVh0dZ3hwvLGZep/vxDS2PwCA5/xw31714vT5LxidKo8yECjBynMU/wUTTS695D3NY="
addons:
apt:
packages:
# required by publish_gh_release_notes
- pandoc
after_deploy: tox -e publish_gh_release_notes
deploy:
provider: pypi
user: nicoddemus
@@ -130,7 +143,7 @@ before_script:
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
fi
script: tox --recreate
script: tox
after_success:
- |

View File

@@ -135,6 +135,7 @@ Kale Kundert
Katarzyna Jachim
Katerina Koukiou
Kevin Cox
Kevin J. Foley
Kodi B. Arfer
Kostis Anagnostopoulos
Kristoffer Nordström

View File

@@ -18,6 +18,117 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 4.6.6 (2019-10-11)
=========================
Bug Fixes
---------
- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+.
- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the
standard library on Python 3.8+.
- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text".
- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``.
Trivial/Internal Changes
------------------------
- `#5801 <https://github.com/pytest-dev/pytest/issues/5801>`_: Fixes python version checks (detected by ``flake8-2020``) in case python4 becomes a thing.
pytest 4.6.5 (2019-08-05)
=========================
Bug Fixes
---------
- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only.
- `#5478 <https://github.com/pytest-dev/pytest/issues/5478>`_: Fix encode error when using unicode strings in exceptions with ``pytest.raises``.
- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only,
which could lead to pytest crashing when executed a second time with the ``--basetemp`` option.
- `#5547 <https://github.com/pytest-dev/pytest/issues/5547>`_: ``--step-wise`` now handles ``xfail(strict=True)`` markers properly.
- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails.
pytest 4.6.4 (2019-06-28)
=========================
Bug Fixes
---------
- `#5404 <https://github.com/pytest-dev/pytest/issues/5404>`_: Emit a warning when attempting to unwrap a broken object raises an exception,
for easier debugging (`#5080 <https://github.com/pytest-dev/pytest/issues/5080>`__).
- `#5444 <https://github.com/pytest-dev/pytest/issues/5444>`_: Fix ``--stepwise`` mode when the first file passed on the command-line fails to collect.
- `#5482 <https://github.com/pytest-dev/pytest/issues/5482>`_: Fix bug introduced in 4.6.0 causing collection errors when passing
more than 2 positional arguments to ``pytest.mark.parametrize``.
- `#5505 <https://github.com/pytest-dev/pytest/issues/5505>`_: Fix crash when discovery fails while using ``-p no:terminal``.
pytest 4.6.3 (2019-06-11)
=========================
Bug Fixes
---------
- `#5383 <https://github.com/pytest-dev/pytest/issues/5383>`_: ``-q`` has again an impact on the style of the collected items
(``--collect-only``) when ``--log-cli-level`` is used.
- `#5389 <https://github.com/pytest-dev/pytest/issues/5389>`_: Fix regressions of `#5063 <https://github.com/pytest-dev/pytest/pull/5063>`__ for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
- `#5390 <https://github.com/pytest-dev/pytest/issues/5390>`_: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
pytest 4.6.2 (2019-06-03)
=========================
Bug Fixes
---------
- `#5370 <https://github.com/pytest-dev/pytest/issues/5370>`_: Revert unrolling of ``all()`` to fix ``NameError`` on nested comprehensions.
- `#5371 <https://github.com/pytest-dev/pytest/issues/5371>`_: Revert unrolling of ``all()`` to fix incorrect handling of generators with ``if``.
- `#5372 <https://github.com/pytest-dev/pytest/issues/5372>`_: Revert unrolling of ``all()`` to fix incorrect assertion when using ``all()`` in an expression.
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)
=========================

View File

@@ -91,7 +91,7 @@ jobs:
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 tox
displayName: 'Install tox'
- script: |

View File

@@ -6,6 +6,12 @@ Release announcements
:maxdepth: 2
release-4.6.6
release-4.6.5
release-4.6.4
release-4.6.3
release-4.6.2
release-4.6.1
release-4.6.0
release-4.5.0
release-4.4.2

View File

@@ -0,0 +1,19 @@
pytest-4.6.1
=======================================
pytest 4.6.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Bruno Oliveira
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,18 @@
pytest-4.6.2
=======================================
pytest 4.6.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,21 @@
pytest-4.6.3
=======================================
pytest 4.6.3 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
* Daniel Hahler
* Dirk Thomas
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,22 @@
pytest-4.6.4
=======================================
pytest 4.6.4 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
* Daniel Hahler
* Thomas Grainger
* Zac Hatfield-Dodds
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,21 @@
pytest-4.6.5
=======================================
pytest 4.6.5 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
* Daniel Hahler
* Thomas Grainger
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,20 @@
pytest-4.6.6
=======================================
pytest 4.6.6 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
* Michael Goerz
Happy testing,
The pytest Development Team

View File

@@ -434,10 +434,11 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
...sss...sssssssss...sss... [100%]
...ssssssssssssssssssssssss [100%]
========================= short test summary info ==========================
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
12 passed, 15 skipped in 0.12 seconds
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.5' not found
3 passed, 24 skipped in 0.12 seconds
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------

View File

@@ -436,7 +436,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
items = [1, 2, 3]
print("items is %r" % items)
> a, b = items.pop()
E TypeError: 'int' object is not iterable
E TypeError: cannot unpack non-iterable int object
failure_demo.py:182: TypeError
--------------------------- Captured stdout call ---------------------------
@@ -515,7 +515,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_z2_type_error(self):
items = 3
> a, b = items
E TypeError: 'int' object is not iterable
E TypeError: cannot unpack non-iterable int object
failure_demo.py:222: TypeError
______________________ TestMoreErrors.test_startswith ______________________

View File

@@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py
.. _`simpletest`:

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
"""
Script used to publish GitHub release notes extracted from CHANGELOG.rst.
This script is meant to be executed after a successful deployment in Travis.
Uses the following environment variables:
* GIT_TAG: the name of the tag of the current commit.
* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. It should be encrypted using:
$travis encrypt GH_RELEASE_NOTES_TOKEN=<token> -r pytest-dev/pytest
And the contents pasted in the ``deploy.env.secure`` section in the ``travis.yml`` file.
The script also requires ``pandoc`` to be previously installed in the system.
Requires Python3.6+.
"""
import os
import re
import sys
from pathlib import Path
import github3
import pypandoc
def publish_github_release(slug, token, tag_name, body):
github = github3.login(token=token)
owner, repo = slug.split("/")
repo = github.repository(owner, repo)
return repo.create_release(tag_name=tag_name, body=body)
def parse_changelog(tag_name):
p = Path(__file__).parent.parent / "CHANGELOG.rst"
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
consuming_version = False
version_lines = []
for line in changelog_lines:
m = title_regex.match(line)
if m:
# found the version we want: start to consume lines until we find the next version title
if m.group(1) == tag_name:
consuming_version = True
# found a new version title while parsing the version we want: break out
elif consuming_version:
break
if consuming_version:
version_lines.append(line)
return "\n".join(version_lines)
def convert_rst_to_md(text):
return pypandoc.convert_text(text, "md", format="rst")
def main(argv):
if len(argv) > 1:
tag_name = argv[1]
else:
tag_name = os.environ.get("TRAVIS_TAG")
if not tag_name:
print("tag_name not given and $TRAVIS_TAG not set", file=sys.stderr)
return 1
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
if not token:
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
return 1
slug = os.environ.get("TRAVIS_REPO_SLUG")
if not slug:
print("TRAVIS_REPO_SLUG not set", file=sys.stderr)
return 1
rst_body = parse_changelog(tag_name)
md_body = convert_rst_to_md(rst_body)
if not publish_github_release(slug, token, tag_name, md_body):
print("Could not publish release notes:", file=sys.stderr)
print(md_body, file=sys.stderr)
return 5
print()
print(f"Release notes for {tag_name} published successfully:")
print(f"https://github.com/{slug}/releases/tag/{tag_name}")
print()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@@ -15,7 +15,7 @@ INSTALL_REQUIRES = [
'pathlib2>=2.2.0;python_version<"3.6"',
'colorama;sys_platform=="win32"',
"pluggy>=0.12,<1.0",
"importlib-metadata>=0.12",
'importlib-metadata>=0.12;python_version<"3.8"',
"wcwidth",
]

View File

@@ -572,8 +572,13 @@ class ExceptionInfo(object):
raised.
"""
__tracebackhide__ = True
if not re.search(regexp, str(self.value)):
assert 0, "Pattern '{!s}' not found in '{!s}'".format(regexp, self.value)
value = (
text_type(self.value) if isinstance(regexp, text_type) else str(self.value)
)
if not re.search(regexp, value):
raise AssertionError(
u"Pattern {!r} not found in {!r}".format(regexp, value)
)
return True

View File

@@ -953,8 +953,6 @@ warn_explicit(
"""
visit `ast.Call` nodes on Python3.5 and after
"""
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 = []
@@ -978,27 +976,6 @@ warn_explicit(
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
def _visit_all(self, call):
"""Special rewrite for the builtin all function, see #5062"""
if not isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp)):
return
gen_exp = call.args[0]
assertion_module = ast.Module(
body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)]
)
AssertionRewriter(module_path=None, config=None).run(assertion_module)
for_loop = ast.For(
iter=gen_exp.generators[0].iter,
target=gen_exp.generators[0].target,
body=assertion_module.body,
orelse=[],
)
self.statements.append(for_loop)
return (
ast.Num(n=1),
"",
) # Return an empty expression, all the asserts are in the for_loop
def visit_Starred(self, starred):
# From Python 3.5, a Starred node can appear in a function call
res, expl = self.visit(starred.value)
@@ -1009,8 +986,6 @@ warn_explicit(
"""
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 = []

View File

@@ -12,6 +12,7 @@ import _pytest._code
from ..compat import Sequence
from _pytest import outcomes
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
@@ -374,7 +375,9 @@ def _compare_eq_cls(left, right, verbose, type_fns):
fields_to_check = [field for field, info in all_fields.items() if info.compare]
elif isattrs(left):
all_fields = left.__attrs_attrs__
fields_to_check = [field.name for field in all_fields if field.cmp]
fields_to_check = [
field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD)
]
same = []
diff = []

View File

@@ -21,7 +21,7 @@ import pytest
from .compat import _PY2 as PY2
from .pathlib import Path
from .pathlib import resolve_from_str
from .pathlib import rmtree
from .pathlib import rm_rf
README_CONTENT = u"""\
# pytest cache directory #
@@ -51,7 +51,7 @@ class Cache(object):
def for_config(cls, config):
cachedir = cls.cache_dir_from_config(config)
if config.getoption("cacheclear") and cachedir.exists():
rmtree(cachedir, force=True)
rm_rf(cachedir)
cachedir.mkdir()
return cls(cachedir, config)

View File

@@ -13,6 +13,7 @@ import re
import sys
from contextlib import contextmanager
import attr
import py
import six
from six import text_type
@@ -61,6 +62,12 @@ else:
return None
if sys.version_info >= (3, 8):
from importlib import metadata as importlib_metadata # noqa
else:
import importlib_metadata # noqa
def _format_args(func):
return str(signature(func))
@@ -377,7 +384,7 @@ if _PY3:
else:
def safe_str(v):
"""returns v as string, converting to ascii if necessary"""
"""returns v as string, converting to utf-8 if necessary"""
try:
return str(v)
except UnicodeError:
@@ -406,8 +413,8 @@ def _setup_collect_fakemodule():
pytest.collect = ModuleType("pytest.collect")
pytest.collect.__all__ = [] # used for setns
for attr in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attr, getattr(pytest, attr))
for attribute in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attribute, getattr(pytest, attribute))
if _PY2:
@@ -455,3 +462,9 @@ if six.PY2:
else:
from functools import lru_cache # noqa: F401
if getattr(attr, "__version_info__", ()) >= (19, 2):
ATTRS_EQ_FIELD = "eq"
else:
ATTRS_EQ_FIELD = "cmp"

View File

@@ -13,7 +13,6 @@ import sys
import types
import warnings
import importlib_metadata
import py
import six
from packaging.version import Version
@@ -31,6 +30,7 @@ from .findpaths import exists
from _pytest import deprecated
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
from _pytest.compat import importlib_metadata
from _pytest.compat import lru_cache
from _pytest.compat import safe_str
from _pytest.outcomes import fail
@@ -800,7 +800,7 @@ class Config(object):
str(file)
for dist in importlib_metadata.distributions()
if any(ep.group == "pytest11" for ep in dist.entry_points)
for file in dist.files
for file in dist.files or []
)
for name in _iter_rewritable_modules(package_files):

View File

@@ -33,7 +33,11 @@ def getcfg(args, config=None):
for inibasename in inibasenames:
p = base.join(inibasename)
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
try:
iniconfig = py.iniconfig.IniConfig(p)
except py.iniconfig.ParseError as exc:
raise UsageError(str(exc))
if (
inibasename == "setup.cfg"
and "tool:pytest" in iniconfig.sections

View File

@@ -40,8 +40,8 @@ GETFUNCARGVALUE = RemovedInPytest4Warning(
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"The 'message' parameter is deprecated.\n"
"(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
"if you have concerns about removal of this parameter."
"Please see:\n"
" https://docs.pytest.org/en/4.6-maintenance/deprecations.html#message-parameter-of-pytest-raises"
)
RESULT_LOG = PytestDeprecationWarning(

View File

@@ -8,6 +8,7 @@ import inspect
import platform
import sys
import traceback
import warnings
from contextlib import contextmanager
import pytest
@@ -17,6 +18,7 @@ from _pytest._code.code import TerminalRepr
from _pytest.compat import safe_getattr
from _pytest.fixtures import FixtureRequest
from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestWarning
DOCTEST_REPORT_CHOICE_NONE = "none"
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
@@ -374,10 +376,18 @@ def _patch_unwrap_mock_aware():
else:
def _mock_aware_unwrap(obj, stop=None):
if stop is None:
return real_unwrap(obj, stop=_is_mocked)
else:
try:
if stop is None or stop is _is_mocked:
return real_unwrap(obj, stop=_is_mocked)
return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj))
except Exception as e:
warnings.warn(
"Got %r when unwrapping %r. This is usually caused "
"by a violation of Python's object protocol; see e.g. "
"https://github.com/pytest-dev/pytest/issues/5080" % (e, obj),
PytestWarning,
)
raise
inspect.unwrap = _mock_aware_unwrap
try:

View File

@@ -424,10 +424,6 @@ class LoggingPlugin(object):
"""
self._config = config
# enable verbose output automatically if live logging is enabled
if self._log_cli_enabled() and config.getoption("verbose") < 1:
config.option.verbose = 1
self.print_logs = get_option_ini(config, "log_print")
self.formatter = self._create_formatter(
get_option_ini(config, "log_format"),
@@ -644,6 +640,15 @@ class LoggingPlugin(object):
@pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session):
"""Runs all collected test items."""
if session.config.option.collectonly:
yield
return
if self._log_cli_enabled() and self._config.getoption("verbose") < 1:
# setting verbose flag is needed to avoid messy test progress output
self._config.option.verbose = 1
with self.live_logs_context():
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):

View File

@@ -621,7 +621,13 @@ class Session(nodes.FSCollector):
# Module itself, so just use that. If this special case isn't taken, then all
# the files in the package will be yielded.
if argpath.basename == "__init__.py":
yield next(m[0].collect())
try:
yield next(m[0].collect())
except StopIteration:
# The package collects nothing with only an __init__.py
# file in it, which gets ignored by the default
# "python_files" option.
pass
return
for y in m:
yield y

View File

@@ -8,6 +8,7 @@ import attr
import six
from ..compat import ascii_escaped
from ..compat import ATTRS_EQ_FIELD
from ..compat import getfslineno
from ..compat import MappingMixin
from ..compat import NOTSET
@@ -104,23 +105,24 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
return cls(parameterset, marks=[], id=None)
@staticmethod
def _parse_parametrize_args(argnames, argvalues, **_):
"""It receives an ignored _ (kwargs) argument so this function can
take also calls from parametrize ignoring scope, indirect, and other
arguments..."""
def _parse_parametrize_args(argnames, argvalues, *args, **kwargs):
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
force_tuple = len(argnames) == 1
else:
force_tuple = False
parameters = [
return argnames, force_tuple
@staticmethod
def _parse_parametrize_parameters(argvalues, force_tuple):
return [
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
]
return argnames, parameters
@classmethod
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
argnames, parameters = cls._parse_parametrize_args(argnames, argvalues)
argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
del argvalues
if parameters:
@@ -376,7 +378,8 @@ class NodeKeywords(MappingMixin):
return "<NodeKeywords for node %s>" % (self.node,)
@attr.s(cmp=False, hash=False)
# mypy cannot find this overload, remove when on attrs>=19.2
@attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore
class NodeMarkers(object):
"""
internal structure for storing marks belonging to a node

View File

@@ -329,7 +329,7 @@ class Collector(Node):
# Respect explicit tbstyle option, but default to "short"
# (None._repr_failure_py defaults to "long" without "fulltrace" option).
tbstyle = self.config.getoption("tbstyle")
tbstyle = self.config.getoption("tbstyle", "auto")
if tbstyle == "auto":
tbstyle = "short"

View File

@@ -77,11 +77,7 @@ def create_new_paste(contents):
from urllib.request import urlopen
from urllib.parse import urlencode
params = {
"code": contents,
"lexer": "python3" if sys.version_info[0] == 3 else "python",
"expiry": "1week",
}
params = {"code": contents, "lexer": "text", "expiry": "1week"}
url = "https://bpaste.net"
response = urlopen(url, data=urlencode(params).encode("ascii")).read()
m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8"))

View File

@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import atexit
import errno
import fnmatch
@@ -8,6 +10,8 @@ import os
import shutil
import sys
import uuid
import warnings
from functools import partial
from functools import reduce
from os.path import expanduser
from os.path import expandvars
@@ -19,6 +23,7 @@ import six
from six.moves import map
from .compat import PY36
from _pytest.warning_types import PytestWarning
if PY36:
from pathlib import Path, PurePath
@@ -38,17 +43,57 @@ def ensure_reset_dir(path):
ensures the given path is an empty directory
"""
if path.exists():
rmtree(path, force=True)
rm_rf(path)
path.mkdir()
def rmtree(path, force=False):
if force:
# NOTE: ignore_errors might leave dead folders around.
# Python needs a rm -rf as a followup.
shutil.rmtree(str(path), ignore_errors=True)
else:
shutil.rmtree(str(path))
def on_rm_rf_error(func, path, exc, **kwargs):
"""Handles known read-only errors during rmtree."""
start_path = kwargs["start_path"]
excvalue = exc[1]
if not isinstance(excvalue, OSError) or excvalue.errno not in (
errno.EACCES,
errno.EPERM,
):
warnings.warn(
PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
)
return
if func not in (os.rmdir, os.remove, os.unlink):
warnings.warn(
PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
)
return
# Chmod + retry.
import stat
def chmod_rw(p):
mode = os.stat(p).st_mode
os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR)
# For files, we need to recursively go upwards in the directories to
# ensure they all are also writable.
p = Path(path)
if p.is_file():
for parent in p.parents:
chmod_rw(str(parent))
# stop when we reach the original path passed to rm_rf
if parent == start_path:
break
chmod_rw(str(path))
func(path)
def rm_rf(path):
"""Remove the path contents recursively, even if some elements
are read-only.
"""
onerror = partial(on_rm_rf_error, start_path=path)
shutil.rmtree(str(path), onerror=onerror)
def find_prefixed(root, prefix):
@@ -186,7 +231,7 @@ def maybe_delete_a_numbered_dir(path):
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
path.rename(garbage)
rmtree(garbage, force=True)
rm_rf(garbage)
except (OSError, EnvironmentError):
# known races:
# * other process did a cleanup at the same time

View File

@@ -1124,7 +1124,7 @@ class Testdir(object):
if timeout is None:
ret = popen.wait()
elif six.PY3:
elif not six.PY2:
try:
ret = popen.wait(timeout)
except subprocess.TimeoutExpired:

View File

@@ -29,6 +29,7 @@ class StepwisePlugin:
self.config = config
self.active = config.getvalue("stepwise")
self.session = None
self.report_status = ""
if self.active:
self.lastfailed = config.cache.get("cache/stepwise", None)
@@ -70,15 +71,8 @@ class StepwisePlugin:
config.hook.pytest_deselected(items=already_passed)
def pytest_collectreport(self, report):
if self.active and report.failed:
self.session.shouldstop = (
"Error when collecting test, stopping test execution."
)
def pytest_runtest_logreport(self, report):
# Skip this hook if plugin is not active or the test is xfailed.
if not self.active or "xfail" in report.keywords:
if not self.active:
return
if report.failed:
@@ -104,7 +98,7 @@ class StepwisePlugin:
self.lastfailed = None
def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0:
if self.active and self.config.getoption("verbose") >= 0 and self.report_status:
return "stepwise: %s" % self.report_status
def pytest_sessionfinish(self, session):

View File

@@ -114,6 +114,7 @@ class TestCaseFunction(Function):
def setup(self):
self._testcase = self.parent.obj(self.name)
self._fix_unittest_skip_decorator()
self._obj = getattr(self._testcase, self.name)
if hasattr(self, "_request"):
self._request._fillfixtures()
@@ -132,6 +133,7 @@ class TestCaseFunction(Function):
def teardown(self):
self._testcase = None
self._obj = None
def startTest(self, testcase):
pass

View File

@@ -9,11 +9,11 @@ import textwrap
import types
import attr
import importlib_metadata
import py
import six
import pytest
from _pytest.compat import importlib_metadata
from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.main import EXIT_USAGEERROR
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
@@ -223,7 +223,7 @@ class TestGeneralUsage(object):
"conftest.py:2: in foo",
" import qwerty",
"E {}: No module named {q}qwerty{q}".format(
exc_name, q="'" if six.PY3 else ""
exc_name, q="" if six.PY2 else "'"
),
]
)

View File

@@ -1,6 +1,21 @@
# -*- coding: utf-8 -*-
import sys
import pytest
if sys.gettrace():
@pytest.fixture(autouse=True)
def restore_tracing():
"""Restore tracing function (when run with Coverage.py).
https://bugs.python.org/issue37011
"""
orig_trace = sys.gettrace()
yield
if sys.gettrace() != orig_trace:
sys.settrace(orig_trace)
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(config, items):

View File

@@ -921,15 +921,46 @@ def test_collection_live_logging(testdir):
result = testdir.runpytest("--log-cli-level=INFO")
result.stdout.fnmatch_lines(
[
"collecting*",
"*--- live log collection ---*",
"*Normal message*",
"collected 0 items",
]
["*--- live log collection ---*", "*Normal message*", "collected 0 items"]
)
@pytest.mark.parametrize("verbose", ["", "-q", "-qq"])
def test_collection_collect_only_live_logging(testdir, verbose):
testdir.makepyfile(
"""
def test_simple():
pass
"""
)
result = testdir.runpytest("--collect-only", "--log-cli-level=INFO", verbose)
expected_lines = []
if not verbose:
expected_lines.extend(
[
"*collected 1 item*",
"*<Module test_collection_collect_only_live_logging.py>*",
"*no tests ran*",
]
)
elif verbose == "-q":
assert "collected 1 item*" not in result.stdout.str()
expected_lines.extend(
[
"*test_collection_collect_only_live_logging.py::test_simple*",
"no tests ran in * seconds",
]
)
elif verbose == "-qq":
assert "collected 1 item*" not in result.stdout.str()
expected_lines.extend(["*test_collection_collect_only_live_logging.py: 1*"])
result.stdout.fnmatch_lines(expected_lines)
def test_collection_logging_to_file(testdir):
log_file = testdir.tmpdir.join("pytest.log").strpath

View File

@@ -1765,3 +1765,16 @@ class TestMarkersWithParametrization(object):
result.stdout.fnmatch_lines(
["*test_func_a*0*PASS*", "*test_func_a*2*PASS*", "*test_func_b*10*PASS*"]
)
def test_parametrize_positional_args(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.mark.parametrize("a", [1], False)
def test_foo(a):
pass
"""
)
result = testdir.runpytest()
result.assert_outcomes(passed=1)

View File

@@ -4,6 +4,7 @@ import sys
import six
import pytest
from _pytest.compat import dummy_context_manager
from _pytest.outcomes import Failed
from _pytest.warning_types import PytestDeprecationWarning
@@ -220,7 +221,7 @@ class TestRaises(object):
int("asdf")
msg = "with base 16"
expr = r"Pattern '{}' not found in 'invalid literal for int\(\) with base 10: 'asdf''".format(
expr = r"Pattern '{}' not found in \"invalid literal for int\(\) with base 10: 'asdf'\"".format(
msg
)
with pytest.raises(AssertionError, match=expr):
@@ -278,3 +279,47 @@ class TestRaises(object):
with pytest.raises(CrappyClass()):
pass
assert "via __class__" in excinfo.value.args[0]
class TestUnicodeHandling:
"""Test various combinations of bytes and unicode with pytest.raises (#5478)
https://github.com/pytest-dev/pytest/pull/5479#discussion_r298852433
"""
success = dummy_context_manager
py2_only = pytest.mark.skipif(
not six.PY2, reason="bytes in raises only supported in Python 2"
)
@pytest.mark.parametrize(
"message, match, expectation",
[
(u"\u2603", u"\u2603", success()),
(u"\u2603", u"\u2603foo", pytest.raises(AssertionError)),
pytest.param(b"hello", b"hello", success(), marks=py2_only),
pytest.param(
b"hello", b"world", pytest.raises(AssertionError), marks=py2_only
),
pytest.param(u"hello", b"hello", success(), marks=py2_only),
pytest.param(
u"hello", b"world", pytest.raises(AssertionError), marks=py2_only
),
pytest.param(
u"😊".encode("UTF-8"),
b"world",
pytest.raises(AssertionError),
marks=py2_only,
),
pytest.param(
u"world",
u"😊".encode("UTF-8"),
pytest.raises(AssertionError),
marks=py2_only,
),
],
)
def test_handling(self, message, match, expectation):
with expectation:
with pytest.raises(RuntimeError, match=match):
raise RuntimeError(message)

View File

@@ -14,6 +14,7 @@ import pytest
from _pytest import outcomes
from _pytest.assertion import truncate
from _pytest.assertion import util
from _pytest.compat import ATTRS_EQ_FIELD
PY3 = sys.version_info >= (3, 0)
@@ -179,7 +180,8 @@ class TestImportHookInstallation(object):
return check
""",
"mainwrapper.py": """\
import pytest, importlib_metadata
import pytest
from _pytest.compat import importlib_metadata
class DummyEntryPoint(object):
name = 'spam'
@@ -687,7 +689,7 @@ class TestAssert_reprcompare_attrsclass(object):
@attr.s
class SimpleDataObject(object):
field_a = attr.ib()
field_b = attr.ib(cmp=False)
field_b = attr.ib(**{ATTRS_EQ_FIELD: False})
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "b")

View File

@@ -656,12 +656,6 @@ class TestAssertionRewrite(object):
else:
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
def test_unroll_expression(self):
def f():
assert all(x == 1 for x in range(10))
assert "0 == 1" in getmsg(f)
def test_custom_repr_non_ascii(self):
def f():
class A(object):
@@ -677,53 +671,6 @@ class TestAssertionRewrite(object):
assert "UnicodeDecodeError" not in msg
assert "UnicodeEncodeError" not in msg
def test_unroll_generator(self, testdir):
testdir.makepyfile(
"""
def check_even(num):
if num % 2 == 0:
return True
return False
def test_generator():
odd_list = list(range(1,9,2))
assert all(check_even(num) for num in odd_list)"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
def test_unroll_list_comprehension(self, testdir):
testdir.makepyfile(
"""
def check_even(num):
if num % 2 == 0:
return True
return False
def test_list_comprehension():
odd_list = list(range(1,9,2))
assert all([check_even(num) for num in odd_list])"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
def test_for_loop(self, testdir):
testdir.makepyfile(
"""
def check_even(num):
if num % 2 == 0:
return True
return False
def test_for_loop():
odd_list = list(range(1,9,2))
for num in odd_list:
assert check_even(num)
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
class TestRewriteOnImport(object):
def test_pycache_is_a_file(self, testdir):

View File

@@ -1211,6 +1211,18 @@ def test_collect_pkg_init_and_file_in_args(testdir):
)
def test_collect_pkg_init_only(testdir):
subdir = testdir.mkdir("sub")
init = subdir.ensure("__init__.py")
init.write("def test_init(): pass")
result = testdir.runpytest(str(init))
result.stdout.fnmatch_lines(["*no tests ran in*"])
result = testdir.runpytest("-v", "-o", "python_files=*.py", str(init))
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
@pytest.mark.skipif(
not hasattr(py.path.local, "mksymlinkto"),
reason="symlink not available on this platform",

View File

@@ -6,15 +6,15 @@ from __future__ import print_function
import sys
import textwrap
import importlib_metadata
import _pytest._code
import pytest
from _pytest.compat import importlib_metadata
from _pytest.config import _iter_rewritable_modules
from _pytest.config.exceptions import UsageError
from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import getcfg
from _pytest.main import EXIT_INTERRUPTED
from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.main import EXIT_OK
from _pytest.main import EXIT_TESTSFAILED
@@ -130,6 +130,12 @@ class TestParseIni(object):
config = testdir.parseconfigure(sub)
assert config.getini("minversion") == "2.0"
def test_ini_parse_error(self, testdir):
testdir.tmpdir.join("pytest.ini").write("addopts = -x")
result = testdir.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"])
@pytest.mark.xfail(reason="probably not needed")
def test_confcutdir(self, testdir):
sub = testdir.mkdir("sub")
@@ -586,6 +592,29 @@ def test_setuptools_importerror_issue1479(testdir, monkeypatch):
testdir.parseconfig()
def test_importlib_metadata_broken_distribution(testdir, monkeypatch):
"""Integration test for broken distributions with 'files' metadata being None (#5389)"""
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
class DummyEntryPoint:
name = "mytestplugin"
group = "pytest11"
def load(self):
return object()
class Distribution:
version = "1.0"
files = None
entry_points = (DummyEntryPoint(),)
def distributions():
return (Distribution(),)
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
testdir.parseconfig()
@pytest.mark.parametrize("block_it", [True, False])
def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block_it):
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
@@ -729,10 +758,10 @@ def test_config_in_subdirectory_colon_command_line_issue2148(testdir):
**{
"conftest": conftest_source,
"subdir/conftest": conftest_source,
"subdir/test_foo": """
"subdir/test_foo": """\
def test_foo(pytestconfig):
assert pytestconfig.getini('foo') == 'subdir'
""",
""",
}
)
@@ -765,6 +794,12 @@ def test_notify_exception(testdir, capfd):
assert "ValueError" in err
def test_no_terminal_discovery_error(testdir):
testdir.makepyfile("raise TypeError('oops!')")
result = testdir.runpytest("-p", "no:terminal", "--collect-only")
assert result.ret == EXIT_INTERRUPTED
def test_load_initial_conftest_last_ordering(testdir, _config_for_test):
pm = _config_for_test.pluginmanager

View File

@@ -3,11 +3,14 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import inspect
import sys
import textwrap
import pytest
from _pytest.compat import MODULE_NOT_FOUND_ERROR
from _pytest.doctest import _is_mocked
from _pytest.doctest import _patch_unwrap_mock_aware
from _pytest.doctest import DoctestItem
from _pytest.doctest import DoctestModule
from _pytest.doctest import DoctestTextfile
@@ -1237,3 +1240,25 @@ def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, testdir):
)
result = testdir.runpytest("--doctest-modules")
result.stdout.fnmatch_lines(["* 1 passed *"])
class Broken:
def __getattr__(self, _):
raise KeyError("This should be an AttributeError")
@pytest.mark.skipif(not hasattr(inspect, "unwrap"), reason="nothing to patch")
@pytest.mark.parametrize( # pragma: no branch (lambdas are not called)
"stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True]
)
def test_warning_on_unwrap_of_broken_object(stop):
bad_instance = Broken()
assert inspect.unwrap.__module__ == "inspect"
with _patch_unwrap_mock_aware():
assert inspect.unwrap.__module__ != "inspect"
with pytest.warns(
pytest.PytestWarning, match="^Got KeyError.* when unwrapping"
):
with pytest.raises(KeyError):
inspect.unwrap(bad_instance, stop=stop)
assert inspect.unwrap.__module__ == "inspect"

View File

@@ -3,7 +3,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import importlib_metadata
from _pytest.compat import importlib_metadata
def test_pytest_entry_points_are_identical():

View File

@@ -413,6 +413,28 @@ def test_parametrized_with_kwargs(testdir):
assert result.ret == 0
def test_parametrize_iterator(testdir):
"""parametrize should work with generators (#5354)."""
py_file = testdir.makepyfile(
"""\
import pytest
def gen():
yield 1
yield 2
yield 3
@pytest.mark.parametrize('a', gen())
def test(a):
assert a >= 1
"""
)
result = testdir.runpytest(py_file)
assert result.ret == 0
# should not skip any tests
result.stdout.fnmatch_lines(["*3 passed*"])
class TestFunctional(object):
def test_merging_markers_deep(self, testdir):
# issue 199 - propagate markers into nested classes

View File

@@ -209,7 +209,7 @@ class TestEnvironWarnings(object):
VAR_NAME = u"PYTEST_INTERNAL_MY_VAR"
@pytest.mark.skipif(six.PY3, reason="Python 2 only test")
@pytest.mark.skipif(not six.PY2, reason="Python 2 only test")
def test_setenv_unicode_key(self, monkeypatch):
with pytest.warns(
pytest.PytestWarning,
@@ -217,7 +217,7 @@ class TestEnvironWarnings(object):
):
monkeypatch.setenv(self.VAR_NAME, "2")
@pytest.mark.skipif(six.PY3, reason="Python 2 only test")
@pytest.mark.skipif(not six.PY2, reason="Python 2 only test")
def test_delenv_unicode_key(self, monkeypatch):
with pytest.warns(
pytest.PytestWarning,

View File

@@ -74,7 +74,7 @@ class TestPasteCapture(object):
"""
)
result = testdir.runpytest("--pastebin=all")
if sys.version_info[0] == 3:
if sys.version_info[0] >= 3:
expected_msg = "*assert '' == 1*"
else:
expected_msg = "*assert '\\xe2\\x98\\xba' == 1*"
@@ -126,7 +126,7 @@ class TestPaste(object):
assert len(mocked_urlopen) == 1
url, data = mocked_urlopen[0]
assert type(data) is bytes
lexer = "python3" if sys.version_info[0] == 3 else "python"
lexer = "text"
assert url == "https://bpaste.net"
assert "lexer=%s" % lexer in data.decode()
assert "code=full-paste-contents" in data.decode()

View File

@@ -245,8 +245,8 @@ class TestInlineRunModulesCleanup(object):
):
spy_factory = self.spy_factory()
monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory)
original = dict(sys.modules)
testdir.syspathinsert()
original = dict(sys.modules)
testdir.makepyfile(import1="# you son of a silly person")
testdir.makepyfile(import2="# my hovercraft is full of eels")
test_mod = testdir.makepyfile(

View File

@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
import sys
import pytest
@@ -157,14 +159,66 @@ def test_change_testfile(stepwise_testdir):
assert "test_success PASSED" in stdout
def test_stop_on_collection_errors(broken_testdir):
result = broken_testdir.runpytest(
"-v",
"--strict-markers",
"--stepwise",
"working_testfile.py",
"broken_testfile.py",
@pytest.mark.parametrize("broken_first", [True, False])
def test_stop_on_collection_errors(broken_testdir, broken_first):
"""Stop during collection errors. Broken test first or broken test last
actually surfaced a bug (#5444), so we test both situations."""
files = ["working_testfile.py", "broken_testfile.py"]
if broken_first:
files.reverse()
result = broken_testdir.runpytest("-v", "--strict-markers", "--stepwise", *files)
result.stdout.fnmatch_lines("*errors during collection*")
def test_xfail_handling(testdir):
"""Ensure normal xfail is ignored, and strict xfail interrupts the session in sw mode
(#5547)
"""
contents = """
import pytest
def test_a(): pass
@pytest.mark.xfail(strict={strict})
def test_b(): assert {assert_value}
def test_c(): pass
def test_d(): pass
"""
testdir.makepyfile(contents.format(assert_value="0", strict="False"))
result = testdir.runpytest("--sw", "-v")
result.stdout.fnmatch_lines(
[
"*::test_a PASSED *",
"*::test_b XFAIL *",
"*::test_c PASSED *",
"*::test_d PASSED *",
"* 3 passed, 1 xfailed in *",
]
)
stdout = result.stdout.str()
assert "errors during collection" in stdout
testdir.makepyfile(contents.format(assert_value="1", strict="True"))
result = testdir.runpytest("--sw", "-v")
result.stdout.fnmatch_lines(
[
"*::test_a PASSED *",
"*::test_b FAILED *",
"* Interrupted*",
"* 1 failed, 1 passed in *",
]
)
# because we are writing to the same file, mtime might not be affected enough to
# invalidate the cache, making this next run flaky
if not sys.dont_write_bytecode:
testdir.tmpdir.join("__pycache__").remove()
testdir.makepyfile(contents.format(assert_value="0", strict="True"))
result = testdir.runpytest("--sw", "-v")
result.stdout.fnmatch_lines(
[
"*::test_b XFAIL *",
"*::test_c PASSED *",
"*::test_d PASSED *",
"* 2 passed, 1 deselected, 1 xfailed in *",
]
)

View File

@@ -3,6 +3,9 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import errno
import os
import stat
import sys
import attr
@@ -317,22 +320,6 @@ class TestNumberedDir(object):
p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1
)
def test_rmtree(self, tmp_path):
from _pytest.pathlib import rmtree
adir = tmp_path / "adir"
adir.mkdir()
rmtree(adir)
assert not adir.exists()
adir.mkdir()
afile = adir / "afile"
afile.write_bytes(b"aa")
rmtree(adir, force=True)
assert not adir.exists()
def test_cleanup_ignores_symlink(self, tmp_path):
the_symlink = tmp_path / (self.PREFIX + "current")
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
@@ -345,6 +332,86 @@ class TestNumberedDir(object):
assert folder.is_dir()
class TestRmRf:
def test_rm_rf(self, tmp_path):
from _pytest.pathlib import rm_rf
adir = tmp_path / "adir"
adir.mkdir()
rm_rf(adir)
assert not adir.exists()
adir.mkdir()
afile = adir / "afile"
afile.write_bytes(b"aa")
rm_rf(adir)
assert not adir.exists()
def test_rm_rf_with_read_only_file(self, tmp_path):
"""Ensure rm_rf can remove directories with read-only files in them (#5524)"""
from _pytest.pathlib import rm_rf
fn = tmp_path / "dir/foo.txt"
fn.parent.mkdir()
fn.touch()
self.chmod_r(fn)
rm_rf(fn.parent)
assert not fn.parent.is_dir()
def chmod_r(self, path):
mode = os.stat(str(path)).st_mode
os.chmod(str(path), mode & ~stat.S_IWRITE)
def test_rm_rf_with_read_only_directory(self, tmp_path):
"""Ensure rm_rf can remove read-only directories (#5524)"""
from _pytest.pathlib import rm_rf
adir = tmp_path / "dir"
adir.mkdir()
(adir / "foo.txt").touch()
self.chmod_r(adir)
rm_rf(adir)
assert not adir.is_dir()
def test_on_rm_rf_error(self, tmp_path):
from _pytest.pathlib import on_rm_rf_error
adir = tmp_path / "dir"
adir.mkdir()
fn = adir / "foo.txt"
fn.touch()
self.chmod_r(fn)
# unknown exception
with pytest.warns(pytest.PytestWarning):
exc_info = (None, RuntimeError(), None)
on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path)
assert fn.is_file()
permission_error = OSError()
permission_error.errno = errno.EACCES
# unknown function
with pytest.warns(pytest.PytestWarning):
exc_info = (None, permission_error, None)
on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path)
assert fn.is_file()
exc_info = (None, permission_error, None)
on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path)
assert not fn.is_file()
def attempt_symlink_to(path, to_path):
"""Try to make a symlink from "path" to "to_path", skipping in case this platform
does not support it or we don't have sufficient privileges (common on Windows)."""
@@ -358,3 +425,24 @@ def attempt_symlink_to(path, to_path):
def test_tmpdir_equals_tmp_path(tmpdir, tmp_path):
assert Path(tmpdir) == tmp_path
def test_basetemp_with_read_only_files(testdir):
"""Integration test for #5524"""
testdir.makepyfile(
"""
import os
import stat
def test(tmp_path):
fn = tmp_path / 'foo.txt'
fn.write_text(u'hello')
mode = os.stat(str(fn)).st_mode
os.chmod(str(fn), mode & ~stat.S_IREAD)
"""
)
result = testdir.runpytest("--basetemp=tmp")
assert result.ret == 0
# running a second time and ensure we don't crash
result = testdir.runpytest("--basetemp=tmp")
assert result.ret == 0

View File

@@ -144,6 +144,29 @@ def test_new_instances(testdir):
reprec.assertoutcome(passed=2)
def test_function_item_obj_is_instance(testdir):
"""item.obj should be a bound method on unittest.TestCase function items (#5390)."""
testdir.makeconftest(
"""
def pytest_runtest_makereport(item, call):
if call.when == 'call':
class_ = item.parent.obj
assert isinstance(item.obj.__self__, class_)
"""
)
testdir.makepyfile(
"""
import unittest
class Test(unittest.TestCase):
def test_foo(self):
pass
"""
)
result = testdir.runpytest_inprocess()
result.stdout.fnmatch_lines(["* 1 passed in*"])
def test_teardown(testdir):
testpath = testdir.makepyfile(
"""

View File

@@ -569,7 +569,7 @@ class TestDeprecationWarningsByDefault:
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skipif(six.PY3, reason="Python 2 only issue")
@pytest.mark.skipif(not six.PY2, reason="Python 2 only issue")
def test_infinite_loop_warning_against_unicode_usage_py2(testdir):
"""
We need to be careful when raising the warning about unicode usage with "warnings.warn"

15
tox.ini
View File

@@ -93,7 +93,7 @@ commands =
[testenv:regen]
changedir = doc/en
skipsdist = True
basepython = python3.6
basepython = python3
deps =
sphinx
PyYAML
@@ -125,7 +125,7 @@ commands =
[testenv:release]
decription = do a release, required posarg of the version number
basepython = python3.6
basepython = python3
usedevelop = True
passenv = *
deps =
@@ -136,6 +136,17 @@ deps =
wheel
commands = python scripts/release.py {posargs}
[testenv:publish_gh_release_notes]
description = create GitHub release after deployment
basepython = python3
usedevelop = True
passenv = GH_RELEASE_NOTES_TOKEN TRAVIS_TAG TRAVIS_REPO_SLUG
deps =
github3.py
pypandoc
commands = python scripts/publish_gh_release_notes.py
[pytest]
minversion = 2.0
addopts = -ra -p pytester --strict-markers