Compare commits

..

252 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
Florian Bruhin
0ad452bcba Prepare release version 7.0.0rc1 (#9375) (#9377)
Co-authored-by: pytest bot <pytestbot@gmail.com>
(cherry picked from commit 85897eddc6)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2021-12-07 10:13:36 +01: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
93 changed files with 2001 additions and 1470 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]+"
@@ -31,7 +32,6 @@ jobs:
fail-fast: false
matrix:
name: [
"windows-py36",
"windows-py37",
"windows-py37-pluggy",
"windows-py38",
@@ -39,7 +39,6 @@ jobs:
"windows-py310",
"windows-py311",
"ubuntu-py36",
"ubuntu-py37",
"ubuntu-py37-pluggy",
"ubuntu-py37-freeze",
@@ -51,6 +50,8 @@ jobs:
"macos-py37",
"macos-py38",
"macos-py39",
"macos-py310",
"docs",
"doctesting",
@@ -58,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
@@ -88,10 +85,6 @@ jobs:
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
@@ -135,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

@@ -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
@@ -34,18 +34,18 @@ repos:
- 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:
@@ -56,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

@@ -154,7 +154,6 @@ Jaap Broekhuizen
Jakob van Santen
Jakub Mitoraj
James Bourbeau
James Frost
Jan Balster
Janne Vanhala
Jason R. Coombs
@@ -188,6 +187,7 @@ Kevin Cox
Kevin J. Foley
Kian-Meng Ang
Kodi B. Arfer
Kojo Idrissa
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
@@ -327,7 +327,6 @@ Tom Dalton
Tom Viner
Tomáš Gavenčiak
Tomer Keren
Tony Narlock
Tor Colvin
Trevor Bekolay
Tyler Goodlet

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

@@ -1 +0,0 @@
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.

View File

@@ -1,3 +0,0 @@
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.

View File

@@ -1,3 +0,0 @@
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.

View File

@@ -1 +0,0 @@
Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.

View File

@@ -1 +0,0 @@
Malformed ``pyproject.toml`` files now produce a clearer error message.

View File

@@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
release-7.1.0
release-7.0.1
release-7.0.0
release-7.0.0rc1

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.
@@ -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,98 @@ 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)
=========================

View File

@@ -272,9 +272,6 @@ html_show_sourcelink = False
# Output file base name for HTML help builder.
htmlhelp_basename = "pytestdoc"
# The base URL which points to the root of the HTML documentation. It is used
# to indicate the location of document using the canonical link relation (#12363).
html_baseurl = "https://docs.pytest.org/en/stable/"
# -- Options for LaTeX output --------------------------------------------------
@@ -385,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),
@@ -393,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),
}

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:
@@ -275,29 +275,42 @@ The ``yield_fixture`` function/decorator
It has been so for a very long time, so can be search/replaced safely.
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.
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.collect`` module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.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._fillfuncargs`` function
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.0
.. versionremoved:: 7.0
This function was kept for backward compatibility with an older plugin.
@@ -306,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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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.1
pytest 7.1.0
.. _`simpletest`:

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

@@ -358,7 +358,7 @@ 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 **any** warning is emitted, use:
- To ensure that **at least one** warning is emitted, use:
.. code-block:: python
@@ -441,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

@@ -3,7 +3,7 @@
How to monkeypatch/mock modules and environments
================================================================
.. currentmodule:: pytest
.. currentmodule:: _pytest.monkeypatch
Sometimes tests need to invoke functionality which depends
on global settings or which invokes code which cannot be easily
@@ -436,7 +436,7 @@ separate fixtures for each potential mock and reference them in the needed tests
_ = app.create_connection_string()
.. currentmodule:: pytest
.. currentmodule:: _pytest.monkeypatch
API Reference
-------------

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():
...

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

@@ -23,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`
@@ -84,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

File diff suppressed because it is too large Load Diff

View File

@@ -808,8 +808,6 @@ Session related reporting hooks:
.. autofunction:: pytest_fixture_setup
.. hook:: pytest_fixture_post_finalizer
.. autofunction:: pytest_fixture_post_finalizer
.. hook:: pytest_warning_captured
.. autofunction:: pytest_warning_captured
.. hook:: pytest_warning_recorded
.. autofunction:: pytest_warning_recorded

View File

@@ -11,6 +11,9 @@ 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
---------------------------------------------

View File

@@ -1,5 +1,4 @@
import sys
from distutils.core import setup
if __name__ == "__main__":

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

@@ -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

@@ -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)
@@ -295,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
@@ -326,7 +322,6 @@ if sys.platform == "win32":
return False
return True
else:
def _write_pyc(
@@ -379,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

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
@@ -353,13 +352,17 @@ 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
@@ -528,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:
@@ -540,7 +556,7 @@ class PytestPluginManager(PluginManager):
def _getconftestmodules(
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
) -> List[types.ModuleType]:
) -> Sequence[types.ModuleType]:
if self._noconftest:
return []
@@ -556,14 +572,12 @@ class PytestPluginManager(PluginManager):
# 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
@@ -585,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:
@@ -609,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}")
@@ -1341,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],
@@ -1448,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:
@@ -1593,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:
@@ -1635,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

@@ -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."

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
@@ -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.
@@ -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",

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] = {}

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

@@ -689,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():

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

@@ -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

@@ -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:

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
@@ -683,6 +685,7 @@ class Pytester:
self,
request: FixtureRequest,
tmp_path_factory: TempPathFactory,
monkeypatch: MonkeyPatch,
*,
_ispytest: bool = False,
) -> None:
@@ -706,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)
@@ -738,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

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):
@@ -298,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
@@ -335,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
@@ -419,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]]] = []
@@ -897,8 +906,6 @@ class InstanceDummy:
in pytest 8."""
# 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)
@@ -920,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:
@@ -1042,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,
*,
@@ -1112,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,
@@ -1144,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.
@@ -1157,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,
@@ -1170,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
@@ -1200,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:
@@ -1216,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,
@@ -1358,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)))
@@ -1628,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,
@@ -1649,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 can't 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(

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
@@ -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

@@ -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:

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:

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,7 +18,6 @@ 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
@@ -70,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
@@ -83,14 +80,12 @@ set_trace = __pytestPDB.set_trace
__all__ = [
"__version__",
"_fillfuncargs",
"approx",
"Cache",
"CallInfo",
"CaptureFixture",
"Class",
"cmdline",
"collect",
"Collector",
"CollectReport",
"Config",
@@ -131,7 +126,6 @@ __all__ = [
"PytestConfigWarning",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
"PytestRemovedIn7Warning",
"PytestRemovedIn8Warning",
"Pytester",
"PytestPluginManager",

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 *",

View File

@@ -329,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

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(
"""
@@ -275,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",
]

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
@@ -1145,9 +1147,6 @@ class TestAssertionRewriteHookDetails:
_write_pyc(state, co, source_stat, pyc)
assert _read_pyc(fn, pyc, state.trace) is not None
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity"
)
def test_read_pyc_more_invalid(self, tmp_path: Path) -> None:
from _pytest.assertion.rewrite import _read_pyc
@@ -1316,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:
@@ -1335,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
@@ -1379,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.
"""
@@ -1400,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:
"""
@@ -1454,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

@@ -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

@@ -881,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="""

View File

@@ -135,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()

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:

View File

@@ -8,14 +8,6 @@ from _pytest.debugging import _validate_usepdb_cls
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester
try:
# Type ignored for Python <= 3.6.
breakpoint # type: ignore
except NameError:
SUPPORTS_BREAKPOINT_BUILTIN = False
else:
SUPPORTS_BREAKPOINT_BUILTIN = True
_ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "")
@@ -911,14 +903,6 @@ class TestPDB:
class TestDebuggingBreakpoints:
def test_supports_breakpoint_module_global(self) -> None:
"""Test that supports breakpoint global marks on Python 3.7+."""
if sys.version_info >= (3, 7):
assert SUPPORTS_BREAKPOINT_BUILTIN is True
@pytest.mark.skipif(
not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
)
@pytest.mark.parametrize("arg", ["--pdb", ""])
def test_sys_breakpointhook_configure_and_unconfigure(
self, pytester: Pytester, arg: str
@@ -952,9 +936,6 @@ class TestDebuggingBreakpoints:
result = pytester.runpytest_subprocess(*args)
result.stdout.fnmatch_lines(["*1 passed in *"])
@pytest.mark.skipif(
not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
)
def test_pdb_custom_cls(self, pytester: Pytester, custom_debugger_hook) -> None:
p1 = pytester.makepyfile(
"""
@@ -969,9 +950,6 @@ class TestDebuggingBreakpoints:
assert custom_debugger_hook == ["init", "set_trace"]
@pytest.mark.parametrize("arg", ["--pdb", ""])
@pytest.mark.skipif(
not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
)
def test_environ_custom_class(
self, pytester: Pytester, custom_debugger_hook, arg: str
) -> None:
@@ -1002,9 +980,6 @@ class TestDebuggingBreakpoints:
result = pytester.runpytest_subprocess(*args)
result.stdout.fnmatch_lines(["*1 passed in *"])
@pytest.mark.skipif(
not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
)
@pytest.mark.skipif(
not _ENVIRON_PYTHONBREAKPOINT == "",
reason="Requires breakpoint() default value",
@@ -1025,9 +1000,6 @@ class TestDebuggingBreakpoints:
assert "reading from stdin while output" not in rest
TestPDB.flush(child)
@pytest.mark.skipif(
not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
)
def test_pdb_not_altered(self, pytester: Pytester) -> None:
p1 = pytester.makepyfile(
"""

View File

@@ -4,8 +4,6 @@ Tests and examples for correct "+/-" usage in error diffs.
See https://github.com/pytest-dev/pytest/issues/3333 for details.
"""
import sys
import pytest
from _pytest.pytester import Pytester
@@ -210,68 +208,61 @@ TESTCASES = [
""",
id='Test "not in" string',
),
pytest.param(
"""
from dataclasses import dataclass
@dataclass
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(2, 'spam')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam')
E Matching attributes:
E ['b']
E Differing attributes:
E ['a']
E Drill down into differing attribute a:
E a: 1 != 2
""",
id="Compare data classes",
),
pytest.param(
"""
import attr
@attr.s(auto_attribs=True)
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(1, 'eggs')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs')
E Matching attributes:
E ['a']
E Differing attributes:
E ['b']
E Drill down into differing attribute b:
E b: 'spam' != 'eggs'
E - eggs
E + spam
""",
id="Compare attrs classes",
),
]
if sys.version_info[:2] >= (3, 7):
TESTCASES.extend(
[
pytest.param(
"""
from dataclasses import dataclass
@dataclass
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(2, 'spam')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam')
E Matching attributes:
E ['b']
E Differing attributes:
E ['a']
E Drill down into differing attribute a:
E a: 1 != 2
E +1
E -2
""",
id="Compare data classes",
),
pytest.param(
"""
import attr
@attr.s(auto_attribs=True)
class A:
a: int
b: str
def test_this():
result = A(1, 'spam')
expected = A(1, 'eggs')
assert result == expected
""",
"""
> assert result == expected
E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs')
E Matching attributes:
E ['a']
E Differing attributes:
E ['b']
E Drill down into differing attribute b:
E b: 'spam' != 'eggs'
E - eggs
E + spam
""",
id="Compare attrs classes",
),
]
)
@pytest.mark.parametrize("code, expected", TESTCASES)

View File

@@ -14,7 +14,7 @@ def test_item_fspath(pytester: pytest.Pytester) -> None:
items2, hookrec = pytester.inline_genitems(item.nodeid)
(item2,) = items2
assert item2.name == item.name
assert item2.fspath == item.fspath # type: ignore[attr-defined]
assert item2.fspath == item.fspath
assert item2.path == item.path

View File

@@ -823,25 +823,6 @@ class TestKeywordSelection:
assert len(dlist) == 1
assert dlist[0].items[0].name == "test_1"
def test_select_starton(self, pytester: Pytester) -> None:
threepass = pytester.makepyfile(
test_threepass="""
def test_one(): assert 1
def test_two(): assert 1
def test_three(): assert 1
"""
)
reprec = pytester.inline_run(
"-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", threepass
)
passed, skipped, failed = reprec.listoutcomes()
assert len(passed) == 2
assert not failed
dlist = reprec.getcalls("pytest_deselected")
assert len(dlist) == 1
item = dlist[0].items[0]
assert item.name == "test_one"
def test_keyword_extra(self, pytester: Pytester) -> None:
p = pytester.makepyfile(
"""

View File

@@ -50,21 +50,24 @@ def test_setattr() -> None:
class TestSetattrWithImportPath:
def test_string_expression(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("os.path.abspath", lambda x: "hello2")
assert os.path.abspath("123") == "hello2"
with monkeypatch.context() as mp:
mp.setattr("os.path.abspath", lambda x: "hello2")
assert os.path.abspath("123") == "hello2"
def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("_pytest.config.Config", 42)
import _pytest
with monkeypatch.context() as mp:
mp.setattr("_pytest.config.Config", 42)
import _pytest
assert _pytest.config.Config == 42 # type: ignore
assert _pytest.config.Config == 42 # type: ignore
def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("_pytest.config.Config", 42)
import _pytest
with monkeypatch.context() as mp:
mp.setattr("_pytest.config.Config", 42)
import _pytest
assert _pytest.config.Config == 42 # type: ignore
monkeypatch.delattr("_pytest.config.Config")
assert _pytest.config.Config == 42 # type: ignore
mp.delattr("_pytest.config.Config")
def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None:
with pytest.raises(TypeError):
@@ -80,14 +83,16 @@ class TestSetattrWithImportPath:
def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None:
# https://github.com/pytest-dev/pytest/issues/746
monkeypatch.setattr("os.path.qweqwe", 42, raising=False)
assert os.path.qweqwe == 42 # type: ignore
with monkeypatch.context() as mp:
mp.setattr("os.path.qweqwe", 42, raising=False)
assert os.path.qweqwe == 42 # type: ignore
def test_delattr(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.delattr("os.path.abspath")
assert not hasattr(os.path, "abspath")
monkeypatch.undo()
assert os.path.abspath
with monkeypatch.context() as mp:
mp.delattr("os.path.abspath")
assert not hasattr(os.path, "abspath")
mp.undo()
assert os.path.abspath
def test_delattr() -> None:

View File

@@ -295,7 +295,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
check=True,
universal_newlines=True,
text=True,
).stdout
except (OSError, subprocess.CalledProcessError):
pytest.skip("bash is not available")

View File

@@ -1,3 +1,4 @@
import email.message
import io
from typing import List
from typing import Union
@@ -98,7 +99,9 @@ class TestPaste:
def mocked(url, data):
calls.append((url, data))
raise urllib.error.HTTPError(url, 400, "Bad request", {}, io.BytesIO())
raise urllib.error.HTTPError(
url, 400, "Bad request", email.message.Message(), io.BytesIO()
)
monkeypatch.setattr(urllib.request, "urlopen", mocked)
return calls

View File

@@ -143,6 +143,10 @@ class TestImportPath:
assert obj.x == 42 # type: ignore[attr-defined]
assert obj.__name__ == "execfile"
def test_import_path_missing_file(self, path1: Path) -> None:
with pytest.raises(ImportPathMismatchError):
import_path(path1 / "sampledir", root=path1)
def test_renamed_dir_creates_mismatch(
self, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
@@ -450,7 +454,6 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
class TestImportLibMode:
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None:
"""Ensure that importlib mode works with a module containing dataclasses (#7856)."""
fn = tmp_path.joinpath("_src/tests/test_dataclass.py")

View File

@@ -618,14 +618,9 @@ def test_linematcher_string_api() -> None:
def test_pytest_addopts_before_pytester(request, monkeypatch: MonkeyPatch) -> None:
orig = os.environ.get("PYTEST_ADDOPTS", None)
monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused")
pytester: Pytester = request.getfixturevalue("pytester")
_: Pytester = request.getfixturevalue("pytester")
assert "PYTEST_ADDOPTS" not in os.environ
pytester._finalize()
assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused"
monkeypatch.undo()
assert os.environ.get("PYTEST_ADDOPTS") == orig
def test_run_stdin(pytester: Pytester) -> None:

View File

@@ -114,13 +114,13 @@ class TestDeprecatedCall:
# Type ignored because `onceregistry` and `filters` are not
# documented API.
onceregistry = warnings.onceregistry.copy() # type: ignore
filters = warnings.filters[:] # type: ignore
filters = warnings.filters[:]
warn = warnings.warn
warn_explicit = warnings.warn_explicit
self.test_deprecated_call_raises()
self.test_deprecated_call()
assert onceregistry == warnings.onceregistry # type: ignore
assert filters == warnings.filters # type: ignore
assert filters == warnings.filters
assert warn is warnings.warn
assert warn_explicit is warnings.warn_explicit

View File

@@ -335,6 +335,54 @@ def test_sessionfinish_with_start(pytester: Pytester) -> None:
assert res.ret == ExitCode.NO_TESTS_COLLECTED
def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None:
"""Test that when multiple collection args are specified on the command line
for the same module, only a single Module collector is created.
Regression test for #723, #3358.
"""
pytester.makepyfile(
**{
"d/test_it": """
def test_1(): pass
def test_2(): pass
"""
}
)
result = pytester.runpytest(
"--collect-only",
"d/test_it.py::test_1",
"d/test_it.py::test_2",
)
result.stdout.fnmatch_lines(
[
"<Module d/test_it.py>",
" <Function test_1>",
" <Function test_2>",
],
consecutive=True,
)
# Different, but related case.
result = pytester.runpytest(
"--collect-only",
"--keep-duplicates",
"d",
"d",
)
result.stdout.fnmatch_lines(
[
"<Module d/test_it.py>",
" <Function test_1>",
" <Function test_2>",
" <Function test_1>",
" <Function test_2>",
],
consecutive=True,
)
@pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"])
def test_rootdir_option_arg(
pytester: Pytester, monkeypatch: MonkeyPatch, path: str

View File

@@ -385,21 +385,55 @@ class TestTerminal:
def test_10():
pytest.xfail("It's 🕙 o'clock")
@pytest.mark.skip(
reason="cannot do foobar because baz is missing due to I don't know what"
)
def test_long_skip():
pass
@pytest.mark.xfail(
reason="cannot do foobar because baz is missing due to I don't know what"
)
def test_long_xfail():
print(1 / 0)
"""
)
common_output = [
"test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
"test_verbose_skip_reason.py::test_2 XPASS (456) *",
"test_verbose_skip_reason.py::test_3 XFAIL (789) *",
"test_verbose_skip_reason.py::test_4 XFAIL *",
"test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *",
"test_verbose_skip_reason.py::test_6 XPASS *",
"test_verbose_skip_reason.py::test_7 SKIPPED *",
"test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *",
"test_verbose_skip_reason.py::test_9 XFAIL *",
"test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *",
]
result = pytester.runpytest("-v")
result.stdout.fnmatch_lines(
[
"test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
"test_verbose_skip_reason.py::test_2 XPASS (456) *",
"test_verbose_skip_reason.py::test_3 XFAIL (789) *",
"test_verbose_skip_reason.py::test_4 XFAIL *",
"test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *",
"test_verbose_skip_reason.py::test_6 XPASS *",
"test_verbose_skip_reason.py::test_7 SKIPPED *",
"test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *",
"test_verbose_skip_reason.py::test_9 XFAIL *",
"test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *",
common_output
+ [
"test_verbose_skip_reason.py::test_long_skip SKIPPED (cannot *...) *",
"test_verbose_skip_reason.py::test_long_xfail XFAIL (cannot *...) *",
]
)
result = pytester.runpytest("-vv")
result.stdout.fnmatch_lines(
common_output
+ [
(
"test_verbose_skip_reason.py::test_long_skip SKIPPED"
" (cannot do foobar because baz is missing due to I don't know what) *"
),
(
"test_verbose_skip_reason.py::test_long_xfail XFAIL"
" (cannot do foobar because baz is missing due to I don't know what) *"
),
]
)
@@ -682,9 +716,7 @@ class TestTerminalFunctional:
pass
"""
)
result = pytester.runpytest(
"-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", testpath
)
result = pytester.runpytest("-k", "test_t", testpath)
result.stdout.fnmatch_lines(
["collected 3 items / 1 deselected / 2 selected", "*test_deselected.py ..*"]
)

View File

@@ -1,4 +1,5 @@
import os
import sys
import warnings
from typing import List
from typing import Optional
@@ -239,7 +240,7 @@ def test_filterwarnings_mark_registration(pytester: Pytester) -> None:
@pytest.mark.filterwarnings("always::UserWarning")
def test_warning_captured_hook(pytester: Pytester) -> None:
def test_warning_recorded_hook(pytester: Pytester) -> None:
pytester.makeconftest(
"""
def pytest_configure(config):
@@ -276,9 +277,9 @@ def test_warning_captured_hook(pytester: Pytester) -> None:
expected = [
("config warning", "config", ""),
("collect warning", "collect", ""),
("setup warning", "runtest", "test_warning_captured_hook.py::test_func"),
("call warning", "runtest", "test_warning_captured_hook.py::test_func"),
("teardown warning", "runtest", "test_warning_captured_hook.py::test_func"),
("setup warning", "runtest", "test_warning_recorded_hook.py::test_func"),
("call warning", "runtest", "test_warning_recorded_hook.py::test_func"),
("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"),
]
for index in range(len(expected)):
collected_result = collected[index]
@@ -517,6 +518,7 @@ class TestDeprecationWarningsByDefault:
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
@pytest.mark.skip("not relevant until pytest 8.0")
@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None:
"""This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors.
@@ -528,7 +530,7 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No
"""
import warnings, pytest
def test():
warnings.warn(pytest.PytestRemovedIn7Warning("some warning"))
warnings.warn(pytest.PytestRemovedIn8Warning("some warning"))
"""
)
if change_default == "ini":
@@ -536,12 +538,12 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No
"""
[pytest]
filterwarnings =
ignore::pytest.PytestRemovedIn7Warning
ignore::pytest.PytestRemovedIn8Warning
"""
)
args = (
("-Wignore::pytest.PytestRemovedIn7Warning",)
("-Wignore::pytest.PytestRemovedIn8Warning",)
if change_default == "cmdline"
else ()
)
@@ -773,3 +775,57 @@ class TestStackLevel:
"*Unknown pytest.mark.unknown*",
]
)
def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None:
# Some platforms (notably PyPy) don't have tracemalloc.
# We choose to explicitly not skip this in case tracemalloc is not
# available, using `importorskip("tracemalloc")` for example,
# because we want to ensure the same code path does not break in those platforms.
try:
import tracemalloc # noqa
has_tracemalloc = True
except ImportError:
has_tracemalloc = False
# Explicitly disable PYTHONTRACEMALLOC in case pytest's test suite is running
# with it enabled.
monkeypatch.delenv("PYTHONTRACEMALLOC", raising=False)
pytester.makepyfile(
"""
def open_file(p):
f = p.open("r")
assert p.read_text() == "hello"
def test_resource_warning(tmp_path):
p = tmp_path.joinpath("foo.txt")
p.write_text("hello")
open_file(p)
"""
)
result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
expected_extra = (
[
"*ResourceWarning* unclosed file*",
"*Enable tracemalloc to get traceback where the object was allocated*",
"*See https* for more info.",
]
if has_tracemalloc
else []
)
result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])
monkeypatch.setenv("PYTHONTRACEMALLOC", "20")
result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
expected_extra = (
[
"*ResourceWarning* unclosed file*",
"*Object allocated at*",
]
if has_tracemalloc
else []
)
result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])

View File

@@ -4,7 +4,6 @@ minversion = 3.20.0
distshare = {homedir}/.tox/distshare
envlist =
linting
py36
py37
py38
py39
@@ -101,9 +100,9 @@ commands =
[testenv:plugins]
# use latest versions of all plugins, including pre-releases
pip_pre=true
# use latest pip and new dependency resolver (#7783)
# use latest pip to get new dependency resolver (#7783)
download=true
install_command=python -m pip --use-feature=2020-resolver install {opts} {packages}
install_command=python -m pip install {opts} {packages}
changedir = testing/plugins_integration
deps = -rtesting/plugins_integration/requirements.txt
setenv =