Compare commits

...

112 Commits
main ... 4.6.9

Author SHA1 Message Date
Anthony Sottile
f6a5578d5c Preparing release version 4.6.9 2020-01-04 04:40:51 -08:00
Bruno Oliveira
3f94cc9e35 Merge pull request #6392 from hugovk/4.6-maintenance-2020
4.6: Update copyright year to 2020
2020-01-04 07:21:11 -03:00
Hugo
897f1a3ef4 Update copyright year 2020-01-04 09:53:49 +02:00
Hugo
035f51ab71 Update copyright year to 2020 2020-01-03 01:01:32 +02:00
Bruno Oliveira
621028c58d [4.6] Fix assertion rewriting module detection for egg dists (#6368)
[4.6] Fix assertion rewriting module detection for egg dists
2019-12-26 13:37:00 -03:00
Felix Yan
d622f12f69 Fix compatibility for Python 2 2019-12-25 19:44:39 +08:00
Bruno Oliveira
e49282f72c Fix assertion rewriting module detection for egg dists
Fix #6301
2019-12-25 19:43:28 +08:00
Anthony Sottile
197c996345 Merge pull request #6360 from asottile/release-4.6.8
Preparing release version 4.6.8
2019-12-19 15:55:16 -08:00
Anthony Sottile
2d398d8706 Preparing release version 4.6.8 2019-12-19 14:42:09 -08:00
Anthony Sottile
9ab4032f74 Merge pull request #6345 from nightlark/patch-1
Pin the colorama version only for Python 3.4
2019-12-19 14:38:59 -08:00
Ryan Mast
53b08730e4 Pin the colorama version only for Python 3.4 2019-12-14 10:10:16 -03:00
Bruno Oliveira
1deb60f02f Ensure colorama version is no newer than 0.4.1 (#6340)
Ensure colorama version is no newer than 0.4.1
2019-12-13 08:42:59 -03:00
Bruno Oliveira
fb8395d93f Create CHANGELOG for #6340 2019-12-13 07:46:12 -03:00
Ryan Mast
b08c599bad Ensure colorama versions is no newer than 0.4.1 2019-12-12 21:09:21 -08:00
Bruno Oliveira
51fd451dc9 [4.6] Bugfix 5430 pass logs to junit report (#6338)
[4.6] Bugfix 5430 pass logs to junit report
2019-12-12 19:03:58 -03:00
Bruno Oliveira
1d021540a3 Drop 3.4 testing on Azure
Azure no longer supports testing on Python 3.4
2019-12-12 16:47:47 -03:00
Bruno Oliveira
8bfe434f75 Drop validation against multiple xmlfamilies
This was not backported to 4.6 before, so had to adapt the test slightly
to the old method of validation.
2019-12-12 16:44:07 -03:00
Bruno Oliveira
f9ebe3c607 Bugfix 5430 pass logs to junit report (#6274)
Bugfix 5430 pass logs to junit report
2019-12-12 16:39:19 -03:00
Bruno Oliveira
bd54116d03 [4.6] Add hostname and timestamp to JUnit XML testsuite tag (#5… (#6332)
[4.6] Add hostname and timestamp to JUnit XML testsuite tag (#5692)
2019-12-10 18:11:28 -03:00
Bruno Oliveira
8b9482e39c Add hostname and timestamp to JUnit XML testsuite tag (#5692)
Add hostname and timestamp to JUnit XML testsuite tag

Conflicts:
  	testing/test_junitxml.py
2019-12-10 17:27:22 -03:00
Bruno Oliveira
943f4ac236 Release version 4.6.7 (#6318)
Release version 4.6.7
2019-12-05 22:44:59 -03:00
Bruno Oliveira
6f43eee106 Preparing release version 4.6.7 2019-12-05 15:29:25 -05:00
Bruno Oliveira
e1f3c0f9c3 [4.6] Include <testsuites> root tag in generated XML (#5550) (#6295)
[4.6] Include <testsuites> root tag in generated XML (#5550)
2019-11-30 12:13:57 -03:00
Bruno Oliveira
192f6992d2 Include <testsuites> root tag in generated XML (#5550)
Include <testsuites> root tag in generated XML
2019-11-30 11:30:43 -03:00
Daniel Hahler
6465244269 [4.6] Upgrade black (#6209) 2019-11-16 19:25:01 +01:00
Daniel Hahler
097acaf11b re-run black 2019-11-16 19:17:01 +01:00
Daniel Hahler
3d8649b206 Remove (now) unnecessary fmt: off 2019-11-16 19:15:56 +01:00
Daniel Hahler
a8c16d9b75 pre-commit: upgrade black
This brings https://github.com/psf/black/pull/826, which helps with
https://github.com/psf/black/issues/601.
2019-11-16 19:15:54 +01:00
Bruno Oliveira
3edf417969 [4.6] Review rm_rf handling of FileNotFoundErrors (#6050)
[4.6] Review rm_rf handling of FileNotFoundErrors
2019-10-23 20:19:05 -03:00
Bruno Oliveira
0084fd9783 Review rm_rf handling of FileNotFoundErrors (#6044)
Review rm_rf handling of FileNotFoundErrors
Conflicts:
 	 src/_pytest/pathlib.py
  	testing/test_tmpdir.py
2019-10-23 19:36:35 -03:00
Daniel Hahler
e89efa8325 Merge pull request #5957 from blueyed/4.6-comment-off
[4.6] ci: add codecov.yml to turn comments off
2019-10-15 02:14:31 +02:00
Daniel Hahler
3edcc71c41 ci: add codecov.yml to turn comments off
The only benefit for me is to get notified about finished builds, but
that might happen to early anyway.  Apart from that they are rather big
and distract from actual comments.

(cherry picked from commit d50198a3ff)
2019-10-14 22:32:27 +02:00
Bruno Oliveira
866daf57fe Release 4.6.6 (#5947)
Release 4.6.6
2019-10-13 11:55:23 -03:00
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
70 changed files with 1254 additions and 241 deletions

View File

@@ -1,7 +1,7 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/python/black
rev: 19.3b0
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
args: [--safe, --quiet]

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

@@ -58,6 +58,7 @@ Christian Theunert
Christian Tismer
Christopher Gilling
Christopher Dignam
Claudio Madotto
CrazyMerlyn
Cyrus Maden
Damian Skrzypczak
@@ -135,6 +136,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,163 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 4.6.9 (2020-01-04)
=========================
Bug Fixes
---------
- `#6301 <https://github.com/pytest-dev/pytest/issues/6301>`_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``).
pytest 4.6.8 (2019-12-19)
=========================
Features
--------
- `#5471 <https://github.com/pytest-dev/pytest/issues/5471>`_: JUnit XML now includes a timestamp and hostname in the testsuite tag.
Bug Fixes
---------
- `#5430 <https://github.com/pytest-dev/pytest/issues/5430>`_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase.
Trivial/Internal Changes
------------------------
- `#6345 <https://github.com/pytest-dev/pytest/issues/6345>`_: Pin ``colorama`` to ``0.4.1`` only for Python 3.4 so newer Python versions can still receive colorama updates.
pytest 4.6.7 (2019-12-05)
=========================
Bug Fixes
---------
- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element.
- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories,
for instance when multiple processes try to remove the same directory (common with ``pytest-xdist``
for example).
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

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2004-2019 Holger Krekel and others
Copyright (c) 2004-2020 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -131,7 +131,7 @@ Tidelift will coordinate the fix and disclosure.
License
-------
Copyright Holger Krekel and others, 2004-2019.
Copyright Holger Krekel and others, 2004-2020.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@@ -48,12 +48,6 @@ jobs:
# pypy3:
# python.version: 'pypy3'
# tox.env: 'pypy3'
py34-xdist:
python.version: '3.4'
tox.env: 'py34-xdist'
# Coverage for:
# - _pytest.compat._bytes_to_ascii
PYTEST_COVERAGE: '1'
py35-xdist:
python.version: '3.5'
tox.env: 'py35-xdist'
@@ -91,7 +85,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: |

7
codecov.yml Normal file
View File

@@ -0,0 +1,7 @@
coverage:
status:
project: true
patch: true
changes: true
comment: off

View File

@@ -6,6 +6,15 @@ Release announcements
:maxdepth: 2
release-4.6.9
release-4.6.8
release-4.6.7
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

@@ -0,0 +1,19 @@
pytest-4.6.7
=======================================
pytest 4.6.7 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:
* Bruno Oliveira
* Daniel Hahler
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,20 @@
pytest-4.6.8
=======================================
pytest 4.6.8 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
* Ryan Mast
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,21 @@
pytest-4.6.9
=======================================
pytest 4.6.9 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
* Felix Yan
* Hugo
Happy testing,
The pytest Development Team

View File

@@ -65,7 +65,7 @@ master_doc = "contents"
# General information about the project.
project = u"pytest"
year = datetime.datetime.utcnow().year
copyright = u"20152019 , holger krekel and pytest-dev team"
copyright = u"20152020, holger krekel and pytest-dev team"
# The language for content autogenerated by Sphinx. Refer to documentation
@@ -275,7 +275,7 @@ man_pages = [("usage", "pytest", u"pytest usage", [u"holger krekel at merlinux e
epub_title = u"pytest"
epub_author = u"holger krekel at merlinux eu"
epub_publisher = u"holger krekel at merlinux eu"
epub_copyright = u"2013, holger krekel et alii"
epub_copyright = u"2013-2020, holger krekel et alii"
# The language of the text. It defaults to the language option
# or en if the language is not set.

View File

@@ -434,9 +434,9 @@ 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%]
......sss......ssssssssssss [100%]
========================= short test summary info ==========================
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.5' not found
12 passed, 15 skipped in 0.12 seconds
Indirect parametrization of optional implementations/imports

View File

@@ -87,7 +87,7 @@ Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each
License
-------
Copyright Holger Krekel and others, 2004-2017.
Copyright Holger Krekel and others, 2004-2020.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc
The MIT License (MIT)
Copyright (c) 2004-2017 Holger Krekel and others
Copyright (c) 2004-2020 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

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

@@ -13,9 +13,10 @@ INSTALL_REQUIRES = [
"atomicwrites>=1.0",
'funcsigs>=1.0;python_version<"3.0"',
'pathlib2>=2.2.0;python_version<"3.6"',
'colorama;sys_platform=="win32"',
'colorama<=0.4.1;sys_platform=="win32" and python_version=="3.4"',
'colorama;sys_platform=="win32" and python_version!="3.4"',
"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
@@ -116,13 +116,13 @@ def directory_arg(path, optname):
# Plugins that cannot be disabled via "-p no:X" currently.
essential_plugins = ( # fmt: off
essential_plugins = (
"mark",
"main",
"runner",
"fixtures",
"helpconfig", # Provides -p.
) # fmt: on
)
default_plugins = essential_plugins + (
"python",
@@ -622,16 +622,68 @@ notset = Notset()
def _iter_rewritable_modules(package_files):
"""
Given an iterable of file names in a source distribution, return the "names" that should
be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should
be added as "pytest_mock" in the assertion rewrite mechanism.
This function has to deal with dist-info based distributions and egg based distributions
(which are still very much in use for "editable" installs).
Here are the file names as seen in a dist-info based distribution:
pytest_mock/__init__.py
pytest_mock/_version.py
pytest_mock/plugin.py
pytest_mock.egg-info/PKG-INFO
Here are the file names as seen in an egg based distribution:
src/pytest_mock/__init__.py
src/pytest_mock/_version.py
src/pytest_mock/plugin.py
src/pytest_mock.egg-info/PKG-INFO
LICENSE
setup.py
We have to take in account those two distribution flavors in order to determine which
names should be considered for assertion rewriting.
More information:
https://github.com/pytest-dev/pytest-mock/issues/167
"""
package_files = list(package_files)
seen_some = False
for fn in package_files:
is_simple_module = "/" not in fn and fn.endswith(".py")
is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
if is_simple_module:
module_name, _ = os.path.splitext(fn)
yield module_name
# we ignore "setup.py" at the root of the distribution
if module_name != "setup":
seen_some = True
yield module_name
elif is_package:
package_name = os.path.dirname(fn)
seen_some = True
yield package_name
if not seen_some:
# at this point we did not find any packages or modules suitable for assertion
# rewriting, so we try again by stripping the first path component (to account for
# "src" based source trees for example)
# this approach lets us have the common case continue to be fast, as egg-distributions
# are rarer
new_package_files = []
for fn in package_files:
parts = fn.split("/")
new_fn = "/".join(parts[1:])
if new_fn:
new_package_files.append(new_fn)
if new_package_files:
for _module in _iter_rewritable_modules(new_package_files):
yield _module
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
@@ -800,7 +852,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

@@ -15,9 +15,11 @@ from __future__ import print_function
import functools
import os
import platform
import re
import sys
import time
from datetime import datetime
import py
import six
@@ -595,6 +597,8 @@ class LogXML(object):
if report.when == "call":
reporter.append_failure(report)
self.open_reports.append(report)
if not self.log_passing_tests:
reporter.write_captured_output(report)
else:
reporter.append_error(report)
elif report.skipped:
@@ -667,18 +671,19 @@ class LogXML(object):
)
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write(
Junit.testsuite(
self._get_global_properties_node(),
[x.to_xml() for x in self.node_reporters_ordered],
name=self.suite_name,
errors=self.stats["error"],
failures=self.stats["failure"],
skipped=self.stats["skipped"],
tests=numtests,
time="%.3f" % suite_time_delta,
).unicode(indent=0)
suite_node = Junit.testsuite(
self._get_global_properties_node(),
[x.to_xml() for x in self.node_reporters_ordered],
name=self.suite_name,
errors=self.stats["error"],
failures=self.stats["failure"],
skipped=self.stats["skipped"],
tests=numtests,
time="%.3f" % suite_time_delta,
timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(),
hostname=platform.node(),
)
logfile.write(Junit.testsuites([suite_node]).unicode(indent=0))
logfile.close()
def pytest_terminal_summary(self, terminalreporter):

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,72 @@ 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.
The returned value is used only by our own tests.
"""
start_path = kwargs["start_path"]
exctype, excvalue = exc[:2]
# another process removed the file in the middle of the "rm_rf" (xdist for example)
# more context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
if isinstance(excvalue, OSError) and excvalue.errno == errno.ENOENT:
return False
if not isinstance(excvalue, OSError) or excvalue.errno not in (
errno.EACCES,
errno.EPERM,
):
warnings.warn(
PytestWarning(
"(rm_rf) error removing {}\n{}: {}".format(path, exctype, excvalue)
)
)
return False
if func not in (os.rmdir, os.remove, os.unlink):
warnings.warn(
PytestWarning(
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
path, func, exctype, excvalue
)
)
)
return False
# 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)
return True
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 +246,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

@@ -694,7 +694,7 @@ def raises(expected_exception, *args, **kwargs):
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
code, = args
(code,) = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()

View File

@@ -95,7 +95,7 @@ def warns(expected_warning, *args, **kwargs):
return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str):
warnings.warn(WARNS_EXEC, stacklevel=2)
code, = args
(code,) = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()

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

@@ -152,7 +152,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest(
testdir
testdir,
):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@@ -181,7 +181,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conft
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives(
testdir
testdir,
):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST

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

@@ -464,7 +464,7 @@ class TestRequestBasic(object):
assert repr(req).find(req.function.__name__) != -1
def test_request_attributes_method(self, testdir):
item, = testdir.getitems(
(item,) = testdir.getitems(
"""
import pytest
class TestB(object):
@@ -492,7 +492,7 @@ class TestRequestBasic(object):
pass
"""
)
item1, = testdir.genitems([modcol])
(item1,) = testdir.genitems([modcol])
assert item1.name == "test_method"
arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs
assert len(arg2fixturedefs) == 1
@@ -756,7 +756,7 @@ class TestRequestBasic(object):
def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass")
item, = testdir.genitems([modcol])
(item,) = testdir.genitems([modcol])
req = fixtures.FixtureRequest(item)
assert req.fspath == modcol.fspath

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

@@ -494,7 +494,7 @@ class TestSession(object):
p = testdir.makepyfile("def test_func(): pass")
id = "::".join([p.basename, "test_func"])
items, hookrec = testdir.inline_genitems(id)
item, = items
(item,) = items
assert item.name == "test_func"
newid = item.nodeid
assert newid == id
@@ -613,9 +613,9 @@ class TestSession(object):
testdir.makepyfile("def test_func(): pass")
items, hookrec = testdir.inline_genitems()
assert len(items) == 1
item, = items
(item,) = items
items2, hookrec = testdir.inline_genitems(item.nodeid)
item2, = items2
(item2,) = items2
assert item2.name == item.name
assert item2.fspath == item.fspath
@@ -630,7 +630,7 @@ class TestSession(object):
arg = p.basename + "::TestClass::test_method"
items, hookrec = testdir.inline_genitems(arg)
assert len(items) == 1
item, = items
(item,) = items
assert item.nodeid.endswith("TestClass::test_method")
# ensure we are reporting the collection of the single test item (#2464)
assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"]
@@ -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")
@@ -425,15 +431,21 @@ class TestConfigAPI(object):
@pytest.mark.parametrize(
"names, expected",
[
# dist-info based distributions root are files as will be put in PYTHONPATH
(["bar.py"], ["bar"]),
(["foo", "bar.py"], []),
(["foo", "bar.pyc"], []),
(["foo", "__init__.py"], ["foo"]),
(["foo", "bar", "__init__.py"], []),
(["foo/bar.py"], ["bar"]),
(["foo/bar.pyc"], []),
(["foo/__init__.py"], ["foo"]),
(["bar/__init__.py", "xz.py"], ["bar", "xz"]),
(["setup.py"], []),
# egg based distributions root contain the files from the dist root
(["src/bar/__init__.py"], ["bar"]),
(["src/bar/__init__.py", "setup.py"], ["bar"]),
(["source/python/bar/__init__.py", "setup.py"], ["bar"]),
],
)
def test_iter_rewritable_modules(self, names, expected):
assert list(_iter_rewritable_modules(["/".join(names)])) == expected
assert list(_iter_rewritable_modules(names)) == expected
class TestConfigFromdictargs(object):
@@ -586,6 +598,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 +764,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 +800,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

@@ -4,7 +4,9 @@ from __future__ import division
from __future__ import print_function
import os
import platform
import sys
from datetime import datetime
from xml.dom import minidom
import py
@@ -47,6 +49,16 @@ class DomNode(object):
def _by_tag(self, tag):
return self.__node.getElementsByTagName(tag)
@property
def children(self):
return [type(self)(x) for x in self.__node.childNodes]
@property
def get_unique_child(self):
children = self.children
assert len(children) == 1
return children[0]
def find_nth_by_tag(self, tag, n):
items = self._by_tag(tag)
try:
@@ -81,7 +93,7 @@ class DomNode(object):
return self.__node.tagName
@property
def next_siebling(self):
def next_sibling(self):
return type(self)(self.__node.nextSibling)
@@ -135,6 +147,30 @@ class TestPython(object):
node = dom.find_first_by_tag("testsuite")
node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5)
def test_hostname_in_xml(self, testdir):
testdir.makepyfile(
"""
def test_pass():
pass
"""
)
result, dom = runandparse(testdir)
node = dom.find_first_by_tag("testsuite")
node.assert_attr(hostname=platform.node())
def test_timestamp_in_xml(self, testdir):
testdir.makepyfile(
"""
def test_pass():
pass
"""
)
start_time = datetime.now()
result, dom = runandparse(testdir)
node = dom.find_first_by_tag("testsuite")
timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
assert start_time <= timestamp < datetime.now()
def test_timing_function(self, testdir):
testdir.makepyfile(
"""
@@ -390,11 +426,11 @@ class TestPython(object):
fnode = tnode.find_first_by_tag("failure")
fnode.assert_attr(message="ValueError: 42")
assert "ValueError" in fnode.toxml()
systemout = fnode.next_siebling
systemout = fnode.next_sibling
assert systemout.tag == "system-out"
assert "hello-stdout" in systemout.toxml()
assert "info msg" not in systemout.toxml()
systemerr = systemout.next_siebling
systemerr = systemout.next_sibling
assert systemerr.tag == "system-err"
assert "hello-stderr" in systemerr.toxml()
assert "info msg" not in systemerr.toxml()
@@ -1101,6 +1137,20 @@ def test_random_report_log_xdist(testdir, monkeypatch):
assert failed == ["test_x[22]"]
def test_root_testsuites_tag(testdir):
testdir.makepyfile(
"""
def test_x():
pass
"""
)
_, dom = runandparse(testdir)
root = dom.get_unique_child
assert root.tag == "testsuites"
suite_node = root.get_unique_child
assert suite_node.tag == "testsuite"
def test_runs_twice(testdir):
f = testdir.makepyfile(
"""
@@ -1359,3 +1409,39 @@ def test_logging_passing_tests_disabled_does_not_log_test_output(testdir):
node = dom.find_first_by_tag("testcase")
assert len(node.find_by_tag("system-err")) == 0
assert len(node.find_by_tag("system-out")) == 0
@pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"])
def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430(
testdir, junit_logging
):
testdir.makeini(
"""
[pytest]
junit_log_passing_tests=False
"""
)
testdir.makepyfile(
"""
import pytest
import logging
import sys
def test_func():
logging.warning('hello')
assert 0
"""
)
result, dom = runandparse(testdir, "-o", "junit_logging=%s" % junit_logging)
assert result.ret == 1
node = dom.find_first_by_tag("testcase")
if junit_logging == "system-out":
assert len(node.find_by_tag("system-err")) == 0
assert len(node.find_by_tag("system-out")) == 1
elif junit_logging == "system-err":
assert len(node.find_by_tag("system-err")) == 1
assert len(node.find_by_tag("system-out")) == 0
else:
assert junit_logging == "no"
assert len(node.find_by_tag("system-err")) == 0
assert len(node.find_by_tag("system-out")) == 0

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
@@ -986,7 +1008,7 @@ def test_markers_from_parametrize(testdir):
def test_pytest_param_id_requires_string():
with pytest.raises(TypeError) as excinfo:
pytest.param(id=True)
msg, = excinfo.value.args
(msg,) = excinfo.value.args
if six.PY2:
assert msg == "Expected id to be a string, got <type 'bool'>: True"
else:
@@ -1003,7 +1025,7 @@ def test_pytest_param_warning_on_unknown_kwargs():
# typo, should be marks=
pytest.param(1, 2, mark=pytest.mark.xfail())
assert warninfo[0].filename == __file__
msg, = warninfo[0].message.args
(msg,) = warninfo[0].message.args
assert msg == (
"pytest.param() got unexpected keyword arguments: ['mark'].\n"
"This will be an error in future versions."

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

@@ -225,7 +225,7 @@ class TestWarns(object):
assert len(warninfo) == 3
for w in warninfo:
assert w.filename == __file__
msg, = w.message.args
(msg,) = w.message.args
assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated")
def test_function(self):

View File

@@ -136,7 +136,7 @@ class TestEvaluator(object):
)
def test_skipif_class(self, testdir):
item, = testdir.getitems(
(item,) = testdir.getitems(
"""
import pytest
class TestClass(object):

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
@@ -270,7 +273,7 @@ class TestNumberedDir(object):
registry = []
register_cleanup_lock_removal(lock, register=registry.append)
cleanup_func, = registry
(cleanup_func,) = registry
assert lock.is_file()
@@ -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,91 @@ 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()
# we ignore FileNotFoundError
file_not_found = OSError()
file_not_found.errno = errno.ENOENT
exc_info = (None, file_not_found, None)
assert not on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path)
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 +430,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(
"""
@@ -365,7 +388,7 @@ def test_testcase_custom_exception_info(testdir, type):
def test_testcase_totally_incompatible_exception_info(testdir):
item, = testdir.getitems(
(item,) = testdir.getitems(
"""
from unittest import TestCase
class MyTestCase(TestCase):

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