Compare commits

..

251 Commits

Author SHA1 Message Date
Ran Benita
f0bf4c9681 Merge pull request #9760 from bluetech/cherry-pick-release
Merge pull request #9758 from pytest-dev/release-7.1.0
2022-03-13 17:20:13 +02:00
Ran Benita
d87e1e67dd Merge pull request #9758 from pytest-dev/release-7.1.0
Prepare release 7.1.0

(cherry picked from commit 7d4d1ecde6)
2022-03-13 16:59:20 +02:00
Florian Bruhin
d306ec0a7e Update upcoming trainings (#9744)
* Update upcoming trainings

* Remove old-ish link

* Revert "Remove old-ish link"

This reverts commit 30d75f5088.

* Use crossref

* Add past training
2022-03-12 16:51:56 +01:00
Bruno Oliveira
3e4c14bfaa Merge pull request #9751 from fabianegli/main 2022-03-11 09:53:30 -03:00
Fabian Egli
7f924b13a5 Fix typo in deprecation documentation 2022-03-11 11:15:28 +01:00
dependabot[bot]
4a8f8ada43 build(deps): Bump django from 4.0.2 to 4.0.3 in /testing/plugins_integration (#9737)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 13:50:25 +00:00
dependabot[bot]
c0fd2d8839 build(deps): Bump pytest-asyncio from 0.18.1 to 0.18.2 in /testing/plugins_integration (#9736)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 10:29:39 -03:00
Anthony Sottile
843e01824c Merge pull request #9732 from nicoddemus/9730-toml-failure
Improve error message for malformed pyproject.toml files
2022-03-06 10:01:13 -05:00
github-actions[bot]
bc43d66b47 [automated] Update plugin list (#9733)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-03-06 00:01:38 -03:00
Bruno Oliveira
e38d1cac48 Improve error message for malformed pyproject.toml files
Including the file name is enough to let the user know what the problem is.

The same is not needed for `.ini` files because the error message includes the path to the file by default.

Fix #9730
2022-03-05 08:59:28 -03:00
github-actions[bot]
cf0a4f79b0 [automated] Update plugin list (#9717)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-03-04 12:56:11 -03:00
Anthony Sottile
51b86b4dc4 Merge pull request #9714 from nicoddemus/pytester-request-monkeypatch-9708
pytester now requests monkeypatch instead of creating its own instance
2022-03-04 09:22:56 -05:00
Bruno Oliveira
f943d1944a pytester now requests monkeypatch instead of creating its own instance
It is tempting to use `monkeypatch` to replace the other mechanisms in pytester which change global
state: `CwdSnapshot`, `SysModulesSnapshot`, `SysPathsSnapshot`, however those are more delicate
than they look at first glance so leaving those alone for now.

Close #9708
2022-03-04 08:58:44 -05:00
Anthony Sottile
9318b2cb7f Merge pull request #9719 from asottile/fix-setuptools-pollution
prevent import-time side-effects from setuptools bundled importlib-metadata
2022-03-03 09:01:41 -05:00
Anthony Sottile
269611e0b4 prevent import-time side-effects from setuptools bundled importlib-metadata 2022-02-28 19:35:36 -05:00
Kojo Idrissa
e466a87bb0 Add the latest book edition of 'Python Testing with pytest' 2022-02-25 19:13:23 -03:00
Bruno Oliveira
5f3d94c47e Disallow unordered sequences in pytest.approx (#9709)
Fix #9692
2022-02-24 10:16:35 -03:00
Bruno Oliveira
bcc826d0fb Fix numbering and improve changelog entry for #9362 (#9713) 2022-02-23 19:03:13 -03:00
Ran Benita
4778e999a5 Merge pull request #9702 from m-tmatma/feature/fix-bash-complete
fix link of argcomplete page.
2022-02-23 19:30:40 +02:00
Ran Benita
7b6e477e2f Revert "Deprecate raising unittest.SkipTest to skip tests during collection" (#9710)
This reverts commit 25e657bfc1.

Turns out that this *is* a working unittest feature, which pytest should
support, so undo the deprecation.
2022-02-23 15:59:54 +02:00
github-actions[bot]
0c80a1c836 [automated] Update plugin list (#9701)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-02-23 08:26:18 -03:00
Masaru Tsuchiyama
7f50c521b6 fix link of argcomplete page. 2022-02-20 17:29:12 +09:00
Bruno Oliveira
9af3e23695 Expand warnings output for ResourceWarning (#9682)
Fix #9644
2022-02-15 13:00:50 +00:00
Ran Benita
bdbad91493 Merge pull request #9546 from bluetech/fixturedef-attr-doc
fixtures: document FixtureDef's attributes
2022-02-15 14:52:50 +02:00
Bruno Oliveira
fac8f284cd Fix diff output for data types where -v would show less information (#9661)
Close #5192
2022-02-15 09:43:20 -03:00
Ran Benita
afe41e5273 Merge pull request #9678 from bluetech/consistent-idval
python: unify code to generate ID from value
2022-02-15 13:23:35 +02:00
eduardo naufel schettino
c9cf2d4424 Fix count of selected tests on terminal collection summary (#9628) 2022-02-15 11:17:34 +00:00
Bruno Oliveira
f22451717d Merge pull request #9681 from nicoddemus/fix-9645-import-lib 2022-02-14 12:00:29 -03:00
Bruno Oliveira
04cf8db1d7 Merge pull request #9679 from pytest-dev/update-plugin-list/patch-c01a5c177
[automated] Update plugin list
2022-02-14 09:43:59 -03:00
Bruno Oliveira
747b8372ea Try to import module before creating dummy modules with 'importmode=importlib'
The dummy modules we introduce in `insert_missing_modules` (due to #7856 and #7859)
would cause problems if the dummy modules actually end up replacing modules
which could be imported normally because they are available in `PYTHONPATH`.

Now we attempt to first import the module via normal mechanisms, and only
introduce the dummy modules if the intermediary modules don't actually exist.

Close #9645
2022-02-14 09:42:05 -03:00
Andrew Svetlov
fc72ffa39e Merge pull request #9683 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-asyncio-0.18.1
build(deps): Bump pytest-asyncio from 0.17.2 to 0.18.1 in /testing/plugins_integration
2022-02-14 09:10:41 +02:00
dependabot[bot]
3e53307586 build(deps): Bump pytest-asyncio in /testing/plugins_integration
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.17.2 to 0.18.1.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.17.2...v0.18.1)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 03:02:17 +00:00
pytest bot
295e7535c9 [automated] Update plugin list 2022-02-13 00:15:13 +00:00
Ran Benita
c3aa4647c7 python: unify code to generate ID from value
In the following

    @pytest.mark.parametrize(..., ids=[val])

the ID values are only allowed to be `str`, `float`, `int` or `bool`.

In the following

    @pytest.mark.parametrize(..., [val])

    @pytest.mark.parametrize(..., [pytest.param(..., id=val])

a different code path is used, which also allows `bytes`, `complex`,
`re.Pattern`, `Enum` and anything with a `__name__`.

In the interest of consistency, use the latter code path for all cases.
2022-02-12 19:22:37 +02:00
Bruno Oliveira
c01a5c177b Merge pull request #9676 from nicoddemus/cherry-pick-release 2022-02-11 16:09:42 -03:00
Bruno Oliveira
68be319165 Merge pull request #9674 from pytest-dev/release-7.0.1
(cherry picked from commit 18d35b817d)
2022-02-11 15:52:00 -03:00
Bruno Oliveira
8afec9a64d Add autoflake to pre-commit configuration (#9666) 2022-02-11 14:34:02 -03:00
Bruno Oliveira
b79eff065e Enable testing with Python 3.11 (#9511) 2022-02-11 15:20:42 +00:00
Bruno Oliveira
6828ec2f9b Merge pull request #9669 from hugovk/ci-only-update-plugin-list-for-upstream 2022-02-11 09:24:09 -03:00
Bruno Oliveira
2a5bb3b4e0 Merge pull request #9668 from hugovk/test-me-latest-3.10 2022-02-11 09:15:14 -03:00
Hugo van Kemenade
eb8a3ef849 Only update the plugin for upstream 2022-02-11 14:03:22 +02:00
Hugo van Kemenade
be0c41bf8e CI: Test latest 3.10 available 2022-02-11 13:49:11 +02:00
Bruno Oliveira
9e922c4325 Merge pull request #9662 from pytest-dev/dependabot/pip/testing/plugins_integration/twisted-22.1.0 2022-02-11 07:28:32 -03:00
dependabot[bot]
2b12739412 build(deps): Bump twisted in /testing/plugins_integration
Bumps [twisted](https://github.com/twisted/twisted) from 21.7.0 to 22.1.0.
- [Release notes](https://github.com/twisted/twisted/releases)
- [Changelog](https://github.com/twisted/twisted/blob/trunk/NEWS.rst)
- [Commits](https://github.com/twisted/twisted/compare/twisted-21.7.0...twisted-22.1.0)

---
updated-dependencies:
- dependency-name: twisted
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-11 03:47:43 +00:00
Bruno Oliveira
3586edc1c9 Merge pull request #9646 from nicoddemus/9643-delay-warning 2022-02-10 14:11:21 -03:00
Bruno Oliveira
88c35123b8 Delay warning about collector/item diamond inheritance
This allows that warning to be filtered by `filterwarnings`.

Fix #9643
2022-02-10 13:50:25 -03:00
Florian Bruhin
6aaa017b1e doc: Discourage squashing for release (#9657)
Fixes #9655
2022-02-10 08:58:20 +01:00
Ran Benita
2b6708b892 Merge pull request #9624 from bluetech/unittest-getobj
unittest: restore `UnitTestFunction.obj` to return unbound rather than bound method
2022-02-09 22:43:31 +02:00
Ran Benita
23bdf78654 Merge pull request #9651 from bluetech/rename-pythonpath
Rename ``pythonpath`` plugin to ``python_path``
2022-02-09 13:20:22 +02:00
Ran Benita
01e1de7a1a Rename `pythonpath plugin to python_path`
Fix #9636.
2022-02-09 10:03:20 +02:00
Ran Benita
04a6f52d67 Merge pull request #9611 from pllim/patch-1
DOC: pytest.warns() fails on zero warning
2022-02-08 16:00:06 +02:00
Ran Benita
4bf764f9a3 Merge pull request #9625 from pytest-dev/dependabot/pip/testing/plugins_integration/django-4.0.2
build(deps): Bump django from 4.0.1 to 4.0.2 in /testing/plugins_integration
2022-02-08 15:59:19 +02:00
Ran Benita
44290d1f6c Merge pull request #9609 from kdelee/patch-1
importlib.readers not valid until python 3.10
2022-02-08 15:57:48 +02:00
Ran Benita
fb378ea269 Merge pull request #9639 from bluetech/docs-django-https
doc: remove django intersphinx mapping
2022-02-08 15:57:06 +02:00
Anthony Sottile
c326c04494 Merge pull request #9642 from pytest-dev/run-testid-with-colon-colon
allow running testids which contain :: in the parametrized portion
2022-02-08 08:34:49 -05:00
Anthony Sottile
efa16c2c9d Merge pull request #9641 from asottile/fix-test-pollution-assertrewrite
fix test pollution in test_assertrewrite
2022-02-08 08:34:12 -05:00
Anthony Sottile
2442034a1e allow running testids which contain :: in the parametrized portion 2022-02-07 20:19:59 -05:00
Anthony Sottile
579785b6cd fix test pollution in test_assertrewrite
originally reproduced with this pollution set:

```
testing/test_assertrewrite.py::TestEarlyRewriteBailout::test_pattern_contains_subdirectories
testing/test_assertrewrite.py::TestRewriteOnImport::test_remember_rewritten_modules
```
2022-02-07 19:29:18 -05:00
Ran Benita
b80472c5bc doc: fix a reference 2022-02-08 00:53:17 +02:00
dependabot[bot]
ef7d67b665 build(deps): Bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.0.1...4.0.2)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 22:47:20 +00:00
Ran Benita
1612d3d1af doc: remove django intersphinx mapping
Initially I just wanted to fix http -> https, but I think it's not worth
having at all just for this one reference.
2022-02-08 00:46:20 +02:00
Ran Benita
d00ca3f8e5 unittest: restore UnitTestFunction.obj to return unbound rather than bound method
This fixes #9610.

pytest 7.0.0 (unintentionally) changed `UnitTestFunction.obj`'s' behavior
to match `Function.obj`. That is probably a good thing to have, however
it evidently causes some regressions as described in the issue, so
restore the previous behavior for now. In the future we might want to
make this change again, but with proper consideration.
2022-02-08 00:46:00 +02:00
pre-commit-ci[bot]
2073cce105 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-02-08 00:44:52 +02:00
Elijah DeLee
d45a19cfde move function specific imports to function
re: review from @asottile that this should only get imported in the function
modify the else/if logic since inside the function we already know the python version is >= 3.10, and just have to know if it is 3.11 or greater
2022-02-08 00:44:52 +02:00
Bruno Oliveira
f86a87a315 Update src/_pytest/assertion/rewrite.py
Co-authored-by: Ran Benita <ran@unusedvar.com>
2022-02-08 00:44:52 +02:00
Bruno Oliveira
66dc79efd4 Update changelog/9608.bugfix.rst
Co-authored-by: Elijah DeLee <kdelee@redhat.com>
2022-02-08 00:44:52 +02:00
Bruno Oliveira
5f1a2f33da Fix invalid importing of importlib.reader in Python 3.9
Fix #9608
2022-02-08 00:44:52 +02:00
Elijah DeLee
15ddccf700 importlib.readers not valid until python 3.10
This exists https://github.com/python/cpython/blob/3.10/Lib/importlib/readers.py and FileReader is in there
This is a 404 https://github.com/python/cpython/blob/3.9/Lib/importlib/readers.py

This change needs to get backported to the 7.0.z branch(s) too
Fixes https://github.com/pytest-dev/pytest/issues/9608
2022-02-08 00:44:52 +02:00
Anthony Sottile
bc33ba0be9 Merge pull request #9638 from asottile/fix-test-pollution
work around test pollution caused by new setuptools mutating global logger level
2022-02-07 17:19:01 -05:00
Anthony Sottile
b3692fe404 work around test pollution caused by new setuptools mutating global logger level 2022-02-07 15:10:03 -05:00
Ran Benita
6f936aa97c Merge pull request #9622 from pytest-dev/update-plugin-list/patch-18c0cfc0d
[automated] Update plugin list
2022-02-07 15:09:58 +02:00
pytest bot
2c3be75b09 [automated] Update plugin list 2022-02-06 00:13:55 +00:00
Ran Benita
18c0cfc0de Merge pull request #9612 from bluetech/namedtuple-class
mark/structures: use class-based NamedTuple syntax
2022-02-05 12:16:00 +02:00
Ran Benita
20c2c30ff2 mark/structures: use class-based NamedTuple syntax
Should hopefully work now in Python>=3.7.
2022-02-05 11:52:36 +02:00
P. L. Lim
d1b394af88 DOC: pytest.warns() fails on zero warning 2022-02-04 17:45:04 -05:00
Florian Bruhin
77a38a3b75 doc: Add changing RTD version to checklist (#9606)
I still think it makes sense to always serve docs from the release branch (rather than the latest tagged release), but that means we need to update it on new releases.
2022-02-04 17:36:31 +01:00
Gabor Szabo
57f8f5d2b3 Mention basetemp in docs as a way to only keep last temp dir (#9607)
Closes #9599
2022-02-04 12:38:26 +00:00
Florian Bruhin
b5a168aa0e doc: Streamline remote handling in releasing docs (#9603)
The docs already assume an 'upstream' remote, so we can only fetch from there instead of fetching all remotes. We also don't need to hardcode the remote URL.
2022-02-04 12:13:33 +01:00
Florian Bruhin
48f01bdcc2 doc: Hide done training (#9602)
Only commenting out as I expect to add trainings at PyConDE and possibly PyConIT soon
2022-02-04 12:12:58 +01:00
Florian Bruhin
dff1a15881 ci: Add missing macOS CI envs (#9601)
alinsa_vix in Discord noticed that we are not testing Python 3.9 and 3.10 on macOS, which seems strange. Maybe this is due how to macOS CI resources were quite scarce for a while, but I believe this has improved since.
2022-02-04 12:11:30 +01:00
Florian Bruhin
dfa7023862 Prepare release 7.0.0 (#9598) (#9604)
* Prepare release version 7.0.0

* Add note to changelog

Co-authored-by: pytest bot <pytestbot@gmail.com>
Co-authored-by: Florian Bruhin <me@the-compiler.org>
(cherry picked from commit e37fbe5685)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-02-04 07:48:24 -03:00
Florian Bruhin
2982753d80 releasing: Add template for major releases (#9596)
* releasing: Add template for major releases

With pytest 6.0.0, we still used a manual releasing workflow (at least if I
remember correctly), and apparently we never wrote a release announcement
template for major releases. Instead, the minor release template claimed that
the release would contain "breaking changes", which doesn't seem reasonable.
Thus, this adds a new major template based on the former minor template, and
adjusts the latter to only mention fixes and new features instead.

* Update scripts/release.minor.rst

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2022-02-03 15:33:07 +00:00
Florian Bruhin
12b288d84a releasing: Always set doc_version (#9589)
Looks like something (tox?) does not deal with empty arguments being passed to release.py correctly
2022-02-03 11:48:55 +01:00
Anthony Sottile
90b1c93f7e Merge pull request #9585 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-02-01 01:32:31 -05:00
pre-commit-ci[bot]
9d2ffe207b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-01-31 22:20:16 +00:00
pre-commit-ci[bot]
3aef0b9de6 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0)
- [github.com/asottile/blacken-docs: v1.12.0 → v1.12.1](https://github.com/asottile/blacken-docs/compare/v1.12.0...v1.12.1)
- [github.com/asottile/reorder_python_imports: v2.6.0 → v2.7.1](https://github.com/asottile/reorder_python_imports/compare/v2.6.0...v2.7.1)
2022-01-31 22:18:51 +00:00
Bruno Oliveira
4b1d9092a8 Merge pull request #9579 from pytest-dev/update-plugin-list/patch-ee9ddff34 2022-01-31 11:37:05 -03:00
Bruno Oliveira
232f44369a Merge pull request #9581 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-mock-3.7.0 2022-01-31 09:14:21 -03:00
dependabot[bot]
b2701a0272 build(deps): Bump pytest-mock in /testing/plugins_integration
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.6.1 to 3.7.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.6.1...v3.7.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 03:02:08 +00:00
pytest bot
321747628e [automated] Update plugin list 2022-01-30 00:12:51 +00:00
Florian Bruhin
ee9ddff34e doc: Recategorize 7.0.0 changelog items (#9561) 2022-01-27 12:39:32 +00:00
Florian Bruhin
6e1445b521 ci: Bump up timeout (#9560)
macOS apparently can be slow, https://github.com/pytest-dev/pytest/runs/4965510831 for #9556 got cancelled at 91%
2022-01-27 13:33:20 +01:00
Florian Bruhin
597bb9376b Add missing cooperative constructor changelog (#9559)
This was supposed to be part of #9488, but I only now noticed it was lying around uncommitted in my git dir
2022-01-27 13:33:09 +01:00
Florian Bruhin
843f03e3ca doc: Add ellipsis to warning usecase list (#9558)
I feel like this makes it clearer where the code which was inside 'pytest.warns' is supposed to go.
2022-01-27 13:32:37 +01:00
Bruno Oliveira
f064942f2e Make 'warnings' and 'deselected' in assert_outcomes optional (#9475)
Fix #9471
2022-01-27 12:18:36 +01:00
Florian Bruhin
396a7def75 Add additional docs for uncooperative ctor deprecation (#9498)
* Add additional docs for uncooperative ctor deprecation

Fixes #9488

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Break up long line

* Recommend kwonly args

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-01-27 11:49:18 +01:00
Ran Benita
cd8bfa94ec Merge pull request #9545 from bluetech/doc-version
doc/reference: don't document `pytest.__version__` under "Marks"
2022-01-27 10:46:28 +02:00
Ran Benita
04bddfc655 Merge pull request #9547 from bluetech/refactor-idmaker
Refactor idmaker functions into class IdMaker
2022-01-27 10:46:08 +02:00
Tobias Deiminger
b21b008118 Refactor idmaker functions into class IdMaker
This commit only refactors, it does not change or add functionality yet. Public
API is retained. Reason or refactoring:

User provided parameter IDs (e.g. Metafunc.parametrize(ids=...)) had so far
only been used to calculate a unique test ID for each test invocation. That
test ID was a joined string where each parameter contributed some partial ID.

We're soon going to reuse functionality to generate parameter keys for
reorder_items and FixtureDef cache. We will be interested in the partial
IDs, and only if they originate from explicit user information. Refactoring
makes logic and data accessible for reuse, and increases cohesion in general.
2022-01-26 15:42:34 +02:00
Ran Benita
acd2034535 fixtures: document FixtureDef's attributes 2022-01-25 23:44:59 +02:00
Ran Benita
52fbf3dbaa fixtures: make code flow clearer
Make the two cases (direct/indirect fixture) clearer. The try-catch
forces the reader to jump around.
2022-01-25 22:44:04 +02:00
Ran Benita
e6166ccc3c doc/reference: don't document pytest.__version__ under "Marks" 2022-01-25 22:39:13 +02:00
Ran Benita
5c69eced6c Merge pull request #9532 from bluetech/getdir-cache
config: avoid stat storm in _getconftestmodules
2022-01-25 16:21:44 +02:00
Akuli
a17e708352 With -vv, display the full skip/xfail reason instead of "..." (#9537)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-01-25 10:33:22 -03:00
Anthony Sottile
382e3d346e Merge pull request #9539 from pytest-dev/all-repos_autofix_flake8-typing-imports
upgrade flake8-typing-imports
2022-01-24 08:56:04 -05:00
Bruno Oliveira
2907252693 Merge pull request #9528 from SalmonMode/autouse-linearization-graph 2022-01-24 07:47:43 -03:00
Anthony Sottile
039c3a201d upgrade flake8-typing-imports
Committed via https://github.com/asottile/all-repos
2022-01-23 21:21:26 -05:00
Andrew Svetlov
b9fc678770 Merge pull request #9538 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-asyncio-0.17.2
build(deps): Bump pytest-asyncio from 0.16.0 to 0.17.2 in /testing/plugins_integration
2022-01-23 23:23:53 +02:00
Andrew Svetlov
9ae64aae56 Setup strict asyncio mode 2022-01-23 23:07:28 +02:00
dependabot[bot]
7783fb1b1e build(deps): Bump pytest-asyncio in /testing/plugins_integration
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.16.0 to 0.17.2.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.16.0...v0.17.2)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-23 20:37:56 +00:00
Ran Benita
00085391fb Merge pull request #9518 from pytest-dev/dependabot/pip/testing/plugins_integration/anyio-curiotrio--3.5.0
build(deps): Bump anyio[curio,trio] from 3.4.0 to 3.5.0 in /testing/plugins_integration
2022-01-23 22:02:15 +02:00
Ran Benita
bc296443bd Merge pull request #9534 from pytest-dev/update-plugin-list/patch-bb5a4e2d6
[automated] Update plugin list
2022-01-23 21:46:43 +02:00
pytest bot
dddadefa68 [automated] Update plugin list 2022-01-23 00:12:46 +00:00
Ran Benita
aee04cd49f config: avoid stat storm in _getconftestmodules
Fix #9478.
2022-01-22 00:27:43 +02:00
Ran Benita
bb5a4e2d63 Merge pull request #9531 from bluetech/misc
python: fix confused docstring of `Metafunc._resolve_arg_ids`
2022-01-21 17:24:56 +02:00
Ran Benita
471634d6bd python: fix confused docstring of Metafunc._resolve_arg_ids
The docstring (and function name itself) described things as if IDs are
being assigned to the argnames, but actually they're assigned to the
parameter sets.
2022-01-21 16:18:44 +02:00
Ran Benita
888026f7a6 Merge pull request #9171 from bluetech/optimize-keywords-init
Optimizations/fixes around Function `keywords`
2022-01-21 15:19:53 +02:00
Ran Benita
6d128cd52e python: use a more memory-friendly generator 2022-01-21 14:35:25 +02:00
Ran Benita
74571ba55f Add missing keywords type annotations 2022-01-21 14:35:25 +02:00
Ran Benita
456a2538ac python: optimize node keywords initialization
If we do the `update`s in the right order, we can avoid the `mark.name
not in self.keywords` check, since `self.keywords` starts out clean and
`update` will override previously set keywords.
2022-01-21 14:35:25 +02:00
Ran Benita
3c69bc919c python: remove broken/ineffectual keywords marks initialization
By my analysis, this deleted code block has no effect:

1. `self.keywords` is `update`d with `callspec.marks`.
2. `self.own_markers` is `update`d with `callspec.marks`.
3. `self.keywords` is `update`d with `self.own_markers`.

So together steps 2+3 completely undo step 1.
2022-01-21 14:35:24 +02:00
Ran Benita
d9bcfa0c2b python: don't redundantly duplicate parent markers to own keywords
This does have a slight semantic change: in a node hierarchy parent ->
child, if parent has a marker applied, then child is constructed, then
`parent.themarker = "overridden"`, previously
`child.keywords['themarker']` would return `True`, now it returns
`"overridden"`. But that's actually what I would have expected so I see
it as more of a bugfix.
2022-01-21 14:34:27 +02:00
Ran Benita
8713c32462 python: unpacked marks need to be added to keywords on all node types
(except `Instance`)

Currently, `Function` does this manually, but other node types don't get
their markers added to their `keywords`, but they should, if only for
consistency.
2022-01-21 14:34:27 +02:00
Ran Benita
e9bb1aa233 python: be consistent with what value marks have in keywords
Marks are added to keywords in three places:

- `Node.add_marker`: name -> `Mark`
- `Function.__init__(callspec)`: name -> `Mark`
- `Function.__init__ iter_markers`: name -> True

I think it should be consistent, which will also help with some upcoming
code cleaning. The `Mark` seems more useful than just a `True`, so
switch to that.
2022-01-21 14:34:27 +02:00
Ran Benita
4e5fb520b6 python: remove an unneeded normalize_mark_list call
`callspec.mark` is already `List[Mark]` so no need to normalize it.
2022-01-21 14:34:27 +02:00
Ran Benita
6672a10354 fixtures: use node.ihook instead of open-coding it 2022-01-21 14:29:47 +02:00
Bruno Oliveira
4c8fb6f0af Merge pull request #9522 from holmanb/rewrite-test 2022-01-20 13:18:12 -03:00
Chris NeJame
1fd0dcd510 fix missing ref to flattened fixture order image 2022-01-19 09:26:07 -05:00
Brett Holman
3f44b4078c Add test coverage to test rewrite 2022-01-18 11:26:46 -07:00
dependabot[bot]
c393f95c99 build(deps): Bump anyio[curio,trio] in /testing/plugins_integration
Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/agronholm/anyio/releases)
- [Changelog](https://github.com/agronholm/anyio/blob/master/docs/versionhistory.rst)
- [Commits](https://github.com/agronholm/anyio/compare/3.4.0...3.5.0)

---
updated-dependencies:
- dependency-name: anyio[curio,trio]
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-17 03:02:30 +00:00
Bruno Oliveira
fe1f0e5376 Merge pull request #9516 from pytest-dev/update-plugin-list/patch-a6310c20c
[automated] Update plugin list
2022-01-16 09:03:31 -03:00
pytest bot
947b5dbc47 [automated] Update plugin list 2022-01-16 00:13:41 +00:00
Ran Benita
a6310c20c1 Merge pull request #9512 from bluetech/pluggy-private
testing: avoid private pluggy attributes in test
2022-01-14 18:09:10 +02:00
Ran Benita
3f4eab3f8f testing: avoid private pluggy attributes in test 2022-01-14 17:49:38 +02:00
Olga Matoula
2ad1b589af Add docs on pytest.warns(None) deprecation (#9495)
* Add docs on pytest.warns(None) deprecation

* Add new section for common warnings use cases

* Fix references for warnings use cases

* Fix reference link
2022-01-13 18:32:22 +00:00
Bruno Oliveira
e9ed4827a4 Merge pull request #9506 from eamanu/fix-9505 2022-01-13 13:52:09 -03:00
Emmanuel Arias
5c2d752e74 improve docs 2022-01-13 13:29:08 -03:00
Emmanuel Arias
0fe0b78a9f Improve on configuration file docs section
To avoid confusions the part of that "the configuration file can
be locate on your tests folder" is removed.
2022-01-13 12:52:18 -03:00
Ran Benita
f1aa7a25de Merge pull request #9493 from bluetech/conftesting
Some conftest changes
2022-01-12 10:38:14 +02:00
Anthony Sottile
202e44b5e6 Merge pull request #9500 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-01-10 18:33:35 -05:00
pre-commit-ci[bot]
8891d1f449 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.930 → v0.931](https://github.com/pre-commit/mirrors-mypy/compare/v0.930...v0.931)
2022-01-10 21:28:04 +00:00
Ran Benita
a425f15330 Merge pull request #9494 from bluetech/instance-property
python: add back `instance` accessor to all python nodes, not just Function
2022-01-10 21:08:46 +02:00
Bruno Oliveira
610edd156e Merge pull request #9497 from pytest-dev/dependabot/pip/testing/plugins_integration/django-4.0.1
build(deps): Bump django from 4.0 to 4.0.1 in /testing/plugins_integration
2022-01-10 07:58:56 -03:00
dependabot[bot]
0d5f52b127 build(deps): Bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 4.0 to 4.0.1.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.0...4.0.1)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 03:02:30 +00:00
Ran Benita
0995e84adb Merge pull request #9492 from pytest-dev/update-plugin-list/patch-abe2a8f4e
[automated] Update plugin list
2022-01-09 13:14:03 +02:00
Ran Benita
161bc48117 config: get rid of _conftestpath2mod
It duplicates what PluginManager already knows, and no longer needed now
that symlinks are not resolved (see previous commit).
2022-01-09 12:33:45 +02:00
Ran Benita
0ef882364e config: stop resolving symlinks in conftest paths
This became the wrong thing to do since
322190fd84.
2022-01-09 12:33:45 +02:00
Ran Benita
f08a77de77 python: add back instance accessor to all python nodes, not just Function
Regressed in 062d91ab4 (pytest 7.0.0rc1 only).

Fix #9486.
2022-01-09 12:14:46 +02:00
pytest bot
f0dab8ba8d [automated] Update plugin list 2022-01-09 00:12:59 +00:00
Ran Benita
d98b695fec config: return Sequence instead of List from _getconftestmodules
Nothing should mutate the internal data structure here.
2022-01-09 00:30:18 +02:00
Ran Benita
ed83efaf4b testing/test_monkeypatch: fix some patches leaking into pytest code
The tests patch `os.path.abspath` which can break some pytest internal
code since the patching is not undone immediately.
2022-01-09 00:13:40 +02:00
Ran Benita
0c98f19231 config: make confcutdir check a bit more clear & correct
I think this named function makes the code a bit easier to understand.

Also change the check to explicitly check for "is a sub-path of" instead
of the previous check which only worked assuming that path is within
confcutdir or a direct parent of it.
2022-01-08 22:48:40 +02:00
Ran Benita
1c7644cc7a config: some comments 2022-01-08 22:30:48 +02:00
Ran Benita
5f23157b39 config: remove always truthy condition 2022-01-08 12:36:43 +02:00
Ran Benita
abe2a8f4e1 Merge pull request #9484 from bluetech/getconftestmodules-cache-fix
config: fix incorrect cache hit check in _getconftestmodules
2022-01-07 13:27:45 +02:00
Ran Benita
b19374bc19 config: fix incorrect cache hit check in _getconftestmodules
This made the cache not work as intended, causing a major slowdown.

See #9478 for discussion and context.

Authored-by: Anthony Sottile <asottile@umich.edu>
2022-01-07 12:47:39 +02:00
Dan Alvizu
71baf24b6d Doc update: clarify -W syntax (#9464)
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2022-01-04 13:55:01 +00:00
Bruno Oliveira
fa43b8dfb2 Merge pull request #9400 from nicoddemus/configinifile-9396 2022-01-04 10:22:35 -03:00
Bruno Oliveira
696f955ff8 Split wrappers and non-wrappers in test_load_initial_conftest_last_ordering
Seems better to test which one is which explicitly.
2022-01-04 10:03:30 -03:00
Bruno Oliveira
4038752bf3 Ensure Config.inifile is available during pytest_cmdline_main
Fix #9396
2022-01-04 10:03:28 -03:00
Anthony Sottile
1860140460 Merge pull request #9472 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-01-03 19:43:46 -05:00
pre-commit-ci[bot]
b6350b8b97 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.29.1 → v2.31.0](https://github.com/asottile/pyupgrade/compare/v2.29.1...v2.31.0)
2022-01-03 21:08:03 +00:00
github-actions[bot]
56081ca075 [automated] Update plugin list (#9468)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-01-03 18:57:32 +01:00
Florian Bruhin
d60771f986 ci: Try backporting via pull_request_target (#9430)
* ci: Try backporting via pull_request_target

* ci: Security improvements
2022-01-03 14:14:40 +00:00
Anthony Sottile
548cc4fc17 Merge pull request #9467 from hugovk/ci-rm-feature-branch-restriction
Remove branch restriction to allow testing feature branches
2022-01-01 18:29:14 -05:00
Hugo van Kemenade
819c67f58e Also test branches prefixed test-me- 2022-01-01 23:36:10 +02:00
Anthony Sottile
9a992df3c9 Merge pull request #9455 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-12-30 11:54:49 -05:00
Anthony Sottile
b0aabe4081 fix mypy 0.930 errors 2021-12-30 06:19:29 -08:00
pre-commit-ci[bot]
cbccc06302 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)
- [github.com/pre-commit/mirrors-mypy: v0.920 → v0.930](https://github.com/pre-commit/mirrors-mypy/compare/v0.920...v0.930)
2021-12-30 06:19:29 -08:00
Bruno Oliveira
3c8c0d2a33 Merge pull request #9461 from hugovk/main 2021-12-30 08:49:55 -03:00
Hugo van Kemenade
61417b2551 Drop support for EOL Python 3.6 2021-12-30 13:25:34 +02:00
Bruno Oliveira
325744ef86 Merge pull request #9460 from hugovk/rm-3.6 2021-12-30 08:18:44 -03:00
Hugo van Kemenade
1fd3601caa Drop support for EOL Python 3.6 2021-12-30 12:37:18 +02:00
Ran Benita
b9663fed6f Merge pull request #9442 from hramezani/drop_python_36
Drop Python3.6 in CI, setup.cfg, and readme.
2021-12-30 12:22:13 +02:00
Hasan Ramezani
0b7c3d1145 Remove Python 3.6 related warning from filterwarnings. 2021-12-28 17:09:27 +01:00
Ran Benita
79dbd19780 Merge pull request #9410 from pytest-dev/dependabot/pip/testing/plugins_integration/django-4.0
build(deps): Bump django from 3.2.10 to 4.0 in /testing/plugins_integration
2021-12-28 18:04:33 +02:00
dependabot[bot]
4eebfb2f19 build(deps): Bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 3.2.10 to 4.0.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.10...4.0)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-28 15:16:38 +00:00
Ran Benita
755e2509b6 Merge pull request #9456 from bluetech/tox-plugins-py
ci: run plugins job with python 3.9 instead of 3.7
2021-12-28 17:15:35 +02:00
Hasan Ramezani
e5bf3784a4 Update required Python version in docs. 2021-12-28 15:15:52 +01:00
Hasan Ramezani
b72ad0fa8e Remove has_flags conditions from src._pytest.assertion.rewrite._read_pyc 2021-12-28 10:11:45 +01:00
Hasan Ramezani
77042f77cc Remove breakpoint support checking since all python versions support it. 2021-12-28 10:11:45 +01:00
Hasan Ramezani
400915067f Move nullcontext import to src._pytest.logging. 2021-12-28 10:11:45 +01:00
Hasan Ramezani
3d7cd77017 Update syntax to Python3.7+. 2021-12-28 10:11:35 +01:00
Ran Benita
1522afa1cd ci: run plugins job with python 3.9 instead of 3.7
Latest Django release dropped support for 3.7.
2021-12-28 11:01:23 +02:00
Ran Benita
1131f23e04 Merge pull request #9451 from bluetech/code-path-changelog
doc: document {Code,TracebackEntry}.path changes as breaking
2021-12-27 16:59:42 +02:00
Ran Benita
0db1ff0d82 doc: document {Code,TracebackEntry}.path changes as breaking
Closes #9423.
2021-12-27 15:27:38 +02:00
Ran Benita
69da199f6e Merge pull request #9438 from bluetech/pytest-legacypath-imports
pytest: bring back direct imports of TempdirFactory, Testdir
2021-12-27 15:03:05 +02:00
Ran Benita
fcef7e49fd Merge pull request #9447 from bluetech/code-cut-pathlike
code: accept any `os.PathLike[str]` in `Traceback.cut`
2021-12-27 15:02:20 +02:00
Kian Meng, Ang
55debfad1f Fix typos (#9424) 2021-12-27 09:23:15 -03:00
Ran Benita
0da4760715 code: accept any os.PathLike[str] in Traceback.cut
Before 7.0.0rc1, the function accepted `Union[str, py.path.local]`, and
`py.path.local` compares equal to the string path, so a user was able to
pass the path as a string and it would work. In 7.0.0rc1 we changed the
`py.path.local` to `Path` which doesn't compare equal to the string
path, which breaks compatibility (e.g. the `sybil` package).

This restores compatibility for this function by accepting any
`os.PathLike[str]` and only comparing the string representations.
2021-12-27 11:26:32 +02:00
Ran Benita
7a42db2bf0 Merge pull request #9443 from bluetech/undocumented-hooks
doc/reference: add 4 missing hooks to reference
2021-12-27 11:11:56 +02:00
Ran Benita
7fc2cf51c2 Merge pull request #9441 from bluetech/nose-setup-callable
python: skip nose setup/teardown fixtures if non-callable
2021-12-27 11:09:51 +02:00
Hasan Ramezani
5599c5ad45 Drop Python3.6 in CI, setup.cfg, and readme. 2021-12-26 21:08:37 +01:00
Bruno Oliveira
09c0bee288 Merge pull request #9444 from pytest-dev/update-plugin-list/patch-443aa0219
[automated] Update plugin list
2021-12-26 14:10:28 -03:00
Anthony Sottile
d39780f30b Merge pull request #9428 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-12-26 11:50:46 -05:00
pytest bot
427f035b76 [automated] Update plugin list 2021-12-26 00:14:18 +00:00
Ran Benita
05d46ca850 doc/reference: add 4 missing hooks to reference
Fix #9434.
2021-12-25 13:54:42 +02:00
Ran Benita
3128080806 pytest: bring back direct imports of TempdirFactory, Testdir
The monkeypatch approach doesn't work for `import pytest;
pytest.TempdirFactory`.

Fix #9432.
2021-12-25 13:27:02 +02:00
Ran Benita
10e21dadee python: skip nose setup/teardown fixtures if non-callable
Since commit 89f0b5b5a2 cases as in the
added test started to fail, like they do for the standard pytest names
(`setup_module` etc). But the name `setup` in particular is way too
common for us to start taking it over more aggressively, so restore the
previous behavior which required the object to be callable.

Fix #9391.
2021-12-25 12:09:11 +02:00
Ran Benita
443aa0219c Merge pull request #9416 from bluetech/doc-stash-fix
doc: fix a reference in "Writing hook functions"
2021-12-25 10:49:40 +02:00
Ran Benita
8071ad6b90 doc: fix a reference in "Writing hook functions" 2021-12-25 10:30:59 +02:00
Ran Benita
0fecfff2be Merge pull request #9425 from pytest-dev/update-plugin-list/patch-47df71d23
[automated] Update plugin list
2021-12-23 11:07:22 +02:00
Anthony Sottile
c69b84f236 fix typing issues in mypy 0.920 2021-12-21 20:42:32 -05:00
pre-commit-ci[bot]
e358bc65a8 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/mirrors-mypy: v0.910-1 → v0.920](https://github.com/pre-commit/mirrors-mypy/compare/v0.910-1...v0.920)
2021-12-20 20:42:31 +00:00
pytest bot
a73a9a12a2 [automated] Update plugin list 2021-12-19 00:12:37 +00:00
Bruno Oliveira
47df71d23f Merge pull request #9417 from nicoddemus/fix-py3.10.1-9413 2021-12-16 10:37:52 -03:00
Bruno Oliveira
913439f5e5 Fix test_errors_in_xfail_skip_expressions for Python 3.10.1
Decided to remove the condition altogether as seems reasonable to state
that our own test suite requires Python 3.10.1.

Fix #9413
2021-12-16 10:18:07 -03:00
Yuval Shimon
3bbadda0cf Merge pull request #9407 from yuvalshi0/remove-eq-format
Avoid specialized assert formatting when we detect that __eq__ is overridden
2021-12-14 10:53:56 +02:00
Bruno Oliveira
d8ff487b07 Merge pull request #9411 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-django-4.5.2 2021-12-13 08:54:31 -03:00
dependabot[bot]
a3cf2ad3bc build(deps): Bump pytest-django in /testing/plugins_integration
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 03:02:33 +00:00
Ran Benita
8040cfd965 Merge pull request #9401 from bluetech/doc-hooks-ref
doc: add a `hook` crossref type
2021-12-12 18:02:53 +02:00
Ran Benita
ab8c9848a0 Merge pull request #9408 from pytest-dev/dependabot/pip/testing/plugins_integration/django-3.2.10
build(deps): Bump django from 3.2.9 to 3.2.10 in /testing/plugins_integration
2021-12-12 18:02:27 +02:00
Ran Benita
0e69c62ece doc: add a hook crossref type
Allow writing

    🪝`pytest_cmdline_main`

instead of

    :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>`
2021-12-12 16:29:30 +02:00
dependabot[bot]
4d6e8a310b build(deps): Bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 3.2.9 to 3.2.10.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.9...3.2.10)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-12 14:26:36 +00:00
Ran Benita
37d44434d8 Merge pull request #9405 from bluetech/ci-backport
ci: add a workflow for backporting to other branches
2021-12-12 16:26:11 +02:00
Bruno Oliveira
0bc77de158 Merge pull request #9406 from pytest-dev/update-plugin-list/patch-dd609e150
[automated] Update plugin list
2021-12-12 11:22:11 -03:00
Yuval Shimon
0ea039db60 adding auto_detect=true test 2021-12-12 16:11:33 +02:00
Yuval Shimon
0c45065040 removing coverage on eq method 2021-12-12 15:39:30 +02:00
Yuval Shimon
7cf2b51d8e Merge branch 'pytest-dev:main' into remove-eq-format 2021-12-12 15:16:58 +02:00
Yuval Shimon
2d7905b13b support pypy 2021-12-12 15:14:24 +02:00
Yuval Shimon
df74e5c532 fixing pre-commit 2021-12-12 14:53:29 +02:00
pre-commit-ci[bot]
9166ff6cb5 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-12-12 12:49:24 +00:00
Yuval Shimon
8c3b17263b removing unnecessary changes 2021-12-12 14:47:00 +02:00
Yuval Shimon
a67c547536 fixing tests 2021-12-12 14:45:47 +02:00
Yuval Shimon
31f42ef83f small fix 2021-12-12 14:40:46 +02:00
Yuval Shimon
897395afd5 fix 9326 2021-12-12 14:38:45 +02:00
pytest bot
99a8be200a [automated] Update plugin list 2021-12-12 00:12:01 +00:00
Ran Benita
c85b14391d ci: add a workflow for backporting to other branches
To backport a PR, e.g. 1000, to another branch, e.g. `7.0.x`, add a
label `backport 7.0.x` to the PR. This will trigger a workflow which
will create a branch `backport-1000-to-7.0.x` based on the `7.0.x`
branch with a cherry-pick of the PR's merge commit, and create a new PR
for it against the `7.0.x` branch.

It is very simplistic, for instance it doesn't handle cherry-pick
failure gracefully, doesn't validate the state of the PR, doesn't check
if the branch already exists, etc. But we can improve on it later as
needed.

Finally, PRs created by github actions do not themselves trigger further
actions, i.e. the PR isn't checked. You need to close & reopen the PR
for the checks to trigger. There are workarounds for this but they are
either less secure or require more setup.
2021-12-12 01:12:35 +02:00
Ran Benita
dd609e150b Merge pull request #9264 from bluetech/no-pycollector
Make PyCollector an implementation detail - don't use in hook type annotation
2021-12-11 22:57:49 +02:00
Ran Benita
0c8a54ab77 Merge pull request #9310 from bluetech/test-main-same-mod
testing/test_session: add a regression test for an old bug
2021-12-11 22:26:28 +02:00
Ran Benita
c7be96dae4 Merge pull request #9392 from bluetech/rm-7-deprecated
Remove deprecations scheduled for removal in pytest 7.1
2021-12-08 22:59:42 +02:00
Ran Benita
ee93557ef3 Remove PytestRemovedIn7Warning
Fix #8838.
2021-12-08 15:58:08 +02:00
Ran Benita
e05e696fda Make PyCollector an implementation detail - don't use in hook type annotation
The `pytest_pycollector_makeitem` argument `collector` is currently
annotated with type `PyCollector`. As part of #7469, that would have
required us to expose it in the public API. But really it's an
implementation detail, not something we want to expose. So replace the
annotation with the concrete python collector types that are passed.

Strictly speaking, `pytest_pycollector_makeitem` is called from
`PyCollector.collect()`, so the new type annotation is incorrect if
another type subclasses `PyCollector`. But the set of python collectors
is closed (mapping to language constructs), and the type is private, so
there shouldn't be any other deriving classes, and we can consider it
effectively sealed (unfortunately Python does not provide a way to
express this - yet?).
2021-12-08 15:20:23 +02:00
Anthony Sottile
7ae23ff8ae fix python version in changelog message (#9390) 2021-12-08 10:46:55 +01:00
Bruno Oliveira
3ba9c01f9b Hide internal stack when using pytest.approx() in bool context (#9394)
This makes the error traceback point directly to the offending usage, rather
than to the internal `Approx.__bool__` method.
2021-12-08 10:46:44 +01:00
Ran Benita
0f39f11d88 Remove deprecated pytest.collect module 2021-12-07 22:27:35 +02:00
Ran Benita
4a45a5e983 Remove deprecated -k foobar: syntax 2021-12-07 22:27:35 +02:00
Ran Benita
927d9d274f Remove deprecated -k -foobar syntax 2021-12-07 22:27:35 +02:00
Ran Benita
41d8fb09ca Remove deprecated pytest_warning_captured hook 2021-12-07 22:27:35 +02:00
Ran Benita
0b0e2d2dbb Remove deprecated _fillfuncargs function 2021-12-07 22:27:35 +02:00
Ran Benita
4d7a962ca0 Merge pull request #9389 from merwok/patch-1
fix markup typo that breaks rendering
2021-12-07 18:35:42 +02:00
Éric
b691d31897 fix typo 2021-12-07 18:16:59 +02:00
Ran Benita
d4120738b5 Merge pull request #9387 from pytest-dev/releasing-prerelease-tag
RELEASING: should also tag dev0 for prereleases
2021-12-07 17:16:16 +02:00
Ran Benita
49278c1df8 RELEASING: should also tag dev0 for prereleases 2021-12-07 15:04:27 +02:00
Florian Bruhin
21a186bbda scripts: Use release branch for changelog URL (#9380)
* scripts: Use release branch for changelog URL

With a prerelease, /stable won't show the correct changelog.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2021-12-07 10:24:57 +00:00
Florian Bruhin
5cb50fa13c Fix changelog URL in 7.0.0rc1 announcement (#9379)
The changelog does not exist at /stable because an rc isn't stable...
2021-12-07 10:04:55 +00:00
Yuval Shimon
dc7091502d Fixed error message prints function decorators when using assert in Python 3.9 and above. (#9359) 2021-12-07 11:31:03 +02:00
Ran Benita
ef76c28ea2 Merge pull request #9374 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-12-07 11:20:28 +02:00
pre-commit-ci[bot]
dd53cc7e38 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2021-12-06 21:12:33 +00:00
pre-commit-ci[bot]
d7e7c32a5f [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.11b1 → 21.12b0](https://github.com/psf/black/compare/21.11b1...21.12b0)
2021-12-06 21:10:55 +00:00
Ran Benita
43213add57 testing/test_session: add a regression test for an old bug
Nothing tests this currently. Make sure it doesn't regress if/when the
complex code in `Session.collect` is cleaned up.
2021-11-15 23:22:54 +02:00
123 changed files with 3094 additions and 1824 deletions

51
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: backport
on:
# Note that `pull_request_target` has security implications:
# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
# In particular:
# - Only allow triggers that can be used only be trusted users
# - Don't execute any code from the target branch
# - Don't use cache
pull_request_target:
types: [labeled]
# Set permissions at the job level.
permissions: {}
jobs:
backport:
if: startsWith(github.event.label.name, 'backport ') && github.event.pull_request.merged
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
persist-credentials: true
- name: Create backport PR
run: |
set -eux
git config --global user.name "pytest bot"
git config --global user.email "pytestbot@gmail.com"
label='${{ github.event.label.name }}'
target_branch="${label#backport }"
backport_branch=backport-${{ github.event.number }}-to-"${target_branch}"
subject="[$target_branch] $(gh pr view --json title -q .title ${{ github.event.number }})"
git checkout origin/"${target_branch}" -b "${backport_branch}"
git cherry-pick -x --mainline 1 ${{ github.event.pull_request.merge_commit_sha }}
git commit --amend --message "$subject"
git push --set-upstream origin --force-with-lease "${backport_branch}"
gh pr create \
--base "${target_branch}" \
--title "${subject}" \
--body "Backport of PR #${{ github.event.number }} to $target_branch branch. PR created by backport workflow."
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,6 +5,7 @@ on:
branches:
- main
- "[0-9]+.[0-9]+.x"
- "test-me-*"
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
@@ -23,7 +24,7 @@ permissions: {}
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 45
permissions:
contents: read
@@ -31,24 +32,26 @@ jobs:
fail-fast: false
matrix:
name: [
"windows-py36",
"windows-py37",
"windows-py37-pluggy",
"windows-py38",
"windows-py39",
"windows-py310",
"windows-py311",
"ubuntu-py36",
"ubuntu-py37",
"ubuntu-py37-pluggy",
"ubuntu-py37-freeze",
"ubuntu-py38",
"ubuntu-py39",
"ubuntu-py310",
"ubuntu-py311",
"ubuntu-pypy3",
"macos-py37",
"macos-py38",
"macos-py39",
"macos-py310",
"docs",
"doctesting",
@@ -56,10 +59,6 @@ jobs:
]
include:
- name: "windows-py36"
python: "3.6"
os: windows-latest
tox_env: "py36-xdist"
- name: "windows-py37"
python: "3.7"
os: windows-latest
@@ -78,14 +77,14 @@ jobs:
os: windows-latest
tox_env: "py39-xdist"
- name: "windows-py310"
python: "3.10-dev"
python: "3.10"
os: windows-latest
tox_env: "py310-xdist"
- name: "windows-py311"
python: "3.11-dev"
os: windows-latest
tox_env: "py311"
- name: "ubuntu-py36"
python: "3.6"
os: ubuntu-latest
tox_env: "py36-xdist"
- name: "ubuntu-py37"
python: "3.7"
os: ubuntu-latest
@@ -108,9 +107,13 @@ jobs:
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-py310"
python: "3.10-dev"
python: "3.10"
os: ubuntu-latest
tox_env: "py310-xdist"
- name: "ubuntu-py311"
python: "3.11-dev"
os: ubuntu-latest
tox_env: "py311"
- name: "ubuntu-pypy3"
python: "pypy-3.7"
os: ubuntu-latest
@@ -125,9 +128,17 @@ jobs:
os: macos-latest
tox_env: "py38-xdist"
use_coverage: true
- name: "macos-py39"
python: "3.9"
os: macos-latest
tox_env: "py39-xdist"
- name: "macos-py310"
python: "3.10"
os: macos-latest
tox_env: "py310-xdist"
- name: "plugins"
python: "3.7"
python: "3.9"
os: ubuntu-latest
tox_env: "plugins"

View File

@@ -12,6 +12,7 @@ permissions: {}
jobs:
createPullRequest:
if: github.repository_owner == 'pytest-dev'
runs-on: ubuntu-latest
permissions:
contents: write

View File

@@ -1,16 +1,16 @@
repos:
- repo: https://github.com/psf/black
rev: 21.11b1
rev: 22.1.0
hooks:
- id: black
args: [--safe, --quiet]
- repo: https://github.com/asottile/blacken-docs
rev: v1.12.0
rev: v1.12.1
hooks:
- id: blacken-docs
additional_dependencies: [black==20.8b1]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -20,24 +20,32 @@ repos:
- id: debug-statements
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://github.com/myint/autoflake
rev: v1.4
hooks:
- id: autoflake
name: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
language: python
files: \.py$
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
language_version: python3
additional_dependencies:
- flake8-typing-imports==1.9.0
- flake8-typing-imports==1.12.0
- flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.6.0
rev: v2.7.1
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py36-plus]
args: ['--application-directories=.:src', --py37-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v2.29.1
rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py36-plus]
args: [--py37-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.20.0
hooks:
@@ -48,7 +56,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.910-1
rev: v0.931
hooks:
- id: mypy
files: ^(src/|testing/)

View File

@@ -185,7 +185,9 @@ Katerina Koukiou
Keri Volans
Kevin Cox
Kevin J. Foley
Kian-Meng Ang
Kodi B. Arfer
Kojo Idrissa
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
@@ -348,6 +350,7 @@ Xixi Zhao
Xuan Luong
Xuecong Liao
Yoav Caspi
Yuval Shimon
Zac Hatfield-Dodds
Zachary Kneupper
Zoltán Máté

View File

@@ -391,6 +391,13 @@ actual latest release). The procedure for this is:
request, as described above. An exception to this is if the bug fix is not
applicable to ``main`` anymore.
Automatic method:
Add a ``backport 1.2.x`` label to the PR you want to backport. This will create
a backport PR against the ``1.2.x`` branch.
Manual method:
#. ``git checkout origin/1.2.x -b backport-XXXX`` # use the main PR number here
#. Locate the merge commit on the PR, in the *merged* message, for example:

View File

@@ -100,7 +100,7 @@ Features
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
- Python 3.6+ and PyPy3
- Python 3.7+ or PyPy3
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community

View File

@@ -37,7 +37,7 @@ breaking changes or new features.
For a new minor release, first create a new maintenance branch from ``main``::
git fetch --all
git fetch upstream
git branch 7.1.x upstream/main
git push upstream 7.1.x
@@ -63,7 +63,7 @@ Major releases
1. Create a new maintenance branch from ``main``::
git fetch --all
git fetch upstream
git branch 8.0.x upstream/main
git push upstream 8.0.x
@@ -136,29 +136,31 @@ Both automatic and manual processes described above follow the same steps from t
#. After all tests pass and the PR has been approved, tag the release commit
in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI::
git fetch --all
git fetch upstream
git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH
git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH
git push upstream MAJOR.MINOR.PATCH
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR.
#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch.
#. Cherry-pick the CHANGELOG / announce files to the ``main`` branch::
git fetch --all --prune
git fetch upstream
git checkout upstream/main -b cherry-pick-release
git cherry-pick -x -m1 upstream/MAJOR.MINOR.x
#. Open a PR for ``cherry-pick-release`` and merge it once CI passes. No need to wait for approvals if there were no conflicts on the previous step.
#. For major and minor releases, tag the release cherry-pick merge commit in main with
#. For major and minor releases (or the first prerelease of it), tag the release cherry-pick merge commit in main with
a dev tag for the next feature release::
git checkout main
git pull
git tag MAJOR.{MINOR+1}.0.dev0
git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0
git push upstream MAJOR.{MINOR+1}.0.dev0
#. For major and minor releases, change the default version in the `Read the Docs Settings <https://readthedocs.org/dashboard/pytest/advanced/>`_ to the new branch.
#. Send an email announcement with the contents from::

View File

@@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-7.1.0
release-7.0.1
release-7.0.0
release-7.0.0rc1
release-6.2.5
release-6.2.4

View File

@@ -11,7 +11,7 @@ clear information about the circumstances and a simple example which
reproduces the problem.
The issue tracker is of course not empty now. We have many remaining
"enhacement" issues which we'll hopefully can tackle in 2014 with your
"enhancement" issues which we'll hopefully can tackle in 2014 with your
help.
For those who use older Python versions, please note that pytest is not

View File

@@ -0,0 +1,74 @@
pytest-7.0.0
=======================================
The pytest team is proud to announce the 7.0.0 release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Adam J. Stewart
* Alexander King
* Amin Alaee
* Andrew Neitsch
* Anthony Sottile
* Ben Davies
* Bernát Gábor
* Brian Okken
* Bruno Oliveira
* Cristian Vera
* Dan Alvizu
* David Szotten
* Eddie
* Emmanuel Arias
* Emmanuel Meric de Bellefon
* Eric Liu
* Florian Bruhin
* GergelyKalmar
* Graeme Smecher
* Harshna
* Hugo van Kemenade
* Jakub Kulík
* James Myatt
* Jeff Rasley
* Kale Kundert
* Kian Meng, Ang
* Miro Hrončok
* Naveen-Pratap
* Oleg Höfling
* Olga Matoula
* Ran Benita
* Ronny Pfannschmidt
* Simon K
* Srip
* Sören Wegener
* Taneli Hukkinen
* Terje Runde
* Thomas Grainger
* Thomas Hisch
* William Jamir Silva
* Yuval Shimon
* Zac Hatfield-Dodds
* andrewdotn
* denivyruck
* ericluoliu
* oleg.hoefling
* symonk
* ziebam
* Éloi Rivard
* Éric
Happy testing,
The pytest Development Team

View File

@@ -19,7 +19,7 @@ You can upgrade from PyPI via:
Users are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
https://docs.pytest.org/en/7.0.x/changelog.html
Thanks to all the contributors to this release:

View File

@@ -0,0 +1,20 @@
pytest-7.0.1
=======================================
pytest 7.0.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/stable/changelog.html.
Thanks to all of the contributors to this release:
* Anthony Sottile
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,48 @@
pytest-7.1.0
=======================================
The pytest team is proud to announce the 7.1.0 release!
This release contains new features, improvements, and bug fixes,
the full list of changes is available in the changelog:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Akuli
* Andrew Svetlov
* Anthony Sottile
* Brett Holman
* Bruno Oliveira
* Chris NeJame
* Dan Alvizu
* Elijah DeLee
* Emmanuel Arias
* Fabian Egli
* Florian Bruhin
* Gabor Szabo
* Hasan Ramezani
* Hugo van Kemenade
* Kian Meng, Ang
* Kojo Idrissa
* Masaru Tsuchiyama
* Olga Matoula
* P. L. Lim
* Ran Benita
* Tobias Deiminger
* Yuval Shimon
* eduardo naufel schettino
* Éric
Happy testing,
The pytest Development Team

View File

@@ -65,7 +65,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Fixture that returns a :py:class:`dict` that will be injected into the
namespace of doctests.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1365
pytestconfig [session scope] -- .../_pytest/fixtures.py:1334
Session-scoped fixture that returns the session's :class:`pytest.Config`
object.
@@ -117,10 +117,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
:issue:`7767` for details.
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:292
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:295
Return a :class:`pytest.TempdirFactory` instance for the test session.
tmpdir -- .../_pytest/legacypath.py:299
tmpdir -- .../_pytest/legacypath.py:302
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
@@ -134,7 +134,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
caplog -- .../_pytest/logging.py:483
caplog -- .../_pytest/logging.py:487
Access and control log capturing.
Captured logs are available through the following properties/methods::

View File

@@ -28,6 +28,167 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 7.1.0 (2022-03-13)
=========================
Breaking Changes
----------------
- `#8838 <https://github.com/pytest-dev/pytest/issues/8838>`_: As per our policy, the following features have been deprecated in the 6.X series and are now
removed:
* ``pytest._fillfuncargs`` function.
* ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
* ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
* ``-k foobar:`` syntax.
* ``pytest.collect`` module - import from ``pytest`` directly.
For more information consult
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
- `#9437 <https://github.com/pytest-dev/pytest/issues/9437>`_: Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
Improvements
------------
- `#5192 <https://github.com/pytest-dev/pytest/issues/5192>`_: Fixed test output for some data types where ``-v`` would show less information.
Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
- `#9362 <https://github.com/pytest-dev/pytest/issues/9362>`_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
- `#9536 <https://github.com/pytest-dev/pytest/issues/9536>`_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
- `#9644 <https://github.com/pytest-dev/pytest/issues/9644>`_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
be obtained by enabling :mod:`tracemalloc`.
See :ref:`resource-warnings` for more information.
- `#9678 <https://github.com/pytest-dev/pytest/issues/9678>`_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
Previously only `str`, `float`, `int` and `bool` were accepted;
now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
- `#9692 <https://github.com/pytest-dev/pytest/issues/9692>`_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
Bug Fixes
---------
- `#8242 <https://github.com/pytest-dev/pytest/issues/8242>`_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of
tests during the pytest collection phase is reverted - this is now a supported
feature again.
- `#9493 <https://github.com/pytest-dev/pytest/issues/9493>`_: Symbolic link components are no longer resolved in conftest paths.
This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
For example, given
tests/real/conftest.py
tests/real/test_it.py
tests/link -> tests/real
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
- `#9645 <https://github.com/pytest-dev/pytest/issues/9645>`_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
- `#9708 <https://github.com/pytest-dev/pytest/issues/9708>`_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
- `#9730 <https://github.com/pytest-dev/pytest/issues/9730>`_: Malformed ``pyproject.toml`` files now produce a clearer error message.
pytest 7.0.1 (2022-02-11)
=========================
Bug Fixes
---------
- `#9608 <https://github.com/pytest-dev/pytest/issues/9608>`_: Fix invalid importing of ``importlib.readers`` in Python 3.9.
- `#9610 <https://github.com/pytest-dev/pytest/issues/9610>`_: Restore `UnitTestFunction.obj` to return unbound rather than bound method.
Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`.
Regressed in pytest 7.0.0.
- `#9636 <https://github.com/pytest-dev/pytest/issues/9636>`_: The ``pythonpath`` plugin was renamed to ``python_path``. This avoids a conflict with the ``pytest-pythonpath`` plugin.
- `#9642 <https://github.com/pytest-dev/pytest/issues/9642>`_: Fix running tests by id with ``::`` in the parametrize portion.
- `#9643 <https://github.com/pytest-dev/pytest/issues/9643>`_: Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and
:class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters <warnings>`.
pytest 7.0.0 (2022-02-03)
=========================
(**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**)
Deprecations
------------
- `#9488 <https://github.com/pytest-dev/pytest/issues/9488>`_: If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. See
:ref:`uncooperative-constructors-deprecated` for details.
Note that a deprection warning is only emitted when there is a conflict in the
arguments pytest expected to pass. This deprecation was already part of pytest
7.0.0rc1 but wasn't documented.
Bug Fixes
---------
- `#9355 <https://github.com/pytest-dev/pytest/issues/9355>`_: Fixed error message prints function decorators when using assert in Python 3.8 and above.
- `#9396 <https://github.com/pytest-dev/pytest/issues/9396>`_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``).
Improved Documentation
----------------------
- `#9404 <https://github.com/pytest-dev/pytest/issues/9404>`_: Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
- `#9505 <https://github.com/pytest-dev/pytest/issues/9505>`_: Clarify where the configuration files are located. To avoid confusions documentation mentions
that configuration file is located in the root of the repository.
Trivial/Internal Changes
------------------------
- `#9521 <https://github.com/pytest-dev/pytest/issues/9521>`_: Add test coverage to assertion rewrite path.
pytest 7.0.0rc1 (2021-12-06)
============================
@@ -142,6 +303,8 @@ Deprecations
:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
:func:`unittest.skip` in unittest test cases is fully supported.
.. note:: This deprecation has been reverted in pytest 7.1.0.
- `#8315 <https://github.com/pytest-dev/pytest/issues/8315>`_: Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
@@ -156,7 +319,7 @@ Deprecations
See :ref:`the deprecation note <diamond-inheritance-deprecated>` for full details.
- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` has been officially deprecated. It will be removed in a future release. Use :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` instead.
- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: :hook:`pytest_cmdline_preparse` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead.
See :ref:`the deprecation note <cmdline-preparse-deprecated>` for full details.
@@ -173,6 +336,12 @@ Deprecations
This was changed for consistency with :func:`pytest.mark.skip <pytest.mark.skip>` and :func:`pytest.mark.xfail <pytest.mark.xfail>` which both accept
``reason`` as an argument.
- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to types reachable through :attr:`pytest.ExceptionInfo.traceback`:
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
There was no deprecation period for this change (sorry!).
Features
@@ -203,11 +372,11 @@ Features
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
- ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :hook:`pytest_generate_tests` hook.
- ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
- ``pytest.PytestPluginManager`` for :class:`PytestPluginManager <pytest.PytestPluginManager>`.
- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :func:`pytest_addoption <pytest.hookspec.pytest_addoption>` hook.
- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :hook:`pytest_addoption` hook.
- ``pytest.OptionGroup`` for the :class:`OptionGroup <pytest.OptionGroup>` type returned from the :func:`parser.addgroup <pytest.Parser.getgroup>` method.
- ``pytest.HookRecorder`` for the :class:`HookRecorder <pytest.HookRecorder>` type returned from :class:`~pytest.Pytester`.
- ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall <pytest.HookRecorder>` type returned from :class:`~pytest.HookRecorder`.
@@ -228,11 +397,11 @@ Features
- `#8144 <https://github.com/pytest-dev/pytest/issues/8144>`_: The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :hook:`pytest_ignore_collect` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_collect_file` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_pycollect_makemodule` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_report_header` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :hook:`pytest_report_collectionfinish` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
@@ -473,10 +642,9 @@ Trivial/Internal Changes
- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to internal pytest types/functions:
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.
- The ``_pytest._code.Traceback.cut()`` function accepts any ``os.PathLike[str]``, not just ``py.path.local``.
- `#8248 <https://github.com/pytest-dev/pytest/issues/8248>`_: Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information.
@@ -491,7 +659,7 @@ Trivial/Internal Changes
- `#8913 <https://github.com/pytest-dev/pytest/issues/8913>`_: The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring.
- `#8967 <https://github.com/pytest-dev/pytest/issues/8967>`_: :func:`pytest_assertion_pass <_pytest.hookspec.pytest_assertion_pass>` is no longer considered experimental and
- `#8967 <https://github.com/pytest-dev/pytest/issues/8967>`_: :hook:`pytest_assertion_pass` is no longer considered experimental and
future changes to it will be considered more carefully.
@@ -538,7 +706,7 @@ Bug Fixes
the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with
private permissions.
pytest used to silenty use a pre-existing ``/tmp/pytest-of-<username>`` directory,
pytest used to silently use a pre-existing ``/tmp/pytest-of-<username>`` directory,
even if owned by another user. This means another user could pre-create such a
directory and gain control of another user's temporary directory. Now such a
condition results in an error.
@@ -853,8 +1021,8 @@ Deprecations
if you use this and want a replacement.
- :issue:`7255`: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor
of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version.
- :issue:`7255`: The :hook:`pytest_warning_captured` hook is deprecated in favor
of :hook:`pytest_warning_recorded`, and will be removed in a future version.
- :issue:`7648`: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated;
@@ -962,7 +1130,7 @@ Trivial/Internal Changes
process, pytest now ignores builtin attributes (like ``__class__``,
``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and
:confval:`python_functions` configuration options and without passing them to plugins
using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook.
using the :hook:`pytest_pycollect_makeitem` hook.
pytest 6.0.2 (2020-09-04)
@@ -1304,7 +1472,7 @@ Improvements
is not displayed by default for passing tests. This change makes the mistake
visible during testing.
You may supress this behavior temporarily or permanently by setting
You may suppress this behavior temporarily or permanently by setting
``logging.raiseExceptions = False``.
@@ -1751,7 +1919,7 @@ Bug Fixes
- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger.
- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger.
- :issue:`6752`: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager),
@@ -1759,7 +1927,7 @@ Bug Fixes
it was swallowed and ignored (regression in pytest 5.1.0).
- :issue:`6801`: Do not display empty lines inbetween traceback for unexpected exceptions with doctests.
- :issue:`6801`: Do not display empty lines in between traceback for unexpected exceptions with doctests.
- :issue:`6802`: The :fixture:`testdir fixture <testdir>` works within doctests now.
@@ -1863,7 +2031,7 @@ Improvements
- :issue:`6231`: Improve check for misspelling of :ref:`pytest.mark.parametrize ref`.
- :issue:`6257`: Handle :py:func:`pytest.exit` being used via :py:func:`~_pytest.hookspec.pytest_internalerror`, e.g. when quitting pdb from post mortem.
- :issue:`6257`: Handle :func:`pytest.exit` being used via :hook:`pytest_internalerror`, e.g. when quitting pdb from post mortem.
@@ -2497,7 +2665,7 @@ Deprecations
Features
--------
- :issue:`3457`: New :func:`~_pytest.hookspec.pytest_assertion_pass`
- :issue:`3457`: New :hook:`pytest_assertion_pass`
hook, called with context information when an assertion *passes*.
This hook is still **experimental** so use it with caution.
@@ -4628,7 +4796,7 @@ Bug Fixes
- Fixed a bug where stdout and stderr were logged twice by junitxml when a test
was marked xfail. (:issue:`3491`)
- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating
- Fix ``usefixtures`` mark applied to unittest tests by correctly instantiating
``FixtureInfo``. (:issue:`3498`)
- Fix assertion rewriter compatibility with libraries that monkey patch
@@ -5017,9 +5185,9 @@ Features
- Console output falls back to "classic" mode when capturing is disabled (``-s``),
otherwise the output gets garbled to the point of being useless. (:issue:`3038`)
- New :func:`~_pytest.hookspec.pytest_runtest_logfinish`
- New :hook:`pytest_runtest_logfinish`
hook which is called when a test item has finished executing, analogous to
:func:`~_pytest.hookspec.pytest_runtest_logstart`.
:hook:`pytest_runtest_logstart`.
(:issue:`3101`)
- Improve performance when collecting tests using many fixtures. (:issue:`3107`)
@@ -8009,7 +8177,7 @@ Bug fixes:
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/en/stable/how-to/mark.html
for your test suite. See examples at http://pytest.org/en/stable/how-to/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that "-m"
@@ -8321,7 +8489,7 @@ Bug fixes:
- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
- fix py.code.compile(source) to generate unique filenames
- fix assertion re-interp problems on PyPy, by defering code
- fix assertion re-interp problems on PyPy, by deferring code
compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
- fix py.path.local.pyimport() to work with directories
- streamline py.path.local.mkdtemp implementation and usage
@@ -8395,7 +8563,7 @@ Bug fixes:
- improve support for raises and other dynamically compiled code by
manipulating python's linecache.cache instead of the previous
rather hacky way of creating custom code objects. This makes
it seemlessly work on Jython and PyPy where it previously didn't.
it seamlessly work on Jython and PyPy where it previously didn't.
- fix issue96: make capturing more resilient against Control-C
interruptions (involved somewhat substantial refactoring

View File

@@ -382,7 +382,6 @@ texinfo_documents = [
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
"python": ("https://docs.python.org/3", None),
@@ -390,10 +389,6 @@ intersphinx_mapping = {
"pip": ("https://pip.pypa.io/en/stable", None),
"tox": ("https://tox.wiki/en/stable", None),
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
"django": (
"http://docs.djangoproject.com/en/stable",
"http://docs.djangoproject.com/en/stable/_objects",
),
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
}
@@ -445,6 +440,13 @@ def setup(app: "sphinx.application.Sphinx") -> None:
indextemplate="pair: %s; global variable interpreted by pytest",
)
app.add_crossref_type(
directivename="hook",
rolename="hook",
objname="pytest hook",
indextemplate="pair: %s; hook",
)
configure_logging(app)
# Make Sphinx mark classes with "final" when decorated with @final.

View File

@@ -16,7 +16,7 @@ Deprecated Features
-------------------
Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
.. _instance-collector-deprecation:
@@ -56,6 +56,10 @@ Plugins which implement custom items and collectors are encouraged to replace
``fspath`` parameters (``py.path.local``) with ``path`` parameters
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
If possible, plugins with custom items should use :ref:`cooperative
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
arguments they only pass on to the superclass.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
new attribute being ``path``) is **the opposite** of the situation for
@@ -81,11 +85,11 @@ no matter what argument was used in the constructor. We expect to deprecate the
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :func:`pytest_ignore_collect(collection_path: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` as equivalent to ``path``
* :func:`pytest_collect_file(file_path: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` as equivalent to ``path``
* :func:`pytest_pycollect_makemodule(module_path: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` as equivalent to ``path``
* :func:`pytest_report_header(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_header>` as equivalent to ``startdir``
* :func:`pytest_report_collectionfinish(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` as equivalent to ``startdir``
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
@@ -127,7 +131,7 @@ Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these
functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
.. code-block:: python
@@ -157,8 +161,8 @@ Implementing the ``pytest_cmdline_preparse`` hook
.. deprecated:: 7.0
Implementing the :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` hook has been officially deprecated.
Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` hook instead.
Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated.
Implement the :hook:`pytest_load_initial_conftests` hook instead.
.. code-block:: python
@@ -191,6 +195,40 @@ Instead, a separate collector node should be used, which collects the item. See
.. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files
.. _uncooperative-constructors-deprecated:
Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. Thus,
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, name, parent, additional_arg):
super().__init__(name, parent)
self.additional_arg = additional_arg
should be turned into:
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, *, additional_arg, **kwargs):
super().__init__(**kwargs)
self.additional_arg = additional_arg
to avoid hard-coding the arguments pytest can pass to the superclass.
See :ref:`non-python tests` for a full example.
For cases without conflicts, no deprecation warning is emitted. For cases with
conflicts (such as :class:`pytest.File` now taking ``path`` instead of
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
deprecation warning is now raised.
Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -203,29 +241,16 @@ scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
Raising ``unittest.SkipTest`` during collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
Raising :class:`unittest.SkipTest` to skip collection of tests during the
pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
Note: This deprecation only relates to using `unittest.SkipTest` during test
collection. You are probably not doing that. Ordinary usage of
:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
:func:`unittest.skip` in unittest test cases is fully supported.
Using ``pytest.warns(None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because many people used
it to mean "this code does not emit warnings", but it actually had the effect of
checking that the code emits at least one warning of any type - like ``pytest.warns()``
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because it was frequently misused.
Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()``
or ``pytest.warns(Warning)``.
See :ref:`warns use cases` for examples.
The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -250,29 +275,42 @@ The ``yield_fixture`` function/decorator
It has been so for a very long time, so can be search/replaced safely.
The ``pytest_warning_captured`` hook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Removed Features
----------------
.. deprecated:: 6.0
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter
by a ``nodeid`` parameter.
The ``pytest.collect`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.0
.. versionremoved:: 7.0
The ``pytest.collect`` module is no longer part of the public API, all its names
should now be imported from ``pytest`` directly instead.
The ``pytest_warning_captured`` hook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.0
.. versionremoved:: 7.0
This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter
by a ``nodeid`` parameter.
The ``pytest._fillfuncargs`` function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.0
.. versionremoved:: 7.0
This function was kept for backward compatibility with an older plugin.
@@ -281,12 +319,6 @@ it, use `function._request._fillfixtures()` instead, though note this is not
a public API and may break in the future.
Removed Features
----------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
``--no-print-logs`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -324,8 +356,8 @@ at some point, depending on the plans for the plugins and number of users using
.. versionremoved:: 6.0
The ``pytest_collect_directory`` has not worked properly for years (it was called
but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead.
The ``pytest_collect_directory`` hook has not worked properly for years (it was called
but the results were ignored). Users may consider using :hook:`pytest_collection_modifyitems` instead.
TerminalReporter.writer
~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="112" height="682">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
path, line {
stroke: black;
stroke-width: 2;
fill: none;
}
rect.autouse {
fill: #ca7f3d;
}
</style>
<line x1="56" x2="56" y1="681" y2="26" />
<ellipse class="fixture" rx="50" ry="25" cx="56" cy="26" />
<text x="56" y="26">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="96" />
<text x="56" y="96">a</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="166" />
<text x="56" y="166">b</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="236" />
<text x="56" y="236">c</text>
<rect class="autouse" width="112" height="40" x="0" y="286" />
<text x="56" y="306">autouse</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="376" />
<text x="56" y="376">d</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="446" />
<text x="56" y="446">e</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="516" />
<text x="56" y="516">f</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="586" />
<text x="56" y="586">g</text>
<rect class="test" width="110" height="50" x="1" y="631" />
<text x="56" y="656">test_order</text>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -18,8 +18,8 @@ class YamlFile(pytest.File):
class YamlItem(pytest.Item):
def __init__(self, name, parent, spec):
super().__init__(name, parent)
def __init__(self, *, spec, **kwargs):
super().__init__(**kwargs)
self.spec = spec
def runtest(self):

View File

@@ -155,7 +155,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert [0, 1, 2] == [0, 1, 3]
E assert [0, 1, 2] == [0, 1, 3]
E At index 2 diff: 2 != 3
E Use -v to get the full diff
E Use -v to get more diff
failure_demo.py:63: AssertionError
______________ TestSpecialisedExplanations.test_eq_list_long _______________
@@ -168,7 +168,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert a == b
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
E At index 100 diff: 1 != 2
E Use -v to get the full diff
E Use -v to get more diff
failure_demo.py:68: AssertionError
_________________ TestSpecialisedExplanations.test_eq_dict _________________
@@ -215,7 +215,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert [1, 2] == [1, 2, 3]
E assert [1, 2] == [1, 2, 3]
E Right contains one more item: 3
E Use -v to get the full diff
E Use -v to get more diff
failure_demo.py:77: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________

View File

@@ -9,7 +9,7 @@ Get Started
Install ``pytest``
----------------------------------------
``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3.
``pytest`` requires: Python 3.7+ or PyPy3.
1. Run the following command in your command line:
@@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 7.0.0rc1
pytest 7.1.0
.. _`simpletest`:

View File

@@ -139,7 +139,7 @@ project:
which adds ``pytest`` (rather than ``py.test``) as the recommended
command-line entry point
Due to this history, it's diffcult to answer the question when pytest was started.
Due to this history, it's difficult to answer the question when pytest was started.
It depends what point should really be seen as the start of it all. One
possible interpretation is to pick Europython 2004, i.e. around June/July
2004.

View File

@@ -201,7 +201,7 @@ if you run this module:
E '1'
E Extra items in the right set:
E '5'
E Use -v to get the full diff
E Use -v to get more diff
test_assert2.py:4: AssertionError
========================= short test summary info ==========================

View File

@@ -5,7 +5,7 @@ How to set up bash completion
=============================
When using bash as your shell, ``pytest`` can use argcomplete
(https://argcomplete.readthedocs.io/) for auto-completion.
(https://kislyuk.github.io/argcomplete/) for auto-completion.
For this ``argcomplete`` needs to be installed **and** enabled.
Install argcomplete using:

View File

@@ -42,8 +42,18 @@ Running pytest now produces this output:
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors:
Controlling warnings
--------------------
Similar to Python's `warning filter`_ and :option:`-W option <python:-W>` flag, pytest provides
its own ``-W`` flag to control which warnings are ignored, displayed, or turned into
errors. See the `warning filter`_ documentation for more
advanced use-cases.
.. _`warning filter`: https://docs.python.org/3/library/warnings.html#warning-filter
This code sample shows how to treat any ``UserWarning`` category class of warning
as an error:
.. code-block:: pytest
@@ -96,9 +106,6 @@ all other warnings into errors.
When a warning matches more than one option in the list, the action for the last matching option
is performed.
Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
:option:`-W option <python:-W>` and :func:`warnings.simplefilter`, so please refer to those sections in the Python
documentation for other examples and advanced usage.
.. _`filterwarnings`:
@@ -344,6 +351,37 @@ warnings, or index into it to get a particular recorded warning.
Full API: :class:`~_pytest.recwarn.WarningsRecorder`.
.. _`warns use cases`:
Additional use cases of warnings in tests
-----------------------------------------
Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:
- To ensure that **at least one** warning is emitted, use:
.. code-block:: python
with pytest.warns():
...
- To ensure that **no** warnings are emitted, use:
.. code-block:: python
with warnings.catch_warnings():
warnings.simplefilter("error")
...
- To suppress warnings, use:
.. code-block:: python
with warnings.catch_warnings():
warnings.simplefilter("ignore")
...
.. _custom_failure_messages:
Custom failure messages
@@ -403,3 +441,18 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep
features.
The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`.
.. _`resource-warnings`:
Resource Warnings
-----------------
Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if
:mod:`tracemalloc` module is enabled.
One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large
enough number of frames (say ``20``, but that number is application dependent).
For more information, consult the `Python Development Mode <https://docs.python.org/3/library/devmode.html>`__
section in the Python documentation.

View File

@@ -84,7 +84,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
> assert fruits1 == fruits2
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
E At index 2 diff: 'grapes' != 'orange'
E Use -v to get the full diff
E Use -v to get more diff
test_verbosity_example.py:8: AssertionError
____________________________ test_numbers_fail _____________________________
@@ -99,7 +99,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
E {'1': 1, '2': 2, '3': 3, '4': 4}
E Right contains 4 more items:
E {'10': 10, '20': 20, '30': 30, '40': 40}
E Use -v to get the full diff
E Use -v to get more diff
test_verbosity_example.py:14: AssertionError
___________________________ test_long_text_fail ____________________________

View File

@@ -21,7 +21,7 @@ there is no need to activate it.
Here is a little annotated list for some popular plugins:
* :pypi:`pytest-django`: write tests
for :std:doc:`django <django:index>` apps, using pytest integration.
for `django <https://docs.djangoproject.com/>`_ apps, using pytest integration.
* :pypi:`pytest-twisted`: write tests
for `twisted <https://twistedmatrix.com/>`_ apps, starting a reactor and
@@ -51,9 +51,6 @@ Here is a little annotated list for some popular plugins:
* :pypi:`pytest-flakes`:
check source code with pyflakes.
* :pypi:`oejskit`:
a plugin to run javascript unittests in live browsers.
To see a complete list of all plugins with their latest testing
status against different pytest and Python versions, please visit
:ref:`plugin-list`.

View File

@@ -84,14 +84,14 @@ It is also possible to skip the whole module using
If you wish to skip something conditionally then you can use ``skipif`` instead.
Here is an example of marking a test function to be skipped
when run on an interpreter earlier than Python3.6:
when run on an interpreter earlier than Python3.10:
.. code-block:: python
import sys
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
def test_function():
...
@@ -369,7 +369,7 @@ Here is a simple test file with the several usages:
Running it with the report-on-xfail option gives this output:
.. FIXME: Use $ instead of ! again to reenable regendoc once it's fixed:
.. FIXME: Use $ instead of ! again to re-enable regendoc once it's fixed:
https://github.com/pytest-dev/pytest/issues/8807
.. code-block:: pytest

View File

@@ -121,6 +121,10 @@ the system temporary directory. The base name will be ``pytest-NUM`` where
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
The number of entries currently cannot be changed, but using the ``--basetemp``
option will remove the directory before every run, effectively meaning the temporary directories
of only the most recent run will be kept.
You can override the default temporary directory setting like this:
.. code-block:: bash

View File

@@ -321,7 +321,7 @@ Plugins often need to store data on :class:`~pytest.Item`\s in one hook
implementation, and access it in another. One common solution is to just
assign some private attribute directly on the item, but type-checkers like
mypy frown upon this, and it may also cause conflicts with other plugins.
So pytest offers a better way to do this, :attr:`_pytest.nodes.Node.stash <item.stash>`.
So pytest offers a better way to do this, :attr:`item.stash <_pytest.nodes.Node.stash>`.
To use the "stash" in your plugins, first create "stash keys" somewhere at the
top level of your plugin:

View File

@@ -2,9 +2,15 @@
.. sidebar:: Next Open Trainings
- `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
- `PyConDE <https://2022.pycon.de/program/W93DBJ/>`__, April 11th 2022 (3h), Berlin, Germany
- `PyConIT <https://pycon.it/en/talk/pytest-simple-rapid-and-fun-testing-with-python>`__, June 3rd 2022 (4h), Florence, Italy
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany
Also see `previous talks and blogposts <talks.html>`_.
Also see :doc:`previous talks and blogposts <talks>`.
..
- `Europython <https://ep2022.europython.eu/>`__, July 11th to 17th (3h), Dublin, Ireland
- `CH Open Workshoptage <https://workshoptage.ch/>`__ (German), September 6th to 8th (1 day), Bern, Switzerland
.. _features:
@@ -17,7 +23,7 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can
scale to support complex functional testing for applications and libraries.
**Pythons**: ``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3.
``pytest`` requires: Python 3.7+ or PyPy3.
**PyPI package name**: :pypi:`pytest`
@@ -78,7 +84,7 @@ Features
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box
- Python 3.6+ and PyPy 3
- Python 3.7+ or PyPy 3
- Rich plugin architecture, with over 800+ :ref:`external plugins <plugin-list>` and thriving community

View File

@@ -20,8 +20,7 @@ Configuration file formats
--------------------------
Many :ref:`pytest settings <ini options ref>` can be set in a *configuration file*, which
by convention resides on the root of your repository or in your
tests folder.
by convention resides in the root directory of your repository.
A quick example of the configuration files supported by pytest:

View File

@@ -208,7 +208,7 @@ the one defined in ``tests/test_top.py`` would be unavailable to it because it
would have to step down a level (step inside a circle) to find it.
The first fixture the test finds is the one that will be used, so
:ref:`fixtures can be overriden <override fixtures>` if you need to change or
:ref:`fixtures can be overridden <override fixtures>` if you need to change or
extend what one does for a particular scope.
You can also use the ``conftest.py`` file to implement
@@ -401,6 +401,9 @@ the graph would look like this:
Because ``c`` can now be put above ``d`` in the graph, pytest can once again
linearize the graph to this:
.. image:: /example/fixtures/test_fixtures_order_autouse_flat.*
:align: center
In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as
well.

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,39 @@ This page contains the full reference to pytest's API.
:depth: 3
:local:
Constants
---------
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Functions
---------
@@ -226,37 +259,6 @@ Marks a test function as *expected to fail*.
a new release of a library fixes a known bug).
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Custom marks
~~~~~~~~~~~~
@@ -671,9 +673,13 @@ Bootstrapping hooks
Bootstrapping hooks called for plugins registered early enough (internal and setuptools plugins).
.. hook:: pytest_load_initial_conftests
.. autofunction:: pytest_load_initial_conftests
.. hook:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_preparse
.. hook:: pytest_cmdline_parse
.. autofunction:: pytest_cmdline_parse
.. hook:: pytest_cmdline_main
.. autofunction:: pytest_cmdline_main
.. _`initialization-hooks`:
@@ -683,13 +689,20 @@ Initialization hooks
Initialization hooks called for plugins and ``conftest.py`` files.
.. hook:: pytest_addoption
.. autofunction:: pytest_addoption
.. hook:: pytest_addhooks
.. autofunction:: pytest_addhooks
.. hook:: pytest_configure
.. autofunction:: pytest_configure
.. hook:: pytest_unconfigure
.. autofunction:: pytest_unconfigure
.. hook:: pytest_sessionstart
.. autofunction:: pytest_sessionstart
.. hook:: pytest_sessionfinish
.. autofunction:: pytest_sessionfinish
.. hook:: pytest_plugin_registered
.. autofunction:: pytest_plugin_registered
Collection hooks
@@ -697,21 +710,34 @@ Collection hooks
``pytest`` calls the following hooks for collecting files and directories:
.. hook:: pytest_collection
.. autofunction:: pytest_collection
.. hook:: pytest_ignore_collect
.. autofunction:: pytest_ignore_collect
.. hook:: pytest_collect_file
.. autofunction:: pytest_collect_file
.. hook:: pytest_pycollect_makemodule
.. autofunction:: pytest_pycollect_makemodule
For influencing the collection of objects in Python modules
you can use the following hook:
.. hook:: pytest_pycollect_makeitem
.. autofunction:: pytest_pycollect_makeitem
.. hook:: pytest_generate_tests
.. autofunction:: pytest_generate_tests
.. hook:: pytest_make_parametrize_id
.. autofunction:: pytest_make_parametrize_id
Hooks for influencing test skipping:
.. hook:: pytest_markeval_namespace
.. autofunction:: pytest_markeval_namespace
After collection is complete, you can modify the order of
items, delete or otherwise amend the test items:
.. hook:: pytest_collection_modifyitems
.. autofunction:: pytest_collection_modifyitems
.. note::
@@ -725,13 +751,21 @@ Test running (runtest) hooks
All runtest related hooks receive a :py:class:`pytest.Item <pytest.Item>` object.
.. hook:: pytest_runtestloop
.. autofunction:: pytest_runtestloop
.. hook:: pytest_runtest_protocol
.. autofunction:: pytest_runtest_protocol
.. hook:: pytest_runtest_logstart
.. autofunction:: pytest_runtest_logstart
.. hook:: pytest_runtest_logfinish
.. autofunction:: pytest_runtest_logfinish
.. hook:: pytest_runtest_setup
.. autofunction:: pytest_runtest_setup
.. hook:: pytest_runtest_call
.. autofunction:: pytest_runtest_call
.. hook:: pytest_runtest_teardown
.. autofunction:: pytest_runtest_teardown
.. hook:: pytest_runtest_makereport
.. autofunction:: pytest_runtest_makereport
For deeper understanding you may look at the default implementation of
@@ -740,6 +774,7 @@ in ``_pytest.pdb`` which interacts with ``_pytest.capture``
and its input/output capturing in order to immediately drop
into interactive debugging when a test failure occurs.
.. hook:: pytest_pyfunc_call
.. autofunction:: pytest_pyfunc_call
Reporting hooks
@@ -747,27 +782,45 @@ Reporting hooks
Session related reporting hooks:
.. hook:: pytest_collectstart
.. autofunction:: pytest_collectstart
.. hook:: pytest_make_collect_report
.. autofunction:: pytest_make_collect_report
.. hook:: pytest_itemcollected
.. autofunction:: pytest_itemcollected
.. hook:: pytest_collectreport
.. autofunction:: pytest_collectreport
.. hook:: pytest_deselected
.. autofunction:: pytest_deselected
.. hook:: pytest_report_header
.. autofunction:: pytest_report_header
.. hook:: pytest_report_collectionfinish
.. autofunction:: pytest_report_collectionfinish
.. hook:: pytest_report_teststatus
.. autofunction:: pytest_report_teststatus
.. hook:: pytest_report_to_serializable
.. autofunction:: pytest_report_to_serializable
.. hook:: pytest_report_from_serializable
.. autofunction:: pytest_report_from_serializable
.. hook:: pytest_terminal_summary
.. autofunction:: pytest_terminal_summary
.. hook:: pytest_fixture_setup
.. autofunction:: pytest_fixture_setup
.. hook:: pytest_fixture_post_finalizer
.. autofunction:: pytest_fixture_post_finalizer
.. autofunction:: pytest_warning_captured
.. hook:: pytest_warning_recorded
.. autofunction:: pytest_warning_recorded
Central hook for reporting about test execution:
.. hook:: pytest_runtest_logreport
.. autofunction:: pytest_runtest_logreport
Assertion related hooks:
.. hook:: pytest_assertrepr_compare
.. autofunction:: pytest_assertrepr_compare
.. hook:: pytest_assertion_pass
.. autofunction:: pytest_assertion_pass
@@ -777,10 +830,16 @@ Debugging/Interaction hooks
There are few hooks which can be used for special
reporting or interaction with exceptions:
.. hook:: pytest_internalerror
.. autofunction:: pytest_internalerror
.. hook:: pytest_keyboard_interrupt
.. autofunction:: pytest_keyboard_interrupt
.. hook:: pytest_exception_interact
.. autofunction:: pytest_exception_interact
.. hook:: pytest_enter_pdb
.. autofunction:: pytest_enter_pdb
.. hook:: pytest_leave_pdb
.. autofunction:: pytest_leave_pdb
Objects

View File

@@ -11,9 +11,14 @@ Books
- `Python Testing with pytest, by Brian Okken (2017)
<https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.
- `Python Testing with pytest, Second Edition, by Brian Okken (2022)
<https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition>`_.
Talks and blog postings
---------------------------------------------
- `pytest: Simple, rapid and fun testing with Python, <https://youtu.be/cSJ-X3TbQ1c?t=15752>`_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021
- Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020
- Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020

View File

@@ -28,8 +28,6 @@ filterwarnings = [
"default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*",
# distutils is deprecated in 3.10, scheduled for removal in 3.12
"ignore:The distutils package is deprecated:DeprecationWarning",
# produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)."
"ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))",
# produced by pytest-xdist
"ignore:.*type argument to addoption.*:DeprecationWarning",
# produced on execnet (pytest-xdist)
@@ -113,4 +111,4 @@ template = "changelog/_template.rst"
showcontent = true
[tool.black]
target-version = ['py36']
target-version = ['py37']

View File

@@ -88,7 +88,9 @@ def prepare_release_pr(
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
if prerelease:
if is_major:
template_name = "release.major.rst"
elif prerelease:
template_name = "release.pre.rst"
elif is_feature_release:
template_name = "release.minor.rst"
@@ -104,6 +106,7 @@ def prepare_release_pr(
"--",
version,
template_name,
release_branch, # doc_version
"--skip-check-links",
]
print("Running", " ".join(cmdline))

24
scripts/release.major.rst Normal file
View File

@@ -0,0 +1,24 @@
pytest-{version}
=======================================
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
{contributors}
Happy testing,
The pytest Development Team

View File

@@ -3,8 +3,8 @@ pytest-{version}
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
This release contains new features, improvements, and bug fixes,
the full list of changes is available in the changelog:
https://docs.pytest.org/en/stable/changelog.html

View File

@@ -19,7 +19,7 @@ You can upgrade from PyPI via:
Users are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
https://docs.pytest.org/en/{doc_version}/changelog.html
Thanks to all the contributors to this release:

View File

@@ -10,7 +10,7 @@ from colorama import Fore
from colorama import init
def announce(version, template_name):
def announce(version, template_name, doc_version):
"""Generates a new release announcement entry in the docs."""
# Get our list of authors
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
@@ -31,7 +31,9 @@ def announce(version, template_name):
)
contributors_text = "\n".join(f"* {name}" for name in sorted(contributors)) + "\n"
text = template_text.format(version=version, contributors=contributors_text)
text = template_text.format(
version=version, contributors=contributors_text, doc_version=doc_version
)
target = Path(__file__).parent.joinpath(f"../doc/en/announce/release-{version}.rst")
target.write_text(text, encoding="UTF-8")
@@ -82,9 +84,9 @@ def check_links():
check_call(["tox", "-e", "docs-checklinks"])
def pre_release(version, template_name, *, skip_check_links):
def pre_release(version, template_name, doc_version, *, skip_check_links):
"""Generates new docs, release announcements and creates a local tag."""
announce(version, template_name)
announce(version, template_name, doc_version)
regen(version)
changelog(version, write_out=True)
fix_formatting()
@@ -112,11 +114,15 @@ def main():
parser.add_argument(
"template_name", help="Name of template file to use for release announcement"
)
parser.add_argument(
"doc_version", help="For prereleases, the version to link to in the docs"
)
parser.add_argument("--skip-check-links", action="store_true", default=False)
options = parser.parse_args()
pre_release(
options.version,
options.template_name,
options.doc_version,
skip_check_links=options.skip_check_links,
)

View File

@@ -17,7 +17,6 @@ classifiers =
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
@@ -51,7 +50,7 @@ install_requires =
atomicwrites>=1.0;sys_platform=="win32"
colorama;sys_platform=="win32"
importlib-metadata>=0.12;python_version<"3.8"
python_requires = >=3.6
python_requires = >=3.7
package_dir =
=src
setup_requires =

View File

@@ -108,7 +108,6 @@ if os.environ.get("_ARGCOMPLETE"):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
argcomplete.autocomplete(parser, always_complete_options=False)
else:
def try_argcomplete(parser: argparse.ArgumentParser) -> None:

View File

@@ -1,5 +1,6 @@
import ast
import inspect
import os
import re
import sys
import traceback
@@ -343,10 +344,10 @@ class Traceback(List[TracebackEntry]):
def cut(
self,
path: Optional[Union[Path, str]] = None,
path: Optional[Union["os.PathLike[str]", str]] = None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath: Optional[Path] = None,
excludepath: Optional["os.PathLike[str]"] = None,
) -> "Traceback":
"""Return a Traceback instance wrapping part of this Traceback.
@@ -357,15 +358,17 @@ class Traceback(List[TracebackEntry]):
for formatting reasons (removing some uninteresting bits that deal
with handling of the exception/traceback).
"""
path_ = None if path is None else os.fspath(path)
excludepath_ = None if excludepath is None else os.fspath(excludepath)
for x in self:
code = x.frame.code
codepath = code.path
if path is not None and codepath != path:
if path is not None and str(codepath) != path_:
continue
if (
excludepath is not None
and isinstance(codepath, Path)
and excludepath in codepath.parents
and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator]
):
continue
if lineno is not None and x.lineno != lineno:
@@ -895,7 +898,7 @@ class FormattedExcinfo:
max_frames=max_frames,
total=len(traceback),
)
# Type ignored because adding two instaces of a List subtype
# Type ignored because adding two instances of a List subtype
# currently incorrectly has type List instead of the subtype.
traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
else:

View File

@@ -149,6 +149,11 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i
values: List[int] = []
for x in ast.walk(node):
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
# Before Python 3.8, the lineno of a decorated class or function pointed at the decorator.
# Since Python 3.8, the lineno points to the class/def, so need to include the decorators.
if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
for d in x.decorator_list:
values.append(d.lineno - 1)
values.append(x.lineno - 1)
for name in ("finalbody", "orelse"):
val: Optional[List[ast.stmt]] = getattr(x, name, None)

View File

@@ -100,9 +100,6 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
spec is None
# this is a namespace package (without `__init__.py`)
# there's nothing to rewrite there
# python3.6: `namespace`
# python3.7+: `None`
or spec.origin == "namespace"
or spec.origin is None
# we can only rewrite source files
or not isinstance(spec.loader, importlib.machinery.SourceFileLoader)
@@ -276,13 +273,15 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
with open(pathname, "rb") as f:
return f.read()
if sys.version_info >= (3, 9):
if sys.version_info >= (3, 10):
def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore
from types import SimpleNamespace
from importlib.readers import FileReader
if sys.version_info < (3, 11):
from importlib.readers import FileReader
else:
from importlib.resources.readers import FileReader
return FileReader(SimpleNamespace(path=self._rewritten_names[name]))
return FileReader(types.SimpleNamespace(path=self._rewritten_names[name]))
def _write_pyc_fp(
@@ -293,9 +292,8 @@ def _write_pyc_fp(
# import. However, there's little reason to deviate.
fp.write(importlib.util.MAGIC_NUMBER)
# https://www.python.org/dev/peps/pep-0552/
if sys.version_info >= (3, 7):
flags = b"\x00\x00\x00\x00"
fp.write(flags)
flags = b"\x00\x00\x00\x00"
fp.write(flags)
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
size = source_stat.st_size & 0xFFFFFFFF
@@ -324,7 +322,6 @@ if sys.platform == "win32":
return False
return True
else:
def _write_pyc(
@@ -377,31 +374,29 @@ def _read_pyc(
except OSError:
return None
with fp:
# https://www.python.org/dev/peps/pep-0552/
has_flags = sys.version_info >= (3, 7)
try:
stat_result = os.stat(source)
mtime = int(stat_result.st_mtime)
size = stat_result.st_size
data = fp.read(16 if has_flags else 12)
data = fp.read(16)
except OSError as e:
trace(f"_read_pyc({source}): OSError {e}")
return None
# Check for invalid or out of date pyc file.
if len(data) != (16 if has_flags else 12):
if len(data) != (16):
trace("_read_pyc(%s): invalid pyc (too short)" % source)
return None
if data[:4] != importlib.util.MAGIC_NUMBER:
trace("_read_pyc(%s): invalid pyc (bad magic number)" % source)
return None
if has_flags and data[4:8] != b"\x00\x00\x00\x00":
if data[4:8] != b"\x00\x00\x00\x00":
trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source)
return None
mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8]
mtime_data = data[8:12]
if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF:
trace("_read_pyc(%s): out of date" % source)
return None
size_data = data[12 if has_flags else 8 : 16 if has_flags else 12]
size_data = data[12:16]
if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
trace("_read_pyc(%s): invalid pyc (incorrect size)" % source)
return None
@@ -514,7 +509,7 @@ def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None:
def _check_if_assertion_pass_impl() -> bool:
"""Check if any plugins implement the pytest_assertion_pass hook
in order not to generate explanation unecessarily (might be expensive)."""
in order not to generate explanation unnecessarily (might be expensive)."""
return True if util._assertion_pass else False

View File

@@ -135,6 +135,27 @@ def isiterable(obj: Any) -> bool:
return False
def has_default_eq(
obj: object,
) -> bool:
"""Check if an instance of an object contains the default eq
First, we check if the object's __eq__ attribute has __code__,
if so, we check the equally of the method code filename (__code__.co_filename)
to the default one generated by the dataclass and attr module
for dataclasses the default co_filename is <string>, for attrs class, the __eq__ should contain "attrs eq generated"
"""
# inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68
if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"):
code_filename = obj.__eq__.__code__.co_filename
if isattrs(obj):
return "attrs generated eq" in code_filename
return code_filename == "<string>" # data class
return True
def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands."""
verbose = config.getoption("verbose")
@@ -202,8 +223,6 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
@@ -260,18 +279,6 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
return explanation
def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
keepends = True
left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends)
explanation: List[str] = []
explanation += ["+" + line for line in left_lines]
explanation += ["-" + line for line in right_lines]
return explanation
def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
"""Move opening/closing parenthesis/bracket to own lines."""
opening = lines[0][:1]
@@ -287,8 +294,8 @@ def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
def _compare_eq_iterable(
left: Iterable[Any], right: Iterable[Any], verbose: int = 0
) -> List[str]:
if not verbose and not running_on_ci():
return ["Use -v to get the full diff"]
if verbose <= 0 and not running_on_ci():
return ["Use -v to get more diff"]
# dynamic import to speedup pytest
import difflib
@@ -427,6 +434,8 @@ def _compare_eq_dict(
def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
if not has_default_eq(left):
return []
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]

View File

@@ -68,8 +68,8 @@ def _colorama_workaround() -> None:
pass
def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
"""Workaround for Windows Unicode console handling on Python>=3.6.
def _windowsconsoleio_workaround(stream: TextIO) -> None:
"""Workaround for Windows Unicode console handling.
Python 3.6 implemented Unicode console handling for Windows. This works
by reading/writing to the raw console handle using
@@ -112,7 +112,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
buffering = -1
return io.TextIOWrapper(
open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type]
open(os.dup(f.fileno()), mode, buffering),
f.encoding,
f.errors,
f.newlines,
@@ -128,7 +128,7 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
def pytest_load_initial_conftests(early_config: Config):
ns = early_config.known_args_namespace
if ns.capture == "fd":
_py36_windowsconsoleio_workaround(sys.stdout)
_windowsconsoleio_workaround(sys.stdout)
_colorama_workaround()
pluginmanager = early_config.pluginmanager
capman = CaptureManager(ns.capture)

View File

@@ -4,7 +4,6 @@ import functools
import inspect
import os
import sys
from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from pathlib import Path
@@ -186,17 +185,6 @@ def getfuncargnames(
return arg_names
if sys.version_info < (3, 7):
@contextmanager
def nullcontext():
yield
else:
from contextlib import nullcontext as nullcontext # noqa: F401
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
# Note: this code intentionally mirrors the code at the beginning of
# getfuncargnames, to get the arguments which were excluded from its result

View File

@@ -1,7 +1,6 @@
"""Command line options, ini-file and conftest.py processing."""
import argparse
import collections.abc
import contextlib
import copy
import enum
import inspect
@@ -255,7 +254,7 @@ default_plugins = essential_plugins + (
"warnings",
"logging",
"reports",
"pythonpath",
"python_path",
*(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
"faulthandler",
)
@@ -331,6 +330,14 @@ def _prepareconfig(
raise
def _get_directory(path: Path) -> Path:
"""Get the directory of a path - itself if already a directory."""
if path.is_file():
return path.parent
else:
return path
@final
class PytestPluginManager(PluginManager):
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
@@ -345,14 +352,24 @@ class PytestPluginManager(PluginManager):
import _pytest.assertion
super().__init__("pytest")
# The objects are module objects, only used generically.
self._conftest_plugins: Set[types.ModuleType] = set()
# State related to local conftest plugins.
# -- State related to local conftest plugins.
# All loaded conftest modules.
self._conftest_plugins: Set[types.ModuleType] = set()
# All conftest modules applicable for a directory.
# This includes the directory's own conftest modules as well
# as those of its parent directories.
self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
# Cutoff directory above which conftests are no longer discovered.
self._confcutdir: Optional[Path] = None
# If set, conftest loading is skipped.
self._noconftest = False
# _getconftestmodules()'s call to _get_directory() causes a stat
# storm when it's called potentially thousands of times in a test
# session (#9478), often with the same path, so cache it.
self._get_directory = lru_cache(256)(_get_directory)
self._duplicatepaths: Set[Path] = set()
# plugins that were explicitly skipped with pytest.skip
@@ -514,6 +531,19 @@ class PytestPluginManager(PluginManager):
if not foundanchor:
self._try_load_conftest(current, namespace.importmode, rootpath)
def _is_in_confcutdir(self, path: Path) -> bool:
"""Whether a path is within the confcutdir.
When false, should not load conftest.
"""
if self._confcutdir is None:
return True
try:
path.relative_to(self._confcutdir)
except ValueError:
return False
return True
def _try_load_conftest(
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> None:
@@ -526,33 +556,28 @@ class PytestPluginManager(PluginManager):
def _getconftestmodules(
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> List[types.ModuleType]:
) -> Sequence[types.ModuleType]:
if self._noconftest:
return []
if path.is_file():
directory = path.parent
else:
directory = path
directory = self._get_directory(path)
# Optimization: avoid repeated searches in the same directory.
# Assumes always called with same importmode and rootpath.
existing_clist = self._dirpath2confmods.get(directory)
if existing_clist:
if existing_clist is not None:
return existing_clist
# XXX these days we may rather want to use config.rootpath
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir.
clist = []
confcutdir_parents = self._confcutdir.parents if self._confcutdir else []
for parent in reversed((directory, *directory.parents)):
if parent in confcutdir_parents:
continue
conftestpath = parent / "conftest.py"
if conftestpath.is_file():
mod = self._importconftest(conftestpath, importmode, rootpath)
clist.append(mod)
if self._is_in_confcutdir(parent):
conftestpath = parent / "conftest.py"
if conftestpath.is_file():
mod = self._importconftest(conftestpath, importmode, rootpath)
clist.append(mod)
self._dirpath2confmods[directory] = clist
return clist
@@ -574,15 +599,9 @@ class PytestPluginManager(PluginManager):
def _importconftest(
self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> types.ModuleType:
# Use a resolved Path object as key to avoid loading the same conftest
# twice with build systems that create build directories containing
# symlinks to actual files.
# Using Path().resolve() is better than py.path.realpath because
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
key = conftestpath.resolve()
with contextlib.suppress(KeyError):
return self._conftestpath2mod[key]
existing = self.get_plugin(str(conftestpath))
if existing is not None:
return cast(types.ModuleType, existing)
pkgpath = resolve_package_path(conftestpath)
if pkgpath is None:
@@ -598,11 +617,10 @@ class PytestPluginManager(PluginManager):
self._check_non_top_pytest_plugins(mod, conftestpath)
self._conftest_plugins.add(mod)
self._conftestpath2mod[key] = mod
dirpath = conftestpath.parent
if dirpath in self._dirpath2confmods:
for path, mods in self._dirpath2confmods.items():
if path and dirpath in path.parents or path == dirpath:
if dirpath in path.parents or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace(f"loading conftestmodule {mod!r}")
@@ -970,7 +988,7 @@ class Config:
def add_cleanup(self, func: Callable[[], None]) -> None:
"""Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
use (usually coinciding with pytest_unconfigure)."""
self._cleanup.append(func)
def _do_configure(self) -> None:
@@ -1330,14 +1348,6 @@ class Config:
if records:
frame = sys._getframe(stacklevel - 1)
location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
self.hook.pytest_warning_captured.call_historic(
kwargs=dict(
warning_message=records[0],
when="config",
item=None,
location=location,
)
)
self.hook.pytest_warning_recorded.call_historic(
kwargs=dict(
warning_message=records[0],
@@ -1437,6 +1447,7 @@ class Config:
)
except KeyError:
return None
assert mod.__file__ is not None
modpath = Path(mod.__file__).parent
values: List[Path] = []
for relroot in relroots:
@@ -1582,7 +1593,7 @@ def _strtobool(val: str) -> bool:
@lru_cache(maxsize=50)
def parse_warning_filter(
arg: str, *, escape: bool
) -> Tuple[str, str, Type[Warning], str, int]:
) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]:
"""Parse a warnings filter string.
This is copied from warnings._setoption with the following changes:
@@ -1624,7 +1635,7 @@ def parse_warning_filter(
parts.append("")
action_, message, category_, module, lineno_ = (s.strip() for s in parts)
try:
action: str = warnings._getaction(action_) # type: ignore[attr-defined]
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
except warnings._OptionError as e:
raise UsageError(error_template.format(error=str(e)))
try:

View File

@@ -23,7 +23,7 @@ class PathAwareHookProxy:
this helper wraps around hook callers
until pluggy supports fixingcalls, this one will do
it currently doesnt return full hook caller proxies for fixed hooks,
it currently doesn't return full hook caller proxies for fixed hooks,
this may have to be changed later depending on bugs
"""

View File

@@ -70,7 +70,7 @@ def load_config_dict_from_file(
try:
config = tomli.loads(toml_text)
except tomli.TOMLDecodeError as exc:
raise UsageError(str(exc)) from exc
raise UsageError(f"{filepath}: {exc}") from exc
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None:

View File

@@ -11,7 +11,6 @@ in case of warnings which need to format their messages.
from warnings import warn
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import UnformattedWarning
@@ -24,18 +23,6 @@ DEPRECATED_EXTERNAL_PLUGINS = {
}
FILLFUNCARGS = UnformattedWarning(
PytestRemovedIn7Warning,
"{name} is deprecated, use "
"function._request._fillfixtures() instead if you cannot avoid reaching into internals.",
)
PYTEST_COLLECT_MODULE = UnformattedWarning(
PytestRemovedIn7Warning,
"pytest.collect.{name} was moved to pytest.{name}\n"
"Please update to the new name.",
)
# This can be* removed pytest 8, but it's harmless and common, so no rush to remove.
# * If you're in the future: "could have been".
YIELD_FIXTURE = PytestDeprecationWarning(
@@ -43,20 +30,6 @@ YIELD_FIXTURE = PytestDeprecationWarning(
"Use @pytest.fixture instead; they are the same."
)
MINUS_K_DASH = PytestRemovedIn7Warning(
"The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead."
)
MINUS_K_COLON = PytestRemovedIn7Warning(
"The `-k 'expr:'` syntax to -k is deprecated.\n"
"Please open an issue if you use this and want a replacement."
)
WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning(
"The pytest_warning_captured is deprecated and will be removed in a future release.\n"
"Please use pytest_warning_recorded instead."
)
WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning(
"The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n"
"Please use pytest_load_initial_conftests hook instead."
@@ -74,11 +47,6 @@ STRICT_OPTION = PytestRemovedIn8Warning(
# This deprecation is never really meant to be removed.
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning(
"Raising unittest.SkipTest to skip tests during collection is deprecated. "
"Use pytest.skip() instead."
)
ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning(
'pytest now uses argparse. "%default" should be changed to "%(default)s"',
)
@@ -115,8 +83,10 @@ NODE_CTOR_FSPATH_ARG = UnformattedWarning(
)
WARNS_NONE_ARG = PytestRemovedIn8Warning(
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
" Replace pytest.warns(None) by simply pytest.warns()."
"Passing None has been deprecated.\n"
"See https://docs.pytest.org/en/latest/how-to/capture-warnings.html"
"#additional-use-cases-of-warnings-in-tests"
" for alternatives in common use cases."
)
KEYWORD_MSG_ARG = UnformattedWarning(

View File

@@ -656,7 +656,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
precision = 0 if fraction is None else len(fraction)
if exponent is not None:
precision -= int(exponent)
if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):
if float(w.group()) == approx(float(g.group()), abs=10**-precision):
# They're close enough. Replace the text we actually
# got with the text we want, so that it will match when we
# check the string literally.

View File

@@ -52,7 +52,6 @@ from _pytest.config import _PluggyPlugin
from _pytest.config import Config
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import FILLFUNCARGS
from _pytest.deprecated import YIELD_FIXTURE
from _pytest.mark import Mark
from _pytest.mark import ParameterSet
@@ -73,7 +72,6 @@ if TYPE_CHECKING:
from _pytest.scope import _ScopeName
from _pytest.main import Session
from _pytest.python import CallSpec2
from _pytest.python import Function
from _pytest.python import Metafunc
@@ -352,41 +350,6 @@ def reorder_items_atscope(
return items_done
def _fillfuncargs(function: "Function") -> None:
"""Fill missing fixtures for a test function, old public API (deprecated)."""
warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2)
_fill_fixtures_impl(function)
def fillfixtures(function: "Function") -> None:
"""Fill missing fixtures for a test function (deprecated)."""
warnings.warn(
FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2
)
_fill_fixtures_impl(function)
def _fill_fixtures_impl(function: "Function") -> None:
"""Internal implementation to fill fixtures on the given function object."""
try:
request = function._request
except AttributeError:
# XXX this special code path is only expected to execute
# with the oejskit plugin. It uses classes with funcargs
# and we thus have to work a bit to allow this.
fm = function.session._fixturemanager
assert function.parent is not None
fi = fm.getfixtureinfo(function.parent, function.obj, None)
function._fixtureinfo = fi
request = function._request = FixtureRequest(function, _ispytest=True)
fm.session._setupstate.setup(function)
request._fillfixtures()
# Prune out funcargs for jstests.
function.funcargs = {name: function.funcargs[name] for name in fi.argnames}
else:
request._fillfixtures()
def get_direct_param_fixture_func(request):
return request.param
@@ -634,8 +597,17 @@ class FixtureRequest:
funcitem = self._pyfuncitem
scope = fixturedef._scope
try:
param = funcitem.callspec.getparam(argname)
except (AttributeError, ValueError):
callspec = funcitem.callspec
except AttributeError:
callspec = None
if callspec is not None and argname in callspec.params:
param = callspec.params[argname]
param_index = callspec.indices[argname]
# If a parametrize invocation set a scope it will override
# the static scope defined with the fixture function.
with suppress(KeyError):
scope = callspec._arg2scope[argname]
else:
param = NOTSET
param_index = 0
has_params = fixturedef.params is not None
@@ -675,12 +647,6 @@ class FixtureRequest:
)
)
fail(msg, pytrace=False)
else:
param_index = funcitem.callspec.indices[argname]
# If a parametrize invocation set a scope it will override
# the static scope defined with the fixture function.
with suppress(KeyError):
scope = funcitem.callspec._arg2scope[argname]
subrequest = SubRequest(
self, scope, param, param_index, fixturedef, _ispytest=True
@@ -964,7 +930,7 @@ def _eval_scope_callable(
@final
class FixtureDef(Generic[FixtureValue]):
"""A container for a factory definition."""
"""A container for a fixture definition."""
def __init__(
self,
@@ -976,33 +942,56 @@ class FixtureDef(Generic[FixtureValue]):
params: Optional[Sequence[object]],
unittest: bool = False,
ids: Optional[
Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
]
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
] = None,
) -> None:
self._fixturemanager = fixturemanager
# The "base" node ID for the fixture.
#
# This is a node ID prefix. A fixture is only available to a node (e.g.
# a `Function` item) if the fixture's baseid is a parent of the node's
# nodeid (see the `iterparentnodeids` function for what constitutes a
# "parent" and a "prefix" in this context).
#
# For a fixture found in a Collector's object (e.g. a `Module`s module,
# a `Class`'s class), the baseid is the Collector's nodeid.
#
# For a fixture found in a conftest plugin, the baseid is the conftest's
# directory path relative to the rootdir.
#
# For other plugins, the baseid is the empty string (always matches).
self.baseid = baseid or ""
# Whether the fixture was found from a node or a conftest in the
# collection tree. Will be false for fixtures defined in non-conftest
# plugins.
self.has_location = baseid is not None
# The fixture factory function.
self.func = func
# The name by which the fixture may be requested.
self.argname = argname
if scope is None:
scope = Scope.Function
elif callable(scope):
scope = _eval_scope_callable(scope, argname, fixturemanager.config)
if isinstance(scope, str):
scope = Scope.from_user(
scope, descr=f"Fixture '{func.__name__}'", where=baseid
)
self._scope = scope
# If the fixture is directly parametrized, the parameter values.
self.params: Optional[Sequence[object]] = params
self.argnames: Tuple[str, ...] = getfuncargnames(
func, name=argname, is_method=unittest
)
self.unittest = unittest
# If the fixture is directly parametrized, a tuple of explicit IDs to
# assign to the parameter values, or a callable to generate an ID given
# a parameter value.
self.ids = ids
# The names requested by the fixtures.
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
# Whether the fixture was collected from a unittest TestCase class.
# Note that it really only makes sense to define autouse fixtures in
# unittest TestCases.
self.unittest = unittest
# If the fixture was executed, the current value of the fixture.
# Can change if the fixture is executed with different parameters.
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
self._finalizers: List[Callable[[], object]] = []
@@ -1029,8 +1018,8 @@ class FixtureDef(Generic[FixtureValue]):
if exc:
raise exc
finally:
hook = self._fixturemanager.session.gethookproxy(request.node.path)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
ihook = request.node.ihook
ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
# which will keep instances alive.
@@ -1064,8 +1053,8 @@ class FixtureDef(Generic[FixtureValue]):
self.finish(request)
assert self.cached_result is None
hook = self._fixturemanager.session.gethookproxy(request.node.path)
result = hook.pytest_fixture_setup(fixturedef=self, request=request)
ihook = request.node.ihook
result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
return result
def cache_key(self, request: SubRequest) -> object:
@@ -1130,18 +1119,8 @@ def pytest_fixture_setup(
def _ensure_immutable_ids(
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
],
) -> Optional[
Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
]
]:
ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]]
) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]:
if ids is None:
return None
if callable(ids):
@@ -1185,9 +1164,8 @@ class FixtureFunctionMarker:
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter)
autouse: bool = False
ids: Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
ids: Optional[
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
] = attr.ib(
default=None,
converter=_ensure_immutable_ids,
@@ -1228,10 +1206,7 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = ...,
name: Optional[str] = ...,
) -> FixtureFunction:
@@ -1246,10 +1221,7 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = ...,
name: Optional[str] = None,
) -> FixtureFunctionMarker:
@@ -1263,10 +1235,7 @@ def fixture(
params: Optional[Iterable[object]] = None,
autouse: bool = False,
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
] = None,
name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, FixtureFunction]:
@@ -1308,7 +1277,7 @@ def fixture(
the fixture.
:param ids:
List of string ids each corresponding to the params so that they are
Sequence of ids each corresponding to the params so that they are
part of the test id. If no ids are provided they will be generated
automatically from the params.

View File

@@ -13,7 +13,6 @@ from typing import Union
from pluggy import HookspecMarker
from _pytest.deprecated import WARNING_CAPTURED_HOOK
from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK
if TYPE_CHECKING:
@@ -34,10 +33,10 @@ if TYPE_CHECKING:
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.outcomes import Exit
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Metafunc
from _pytest.python import Module
from _pytest.python import PyCollector
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.runner import CallInfo
@@ -163,7 +162,7 @@ def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None:
"""(**Deprecated**) modify command line arguments before option parsing.
This hook is considered deprecated and will be removed in a future pytest version. Consider
using :func:`pytest_load_initial_conftests` instead.
using :hook:`pytest_load_initial_conftests` instead.
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
@@ -360,7 +359,7 @@ def pytest_pycollect_makemodule(
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(
collector: "PyCollector", name: str, obj: object
collector: Union["Module", "Class"], name: str, obj: object
) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]:
"""Return a custom item/collector for a Python object in a module, or None.
@@ -467,7 +466,7 @@ def pytest_runtest_logstart(
) -> None:
"""Called at the start of running the runtest protocol for a single item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param str nodeid: Full node ID of the item.
:param location: A tuple of ``(filename, lineno, testname)``.
@@ -479,7 +478,7 @@ def pytest_runtest_logfinish(
) -> None:
"""Called at the end of running the runtest protocol for a single item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param str nodeid: Full node ID of the item.
:param location: A tuple of ``(filename, lineno, testname)``.
@@ -526,7 +525,7 @@ def pytest_runtest_makereport(
"""Called to create a :class:`~pytest.TestReport` for each of
the setup, call and teardown runtest phases of a test item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param call: The :class:`~pytest.CallInfo` for the phase.
@@ -538,7 +537,7 @@ def pytest_runtest_logreport(report: "TestReport") -> None:
"""Process the :class:`~pytest.TestReport` produced for each
of the setup, call and teardown runtest phases of an item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
"""
@@ -557,7 +556,7 @@ def pytest_report_from_serializable(
data: Dict[str, Any],
) -> Optional[Union["CollectReport", "TestReport"]]:
"""Restore a report object previously serialized with
:func:`pytest_report_to_serializable`."""
:hook:`pytest_report_to_serializable`."""
# -------------------------------------------------------------------------
@@ -777,41 +776,6 @@ def pytest_terminal_summary(
"""
@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK)
def pytest_warning_captured(
warning_message: "warnings.WarningMessage",
when: "Literal['config', 'collect', 'runtest']",
item: Optional["Item"],
location: Optional[Tuple[str, int, str]],
) -> None:
"""(**Deprecated**) Process a warning captured by the internal pytest warnings plugin.
.. deprecated:: 6.0
This hook is considered deprecated and will be removed in a future pytest version.
Use :func:`pytest_warning_recorded` instead.
:param warnings.WarningMessage warning_message:
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
the same attributes as the parameters of :py:func:`warnings.showwarning`.
:param str when:
Indicates when the warning was captured. Possible values:
* ``"config"``: during pytest configuration/initialization stage.
* ``"collect"``: during test collection.
* ``"runtest"``: during test execution.
:param pytest.Item|None item:
The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
:param tuple location:
When available, holds information about the execution context of the captured
warning (filename, linenumber, function). ``function`` evaluates to <module>
when the execution context is at the module level.
"""
@hookspec(historic=True)
def pytest_warning_recorded(
warning_message: "warnings.WarningMessage",
@@ -895,10 +859,10 @@ def pytest_exception_interact(
"""Called when an exception was raised which can potentially be
interactively handled.
May be called during collection (see :py:func:`pytest_make_collect_report`),
May be called during collection (see :hook:`pytest_make_collect_report`),
in which case ``report`` is a :class:`CollectReport`.
May be called during runtest of an item (see :py:func:`pytest_runtest_protocol`),
May be called during runtest of an item (see :hook:`pytest_runtest_protocol`),
in which case ``report`` is a :class:`TestReport`.
This hook is not called if the exception that was raised is an internal

View File

@@ -92,7 +92,7 @@ class _NodeReporter:
self.xml = xml
self.add_stats = self.xml.add_stats
self.family = self.xml.family
self.duration = 0
self.duration = 0.0
self.properties: List[Tuple[str, str]] = []
self.nodes: List[ET.Element] = []
self.attrs: Dict[str, str] = {}
@@ -256,7 +256,7 @@ class _NodeReporter:
def finalize(self) -> None:
data = self.to_xml()
self.__dict__.clear()
# Type ignored becuase mypy doesn't like overriding a method.
# Type ignored because mypy doesn't like overriding a method.
# Also the return value doesn't match...
self.to_xml = lambda: data # type: ignore[assignment]

View File

@@ -10,13 +10,26 @@ from typing import Union
import attr
from iniconfig import SectionWrapper
import pytest
from _pytest.cacheprovider import Cache
from _pytest.compat import final
from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config import PytestPluginManager
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.pytester import HookRecorder
from _pytest.pytester import Pytester
from _pytest.pytester import RunResult
from _pytest.terminal import TerminalReporter
from _pytest.tmpdir import TempPathFactory
if TYPE_CHECKING:
from typing_extensions import Final
@@ -35,10 +48,10 @@ class Testdir:
__test__ = False
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = Pytester.TimeoutExpired
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
check_ispytest(_ispytest)
self._pytester = pytester
@@ -64,10 +77,10 @@ class Testdir:
self._pytester.plugins = plugins
@property
def monkeypatch(self) -> pytest.MonkeyPatch:
def monkeypatch(self) -> MonkeyPatch:
return self._pytester._monkeypatch
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
"""See :meth:`Pytester.make_hook_recorder`."""
return self._pytester.make_hook_recorder(pluginmanager)
@@ -131,9 +144,7 @@ class Testdir:
"""See :meth:`Pytester.copy_example`."""
return legacy_path(self._pytester.copy_example(name))
def getnode(
self, config: pytest.Config, arg
) -> Optional[Union[pytest.Item, pytest.Collector]]:
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.getnode`."""
return self._pytester.getnode(config, arg)
@@ -141,9 +152,7 @@ class Testdir:
"""See :meth:`Pytester.getpathnode`."""
return self._pytester.getpathnode(path)
def genitems(
self, colitems: List[Union[pytest.Item, pytest.Collector]]
) -> List[pytest.Item]:
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
"""See :meth:`Pytester.genitems`."""
return self._pytester.genitems(colitems)
@@ -165,19 +174,19 @@ class Testdir:
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
)
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest_inprocess`."""
return self._pytester.runpytest_inprocess(*args, **kwargs)
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
def runpytest(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest`."""
return self._pytester.runpytest(*args, **kwargs)
def parseconfig(self, *args) -> pytest.Config:
def parseconfig(self, *args) -> Config:
"""See :meth:`Pytester.parseconfig`."""
return self._pytester.parseconfig(*args)
def parseconfigure(self, *args) -> pytest.Config:
def parseconfigure(self, *args) -> Config:
"""See :meth:`Pytester.parseconfigure`."""
return self._pytester.parseconfigure(*args)
@@ -196,8 +205,8 @@ class Testdir:
)
def collect_by_name(
self, modcol: pytest.Collector, name: str
) -> Optional[Union[pytest.Item, pytest.Collector]]:
self, modcol: Collector, name: str
) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.collect_by_name`."""
return self._pytester.collect_by_name(modcol, name)
@@ -212,11 +221,11 @@ class Testdir:
"""See :meth:`Pytester.popen`."""
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
"""See :meth:`Pytester.run`."""
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
def runpython(self, script) -> pytest.RunResult:
def runpython(self, script) -> RunResult:
"""See :meth:`Pytester.runpython`."""
return self._pytester.runpython(script)
@@ -224,7 +233,7 @@ class Testdir:
"""See :meth:`Pytester.runpython_c`."""
return self._pytester.runpython_c(command)
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
"""See :meth:`Pytester.runpytest_subprocess`."""
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
@@ -245,13 +254,10 @@ class Testdir:
return str(self.tmpdir)
pytest.Testdir = Testdir # type: ignore[attr-defined]
class LegacyTestdirPlugin:
@staticmethod
@pytest.fixture
def testdir(pytester: pytest.Pytester) -> Testdir:
@fixture
def testdir(pytester: Pytester) -> Testdir:
"""
Identical to :fixture:`pytester`, and provides an instance whose methods return
legacy ``LEGACY_PATH`` objects instead when applicable.
@@ -267,10 +273,10 @@ class TempdirFactory:
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
for :class:``TempPathFactory``."""
_tmppath_factory: pytest.TempPathFactory
_tmppath_factory: TempPathFactory
def __init__(
self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False
self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
) -> None:
check_ispytest(_ispytest)
self._tmppath_factory = tmppath_factory
@@ -284,19 +290,16 @@ class TempdirFactory:
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined]
class LegacyTmpdirPlugin:
@staticmethod
@pytest.fixture(scope="session")
def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory:
@fixture(scope="session")
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
"""Return a :class:`pytest.TempdirFactory` instance for the test session."""
# Set dynamically by pytest_configure().
return request.config._tmpdirhandler # type: ignore
@staticmethod
@pytest.fixture
@fixture
def tmpdir(tmp_path: Path) -> LEGACY_PATH:
"""Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
@@ -314,7 +317,7 @@ class LegacyTmpdirPlugin:
return legacy_path(tmp_path)
def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH:
"""Return a directory path object with the given name.
Same as :func:`mkdir`, but returns a legacy py path instance.
@@ -322,7 +325,7 @@ def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
return legacy_path(self.mkdir(name))
def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH:
def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH:
"""(deprecated) The file system path of the test module which collected this test."""
return legacy_path(self.path)
@@ -337,7 +340,7 @@ def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
return legacy_path(self.startpath)
def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
def Config_invocation_dir(self: Config) -> LEGACY_PATH:
"""The directory from which pytest was invoked.
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
@@ -348,7 +351,7 @@ def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
return legacy_path(str(self.invocation_params.dir))
def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
def Config_rootdir(self: Config) -> LEGACY_PATH:
"""The path to the :ref:`rootdir <rootdir>`.
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
@@ -358,7 +361,7 @@ def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
return legacy_path(str(self.rootpath))
def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
def Config_inifile(self: Config) -> Optional[LEGACY_PATH]:
"""The path to the :ref:`configfile <configfiles>`.
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
@@ -368,7 +371,7 @@ def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
return legacy_path(str(self.inipath)) if self.inipath else None
def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
def Session_stardir(self: Session) -> LEGACY_PATH:
"""The path from which pytest was invoked.
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
@@ -400,12 +403,44 @@ def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
self.path = Path(value)
@pytest.hookimpl
def pytest_configure(config: pytest.Config) -> None:
mp = pytest.MonkeyPatch()
config.add_cleanup(mp.undo)
@hookimpl(tryfirst=True)
def pytest_load_initial_conftests(early_config: Config) -> None:
"""Monkeypatch legacy path attributes in several classes, as early as possible."""
mp = MonkeyPatch()
early_config.add_cleanup(mp.undo)
# Add Cache.makedir().
mp.setattr(Cache, "makedir", Cache_makedir, raising=False)
# Add FixtureRequest.fspath property.
mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)
# Add TerminalReporter.startdir property.
mp.setattr(
TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
)
# Add Config.{invocation_dir,rootdir,inifile} properties.
mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False)
mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False)
mp.setattr(Config, "inifile", property(Config_inifile), raising=False)
# Add Session.startdir property.
mp.setattr(Session, "startdir", property(Session_stardir), raising=False)
# Add pathlist configuration type.
mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type)
# Add Node.fspath property.
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
@hookimpl
def pytest_configure(config: Config) -> None:
"""Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed."""
if config.pluginmanager.has_plugin("tmpdir"):
mp = MonkeyPatch()
config.add_cleanup(mp.undo)
# Create TmpdirFactory and attach it to the config object.
#
# This is to comply with existing plugins which expect the handler to be
@@ -422,40 +457,9 @@ def pytest_configure(config: pytest.Config) -> None:
config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
# Add Cache.makedir().
mp.setattr(pytest.Cache, "makedir", Cache_makedir, raising=False)
# Add FixtureRequest.fspath property.
mp.setattr(
pytest.FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False
)
# Add TerminalReporter.startdir property.
mp.setattr(
TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
)
# Add Config.{invocation_dir,rootdir,inifile} properties.
mp.setattr(
pytest.Config, "invocation_dir", property(Config_invocation_dir), raising=False
)
mp.setattr(pytest.Config, "rootdir", property(Config_rootdir), raising=False)
mp.setattr(pytest.Config, "inifile", property(Config_inifile), raising=False)
# Add Session.startdir property.
mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False)
# Add pathlist configuration type.
mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type)
# Add Node.fspath property.
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
@pytest.hookimpl
def pytest_plugin_registered(
plugin: object, manager: pytest.PytestPluginManager
) -> None:
@hookimpl
def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None:
# pytester is not loaded by default and is commonly loaded from a conftest,
# so checking for it in `pytest_configure` is not enough.
is_pytester = plugin is manager.get_plugin("pytester")

View File

@@ -1,9 +1,10 @@
"""Access and control log capturing."""
import io
import logging
import os
import re
import sys
from contextlib import contextmanager
from contextlib import nullcontext
from io import StringIO
from pathlib import Path
from typing import AbstractSet
@@ -13,6 +14,7 @@ from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
@@ -20,7 +22,6 @@ from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.capture import CaptureManager
from _pytest.compat import final
from _pytest.compat import nullcontext
from _pytest.config import _strtobool
from _pytest.config import Config
from _pytest.config import create_terminal_writer
@@ -34,6 +35,11 @@ from _pytest.main import Session
from _pytest.stash import StashKey
from _pytest.terminal import TerminalReporter
if TYPE_CHECKING:
logging_StreamHandler = logging.StreamHandler[StringIO]
else:
logging_StreamHandler = logging.StreamHandler
DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
@@ -322,11 +328,9 @@ class catching_logs:
root_logger.removeHandler(self.handler)
class LogCaptureHandler(logging.StreamHandler):
class LogCaptureHandler(logging_StreamHandler):
"""A logging handler that stores log records and the log text."""
stream: StringIO
def __init__(self) -> None:
"""Create a new log handler."""
super().__init__(StringIO())
@@ -621,20 +625,11 @@ class LoggingPlugin:
if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True)
stream = fpath.open(mode="w", encoding="UTF-8")
if sys.version_info >= (3, 7):
old_stream = self.log_file_handler.setStream(stream)
else:
old_stream = self.log_file_handler.stream
self.log_file_handler.acquire()
try:
self.log_file_handler.flush()
self.log_file_handler.stream = stream
finally:
self.log_file_handler.release()
# https://github.com/python/mypy/issues/11193
stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment]
old_stream = self.log_file_handler.setStream(stream)
if old_stream:
# https://github.com/python/typeshed/pull/5663
old_stream.close() # type:ignore[attr-defined]
old_stream.close()
def _log_cli_enabled(self):
"""Return whether live logging is enabled."""
@@ -758,7 +753,7 @@ class _FileHandler(logging.FileHandler):
pass
class _LiveLoggingStreamHandler(logging.StreamHandler):
class _LiveLoggingStreamHandler(logging_StreamHandler):
"""A logging StreamHandler used by the live logging feature: it will
write a newline before the first log message in each test.

View File

@@ -605,8 +605,7 @@ class Session(nodes.FSCollector):
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
"""Perform the collection phase for this session.
This is called by the default
:func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook
This is called by the default :hook:`pytest_collection` hook
implementation; see the documentation of this hook for more details.
For testing purposes, it may also be called directly on a fresh
``Session``.
@@ -690,9 +689,8 @@ class Session(nodes.FSCollector):
# No point in finding packages when collecting doctests.
if not self.config.getoption("doctestmodules", False):
pm = self.config.pluginmanager
confcutdir = pm._confcutdir
for parent in (argpath, *argpath.parents):
if confcutdir and parent in confcutdir.parents:
if not pm._is_in_confcutdir(argpath):
break
if parent.is_dir():
@@ -872,7 +870,10 @@ def resolve_collection_argument(
If the path doesn't exist, raise UsageError.
If the path is a directory and selection parts are present, raise UsageError.
"""
strpath, *parts = str(arg).split("::")
base, squacket, rest = str(arg).partition("[")
strpath, *parts = base.split("::")
if parts:
parts[-1] = f"{parts[-1]}{squacket}{rest}"
if as_pypath:
strpath = search_pypath(strpath)
fspath = invocation_path / strpath

View File

@@ -1,5 +1,4 @@
"""Generic mechanism for marking and selecting python functions."""
import warnings
from typing import AbstractSet
from typing import Collection
from typing import List
@@ -23,8 +22,6 @@ from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.config.argparsing import Parser
from _pytest.deprecated import MINUS_K_COLON
from _pytest.deprecated import MINUS_K_DASH
from _pytest.stash import StashKey
if TYPE_CHECKING:
@@ -189,27 +186,14 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
if not keywordexpr:
return
if keywordexpr.startswith("-"):
# To be removed in pytest 8.0.0.
warnings.warn(MINUS_K_DASH, stacklevel=2)
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
if keywordexpr[-1:] == ":":
# To be removed in pytest 8.0.0.
warnings.warn(MINUS_K_COLON, stacklevel=2)
selectuntil = True
keywordexpr = keywordexpr[:-1]
expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'")
remaining = []
deselected = []
for colitem in items:
if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)):
if not expr.evaluate(KeywordMatcher.from_item(colitem)):
deselected.append(colitem)
else:
if selectuntil:
keywordexpr = None
remaining.append(colitem)
if deselected:

View File

@@ -190,7 +190,7 @@ class MatcherAdapter(Mapping[str, bool]):
class Expression:
"""A compiled match expression as used by -k and -m.
The expression can be evaulated against different matchers.
The expression can be evaluated against different matchers.
"""
__slots__ = ("code",)

View File

@@ -72,16 +72,11 @@ def get_empty_parameterset_mark(
return mark
class ParameterSet(
NamedTuple(
"ParameterSet",
[
("values", Sequence[Union[object, NotSetType]]),
("marks", Collection[Union["MarkDecorator", "Mark"]]),
("id", Optional[str]),
],
)
):
class ParameterSet(NamedTuple):
values: Sequence[Union[object, NotSetType]]
marks: Collection[Union["MarkDecorator", "Mark"]]
id: Optional[str]
@classmethod
def param(
cls,

View File

@@ -55,7 +55,7 @@ def resolve(name: str) -> object:
parts = name.split(".")
used = parts.pop(0)
found = __import__(used)
found: object = __import__(used)
for part in parts:
used += "." + part
try:

View File

@@ -145,7 +145,10 @@ class NodeMeta(type):
warnings.warn(
PytestDeprecationWarning(
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}"
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
"See https://docs.pytest.org/en/stable/deprecations.html"
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
"for more details."
)
)
@@ -653,20 +656,6 @@ class Item(Node):
nextitem = None
def __init_subclass__(cls) -> None:
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def __init__(
self,
name,
@@ -694,6 +683,37 @@ class Item(Node):
#: for this test.
self.user_properties: List[Tuple[str, object]] = []
self._check_item_and_collector_diamond_inheritance()
def _check_item_and_collector_diamond_inheritance(self) -> None:
"""
Check if the current type inherits from both File and Collector
at the same time, emitting a warning accordingly (#8447).
"""
cls = type(self)
# We inject an attribute in the type to avoid issuing this warning
# for the same class more than once, which is not helpful.
# It is a hack, but was deemed acceptable in order to avoid
# flooding the user in the common case.
attr_name = "_pytest_diamond_inheritance_warning_shown"
if getattr(cls, attr_name, False):
return
setattr(cls, attr_name, True)
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def runtest(self) -> None:
"""Run the test case for this item.

View File

@@ -539,6 +539,9 @@ def import_path(
ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "")
if ignore != "1":
module_file = mod.__file__
if module_file is None:
raise ImportPathMismatchError(module_name, module_file, path)
if module_file.endswith((".pyc", ".pyo")):
module_file = module_file[:-1]
if module_file.endswith(os.path.sep + "__init__.py"):
@@ -562,7 +565,6 @@ if sys.platform.startswith("win"):
def _is_same(f1: str, f2: str) -> bool:
return Path(f1) == Path(f2) or os.path.samefile(f1, f2)
else:
def _is_same(f1: str, f2: str) -> bool:
@@ -601,11 +603,20 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
module_parts = module_name.split(".")
while module_name:
if module_name not in modules:
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
try:
# If sys.meta_path is empty, calling import_module will issue
# a warning and raise ModuleNotFoundError. To avoid the
# warning, we check sys.meta_path explicitly and raise the error
# ourselves to fall back to creating a dummy module.
if not sys.meta_path:
raise ModuleNotFoundError
importlib.import_module(module_name)
except ModuleNotFoundError:
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
module_parts.pop(-1)
module_name = ".".join(module_parts)

View File

@@ -128,7 +128,7 @@ class LsofFdLeakChecker:
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
check=True,
universal_newlines=True,
text=True,
).stdout
def isopen(line: str) -> bool:
@@ -477,7 +477,9 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
@fixture
def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
def pytester(
request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch
) -> "Pytester":
"""
Facilities to write tests/configuration files, execute pytest in isolation, and match
against expected output, perfect for black-box testing of pytest plugins.
@@ -488,7 +490,7 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path`
fixture but provides methods which aid in testing pytest itself.
"""
return Pytester(request, tmp_path_factory, _ispytest=True)
return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True)
@fixture
@@ -596,11 +598,15 @@ class RunResult:
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: int = 0,
deselected: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
"""
Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
``warnings`` and ``deselected`` are only checked if not None.
"""
__tracebackhide__ = True
from _pytest.pytester_assertions import assert_outcomes
@@ -679,6 +685,7 @@ class Pytester:
self,
request: FixtureRequest,
tmp_path_factory: TempPathFactory,
monkeypatch: MonkeyPatch,
*,
_ispytest: bool = False,
) -> None:
@@ -702,7 +709,7 @@ class Pytester:
self._method = self._request.config.getoption("--runpytest")
self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
self._monkeypatch = mp = MonkeyPatch()
self._monkeypatch = mp = monkeypatch
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
# Ensure no unexpected caching via tox.
mp.delenv("TOX_ENV_DIR", raising=False)
@@ -734,7 +741,6 @@ class Pytester:
self._sys_modules_snapshot.restore()
self._sys_path_snapshot.restore()
self._cwd_snapshot.restore()
self._monkeypatch.undo()
def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
# Some zope modules used by twisted-related tests keep internal state
@@ -1288,7 +1294,7 @@ class Pytester:
) -> Optional[Union[Item, Collector]]:
"""Return the collection node for name from the module collection.
Searchs a module collection node for a collection node matching the
Searches a module collection node for a collection node matching the
given name.
:param modcol: A module collection node; see :py:meth:`getmodulecol`.

View File

@@ -4,6 +4,7 @@
# hence cannot be subject to assertion rewriting, which requires a
# module to not be already imported.
from typing import Dict
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
@@ -42,8 +43,8 @@ def assert_outcomes(
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: int = 0,
deselected: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
@@ -56,8 +57,6 @@ def assert_outcomes(
"errors": outcomes.get("errors", 0),
"xpassed": outcomes.get("xpassed", 0),
"xfailed": outcomes.get("xfailed", 0),
"warnings": outcomes.get("warnings", 0),
"deselected": outcomes.get("deselected", 0),
}
expected = {
"passed": passed,
@@ -66,7 +65,11 @@ def assert_outcomes(
"errors": errors,
"xpassed": xpassed,
"xfailed": xfailed,
"warnings": warnings,
"deselected": deselected,
}
if warnings is not None:
obtained["warnings"] = outcomes.get("warnings", 0)
expected["warnings"] = warnings
if deselected is not None:
obtained["deselected"] = outcomes.get("deselected", 0)
expected["deselected"] = deselected
assert obtained == expected

View File

@@ -224,11 +224,15 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
@hookimpl(trylast=True)
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
def pytest_pycollect_makeitem(
collector: Union["Module", "Class"], name: str, obj: object
) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]:
assert isinstance(collector, (Class, Module)), type(collector)
# Nothing was collected elsewhere, let's do it here.
if safe_isclass(obj):
if collector.istestclass(obj, name):
return Class.from_parent(collector, name=name, obj=obj)
klass: Class = Class.from_parent(collector, name=name, obj=obj)
return klass
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it.
obj = getattr(obj, "__func__", obj)
@@ -247,15 +251,16 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Function.from_parent(collector, name=name)
res: Function = Function.from_parent(collector, name=name)
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
name=name
)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestCollectionWarning(reason))
return res
else:
res = list(collector._genfunctions(name, obj))
return res
return list(collector._genfunctions(name, obj))
return None
class PyobjMixin(nodes.Node):
@@ -278,6 +283,16 @@ class PyobjMixin(nodes.Node):
node = self.getparent(Class)
return node.obj if node is not None else None
@property
def instance(self):
"""Python instance object the function is bound to.
Returns None if not a test method, e.g. for a standalone test function,
a staticmethod, a class or a module.
"""
node = self.getparent(Function)
return getattr(node.obj, "__self__", None) if node is not None else None
@property
def obj(self):
"""Underlying Python object."""
@@ -288,6 +303,9 @@ class PyobjMixin(nodes.Node):
# used to avoid Function marker duplication
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
# This assumes that `obj` is called before there is a chance
# to add custom keys to `self.keywords`, so no fear of overriding.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
return obj
@obj.setter
@@ -325,6 +343,7 @@ class PyobjMixin(nodes.Node):
if isinstance(compat_co_firstlineno, int):
# nose compatibility
file_path = sys.modules[obj.__module__].__file__
assert file_path is not None
if file_path.endswith(".pyc"):
file_path = file_path[:-1]
path: Union["os.PathLike[str]", str] = file_path
@@ -409,7 +428,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
for basecls in self.obj.__mro__:
dicts.append(basecls.__dict__)
# In each class, nodes should be definition ordered. Since Python 3.6,
# In each class, nodes should be definition ordered.
# __dict__ is definition ordered.
seen: Set[str] = set()
dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
@@ -517,12 +536,18 @@ class Module(nodes.File, PyCollector):
self.obj, ("setUpModule", "setup_module")
)
if setup_module is None and has_nose:
# The name "setup" is too common - only treat as fixture if callable.
setup_module = _get_first_non_fixture_func(self.obj, ("setup",))
if not callable(setup_module):
setup_module = None
teardown_module = _get_first_non_fixture_func(
self.obj, ("tearDownModule", "teardown_module")
)
if teardown_module is None and has_nose:
teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",))
# Same as "setup" above - only treat as fixture if callable.
if not callable(teardown_module):
teardown_module = None
if setup_module is None and teardown_module is None:
return
@@ -880,11 +905,7 @@ class InstanceDummy:
only to ignore it; this dummy class keeps them working. This will be removed
in pytest 8."""
pass
# Note: module __getattr__ only works on Python>=3.7. Unfortunately
# we can't provide this deprecation warning on Python 3.6.
def __getattr__(name: str) -> object:
if name == "Instance":
warnings.warn(INSTANCE_COLLECTOR, 2)
@@ -906,6 +927,159 @@ def hasnew(obj: object) -> bool:
return False
@final
@attr.s(frozen=True, auto_attribs=True, slots=True)
class IdMaker:
"""Make IDs for a parametrization."""
# The argnames of the parametrization.
argnames: Sequence[str]
# The ParameterSets of the parametrization.
parametersets: Sequence[ParameterSet]
# Optionally, a user-provided callable to make IDs for parameters in a
# ParameterSet.
idfn: Optional[Callable[[Any], Optional[object]]]
# Optionally, explicit IDs for ParameterSets by index.
ids: Optional[Sequence[Optional[object]]]
# Optionally, the pytest config.
# Used for controlling ASCII escaping, and for calling the
# :hook:`pytest_make_parametrize_id` hook.
config: Optional[Config]
# Optionally, the ID of the node being parametrized.
# Used only for clearer error messages.
nodeid: Optional[str]
# Optionally, the ID of the function being parametrized.
# Used only for clearer error messages.
func_name: Optional[str]
def make_unique_parameterset_ids(self) -> List[str]:
"""Make a unique identifier for each ParameterSet, that may be used to
identify the parametrization in a node ID.
Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
- user-provided id, if given
- else an id derived from the value, applicable for certain types
- else <argname><parameterset index>
The counter suffix is appended only in case a string wouldn't be unique
otherwise.
"""
resolved_ids = list(self._resolve_ids())
# All IDs must be unique!
if len(resolved_ids) != len(set(resolved_ids)):
# Record the number of occurrences of each ID.
id_counts = Counter(resolved_ids)
# Map the ID to its next suffix.
id_suffixes: Dict[str, int] = defaultdict(int)
# Suffix non-unique IDs to make them unique.
for index, id in enumerate(resolved_ids):
if id_counts[id] > 1:
resolved_ids[index] = f"{id}{id_suffixes[id]}"
id_suffixes[id] += 1
return resolved_ids
def _resolve_ids(self) -> Iterable[str]:
"""Resolve IDs for all ParameterSets (may contain duplicates)."""
for idx, parameterset in enumerate(self.parametersets):
if parameterset.id is not None:
# ID provided directly - pytest.param(..., id="...")
yield parameterset.id
elif self.ids and idx < len(self.ids) and self.ids[idx] is not None:
# ID provided in the IDs list - parametrize(..., ids=[...]).
yield self._idval_from_value_required(self.ids[idx], idx)
else:
# ID not provided - generate it.
yield "-".join(
self._idval(val, argname, idx)
for val, argname in zip(parameterset.values, self.argnames)
)
def _idval(self, val: object, argname: str, idx: int) -> str:
"""Make an ID for a parameter in a ParameterSet."""
idval = self._idval_from_function(val, argname, idx)
if idval is not None:
return idval
idval = self._idval_from_hook(val, argname)
if idval is not None:
return idval
idval = self._idval_from_value(val)
if idval is not None:
return idval
return self._idval_from_argname(argname, idx)
def _idval_from_function(
self, val: object, argname: str, idx: int
) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet using the
user-provided id callable, if given."""
if self.idfn is None:
return None
try:
id = self.idfn(val)
except Exception as e:
prefix = f"{self.nodeid}: " if self.nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
if id is None:
return None
return self._idval_from_value(id)
def _idval_from_hook(self, val: object, argname: str) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet by calling the
:hook:`pytest_make_parametrize_id` hook."""
if self.config:
id: Optional[str] = self.config.hook.pytest_make_parametrize_id(
config=self.config, val=val, argname=argname
)
return id
return None
def _idval_from_value(self, val: object) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet from its value,
if the value type is supported."""
if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, self.config)
elif val is None or isinstance(val, (float, int, bool, complex)):
return str(val)
elif isinstance(val, Pattern):
return ascii_escaped(val.pattern)
elif val is NOTSET:
# Fallback to default. Note that NOTSET is an enum.Enum.
pass
elif isinstance(val, enum.Enum):
return str(val)
elif isinstance(getattr(val, "__name__", None), str):
# Name of a class, function, module, etc.
name: str = getattr(val, "__name__")
return name
return None
def _idval_from_value_required(self, val: object, idx: int) -> str:
"""Like _idval_from_value(), but fails if the type is not supported."""
id = self._idval_from_value(val)
if id is not None:
return id
# Fail.
if self.func_name is not None:
prefix = f"In {self.func_name}: "
elif self.nodeid is not None:
prefix = f"In {self.nodeid}: "
else:
prefix = ""
msg = (
f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. "
"Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
)
fail(msg, pytrace=False)
@staticmethod
def _idval_from_argname(argname: str, idx: int) -> str:
"""Make an ID for a parameter in a ParameterSet from the argument name
and the index of the ParameterSet."""
return str(argname) + str(idx)
@final
@attr.s(frozen=True, slots=True, auto_attribs=True)
class CallSpec2:
@@ -980,7 +1154,7 @@ class CallSpec2:
@final
class Metafunc:
"""Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
"""Objects passed to the :hook:`pytest_generate_tests` hook.
They help to inspect a test function and to generate tests according to
test configuration or values specified in the class or module where a
@@ -1028,10 +1202,7 @@ class Metafunc:
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
indirect: Union[bool, Sequence[str]] = False,
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
] = None,
scope: "Optional[_ScopeName]" = None,
*,
@@ -1098,7 +1269,7 @@ class Metafunc:
It will also override any fixture-function defined scope, allowing
to set a dynamic scope using test context or configuration.
"""
argnames, parameters = ParameterSet._for_parametrize(
argnames, parametersets = ParameterSet._for_parametrize(
argnames,
argvalues,
self.function,
@@ -1130,8 +1301,8 @@ class Metafunc:
if generated_ids is not None:
ids = generated_ids
ids = self._resolve_arg_ids(
argnames, ids, parameters, nodeid=self.definition.nodeid
ids = self._resolve_parameter_set_ids(
argnames, ids, parametersets, nodeid=self.definition.nodeid
)
# Store used (possibly generated) ids with parametrize Marks.
@@ -1143,7 +1314,9 @@ class Metafunc:
# of all calls.
newcalls = []
for callspec in self._calls or [CallSpec2()]:
for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
for param_index, (param_id, param_set) in enumerate(
zip(ids, parametersets)
):
newcallspec = callspec.setmulti(
valtypes=arg_values_types,
argnames=argnames,
@@ -1156,27 +1329,29 @@ class Metafunc:
newcalls.append(newcallspec)
self._calls = newcalls
def _resolve_arg_ids(
def _resolve_parameter_set_ids(
self,
argnames: Sequence[str],
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
],
parameters: Sequence[ParameterSet],
parametersets: Sequence[ParameterSet],
nodeid: str,
) -> List[str]:
"""Resolve the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.
"""Resolve the actual ids for the given parameter sets.
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param ids: The ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``.
:param str str: The nodeid of the item that generated this parametrized call.
:rtype: List[str]
:returns: The list of ids for each argname given.
:param argnames:
Argument names passed to ``parametrize()``.
:param ids:
The `ids` parameter of the ``parametrize()`` call (see docs).
:param parametersets:
The parameter sets, each containing a set of values corresponding
to ``argnames``.
:param nodeid str:
The nodeid of the definition item that generated this
parametrization.
:returns:
List with ids for each parameter set given.
"""
if ids is None:
idfn = None
@@ -1186,15 +1361,24 @@ class Metafunc:
ids_ = None
else:
idfn = None
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
id_maker = IdMaker(
argnames,
parametersets,
idfn,
ids_,
self.config,
nodeid=nodeid,
func_name=self.function.__name__,
)
return id_maker.make_unique_parameterset_ids()
def _validate_ids(
self,
ids: Iterable[Union[None, str, float, int, bool]],
parameters: Sequence[ParameterSet],
ids: Iterable[Optional[object]],
parametersets: Sequence[ParameterSet],
func_name: str,
) -> List[Union[None, str]]:
) -> List[Optional[object]]:
try:
num_ids = len(ids) # type: ignore[arg-type]
except TypeError:
@@ -1202,29 +1386,14 @@ class Metafunc:
iter(ids)
except TypeError as e:
raise TypeError("ids must be a callable or an iterable") from e
num_ids = len(parameters)
num_ids = len(parametersets)
# num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
if num_ids != len(parameters) and num_ids != 0:
if num_ids != len(parametersets) and num_ids != 0:
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
fail(msg.format(func_name, len(parameters), num_ids), pytrace=False)
fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
new_ids = []
for idx, id_value in enumerate(itertools.islice(ids, num_ids)):
if id_value is None or isinstance(id_value, str):
new_ids.append(id_value)
elif isinstance(id_value, (float, int, bool)):
new_ids.append(str(id_value))
else:
msg = ( # type: ignore[unreachable]
"In {}: ids must be list of string/float/int/bool, "
"found: {} (type: {!r}) at index {}"
)
fail(
msg.format(func_name, saferepr(id_value), type(id_value), idx),
pytrace=False,
)
return new_ids
return list(itertools.islice(ids, num_ids))
def _resolve_arg_value_types(
self,
@@ -1344,105 +1513,6 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
return val if escape_option else ascii_escaped(val) # type: ignore
def _idval(
val: object,
argname: str,
idx: int,
idfn: Optional[Callable[[Any], Optional[object]]],
nodeid: Optional[str],
config: Optional[Config],
) -> str:
if idfn:
try:
generated_id = idfn(val)
if generated_id is not None:
val = generated_id
except Exception as e:
prefix = f"{nodeid}: " if nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
elif config:
hook_id: Optional[str] = config.hook.pytest_make_parametrize_id(
config=config, val=val, argname=argname
)
if hook_id:
return hook_id
if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, config)
elif val is None or isinstance(val, (float, int, bool, complex)):
return str(val)
elif isinstance(val, Pattern):
return ascii_escaped(val.pattern)
elif val is NOTSET:
# Fallback to default. Note that NOTSET is an enum.Enum.
pass
elif isinstance(val, enum.Enum):
return str(val)
elif isinstance(getattr(val, "__name__", None), str):
# Name of a class, function, module, etc.
name: str = getattr(val, "__name__")
return name
return str(argname) + str(idx)
def _idvalset(
idx: int,
parameterset: ParameterSet,
argnames: Iterable[str],
idfn: Optional[Callable[[Any], Optional[object]]],
ids: Optional[List[Union[None, str]]],
nodeid: Optional[str],
config: Optional[Config],
) -> str:
if parameterset.id is not None:
return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None:
this_id = [
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
for val, argname in zip(parameterset.values, argnames)
]
return "-".join(this_id)
else:
return _ascii_escaped_by_config(id, config)
def idmaker(
argnames: Iterable[str],
parametersets: Iterable[ParameterSet],
idfn: Optional[Callable[[Any], Optional[object]]] = None,
ids: Optional[List[Union[None, str]]] = None,
config: Optional[Config] = None,
nodeid: Optional[str] = None,
) -> List[str]:
resolved_ids = [
_idvalset(
valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
)
for valindex, parameterset in enumerate(parametersets)
]
# All IDs must be unique!
unique_ids = set(resolved_ids)
if len(unique_ids) != len(resolved_ids):
# Record the number of occurrences of each test ID.
test_id_counts = Counter(resolved_ids)
# Map the test ID to its next suffix.
test_id_suffixes: Dict[str, int] = defaultdict(int)
# Suffix non-unique IDs to make them unique.
for index, test_id in enumerate(resolved_ids):
if test_id_counts[test_id] > 1:
resolved_ids[index] = f"{test_id}{test_id_suffixes[test_id]}"
test_id_suffixes[test_id] += 1
return resolved_ids
def _pretty_fixture_path(func) -> str:
cwd = Path.cwd()
loc = Path(getlocation(func, str(cwd)))
@@ -1614,7 +1684,7 @@ class Function(PyobjMixin, nodes.Item):
config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None,
callobj=NOTSET,
keywords=None,
keywords: Optional[Mapping[str, Any]] = None,
session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None,
@@ -1635,31 +1705,20 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name.
self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec:
self.callspec = callspec
# this is total hostile and a mess
# keywords are broken by design by now
# this will be redeemed later
for mark in callspec.marks:
# feel free to cry, this was broken for years before
# and keywords cant fix it per design
self.keywords[mark.name] = mark
self.own_markers.extend(normalize_mark_list(callspec.marks))
if keywords:
self.keywords.update(keywords)
self.own_markers.extend(callspec.marks)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
self.keywords.update(
{
mark.name: True
for mark in self.iter_markers()
if mark.name not in self.keywords
}
)
# Note: the order of the updates is important here; indicates what
# takes priority (ctor argument over function attributes over markers).
# Take own_markers only; NodeKeywords handles parent traversal on its own.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
self.keywords.update(self.obj.__dict__)
if keywords:
self.keywords.update(keywords)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
@@ -1683,15 +1742,6 @@ class Function(PyobjMixin, nodes.Item):
"""Underlying python 'function' object."""
return getimfunc(self.obj)
@property
def instance(self):
"""Python instance object the function is bound to.
Returns None if not a test method, e.g. for a standalone test function
or a staticmethod.
"""
return getattr(self.obj, "__self__", None)
def _getobj(self):
assert self.parent is not None
if isinstance(self.parent, Class):

View File

@@ -1,5 +1,6 @@
import math
import pprint
from collections.abc import Collection
from collections.abc import Sized
from decimal import Decimal
from numbers import Complex
@@ -8,7 +9,6 @@ from typing import Any
from typing import Callable
from typing import cast
from typing import Generic
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
@@ -101,6 +101,7 @@ class ApproxBase:
)
def __bool__(self):
__tracebackhide__ = True
raise AssertionError(
"approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?"
)
@@ -130,7 +131,6 @@ class ApproxBase:
# a numeric type. For this reason, the default is to do nothing. The
# classes that deal with sequences should reimplement this method to
# raise if there are any non-numeric elements in the sequence.
pass
def _recursive_list_map(f, x):
@@ -306,12 +306,12 @@ class ApproxMapping(ApproxBase):
raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
class ApproxSequencelike(ApproxBase):
class ApproxSequenceLike(ApproxBase):
"""Perform approximate comparisons where the expected value is a sequence of numbers."""
def __repr__(self) -> str:
seq_type = type(self.expected)
if seq_type not in (tuple, list, set):
if seq_type not in (tuple, list):
seq_type = list
return "approx({!r})".format(
seq_type(self._approx_scalar(x) for x in self.expected)
@@ -515,7 +515,7 @@ class ApproxDecimal(ApproxScalar):
def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
"""Assert that two numbers (or two sets of numbers) are equal to each other
"""Assert that two numbers (or two ordered sequences of numbers) are equal to each other
within some tolerance.
Due to the :std:doc:`tutorial/floatingpoint`, numbers that we
@@ -547,16 +547,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> 0.1 + 0.2 == approx(0.3)
True
The same syntax also works for sequences of numbers::
The same syntax also works for ordered sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
Dictionary *values*::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
``numpy`` arrays::
>>> import numpy as np # doctest: +SKIP
@@ -569,6 +564,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
True
Only ordered sequences are supported, because ``approx`` needs
to infer the relative position of the sequences without ambiguity. This means
``sets`` and other unordered sequences are not supported.
Finally, dictionary *values* can also be compared::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
The comparision will be true if both mappings have the same keys and their
respective values match the expected tolerances.
**Tolerances**
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
@@ -708,12 +717,19 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
expected = _as_numpy_array(expected)
cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
hasattr(expected, "__getitem__")
and isinstance(expected, Sized)
# Type ignored because the error is wrong -- not unreachable.
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
):
cls = ApproxSequencelike
cls = ApproxSequenceLike
elif (
isinstance(expected, Collection)
# Type ignored because the error is wrong -- not unreachable.
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
):
msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}"
raise TypeError(msg)
else:
cls = ApproxScalar

View File

@@ -7,6 +7,7 @@ from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type
@@ -254,7 +255,7 @@ class TestReport(BaseReport):
self,
nodeid: str,
location: Tuple[str, Optional[int], str],
keywords,
keywords: Mapping[str, Any],
outcome: "Literal['passed', 'failed', 'skipped']",
longrepr: Union[
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr

View File

@@ -2,7 +2,6 @@
import bdb
import os
import sys
import warnings
from typing import Callable
from typing import cast
from typing import Dict
@@ -28,7 +27,6 @@ from _pytest._code.code import TerminalRepr
from _pytest.compat import final
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
@@ -379,11 +377,6 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
# Type ignored because unittest is loaded dynamically.
skip_exceptions.append(unittest.SkipTest) # type: ignore
if isinstance(call.excinfo.value, tuple(skip_exceptions)):
if unittest is not None and isinstance(
call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined]
):
warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2)
outcome = "skipped"
r_ = collector._repr_failure_py(call.excinfo, "line")
assert isinstance(r_, ExceptionChainRepr), repr(r_)

View File

@@ -542,15 +542,21 @@ class TerminalReporter:
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
if rep.skipped or hasattr(report, "wasxfail"):
available_width = (
(self._tw.fullwidth - self._tw.width_of_current_line)
- len(" [100%]")
- 1
)
reason = _get_raw_skip_reason(rep)
reason_ = _format_trimmed(" ({})", reason, available_width)
if reason and reason_ is not None:
self._tw.write(reason_)
if self.config.option.verbose < 2:
available_width = (
(self._tw.fullwidth - self._tw.width_of_current_line)
- len(" [100%]")
- 1
)
formatted_reason = _format_trimmed(
" ({})", reason, available_width
)
else:
formatted_reason = f" ({reason})"
if reason and formatted_reason is not None:
self._tw.write(formatted_reason)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
@@ -657,7 +663,7 @@ class TerminalReporter:
errors = len(self.stats.get("error", []))
skipped = len(self.stats.get("skipped", []))
deselected = len(self.stats.get("deselected", []))
selected = self._numcollected - errors - skipped - deselected
selected = self._numcollected - deselected
line = "collected " if final else "collecting "
line += (
str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
@@ -668,7 +674,7 @@ class TerminalReporter:
line += " / %d deselected" % deselected
if skipped:
line += " / %d skipped" % skipped
if self._numcollected > selected > 0:
if self._numcollected > selected:
line += " / %d selected" % selected
if self.isatty:
self.rewrite(line, bold=True, erase=True)

View File

@@ -27,7 +27,7 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import PyCollector
from _pytest.python import Module
from _pytest.runner import CallInfo
from _pytest.scope import Scope
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
def pytest_pycollect_makeitem(
collector: PyCollector, name: str, obj: object
collector: Union[Module, Class], name: str, obj: object
) -> Optional["UnitTestCase"]:
# Has unittest been imported and is obj a subclass of its TestCase?
try:
@@ -185,6 +185,15 @@ class TestCaseFunction(Function):
_excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
_testcase: Optional["unittest.TestCase"] = None
def _getobj(self):
assert self.parent is not None
# Unlike a regular Function in a Class, where `item.obj` returns
# a *bound* method (attached to an instance), TestCaseFunction's
# `obj` returns an *unbound* method (not attached to an instance).
# This inconsistency is probably not desirable, but needs some
# consideration before changing.
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]
def setup(self) -> None:
# A bound method to be called during teardown() if set (see 'runtest()').
self._explicit_tearDown: Optional[Callable[[], None]] = None

View File

@@ -48,13 +48,6 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
__module__ = "pytest"
@final
class PytestRemovedIn7Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 7."""
__module__ = "pytest"
@final
class PytestRemovedIn8Warning(PytestDeprecationWarning):
"""Warning class for features that will be removed in pytest 8."""

View File

@@ -49,8 +49,6 @@ def catch_warnings_for_item(
warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning)
warnings.filterwarnings("error", category=pytest.PytestRemovedIn7Warning)
apply_warning_filters(config_filters, cmdline_filters)
# apply filters from "filterwarnings" marks
@@ -63,14 +61,6 @@ def catch_warnings_for_item(
yield
for warning_message in log:
ihook.pytest_warning_captured.call_historic(
kwargs=dict(
warning_message=warning_message,
when=when,
item=item,
location=None,
)
)
ihook.pytest_warning_recorded.call_historic(
kwargs=dict(
warning_message=warning_message,
@@ -91,6 +81,23 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
warning_message.lineno,
warning_message.line,
)
if warning_message.source is not None:
try:
import tracemalloc
except ImportError:
pass
else:
tb = tracemalloc.get_object_traceback(warning_message.source)
if tb is not None:
formatted_tb = "\n".join(tb.format())
# Use a leading new line to better separate the (large) output
# from the traceback to the previous warning text.
msg += f"\nObject allocated at:\n{formatted_tb}"
else:
# No need for a leading new line.
url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings"
msg += "Enable tracemalloc to get traceback where the object was allocated.\n"
msg += f"See {url} for more info."
return msg

View File

@@ -1,6 +1,5 @@
# PYTHON_ARGCOMPLETE_OK
"""pytest: unit and functional testing with Python."""
from . import collect
from _pytest import __version__
from _pytest import version_tuple
from _pytest._code import ExceptionInfo
@@ -19,12 +18,13 @@ from _pytest.config import UsageError
from _pytest.config.argparsing import OptionGroup
from _pytest.config.argparsing import Parser
from _pytest.debugging import pytestPDB as __pytestPDB
from _pytest.fixtures import _fillfuncargs
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureRequest
from _pytest.fixtures import yield_fixture
from _pytest.freeze_support import freeze_includes
from _pytest.legacypath import TempdirFactory
from _pytest.legacypath import Testdir
from _pytest.logging import LogCaptureFixture
from _pytest.main import Session
from _pytest.mark import Mark
@@ -68,7 +68,6 @@ from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestConfigWarning
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import PytestExperimentalApiWarning
from _pytest.warning_types import PytestRemovedIn7Warning
from _pytest.warning_types import PytestRemovedIn8Warning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
@@ -81,14 +80,12 @@ set_trace = __pytestPDB.set_trace
__all__ = [
"__version__",
"_fillfuncargs",
"approx",
"Cache",
"CallInfo",
"CaptureFixture",
"Class",
"cmdline",
"collect",
"Collector",
"CollectReport",
"Config",
@@ -129,7 +126,6 @@ __all__ = [
"PytestConfigWarning",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
"PytestRemovedIn7Warning",
"PytestRemovedIn8Warning",
"Pytester",
"PytestPluginManager",
@@ -148,7 +144,9 @@ __all__ = [
"Stash",
"StashKey",
"version_tuple",
"TempdirFactory",
"TempPathFactory",
"Testdir",
"TestReport",
"UsageError",
"WarningsRecorder",

View File

@@ -1,38 +0,0 @@
import sys
import warnings
from types import ModuleType
from typing import Any
from typing import List
import pytest
from _pytest.deprecated import PYTEST_COLLECT_MODULE
COLLECT_FAKEMODULE_ATTRIBUTES = [
"Collector",
"Module",
"Function",
"Session",
"Item",
"Class",
"File",
"_fillfuncargs",
]
class FakeCollectModule(ModuleType):
def __init__(self) -> None:
super().__init__("pytest.collect")
self.__all__ = list(COLLECT_FAKEMODULE_ATTRIBUTES)
self.__pytest = pytest
def __dir__(self) -> List[str]:
return dir(super()) + self.__all__
def __getattr__(self, name: str) -> Any:
if name not in self.__all__:
raise AttributeError(name)
warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2)
return getattr(pytest, name)
sys.modules["pytest.collect"] = FakeCollectModule()

View File

@@ -1238,8 +1238,6 @@ def test_pdb_can_be_rewritten(pytester: Pytester) -> None:
" def check():",
"> assert 1 == 2",
"E assert 1 == 2",
"E +1",
"E -2",
"",
"pdb.py:2: AssertionError",
"*= 1 failed in *",
@@ -1281,7 +1279,7 @@ def test_tee_stdio_captures_and_live_prints(pytester: Pytester) -> None:
reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
)
def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
"""Ensure that the broken pipe error message is supressed.
"""Ensure that the broken pipe error message is suppressed.
In some Python versions, it reaches sys.unraisablehook, in others
a BrokenPipeError exception is propagated, but either way it prints

View File

@@ -1,16 +1,13 @@
# flake8: noqa
# disable flake check on this file because some constructs are strange
# or redundant on purpose and can't be disable on a line-by-line basis
import ast
import inspect
import linecache
import sys
import textwrap
from pathlib import Path
from types import CodeType
from typing import Any
from typing import Dict
from typing import Optional
import pytest
from _pytest._code import Code
@@ -332,8 +329,7 @@ def test_findsource(monkeypatch) -> None:
lines = ["if 1:\n", " def x():\n", " pass\n"]
co = compile("".join(lines), filename, "exec")
# Type ignored because linecache.cache is private.
monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) # type: ignore[attr-defined]
monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename))
src, lineno = findsource(co)
assert src is not None
@@ -484,7 +480,7 @@ def test_source_with_decorator() -> None:
src = inspect.getsource(deco_fixture)
assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n"
# currenly Source does not unwrap decorators, testing the
# currently Source does not unwrap decorators, testing the
# existing behavior here for explicitness, but perhaps we should revisit/change this
# in the future
assert str(Source(deco_fixture)).startswith("@functools.wraps(function)")
@@ -618,6 +614,19 @@ def something():
assert str(source) == "def func(): raise ValueError(42)"
def test_decorator() -> None:
s = """\
def foo(f):
pass
@foo
def bar():
pass
"""
source = getstatement(3, s)
assert "@foo" in str(source)
def XXX_test_expression_multiline() -> None:
source = """\
something

View File

@@ -2,7 +2,6 @@ import re
import sys
import warnings
from pathlib import Path
from unittest import mock
import pytest
from _pytest import deprecated
@@ -11,13 +10,6 @@ from _pytest.pytester import Pytester
from pytest import PytestDeprecationWarning
@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore
# false positive due to dynamic attribute
def test_pytest_collect_module_deprecated(attribute) -> None:
with pytest.warns(DeprecationWarning, match=attribute):
getattr(pytest.collect, attribute)
@pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS))
@pytest.mark.filterwarnings("default")
def test_external_plugins_integrated(pytester: Pytester, plugin) -> None:
@@ -28,54 +20,6 @@ def test_external_plugins_integrated(pytester: Pytester, plugin) -> None:
pytester.parseconfig("-p", plugin)
def test_fillfuncargs_is_deprecated() -> None:
with pytest.warns(
pytest.PytestDeprecationWarning,
match=re.escape(
"pytest._fillfuncargs() is deprecated, use "
"function._request._fillfixtures() instead if you cannot avoid reaching into internals."
),
):
pytest._fillfuncargs(mock.Mock())
def test_fillfixtures_is_deprecated() -> None:
import _pytest.fixtures
with pytest.warns(
pytest.PytestDeprecationWarning,
match=re.escape(
"_pytest.fixtures.fillfixtures() is deprecated, use "
"function._request._fillfixtures() instead if you cannot avoid reaching into internals."
),
):
_pytest.fixtures.fillfixtures(mock.Mock())
def test_minus_k_dash_is_deprecated(pytester: Pytester) -> None:
threepass = pytester.makepyfile(
test_threepass="""
def test_one(): assert 1
def test_two(): assert 1
def test_three(): assert 1
"""
)
result = pytester.runpytest("-k=-test_two", threepass)
result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"])
def test_minus_k_colon_is_deprecated(pytester: Pytester) -> None:
threepass = pytester.makepyfile(
test_threepass="""
def test_one(): assert 1
def test_two(): assert 1
def test_three(): assert 1
"""
)
result = pytester.runpytest("-k", "test_two:", threepass)
result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"])
def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None:
module = pytester.getmodulecol(
"""
@@ -142,23 +86,6 @@ def test_private_is_deprecated() -> None:
PrivateInit(10, _ispytest=True)
def test_raising_unittest_skiptest_during_collection_is_deprecated(
pytester: Pytester,
) -> None:
pytester.makepyfile(
"""
import unittest
raise unittest.SkipTest()
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"*PytestRemovedIn8Warning: Raising unittest.SkipTest*",
]
)
@pytest.mark.parametrize("hooktype", ["hook", "ihook"])
def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request):
path = legacy_path(tmp_path)
@@ -194,8 +121,10 @@ def test_warns_none_is_deprecated():
with pytest.warns(
PytestDeprecationWarning,
match=re.escape(
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n "
"Replace pytest.warns(None) by simply pytest.warns()."
"Passing None has been deprecated.\n"
"See https://docs.pytest.org/en/latest/how-to/capture-warnings.html"
"#additional-use-cases-of-warnings-in-tests"
" for alternatives in common use cases."
),
):
with pytest.warns(None): # type: ignore[call-overload]
@@ -290,10 +219,6 @@ def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
)
@pytest.mark.skipif(
sys.version_info < (3, 7),
reason="This deprecation can only be emitted on python>=3.7",
)
def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
with pytest.warns(
pytest.PytestDeprecationWarning,

View File

@@ -0,0 +1,17 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses() -> None:
@dataclass
class SimpleDataObject:
field_a: int = field()
field_b: str = field()
def __eq__(self, __o: object) -> bool:
return super().__eq__(__o)
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@@ -56,7 +56,7 @@ def test_terminalwriter_not_unicode() -> None:
file = io.TextIOWrapper(buffer, encoding="cp1252")
tw = terminalwriter.TerminalWriter(file)
tw.write("hello 🌀 wôrld אבג", flush=True)
assert buffer.getvalue() == br"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2"
assert buffer.getvalue() == rb"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2"
win32 = int(sys.platform == "win32")

View File

@@ -1,5 +1,6 @@
[pytest]
addopts = --strict-markers
asyncio_mode = strict
filterwarnings =
error::pytest.PytestWarning
ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning

View File

@@ -1,15 +1,15 @@
anyio[curio,trio]==3.4.0
django==3.2.9
pytest-asyncio==0.16.0
anyio[curio,trio]==3.5.0
django==4.0.3
pytest-asyncio==0.18.2
pytest-bdd==5.0.0
pytest-cov==3.0.0
pytest-django==4.5.1
pytest-django==4.5.2
pytest-flakes==4.0.5
pytest-html==3.1.1
pytest-mock==3.6.1
pytest-mock==3.7.0
pytest-rerunfailures==10.2
pytest-sugar==0.9.4
pytest-trio==0.7.0
pytest-twisted==1.13.4
twisted==21.7.0
twisted==22.1.0
pytest-xvfb==2.0.0

View File

@@ -1,5 +1,4 @@
import operator
import sys
from contextlib import contextmanager
from decimal import Decimal
from fractions import Fraction
@@ -772,7 +771,7 @@ class TestApprox:
def test_expected_value_type_error(self, x, name):
with pytest.raises(
TypeError,
match=fr"pytest.approx\(\) does not support nested {name}:",
match=rf"pytest.approx\(\) does not support nested {name}:",
):
approx(x)
@@ -810,7 +809,6 @@ class TestApprox:
assert 1.0 != approx([None])
assert None != approx([1.0]) # noqa: E711
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires ordered dicts")
def test_nonnumeric_dict_repr(self):
"""Dicts with non-numerics and infinites have no tolerances"""
x1 = {"foo": 1.0000005, "bar": None, "foobar": inf}
@@ -860,13 +858,21 @@ class TestApprox:
assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual
def test_generic_sized_iterable_object(self):
class MySizedIterable:
def __iter__(self):
return iter([1, 2, 3, 4])
def test_generic_ordered_sequence(self):
class MySequence:
def __getitem__(self, i):
return [1, 2, 3, 4][i]
def __len__(self):
return 4
expected = MySizedIterable()
assert [1, 2, 3, 4] == approx(expected)
expected = MySequence()
assert [1, 2, 3, 4] == approx(expected, abs=1e-4)
expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])"
assert repr(approx(expected)) == expected_repr
def test_allow_ordered_sequences_only(self) -> None:
"""pytest.approx() should raise an error on unordered sequences (#9692)."""
with pytest.raises(TypeError, match="only supports ordered sequences"):
assert {1, 2, 3} == approx({1, 2, 3})

View File

@@ -103,10 +103,6 @@ def test_getfuncargnames_staticmethod_partial():
@pytest.mark.pytester_example_path("fixtures/fill_fixtures")
class TestFillFixtures:
def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility
assert pytest._fillfuncargs == fixtures._fillfuncargs
def test_funcarg_lookupfails(self, pytester: Pytester) -> None:
pytester.copy_example()
result = pytester.runpytest() # "--collect-only")

View File

@@ -1,84 +1,10 @@
from typing import Any
import pytest
from _pytest import runner
from _pytest._code import getfslineno
from _pytest.fixtures import getfixturemarker
from _pytest.pytester import Pytester
from _pytest.python import Function
class TestOEJSKITSpecials:
def test_funcarg_non_pycollectobj(
self, pytester: Pytester, recwarn
) -> None: # rough jstests usage
pytester.makeconftest(
"""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.path, 3, "xyz"
"""
)
modcol = pytester.getmodulecol(
"""
import pytest
@pytest.fixture
def arg1(request):
return 42
class MyClass(object):
pass
"""
)
# this hook finds funcarg factories
rep = runner.collect_one_node(collector=modcol)
# TODO: Don't treat as Any.
clscol: Any = rep.result[0]
clscol.obj = lambda arg1: None
clscol.funcargs = {}
pytest._fillfuncargs(clscol)
assert clscol.funcargs["arg1"] == 42
def test_autouse_fixture(
self, pytester: Pytester, recwarn
) -> None: # rough jstests usage
pytester.makeconftest(
"""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
if name == "MyClass":
return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector):
def reportinfo(self):
return self.path, 3, "xyz"
"""
)
modcol = pytester.getmodulecol(
"""
import pytest
@pytest.fixture(autouse=True)
def hello():
pass
@pytest.fixture
def arg1(request):
return 42
class MyClass(object):
pass
"""
)
# this hook finds funcarg factories
rep = runner.collect_one_node(modcol)
# TODO: Don't treat as Any.
clscol: Any = rep.result[0]
clscol.obj = lambda: None
clscol.funcargs = {}
pytest._fillfuncargs(clscol)
assert not clscol.funcargs
def test_wrapped_getfslineno() -> None:
def func():
pass

View File

@@ -24,8 +24,7 @@ from _pytest.compat import getfuncargnames
from _pytest.compat import NOTSET
from _pytest.outcomes import fail
from _pytest.pytester import Pytester
from _pytest.python import _idval
from _pytest.python import idmaker
from _pytest.python import IdMaker
from _pytest.scope import Scope
@@ -107,8 +106,8 @@ class TestMetafunc:
with pytest.raises(
fail.Exception,
match=(
r"In func: ids must be list of string/float/int/bool, found:"
r" Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2"
r"In func: ids contains unsupported value Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2. "
r"Supported types are: .*"
),
):
metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type]
@@ -286,7 +285,7 @@ class TestMetafunc:
deadline=400.0
) # very close to std deadline and CI boxes are not reliable in CPU power
def test_idval_hypothesis(self, value) -> None:
escaped = _idval(value, "a", 6, None, nodeid=None, config=None)
escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6)
assert isinstance(escaped, str)
escaped.encode("ascii")
@@ -308,7 +307,10 @@ class TestMetafunc:
),
]
for val, expected in values:
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_unicode_idval_with_config(self) -> None:
"""Unit test for expected behavior to obtain ids with
@@ -336,7 +338,7 @@ class TestMetafunc:
("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for val, config, expected in values:
actual = _idval(val, "a", 6, None, nodeid=None, config=config)
actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6)
assert actual == expected
def test_bytes_idval(self) -> None:
@@ -349,7 +351,10 @@ class TestMetafunc:
("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"),
]
for val, expected in values:
assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_class_or_function_idval(self) -> None:
"""Unit test for the expected behavior to obtain ids for parametrized
@@ -363,7 +368,10 @@ class TestMetafunc:
values = [(TestClass, "TestClass"), (test_function, "test_function")]
for val, expected in values:
assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
assert (
IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
== expected
)
def test_notset_idval(self) -> None:
"""Test that a NOTSET value (used by an empty parameterset) generates
@@ -371,29 +379,47 @@ class TestMetafunc:
Regression test for #7686.
"""
assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0"
assert (
IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0"
)
def test_idmaker_autoname(self) -> None:
"""#250"""
result = idmaker(
("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
)
result = IdMaker(
("a", "b"),
[pytest.param("string", 1.0), pytest.param("st-ring", 2.0)],
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["string-1.0", "st-ring-2.0"]
result = idmaker(
("a", "b"), [pytest.param(object(), 1.0), pytest.param(object(), object())]
)
result = IdMaker(
("a", "b"),
[pytest.param(object(), 1.0), pytest.param(object(), object())],
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a0-1.0", "a1-b1"]
# unicode mixing, issue250
result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")])
result = IdMaker(
("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["a0-\\xc3\\xb4"]
def test_idmaker_with_bytes_regex(self) -> None:
result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
result = IdMaker(
("a"), [pytest.param(re.compile(b"foo"), 1.0)], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["foo"]
def test_idmaker_native_strings(self) -> None:
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(1.0, -1.1),
@@ -410,7 +436,12 @@ class TestMetafunc:
pytest.param(b"\xc3\xb4", "other"),
pytest.param(1.0j, -2.0j),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == [
"1.0--1.1",
"2--202",
@@ -428,7 +459,7 @@ class TestMetafunc:
]
def test_idmaker_non_printable_characters(self) -> None:
result = idmaker(
result = IdMaker(
("s", "n"),
[
pytest.param("\x00", 1),
@@ -438,23 +469,35 @@ class TestMetafunc:
pytest.param("\t", 5),
pytest.param(b"\t", 6),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
def test_idmaker_manual_ids_must_be_printable(self) -> None:
result = idmaker(
result = IdMaker(
("s",),
[
pytest.param("x00", id="hello \x00"),
pytest.param("x05", id="hello \x05"),
],
)
None,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["hello \\x00", "hello \\x05"]
def test_idmaker_enum(self) -> None:
enum = pytest.importorskip("enum")
e = enum.Enum("Foo", "one, two")
result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
result = IdMaker(
("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None
).make_unique_parameterset_ids()
assert result == ["Foo.one-Foo.two"]
def test_idmaker_idfn(self) -> None:
@@ -465,15 +508,19 @@ class TestMetafunc:
return repr(val)
return None
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
],
idfn=ids,
)
ids,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"]
def test_idmaker_idfn_unique_names(self) -> None:
@@ -482,15 +529,19 @@ class TestMetafunc:
def ids(val: object) -> str:
return "a"
result = idmaker(
result = IdMaker(
("a", "b"),
[
pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
],
idfn=ids,
)
ids,
None,
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a-a0", "a-a1", "a-a2"]
def test_idmaker_with_idfn_and_config(self) -> None:
@@ -520,12 +571,15 @@ class TestMetafunc:
(MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for config, expected in values:
result = idmaker(
result = IdMaker(
("a",),
[pytest.param("string")],
idfn=lambda _: "ação",
config=config,
)
lambda _: "ação",
None,
config,
None,
None,
).make_unique_parameterset_ids()
assert result == [expected]
def test_idmaker_with_ids_and_config(self) -> None:
@@ -555,12 +609,9 @@ class TestMetafunc:
(MockConfig({option: False}), "a\\xe7\\xe3o"),
]
for config, expected in values:
result = idmaker(
("a",),
[pytest.param("string")],
ids=["ação"],
config=config,
)
result = IdMaker(
("a",), [pytest.param("string")], None, ["ação"], config, None, None
).make_unique_parameterset_ids()
assert result == [expected]
def test_parametrize_ids_exception(self, pytester: Pytester) -> None:
@@ -617,23 +668,39 @@ class TestMetafunc:
)
def test_idmaker_with_ids(self) -> None:
result = idmaker(
("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
)
result = IdMaker(
("a", "b"),
[pytest.param(1, 2), pytest.param(3, 4)],
None,
["a", None],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a", "3-4"]
def test_idmaker_with_paramset_id(self) -> None:
result = idmaker(
result = IdMaker(
("a", "b"),
[pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
ids=["a", None],
)
None,
["a", None],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["me", "you"]
def test_idmaker_with_ids_unique_names(self) -> None:
result = idmaker(
("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
)
result = IdMaker(
("a"),
list(map(pytest.param, [1, 2, 3, 4, 5])),
None,
["a", "a", "b", "c", "b"],
None,
None,
None,
).make_unique_parameterset_ids()
assert result == ["a0", "a1", "b0", "c", "b1"]
def test_parametrize_indirect(self) -> None:
@@ -1272,7 +1339,7 @@ class TestMetafuncFunctional:
"""
import pytest
@pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type))
@pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, OSError()))
def test_ids_numbers(x,expected):
assert x * 2 == expected
"""
@@ -1280,8 +1347,8 @@ class TestMetafuncFunctional:
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"In test_ids_numbers: ids must be list of string/float/int/bool,"
" found: <class 'type'> (type: <class 'type'>) at index 2"
"In test_ids_numbers: ids contains unsupported value OSError() (type: <class 'OSError'>) at index 2. "
"Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
]
)

View File

@@ -83,7 +83,7 @@ class TestImportHookInstallation:
"E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
"E Omitting 1 identical items, use -vv to show",
"E Differing items:",
"E Use -v to get the full diff",
"E Use -v to get more diff",
]
)
# XXX: unstable output.
@@ -376,7 +376,7 @@ class TestAssert_reprcompare:
assert diff == [
"b'spam' == b'eggs'",
"At index 0 diff: b's' != b'e'",
"Use -v to get the full diff",
"Use -v to get more diff",
]
def test_bytes_diff_verbose(self) -> None:
@@ -444,11 +444,19 @@ class TestAssert_reprcompare:
"""
expl = callequal(left, right, verbose=0)
assert expl is not None
assert expl[-1] == "Use -v to get the full diff"
assert expl[-1] == "Use -v to get more diff"
verbose_expl = callequal(left, right, verbose=1)
assert verbose_expl is not None
assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip())
def test_iterable_quiet(self) -> None:
expl = callequal([1, 2], [10, 2], verbose=-1)
assert expl == [
"[1, 2] == [10, 2]",
"At index 0 diff: 1 != 10",
"Use -v to get more diff",
]
def test_iterable_full_diff_ci(
self, monkeypatch: MonkeyPatch, pytester: Pytester
) -> None:
@@ -466,7 +474,7 @@ class TestAssert_reprcompare:
monkeypatch.delenv("CI", raising=False)
result = pytester.runpytest()
result.stdout.fnmatch_lines(["E Use -v to get the full diff"])
result.stdout.fnmatch_lines(["E Use -v to get more diff"])
def test_list_different_lengths(self) -> None:
expl = callequal([0, 1], [0, 1, 2])
@@ -699,32 +707,6 @@ class TestAssert_reprcompare:
assert expl is not None
assert len(expl) > 1
def test_repr_verbose(self) -> None:
class Nums:
def __init__(self, nums):
self.nums = nums
def __repr__(self):
return str(self.nums)
list_x = list(range(5000))
list_y = list(range(5000))
list_y[len(list_y) // 2] = 3
nums_x = Nums(list_x)
nums_y = Nums(list_y)
assert callequal(nums_x, nums_y) is None
expl = callequal(nums_x, nums_y, verbose=1)
assert expl is not None
assert "+" + repr(nums_x) in expl
assert "-" + repr(nums_y) in expl
expl = callequal(nums_x, nums_y, verbose=2)
assert expl is not None
assert "+" + repr(nums_x) in expl
assert "-" + repr(nums_y) in expl
def test_list_bad_repr(self) -> None:
class A:
def __repr__(self):
@@ -796,7 +778,6 @@ class TestAssert_reprcompare:
class TestAssert_reprcompare_dataclass:
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses(self, pytester: Pytester) -> None:
p = pytester.copy_example("dataclasses/test_compare_dataclasses.py")
result = pytester.runpytest(p)
@@ -815,7 +796,6 @@ class TestAssert_reprcompare_dataclass:
consecutive=True,
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_recursive_dataclasses(self, pytester: Pytester) -> None:
p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py")
result = pytester.runpytest(p)
@@ -834,7 +814,6 @@ class TestAssert_reprcompare_dataclass:
consecutive=True,
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None:
p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py")
result = pytester.runpytest(p, "-vv")
@@ -854,8 +833,6 @@ class TestAssert_reprcompare_dataclass:
"E ",
"E Drill down into differing attribute a:",
"E a: 10 != 20",
"E +10",
"E -20",
"E ",
"E Drill down into differing attribute b:",
"E b: 'ten' != 'xxx'",
@@ -867,7 +844,6 @@ class TestAssert_reprcompare_dataclass:
consecutive=True,
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses_verbose(self, pytester: Pytester) -> None:
p = pytester.copy_example("dataclasses/test_compare_dataclasses_verbose.py")
result = pytester.runpytest(p, "-vv")
@@ -881,7 +857,6 @@ class TestAssert_reprcompare_dataclass:
]
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses_with_attribute_comparison_off(
self, pytester: Pytester
) -> None:
@@ -891,7 +866,6 @@ class TestAssert_reprcompare_dataclass:
result = pytester.runpytest(p, "-vv")
result.assert_outcomes(failed=0, passed=1)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None:
p = pytester.copy_example(
"dataclasses/test_compare_two_different_dataclasses.py"
@@ -899,6 +873,15 @@ class TestAssert_reprcompare_dataclass:
result = pytester.runpytest(p, "-vv")
result.assert_outcomes(failed=0, passed=1)
def test_data_classes_with_custom_eq(self, pytester: Pytester) -> None:
p = pytester.copy_example(
"dataclasses/test_compare_dataclasses_with_custom_eq.py"
)
# issue 9362
result = pytester.runpytest(p, "-vv")
result.assert_outcomes(failed=1, passed=0)
result.stdout.no_re_match_line(".*Differing attributes.*")
class TestAssert_reprcompare_attrsclass:
def test_attrs(self) -> None:
@@ -982,7 +965,6 @@ class TestAssert_reprcompare_attrsclass:
right = SimpleDataObject(1, "b")
lines = callequal(left, right, verbose=2)
print(lines)
assert lines is not None
assert lines[2].startswith("Matching attributes:")
assert "Omitting" not in lines[1]
@@ -1007,6 +989,36 @@ class TestAssert_reprcompare_attrsclass:
lines = callequal(left, right)
assert lines is None
def test_attrs_with_auto_detect_and_custom_eq(self) -> None:
@attr.s(
auto_detect=True
) # attr.s doesnt ignore a custom eq if auto_detect=True
class SimpleDataObject:
field_a = attr.ib()
def __eq__(self, other): # pragma: no cover
return super().__eq__(other)
left = SimpleDataObject(1)
right = SimpleDataObject(2)
# issue 9362
lines = callequal(left, right, verbose=2)
assert lines is None
def test_attrs_with_custom_eq(self) -> None:
@attr.define(slots=False)
class SimpleDataObject:
field_a = attr.ib()
def __eq__(self, other): # pragma: no cover
return super().__eq__(other)
left = SimpleDataObject(1)
right = SimpleDataObject(2)
# issue 9362
lines = callequal(left, right, verbose=2)
assert lines is None
class TestAssert_reprcompare_namedtuple:
def test_namedtuple(self) -> None:
@@ -1027,7 +1039,7 @@ class TestAssert_reprcompare_namedtuple:
" b: 'b' != 'c'",
" - c",
" + b",
"Use -v to get the full diff",
"Use -v to get more diff",
]
def test_comparing_two_different_namedtuple(self) -> None:
@@ -1042,7 +1054,7 @@ class TestAssert_reprcompare_namedtuple:
assert lines == [
"NT1(a=1, b='b') == NT2(a=2, b='b')",
"At index 0 diff: 1 != 2",
"Use -v to get the full diff",
"Use -v to get more diff",
]
@@ -1616,7 +1628,7 @@ def test_raise_unprintable_assertion_error(pytester: Pytester) -> None:
)
def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None:
def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None:
pytester.makepyfile(
"""
class RaisingRepr(object):
@@ -1627,9 +1639,15 @@ def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None:
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
["E AssertionError: <unprintable AssertionError object>"]
)
if sys.version_info >= (3, 11):
# python 3.11 has native support for un-str-able exceptions
result.stdout.fnmatch_lines(
["E AssertionError: <exception str() failed>"]
)
else:
result.stdout.fnmatch_lines(
["E AssertionError: <unprintable AssertionError object>"]
)
def test_issue_1944(pytester: Pytester) -> None:

View File

@@ -13,10 +13,12 @@ from functools import partial
from pathlib import Path
from typing import cast
from typing import Dict
from typing import Generator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Set
from unittest import mock
import _pytest._code
import pytest
@@ -1057,7 +1059,7 @@ class TestAssertionRewriteHookDetails:
e = OSError()
e.errno = 10
raise e
yield # type:ignore[unreachable]
yield
monkeypatch.setattr(
_pytest.assertion.rewrite, "atomic_write", atomic_write_failed
@@ -1123,9 +1125,28 @@ class TestAssertionRewriteHookDetails:
assert _read_pyc(source, pyc) is None # no error
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity"
)
def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None:
"""
Ensure that the _rewrite_test() -> _write_pyc() produces a pyc file
that can be properly read with _read_pyc()
"""
from _pytest.assertion import AssertionState
from _pytest.assertion.rewrite import _read_pyc
from _pytest.assertion.rewrite import _rewrite_test
from _pytest.assertion.rewrite import _write_pyc
config = pytester.parseconfig()
state = AssertionState(config, "rewrite")
fn = tmp_path / "source.py"
pyc = Path(str(fn) + "c")
fn.write_text("def test(): assert True")
source_stat, co = _rewrite_test(fn, config)
_write_pyc(state, co, source_stat, pyc)
assert _read_pyc(fn, pyc, state.trace) is not None
def test_read_pyc_more_invalid(self, tmp_path: Path) -> None:
from _pytest.assertion.rewrite import _read_pyc
@@ -1294,7 +1315,7 @@ class TestIssue2121:
@pytest.mark.skipif(
sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems"
sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
)
@pytest.mark.parametrize("offset", [-1, +1])
def test_source_mtime_long_long(pytester: Pytester, offset) -> None:
@@ -1313,7 +1334,7 @@ def test_source_mtime_long_long(pytester: Pytester, offset) -> None:
# use unsigned long timestamp which overflows signed long,
# which was the cause of the bug
# +1 offset also tests masking of 0xFFFFFFFF
timestamp = 2 ** 32 + offset
timestamp = 2**32 + offset
os.utime(str(p), (timestamp, timestamp))
result = pytester.runpytest()
assert result.ret == 0
@@ -1357,7 +1378,7 @@ class TestEarlyRewriteBailout:
@pytest.fixture
def hook(
self, pytestconfig, monkeypatch, pytester: Pytester
) -> AssertionRewritingHook:
) -> Generator[AssertionRewritingHook, None, None]:
"""Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track
if PathFinder.find_spec has been called.
"""
@@ -1378,11 +1399,11 @@ class TestEarlyRewriteBailout:
hook = AssertionRewritingHook(pytestconfig)
# use default patterns, otherwise we inherit pytest's testing config
hook.fnpats[:] = ["test_*.py", "*_test.py"]
monkeypatch.setattr(hook, "_find_spec", spy_find_spec)
hook.set_session(StubSession()) # type: ignore[arg-type]
pytester.syspathinsert()
return hook
with mock.patch.object(hook, "fnpats", ["test_*.py", "*_test.py"]):
monkeypatch.setattr(hook, "_find_spec", spy_find_spec)
hook.set_session(StubSession()) # type: ignore[arg-type]
pytester.syspathinsert()
yield hook
def test_basic(self, pytester: Pytester, hook: AssertionRewritingHook) -> None:
"""
@@ -1432,9 +1453,9 @@ class TestEarlyRewriteBailout:
}
)
pytester.syspathinsert("tests")
hook.fnpats[:] = ["tests/**.py"]
assert hook.find_spec("file") is not None
assert self.find_spec_calls == ["file"]
with mock.patch.object(hook, "fnpats", ["tests/**.py"]):
assert hook.find_spec("file") is not None
assert self.find_spec_calls == ["file"]
@pytest.mark.skipif(
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"

View File

@@ -773,7 +773,7 @@ class TestLastFailed:
result = pytester.runpytest("--lf", "--lfnf", "none")
result.stdout.fnmatch_lines(
[
"collected 2 items / 2 deselected",
"collected 2 items / 2 deselected / 0 selected",
"run-last-failure: no previously failed tests, deselecting all items.",
"deselected=2",
"* 2 deselected in *",

View File

@@ -1433,19 +1433,19 @@ def test_error_attribute_issue555(pytester: Pytester) -> None:
not sys.platform.startswith("win"),
reason="only on windows",
)
def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None:
def test_windowsconsoleio_workaround_non_standard_streams() -> None:
"""
Ensure _py36_windowsconsoleio_workaround function works with objects that
Ensure _windowsconsoleio_workaround function works with objects that
do not implement the full ``io``-based stream protocol, for example execnet channels (#2666).
"""
from _pytest.capture import _py36_windowsconsoleio_workaround
from _pytest.capture import _windowsconsoleio_workaround
class DummyStream:
def write(self, s):
pass
stream = cast(TextIO, DummyStream())
_py36_windowsconsoleio_workaround(stream)
_windowsconsoleio_workaround(stream)
def test_dontreadfrominput_has_encoding(pytester: Pytester) -> None:

View File

@@ -64,7 +64,7 @@ class TestCollector:
assert pytester.collect_by_name(modcol, "doesnotexist") is None
def test_getparent(self, pytester: Pytester) -> None:
def test_getparent_and_accessors(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol(
"""
class TestClass:
@@ -77,14 +77,21 @@ class TestCollector:
fn = pytester.collect_by_name(cls, "test_foo")
assert isinstance(fn, pytest.Function)
module_parent = fn.getparent(pytest.Module)
assert module_parent is modcol
assert fn.getparent(pytest.Module) is modcol
assert modcol.module is not None
assert modcol.cls is None
assert modcol.instance is None
function_parent = fn.getparent(pytest.Function)
assert function_parent is fn
assert fn.getparent(pytest.Class) is cls
assert cls.module is not None
assert cls.cls is not None
assert cls.instance is None
class_parent = fn.getparent(pytest.Class)
assert class_parent is cls
assert fn.getparent(pytest.Function) is fn
assert fn.module is not None
assert fn.cls is not None
assert fn.instance is not None
assert fn.function is not None
def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None:
hello = pytester.makefile(".xxx", hello="world")
@@ -874,6 +881,36 @@ class TestNodeKeywords:
assert item.keywords["kw"] == "method"
assert len(item.keywords) == len(set(item.keywords))
def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None:
item = pytester.getitem(
"""
import pytest
pytestmark = pytest.mark.foo
class TestClass:
pytestmark = pytest.mark.bar
def test_method(self): pass
test_method.pytestmark = pytest.mark.baz
""",
"test_method",
)
assert isinstance(item, pytest.Function)
cls = item.getparent(pytest.Class)
assert cls is not None
mod = item.getparent(pytest.Module)
assert mod is not None
assert item.keywords["foo"] == pytest.mark.foo.mark
assert item.keywords["bar"] == pytest.mark.bar.mark
assert item.keywords["baz"] == pytest.mark.baz.mark
assert cls.keywords["foo"] == pytest.mark.foo.mark
assert cls.keywords["bar"] == pytest.mark.bar.mark
assert "baz" not in cls.keywords
assert mod.keywords["foo"] == pytest.mark.foo.mark
assert "bar" not in mod.keywords
assert "baz" not in mod.keywords
COLLECTION_ERROR_PY_FILES = dict(
test_01_failure="""
@@ -1470,6 +1507,35 @@ class TestImportModeImportlib:
]
)
def test_using_python_path(self, pytester: Pytester) -> None:
"""
Dummy modules created by insert_missing_modules should not get in
the way of modules that could be imported via python path (#9645).
"""
pytester.makeini(
"""
[pytest]
pythonpath = .
addopts = --import-mode importlib
"""
)
pytester.makepyfile(
**{
"tests/__init__.py": "",
"tests/conftest.py": "",
"tests/subpath/__init__.py": "",
"tests/subpath/helper.py": "",
"tests/subpath/test_something.py": """
import tests.subpath.helper
def test_something():
assert True
""",
}
)
result = pytester.runpytest()
result.stdout.fnmatch_lines("*1 passed in*")
def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None:
"""Regression test for an issue around bad exception formatting due to

View File

@@ -1,4 +1,5 @@
import enum
import sys
from functools import partial
from functools import wraps
from typing import TYPE_CHECKING
@@ -91,6 +92,7 @@ def test_get_real_func_partial() -> None:
assert get_real_func(partial(foo)) is foo
@pytest.mark.skipif(sys.version_info >= (3, 11), reason="couroutine removed")
def test_is_generator_asyncio(pytester: Pytester) -> None:
pytester.makepyfile(
"""
@@ -133,7 +135,7 @@ def test_is_generator_async_gen_syntax(pytester: Pytester) -> None:
pytester.makepyfile(
"""
from _pytest.compat import is_generator
def test_is_generator_py36():
def test_is_generator():
async def foo():
yield
await foo()
@@ -156,11 +158,11 @@ class ErrorsHelper:
@property
def raise_exception(self):
raise Exception("exception should be catched")
raise Exception("exception should be caught")
@property
def raise_fail_outcome(self):
pytest.fail("fail should be catched")
pytest.fail("fail should be caught")
def test_helper_failures() -> None:

View File

@@ -163,7 +163,17 @@ class TestParseIni:
pytester.path.joinpath("pytest.ini").write_text("addopts = -x")
result = pytester.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"])
result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined")
def test_toml_parse_error(self, pytester: Pytester) -> None:
pytester.makepyprojecttoml(
"""
\\"
"""
)
result = pytester.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines("ERROR: *pyproject.toml: Invalid statement*")
@pytest.mark.xfail(reason="probably not needed")
def test_confcutdir(self, pytester: Pytester) -> None:
@@ -386,7 +396,7 @@ class TestParseIni:
pytest.param(
"""
[some_other_header]
required_plugins = wont be triggered
required_plugins = won't be triggered
[pytest]
""",
"1.5",
@@ -807,7 +817,7 @@ class TestConfigAPI:
with pytest.raises(pytest.UsageError, match=exp_match):
pytester.parseconfig("--confcutdir", pytester.path.joinpath("file"))
with pytest.raises(pytest.UsageError, match=exp_match):
pytester.parseconfig("--confcutdir", pytester.path.joinpath("inexistant"))
pytester.parseconfig("--confcutdir", pytester.path.joinpath("nonexistent"))
p = pytester.mkdir("dir")
config = pytester.parseconfig("--confcutdir", p)
@@ -1264,15 +1274,21 @@ def test_load_initial_conftest_last_ordering(_config_for_test):
m = My()
pm.register(m)
hc = pm.hook.pytest_load_initial_conftests
values = hc._nonwrappers + hc._wrappers
expected = [
"_pytest.config",
m.__module__,
"_pytest.pythonpath",
"_pytest.capture",
"_pytest.warnings",
hookimpls = [
(
hookimpl.function.__module__,
"wrapper" if hookimpl.hookwrapper else "nonwrapper",
)
for hookimpl in hc.get_hookimpls()
]
assert hookimpls == [
("_pytest.config", "nonwrapper"),
(m.__module__, "nonwrapper"),
("_pytest.legacypath", "nonwrapper"),
("_pytest.python_path", "nonwrapper"),
("_pytest.capture", "wrapper"),
("_pytest.warnings", "wrapper"),
]
assert [x.function.__module__ for x in values] == expected
def test_get_plugin_specs_as_list() -> None:

View File

@@ -114,6 +114,7 @@ class TestConftestValueAccessGlobal:
"a", startdir, importmode="prepend", rootpath=Path(basedir)
)
assert value == 1.5
assert mod.__file__ is not None
path = Path(mod.__file__)
assert path.parent == basedir / "adir" / "b"
assert path.stem == "conftest"
@@ -145,10 +146,9 @@ def test_issue151_load_all_conftests(pytester: Pytester) -> None:
p = pytester.mkdir(name)
p.joinpath("conftest.py").touch()
conftest = PytestPluginManager()
conftest_setinitial(conftest, names)
d = list(conftest._conftestpath2mod.values())
assert len(d) == len(names)
pm = PytestPluginManager()
conftest_setinitial(pm, names)
assert len(set(pm.get_plugins()) - {pm}) == len(names)
def test_conftest_global_import(pytester: Pytester) -> None:
@@ -191,18 +191,20 @@ def test_conftestcutdir(pytester: Pytester) -> None:
conf.parent, importmode="prepend", rootpath=pytester.path
)
assert len(values) == 0
assert Path(conf) not in conftest._conftestpath2mod
assert not conftest.has_plugin(str(conf))
# but we can still import a conftest directly
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
values = conftest._getconftestmodules(
conf.parent, importmode="prepend", rootpath=pytester.path
)
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
# and all sub paths get updated properly
values = conftest._getconftestmodules(
p, importmode="prepend", rootpath=pytester.path
)
assert len(values) == 1
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
@@ -214,6 +216,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
conf.parent, importmode="prepend", rootpath=pytester.path
)
assert len(values) == 1
assert values[0].__file__ is not None
assert values[0].__file__.startswith(str(conf))
@@ -222,15 +225,15 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None:
sub = pytester.mkdir(name)
subconftest = sub.joinpath("conftest.py")
subconftest.touch()
conftest = PytestPluginManager()
conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path)
pm = PytestPluginManager()
conftest_setinitial(pm, [sub.parent], confcutdir=pytester.path)
key = subconftest.resolve()
if name not in ("whatever", ".dotdir"):
assert key in conftest._conftestpath2mod
assert len(conftest._conftestpath2mod) == 1
assert pm.has_plugin(str(key))
assert len(set(pm.get_plugins()) - {pm}) == 1
else:
assert key not in conftest._conftestpath2mod
assert len(conftest._conftestpath2mod) == 0
assert not pm.has_plugin(str(key))
assert len(set(pm.get_plugins()) - {pm}) == 0
def test_conftest_confcutdir(pytester: Pytester) -> None:

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