Compare commits

...

307 Commits
6.1.2 ... 6.2.3

Author SHA1 Message Date
pytest bot
3a2fd96305 Prepare release version 6.2.3 2021-04-03 21:41:18 +00:00
Ran Benita
138b19a930 Merge pull request #8517 from bluetech/backport-mktmp
[6.2.x] Fix minor temporary directory security issue
2021-04-04 00:34:15 +03:00
Ran Benita
822686e880 tmpdir: prevent using a non-private root temp directory
pytest uses a root temp directory named `/tmp/pytest-of-<username>`. The
name is predictable, and the directory might already exists from a
previous run, so that's allowed.

This makes it possible for my_user to pre-create
`/tmp/pytest-of-another_user`, thus giving my_user control of
another_user's tempdir.

Prevent this scenario by adding a couple of safety checks. I believe
they are sufficient.

Testing the first check requires changing the owner, which requires
root permissions, so can't be unit-tested easily, but I checked it
manually.
2021-04-04 00:04:50 +03:00
Ran Benita
9dc54f79b0 tmpdir: fix temporary directories created with world-readable permissions
(Written for a Unix system, but might be applicable to Windows as well).

pytest creates a root temporary directory under /tmp, named
`pytest-of-<username>`, and creates tmp_path's and other under it.
/tmp is shared between all users of the system.

This root temporary directory was created with 0o777&~umask permissions,
which usually becomes 0o755, meaning any user in the system could list
and read the files, which is undesirable.

Use 0o700 permissions instead. Also for subdirectories, because the root
dir is adjustable.
2021-04-04 00:00:42 +03:00
Ran Benita
93dbae24e1 pathlib: inline ensure_reset_dir()
This is only used in TempPathFactory.getbasetemp(). We'll be wanting
further control/care there, so move it into there.
2021-04-03 23:39:37 +03:00
Ran Benita
02fdbe2e76 pathlib: remove useless temporary variable 2021-04-03 23:39:32 +03:00
Bruno Oliveira
12e7db85af Merge pull request #8285 from nicoddemus/backport-8280
[6.2.x] Doc: Move the module declaration to index.rst
2021-01-27 09:13:27 -03:00
Bruno Oliveira
56e4392444 Merge pull request #8280 from xuhdev/module
Doc: Move the module declaration to index.rst
2021-01-27 09:07:33 -03:00
Bruno Oliveira
8220eca963 Merge pull request #8275 from pytest-dev/release-6.2.2
Prepare release 6.2.2
2021-01-25 11:52:23 -03:00
pytest bot
b9c98762f5 Prepare release version 6.2.2 2021-01-25 12:30:53 +00:00
Bruno Oliveira
8003fd23b9 Merge pull request #8259 from nicoddemus/backport-8250
[6.2.x] Fix faulthandler for Twisted Logger when used with "--capture=no"
2021-01-20 10:14:34 -03:00
Bruno Oliveira
8d605b9b26 Merge pull request #8250 from daq-tools/fix-twisted-capture 2021-01-20 09:47:10 -03:00
Bruno Oliveira
14e0c3e105 Merge pull request #8225 from The-Compiler/training-update (#8226)
doc: Add note about training early bird discount
2021-01-05 20:49:50 +01:00
Bruno Oliveira
45facc16c8 Merge pull request #8224 from nicoddemus/backport-8220
[6.2.x] DOC: Mark pytest module
2021-01-05 13:31:19 -03:00
Bruno Oliveira
99fe887d7c Merge pull request #8220 from xuhdev/module-doc
DOC: Mark pytest module
2021-01-05 13:26:19 -03:00
Bruno Oliveira
8dbf9dc1aa Merge pull request #8167 from nicoddemus/backport-8166
[6.2.x] Add Changelog to setup.cfg (#8166)
2020-12-17 13:59:14 -03:00
Adam Johnson
baaee2148d Add Changelog to setup.cfg (#8166)
Co-authored-by: Thomas Grainger <tagrain@gmail.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-12-17 13:56:18 -03:00
Bruno Oliveira
f7d1ab870f Merge pull request #8163 from bluetech/backport-8152
[6.2.x] terminal: fix "(<Skipped instance>)" skip reason in test status line
2020-12-17 08:31:53 -03:00
Ran Benita
b8201c280e Merge pull request #8152 from bluetech/empty-skip
terminal: fix "(<Skipped instance>)" skip reason in test status line
(cherry picked from commit 02e69e5cdc)
2020-12-17 12:59:01 +02:00
Bruno Oliveira
1f0c50b475 Merge pull request #8160 from nicoddemus/backport-7381
[6.2.x] Clarify fixture execution order and provide visual aids (#7381)
2020-12-16 14:02:02 -03:00
Chris NeJame
da82e1853c Clarify fixture execution order and provide visual aids (#7381)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
Co-authored-by: Ran Benita <ran@unusedvar.com>
2020-12-16 13:54:52 -03:00
Bruno Oliveira
a566eb9c70 Merge pull request #8149 from pytest-dev/release-6.2.1
Prepare release 6.2.1
2020-12-15 12:39:06 -03:00
pytest bot
d3971c30f4 Prepare release version 6.2.1 2020-12-15 13:06:34 +00:00
Bruno Oliveira
780044b64a Merge pull request #8147 from nicoddemus/backport-8137
[6.2.x] python_api: handle array-like args in approx() #8137
2020-12-15 09:08:58 -03:00
Jakob van Santen
8354995abc python_api: handle array-like args in approx() (#8137) 2020-12-15 08:50:11 -03:00
Bruno Oliveira
8b8b1214f4 Merge pull request #8135 from nicoddemus/backport-8123
[6.2] Merge pull request #8123 from nicoddemus/import-mismatch-unc
2020-12-13 10:50:49 -03:00
Bruno Oliveira
f854cf66f4 Merge pull request #8123 from nicoddemus/import-mismatch-unc
Compare also paths on Windows when considering ImportPathMismatchError
2020-12-13 10:35:59 -03:00
Ran Benita
c475106f12 Merge pull request #8130 from pytest-dev/release-6.2.0
Prepare release 6.2.0
2020-12-12 23:21:28 +02:00
pytest bot
e7073afe6e Prepare release version 6.2.0 2020-12-12 22:45:09 +02:00
Ran Benita
683f29f84d Merge pull request #8129 from bluetech/docs-pygments-workaround
doc: temporary workaround for pytest-pygments lexing error
2020-12-12 22:31:10 +02:00
Ran Benita
0feeddf8ed doc: temporary workaround for pytest-pygments lexing error
pytest-pygments doesn't yet recognize the skip reason in summary line
added recently. Workaround it until we get to updating it.
2020-12-12 22:18:23 +02:00
Ran Benita
b478275777 Merge pull request #8128 from bluetech/skip-reason-empty
terminal: when the skip/xfail is empty, don't show it as "()"
2020-12-12 22:18:06 +02:00
Ran Benita
3302ff9949 terminal: when the skip/xfail is empty, don't show it as "()"
Avoid showing a line like

    x.py::test_4 XPASS ()   [100%]

which looks funny.
2020-12-12 22:09:00 +02:00
Ran Benita
59bd0f6912 Merge pull request #8126 from bluetech/tox-regen-pretend-scm2
tox: use pip legacy resolver for regen job
2020-12-12 20:50:10 +02:00
Ran Benita
6298ff1f4e tox: use pip legacy resolver for regen job
The env var effects all of the pip installs, including regendoc which
also uses setuptools-scm, so it gets the wrong version, and fails to
install with the new pip resolver:

    ERROR: Requested regendoc from 206e495142/regendoc-0.6.1.tar.gz (sha256)=db1e8c9ae02c1af559eae105bfd77ba41ed07fc8ca7030ea59db5f3f161236a4 has different version in metadata: '6.2.0'
2020-12-12 20:27:59 +02:00
Ran Benita
d51ecbd44d Merge pull request #8125 from bluetech/tox-rm-pip-req
tox: remove requires: pip>=20.3.1
2020-12-12 19:11:27 +02:00
Ran Benita
f237b077fc tox: remove requires: pip>=20.3.1
Causes some trouble in CI and not really needed as old pip should still
work.
2020-12-12 18:31:52 +02:00
Ran Benita
95e0e19b8d Merge pull request #8124 from bluetech/s0undt3ch-feature/skip-context-hook
Add `pytest_markeval_namespace` hook.
2020-12-12 18:14:02 +02:00
Anton
cf1051cfba infrastructure: Stricter tox dependensies (#8119) 2020-12-12 18:08:15 +02:00
Pedro Algarvio
b16c091253 Add pytest_markeval_namespace hook.
Add a new hook , `pytest_markeval_namespace` which should return a dictionary.
This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers.

Pseudo example

``conftest.py``:

.. code-block:: python
   def pytest_markeval_namespace():
       return {"color": "red"}
``test_func.py``:

.. code-block:: python
   @pytest.mark.skipif("color == 'blue'", reason="Color is not red")
   def test_func():
       assert False
2020-12-12 17:41:37 +02:00
Ran Benita
902739cfc3 Merge pull request #7208 from CarycaKatarzyna/issue2044
Issue 2044 - show skipping reason in verbose mode
2020-12-09 10:11:47 +02:00
Katarzyna
612f157dbd Show reason for skipped test in verbose mode 2020-12-09 09:43:47 +02:00
Anton
810b878ef8 Migrate to pytester: test_capture.py, test_terminal.py, approx.py (#8108)
* Migrate to pytester: test_capture.py, test_config.py, approx.py

* migrate test_terminal.py

* revert test_config.py

* more typing in test_terminal.py

* try-out 'tr' fixture update

* revert 'tr' fixture, update test_config.py
2020-12-08 22:20:02 +02:00
Bruno Oliveira
059f6ff315 Merge pull request #8107 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-html-3.1.0
build(deps): bump pytest-html from 3.0.0 to 3.1.0 in /testing/plugins_integration
2020-12-07 08:52:56 -03:00
Bruno Oliveira
f3006ecb2b Merge pull request #8106 from pytest-dev/dependabot/pip/testing/plugins_integration/django-3.1.4
build(deps): bump django from 3.1.3 to 3.1.4 in /testing/plugins_integration
2020-12-07 08:52:36 -03:00
dependabot[bot]
19de6bccff build(deps): bump pytest-html in /testing/plugins_integration
Bumps [pytest-html](https://github.com/pytest-dev/pytest-html) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/pytest-dev/pytest-html/releases)
- [Changelog](https://github.com/pytest-dev/pytest-html/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-html/compare/v3.0.0...v3.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-07 03:08:29 +00:00
dependabot[bot]
8c120c042c build(deps): bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.3...3.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-07 03:08:29 +00:00
Bruno Oliveira
68a0f22eee Merge pull request #8100 from nicoddemus/doctest-alternatives
List pytest-doctestplus in doctest docs
2020-12-05 17:42:09 -03:00
Ran Benita
1a1bb61340 Merge pull request #8038 from matthewhughes934/note-tmpdir-lifetime
Docs: Note lifetime of temporary directories
2020-12-05 21:54:04 +02:00
Ran Benita
e398c93884 Merge pull request #8055 from bluetech/unraisable
Add unraisableexception and threadexception plugins
2020-12-05 21:52:17 +02:00
Ran Benita
760a73c08c Merge pull request #8017 from bluetech/typing-public-fixtures
Export types of builtin fixtures for type annotations
2020-12-05 21:51:20 +02:00
Bruno Oliveira
4fc20c8d28 List pytest-doctestplus in doctest docs
As per https://github.com/pytest-dev/pytest/discussions/8088
2020-12-04 15:24:56 -03:00
Ran Benita
954151cdbd Merge pull request #8092 from cmecklenborg/pytester_refactor_test_pastebin
Migrate test_pastebin.py from testdir to pytester
2020-12-02 18:17:59 +02:00
Christine Mecklenborg
eeb3afb8ab Migrate test_pastebin.py from testdir to pytester 2020-12-01 12:55:59 -06:00
Ran Benita
64bb5f2ad1 Merge pull request #8091 from cmecklenborg/pytester_refactor_test_parseopt
Migrate test_parseopt.py from testdir to pytester
2020-12-01 14:10:09 +02:00
Ran Benita
0d0dfdd7aa Merge pull request #8090 from cmecklenborg/pytester_refactor_test_nose
Migrate test_nose.py from testdir to pytester
2020-12-01 14:07:55 +02:00
dependabot[bot]
7a06bc2416 build(deps): bump pytest-flakes in /testing/plugins_integration (#8087)
Bumps [pytest-flakes](https://github.com/asmeurer/pytest-flakes) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/asmeurer/pytest-flakes/releases)
- [Commits](https://github.com/asmeurer/pytest-flakes/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-01 14:06:38 +02:00
Christine Mecklenborg
4abd71121d Migrate test_parseopt.py from testdir to pytester 2020-11-30 16:27:39 -06:00
Christine Mecklenborg
d4c81ffab4 Migrate test_nose.py from testdir to pytester 2020-11-30 16:07:26 -06:00
Shubham Adep
d1cb9de211 Custom multiple marker execution order (#8065)
* Custom multiple marker execution order

https://github.com/pytest-dev/pytest/issues/8020 issue stated that ordering of multiple custom markers is from inside - out. I have added example for the same in the documentation. Please let me know for further changes / concerns.

* remove trailing spaces

The last commit was failing due to extra spaces

* Ran tox tests locally to debug white space trimming issues

* Resolve: ERROR:   docs: commands failed for tox -e docs

* Update doc/en/reference.rst

Committed PR suggestions.

Co-authored-by: Florian Bruhin <me@the-compiler.org>

* Added reference to Node.iter_markers_with_node in documentation

* Add myself to Authors

Co-authored-by: Shubham <shubham.adep@wsu.edu>
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2020-11-30 16:48:08 +01:00
Matthew Hughes
6a256606c6 Docs: Note lifetime of temporary directories
Explanation: The default handling of these lifetimes is done in
`tmpdir.TempPathFactory.getbasetemp`, which passes `keep=3` to
`pathlib.make_numbered_dir_with_cleanup`.

GH Issue: #8036
2020-11-28 21:18:16 +00:00
Prakhar Gurunani
3405c7e6a8 Add more info about skipping doctests (#8080)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-11-28 12:47:02 -03:00
Dominic Mortlock
775ba63c67 Refactor acceptance_test to use pytester (#8070) 2020-11-25 14:42:47 +02:00
Ran Benita
70823da7ed Merge pull request #8064 from symonk/fix-typo-in-mock-timing
fix mock_timing fixture name (typo) in timing.py
2020-11-25 12:29:34 +02:00
Jürgen Gmach
d27806295a fix typo (#8069) 2020-11-24 22:27:34 +01:00
Simon K
b310872300 fix mock_timing fixture name (typo) in timing.py 2020-11-23 20:45:12 +00:00
Ran Benita
d50df85e26 Add unraisableexception and threadexception plugins 2020-11-21 21:11:48 +02:00
Ran Benita
d59a4996ae Merge pull request #8057 from nicoddemus/changelog-links
Add links to some CHANGELOG entries
2020-11-21 20:47:27 +02:00
Maximilian Cosmo Sitter
0cef530d10 Add str() support to LineMatcher (#8050) 2020-11-21 20:45:20 +02:00
Bruno Oliveira
31021ac8d5 Add links to some CHANGELOG entries
While adding links to https://github.com/pytest-dev/pytest/pull/8052, noticed
a few more missing.
2020-11-21 11:05:54 -03:00
Simon K
52fef811c2 permit node to warn with any warning type, not just PytestWarning (#8052)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-11-21 10:49:17 -03:00
Ran Benita
148e3c582a pytester: always close stdin pipe in pytester.run()
If the user passed stdin=PIPE for some reason, they have no way to close
it themselves since it is not exposed.
2020-11-20 17:55:30 +02:00
Ran Benita
3e0bbd2f57 testing: fix ResourceWarning in broken-pipe test 2020-11-20 17:55:23 +02:00
Tim Hoffmann
afd53ede6f Link mentioned functions instead of using literals (#8045) 2020-11-19 15:44:59 +01:00
Bruno Oliveira
329e66c22e Merge pull request #8051 from The-Compiler/nose-fixup
Fix nose documentation
2020-11-19 10:04:59 -03:00
Florian Bruhin
c6ac618baf Fix nose documentation
Follow-up to #8048 which seems to have been merged without the suggested changes.
2020-11-19 13:54:40 +01:00
Bruno Oliveira
42f9622a90 Merge pull request #8048 from mickeypash/patch-1
Add small section on migrating from nose to pytest.
2020-11-19 09:02:04 -03:00
mickeypash
ce825ed16c Add small section on migrating from nose to pytest
The section currently features the nose2pytest tool with plans to expand
on some of the common gotchas when performing such migrations.
2020-11-19 08:50:11 -03:00
Petter Strandmark
eda681af2b Call Python 3.8 doClassCleanups (#8033) 2020-11-19 12:07:15 +02:00
Tim Hoffmann
b7ba76653d Prefix contextmanagers with module name in doc examples (#8044)
* Prefix contextmanagers with module name in doc examples

* Import pytest explicitly for doctests

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-11-19 12:06:24 +02:00
Ran Benita
30d89fd07e Merge pull request #8039 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-html-3.0.0
build(deps): bump pytest-html from 2.1.1 to 3.0.0 in /testing/plugins_integration
2020-11-19 12:05:04 +02:00
Ran Benita
8ea8cdb36d Merge pull request #8040 from symonk/remove-unused-var-in-pytester
stop assigning nextline if its potentially not used
2020-11-17 18:00:17 +02:00
symonk
3b677f79f4 stop assigning nextline if its potentially not used 2020-11-17 11:30:09 +00:00
Ran Benita
537215a16c Merge pull request #7987 from bluetech/ci-deprecated
ci: fix deprecation message & couple updates
2020-11-16 23:29:35 +02:00
Ran Benita
c2f949d68e Merge pull request #8013 from itsmegarvi/master
#7942 migrate from tempdir to pytester
2020-11-16 19:44:43 +02:00
Garvit Shubham
6fe9d2fb9f testing: convert test_{conftest,recwarn,tmpdir} to pytester 2020-11-16 19:22:57 +02:00
dependabot[bot]
3a899ced76 build(deps): bump pytest-html in /testing/plugins_integration
Bumps [pytest-html](https://github.com/pytest-dev/pytest-html) from 2.1.1 to 3.0.0.
- [Release notes](https://github.com/pytest-dev/pytest-html/releases)
- [Changelog](https://github.com/pytest-dev/pytest-html/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-html/compare/v2.1.1...v3.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-16 03:15:18 +00:00
Ran Benita
825b81ba52 Merge pull request #8014 from bluetech/pyc-pep552
assertion/rewrite: write pyc's according to PEP-552 on Python>=3.7
2020-11-14 23:38:45 +02:00
Ran Benita
1d532da49e assertion/rewrite: write pyc's according to PEP-552 on Python>=3.7
Python 3.7 changes the pyc format by adding a flags byte. Even though it
is not necessary for us to match it, it is nice to be able to read pyc
files we emit for debugging the rewriter.

Update our custom pyc files to use that format. We write flags==0
meaning we still use the mtime+size format rather the newer hash format.
2020-11-14 23:20:12 +02:00
Ran Benita
767cbeb086 Merge pull request #8002 from mcsitter/add-pythonenv-gitignore
Add pythonenv* to gitignore
2020-11-13 23:14:23 +02:00
Maximilian Cosmo Sitter
25e4dd0d2c Fix scope to accomodate requested changes 2020-11-13 20:00:44 +00:00
Ran Benita
c14f498622 Merge pull request #7908 from bluetech/post-release-tag
RELEASING: make sure we have an accurate version in a commit in master
2020-11-13 12:57:53 +02:00
Ran Benita
f6b682ad49 RELEASING: start new dev cycle by tagging MAJOR.{MINOR+1}.0.dev0 in master
This is needed so setuptools-scm in master shows an accurate version.
In particular, higher than the stable branch.
2020-11-13 12:38:58 +02:00
Ran Benita
701ff1f5a1 ci: only deploy to PyPI on X.Y.Z{,rcN} tags
We want to reserve other tags for our own purposes without it creating a
release.
2020-11-13 12:38:30 +02:00
Ran Benita
f1e6fdcddb Export types of builtin fixture for type annotations
In order to allow users to type annotate fixtures they request, the
types need to be imported from the `pytest` namespace. They are/were
always available to import from the `_pytest` namespace, but that is
not guaranteed to be stable.

These types are only exported for the purpose of typing. Specifically,
the following are *not* public:

- Construction (`__init__`)
- Subclassing
- staticmethods and classmethods

We try to combat them being used anyway by:

- Marking the classes as `@final` when possible (already done).

- Not documenting private stuff in the API Reference.

- Using `_`-prefixed names or marking as `:meta private:` for private
  stuff.

- Adding a keyword-only `_ispytest=False` to private constructors,
  warning if False, and changing pytest itself to pass True. In the
  future it will (hopefully) become a hard error.

Hopefully that will be enough.
2020-11-13 11:25:09 +02:00
Ran Benita
b050578882 pytester: split asserts to a separate plugin, don't rewrite pytester itself
An upcoming commit wants to import from `_pytest.pytester` in the public
`pytest` module. This means that `_pytest.pytester` would start to get
imported during import time, which it hasn't up to now -- it was
imported by the plugin loader (if requested). When a plugin is loaded,
it is subjected to assertion rewriting, but only if the module isn't
imported yet, it issues a warning "Module already imported so cannot be
rewritten" and skips the rewriting. So we'd end up with the pytester
plugin not being rewritten, but it wants to be.

Absent better ideas, the solution here is to split the pytester
assertions to their own plugin (which will always only be imported by
the plugin loader) and exclude pytester itself from plugin rewriting.
2020-11-13 11:25:09 +02:00
Ran Benita
66311ff702 Merge pull request #8022 from bluetech/doctest-init
main: fix only one doctest collected on pytest --doctest-modules __init__.py
2020-11-13 10:46:46 +02:00
Ran Benita
ea3c0aa245 Merge pull request #8019 from JosiasAurel/mypytester-change-01
Migrate from testdir to pytester
2020-11-13 10:36:03 +02:00
Bruno Oliveira
843bca8c0c Merge pull request #8025 from adamchainz/issue_8023_norecursedirs
Add 'node_modules' to norecursedirs
2020-11-12 07:36:38 -03:00
Josias Aurel
fa148eadfe Update testing/test_faulthandler.py
Co-authored-by: Sanket Duthade <duthades@gmail.com>
2020-11-11 18:35:06 +01:00
Bruno Oliveira
ff9e35243e Merge pull request #7928 from graingert/feature-request-template 2020-11-11 09:20:00 -03:00
Josias Aurel
06a597db14 Add type annotations 2020-11-11 05:02:32 +01:00
Josias Aurel
1ed8159c7d Update testing/test_faulthandler.py
Co-authored-by: Sanket Duthade <duthades@gmail.com>
2020-11-11 04:45:57 +01:00
Josias Aurel
8320c07134 Update testing/test_faulthandler.py
Co-authored-by: Sanket Duthade <duthades@gmail.com>
2020-11-11 04:45:42 +01:00
Adam Johnson
39b2706f6a Add 'node_modules' to norecursedirs
Fixes #8023.
2020-11-11 01:52:18 +00:00
Ran Benita
265cc2cfec main: fix only one doctest collected on pytest --doctest-modules __init__.py
When --doctest-modules is used, an `__init__.py` file is not a `Package`
but a `DoctestModule`, but some collection code assumed that
`__init__.py` implies a `Package`. That code caused only a single test
to be collected in the scenario in the subject.

Tighten up this check to explicitly check for `Package`. There are
better solutions, but for another time.

Report & test by Nick Gates <nickgatzgates@gmail.com>.
2020-11-10 22:50:46 +02:00
Josias Aurel
043ed55056 Migrate from testdir to pytester 2020-11-09 18:07:34 +01:00
Ran Benita
e986d84466 Merge pull request #8006 from bluetech/export-MonkeyPatch
Export MonkeyPatch as pytest.MonkeyPatch
2020-11-09 11:45:38 +02:00
Ran Benita
6f13d1b03b Export MonkeyPatch as pytest.MonkeyPatch
We want to export `pytest.MonkeyPatch` for the purpose of
type-annotating the `monkeypatch` fixture. For other fixtures we export
in this way, we also make direct construction of them (e.g.
`MonkeyPatch()`) private. But unlike the others, `MonkeyPatch` is also
widely used directly already, mostly because the `monkeypatch` fixture
only works in `function` scope (issue #363), but also in other cases. So
making it private will be annoying and we don't offer a decent
replacement yet.

So, let's just make direct construction public & documented.
2020-11-09 11:28:15 +02:00
Ran Benita
7aa5e49fc4 Merge pull request #8012 from pytest-dev/dependabot/pip/testing/plugins_integration/django-3.1.3
build(deps): bump django from 3.1.2 to 3.1.3 in /testing/plugins_integration
2020-11-09 11:26:35 +02:00
dependabot[bot]
02d4b3d75f build(deps): bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.2...3.1.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-09 03:17:35 +00:00
Bruno Oliveira
b2e7b9df9e Merge pull request #8010 from nicoddemus/pluralize 2020-11-08 13:31:27 -03:00
Bruno Oliveira
c7f8ad17f5 Rename _make_plural to pluralize
A bit shorter and a better name, IMHO.
2020-11-08 12:42:52 -03:00
Hugo Martins
5b2e5e8a40 Improve summary stats when using '--collect-only' (#7875)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-11-08 11:45:10 -03:00
Ran Benita
29f2f4e854 Merge pull request #8005 from bluetech/pytest-import
Stop importing `pytest` to avoid upcoming import cycles
2020-11-08 14:15:19 +02:00
Bruno Oliveira
10a3a49bd6 Merge pull request #8007 from bluetech/dont-ignore-already-imported
testing: don't ignore "Module already imported so cannot be rewritten" warning
2020-11-07 22:46:15 -03:00
frankgerhardt
9bc633064b Capitalize headlines (#8008) 2020-11-07 22:44:04 -03:00
Ran Benita
361f9e20c3 testing: don't ignore "Module already imported so cannot be rewritten" warning
The test suite passes without it being ignored. The absence of this
warning cost me some head-scratching time, so enable it again.
2020-11-07 18:14:21 +02:00
Ran Benita
1cbb0c3554 Stop importing pytest to avoid upcoming import cycles
Don't import `pytest` from within some `_pytest` modules since an
upcoming commit will import from them into `pytest`.

It would have been nice not to have to do it, so that internal plugins
look more like external plugins, but with the existing layout this seems
unavoidable.
2020-11-07 18:08:30 +02:00
Ran Benita
c784c142a4 Merge pull request #7988 from bluetech/deprecate-yield-fixture
fixtures: deprecate pytest.yield_fixture()
2020-11-07 18:07:00 +02:00
Ran Benita
4c0513bc18 fixtures: deprecate pytest.yield_fixture() 2020-11-07 17:06:40 +02:00
Sanket Duthade
3bcd316f07 test_collection.py migrate from testdir to Pytester (#8003) 2020-11-07 16:56:00 +02:00
Garvit Shubham
6a5037a25b #7942 test_setupplan.py migrate from testdir to Pytester (#8004)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-11-07 09:29:45 -03:00
Maximilian Cosmo Sitter
a73fb6e006 Add pythonenv* to gitignore 2020-11-06 14:29:12 +00:00
Bruno Oliveira
30287b49cd Deprecate --strict (#7985)
Fix #7530
2020-11-06 09:48:20 +01:00
Anthony Sottile
4cd0fde277 Merge pull request #8001 from bluetech/silence-pyparsing-warning
testing: silence deprecation warning from older pyparsing releases
2020-11-05 08:46:50 -08:00
Ran Benita
070f8e0f9d testing: silence deprecation warning from older pyparsing releases
This causes some tests to fail when using these older versions.
2020-11-05 16:08:54 +02:00
Ran Benita
1d4cc7eb36 Merge pull request #7997 from duthades/master
#7942 test_session.py migrate from testdir to Pytester
2020-11-05 15:55:36 +02:00
duthades
b815f430e5 #7942 test_session.py migrate from testdir to pytester
- Add name to AUTHORS
2020-11-04 21:55:07 +05:30
Ran Benita
3adece9fb7 Merge pull request #7990 from bluetech/xunit-quadratic-3
unittest: fix quadratic behavior in collection of unittests using setUpClass/setup_method
2020-11-03 18:01:25 +02:00
Ran Benita
489f6f4499 unittest: fix quadratic behavior in collection of unittests using setUpClass/setup_method
This is similar to 50114d4731876dae; I missed that unittest does the
same thing.
2020-11-01 15:17:42 +02:00
Ran Benita
a95da7a425 Merge pull request #7980 from bluetech/code-changes
code: a few minor improvements
2020-11-01 09:51:39 +02:00
Ran Benita
8aa9ea95e1 ci: test on Python 3.9 final 2020-11-01 09:49:41 +02:00
Ran Benita
76226182ae ci: change cache action to v2
Supposed to be faster.
2020-11-01 09:49:41 +02:00
Ran Benita
f9d82a34f4 ci: replace deprecated ::set-env 2020-11-01 09:49:23 +02:00
Ran Benita
7fb0ea3f68 Merge pull request #7956 from csernazs/fix-7951
Fix handling recursive symlinks
2020-10-31 18:59:50 +02:00
Cserna Zsolt
8a38e7a6e8 Fix handling recursive symlinks
When pytest was run on a directory containing a recursive symlink it failed
with ELOOP as the library was not able to determine the type of the
direntry:

src/_pytest/main.py:685: in collect
    if not direntry.is_file():
E   OSError: [Errno 40] Too many levels of symbolic links: '/home/florian/proj/pytest/tests/recursive'

This is fixed by handling ELOOP and other errors in the visit function in
pathlib.py, so the entries whose is_file() call raises an OSError with the
pre-defined list of error numbers will be exluded from the result.

The _ignore_errors function was copied from Lib/pathlib.py of cpython 3.9.

Fixes #7951
2020-10-31 17:40:56 +01:00
Ran Benita
1c18fb8ccc Merge pull request #7553 from tirkarthi/namedtuple-diff
Add support to display field names in namedtuple diffs.
2020-10-31 15:02:31 +02:00
Bruno Oliveira
2753859ff0 Merge pull request #7979 from nicoddemus/metafunc-ref
Add FunctionDefinition to the reference docs
2020-10-31 10:00:48 -03:00
Ran Benita
a14a229d1b Merge pull request #7982 from bluetech/symlink-collect
pathlib: fix symlinked directories not followed during collection
2020-10-31 14:46:50 +02:00
Karthikeyan Singaravelan
9a0f4e57ee Add support to display field names in namedtuple diffs. 2020-10-31 14:41:53 +02:00
Ran Benita
dd323980f9 Merge pull request #7978 from nicoddemus/port-4.6-release-notes
Manually add the remaining 4.6.x release notes to the changelog
2020-10-31 14:37:45 +02:00
Ran Benita
6cdae8ed40 pathlib: fix symlinked directories not followed during collection 2020-10-31 14:22:15 +02:00
Bruno Oliveira
569c091769 Add FunctionDefinition to the reference docs
Fix #7968
2020-10-31 08:45:34 -03:00
Bruno Oliveira
0c7233032f Manually add the remaining 4.6.x release notes to the changelog
Fix #7967
2020-10-31 08:36:26 -03:00
Ran Benita
531416cc5a code: simplify Code construction 2020-10-31 12:40:25 +02:00
Ran Benita
6506f016ac testing/test_source: use unqualified imports 2020-10-31 12:40:25 +02:00
Ran Benita
a1df458e85 code: use properties for derived attributes, use slots
Make the objects more light weight.

Remove unused properties.
2020-10-31 12:40:25 +02:00
Ariel Pillemer
a7e38c5c61 pytest-dev#7942 test_runner_xunit.py (#7964) 2020-10-31 12:38:11 +02:00
crricks
3c7eb5a398 migrated test_nodes.py from testdir to pytester #7492. (#7969) 2020-10-30 22:34:05 +02:00
Ran Benita
ad94456ca0 Merge pull request #7976 from symonk/7942-refactor-stepwise-to-use-pytester
#7942 refactor stepwise tests to utilize pytester
2020-10-30 22:15:12 +02:00
Christine Mecklenborg
aa843746a4 Migrate test_error_diffs.py from testdir to pytester (#7971) 2020-10-30 22:12:40 +02:00
symonk
c58abf7ad1 #7942 refactor stepwise tests to utilize pytester 2020-10-30 19:21:42 +00:00
Ran Benita
5913cd20ec assertion/util: remove unhelpful type_fns indirection
It doesn't serve any purpose that I am able to discern.
2020-10-30 21:15:48 +02:00
Simon K
6cddeb8cb3 #7938 - [Plugin: Stepwise][Enhancements] Refactoring, smarter registration & --sw-skip functionality (#7939)
* adding --sw-skip shorthand for stepwise skip

* be explicit rather than implicit with default args for stepwise

* add constant for sw cache dir; only register plugin if necessary rather check check activity always;

* use str format; remove unused args in hooks

* assert cache upfront, allow stepwise to have a reference to the cache

* type hinting lf, skip, move literal strings into module constants

* convert parametrized option into a list

* add a sessionfinish hook for stepwise to keep backwards behaviour the same

* add changelog for #7938

* Improve performance of stepwise modifyitems & address PR feedback

* add test for stepwise deselected based on performance enhancements

* Apply suggestions from code review

* delete from items, account for edge case where failed_index = 0

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-10-30 19:13:06 +00:00
Ran Benita
0cd190f037 Merge pull request #7970 from cmecklenborg/pytester_refactor_test_assertion
Migrate test_assertion.py from testdir to pytester
2020-10-30 20:56:21 +02:00
Christine Mecklenborg
47ff911c8f Migrate test_assertion.py from testdir to pytester 2020-10-29 20:39:44 -05:00
Ran Benita
8f52fc777a Merge pull request #7962 from bluetech/mypy-typed-deps
pre-commit: install typed dependencies in the mypy target
2020-10-29 17:52:12 +02:00
Ran Benita
65dfa98877 Merge pull request #7961 from bluetech/rm-checkqa-mypy
tox: remove checkqa-mypy environment
2020-10-29 17:25:51 +02:00
Christine Mecklenborg
65148e3120 Migrate test_compat.py from testdir to pytester (#7963) 2020-10-29 09:56:34 +02:00
Christine Mecklenborg
460b51dd95 Migrate test_setuponly.py from testdir to pytester (#7959) 2020-10-29 09:55:30 +02:00
Christine Mecklenborg
efe470bf1c Migrate test_assertrewrite.py from testdir to pytester (#7952) 2020-10-29 09:54:34 +02:00
Ran Benita
e3ce5d6b53 pre-commit: install typed dependencies in the mypy target
Otherwise, mypy doesn't know about them and their types are considered
Any.
2020-10-28 22:22:33 +02:00
Ran Benita
de810152ec tox: remove checkqa-mypy environment
We run mypy through pre-commit, and we don't keep duplicate targets in
tox for all of the other linters. Since this adds some (small)
maintenance overhead, remove it.
2020-10-28 22:16:25 +02:00
Bruno Oliveira
b95991aeea Merge pull request #7960 from nicoddemus/cherry-pick-release
Merge pull request #7958 from pytest-dev/release-6.1.2
2020-10-28 14:36:37 -03:00
Bruno Oliveira
5711ced250 Merge pull request #7958 from pytest-dev/release-6.1.2
Prepare release 6.1.2

(cherry picked from commit 1ed903e8fc)
2020-10-28 14:23:09 -03:00
Christine M
8d369f73ba Migrate test_skipping.py from testdir to pytester (#7953) 2020-10-28 17:05:54 +02:00
Ran Benita
6cd6d9b61a Merge pull request #7949 from gcamargo1/gcamargo1-patch-1
Update doctest.rst
2020-10-28 15:56:59 +02:00
Vasilis Gerakaris
a431310c0a Increase temp dir deletion period to 3 days (#7914)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-10-28 08:23:35 -03:00
Ran Benita
78c09b9931 Merge pull request #7944 from symonk/refactor-test-mark-to-use-pytester
Refactor test_warning_types.py & test_mark.py to use pytester
2020-10-27 21:06:42 +02:00
symonk
434e30424e Address feedback for converting testdir to pytester 2020-10-27 17:50:54 +00:00
Gustavo Camargo
b308c6ddb3 Update doctest.rst 2020-10-26 14:30:00 -05:00
Ran Benita
0fe8a8dfe6 Merge pull request #7945 from proggga/fix_test_helpconfig_7942
#7942 test_helpconfig.py migrate from testdir to pytester
2020-10-26 17:52:41 +02:00
Mikhail Fesenko
cd9b3618c7 #7942 test_helpconfig.py migrate from testdir to pytester 2020-10-26 18:30:48 +03:00
Bruno Oliveira
20b710c4b4 Merge pull request #7948 from bluetech/testing-conftest-pytester
testing: make conftest stuff check for pytester not testdir
2020-10-26 12:08:19 -03:00
Ran Benita
c31f4dc112 testing: make conftest stuff check for pytester not testdir
testdir uses pytester so this applies to it as well, but now includes
pytester as well.
2020-10-26 15:01:38 +02:00
Ran Benita
096d096539 Merge pull request #7946 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-django-4.1.0
build(deps): bump pytest-django from 4.0.0 to 4.1.0 in /testing/plugins_integration
2020-10-26 14:36:49 +02:00
dependabot[bot]
f7c5067823 build(deps): bump pytest-django in /testing/plugins_integration
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.0.0 to 4.1.0.
- [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.0.0...v4.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-26 03:15:50 +00:00
symonk
cde50db6e7 add type hint to parametrized warning_class 2020-10-25 18:31:43 +00:00
symonk
c818ac2248 Tidy up type hints for pytest in test_marks & test_warning_types 2020-10-25 18:03:59 +00:00
symonk
6b7203aba7 add conversion for test_warning_types.py also 2020-10-25 17:38:12 +00:00
symonk
7495d2c345 add missing pytester type hints 2020-10-25 17:33:40 +00:00
symonk
1bd83e75a4 refactor test mark to use new pytester 2020-10-25 17:27:19 +00:00
Ran Benita
8105e60f20 Merge pull request #7937 from bluetech/testing-fixes
testing: fix pexpect hang
2020-10-25 16:53:03 +02:00
Ran Benita
ca82214444 pytester: workaround issue causing spawn to crash or hang
In pytester tests, pytest stashes & restores the sys.modules for each
test. So if the test imports a new module, it is initialized anew each
time.

Turns out the readline module isn't multi-init safe, which causes
pytester.spawn to crash or hang. So preserve it as a workaround.
2020-10-25 16:31:47 +02:00
Ran Benita
897f151e94 testing: use pytester.spawn instead of testdir
Part of investigating a bug, but didn't fix it.
2020-10-25 10:11:10 +02:00
Ran Benita
25dee8fef6 testing: fix test_assertrewrite with PYTHONPYCACHEPREFIX
Make the tests work when running with PYTHONPYCACHEPREFIX (possible when
running in a dirty environment, not under tox).
2020-10-25 10:11:10 +02:00
Ran Benita
d9ac2efbcd testing: python 3.10 fix 2020-10-25 01:27:44 +02:00
Ran Benita
65e6e39b76 Merge pull request #7931 from bluetech/xunit-quadratic-2
fixtures: fix quadratic behavior in the number of autouse fixtures
2020-10-25 01:24:38 +03:00
Ran Benita
470ea504e2 fixtures: fix quadratic behavior in the number of autouse fixtures
It turns out all autouse fixtures are kept in a global list, and thinned
out for a particular node using a linear scan of the entire list each
time.

Change the list to a dict, and only take the nodes we need.
2020-10-25 00:49:23 +03:00
Ran Benita
d6becfa177 fixtures: change _getautousenames to an iterator
This reads better.
2020-10-25 00:49:06 +03:00
Ran Benita
aa0e2d654f fixtures: use a faster replacement for ischildnode
ischildnode can be quite hot in some cases involving many fixtures.
However it is always used in a way that the nodeid is constant and the
baseid is iterated. So we can save work by pre-computing the parents of
the nodeid and use a simple containment test.

The `_getautousenames` function has the same stuff open-coded, so change
it to use the new function as well.
2020-10-25 00:48:35 +03:00
Bruno Oliveira
f7d4f457d0 Merge pull request #7934 from symonk/small-adjustment-to-hookspec-docstrings
Replace term 'triple' with 'tuple' in hookspec docstrings
2020-10-24 08:01:05 -03:00
symonk
751575fa97 make some hookspec docstrings technically correct 2020-10-24 10:59:25 +01:00
Thomas Grainger
e14b724ff4 Update .github/ISSUE_TEMPLATE/2_feature_request.md 2020-10-23 22:35:56 +01:00
Bruno Oliveira
daa11ab9f1 Merge pull request #7930 from bluetech/pytester-doc-fixes
pytester: minor doc fixes
2020-10-23 17:21:22 -03:00
Ran Benita
0b14350f23 Merge pull request #7929 from bluetech/xunit-quadratic-1
python: fix quadratic behavior in collection of items using xunit fixtures
2020-10-23 23:10:25 +03:00
Ran Benita
50114d4731 python: fix quadratic behavior in collection of items using xunit fixtures
Since commit 0f918b1a9d pytest uses auto-generated autouse
pytest fixtures for the xunit fixtures
{setup,teardown}_{module,class,method,function}. All of these fixtures
were given the same name.

Unfortunately, pytest fixture lookup for a name works by grabbing all of
the fixtures globally declared with a name and filtering to only those
which match the specific node. So each xunit-using item iterates over a
list (of fixturedefs) of a size of all previous same-xunit-using items,
i.e. quadratic.

Fixing this properly to use a better data structure is likely to take
some effort, but we can avoid the immediate problem by just using
a different name for each item's autouse fixture, so it only matches
itself.

A benchmark is added to demonstrate the issue. It is still way too slow
after the fix and possibly still quadratic, but for a different reason
which is another matter.

Running --collect-only, before (snipped):

         202533232 function calls (201902604 primitive calls) in 86.379 seconds

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   85.688   85.688 main.py:320(pytest_collection)
        1    0.000    0.000   85.688   85.688 main.py:567(perform_collect)
80557/556    0.021    0.000   85.050    0.153 {method 'extend' of 'list' objects}
85001/15001  0.166    0.000   85.045    0.006 main.py:785(genitems)
    10002    0.050    0.000   84.219    0.008 runner.py:455(collect_one_node)
    10002    0.049    0.000   83.763    0.008 runner.py:340(pytest_make_collect_report)
    10002    0.079    0.000   83.668    0.008 runner.py:298(from_call)
    10002    0.019    0.000   83.541    0.008 runner.py:341(<lambda>)
     5001    0.184    0.000   81.922    0.016 python.py:412(collect)
     5000    0.020    0.000   81.072    0.016 python.py:842(collect)
    30003    0.118    0.000   78.478    0.003 python.py:218(pytest_pycollect_makeitem)
    30000    0.190    0.000   77.957    0.003 python.py:450(_genfunctions)
    40001    0.081    0.000   76.664    0.002 nodes.py:183(from_parent)
    30000    0.087    0.000   76.629    0.003 python.py:1595(from_parent)
    40002    0.092    0.000   76.583    0.002 nodes.py:102(_create)
    30000    0.305    0.000   76.404    0.003 python.py:1533(__init__)
    15000    0.132    0.000   74.765    0.005 fixtures.py:1439(getfixtureinfo)
    15000    0.165    0.000   73.664    0.005 fixtures.py:1492(getfixtureclosure)
    15000    0.044    0.000   57.584    0.004 fixtures.py:1653(getfixturedefs)
    30000   18.840    0.001   57.540    0.002 fixtures.py:1668(_matchfactories)
 37507500   31.352    0.000   38.700    0.000 nodes.py:76(ischildnode)
    15000   10.464    0.001   15.806    0.001 fixtures.py:1479(_getautousenames)
112930587/112910019   7.333    0.000    7.339    0.000 {built-in method builtins.len}

After:

         51890333 function calls (51259706 primitive calls) in 27.306 seconds

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   26.783   26.783 main.py:320(pytest_collection)
        1    0.000    0.000   26.783   26.783 main.py:567(perform_collect)
80557/556    0.020    0.000   26.108    0.047 {method 'extend' of 'list' objects}
85001/15001  0.151    0.000   26.103    0.002 main.py:785(genitems)
    10002    0.047    0.000   25.324    0.003 runner.py:455(collect_one_node)
    10002    0.045    0.000   24.888    0.002 runner.py:340(pytest_make_collect_report)
    10002    0.069    0.000   24.805    0.002 runner.py:298(from_call)
    10002    0.017    0.000   24.690    0.002 runner.py:341(<lambda>)
     5001    0.168    0.000   23.150    0.005 python.py:412(collect)
     5000    0.019    0.000   22.223    0.004 python.py:858(collect)
    30003    0.101    0.000   19.818    0.001 python.py:218(pytest_pycollect_makeitem)
    30000    0.161    0.000   19.368    0.001 python.py:450(_genfunctions)
    30000    0.302    0.000   18.236    0.001 python.py:1611(from_parent)
    40001    0.084    0.000   18.051    0.000 nodes.py:183(from_parent)
    40002    0.116    0.000   17.967    0.000 nodes.py:102(_create)
    30000    0.308    0.000   17.770    0.001 python.py:1549(__init__)
    15000    0.117    0.000   16.111    0.001 fixtures.py:1439(getfixtureinfo)
    15000    0.134    0.000   15.135    0.001 fixtures.py:1492(getfixtureclosure)
    15000    9.320    0.001   14.738    0.001 fixtures.py:1479(_getautousenames)
2020-10-23 22:36:23 +03:00
Ran Benita
1c0c56dfb9 pytester: minor doc fixes 2020-10-23 21:01:31 +03:00
Emiel van de Laar
0d9e27a363 doc: Remove unused imports in examples (#7924)
The "os" imports in the `tmp_path` and `tmpdir` fixture examples are
unused and thus have been removed to prevent confusion.
2020-10-23 17:31:17 +03:00
Ran Benita
0cdbf8b377 Merge pull request #7910 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-trio-0.7.0
build(deps): bump pytest-trio from 0.6.0 to 0.7.0 in /testing/plugins_integration
2020-10-23 16:52:54 +03:00
Ran Benita
e1848349a7 Merge pull request #7909 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-django-4.0.0
build(deps): bump pytest-django from 3.10.0 to 4.0.0 in /testing/plugins_integration
2020-10-23 16:52:29 +03:00
Thomas Grainger
03363473f7 expand feature request issue template 2020-10-23 14:03:24 +01:00
Matthias Gabriel
e8504e04f3 Fix small typo in reference.rst (#7922)
Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
2020-10-22 07:11:49 -03:00
Bruno Oliveira
824e9cf67a Merge pull request #7917 from bluetech/ci-timeout
ci: decrease job timeout from 6 hours to 30 minutes
2020-10-21 08:02:30 -03:00
Ran Benita
fe69d0d680 ci: decrease job timeout from 6 hours to 30 minutes
We don't have any jobs that should go beyond that, so let's be nicer to
the CI host and quicker to report the failure.
2020-10-21 10:17:25 +03:00
Ran Benita
a66b6b857a Merge pull request #7915 from bluetech/fix-lf-package
cacheprovider: fix some files in packages getting lost from --lf
2020-10-21 10:12:32 +03:00
Ran Benita
afaabdda8c cacheprovider: fix some files in packages getting lost from --lf
--lf has an optimization where it skips collecting Modules (python
files) which don't contain failing tests. The optimization works by
getting the paths of all cached failed tests and skipping the collection
of Modules whose path is not included in that list.

In pytest, Package nodes are Module nodes with the fspath being the file
`<package dir>/__init__.py`. Since it's a Module the logic above
triggered for it, and because it's an `__init__.py` file which is
unlikely to have any failing tests in it, it is skipped, which causes
its entire directory to be skipped, including any Modules inside it with
failing tests.

Fix by special-casing Packages to never filter. This means entire
Packages are never filtered, the Modules themselves are always checked.
It is reasonable to consider an optimization which does filter entire
packages bases on parent paths etc. but this wouldn't actually save any
real work so is really not worth it.
2020-10-19 19:02:43 +03:00
Bruno Oliveira
f453460ae7 Merge pull request #7912 from hugovk/rm-3.5 2020-10-19 10:27:49 -03:00
Hugo van Kemenade
c9e5042d6d Remove redundant Python 2.7 code 2020-10-19 10:47:35 +03:00
Hugo van Kemenade
a642650e17 Drop support for EOL Python 3.5 2020-10-19 10:02:36 +03:00
dependabot[bot]
f335144d1d build(deps): bump pytest-trio in /testing/plugins_integration
Bumps [pytest-trio](https://github.com/python-trio/pytest-trio) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/python-trio/pytest-trio/releases)
- [Commits](https://github.com/python-trio/pytest-trio/compare/v0.6.0...v0.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-19 03:05:42 +00:00
dependabot[bot]
23aac10391 build(deps): bump pytest-django in /testing/plugins_integration
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 3.10.0 to 4.0.0.
- [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/v3.10.0...v4.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-19 03:05:42 +00:00
Ran Benita
f61d4ed9d5 Merge pull request #7907 from bluetech/mypy-790
Update mypy 0.782 -> 0.790, iniconfig typing
2020-10-19 00:28:04 +03:00
Ran Benita
09e38b1697 runner: combine a sort+reverse to a sort(reverse=True)
Suggested by Zac-HD.
2020-10-19 00:02:01 +03:00
Ran Benita
1b23a111d2 Update mypy 0.782 -> 0.790 2020-10-17 19:25:45 +03:00
Ran Benita
e5e47c1097 Fix typing related to iniconfig
iniconfig now has typing stubs which reveal a couple issues.
2020-10-17 19:25:45 +03:00
Bruno Oliveira
0a258f534f Fix linting 2020-10-17 08:42:15 -03:00
Bruno Oliveira
f58d0a8c3d Merge pull request #7903 from ekrecker/patch-1
Fix typos
2020-10-17 08:41:10 -03:00
Nimesh Vashistha
991bc7bd50 Added note to writing_plugins.rst (#7896)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-10-17 08:26:30 -03:00
kwgchi
fc70fd23a2 Fix typos 2020-10-16 21:29:58 +09:00
Bruno Oliveira
b4c28dcaa2 Merge pull request #7893 from nicoddemus/testdir-docs-7892
List Testdir members in the docs
2020-10-14 10:34:49 -03:00
Prashant Sharma
5182c73fea Add example for registering multiple custom mark (#7886) 2020-10-14 15:17:50 +03:00
Bruno Oliveira
3cae145e41 List Testdir members in the docs
Also include docstrings pointing to the counterparts in Pytester.

Fix #7892
2020-10-13 12:01:11 -03:00
Bruno Oliveira
69419cb700 New pytester fixture (#7854) 2020-10-12 12:13:06 -03:00
Bruno Oliveira
cb578a918e Merge pull request #7883 from pytest-dev/dependabot/pip/testing/plugins_integration/django-3.1.2
build(deps): bump django from 3.1.1 to 3.1.2 in /testing/plugins_integration
2020-10-12 09:43:17 -03:00
dependabot[bot]
b53a8bb60f build(deps): bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.1.1...3.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-12 12:39:31 +00:00
Bruno Oliveira
3434488af4 Merge pull request #7882 from pytest-dev/dependabot/pip/testing/plugins_integration/anyio-curiotrio--2.0.2
build(deps): bump anyio[curio,trio] from 2.0.0 to 2.0.2 in /testing/plugins_integration
2020-10-12 09:38:54 -03:00
Ronny Pfannschmidt
cdaa1b52be Merge pull request #7884 from bluetech/release-on-comment-fixes
release-on-comment: add "Closes <release issue number>" to release PR
2020-10-11 10:49:45 +02:00
Ran Benita
008863aeb9 release-on-comment: add "Closes <release issue number>" to release PR 2020-10-10 19:01:41 +03:00
dependabot[bot]
37cf4693cf build(deps): bump anyio[curio,trio] in /testing/plugins_integration
Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 2.0.0 to 2.0.2.
- [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/2.0.0...2.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-10 15:52:11 +00:00
Hugo van Kemenade
3059caf1ee Put smoke test deps in requirements.txt for Dependabot (#7806) 2020-10-10 18:51:35 +03:00
Ran Benita
55127b2142 Merge pull request #7879 from charlesaracil-ulti/ask_for_commit_after_in_contributing
ask for commit after changelog and authors file edit in contributing
2020-10-09 22:34:06 +03:00
Charles Aracil
2e322f183c ask for commit after changelog and authors file edit (#7878) 2020-10-09 16:10:54 +02:00
Zac Hatfield-Dodds
dbd082af96 Merge pull request #7874 from tanvimehta/master 2020-10-08 17:36:54 +11:00
Tanvi Mehta
d093931464 Added name to authors list 2020-10-07 22:57:52 -07:00
Tanvi Mehta
779b511bfe Fixed formatting 2020-10-07 22:25:27 -07:00
Tanvi Mehta
43b1eb3c9e Use instead of a in Issue #7868
Use `collections.Counter` instead of a `dict` in `terminal.py` Issue #7868
2020-10-07 21:51:28 -07:00
Kyle Altendorf
5acc55e838 Merge pull request #7872 from altendky/addini_takes_string
Document that Parser.addini() can take, and defaults to, 'string'
2020-10-07 19:12:54 -04:00
Kyle Altendorf
1630c37266 Added changelog/7872.doc.rst 2020-10-07 18:06:13 -04:00
Kyle Altendorf
76acb44330 Update tests to cover explicit None and "string" as addini() types 2020-10-07 17:56:54 -04:00
Kyle Altendorf
af3759a503 Parser.addini() can take and defaults to 'string' 2020-10-07 17:21:55 -04:00
Anthony Sottile
95917f8833 Merge pull request #7863 from asottile/py36_order_preserving_dict
py36+: remove _pytest.compat.order_preserving_dict
2020-10-06 08:35:38 -07:00
Manuel Mariñez
13ddec9a00 Add alias clarification to deprecation warning (#7829)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-10-06 11:48:34 -03:00
Anthony Sottile
b6b75383ce py36+: remove _pytest.compat.order_preserving_dict 2020-10-06 00:22:09 -07:00
Anthony Sottile
f54ec30a6d Merge pull request #7862 from asottile/comm2ann
py36+: com2ann
2020-10-06 00:20:57 -07:00
Anthony Sottile
33d119f71a py36+: com2ann 2020-10-05 18:33:17 -07:00
William Jamir Silva
703e89134c Update reference.rst informing the default junit_family (#7860)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-10-05 14:04:37 -03:00
Anthony Sottile
f81c6c00a9 Merge pull request #7852 from asottile/py36_pyupgrade
py36+: pyupgrade: py36+
2020-10-04 08:25:32 -07:00
Ran Benita
1c08f1dd0f Merge pull request #7844 from bluetech/typing-fixtures
fixtures: some type annotations, remove cyclic dependency
2020-10-04 17:41:33 +03:00
Bruno Oliveira
7581f0b3a1 Merge pull request #7853 from albertvillanova/doc-patch-1 2020-10-04 09:15:46 -03:00
Albert Villanova del Moral
8593b57666 Update link to numpy 2020-10-04 08:54:43 +02:00
Ran Benita
d0a3f1dcbc nodes: remove cyclic dependency on _pytest.fixtures
- Change the fixtures plugin to store its one piece of data on the node's
  Store instead of directly.

- Import FixtureLookupError lazily.
2020-10-04 09:44:45 +03:00
Ran Benita
bf09e7792f fixtures: some type annotations 2020-10-04 09:43:58 +03:00
Ran Benita
7f794b7aff Merge pull request #7851 from bluetech/cherry-pick-release
Merge pull request #7849 from pytest-dev/release-6.1.1
2020-10-03 22:57:41 +03:00
Anthony Sottile
66bd44c13a py36+: pyupgrade: py36+ 2020-10-03 12:46:54 -07:00
Anthony Sottile
b1bcb9fba8 Merge pull request #7850 from asottile/py36_pathlib_export
py36+: remove rexport of Path and PurePath
2020-10-03 12:43:25 -07:00
Ran Benita
fd74dd3dcb Merge pull request #7849 from pytest-dev/release-6.1.1
Prepare release 6.1.1

(cherry picked from commit 69d903260d)
2020-10-03 22:38:33 +03:00
Anthony Sottile
fb1d550aac py36+: remove rexport of Path and PurePath 2020-10-03 12:16:52 -07:00
Anthony Sottile
022ac9b9e8 Merge pull request #7846 from asottile/py36_black
py36+: update the target version of black to py36
2020-10-03 11:45:45 -07:00
Anthony Sottile
3b957c2244 Merge pull request #7845 from pytest-dev/py36_overload
py36+: remove _pytest.compat.overload
2020-10-03 11:45:29 -07:00
Hugo van Kemenade
133e8af4ee Merge pull request #7805 from hugovk/pytest-rerunfailures
Smoke test pytest-rerunfailures
2020-10-03 20:17:06 +03:00
Anthony Sottile
f295b0267d py36+: update the target version of black to py36 2020-10-03 08:17:22 -07:00
Anthony Sottile
7f0d2beb50 py36+: remove _pytest.compat.overload 2020-10-03 08:01:22 -07:00
Anthony Sottile
6ed07a1c25 Merge pull request #7840 from asottile/py36_typing_Type
py36+: from typing import Type: no longer need guard
2020-10-03 07:44:06 -07:00
Anthony Sottile
aa077ab188 Merge pull request #7841 from asottile/py36_todo
py36+: resolve py36 TODOs
2020-10-03 07:43:15 -07:00
Anthony Sottile
c2a197f351 Merge pull request #7839 from asottile/py36_compat_fspath
py36+: remove _pytest.compat.fspath
2020-10-03 07:42:54 -07:00
Ran Benita
5efddd32db Merge pull request #7826 from bluetech/doc-final
doc: patch Sphinx to detect our `@final` for marking classes as `final`
2020-10-03 13:29:17 +03:00
Ran Benita
7705e5e624 doc: patch Sphinx to detect our @final for marking classes as final
Thanks to Dominic Davis-Foster for code & assistance.
2020-10-03 13:13:14 +03:00
Ran Benita
a6a7ba57e0 Merge pull request #7817 from bluetech/fix-testpaths-bestrelpath2
terminal: fix crash in header reporting when absolute testpaths is used
2020-10-03 12:59:18 +03:00
Anthony Sottile
53b5f64b4b py36+: resolve py36 TODOs 2020-10-02 19:57:55 -07:00
Anthony Sottile
bfadd4060e py36+: from typing import Type: no longer need guard 2020-10-02 19:50:10 -07:00
Anthony Sottile
be43c7c67b py36+: remove _pytest.compat.fspath 2020-10-02 19:49:32 -07:00
Anthony Sottile
a23666d554 Merge pull request #7838 from asottile/py36_requires_ordered_markup
py36+: remove requires_ordered_markup
2020-10-02 19:48:01 -07:00
Anthony Sottile
ced0a52a87 Merge pull request #7837 from asottile/py36_union_pattern_match
py36+: remove workaround for Union[Pattern/Match] bug
2020-10-02 19:47:50 -07:00
Anthony Sottile
2c7b7d8f66 Merge pull request #7836 from asottile/py36_typing_X
py36+: replace typing.X with X
2020-10-02 19:47:42 -07:00
Anthony Sottile
ac189885f6 Merge pull request #7835 from asottile/py36_misc
py36+: miscellaneous (3, 6) cleanup
2020-10-02 19:47:35 -07:00
Anthony Sottile
6ba13ed528 Merge pull request #7834 from asottile/py36_TYPE_CHECKING
py36+: remove TYPE_CHECKING from _pytest.compat
2020-10-02 19:47:27 -07:00
Anthony Sottile
a6ef0f8f67 Merge pull request #7833 from asottile/py36_ModuleNotFoundError
py36+: remove _pytest.compat.MODULE_NOT_FOUND_ERROR
2020-10-02 19:47:18 -07:00
Anthony Sottile
7836c2c371 Merge pull request #7832 from asottile/py36_pathlib2
py36+: remove pathlib2 compatibility shim
2020-10-02 19:47:10 -07:00
Bruno Oliveira
6ee1eadd1c Fake setuptools-scm into using version 6.2.0a1
Due to pytest-rerunfailures latest version requiring 6.1.0, which is not
tagged on master.
2020-10-02 23:40:50 -03:00
Anthony Sottile
daba7ceb71 py36+: remove requires_ordered_markup 2020-10-02 15:06:59 -07:00
Anthony Sottile
e622cb7c41 py36+: remove workaround for Union[Pattern/Match] bug 2020-10-02 15:06:02 -07:00
Anthony Sottile
cf220b92a2 py36+: replace typing.X with X 2020-10-02 15:05:13 -07:00
Anthony Sottile
284fd45a08 py36+: miscellaneous (3, 6) cleanup 2020-10-02 15:04:16 -07:00
Anthony Sottile
a238d1f37d py36+: remove TYPE_CHECKING from _pytest.compat
automated with:

```bash
git grep -l 'from .* import TYPE_CHECKING' |
    xargs reorder-python-imports \
        --application-directories .:src \
        --remove-import 'from _pytest.compat import TYPE_CHECKING' \
        --add-import 'from typing import TYPE_CHECKING'
```
2020-10-02 15:03:24 -07:00
Anthony Sottile
1f57fb079d py36+: remove _pytest.compat.MODULE_NOT_FOUND_ERROR 2020-10-02 15:02:45 -07:00
Anthony Sottile
3c93eb0f04 py36+: remove pathlib2 compatibility shim 2020-10-02 14:59:07 -07:00
Anthony Sottile
325b988ca8 Merge pull request #7831 from asottile/py36_drop_in_CI
py36+: drop python3.5 in CI and setup.cfg
2020-10-02 14:55:02 -07:00
Anthony Sottile
179f4326df py36+: drop python3.5 in CI and setup.cfg 2020-10-02 14:00:11 -07:00
Max Voitko
cb0a13a523 Fix minor typos in doctest.rst (#7828) 2020-10-02 15:39:15 +02:00
Ran Benita
b250c9d615 Merge pull request #7813 from bluetech/findpaths-confusion
findpaths: fix regression causing incorrect rootdir to be determined
2020-09-30 13:21:18 +03:00
Ran Benita
3ecdad67b7 terminal: improve condition on whether to display testpaths in header
Make it match better the condition on whether testpaths is used (found
in config/__init__.py).
2020-09-29 15:23:47 +03:00
Ran Benita
61f80a783a terminal: fix crash in header reporting when absolute testpaths is used
Regressed in 6.1.0 in 62e249a1f9.
The `x` is an `str` but is expected to be a `pathlib.Path`. Not caught
by mypy because `config.getini()` returns `Any`.

Fix by just removing the `bestrelpath` call:

- testpaths are always relative to the rootdir, it thus would be very
  unusual to specify an absolute path there.

- The code was wrong even before the regression: `py.path.local`'s
  `bestrelpath` function expects a `py.path.local`, not an `str`. But it
  had some weird `try ... except AttributeError` fallback which just
  returns the argument, i.e. it was a no-op. So there is no behavior
  change.

- It seems reasonable to me to just print the full path if that's what
  the ini specifies.
2020-09-29 15:23:47 +03:00
Ran Benita
db08c7fbb0 pathlib: improve comments on commonpath and bestrelpath 2020-09-29 13:11:47 +03:00
Hugo van Kemenade
875f226d3b Smoke test pytest-rerunfailures 2020-09-29 12:47:46 +03:00
Ran Benita
cd67c2a8cf Merge pull request #7802 from bluetech/bump-attrs
Bump attrs requirement from >=17.4.0 to >=19.2.0
2020-09-28 19:30:58 +03:00
Ran Benita
4a9192f727 findpaths: fix regression causing incorrect rootdir to be determined
When switching from py.path.local to pathlib (70f3ad1c1f),
`local.parts(reverse=True)` was translated incorrectly, leading to the
wrong rootdir being determined in some non-trivial cases where parent
directories have config files as well.
2020-09-28 19:13:01 +03:00
Jakob van Santen
91fa11bed0 python_api: let approx() take nonnumeric values (#7710)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-09-28 12:17:23 -03:00
Ran Benita
f324b27d02 Merge pull request #7748 from nicoddemus/fix-plugin-order-docs
Improve docs about plugin discovery/loading at startup
2020-09-27 13:19:58 +03:00
Ran Benita
32bb8f3a63 Bump attrs requirement from >=17.4.0 to >=19.2.0
This allows us to remove the `ATTRS_EQ_FIELD` thing which is causing
some annoyance.
2020-09-27 13:17:59 +03:00
Anthony Sottile
28ba9ab737 Merge pull request #7801 from pytest-dev/fix-bot-typo
Fix typos in pytestbot
2020-09-26 15:53:14 -07:00
Hugo van Kemenade
14de6781d8 Fix typos in pytestbot 2020-09-26 23:46:58 +03:00
Ran Benita
e0ea00a70d Merge pull request #7798 from bluetech/cherry-pick-release
Cherry-pick 6.1.0 release notes
2020-09-26 21:24:11 +03:00
Ran Benita
19c78ab574 Merge pull request #7797 from pytest-dev/release-6.1.0
Prepare release 6.1.0

(cherry picked from commit 08a1ab3a8a)
2020-09-26 21:11:48 +03:00
Bruno Oliveira
7324e90199 Update doc/en/writing_plugins.rst
Co-authored-by: Ran Benita <ran@unusedvar.com>
2020-09-19 16:14:28 -03:00
Bruno Oliveira
541b30a044 Improve docs about plugin discovery/loading at startup
Fix #7691
2020-09-12 14:11:43 -03:00
205 changed files with 11136 additions and 6043 deletions

View File

@@ -3,3 +3,23 @@ name: 🚀 Feature Request
about: Ideas for new features and improvements
---
<!--
Thanks for suggesting a feature!
Quick check-list while suggesting features:
-->
#### What's the problem this feature will solve?
<!-- What are you trying to do, that you are unable to achieve with pytest as it currently stands? -->
#### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
<!-- Provide examples of real-world use cases that this would enable and how it solves the problem described above. -->
#### Alternative Solutions
<!-- Have you tried to workaround the problem using a pytest plugin or other tools? Or a different approach to solving this issue? Please elaborate here. -->
#### Additional context
<!-- Add any other context, links, etc. about the feature here. -->

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: pip
directory: "/testing/plugins_integration"
schedule:
interval: weekly
time: "03:00"
open-pull-requests-limit: 10
allow:
- dependency-type: direct
- dependency-type: indirect

View File

@@ -6,7 +6,8 @@ on:
- master
- "[0-9]+.[0-9]+.x"
tags:
- "*"
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
pull_request:
branches:
@@ -16,18 +17,17 @@ on:
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
name: [
"windows-py35",
"windows-py36",
"windows-py37",
"windows-py37-pluggy",
"windows-py38",
"ubuntu-py35",
"ubuntu-py36",
"ubuntu-py37",
"ubuntu-py37-pluggy",
@@ -45,11 +45,6 @@ jobs:
]
include:
- name: "windows-py35"
python: "3.5"
os: windows-latest
tox_env: "py35-xdist"
use_coverage: true
- name: "windows-py36"
python: "3.6"
os: windows-latest
@@ -68,10 +63,6 @@ jobs:
tox_env: "py38-unittestextras"
use_coverage: true
- name: "ubuntu-py35"
python: "3.5"
os: ubuntu-latest
tox_env: "py35-xdist"
- name: "ubuntu-py36"
python: "3.6"
os: ubuntu-latest
@@ -79,7 +70,7 @@ jobs:
- name: "ubuntu-py37"
python: "3.7"
os: ubuntu-latest
tox_env: "py37-lsof-numpy-oldattrs-pexpect"
tox_env: "py37-lsof-numpy-pexpect"
use_coverage: true
- name: "ubuntu-py37-pluggy"
python: "3.7"
@@ -94,7 +85,7 @@ jobs:
os: ubuntu-latest
tox_env: "py38-xdist"
- name: "ubuntu-py39"
python: "3.9-dev"
python: "3.9"
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-pypy3"
@@ -133,12 +124,6 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2
if: matrix.python != '3.9-dev'
with:
python-version: ${{ matrix.python }}
- name: Set up Python ${{ matrix.python }} (deadsnakes)
uses: deadsnakes/action@v2.0.0
if: matrix.python == '3.9-dev'
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
@@ -175,18 +160,22 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: set PY
run: echo "::set-env name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')"
- uses: actions/cache@v1
run: echo "name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV
- uses: actions/cache@v2
with:
path: ~/.cache/pre-commit
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
- run: pip install tox
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
- run: tox -e linting
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [build]

1
.gitignore vendored
View File

@@ -34,6 +34,7 @@ issue/
env/
.env/
.venv/
/pythonenv*/
3rdparty/
.tox
.cache

View File

@@ -5,12 +5,12 @@ repos:
- id: black
args: [--safe, --quiet]
- repo: https://github.com/asottile/blacken-docs
rev: v1.7.0
rev: v1.8.0
hooks:
- id: blacken-docs
additional_dependencies: [black==19.10b0]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -21,7 +21,7 @@ repos:
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.2
rev: 3.8.3
hooks:
- id: flake8
language_version: python3
@@ -29,27 +29,36 @@ repos:
- flake8-typing-imports==1.9.0
- flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.3.0
rev: v2.3.5
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py3-plus]
args: ['--application-directories=.:src', --py36-plus]
- repo: https://github.com/asottile/pyupgrade
rev: v2.4.4
rev: v2.7.2
hooks:
- id: pyupgrade
args: [--py3-plus]
args: [--py36-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.9.0
rev: v1.11.0
hooks:
- id: setup-cfg-fmt
# TODO: when upgrading setup-cfg-fmt this can be removed
args: [--max-py-version=3.9]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.6.0
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.780 # NOTE: keep this in sync with setup.cfg.
rev: v0.790
hooks:
- id: mypy
files: ^(src/|testing/)
args: []
additional_dependencies:
- iniconfig>=1.1.0
- py>=1.8.2
- attrs>=19.2.0
- packaging
- repo: local
hooks:
- id: rst

View File

@@ -1,60 +0,0 @@
language: python
dist: trusty
python: '3.5.1'
cache: false
env:
global:
- PYTEST_ADDOPTS=-vv
# setuptools-scm needs all tags in order to obtain a proper version
git:
depth: false
install:
- python -m pip install --upgrade --pre tox
jobs:
include:
# Coverage for Python 3.5.{0,1} specific code, mostly typing related.
- env: TOXENV=py35 PYTEST_COVERAGE=1 PYTEST_ADDOPTS="-k test_raises_cyclic_reference"
before_install:
# Work around https://github.com/jaraco/zipp/issues/40.
- python -m pip install -U 'setuptools>=34.4.0' virtualenv==16.7.9
before_script:
- |
# Do not (re-)upload coverage with cron runs.
if [[ "$TRAVIS_EVENT_TYPE" = cron ]]; then
PYTEST_COVERAGE=0
fi
- |
if [[ "$PYTEST_COVERAGE" = 1 ]]; then
export COVERAGE_FILE="$PWD/.coverage"
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
export _PYTEST_TOX_COVERAGE_RUN="coverage run -m"
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
fi
script: tox
after_success:
- |
if [[ "$PYTEST_COVERAGE" = 1 ]]; then
env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh -F Travis
fi
notifications:
irc:
channels:
- "chat.freenode.net#pytest"
on_success: change
on_failure: change
skip_join: true
email:
- pytest-commit@python.org
branches:
only:
- master
- /^\d+\.\d+\.x$/

16
AUTHORS
View File

@@ -21,6 +21,7 @@ Anders Hovmöller
Andras Mitzki
Andras Tim
Andrea Cimatoribus
Andreas Motl
Andreas Zeidler
Andrey Paramonov
Andrzej Klajnert
@@ -32,6 +33,7 @@ Anthony Sottile
Anton Lodder
Antony Lee
Arel Cordero
Ariel Pillemer
Armin Rigo
Aron Coyle
Aron Curzon
@@ -55,11 +57,13 @@ Charles Cloud
Charles Machalow
Charnjit SiNGH (CCSJ)
Chris Lamb
Chris NeJame
Christian Boelsen
Christian Fetzer
Christian Neumüller
Christian Theunert
Christian Tismer
Christine Mecklenborg
Christoph Buelter
Christopher Dignam
Christopher Gilling
@@ -87,6 +91,7 @@ Dhiren Serai
Diego Russo
Dmitry Dygalo
Dmitry Pribysh
Dominic Mortlock
Duncan Betts
Edison Gustavo Muenz
Edoardo Batini
@@ -107,6 +112,7 @@ Florian Bruhin
Florian Dahlitz
Floris Bruynooghe
Gabriel Reis
Garvit Shubham
Gene Wood
George Kussumoto
Georgy Dyuldin
@@ -129,6 +135,7 @@ Ilya Konstantinov
Ionuț Turturică
Iwan Briquemont
Jaap Broekhuizen
Jakob van Santen
Jakub Mitoraj
Jan Balster
Janne Vanhala
@@ -153,6 +160,7 @@ Justyna Janczyszyn
Kale Kundert
Kamran Ahmad
Karl O. Pinc
Karthikeyan Singaravelan
Katarzyna Jachim
Katarzyna Król
Katerina Koukiou
@@ -195,6 +203,7 @@ Matthias Hafner
Maxim Filipenko
Maximilian Cosmo Sitter
mbyt
Mickey Pashov
Michael Aquilina
Michael Birtwell
Michael Droettboom
@@ -227,11 +236,14 @@ Pauli Virtanen
Pavel Karateev
Paweł Adamczak
Pedro Algarvio
Petter Strandmark
Philipp Loose
Pieter Mulder
Piotr Banaszkiewicz
Piotr Helm
Prakhar Gurunani
Prashant Anand
Prashant Sharma
Pulkit Goyal
Punyashloka Biswal
Quentin Pradet
@@ -256,10 +268,12 @@ Ryan Wooden
Samuel Dion-Girardeau
Samuel Searles-Bryant
Samuele Pedroni
Sanket Duthade
Sankt Petersbug
Segev Finer
Serhii Mozghovyi
Seth Junot
Shubham Adep
Simon Gomizelj
Simon Kerr
Skylar Downes
@@ -274,6 +288,7 @@ Sven-Hendrik Haase
Sylvain Marié
Tadek Teleżyński
Takafumi Arakaki
Tanvi Mehta
Tarcisio Fischer
Tareq Alayan
Ted Xiao
@@ -310,3 +325,4 @@ Xuecong Liao
Yoav Caspi
Zac Hatfield-Dodds
Zoltán Máté
Zsolt Cserna

View File

@@ -299,12 +299,6 @@ Here is a simple overview, with pytest-specific bits:
$ pytest testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s)::
$ git commit -a -m "<commit message>"
$ git push -u
#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>.rst``,
where *issueid* is the number of the issue related to the change and *type* is one of
``feature``, ``improvement``, ``bugfix``, ``doc``, ``deprecation``, ``breaking``, ``vendor``
@@ -313,6 +307,11 @@ Here is a simple overview, with pytest-specific bits:
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
#. Commit and push once your tests pass and you are happy with your change(s)::
$ git commit -a -m "<commit message>"
$ git push -u
#. Finally, submit a pull request through the GitHub website using this data::
head-fork: YOUR_GITHUB_USERNAME/pytest

View File

@@ -89,7 +89,7 @@ Features
- Can run `unittest <https://docs.pytest.org/en/stable/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/stable/nose.html>`_ test suites out of the box
- Python 3.5+ and PyPy3
- Python 3.6+ and PyPy3
- Rich plugin architecture, with over 850+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community

View File

@@ -122,6 +122,14 @@ Both automatic and manual processes described above follow the same steps from t
#. 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 master with
a dev tag for the next feature release::
git checkout master
git pull
git tag MAJOR.{MINOR+1}.0.dev0
git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0
#. Send an email announcement with the contents from::
doc/en/announce/release-<VERSION>.rst

13
bench/unit_test.py Normal file
View File

@@ -0,0 +1,13 @@
from unittest import TestCase # noqa: F401
for i in range(15000):
exec(
f"""
class Test{i}(TestCase):
@classmethod
def setUpClass(cls): pass
def test_1(self): pass
def test_2(self): pass
def test_3(self): pass
"""
)

11
bench/xunit.py Normal file
View File

@@ -0,0 +1,11 @@
for i in range(5000):
exec(
f"""
class Test{i}:
@classmethod
def setup_class(cls): pass
def test_1(self): pass
def test_2(self): pass
def test_3(self): pass
"""
)

View File

@@ -1 +0,0 @@
Removed faq.rst and its reference in contents.rst.

View File

@@ -1,20 +0,0 @@
Fixed error when overwriting a parametrized fixture, while also reusing the super fixture value.
.. code-block:: python
# conftest.py
import pytest
@pytest.fixture(params=[1, 2])
def foo(request):
return request.param
# test_foo.py
import pytest
@pytest.fixture
def foo(foo):
return foo * 2

View File

@@ -1,3 +0,0 @@
Fixed an internal error crash with ``IndexError: list index out of range`` when
collecting a module which starts with a decorated function, the decorator
raises, and assertion rewriting is enabled.

View File

@@ -1,18 +0,0 @@
As per our policy, the following features which have been deprecated in the 5.X series are now
removed:
* The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute.
* ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead.
* Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead.
* The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file.
* The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly.
* The ``--result-log`` option has been removed. Users are recommended to use the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead.
For more information consult
`Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ in the docs.

View File

@@ -1,3 +0,0 @@
Internal pytest warnings issued during the early stages of initialization are now properly handled and can filtered through :confval:`filterwarnings` or ``--pythonwarnings/-W``.
This also fixes a number of long standing issues: `#2891 <https://github.com/pytest-dev/pytest/issues/2891>`__, `#7620 <https://github.com/pytest-dev/pytest/issues/7620>`__, `#7426 <https://github.com/pytest-dev/pytest/issues/7426>`__.

View File

@@ -1 +0,0 @@
The ``pytest.collect`` module is deprecated: all its names can be imported from ``pytest`` directly.

View File

@@ -1,6 +0,0 @@
The ``pytest._fillfuncargs`` function is deprecated. This function was kept
for backward compatibility with an older plugin.
It's functionality is not meant to be used directly, but if you must replace
it, use `function._request._fillfixtures()` instead, though note this is not
a public API and may break in the future.

View File

@@ -1,5 +0,0 @@
The special ``-k '-expr'`` syntax to ``-k`` is deprecated. Use ``-k 'not expr'``
instead.
The special ``-k 'expr:'`` syntax to ``-k`` is deprecated. Please open an issue
if you use this and want a replacement.

View File

@@ -1,2 +0,0 @@
The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor
of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version.

View File

@@ -1,3 +0,0 @@
The internal ``junitxml`` plugin has rewritten to use ``xml.etree.ElementTree``.
The order of attributes in XML elements might differ. Some unneeded escaping is
no longer performed.

View File

@@ -1 +0,0 @@
When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace.

View File

@@ -1 +0,0 @@
The dependency on the ``more-itertools`` package has been removed.

View File

@@ -1 +0,0 @@
pylint shouldn't complain anymore about unimplemented abstract methods when inheriting from :ref:`File <non-python tests>`.

View File

@@ -1 +0,0 @@
Fixed test collection when a full path without a drive letter was passed to pytest on Windows (for example ``\projects\tests\test.py`` instead of ``c:\projects\tests\pytest.py``).

View File

@@ -1,2 +0,0 @@
The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
but should behave like one in all respects. This was done for technical reasons.

View File

@@ -1 +0,0 @@
Fix handling of command-line options that appear as paths but trigger an OS-level syntax error on Windows, such as the options used internally by ``pytest-xdist``.

View File

@@ -1,3 +0,0 @@
The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated;
use ``self.session.gethookproxy()`` and ``self.session.isinitpath()`` instead.
This should work on all pytest versions.

View File

@@ -1 +0,0 @@
New ``--durations-min`` command-line flag controls the minimal duration for inclusion in the slowest list of tests shown by ``--durations``. Previously this was hard-coded to ``0.005s``.

View File

@@ -1,6 +0,0 @@
When collecting tests, pytest finds test classes and functions by examining the
attributes of python objects (modules, classes and instances). To speed up this
process, pytest now ignores builtin attributes (like ``__class__``,
``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and
:confval:`python_functions` configuration options and without passing them to plugins
using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook.

View File

@@ -1,3 +0,0 @@
Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`.
These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes,
and should be preferred over them when possible.

View File

@@ -1 +0,0 @@
Fixed INTERNALERROR when accessing locals / globals with faulty ``exec``.

View File

@@ -1,3 +0,0 @@
Public classes which are not designed to be inherited from are now marked `@final <https://docs.python.org/3/library/typing.html#typing.final>`_.
Code which inherits from these classes will trigger a type-checking (e.g. mypy) error, but will still work in runtime.
Currently the ``final`` designation does not appear in the API Reference but hopefully will in the future.

View File

@@ -6,6 +6,13 @@ Release announcements
:maxdepth: 2
release-6.2.3
release-6.2.2
release-6.2.1
release-6.2.0
release-6.1.2
release-6.1.1
release-6.1.0
release-6.0.2
release-6.0.1
release-6.0.0

View File

@@ -0,0 +1,44 @@
pytest-6.1.0
=======================================
The pytest team is proud to announce the 6.1.0 release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Anthony Sottile
* Bruno Oliveira
* C. Titus Brown
* Drew Devereux
* Faris A Chugthai
* Florian Bruhin
* Hugo van Kemenade
* Hynek Schlawack
* Joseph Lucas
* Kamran Ahmad
* Mattreex
* Maximilian Cosmo Sitter
* Ran Benita
* Rüdiger Busche
* Sam Estep
* Sorin Sbarnea
* Thomas Grainger
* Vipul Kumar
* Yutaro Ikeda
* hp310780
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,18 @@
pytest-6.1.1
=======================================
pytest 6.1.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,22 @@
pytest-6.1.2
=======================================
pytest 6.1.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Bruno Oliveira
* Manuel Mariñez
* Ran Benita
* Vasilis Gerakaris
* William Jamir Silva
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,76 @@
pytest-6.2.0
=======================================
The pytest team is proud to announce the 6.2.0 release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Adam Johnson
* Albert Villanova del Moral
* Anthony Sottile
* Anton
* Ariel Pillemer
* Bruno Oliveira
* Charles Aracil
* Christine M
* Christine Mecklenborg
* Cserna Zsolt
* Dominic Mortlock
* Emiel van de Laar
* Florian Bruhin
* Garvit Shubham
* Gustavo Camargo
* Hugo Martins
* Hugo van Kemenade
* Jakob van Santen
* Josias Aurel
* Jürgen Gmach
* Karthikeyan Singaravelan
* Katarzyna
* Kyle Altendorf
* Manuel Mariñez
* Matthew Hughes
* Matthias Gabriel
* Max Voitko
* Maximilian Cosmo Sitter
* Mikhail Fesenko
* Nimesh Vashistha
* Pedro Algarvio
* Petter Strandmark
* Prakhar Gurunani
* Prashant Sharma
* Ran Benita
* Ronny Pfannschmidt
* Sanket Duthade
* Shubham Adep
* Simon K
* Tanvi Mehta
* Thomas Grainger
* Tim Hoffmann
* Vasilis Gerakaris
* William Jamir Silva
* Zac Hatfield-Dodds
* crricks
* dependabot[bot]
* duthades
* frankgerhardt
* kwgchi
* mickeypash
* symonk
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,20 @@
pytest-6.2.1
=======================================
pytest 6.2.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Bruno Oliveira
* Jakob van Santen
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,21 @@
pytest-6.2.2
=======================================
pytest 6.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Adam Johnson
* Bruno Oliveira
* Chris NeJame
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,19 @@
pytest-6.2.3
=======================================
pytest 6.2.3 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -74,7 +74,7 @@ Assertions about expected exceptions
------------------------------------------
In order to write assertions about raised exceptions, you can use
``pytest.raises`` as a context manager like this:
:func:`pytest.raises` as a context manager like this:
.. code-block:: python
@@ -123,7 +123,7 @@ The regexp parameter of the ``match`` method is matched with the ``re.search``
function, so in the above example ``match='123'`` would have worked as
well.
There's an alternate form of the ``pytest.raises`` function where you pass
There's an alternate form of the :func:`pytest.raises` function where you pass
a function that will be executed with the given ``*args`` and ``**kwargs`` and
assert that the given exception is raised:
@@ -144,8 +144,8 @@ specific way than just having any exception raised:
def test_f():
f()
Using ``pytest.raises`` is likely to be better for cases where you are testing
exceptions your own code is deliberately raising, whereas using
Using :func:`pytest.raises` is likely to be better for cases where you are
testing exceptions your own code is deliberately raising, whereas using
``@pytest.mark.xfail`` with a check function is probably better for something
like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.

View File

@@ -10,7 +10,7 @@ we keep learning about new and better structures to express different details ab
While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
As of now, pytest considers multipe types of backward compatibility transitions:
As of now, pytest considers multiple types of backward compatibility transitions:
a) trivial: APIs which trivially translate to the new mechanism,
and do not cause problematic changes.
@@ -25,7 +25,7 @@ b) transitional: the old and new API don't conflict
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
c) true breakage: should only to be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
c) true breakage: should only be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance.
Examples for such upcoming changes:
@@ -42,7 +42,7 @@ c) true breakage: should only to be considered when normal transition is unreaso
After there's no hard *-1* on the issue it should be followed up by an initial proof-of-concept Pull Request.
This POC serves as both a coordination point to assess impact and potential inspriation to come up with a transitional solution after all.
This POC serves as both a coordination point to assess impact and potential inspiration to come up with a transitional solution after all.
After a reasonable amount of time the PR can be merged to base a new major release.

View File

@@ -23,7 +23,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
cache.get(key, default)
cache.set(key, value)
Keys must be a ``/`` separated value, where the first part is usually the
Keys must be ``/`` separated strings, where the first part is usually the
name of your plugin or application to avoid clashes with other cache users.
Values can be any object handled by the json stdlib module.
@@ -57,7 +57,8 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
``out`` and ``err`` will be ``byte`` objects.
doctest_namespace [session scope]
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
Fixture that returns a :py:class:`dict` that will be injected into the
namespace of doctests.
pytestconfig [session scope]
Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
@@ -89,8 +90,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
automatically XML-encoded.
record_testsuite_property [session scope]
Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to
writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family.
Record a new ``<property>`` tag as child of the root ``<testsuite>``.
This is suitable to writing global information regarding the entire test
suite, and is compatible with ``xunit2`` JUnit family.
This is a ``session``-scoped fixture which is called with ``(name, value)``. Example:
@@ -102,6 +105,12 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
.. warning::
Currently this fixture **does not work** with the
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See issue
`#7767 <https://github.com/pytest-dev/pytest/issues/7767>`__ for details.
caplog
Access and control log capturing.
@@ -114,8 +123,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
* caplog.clear() -> clear captured records and formatted log output string
monkeypatch
The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries or
os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
@@ -126,10 +137,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting
test function or fixture has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
All modifications will be undone after the requesting test function or
fixture has finished. The ``raising`` parameter determines if a KeyError
or AttributeError will be raised if the set/deletion operation has no target.
recwarn
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
@@ -140,30 +150,34 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
tmpdir_factory [session scope]
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
tmp_path_factory [session scope]
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
tmpdir
Return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
By default, a new base temporary directory is created each test session,
and old bases are removed after 3 sessions, to aid in debugging. If
``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
The returned object is a `py.path.local`_ path object.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
tmp_path
Return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a :class:`pathlib.Path`
object.
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
.. note::
By default, a new base temporary directory is created each test session,
and old bases are removed after 3 sessions, to aid in debugging. If
``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
in python < 3.6 this is a pathlib2.Path
The returned object is a :class:`pathlib.Path` object.
no tests ran in 0.12s

View File

@@ -28,6 +28,446 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 6.2.3 (2021-04-03)
=========================
Bug Fixes
---------
- `#8414 <https://github.com/pytest-dev/pytest/issues/8414>`_: pytest used to create directories under ``/tmp`` with world-readable
permissions. This means that any user in the system was able to read
information written by tests in temporary directories (such as those created by
the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with
private permissions.
pytest used silenty use a pre-existing ``/tmp/pytest-of-<username>`` directory,
even if owned by another user. This means another user could pre-create such a
directory and gain control of another user's temporary directory. Now such a
condition results in an error.
pytest 6.2.2 (2021-01-25)
=========================
Bug Fixes
---------
- `#8152 <https://github.com/pytest-dev/pytest/issues/8152>`_: Fixed "(<Skipped instance>)" being shown as a skip reason in the verbose test summary line when the reason is empty.
- `#8249 <https://github.com/pytest-dev/pytest/issues/8249>`_: Fix the ``faulthandler`` plugin for occasions when running with ``twisted.logger`` and using ``pytest --capture=no``.
pytest 6.2.1 (2020-12-15)
=========================
Bug Fixes
---------
- `#7678 <https://github.com/pytest-dev/pytest/issues/7678>`_: Fixed bug where ``ImportPathMismatchError`` would be raised for files compiled in
the host and loaded later from an UNC mounted path (Windows).
- `#8132 <https://github.com/pytest-dev/pytest/issues/8132>`_: Fixed regression in ``approx``: in 6.2.0 ``approx`` no longer raises
``TypeError`` when dealing with non-numeric types, falling back to normal comparison.
Before 6.2.0, array types like tf.DeviceArray fell through to the scalar case,
and happened to compare correctly to a scalar if they had only one element.
After 6.2.0, these types began failing, because they inherited neither from
standard Python number hierarchy nor from ``numpy.ndarray``.
``approx`` now converts arguments to ``numpy.ndarray`` if they expose the array
protocol and are not scalars. This treats array-like objects like numpy arrays,
regardless of size.
pytest 6.2.0 (2020-12-12)
=========================
Breaking Changes
----------------
- `#7808 <https://github.com/pytest-dev/pytest/issues/7808>`_: pytest now supports python3.6+ only.
Deprecations
------------
- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_: Directly constructing/calling the following classes/functions is now deprecated:
- ``_pytest.cacheprovider.Cache``
- ``_pytest.cacheprovider.Cache.for_config()``
- ``_pytest.cacheprovider.Cache.clear_cache()``
- ``_pytest.cacheprovider.Cache.cache_dir_from_config()``
- ``_pytest.capture.CaptureFixture``
- ``_pytest.fixtures.FixtureRequest``
- ``_pytest.fixtures.SubRequest``
- ``_pytest.logging.LogCaptureFixture``
- ``_pytest.pytester.Pytester``
- ``_pytest.pytester.Testdir``
- ``_pytest.recwarn.WarningsRecorder``
- ``_pytest.recwarn.WarningsChecker``
- ``_pytest.tmpdir.TempPathFactory``
- ``_pytest.tmpdir.TempdirFactory``
These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.
- `#7530 <https://github.com/pytest-dev/pytest/issues/7530>`_: The ``--strict`` command-line option has been deprecated, use ``--strict-markers`` instead.
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing flag for all strictness
related options (``--strict-markers`` and ``--strict-config`` at the moment, more might be introduced in the future).
- `#7988 <https://github.com/pytest-dev/pytest/issues/7988>`_: The ``@pytest.yield_fixture`` decorator/function is now deprecated. Use :func:`pytest.fixture` instead.
``yield_fixture`` has been an alias for ``fixture`` for a very long time, so can be search/replaced safely.
Features
--------
- `#5299 <https://github.com/pytest-dev/pytest/issues/5299>`_: pytest now warns about unraisable exceptions and unhandled thread exceptions that occur in tests on Python>=3.8.
See :ref:`unraisable` for more information.
- `#7425 <https://github.com/pytest-dev/pytest/issues/7425>`_: New :fixture:`pytester` fixture, which is identical to :fixture:`testdir` but its methods return :class:`pathlib.Path` when appropriate instead of ``py.path.local``.
This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future.
Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface.
- `#7695 <https://github.com/pytest-dev/pytest/issues/7695>`_: A new hook was added, `pytest_markeval_namespace` which should return a dictionary.
This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers.
Pseudo example
``conftest.py``:
.. code-block:: python
def pytest_markeval_namespace():
return {"color": "red"}
``test_func.py``:
.. code-block:: python
@pytest.mark.skipif("color == 'blue'", reason="Color is not red")
def test_func():
assert False
- `#8006 <https://github.com/pytest-dev/pytest/issues/8006>`_: It is now possible to construct a :class:`~pytest.MonkeyPatch` object directly as ``pytest.MonkeyPatch()``,
in cases when the :fixture:`monkeypatch` fixture cannot be used. Previously some users imported it
from the private `_pytest.monkeypatch.MonkeyPatch` namespace.
Additionally, :meth:`MonkeyPatch.context <pytest.MonkeyPatch.context>` is now a classmethod,
and can be used as ``with MonkeyPatch.context() as mp: ...``. This is the recommended way to use
``MonkeyPatch`` directly, since unlike the ``monkeypatch`` fixture, an instance created directly
is not ``undo()``-ed automatically.
Improvements
------------
- `#1265 <https://github.com/pytest-dev/pytest/issues/1265>`_: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method.
- `#2044 <https://github.com/pytest-dev/pytest/issues/2044>`_: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS".
- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_ The types of builtin pytest fixtures are now exported so they may be used in type annotations of test functions.
The newly-exported types are:
- ``pytest.FixtureRequest`` for the :fixture:`request` fixture.
- ``pytest.Cache`` for the :fixture:`cache` fixture.
- ``pytest.CaptureFixture[str]`` for the :fixture:`capfd` and :fixture:`capsys` fixtures.
- ``pytest.CaptureFixture[bytes]`` for the :fixture:`capfdbinary` and :fixture:`capsysbinary` fixtures.
- ``pytest.LogCaptureFixture`` for the :fixture:`caplog` fixture.
- ``pytest.Pytester`` for the :fixture:`pytester` fixture.
- ``pytest.Testdir`` for the :fixture:`testdir` fixture.
- ``pytest.TempdirFactory`` for the :fixture:`tmpdir_factory` fixture.
- ``pytest.TempPathFactory`` for the :fixture:`tmp_path_factory` fixture.
- ``pytest.MonkeyPatch`` for the :fixture:`monkeypatch` fixture.
- ``pytest.WarningsRecorder`` for the :fixture:`recwarn` fixture.
Constructing them is not supported (except for `MonkeyPatch`); they are only meant for use in type annotations.
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.
Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy.
- `#7527 <https://github.com/pytest-dev/pytest/issues/7527>`_: When a comparison between :func:`namedtuple <collections.namedtuple>` instances of the same type fails, pytest now shows the differing field names (possibly nested) instead of their indexes.
- `#7615 <https://github.com/pytest-dev/pytest/issues/7615>`_: :meth:`Node.warn <_pytest.nodes.Node.warn>` now permits any subclass of :class:`Warning`, not just :class:`PytestWarning <pytest.PytestWarning>`.
- `#7701 <https://github.com/pytest-dev/pytest/issues/7701>`_: Improved reporting when using ``--collected-only``. It will now show the number of collected tests in the summary stats.
- `#7710 <https://github.com/pytest-dev/pytest/issues/7710>`_: Use strict equality comparison for non-numeric types in :func:`pytest.approx` instead of
raising :class:`TypeError`.
This was the undocumented behavior before 3.7, but is now officially a supported feature.
- `#7938 <https://github.com/pytest-dev/pytest/issues/7938>`_: New ``--sw-skip`` argument which is a shorthand for ``--stepwise-skip``.
- `#8023 <https://github.com/pytest-dev/pytest/issues/8023>`_: Added ``'node_modules'`` to default value for :confval:`norecursedirs`.
- `#8032 <https://github.com/pytest-dev/pytest/issues/8032>`_: :meth:`doClassCleanups <unittest.TestCase.doClassCleanups>` (introduced in :mod:`unittest` in Python and 3.8) is now called appropriately.
Bug Fixes
---------
- `#4824 <https://github.com/pytest-dev/pytest/issues/4824>`_: Fixed quadratic behavior and improved performance of collection of items using autouse fixtures and xunit fixtures.
- `#7758 <https://github.com/pytest-dev/pytest/issues/7758>`_: Fixed an issue where some files in packages are getting lost from ``--lf`` even though they contain tests that failed. Regressed in pytest 5.4.0.
- `#7911 <https://github.com/pytest-dev/pytest/issues/7911>`_: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites.
- `#7913 <https://github.com/pytest-dev/pytest/issues/7913>`_: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved.
- `#7951 <https://github.com/pytest-dev/pytest/issues/7951>`_: Fixed handling of recursive symlinks when collecting tests.
- `#7981 <https://github.com/pytest-dev/pytest/issues/7981>`_: Fixed symlinked directories not being followed during collection. Regressed in pytest 6.1.0.
- `#8016 <https://github.com/pytest-dev/pytest/issues/8016>`_: Fixed only one doctest being collected when using ``pytest --doctest-modules path/to/an/__init__.py``.
Improved Documentation
----------------------
- `#7429 <https://github.com/pytest-dev/pytest/issues/7429>`_: Add more information and use cases about skipping doctests.
- `#7780 <https://github.com/pytest-dev/pytest/issues/7780>`_: Classes which should not be inherited from are now marked ``final class`` in the API reference.
- `#7872 <https://github.com/pytest-dev/pytest/issues/7872>`_: ``_pytest.config.argparsing.Parser.addini()`` accepts explicit ``None`` and ``"string"``.
- `#7878 <https://github.com/pytest-dev/pytest/issues/7878>`_: In pull request section, ask to commit after editing changelog and authors file.
Trivial/Internal Changes
------------------------
- `#7802 <https://github.com/pytest-dev/pytest/issues/7802>`_: The ``attrs`` dependency requirement is now >=19.2.0 instead of >=17.4.0.
- `#8014 <https://github.com/pytest-dev/pytest/issues/8014>`_: `.pyc` files created by pytest's assertion rewriting now conform to the newer PEP-552 format on Python>=3.7.
(These files are internal and only interpreted by pytest itself.)
pytest 6.1.2 (2020-10-28)
=========================
Bug Fixes
---------
- `#7758 <https://github.com/pytest-dev/pytest/issues/7758>`_: Fixed an issue where some files in packages are getting lost from ``--lf`` even though they contain tests that failed. Regressed in pytest 5.4.0.
- `#7911 <https://github.com/pytest-dev/pytest/issues/7911>`_: Directories created by `tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites.
Improved Documentation
----------------------
- `#7815 <https://github.com/pytest-dev/pytest/issues/7815>`_: Improve deprecation warning message for ``pytest._fillfuncargs()``.
pytest 6.1.1 (2020-10-03)
=========================
Bug Fixes
---------
- `#7807 <https://github.com/pytest-dev/pytest/issues/7807>`_: Fixed regression in pytest 6.1.0 causing incorrect rootdir to be determined in some non-trivial cases where parent directories have config files as well.
- `#7814 <https://github.com/pytest-dev/pytest/issues/7814>`_: Fixed crash in header reporting when :confval:`testpaths` is used and contains absolute paths (regression in 6.1.0).
pytest 6.1.0 (2020-09-26)
=========================
Breaking Changes
----------------
- `#5585 <https://github.com/pytest-dev/pytest/issues/5585>`_: As per our policy, the following features which have been deprecated in the 5.X series are now
removed:
* The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute.
* ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead.
* Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead.
* The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file.
* The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly.
* The ``--result-log`` option has been removed. Users are recommended to use the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead.
For more information consult
`Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ in the docs.
Deprecations
------------
- `#6981 <https://github.com/pytest-dev/pytest/issues/6981>`_: The ``pytest.collect`` module is deprecated: all its names can be imported from ``pytest`` directly.
- `#7097 <https://github.com/pytest-dev/pytest/issues/7097>`_: The ``pytest._fillfuncargs`` function is deprecated. This function was kept
for backward compatibility with an older plugin.
It's functionality is not meant to be used directly, but if you must replace
it, use `function._request._fillfixtures()` instead, though note this is not
a public API and may break in the future.
- `#7210 <https://github.com/pytest-dev/pytest/issues/7210>`_: The special ``-k '-expr'`` syntax to ``-k`` is deprecated. Use ``-k 'not expr'``
instead.
The special ``-k 'expr:'`` syntax to ``-k`` is deprecated. Please open an issue
if you use this and want a replacement.
- `#7255 <https://github.com/pytest-dev/pytest/issues/7255>`_: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor
of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version.
- `#7648 <https://github.com/pytest-dev/pytest/issues/7648>`_: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated;
use ``self.session.gethookproxy()`` and ``self.session.isinitpath()`` instead.
This should work on all pytest versions.
Features
--------
- `#7667 <https://github.com/pytest-dev/pytest/issues/7667>`_: New ``--durations-min`` command-line flag controls the minimal duration for inclusion in the slowest list of tests shown by ``--durations``. Previously this was hard-coded to ``0.005s``.
Improvements
------------
- `#6681 <https://github.com/pytest-dev/pytest/issues/6681>`_: Internal pytest warnings issued during the early stages of initialization are now properly handled and can filtered through :confval:`filterwarnings` or ``--pythonwarnings/-W``.
This also fixes a number of long standing issues: `#2891 <https://github.com/pytest-dev/pytest/issues/2891>`__, `#7620 <https://github.com/pytest-dev/pytest/issues/7620>`__, `#7426 <https://github.com/pytest-dev/pytest/issues/7426>`__.
- `#7572 <https://github.com/pytest-dev/pytest/issues/7572>`_: When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace.
- `#7685 <https://github.com/pytest-dev/pytest/issues/7685>`_: Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`.
These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes,
and should be preferred over them when possible.
- `#7780 <https://github.com/pytest-dev/pytest/issues/7780>`_: Public classes which are not designed to be inherited from are now marked `@final <https://docs.python.org/3/library/typing.html#typing.final>`_.
Code which inherits from these classes will trigger a type-checking (e.g. mypy) error, but will still work in runtime.
Currently the ``final`` designation does not appear in the API Reference but hopefully will in the future.
Bug Fixes
---------
- `#1953 <https://github.com/pytest-dev/pytest/issues/1953>`_: Fixed error when overwriting a parametrized fixture, while also reusing the super fixture value.
.. code-block:: python
# conftest.py
import pytest
@pytest.fixture(params=[1, 2])
def foo(request):
return request.param
# test_foo.py
import pytest
@pytest.fixture
def foo(foo):
return foo * 2
- `#4984 <https://github.com/pytest-dev/pytest/issues/4984>`_: Fixed an internal error crash with ``IndexError: list index out of range`` when
collecting a module which starts with a decorated function, the decorator
raises, and assertion rewriting is enabled.
- `#7591 <https://github.com/pytest-dev/pytest/issues/7591>`_: pylint shouldn't complain anymore about unimplemented abstract methods when inheriting from :ref:`File <non-python tests>`.
- `#7628 <https://github.com/pytest-dev/pytest/issues/7628>`_: Fixed test collection when a full path without a drive letter was passed to pytest on Windows (for example ``\projects\tests\test.py`` instead of ``c:\projects\tests\pytest.py``).
- `#7638 <https://github.com/pytest-dev/pytest/issues/7638>`_: Fix handling of command-line options that appear as paths but trigger an OS-level syntax error on Windows, such as the options used internally by ``pytest-xdist``.
- `#7742 <https://github.com/pytest-dev/pytest/issues/7742>`_: Fixed INTERNALERROR when accessing locals / globals with faulty ``exec``.
Improved Documentation
----------------------
- `#1477 <https://github.com/pytest-dev/pytest/issues/1477>`_: Removed faq.rst and its reference in contents.rst.
Trivial/Internal Changes
------------------------
- `#7536 <https://github.com/pytest-dev/pytest/issues/7536>`_: The internal ``junitxml`` plugin has rewritten to use ``xml.etree.ElementTree``.
The order of attributes in XML elements might differ. Some unneeded escaping is
no longer performed.
- `#7587 <https://github.com/pytest-dev/pytest/issues/7587>`_: The dependency on the ``more-itertools`` package has been removed.
- `#7631 <https://github.com/pytest-dev/pytest/issues/7631>`_: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
but should behave like one in all respects. This was done for technical reasons.
- `#7671 <https://github.com/pytest-dev/pytest/issues/7671>`_: When collecting tests, pytest finds test classes and functions by examining the
attributes of python objects (modules, classes and instances). To speed up this
process, pytest now ignores builtin attributes (like ``__class__``,
``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and
:confval:`python_functions` configuration options and without passing them to plugins
using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook.
pytest 6.0.2 (2020-09-04)
=========================
@@ -1682,6 +2122,44 @@ Improved Documentation
- `#5416 <https://github.com/pytest-dev/pytest/issues/5416>`_: Fix PytestUnknownMarkWarning in run/skip example.
pytest 4.6.11 (2020-06-04)
==========================
Bug Fixes
---------
- `#6334 <https://github.com/pytest-dev/pytest/issues/6334>`_: Fix summary entries appearing twice when ``f/F`` and ``s/S`` report chars were used at the same time in the ``-r`` command-line option (for example ``-rFf``).
The upper case variants were never documented and the preferred form should be the lower case.
- `#7310 <https://github.com/pytest-dev/pytest/issues/7310>`_: Fix ``UnboundLocalError: local variable 'letter' referenced before
assignment`` in ``_pytest.terminal.pytest_report_teststatus()``
when plugins return report objects in an unconventional state.
This was making ``pytest_report_teststatus()`` skip
entering if-block branches that declare the ``letter`` variable.
The fix was to set the initial value of the ``letter`` before
the if-block cascade so that it always has a value.
pytest 4.6.10 (2020-05-08)
==========================
Features
--------
- `#6870 <https://github.com/pytest-dev/pytest/issues/6870>`_: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
Remark: while this is technically a new feature and according to our `policy <https://docs.pytest.org/en/latest/py27-py34-deprecation.html#what-goes-into-4-6-x-releases>`_ it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix.
Trivial/Internal Changes
------------------------
- `#6404 <https://github.com/pytest-dev/pytest/issues/6404>`_: Remove usage of ``parser`` module, deprecated in Python 3.9.
pytest 4.6.9 (2020-01-04)
=========================

View File

@@ -15,11 +15,13 @@
#
# The full version, including alpha/beta/rc tags.
# The short X.Y version.
import ast
import os
import sys
from typing import List
from typing import TYPE_CHECKING
from _pytest import __version__ as version
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
import sphinx.application
@@ -398,3 +400,22 @@ def setup(app: "sphinx.application.Sphinx") -> None:
)
configure_logging(app)
# Make Sphinx mark classes with "final" when decorated with @final.
# We need this because we import final from pytest._compat, not from
# typing (for Python < 3.8 compat), so Sphinx doesn't detect it.
# To keep things simple we accept any `@final` decorator.
# Ref: https://github.com/pytest-dev/pytest/pull/7780
import sphinx.pycode.ast
import sphinx.pycode.parser
original_is_final = sphinx.pycode.parser.VariableCommentPicker.is_final
def patched_is_final(self, decorators: List[ast.expr]) -> bool:
if original_is_final(self, decorators):
return True
return any(
sphinx.pycode.ast.unparse(decorator) == "final" for decorator in decorators
)
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final

View File

@@ -18,6 +18,28 @@ 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>`.
The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.2
The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
better conveys what the option does.
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
at the moment, more might be introduced in the future).
The ``yield_fixture`` function/decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 6.2
``pytest.yield_fixture`` is a deprecated alias for :func:`pytest.fixture`.
It has been so for a very long time, so can be search/replaced safely.
The ``pytest_warning_captured`` hook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -2,7 +2,7 @@
Doctest integration for modules and test files
=========================================================
By default all files matching the ``test*.txt`` pattern will
By default, all files matching the ``test*.txt`` pattern will
be run through the python standard ``doctest`` module. You
can change the pattern by issuing:
@@ -77,15 +77,6 @@ putting them into a pytest.ini file like this:
[pytest]
addopts = --doctest-modules
.. note::
The builtin pytest doctest supports only ``doctest`` blocks, but if you are looking
for more advanced checking over *all* your documentation,
including doctests, ``.. codeblock:: python`` Sphinx directive support,
and any other examples your documentation may include, you may wish to
consider `Sybil <https://sybil.readthedocs.io/en/latest/index.html>`__.
It provides pytest integration out of the box.
Encoding
--------
@@ -113,7 +104,7 @@ lengthy exception stack traces you can just write:
.. code-block:: ini
[pytest]
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
Alternatively, options can be enabled by an inline comment in the doc test
itself:
@@ -206,7 +197,7 @@ It is possible to use fixtures using the ``getfixture`` helper:
>>> ...
>>>
Note that the fixture needs to be defined in a place visible by pytest, for example a `conftest.py`
Note that the fixture needs to be defined in a place visible by pytest, for example, a `conftest.py`
file or plugin; normal python files containing docstrings are not normally scanned for fixtures
unless explicitly configured by :confval:`python_files`.
@@ -253,12 +244,32 @@ Note that like the normal ``conftest.py``, the fixtures are discovered in the di
Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
Fixtures will not be discovered in a sibling directory tree!
Skipping tests dynamically
^^^^^^^^^^^^^^^^^^^^^^^^^^
Skipping tests
^^^^^^^^^^^^^^
.. versionadded:: 4.4
For the same reasons one might want to skip normal tests, it is also possible to skip
tests inside doctests.
To skip a single check inside a doctest you can use the standard
`doctest.SKIP <https://docs.python.org/3/library/doctest.html#doctest.SKIP>`__ directive:
.. code-block:: python
def test_random(y):
"""
>>> random.random() # doctest: +SKIP
0.156231223
>>> 1 + 1
2
"""
This will skip the first check, but not the second.
pytest also allows using the standard pytest functions :func:`pytest.skip` and
:func:`pytest.xfail` inside doctests, which might be useful because you can
then skip/xfail tests based on external conditions:
You can use ``pytest.skip`` to dynamically skip doctests. For example:
.. code-block:: text
@@ -266,3 +277,35 @@ You can use ``pytest.skip`` to dynamically skip doctests. For example:
>>> if sys.platform.startswith('win'):
... pytest.skip('this doctest does not work on Windows')
...
>>> import fcntl
>>> ...
However using those functions is discouraged because it reduces the readability of the
docstring.
.. note::
:func:`pytest.skip` and :func:`pytest.xfail` behave differently depending
if the doctests are in a Python file (in docstrings) or a text file containing
doctests intermingled with text:
* Python modules (docstrings): the functions only act in that specific docstring,
letting the other docstrings in the same module execute as normal.
* Text files: the functions will skip/xfail the checks for the rest of the entire
file.
Alternatives
------------
While the built-in pytest support provides a good set of functionalities for using
doctests, if you use them extensively you might be interested in those external packages
which add many more features, and include pytest integration:
* `pytest-doctestplus <https://github.com/astropy/pytest-doctestplus>`__: provides
advanced doctest support and enables the testing of reStructuredText (".rst") files.
* `Sybil <https://sybil.readthedocs.io>`__: provides a way to test examples in
your documentation by parsing them from the documentation source and evaluating
the parsed examples as part of your normal test run.

View File

@@ -176,7 +176,7 @@ class TestRaises:
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3]
print("items is {!r}".format(items))
print(f"items is {items!r}")
a, b = items.pop()
def test_some_error(self):

View File

@@ -11,4 +11,4 @@ def pytest_runtest_setup(item):
return
mod = item.getparent(pytest.Module).obj
if hasattr(mod, "hello"):
print("mod.hello {!r}".format(mod.hello))
print(f"mod.hello {mod.hello!r}")

View File

@@ -0,0 +1,132 @@
<svg xmlns="http://www.w3.org/2000/svg" width="572" height="542">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class, circle.module, circle.package {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class, text.module, text.package {
fill: #0e84b5;
}
line, path {
stroke: black;
stroke-width: 2;
fill: none;
}
</style>
<!-- main scope -->
<circle class="package" r="270" cx="286" cy="271" />
<!-- scope name -->
<defs>
<path d="M 26,271 A 260 260 0 0 1 546 271" id="testp"/>
</defs>
<text class="package">
<textPath href="#testp" startOffset="50%">tests</textPath>
</text>
<!-- subpackage -->
<circle class="package" r="140" cx="186" cy="271" />
<!-- scope name -->
<defs>
<path d="M 56,271 A 130 130 0 0 1 316 271" id="subpackage"/>
</defs>
<text class="package">
<textPath href="#subpackage" startOffset="50%">subpackage</textPath>
</text>
<!-- test_subpackage.py -->
<circle class="module" r="90" cx="186" cy="311" />
<!-- scope name -->
<defs>
<path d="M 106,311 A 80 80 0 0 1 266 311" id="testSubpackage"/>
</defs>
<text class="module">
<textPath href="#testSubpackage" startOffset="50%">test_subpackage.py</textPath>
</text>
<!-- innermost -->
<line x1="186" x2="186" y1="271" y2="351"/>
<!-- mid -->
<path d="M 186 351 L 136 351 L 106 331 L 106 196" />
<!-- order -->
<path d="M 186 351 L 256 351 L 316 291 L 316 136" />
<!-- top -->
<path d="M 186 351 L 186 391 L 231 436 L 331 436" />
<ellipse class="fixture" rx="50" ry="25" cx="186" cy="271" />
<text x="186" y="271">innermost</text>
<rect class="test" width="110" height="50" x="131" y="326" />
<text x="186" y="351">test_order</text>
<ellipse class="fixture" rx="50" ry="25" cx="126" cy="196" />
<text x="126" y="196">mid</text>
<!-- scope order number -->
<mask id="testSubpackageOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="90" cx="186" cy="311" />
</mask>
<circle class="module" r="15" cx="96" cy="311" mask="url(#testSubpackageOrderMask)"/>
<text class="module" x="96" y="311">1</text>
<!-- scope order number -->
<mask id="subpackageOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="140" cx="186" cy="271" />
</mask>
<circle class="module" r="15" cx="46" cy="271" mask="url(#subpackageOrderMask)"/>
<text class="module" x="46" y="271">2</text>
<!-- scope order number -->
<mask id="testsOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="270" cx="286" cy="271" />
</mask>
<circle class="module" r="15" cx="16" cy="271" mask="url(#testsOrderMask)"/>
<text class="module" x="16" y="271">3</text>
<!-- test_top.py -->
<circle class="module" r="85" cx="441" cy="271" />
<!-- scope name -->
<defs>
<path d="M 366,271 A 75 75 0 0 1 516 271" id="testTop"/>
</defs>
<text class="module">
<textPath href="#testTop" startOffset="50%">test_top.py</textPath>
</text>
<!-- innermost -->
<line x1="441" x2="441" y1="306" y2="236"/>
<!-- order -->
<path d="M 441 306 L 376 306 L 346 276 L 346 136" />
<!-- top -->
<path d="M 441 306 L 441 411 L 411 436 L 331 436" />
<ellipse class="fixture" rx="50" ry="25" cx="441" cy="236" />
<text x="441" y="236">innermost</text>
<rect class="test" width="110" height="50" x="386" y="281" />
<text x="441" y="306">test_order</text>
<!-- scope order number -->
<mask id="testTopOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="85" cx="441" cy="271" />
</mask>
<circle class="module" r="15" cx="526" cy="271" mask="url(#testTopOrderMask)"/>
<text class="module" x="526" y="271">1</text>
<!-- scope order number -->
<circle class="module" r="15" cx="556" cy="271" mask="url(#testsOrderMask)"/>
<text class="module" x="556" y="271">2</text>
<ellipse class="fixture" rx="50" ry="25" cx="331" cy="436" />
<text x="331" y="436">top</text>
<ellipse class="fixture" rx="50" ry="25" cx="331" cy="136" />
<text x="331" y="136">order</text>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,142 @@
<svg xmlns="http://www.w3.org/2000/svg" width="587" height="382">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
alignment-baseline: center;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class, circle.module, circle.package, circle.plugin {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class, text.module, text.package, text.plugin {
fill: #0e84b5;
}
line, path {
stroke: black;
stroke-width: 2;
fill: none;
}
</style>
<!-- plugin_a.py scope -->
<circle class="plugin" r="85" cx="486" cy="86" />
<!-- plugin name -->
<defs>
<path d="M 411,86 A 75 75 0 0 1 561 86" id="pluginA"/>
</defs>
<text class="plugin">
<textPath href="#pluginA" startOffset="50%">plugin_a</textPath>
</text>
<!-- scope order number -->
<mask id="pluginAOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="85" cx="486" cy="86" />
</mask>
<circle class="module" r="15" cx="571" cy="86" mask="url(#pluginAOrderMask)"/>
<text class="module" x="571" y="86">4</text>
<!-- plugin_b.py scope -->
<circle class="plugin" r="85" cx="486" cy="296" />
<!-- plugin name -->
<defs>
<path d="M 411,296 A 75 75 0 0 1 561 296" id="pluginB"/>
</defs>
<text class="plugin">
<textPath href="#pluginB" startOffset="50%">plugin_b</textPath>
</text>
<!-- scope order number -->
<mask id="pluginBOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="85" cx="486" cy="296" />
</mask>
<circle class="module" r="15" cx="571" cy="296" mask="url(#pluginBOrderMask)"/>
<text class="module" x="571" y="296">4</text>
<!-- main scope -->
<circle class="package" r="190" cx="191" cy="191" />
<!-- scope name -->
<defs>
<path d="M 11,191 A 180 180 0 0 1 371 191" id="testp"/>
</defs>
<text class="package">
<textPath href="#testp" startOffset="50%">tests</textPath>
</text>
<!-- scope order number -->
<mask id="mainOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="190" cx="191" cy="191" />
</mask>
<circle class="module" r="15" cx="381" cy="191" mask="url(#mainOrderMask)"/>
<text class="module" x="381" y="191">3</text>
<!-- subpackage -->
<circle class="package" r="140" cx="191" cy="231" />
<!-- scope name -->
<defs>
<path d="M 61,231 A 130 130 0 0 1 321 231" id="subpackage"/>
</defs>
<text class="package">
<textPath href="#subpackage" startOffset="50%">subpackage</textPath>
</text>
<!-- scope order number -->
<mask id="subpackageOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="140" cx="191" cy="231" />
</mask>
<circle class="module" r="15" cx="331" cy="231" mask="url(#subpackageOrderMask)"/>
<text class="module" x="331" y="231">2</text>
<!-- test_subpackage.py -->
<circle class="module" r="90" cx="191" cy="271" />
<!-- scope name -->
<defs>
<path d="M 111,271 A 80 80 0 0 1 271 271" id="testSubpackage"/>
</defs>
<text class="module">
<textPath href="#testSubpackage" startOffset="50%">test_subpackage.py</textPath>
</text>
<!-- scope order number -->
<mask id="testSubpackageOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="90" cx="191" cy="271" />
</mask>
<circle class="module" r="15" cx="281" cy="271" mask="url(#testSubpackageOrderMask)"/>
<text class="module" x="281" y="271">1</text>
<!-- innermost -->
<line x1="191" x2="191" y1="231" y2="311"/>
<!-- mid -->
<path d="M 191 306 L 101 306 L 91 296 L 91 156 L 101 146 L 191 146" />
<!-- order -->
<path d="M 191 316 L 91 316 L 81 306 L 81 61 L 91 51 L 191 51" />
<!-- a_fix -->
<path d="M 191 306 L 291 306 L 301 296 L 301 96 L 311 86 L 486 86" />
<!-- b_fix -->
<path d="M 191 316 L 316 316 L 336 296 L 486 296" />
<ellipse class="fixture" rx="50" ry="25" cx="191" cy="231" />
<text x="191" y="231">inner</text>
<rect class="test" width="110" height="50" x="136" y="286" />
<text x="191" y="311">test_order</text>
<ellipse class="fixture" rx="50" ry="25" cx="191" cy="146" />
<text x="191" y="146">mid</text>
<ellipse class="fixture" rx="50" ry="25" cx="191" cy="51" />
<text x="191" y="51">order</text>
<ellipse class="fixture" rx="50" ry="25" cx="486" cy="86" />
<text x="486" y="86">a_fix</text>
<ellipse class="fixture" rx="50" ry="25" cx="486" cy="296" />
<text x="486" y="296">b_fix</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -1,38 +0,0 @@
import pytest
# fixtures documentation order example
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3):
order.append("f1")
@pytest.fixture
def f3():
order.append("f3")
@pytest.fixture(autouse=True)
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
assert order == ["s1", "m1", "a1", "f3", "f1", "f2"]

View File

@@ -0,0 +1,45 @@
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def a(order):
order.append("a")
@pytest.fixture
def b(a, order):
order.append("b")
@pytest.fixture(autouse=True)
def c(b, order):
order.append("c")
@pytest.fixture
def d(b, order):
order.append("d")
@pytest.fixture
def e(d, order):
order.append("e")
@pytest.fixture
def f(e, order):
order.append("f")
@pytest.fixture
def g(f, c, order):
order.append("g")
def test_order_and_g(g, order):
assert order == ["a", "b", "c", "d", "e", "f", "g"]

View File

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

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,31 @@
import pytest
@pytest.fixture(scope="class")
def order():
return []
@pytest.fixture(scope="class", autouse=True)
def c1(order):
order.append("c1")
@pytest.fixture(scope="class")
def c2(order):
order.append("c2")
@pytest.fixture(scope="class")
def c3(order, c1):
order.append("c3")
class TestClassWithC1Request:
def test_order(self, order, c1, c3):
assert order == ["c1", "c3"]
class TestClassWithoutC1Request:
def test_order(self, order, c2):
assert order == ["c1", "c2"]

View File

@@ -0,0 +1,76 @@
<svg xmlns="http://www.w3.org/2000/svg" width="862" height="402">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
line {
stroke: black;
stroke-width: 2;
}
rect.autouse {
fill: #ca7f3d;
}
</style>
<!-- TestWithC1Request -->
<circle class="class" r="200" cx="221" cy="201" />
<line x1="221" x2="221" y1="61" y2="316"/>
<ellipse class="fixture" rx="50" ry="25" cx="221" cy="61" />
<text x="221" y="61">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="221" cy="131" />
<text x="221" y="131">c1</text>
<ellipse class="fixture" rx="25" ry="25" cx="221" cy="271" />
<text x="221" y="271">c3</text>
<rect class="test" width="110" height="50" x="166" y="316" />
<text x="221" y="341">test_order</text>
<!-- scope name -->
<defs>
<path d="M31,201 A 190 190 0 0 1 411 201" id="testClassWith"/>
</defs>
<text class="class">
<textPath href="#testClassWith" startOffset="50%">TestWithC1Request</textPath>
</text>
<!-- TestWithoutC1Request -->
<circle class="class" r="200" cx="641" cy="201" />
<line x1="641" x2="641" y1="61" y2="316"/>
<ellipse class="fixture" rx="50" ry="25" cx="641" cy="61" />
<text x="641" y="61">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="641" cy="131" />
<text x="641" y="131">c1</text>
<ellipse class="fixture" rx="25" ry="25" cx="641" cy="271" />
<text x="641" y="271">c2</text>
<rect class="test" width="110" height="50" x="586" y="316" />
<text x="641" y="341">test_order</text>
<!-- scope name -->
<defs>
<path d="M451,201 A 190 190 0 0 1 831 201" id="testClassWithout"/>
</defs>
<text class="class">
<textPath href="#testClassWithout" startOffset="50%">TestWithoutC1Request</textPath>
</text>
<rect class="autouse" width="862" height="40" x="1" y="181" />
<rect width="10" height="100" class="autouse" x="426" y="151" />
<text x="431" y="201">autouse</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,36 @@
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def c1(order):
order.append("c1")
@pytest.fixture
def c2(order):
order.append("c2")
class TestClassWithAutouse:
@pytest.fixture(autouse=True)
def c3(self, order, c2):
order.append("c3")
def test_req(self, order, c1):
assert order == ["c2", "c3", "c1"]
def test_no_req(self, order):
assert order == ["c2", "c3"]
class TestClassWithoutAutouse:
def test_req(self, order, c1):
assert order == ["c1"]
def test_no_req(self, order):
assert order == []

View File

@@ -0,0 +1,100 @@
<svg xmlns="http://www.w3.org/2000/svg" width="862" height="502">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
line {
stroke: black;
stroke-width: 2;
}
rect.autouse {
fill: #ca7f3d;
}
</style>
<!-- TestWithAutouse -->
<circle class="class" r="250" cx="251" cy="251" />
<!-- scope name -->
<defs>
<path d="M11,251 A 240 240 0 0 1 491 251" id="testClassWith"/>
</defs>
<text class="class">
<textPath href="#testClassWith" startOffset="50%">TestWithAutouse</textPath>
</text>
<mask id="autouseScope">
<circle fill="white" r="249" cx="251" cy="251" />
</mask>
<!-- TestWithAutouse.test_req -->
<line x1="176" x2="176" y1="76" y2="426"/>
<ellipse class="fixture" rx="50" ry="25" cx="176" cy="76" />
<text x="176" y="76">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="176" cy="146" />
<text x="176" y="146">c2</text>
<ellipse class="fixture" rx="25" ry="25" cx="176" cy="216" />
<text x="176" y="216">c3</text>
<ellipse class="fixture" rx="25" ry="25" cx="176" cy="356" />
<text x="176" y="356">c1</text>
<rect class="test" width="100" height="50" x="126" y="401" />
<text x="176" y="426">test_req</text>
<!-- TestWithAutouse.test_no_req -->
<line x1="326" x2="326" y1="76" y2="346"/>
<ellipse class="fixture" rx="50" ry="25" cx="326" cy="76" />
<text x="326" y="76">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="326" cy="146" />
<text x="326" y="146">c2</text>
<ellipse class="fixture" rx="25" ry="25" cx="326" cy="216" />
<text x="326" y="216">c3</text>
<rect class="test" width="120" height="50" x="266" y="331" />
<text x="326" y="356">test_no_req</text>
<rect class="autouse" width="500" height="40" x="1" y="266" mask="url(#autouseScope)"/>
<text x="261" y="286">autouse</text>
<!-- TestWithoutAutouse -->
<circle class="class" r="170" cx="691" cy="251" />
<!-- scope name -->
<defs>
<path d="M 531,251 A 160 160 0 0 1 851 251" id="testClassWithout"/>
</defs>
<text class="class">
<textPath href="#testClassWithout" startOffset="50%">TestWithoutAutouse</textPath>
</text>
<!-- TestWithoutAutouse.test_req -->
<line x1="616" x2="616" y1="181" y2="321"/>
<ellipse class="fixture" rx="50" ry="25" cx="616" cy="181" />
<text x="616" y="181">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="616" cy="251" />
<text x="616" y="251">c1</text>
<rect class="test" width="100" height="50" x="566" y="296" />
<text x="616" y="321">test_req</text>
<!-- TestWithoutAutouse.test_no_req -->
<line x1="766" x2="766" y1="181" y2="251"/>
<ellipse class="fixture" rx="50" ry="25" cx="766" cy="181" />
<text x="766" y="181">order</text>
<rect class="test" width="120" height="50" x="706" y="226" />
<text x="766" y="251">test_no_req</text>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,45 @@
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def a(order):
order.append("a")
@pytest.fixture
def b(a, order):
order.append("b")
@pytest.fixture
def c(a, b, order):
order.append("c")
@pytest.fixture
def d(c, b, order):
order.append("d")
@pytest.fixture
def e(d, b, order):
order.append("e")
@pytest.fixture
def f(e, order):
order.append("f")
@pytest.fixture
def g(f, c, order):
order.append("g")
def test_order(g, order):
assert order == ["a", "b", "c", "d", "e", "f", "g"]

View File

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

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,36 @@
import pytest
@pytest.fixture(scope="session")
def order():
return []
@pytest.fixture
def func(order):
order.append("function")
@pytest.fixture(scope="class")
def cls(order):
order.append("class")
@pytest.fixture(scope="module")
def mod(order):
order.append("module")
@pytest.fixture(scope="package")
def pack(order):
order.append("package")
@pytest.fixture(scope="session")
def sess(order):
order.append("session")
class TestClass:
def test_order(self, func, cls, mod, pack, sess, order):
assert order == ["session", "package", "module", "class", "function"]

View File

@@ -0,0 +1,55 @@
<svg xmlns="http://www.w3.org/2000/svg" width="262" height="537">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
line {
stroke: black;
stroke-width: 2;
}
</style>
<!-- TestClass -->
<circle class="class" r="130" cx="131" cy="406" />
<line x1="131" x2="131" y1="21" y2="446"/>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="26" />
<text x="131" y="26">order</text>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="96" />
<text x="131" y="96">sess</text>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="166" />
<text x="131" y="166">pack</text>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="236" />
<text x="131" y="236">mod</text>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="306" />
<text x="131" y="306">cls</text>
<ellipse class="fixture" rx="50" ry="25" cx="131" cy="376" />
<text x="131" y="376">func</text>
<rect class="test" width="110" height="50" x="76" y="421" />
<text x="131" y="446">test_order</text>
<!-- scope name -->
<defs>
<path d="M131,526 A 120 120 0 0 1 136 286" id="testClass"/>
</defs>
<text class="class">
<textPath href="#testClass" startOffset="50%">TestClass</textPath>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,29 @@
import pytest
@pytest.fixture
def order():
return []
@pytest.fixture
def outer(order, inner):
order.append("outer")
class TestOne:
@pytest.fixture
def inner(self, order):
order.append("one")
def test_order(self, order, outer):
assert order == ["one", "outer"]
class TestTwo:
@pytest.fixture
def inner(self, order):
order.append("two")
def test_order(self, order, outer):
assert order == ["two", "outer"]

View File

@@ -0,0 +1,115 @@
<svg xmlns="http://www.w3.org/2000/svg" width="562" height="532">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
circle.module {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class, text.module {
fill: #0e84b5;
}
line, path {
stroke: black;
stroke-width: 2;
fill: none;
}
</style>
<!-- main scope -->
<circle class="module" r="265" cx="281" cy="266" />
<!-- scope name -->
<defs>
<path d="M 26,266 A 255 255 0 0 1 536 266" id="testModule"/>
</defs>
<text class="module">
<textPath href="#testModule" startOffset="50%">test_fixtures_request_different_scope.py</textPath>
</text>
<!-- TestOne -->
<circle class="class" r="100" cx="141" cy="266" />
<!-- inner -->
<line x1="141" x2="141" y1="231" y2="301"/>
<!-- order -->
<path d="M 141 296 L 201 296 L 211 286 L 211 146 L 221 136 L 281 136" />
<!-- outer -->
<path d="M 141 306 L 201 306 L 211 316 L 211 386 L 221 396 L 281 396" />
<ellipse class="fixture" rx="50" ry="25" cx="141" cy="231" />
<text x="141" y="231">inner</text>
<rect class="test" width="110" height="50" x="86" y="276" />
<text x="141" y="301">test_order</text>
<!-- scope name -->
<defs>
<path d="M 51,266 A 90 90 0 0 1 231 266" id="testOne"/>
</defs>
<text class="class">
<textPath href="#testOne" startOffset="50%">TestOne</textPath>
</text>
<!-- scope order number -->
<mask id="testOneOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="100" cx="141" cy="266" />
</mask>
<circle class="module" r="15" cx="41" cy="266" mask="url(#testOneOrderMask)"/>
<text class="module" x="41" y="266">1</text>
<!-- scope order number -->
<mask id="testMainOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="265" cx="281" cy="266" />
</mask>
<circle class="module" r="15" cx="16" cy="266" mask="url(#testMainOrderMask)"/>
<text class="module" x="16" y="266">2</text>
<!-- TestTwo -->
<circle class="class" r="100" cx="421" cy="266" />
<!-- inner -->
<line x1="421" x2="421" y1="231" y2="301"/>
<!-- order -->
<path d="M 421 296 L 361 296 L 351 286 L 351 146 L 341 136 L 281 136" />
<!-- outer -->
<path d="M 421 306 L 361 306 L 351 316 L 351 386 L 341 396 L 281 396" />
<ellipse class="fixture" rx="50" ry="25" cx="421" cy="231" />
<text x="421" y="231">inner</text>
<rect class="test" width="110" height="50" x="366" y="276" />
<text x="421" y="301">test_order</text>
<!-- scope name -->
<defs>
<path d="M 331,266 A 90 90 0 0 1 511 266" id="testTwo"/>
</defs>
<text class="class">
<textPath href="#testTwo" startOffset="50%">TestTwo</textPath>
</text>
<!-- scope order number -->
<mask id="testTwoOrderMask">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<circle fill="black" stroke="white" stroke-width="2" r="100" cx="421" cy="266" />
</mask>
<circle class="module" r="15" cx="521" cy="266" mask="url(#testTwoOrderMask)"/>
<text class="module" x="521" y="266">1</text>
<!-- scope order number -->
<circle class="module" r="15" cx="546" cy="266" mask="url(#testMainOrderMask)"/>
<text class="module" x="546" y="266">2</text>
<ellipse class="fixture" rx="50" ry="25" cx="281" cy="396" />
<text x="281" y="396">outer</text>
<ellipse class="fixture" rx="50" ry="25" cx="281" cy="136" />
<text x="281" y="136">order</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -221,14 +221,19 @@ Registering markers for your test suite is simple:
[pytest]
markers =
webtest: mark a test as a webtest.
slow: mark test as slow.
You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers:
Multiple custom markers can be registered, by defining each one in its own line, as shown in above example.
You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` and ``slow`` markers:
.. code-block:: pytest
$ pytest --markers
@pytest.mark.webtest: mark a test as a webtest.
@pytest.mark.slow: mark test as slow.
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
@@ -539,7 +544,7 @@ Let's run this without capturing output and see what we get:
.
1 passed in 0.12s
marking platform specific tests with pytest
Marking platform specific tests with pytest
--------------------------------------------------------------
.. regendoc:wipe

View File

@@ -26,7 +26,7 @@ class Python:
def __init__(self, version, picklefile):
self.pythonpath = shutil.which(version)
if not self.pythonpath:
pytest.skip("{!r} not found".format(version))
pytest.skip(f"{version!r} not found")
self.picklefile = picklefile
def dumps(self, obj):
@@ -69,4 +69,4 @@ class Python:
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
def test_basic_objects(python1, python2, obj):
python1.dumps(obj)
python2.load_and_is_true("obj == {}".format(obj))
python2.load_and_is_true(f"obj == {obj}")

View File

@@ -102,4 +102,4 @@ interesting to just look at the collection tree:
<YamlItem hello>
<YamlItem ok>
========================== no tests ran in 0.12s ===========================
======================== 2 tests collected in 0.12s ========================

View File

@@ -40,7 +40,7 @@ class YamlItem(pytest.Item):
)
def reportinfo(self):
return self.fspath, 0, "usecase: {}".format(self.name)
return self.fspath, 0, f"usecase: {self.name}"
class YamlException(Exception):

View File

@@ -175,7 +175,7 @@ objects, they are still using the default pytest representation:
<Function test_timedistance_v3[forward]>
<Function test_timedistance_v3[backward]>
========================== no tests ran in 0.12s ===========================
======================== 8 tests collected in 0.12s ========================
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
together with the actual data, instead of listing them separately.
@@ -252,7 +252,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
<Function test_demo1[advanced]>
<Function test_demo2[advanced]>
========================== no tests ran in 0.12s ===========================
======================== 4 tests collected in 0.12s ========================
Note that we told ``metafunc.parametrize()`` that your scenario values
should be considered class-scoped. With pytest-2.3 this leads to a
@@ -328,7 +328,7 @@ Let's first see how it looks like at collection time:
<Function test_db_initialized[d1]>
<Function test_db_initialized[d2]>
========================== no tests ran in 0.12s ===========================
======================== 2 tests collected in 0.12s ========================
And then when we run the test:
@@ -508,11 +508,12 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
ssssssssssss...ssssssssssss [100%]
sssssssssssssssssssssssssss [100%]
========================= short test summary info ==========================
SKIPPED [12] multipython.py:29: 'python3.5' not found
SKIPPED [12] multipython.py:29: 'python3.7' not found
3 passed, 24 skipped in 0.12s
SKIPPED [9] multipython.py:29: 'python3.5' not found
SKIPPED [9] multipython.py:29: 'python3.6' not found
SKIPPED [9] multipython.py:29: 'python3.7' not found
27 skipped in 0.12s
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------
@@ -637,13 +638,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 14 items / 11 deselected / 3 selected
collecting ... collected 24 items / 21 deselected / 3 selected
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
=============== 2 passed, 11 deselected, 1 xfailed in 0.12s ================
=============== 2 passed, 21 deselected, 1 xfailed in 0.12s ================
As the result:

View File

@@ -157,7 +157,7 @@ The test collection would look like this:
<Function simple_check>
<Function complex_check>
========================== no tests ran in 0.12s ===========================
======================== 2 tests collected in 0.12s ========================
You can check for multiple glob patterns by adding a space between the patterns:
@@ -220,7 +220,7 @@ You can always peek at the collection tree without running tests like this:
<Function test_method>
<Function test_anothermethod>
========================== no tests ran in 0.12s ===========================
======================== 3 tests collected in 0.12s ========================
.. _customizing-test-collection:
@@ -282,7 +282,7 @@ leave out the ``setup.py`` file:
<Module 'pkg/module_py2.py'>
<Function 'test_only_on_python2'>
====== no tests ran in 0.04 seconds ======
====== 1 tests found in 0.04 seconds ======
If you run with a Python 3 interpreter both the one test and the ``setup.py``
file will be left out:
@@ -296,7 +296,7 @@ file will be left out:
rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini
collected 0 items
========================== no tests ran in 0.12s ===========================
======================= no tests collected in 0.12s ========================
It's also possible to ignore files based on Unix shell-style wildcards by adding
patterns to :globalvar:`collect_ignore_glob`.

View File

@@ -446,7 +446,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
items = [1, 2, 3]
print("items is {!r}".format(items))
print(f"items is {items!r}")
> a, b = items.pop()
E TypeError: cannot unpack non-iterable int object

View File

@@ -452,7 +452,7 @@ and nothing when run plainly:
========================== no tests ran in 0.12s ===========================
profiling test duration
Profiling test duration
--------------------------
.. regendoc:wipe
@@ -498,7 +498,7 @@ Now we can profile which test functions execute the slowest:
0.10s call test_some_are_slow.py::test_funcfast
============================ 3 passed in 0.12s =============================
incremental testing - test steps
Incremental testing - test steps
---------------------------------------------------
.. regendoc:wipe
@@ -739,7 +739,7 @@ it (unless you use "autouse" fixture which are always executed ahead of the firs
executing).
post-process test reports / failures
Post-process test reports / failures
---------------------------------------
If you want to postprocess test reports and need access to the executing

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
Installation and Getting Started
===================================
**Pythons**: Python 3.5, 3.6, 3.7, 3.8, 3.9, PyPy3
**Pythons**: Python 3.6, 3.7, 3.8, 3.9, PyPy3
**Platforms**: Linux and Windows
@@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 6.0.2
pytest 6.2.3
.. _`simpletest`:

View File

@@ -2,7 +2,7 @@
.. sidebar:: Next Open Trainings
- `Professional testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via Python Academy, February 1-3 2021, Leipzig (Germany) and remote.
- `Professional testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via Python Academy, February 1-3 2021, remote and Leipzig (Germany). **Early-bird discount available until January 15th**.
Also see `previous talks and blogposts <talks.html>`_.
@@ -11,6 +11,7 @@
pytest: helps you write better programs
=======================================
.. module:: pytest
The ``pytest`` framework makes it easy to write small tests, yet
scales to support complex functional testing for applications and libraries.
@@ -69,7 +70,7 @@ Features
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box
- Python 3.5+ and PyPy 3
- Python 3.6+ and PyPy 3
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community

View File

@@ -68,4 +68,13 @@ Unsupported idioms / known issues
fundamentally incompatible with pytest because they don't support fixtures
properly since collection and test execution are separated.
Migrating from nose to pytest
------------------------------
`nose2pytest <https://github.com/pytest-dev/nose2pytest>`_ is a Python script
and pytest plugin to help convert Nose-based tests into pytest-based tests.
Specifically, the script transforms nose.tools.assert_* function calls into
raw assert statements, while preserving format of original arguments
as much as possible.
.. _nose: https://nose.readthedocs.io/en/latest/

View File

@@ -189,7 +189,7 @@ Mark a test function as using the given fixture names.
When using `usefixtures` in hooks, it can only load fixtures when applied to a test function before test setup
(for example in the `pytest_collection_modifyitems` hook).
Also not that his mark has no effect when applied to **fixtures**.
Also note that this mark has no effect when applied to **fixtures**.
@@ -248,6 +248,16 @@ Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to
mark.args == (10, "slow")
mark.kwargs == {"method": "thread"}
Example for using multiple custom markers:
.. code-block:: python
@pytest.mark.timeout(10, "slow", method="thread")
@pytest.mark.slow
def test_function():
...
When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``.
.. _`fixtures-api`:
@@ -304,11 +314,10 @@ request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache`
Under the hood, the cache plugin uses the simple
``dumps``/``loads`` API of the :py:mod:`json` stdlib module.
.. currentmodule:: _pytest.cacheprovider
``config.cache`` is an instance of :class:`pytest.Cache`:
.. automethod:: Cache.get
.. automethod:: Cache.set
.. automethod:: Cache.makedir
.. autoclass:: pytest.Cache()
:members:
.. fixture:: capsys
@@ -318,12 +327,10 @@ capsys
**Tutorial**: :doc:`capture`.
.. currentmodule:: _pytest.capture
.. autofunction:: capsys()
.. autofunction:: _pytest.capture.capsys()
:no-auto-options:
Returns an instance of :py:class:`CaptureFixture`.
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
@@ -334,7 +341,7 @@ capsys
captured = capsys.readouterr()
assert captured.out == "hello\n"
.. autoclass:: CaptureFixture()
.. autoclass:: pytest.CaptureFixture()
:members:
@@ -345,10 +352,10 @@ capsysbinary
**Tutorial**: :doc:`capture`.
.. autofunction:: capsysbinary()
.. autofunction:: _pytest.capture.capsysbinary()
:no-auto-options:
Returns an instance of :py:class:`CaptureFixture`.
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
@@ -367,10 +374,10 @@ capfd
**Tutorial**: :doc:`capture`.
.. autofunction:: capfd()
.. autofunction:: _pytest.capture.capfd()
:no-auto-options:
Returns an instance of :py:class:`CaptureFixture`.
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
@@ -389,10 +396,10 @@ capfdbinary
**Tutorial**: :doc:`capture`.
.. autofunction:: capfdbinary()
.. autofunction:: _pytest.capture.capfdbinary()
:no-auto-options:
Returns an instance of :py:class:`CaptureFixture`.
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
@@ -433,7 +440,7 @@ request
The ``request`` fixture is a special fixture providing information of the requesting test function.
.. autoclass:: _pytest.fixtures.FixtureRequest()
.. autoclass:: pytest.FixtureRequest()
:members:
@@ -475,9 +482,9 @@ caplog
.. autofunction:: _pytest.logging.caplog()
:no-auto-options:
Returns a :class:`_pytest.logging.LogCaptureFixture` instance.
Returns a :class:`pytest.LogCaptureFixture` instance.
.. autoclass:: _pytest.logging.LogCaptureFixture
.. autoclass:: pytest.LogCaptureFixture()
:members:
@@ -486,30 +493,30 @@ caplog
monkeypatch
~~~~~~~~~~~
.. currentmodule:: _pytest.monkeypatch
**Tutorial**: :doc:`monkeypatch`.
.. autofunction:: _pytest.monkeypatch.monkeypatch()
:no-auto-options:
Returns a :class:`MonkeyPatch` instance.
Returns a :class:`~pytest.MonkeyPatch` instance.
.. autoclass:: _pytest.monkeypatch.MonkeyPatch
.. autoclass:: pytest.MonkeyPatch
:members:
.. fixture:: testdir
.. fixture:: pytester
testdir
~~~~~~~
pytester
~~~~~~~~
.. currentmodule:: _pytest.pytester
.. versionadded:: 6.2
This fixture provides a :class:`Testdir` instance useful for black-box testing of test files, making it ideal to
test plugins.
Provides a :class:`~pytest.Pytester` instance that can be used to run and test pytest itself.
To use it, include in your top-most ``conftest.py`` file:
It provides an empty directory where pytest can be executed in isolation, and contains facilities
to write tests, configuration files, and match against expected output.
To use it, include in your topmost ``conftest.py`` file:
.. code-block:: python
@@ -517,13 +524,30 @@ To use it, include in your top-most ``conftest.py`` file:
.. autoclass:: Testdir()
.. autoclass:: pytest.Pytester()
:members:
.. autoclass:: RunResult()
.. autoclass:: _pytest.pytester.RunResult()
:members:
.. autoclass:: LineMatcher()
.. autoclass:: _pytest.pytester.LineMatcher()
:members:
:special-members: __str__
.. autoclass:: _pytest.pytester.HookRecorder()
:members:
.. fixture:: testdir
testdir
~~~~~~~
Identical to :fixture:`pytester`, but provides an instance whose methods return
legacy ``py.path.local`` objects instead when applicable.
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
.. autoclass:: pytest.Testdir()
:members:
@@ -534,12 +558,10 @@ recwarn
**Tutorial**: :ref:`assertwarnings`
.. currentmodule:: _pytest.recwarn
.. autofunction:: recwarn()
.. autofunction:: _pytest.recwarn.recwarn()
:no-auto-options:
.. autoclass:: WarningsRecorder()
.. autoclass:: pytest.WarningsRecorder()
:members:
Each recorded warning is an instance of :class:`warnings.WarningMessage`.
@@ -556,13 +578,11 @@ tmp_path
**Tutorial**: :doc:`tmpdir`
.. currentmodule:: _pytest.tmpdir
.. autofunction:: tmp_path()
.. autofunction:: _pytest.tmpdir.tmp_path()
:no-auto-options:
.. fixture:: tmp_path_factory
.. fixture:: _pytest.tmpdir.tmp_path_factory
tmp_path_factory
~~~~~~~~~~~~~~~~
@@ -571,12 +591,9 @@ tmp_path_factory
.. _`tmp_path_factory factory api`:
``tmp_path_factory`` instances have the following methods:
``tmp_path_factory`` is an instance of :class:`~pytest.TempPathFactory`:
.. currentmodule:: _pytest.tmpdir
.. automethod:: TempPathFactory.mktemp
.. automethod:: TempPathFactory.getbasetemp
.. autoclass:: pytest.TempPathFactory()
.. fixture:: tmpdir
@@ -586,9 +603,7 @@ tmpdir
**Tutorial**: :doc:`tmpdir`
.. currentmodule:: _pytest.tmpdir
.. autofunction:: tmpdir()
.. autofunction:: _pytest.tmpdir.tmpdir()
:no-auto-options:
@@ -601,12 +616,9 @@ tmpdir_factory
.. _`tmpdir factory api`:
``tmpdir_factory`` instances have the following methods:
``tmp_path_factory`` is an instance of :class:`~pytest.TempdirFactory`:
.. currentmodule:: _pytest.tmpdir
.. automethod:: TempdirFactory.mktemp
.. automethod:: TempdirFactory.getbasetemp
.. autoclass:: pytest.TempdirFactory()
.. _`hook-reference`:
@@ -668,6 +680,10 @@ items, delete or otherwise amend the test items:
.. autofunction:: pytest_collection_modifyitems
.. note::
If this hook is implemented in ``conftest.py`` files, it always receives all collected items, not only those
under the ``conftest.py`` where it is implemented.
.. autofunction:: pytest_collection_finish
Test running (runtest) hooks
@@ -816,6 +832,13 @@ Function
:members:
:show-inheritance:
FunctionDefinition
~~~~~~~~~~~~~~~~~~
.. autoclass:: _pytest.python.FunctionDefinition()
:members:
:show-inheritance:
Item
~~~~
@@ -1061,6 +1084,12 @@ Custom warnings generated in some situations such as improper usage or deprecate
.. autoclass:: pytest.PytestUnknownMarkWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnraisableExceptionWarning
:show-inheritance:
.. autoclass:: pytest.PytestUnhandledThreadExceptionWarning
:show-inheritance:
Consult the :ref:`internal-warnings` section in the documentation for more information.
@@ -1236,12 +1265,13 @@ passed multiple times. The expected format is ``name=value``. For example::
.. confval:: junit_family
.. versionadded:: 4.2
.. versionchanged:: 6.1
Default changed to ``xunit2``.
Configures the format of the generated JUnit XML file. The possible options are:
* ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format. **This is the default**.
* ``xunit2``: produces `xunit 2.0 style output <https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__,
which should be more compatible with latest Jenkins versions.
* ``xunit1`` (or ``legacy``): produces old style output, compatible with the xunit 1.0 format.
* ``xunit2``: produces `xunit 2.0 style output <https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__, which should be more compatible with latest Jenkins versions. **This is the default**.
.. code-block:: ini
@@ -1511,7 +1541,8 @@ passed multiple times. The expected format is ``name=value``. For example::
[seq] matches any character in seq
[!seq] matches any char not in seq
Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'``.
Default patterns are ``'*.egg'``, ``'.*'``, ``'_darcs'``, ``'build'``,
``'CVS'``, ``'dist'``, ``'node_modules'``, ``'venv'``, ``'{arch}'``.
Setting a ``norecursedirs`` replaces the default. Here is an example of
how to avoid certain directories:
@@ -1718,7 +1749,8 @@ All the command-line flags can be obtained by running ``pytest --help``::
failures.
--sw, --stepwise exit on test failure and continue from last failing
test next time
--stepwise-skip ignore the first failing test but stop on the next
--sw-skip, --stepwise-skip
ignore the first failing test but stop on the next
failing test
reporting:
@@ -1760,9 +1792,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
--maxfail=num exit after first num failures or errors.
--strict-config any warnings encountered while parsing the `pytest`
section of the configuration file raise errors.
--strict-markers, --strict
markers not registered in the `markers` section of
--strict-markers markers not registered in the `markers` section of
the configuration file raise errors.
--strict (deprecated) alias to --strict-markers.
-c file load configuration from `file` instead of trying to
locate one of the implicit configuration files.
--continue-on-collection-errors

View File

@@ -91,7 +91,7 @@ when run on an interpreter earlier than Python3.6:
import sys
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_function():
...
@@ -259,7 +259,7 @@ These two examples illustrate situations where you don't want to check for a con
at the module level, which is when a condition would otherwise be evaluated for marks.
This will make ``test_function`` ``XFAIL``. Note that no other code is executed after
the ``pytest.xfail`` call, differently from the marker. That's because it is implemented
the :func:`pytest.xfail` call, differently from the marker. That's because it is implemented
internally by raising a known exception.
**Reference**: :ref:`pytest.mark.xfail ref`
@@ -358,7 +358,7 @@ By specifying on the commandline:
pytest --runxfail
you can force the running and reporting of an ``xfail`` marked test
as if it weren't marked at all. This also causes ``pytest.xfail`` to produce no effect.
as if it weren't marked at all. This also causes :func:`pytest.xfail` to produce no effect.
Examples
~~~~~~~~

View File

@@ -15,13 +15,11 @@ You can use the ``tmp_path`` fixture which will
provide a temporary directory unique to the test invocation,
created in the `base temporary directory`_.
``tmp_path`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage:
``tmp_path`` is a ``pathlib.Path`` object. Here is an example test usage:
.. code-block:: python
# content of test_tmp_path.py
import os
CONTENT = "content"
@@ -63,7 +61,7 @@ Running this would result in a passed test except for the last
> assert 0
E assert 0
test_tmp_path.py:13: AssertionError
test_tmp_path.py:11: AssertionError
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================
@@ -97,9 +95,6 @@ and more. Here is an example test usage:
.. code-block:: python
# content of test_tmpdir.py
import os
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
p.write("content")
@@ -134,7 +129,7 @@ Running this would result in a passed test except for the last
> assert 0
E assert 0
test_tmpdir.py:9: AssertionError
test_tmpdir.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_tmpdir.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================

View File

@@ -470,6 +470,38 @@ seconds to finish (not available on Windows).
the command-line using ``-o faulthandler_timeout=X``.
.. _unraisable:
Warning about unraisable exceptions and unhandled thread exceptions
-------------------------------------------------------------------
.. versionadded:: 6.2
.. note::
These features only work on Python>=3.8.
Unhandled exceptions are exceptions that are raised in a situation in which
they cannot propagate to a caller. The most common case is an exception raised
in a :meth:`__del__ <object.__del__>` implementation.
Unhandled thread exceptions are exceptions raised in a :class:`~threading.Thread`
but not handled, causing the thread to terminate uncleanly.
Both types of exceptions are normally considered bugs, but may go unnoticed
because they don't cause the program itself to crash. Pytest detects these
conditions and issues a warning that is visible in the test run summary.
The plugins are automatically enabled for pytest runs, unless the
``-p no:unraisableexception`` (for unraisable exceptions) and
``-p no:threadexception`` (for thread exceptions) options are given on the
command-line.
The warnings may be silenced selectivly using the :ref:`pytest.mark.filterwarnings ref`
mark. The warning categories are :class:`pytest.PytestUnraisableExceptionWarning` and
:class:`pytest.PytestUnhandledThreadExceptionWarning`.
Creating JUnitXML format files
----------------------------------------------------

View File

@@ -265,7 +265,7 @@ Asserting warnings with the warns function
You can check that code raises a particular warning using ``pytest.warns``,
You can check that code raises a particular warning using func:`pytest.warns`,
which works in a similar manner to :ref:`raises <assertraises>`:
.. code-block:: python
@@ -293,7 +293,7 @@ argument ``match`` to assert that the exception matches a text or regex::
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
You can also call ``pytest.warns`` on a function or code string:
You can also call func:`pytest.warns` on a function or code string:
.. code-block:: python
@@ -328,10 +328,10 @@ Alternatively, you can examine raised warnings in detail using the
Recording warnings
------------------
You can record raised warnings either using ``pytest.warns`` or with
You can record raised warnings either using func:`pytest.warns` or with
the ``recwarn`` fixture.
To record with ``pytest.warns`` without asserting anything about the warnings,
To record with func:`pytest.warns` without asserting anything about the warnings,
pass ``None`` as the expected warning type:
.. code-block:: python
@@ -360,7 +360,7 @@ The ``recwarn`` fixture will record warnings for the whole function:
assert w.filename
assert w.lineno
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
Both ``recwarn`` and func:`pytest.warns` return the same interface for recorded
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
iterate over this instance, call ``len`` on it to get the number of recorded
warnings, or index into it to get a particular recorded warning.
@@ -387,7 +387,7 @@ are met.
pytest.fail("Expected a warning!")
If no warnings are issued when calling ``f``, then ``not record`` will
evaluate to ``True``. You can then call ``pytest.fail`` with a
evaluate to ``True``. You can then call :func:`pytest.fail` with a
custom error message.
.. _internal-warnings:

View File

@@ -33,26 +33,34 @@ Plugin discovery order at tool startup
``pytest`` loads plugin modules at tool startup in the following way:
* by loading all builtin plugins
1. by scanning the command line for the ``-p no:name`` option
and *blocking* that plugin from being loaded (even builtin plugins can
be blocked this way). This happens before normal command-line parsing.
* by loading all plugins registered through `setuptools entry points`_.
2. by loading all builtin plugins.
* by pre-scanning the command line for the ``-p name`` option
and loading the specified plugin before actual command line parsing.
3. by scanning the command line for the ``-p name`` option
and loading the specified plugin. This happens before normal command-line parsing.
* by loading all :file:`conftest.py` files as inferred by the command line
invocation:
4. by loading all plugins registered through `setuptools entry points`_.
- if no test paths are specified use current dir as a test path
- if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
to the directory part of the first test path.
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
Note that pytest does not find ``conftest.py`` files in deeper nested
sub directories at tool startup. It is usually a good idea to keep
your ``conftest.py`` file in the top level test or project root directory.
6. by loading all :file:`conftest.py` files as inferred by the command line
invocation:
* by recursively loading all plugins specified by the
:globalvar:`pytest_plugins` variable in ``conftest.py`` files
- if no test paths are specified, use the current dir as a test path
- if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
to the directory part of the first test path. After the ``conftest.py``
file is loaded, load all plugins specified in its
:globalvar:`pytest_plugins` variable if present.
Note that pytest does not find ``conftest.py`` files in deeper nested
sub directories at tool startup. It is usually a good idea to keep
your ``conftest.py`` file in the top level test or project root directory.
7. by recursively loading all plugins specified by the
:globalvar:`pytest_plugins` variable in ``conftest.py`` files.
.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
@@ -99,6 +107,10 @@ Here is how you might run it::
See also: :ref:`pythonpath`.
.. note::
Some hooks should be implemented only in plugins or conftest.py files situated at the
tests root directory due to how pytest discovers plugins during startup,
see the documentation of each hook for details.
Writing your own plugin
-----------------------
@@ -437,13 +449,7 @@ Additionally it is possible to copy examples for an example folder before runnin
test_example.py .. [100%]
============================= warnings summary =============================
test_example.py::test_plugin
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
testdir.copy_example("test_example.py")
-- Docs: https://docs.pytest.org/en/stable/warnings.html
======================= 2 passed, 1 warning in 0.12s =======================
============================ 2 passed in 0.12s =============================
For more information about the result object that ``runpytest()`` returns, and
the methods that it provides please check out the :py:class:`RunResult

View File

@@ -23,13 +23,14 @@ xfail_strict = true
filterwarnings = [
"error",
"default:Using or importing the ABCs:DeprecationWarning:unittest2.*",
# produced by older pyparsing<=2.2.0.
"default:Using or importing the ABCs:DeprecationWarning:pyparsing.*",
"default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*",
"ignore:Module already imported so cannot be rewritten:pytest.PytestWarning",
# 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 by python >=3.5 on execnet (pytest-xdist)
# produced on execnet (pytest-xdist)
"ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning",
# pytest's own futurewarnings
"ignore::pytest.PytestExperimentalApiWarning",
@@ -102,4 +103,4 @@ template = "changelog/_template.rst"
showcontent = true
[tool.black]
target-version = ['py35']
target-version = ['py36']

View File

@@ -57,6 +57,8 @@ Created automatically from {comment_url}.
Once all builds pass and it has been **approved** by one or more maintainers, the build
can be released by pushing a tag `{version}` to this repository.
Closes #{issue_number}.
"""
@@ -164,7 +166,9 @@ def trigger_release(payload_path: Path, token: str) -> None:
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.")
body = PR_BODY.format(
comment_url=get_comment_data(payload)["html_url"], version=version
comment_url=get_comment_data(payload)["html_url"],
version=version,
issue_number=issue_number,
)
pr = repo.create_pull(
f"Prepare release {version}",
@@ -227,7 +231,7 @@ def find_next_version(base_branch: str, is_major: bool) -> str:
msg = dedent(
f"""
Found features or breaking changes in `{base_branch}`, and feature releases can only be
created from `master`.":
created from `master`:
"""
)
msg += "\n".join(f"* `{x.name}`" for x in sorted(features + breaking))

View File

@@ -17,9 +17,7 @@ def announce(version):
stdout = stdout.decode("utf-8")
last_version = stdout.strip()
stdout = check_output(
["git", "log", "{}..HEAD".format(last_version), "--format=%aN"]
)
stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"])
stdout = stdout.decode("utf-8")
contributors = set(stdout.splitlines())
@@ -31,14 +29,10 @@ def announce(version):
Path(__file__).parent.joinpath(template_name).read_text(encoding="UTF-8")
)
contributors_text = (
"\n".join("* {}".format(name) for name in sorted(contributors)) + "\n"
)
contributors_text = "\n".join(f"* {name}" for name in sorted(contributors)) + "\n"
text = template_text.format(version=version, contributors=contributors_text)
target = Path(__file__).parent.joinpath(
"../doc/en/announce/release-{}.rst".format(version)
)
target = Path(__file__).parent.joinpath(f"../doc/en/announce/release-{version}.rst")
target.write_text(text, encoding="UTF-8")
print(f"{Fore.CYAN}[generate.announce] {Fore.RESET}Generated {target.name}")
@@ -47,7 +41,7 @@ def announce(version):
lines = index_path.read_text(encoding="UTF-8").splitlines()
indent = " "
for index, line in enumerate(lines):
if line.startswith("{}release-".format(indent)):
if line.startswith(f"{indent}release-"):
new_line = indent + target.stem
if line != new_line:
lines.insert(index, new_line)
@@ -96,7 +90,7 @@ def pre_release(version, *, skip_check_links):
if not skip_check_links:
check_links()
msg = "Prepare release version {}".format(version)
msg = f"Prepare release version {version}"
check_call(["git", "commit", "-a", "-m", msg])
print()

View File

@@ -17,7 +17,6 @@ classifiers =
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
@@ -27,6 +26,8 @@ classifiers =
Topic :: Utilities
keywords = test, unittest
project_urls =
Changelog=https://docs.pytest.org/en/stable/changelog.html
Twitter=https://twitter.com/pytestdotorg
Source=https://github.com/pytest-dev/pytest
Tracker=https://github.com/pytest-dev/pytest/issues
@@ -40,22 +41,21 @@ packages =
_pytest.mark
pytest
install_requires =
attrs>=17.4.0
attrs>=19.2.0
iniconfig
packaging
pluggy>=0.12,<1.0
pluggy>=0.12,<1.0.0a1
py>=1.8.2
toml
atomicwrites>=1.0;sys_platform=="win32"
colorama;sys_platform=="win32"
importlib-metadata>=0.12;python_version<"3.8"
pathlib2>=2.2.0;python_version<"3.6"
python_requires = >=3.5
python_requires = >=3.6
package_dir =
=src
setup_requires =
setuptools>=40.0
setuptools-scm
setuptools>=>=42.0
setuptools-scm>=3.4
zip_safe = no
[options.entry_points]
@@ -64,8 +64,6 @@ console_scripts =
py.test=pytest:console_main
[options.extras_require]
checkqa-mypy =
mypy==0.780
testing =
argcomplete
hypothesis>=3.56

View File

@@ -26,7 +26,7 @@ The generic argcomplete script for bash-completion
uses a python program to determine startup script generated by pip.
You can speed up completion somewhat by changing this script to include
# PYTHON_ARGCOMPLETE_OK
so the the python-argcomplete-check-easy-install-script does not
so the python-argcomplete-check-easy-install-script does not
need to be called to find the entry point of the code and see if that is
marked with PYTHON_ARGCOMPLETE_OK.
@@ -103,7 +103,7 @@ if os.environ.get("_ARGCOMPLETE"):
import argcomplete.completers
except ImportError:
sys.exit(-1)
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter()
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
argcomplete.autocomplete(parser, always_complete_options=False)

View File

@@ -5,6 +5,7 @@ import traceback
from inspect import CO_VARARGS
from inspect import CO_VARKEYWORDS
from io import StringIO
from pathlib import Path
from traceback import format_exception_only
from types import CodeType
from types import FrameType
@@ -17,10 +18,13 @@ from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
from typing import overload
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
from weakref import ref
@@ -37,15 +41,10 @@ from _pytest._code.source import Source
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
from _pytest.compat import final
from _pytest.compat import get_real_func
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING
from _pytest.pathlib import Path
if TYPE_CHECKING:
from typing import Type
from typing_extensions import Literal
from weakref import ReferenceType
@@ -55,15 +54,14 @@ if TYPE_CHECKING:
class Code:
"""Wrapper around Python code objects."""
def __init__(self, rawcode) -> None:
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
if not isinstance(rawcode, CodeType):
raise TypeError("not a code object: {!r}".format(rawcode))
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
self.raw = rawcode
__slots__ = ("raw",)
def __init__(self, obj: CodeType) -> None:
self.raw = obj
@classmethod
def from_function(cls, obj: object) -> "Code":
return cls(getrawcode(obj))
def __eq__(self, other):
return self.raw == other.raw
@@ -71,6 +69,14 @@ class Code:
# Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
@property
def firstlineno(self) -> int:
return self.raw.co_firstlineno - 1
@property
def name(self) -> str:
return self.raw.co_name
@property
def path(self) -> Union[py.path.local, str]:
"""Return a path object pointing to source code, or an ``str`` in
@@ -118,12 +124,26 @@ class Frame:
"""Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated."""
__slots__ = ("raw",)
def __init__(self, frame: FrameType) -> None:
self.lineno = frame.f_lineno - 1
self.f_globals = frame.f_globals
self.f_locals = frame.f_locals
self.raw = frame
self.code = Code(frame.f_code)
@property
def lineno(self) -> int:
return self.raw.f_lineno - 1
@property
def f_globals(self) -> Dict[str, Any]:
return self.raw.f_globals
@property
def f_locals(self) -> Dict[str, Any]:
return self.raw.f_locals
@property
def code(self) -> Code:
return Code(self.raw.f_code)
@property
def statement(self) -> "Source":
@@ -165,17 +185,20 @@ class Frame:
class TracebackEntry:
"""A single entry in a Traceback."""
_repr_style = None # type: Optional[Literal["short", "long"]]
exprinfo = None
__slots__ = ("_rawentry", "_excinfo", "_repr_style")
def __init__(
self,
rawentry: TracebackType,
excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
) -> None:
self._excinfo = excinfo
self._rawentry = rawentry
self.lineno = rawentry.tb_lineno - 1
self._excinfo = excinfo
self._repr_style: Optional['Literal["short", "long"]'] = None
@property
def lineno(self) -> int:
return self._rawentry.tb_lineno - 1
def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
assert mode in ("short", "long")
@@ -247,9 +270,9 @@ class TracebackEntry:
Mostly for internal use.
"""
tbh = (
tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = (
False
) # type: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]]
)
for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
# in normal cases, f_locals and f_globals are dictionaries
# however via `exec(...)` / `eval(...)` they can be other types
@@ -302,7 +325,7 @@ class Traceback(List[TracebackEntry]):
if isinstance(tb, TracebackType):
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
cur_ = cur # type: Optional[TracebackType]
cur_: Optional[TracebackType] = cur
while cur_ is not None:
yield TracebackEntry(cur_, excinfo=excinfo)
cur_ = cur_.tb_next
@@ -347,13 +370,11 @@ class Traceback(List[TracebackEntry]):
def __getitem__(self, key: int) -> TracebackEntry:
...
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Traceback": # noqa: F811
@overload
def __getitem__(self, key: slice) -> "Traceback":
...
def __getitem__( # noqa: F811
self, key: Union[int, slice]
) -> Union[TracebackEntry, "Traceback"]:
def __getitem__(self, key: Union[int, slice]) -> Union[TracebackEntry, "Traceback"]:
if isinstance(key, slice):
return self.__class__(super().__getitem__(key))
else:
@@ -384,7 +405,7 @@ class Traceback(List[TracebackEntry]):
def recursionindex(self) -> Optional[int]:
"""Return the index of the frame/TracebackEntry where recursion originates if
appropriate, None if no recursion occurred."""
cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]]
cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {}
for i, entry in enumerate(self):
# id for the code.raw is needed to work around
# the strange metaprogramming in the decorator lib from pypi
@@ -422,14 +443,14 @@ class ExceptionInfo(Generic[_E]):
_assert_start_repr = "AssertionError('assert "
_excinfo = attr.ib(type=Optional[Tuple["Type[_E]", "_E", TracebackType]])
_excinfo = attr.ib(type=Optional[Tuple[Type["_E"], "_E", TracebackType]])
_striptext = attr.ib(type=str, default="")
_traceback = attr.ib(type=Optional[Traceback], default=None)
@classmethod
def from_exc_info(
cls,
exc_info: Tuple["Type[_E]", "_E", TracebackType],
exc_info: Tuple[Type[_E], _E, TracebackType],
exprinfo: Optional[str] = None,
) -> "ExceptionInfo[_E]":
"""Return an ExceptionInfo for an existing exc_info tuple.
@@ -480,13 +501,13 @@ class ExceptionInfo(Generic[_E]):
"""Return an unfilled ExceptionInfo."""
return cls(None)
def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None:
def fill_unfilled(self, exc_info: Tuple[Type[_E], _E, TracebackType]) -> None:
"""Fill an unfilled ExceptionInfo created with ``for_later()``."""
assert self._excinfo is None, "ExceptionInfo was already filled"
self._excinfo = exc_info
@property
def type(self) -> "Type[_E]":
def type(self) -> Type[_E]:
"""The exception class."""
assert (
self._excinfo is not None
@@ -552,7 +573,7 @@ class ExceptionInfo(Generic[_E]):
return text
def errisinstance(
self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]]
self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]]
) -> bool:
"""Return True if the exception is an instance of exc.
@@ -626,7 +647,7 @@ class ExceptionInfo(Generic[_E]):
)
return fmt.repr_excinfo(self)
def match(self, regexp: "Union[str, Pattern[str]]") -> "Literal[True]":
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
"""Check whether the regular expression `regexp` matches the string
representation of the exception using :func:`python:re.search`.
@@ -750,7 +771,7 @@ class FormattedExcinfo:
else:
str_repr = safeformat(value)
# if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)):
lines.append("{:<10} = {}".format(name, str_repr))
lines.append(f"{name:<10} = {str_repr}")
# else:
# self._line("%-10s =\\" % (name,))
# # XXX
@@ -763,7 +784,7 @@ class FormattedExcinfo:
entry: TracebackEntry,
excinfo: Optional[ExceptionInfo[BaseException]] = None,
) -> "ReprEntry":
lines = [] # type: List[str]
lines: List[str] = []
style = entry._repr_style if entry._repr_style is not None else self.style
if style in ("short", "long"):
source = self._getentrysource(entry)
@@ -845,7 +866,7 @@ class FormattedExcinfo:
recursionindex = traceback.recursionindex()
except Exception as e:
max_frames = 10
extraline = (
extraline: Optional[str] = (
"!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
" The following exception happened when comparing locals in the stack frame:\n"
" {exc_type}: {exc_msg}\n"
@@ -855,7 +876,7 @@ class FormattedExcinfo:
exc_msg=str(e),
max_frames=max_frames,
total=len(traceback),
) # type: Optional[str]
)
# Type ignored because adding two instaces of a List subtype
# currently incorrectly has type List instead of the subtype.
traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
@@ -871,20 +892,20 @@ class FormattedExcinfo:
def repr_excinfo(
self, excinfo: ExceptionInfo[BaseException]
) -> "ExceptionChainRepr":
repr_chain = (
[]
) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]]
e = excinfo.value # type: Optional[BaseException]
excinfo_ = excinfo # type: Optional[ExceptionInfo[BaseException]]
repr_chain: List[
Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]
] = []
e: Optional[BaseException] = excinfo.value
excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo
descr = None
seen = set() # type: Set[int]
seen: Set[int] = set()
while e is not None and id(e) not in seen:
seen.add(id(e))
if excinfo_:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash = (
reprcrash: Optional[ReprFileLocation] = (
excinfo_._getreprcrash() if self.style != "value" else None
) # type: Optional[ReprFileLocation]
)
else:
# Fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work.
@@ -918,7 +939,7 @@ class FormattedExcinfo:
return ExceptionChainRepr(repr_chain)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class TerminalRepr:
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
@@ -936,14 +957,14 @@ class TerminalRepr:
# This class is abstract -- only subclasses are instantiated.
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ExceptionRepr(TerminalRepr):
# Provided by subclasses.
reprcrash = None # type: Optional[ReprFileLocation]
reprtraceback = None # type: ReprTraceback
reprcrash: Optional["ReprFileLocation"]
reprtraceback: "ReprTraceback"
def __attrs_post_init__(self) -> None:
self.sections = [] # type: List[Tuple[str, str, str]]
self.sections: List[Tuple[str, str, str]] = []
def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep))
@@ -954,7 +975,7 @@ class ExceptionRepr(TerminalRepr):
tw.line(content)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ExceptionChainRepr(ExceptionRepr):
chain = attr.ib(
type=Sequence[
@@ -978,7 +999,7 @@ class ExceptionChainRepr(ExceptionRepr):
super().toterminal(tw)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprExceptionInfo(ExceptionRepr):
reprtraceback = attr.ib(type="ReprTraceback")
reprcrash = attr.ib(type="ReprFileLocation")
@@ -988,7 +1009,7 @@ class ReprExceptionInfo(ExceptionRepr):
super().toterminal(tw)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprTraceback(TerminalRepr):
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
extraline = attr.ib(type=Optional[str])
@@ -1022,16 +1043,16 @@ class ReprTracebackNative(ReprTraceback):
self.extraline = None
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprEntryNative(TerminalRepr):
lines = attr.ib(type=Sequence[str])
style = "native" # type: _TracebackStyle
style: "_TracebackStyle" = "native"
def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprEntry(TerminalRepr):
lines = attr.ib(type=Sequence[str])
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
@@ -1059,11 +1080,11 @@ class ReprEntry(TerminalRepr):
# separate indents and source lines that are not failures: we want to
# highlight the code but not the indentation, which may contain markers
# such as "> assert 0"
fail_marker = "{} ".format(FormattedExcinfo.fail_marker)
fail_marker = f"{FormattedExcinfo.fail_marker} "
indent_size = len(fail_marker)
indents = [] # type: List[str]
source_lines = [] # type: List[str]
failure_lines = [] # type: List[str]
indents: List[str] = []
source_lines: List[str] = []
failure_lines: List[str] = []
for index, line in enumerate(self.lines):
is_failure_line = line.startswith(fail_marker)
if is_failure_line:
@@ -1111,7 +1132,7 @@ class ReprEntry(TerminalRepr):
)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprFileLocation(TerminalRepr):
path = attr.ib(type=str, converter=str)
lineno = attr.ib(type=int)
@@ -1125,10 +1146,10 @@ class ReprFileLocation(TerminalRepr):
if i != -1:
msg = msg[:i]
tw.write(self.path, bold=True, red=True)
tw.line(":{}: {}".format(self.lineno, msg))
tw.line(f":{self.lineno}: {msg}")
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprLocals(TerminalRepr):
lines = attr.ib(type=Sequence[str])
@@ -1137,7 +1158,7 @@ class ReprLocals(TerminalRepr):
tw.line(indent + line)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
@attr.s(eq=False)
class ReprFuncArgs(TerminalRepr):
args = attr.ib(type=Sequence[Tuple[str, object]])
@@ -1145,7 +1166,7 @@ class ReprFuncArgs(TerminalRepr):
if self.args:
linesofar = ""
for name, value in self.args:
ns = "{} = {}".format(name, value)
ns = f"{name} = {value}"
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
if linesofar:
tw.line(linesofar)
@@ -1175,7 +1196,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
obj = obj.place_as # type: ignore[attr-defined]
try:
code = Code(obj)
code = Code.from_function(obj)
except TypeError:
try:
fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type]

View File

@@ -2,17 +2,17 @@ import ast
import inspect
import textwrap
import tokenize
import types
import warnings
from bisect import bisect_right
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Optional
from typing import overload
from typing import Tuple
from typing import Union
from _pytest.compat import overload
class Source:
"""An immutable object holding a source code fragment.
@@ -22,7 +22,7 @@ class Source:
def __init__(self, obj: object = None) -> None:
if not obj:
self.lines = [] # type: List[str]
self.lines: List[str] = []
elif isinstance(obj, Source):
self.lines = obj.lines
elif isinstance(obj, (tuple, list)):
@@ -30,8 +30,11 @@ class Source:
elif isinstance(obj, str):
self.lines = deindent(obj.split("\n"))
else:
rawcode = getrawcode(obj)
src = inspect.getsource(rawcode)
try:
rawcode = getrawcode(obj)
src = inspect.getsource(rawcode)
except TypeError:
src = inspect.getsource(obj) # type: ignore[arg-type]
self.lines = deindent(src.split("\n"))
def __eq__(self, other: object) -> bool:
@@ -46,11 +49,11 @@ class Source:
def __getitem__(self, key: int) -> str:
...
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Source": # noqa: F811
@overload
def __getitem__(self, key: slice) -> "Source":
...
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: # noqa: F811
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]:
if isinstance(key, int):
return self.lines[key]
else:
@@ -123,19 +126,17 @@ def findsource(obj) -> Tuple[Optional[Source], int]:
return source, lineno
def getrawcode(obj, trycall: bool = True):
def getrawcode(obj: object, trycall: bool = True) -> types.CodeType:
"""Return code object for given function."""
try:
return obj.__code__
return obj.__code__ # type: ignore[attr-defined,no-any-return]
except AttributeError:
obj = getattr(obj, "f_code", obj)
obj = getattr(obj, "__code__", obj)
if trycall and not hasattr(obj, "co_firstlineno"):
if hasattr(obj, "__call__") and not inspect.isclass(obj):
x = getrawcode(obj.__call__, trycall=False)
if hasattr(x, "co_firstlineno"):
return x
return obj
pass
if trycall:
call = getattr(obj, "__call__", None)
if call and not isinstance(obj, type):
return getrawcode(call, trycall=False)
raise TypeError(f"could not get code object for {obj!r}")
def deindent(lines: Iterable[str]) -> List[str]:
@@ -145,12 +146,12 @@ def deindent(lines: Iterable[str]) -> List[str]:
def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]:
# Flatten all statements and except handlers into one lineno-list.
# AST's line numbers start indexing at 1.
values = [] # type: List[int]
values: List[int] = []
for x in ast.walk(node):
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
values.append(x.lineno - 1)
for name in ("finalbody", "orelse"):
val = getattr(x, name, None) # type: Optional[List[ast.stmt]]
val: Optional[List[ast.stmt]] = getattr(x, name, None)
if val:
# Treat the finally/orelse part as its own statement.
values.append(val[0].lineno - 1 - 1)

View File

@@ -122,7 +122,7 @@ def _pformat_dispatch(
width: int = 80,
depth: Optional[int] = None,
*,
compact: bool = False
compact: bool = False,
) -> str:
return AlwaysDispatchingPrettyPrinter(
indent=indent, width=width, depth=depth, compact=compact

View File

@@ -76,7 +76,7 @@ class TerminalWriter:
self._file = file
self.hasmarkup = should_do_markup(file)
self._current_line = ""
self._terminal_width = None # type: Optional[int]
self._terminal_width: Optional[int] = None
self.code_highlight = True
@property
@@ -97,7 +97,7 @@ class TerminalWriter:
def markup(self, text: str, **markup: bool) -> str:
for name in markup:
if name not in self._esctable:
raise ValueError("unknown markup: {!r}".format(name))
raise ValueError(f"unknown markup: {name!r}")
if self.hasmarkup:
esc = [self._esctable[name] for name, on in markup.items() if on]
if esc:
@@ -109,7 +109,7 @@ class TerminalWriter:
sepchar: str,
title: Optional[str] = None,
fullwidth: Optional[int] = None,
**markup: bool
**markup: bool,
) -> None:
if fullwidth is None:
fullwidth = self.fullwidth
@@ -128,7 +128,7 @@ class TerminalWriter:
# N <= (fullwidth - len(title) - 2) // (2*len(sepchar))
N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1)
fill = sepchar * N
line = "{} {} {}".format(fill, title, fill)
line = f"{fill} {title} {fill}"
else:
# we want len(sepchar)*N <= fullwidth
# i.e. N <= fullwidth // len(sepchar)
@@ -204,7 +204,7 @@ class TerminalWriter:
except ImportError:
return source
else:
highlighted = highlight(
highlighted: str = highlight(
source, PythonLexer(), TerminalFormatter(bg="dark")
) # type: str
)
return highlighted

View File

@@ -4,12 +4,12 @@ from typing import Any
from typing import Generator
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from _pytest.assertion import rewrite
from _pytest.assertion import truncate
from _pytest.assertion import util
from _pytest.assertion.rewrite import assertstate_key
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
@@ -83,7 +83,7 @@ class AssertionState:
def __init__(self, config: Config, mode) -> None:
self.mode = mode
self.trace = config.trace.root.get("assertion")
self.hook = None # type: Optional[rewrite.AssertionRewritingHook]
self.hook: Optional[rewrite.AssertionRewritingHook] = None
def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:

View File

@@ -13,6 +13,8 @@ import struct
import sys
import tokenize
import types
from pathlib import Path
from pathlib import PurePath
from typing import Callable
from typing import Dict
from typing import IO
@@ -22,6 +24,7 @@ from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
import py
@@ -32,24 +35,20 @@ from _pytest.assertion import util
from _pytest.assertion.util import ( # noqa: F401
format_explanation as _format_explanation,
)
from _pytest.compat import fspath
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.main import Session
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import Path
from _pytest.pathlib import PurePath
from _pytest.store import StoreKey
if TYPE_CHECKING:
from _pytest.assertion import AssertionState # noqa: F401
from _pytest.assertion import AssertionState
assertstate_key = StoreKey["AssertionState"]()
# pytest caches rewritten pycs in pycache dirs
PYTEST_TAG = "{}-pytest-{}".format(sys.implementation.cache_tag, version)
PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}"
PYC_EXT = ".py" + (__debug__ and "c" or "o")
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
@@ -63,14 +62,14 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
self.fnpats = config.getini("python_files")
except ValueError:
self.fnpats = ["test_*.py", "*_test.py"]
self.session = None # type: Optional[Session]
self._rewritten_names = set() # type: Set[str]
self._must_rewrite = set() # type: Set[str]
self.session: Optional[Session] = None
self._rewritten_names: Set[str] = set()
self._must_rewrite: Set[str] = set()
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
# which might result in infinite recursion (#3506)
self._writing_pyc = False
self._basenames_to_check_rewrite = {"conftest"}
self._marked_for_rewrite_cache = {} # type: Dict[str, bool]
self._marked_for_rewrite_cache: Dict[str, bool] = {}
self._session_paths_checked = False
def set_session(self, session: Optional[Session]) -> None:
@@ -100,7 +99,7 @@ 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.5 - python3.6: `namespace`
# python3.6: `namespace`
# python3.7+: `None`
or spec.origin == "namespace"
or spec.origin is None
@@ -150,7 +149,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
ok = try_makedirs(cache_dir)
if not ok:
write = False
state.trace("read only directory: {}".format(cache_dir))
state.trace(f"read only directory: {cache_dir}")
cache_name = fn.name[:-3] + PYC_TAIL
pyc = cache_dir / cache_name
@@ -158,7 +157,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
# to check for a cached pyc. This may not be optimal...
co = _read_pyc(fn, pyc, state.trace)
if co is None:
state.trace("rewriting {!r}".format(fn))
state.trace(f"rewriting {fn!r}")
source_stat, co = _rewrite_test(fn, self.config)
if write:
self._writing_pyc = True
@@ -167,7 +166,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
finally:
self._writing_pyc = False
else:
state.trace("found cached rewritten pyc for {}".format(fn))
state.trace(f"found cached rewritten pyc for {fn}")
exec(co, module.__dict__)
def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
@@ -206,20 +205,18 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
if self._is_marked_for_rewrite(name, state):
return False
state.trace("early skip of rewriting module: {}".format(name))
state.trace(f"early skip of rewriting module: {name}")
return True
def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
# always rewrite conftest files
if os.path.basename(fn) == "conftest.py":
state.trace("rewriting conftest file: {!r}".format(fn))
state.trace(f"rewriting conftest file: {fn!r}")
return True
if self.session is not None:
if self.session.isinitpath(py.path.local(fn)):
state.trace(
"matched test file (was specified on cmdline): {!r}".format(fn)
)
state.trace(f"matched test file (was specified on cmdline): {fn!r}")
return True
# modules not passed explicitly on the command line are only
@@ -227,7 +224,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
fn_path = PurePath(fn)
for pat in self.fnpats:
if fnmatch_ex(pat, fn_path):
state.trace("matched test file {!r}".format(fn))
state.trace(f"matched test file {fn!r}")
return True
return self._is_marked_for_rewrite(name, state)
@@ -238,9 +235,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
except KeyError:
for marked in self._must_rewrite:
if name == marked or name.startswith(marked + "."):
state.trace(
"matched marked file {!r} (from {!r})".format(name, marked)
)
state.trace(f"matched marked file {name!r} (from {marked!r})")
self._marked_for_rewrite_cache[name] = True
return True
@@ -286,12 +281,16 @@ def _write_pyc_fp(
) -> None:
# Technically, we don't have to have the same pyc format as
# (C)Python, since these "pycs" should never be seen by builtin
# import. However, there's little reason deviate.
# 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)
# 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
# "<LL" stands for 2 unsigned longs, little-ending
# "<LL" stands for 2 unsigned longs, little-endian.
fp.write(struct.pack("<LL", mtime, size))
fp.write(marshal.dumps(co))
@@ -306,10 +305,10 @@ if sys.platform == "win32":
pyc: Path,
) -> bool:
try:
with atomic_write(fspath(pyc), mode="wb", overwrite=True) as fp:
with atomic_write(os.fspath(pyc), mode="wb", overwrite=True) as fp:
_write_pyc_fp(fp, source_stat, co)
except OSError as e:
state.trace("error writing pyc file at {}: {}".format(pyc, e))
state.trace(f"error writing pyc file at {pyc}: {e}")
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, pycache dir being a
# file etc.
@@ -325,20 +324,18 @@ else:
source_stat: os.stat_result,
pyc: Path,
) -> bool:
proc_pyc = "{}.{}".format(pyc, os.getpid())
proc_pyc = f"{pyc}.{os.getpid()}"
try:
fp = open(proc_pyc, "wb")
except OSError as e:
state.trace(
"error writing pyc file at {}: errno={}".format(proc_pyc, e.errno)
)
state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
return False
try:
_write_pyc_fp(fp, source_stat, co)
os.rename(proc_pyc, fspath(pyc))
os.rename(proc_pyc, os.fspath(pyc))
except OSError as e:
state.trace("error writing pyc file at {}: {}".format(pyc, e))
state.trace(f"error writing pyc file at {pyc}: {e}")
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, pycache dir being a
# file etc.
@@ -350,7 +347,7 @@ else:
def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
"""Read and rewrite *fn* and return the code object."""
fn_ = fspath(fn)
fn_ = os.fspath(fn)
stat = os.stat(fn_)
with open(fn_, "rb") as f:
source = f.read()
@@ -368,30 +365,42 @@ def _read_pyc(
Return rewritten code if successful or None if not.
"""
try:
fp = open(fspath(pyc), "rb")
fp = open(os.fspath(pyc), "rb")
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(fspath(source))
stat_result = os.stat(os.fspath(source))
mtime = int(stat_result.st_mtime)
size = stat_result.st_size
data = fp.read(12)
data = fp.read(16 if has_flags else 12)
except OSError as e:
trace("_read_pyc({}): OSError {}".format(source, e))
trace(f"_read_pyc({source}): OSError {e}")
return None
# Check for invalid or out of date pyc file.
if (
len(data) != 12
or data[:4] != importlib.util.MAGIC_NUMBER
or struct.unpack("<LL", data[4:]) != (mtime & 0xFFFFFFFF, size & 0xFFFFFFFF)
):
trace("_read_pyc(%s): invalid or out of date pyc" % source)
if len(data) != (16 if has_flags else 12):
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":
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]
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]
if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
trace("_read_pyc(%s): invalid pyc (incorrect size)" % source)
return None
try:
co = marshal.load(fp)
except Exception as e:
trace("_read_pyc({}): marshal.load error {}".format(source, e))
trace(f"_read_pyc({source}): marshal.load error {e}")
return None
if not isinstance(co, types.CodeType):
trace("_read_pyc(%s): not a code object" % source)
@@ -536,12 +545,12 @@ def set_location(node, lineno, col_offset):
def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
"""Return a mapping from {lineno: "assertion test expression"}."""
ret = {} # type: Dict[int, str]
ret: Dict[int, str] = {}
depth = 0
lines = [] # type: List[str]
assert_lineno = None # type: Optional[int]
seen_lines = set() # type: Set[int]
lines: List[str] = []
assert_lineno: Optional[int] = None
seen_lines: Set[int] = set()
def _write_and_reset() -> None:
nonlocal depth, lines, assert_lineno, seen_lines
@@ -706,12 +715,12 @@ class AssertionRewriter(ast.NodeVisitor):
]
mod.body[pos:pos] = imports
# Collect asserts.
nodes = [mod] # type: List[ast.AST]
nodes: List[ast.AST] = [mod]
while nodes:
node = nodes.pop()
for name, field in ast.iter_fields(node):
if isinstance(field, list):
new = [] # type: List[ast.AST]
new: List[ast.AST] = []
for i, child in enumerate(field):
if isinstance(child, ast.Assert):
# Transform assert.
@@ -783,7 +792,7 @@ class AssertionRewriter(ast.NodeVisitor):
to format a string of %-formatted values as added by
.explanation_param().
"""
self.explanation_specifiers = {} # type: Dict[str, ast.expr]
self.explanation_specifiers: Dict[str, ast.expr] = {}
self.stack.append(self.explanation_specifiers)
def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
@@ -831,19 +840,19 @@ class AssertionRewriter(ast.NodeVisitor):
"assertion is always true, perhaps remove parentheses?"
),
category=None,
filename=fspath(self.module_path),
filename=os.fspath(self.module_path),
lineno=assert_.lineno,
)
self.statements = [] # type: List[ast.stmt]
self.variables = [] # type: List[str]
self.statements: List[ast.stmt] = []
self.variables: List[str] = []
self.variable_counter = itertools.count()
if self.enable_assertion_pass_hook:
self.format_variables = [] # type: List[str]
self.format_variables: List[str] = []
self.stack = [] # type: List[Dict[str, ast.expr]]
self.expl_stmts = [] # type: List[ast.stmt]
self.stack: List[Dict[str, ast.expr]] = []
self.expl_stmts: List[ast.stmt] = []
self.push_format_context()
# Rewrite assert into a bunch of statements.
top_condition, explanation = self.visit(assert_.test)
@@ -950,7 +959,7 @@ class AssertionRewriter(ast.NodeVisitor):
# Process each operand, short-circuiting if needed.
for i, v in enumerate(boolop.values):
if i:
fail_inner = [] # type: List[ast.stmt]
fail_inner: List[ast.stmt] = []
# cond is set in a prior loop iteration below
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
self.expl_stmts = fail_inner
@@ -961,10 +970,10 @@ class AssertionRewriter(ast.NodeVisitor):
call = ast.Call(app, [expl_format], [])
self.expl_stmts.append(ast.Expr(call))
if i < levels:
cond = res # type: ast.expr
cond: ast.expr = res
if is_or:
cond = ast.UnaryOp(ast.Not(), cond)
inner = [] # type: List[ast.stmt]
inner: List[ast.stmt] = []
self.statements.append(ast.If(cond, inner, []))
self.statements = body = inner
self.statements = save
@@ -983,7 +992,7 @@ class AssertionRewriter(ast.NodeVisitor):
symbol = BINOP_MAP[binop.op.__class__]
left_expr, left_expl = self.visit(binop.left)
right_expr, right_expl = self.visit(binop.right)
explanation = "({} {} {})".format(left_expl, symbol, right_expl)
explanation = f"({left_expl} {symbol} {right_expl})"
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation
@@ -1008,11 +1017,11 @@ class AssertionRewriter(ast.NodeVisitor):
new_call = ast.Call(new_func, new_args, new_kwargs)
res = self.assign(new_call)
res_expl = self.explanation_param(self.display(res))
outer_expl = "{}\n{{{} = {}\n}}".format(res_expl, res_expl, expl)
outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
return res, outer_expl
def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]:
# From Python 3.5, a Starred node can appear in a function call.
# A Starred node can appear in a function call.
res, expl = self.visit(starred.value)
new_starred = ast.Starred(res, starred.ctx)
return new_starred, "*" + expl
@@ -1031,7 +1040,7 @@ class AssertionRewriter(ast.NodeVisitor):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
left_expl = "({})".format(left_expl)
left_expl = f"({left_expl})"
res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
@@ -1042,11 +1051,11 @@ class AssertionRewriter(ast.NodeVisitor):
for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
next_expl = "({})".format(next_expl)
next_expl = f"({next_expl})"
results.append(next_res)
sym = BINOP_MAP[op.__class__]
syms.append(ast.Str(sym))
expl = "{} {} {}".format(left_expl, sym, next_expl)
expl = f"{left_expl} {sym} {next_expl}"
expls.append(ast.Str(expl))
res_expr = ast.Compare(left_res, [op], [next_res])
self.statements.append(ast.Assign([store_names[i]], res_expr))
@@ -1060,7 +1069,7 @@ class AssertionRewriter(ast.NodeVisitor):
ast.Tuple(results, ast.Load()),
)
if len(comp.ops) > 1:
res = ast.BoolOp(ast.And(), load_names) # type: ast.expr
res: ast.expr = ast.BoolOp(ast.And(), load_names)
else:
res = load_names[0]
return res, self.explanation_param(self.pop_format_context(expl_call))
@@ -1072,7 +1081,7 @@ def try_makedirs(cache_dir: Path) -> bool:
Returns True if successful or if it already exists.
"""
try:
os.makedirs(fspath(cache_dir), exist_ok=True)
os.makedirs(os.fspath(cache_dir), exist_ok=True)
except (FileNotFoundError, NotADirectoryError, FileExistsError):
# One of the path components was not a directory:
# - we're in a zip file

View File

@@ -70,10 +70,10 @@ def _truncate_explanation(
truncated_line_count += 1 # Account for the part-truncated final line
msg = "...Full output truncated"
if truncated_line_count == 1:
msg += " ({} line hidden)".format(truncated_line_count)
msg += f" ({truncated_line_count} line hidden)"
else:
msg += " ({} lines hidden)".format(truncated_line_count)
msg += ", {}".format(USAGE_MSG)
msg += f" ({truncated_line_count} lines hidden)"
msg += f", {USAGE_MSG}"
truncated_explanation.extend(["", str(msg)])
return truncated_explanation

View File

@@ -9,24 +9,22 @@ from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
import _pytest._code
from _pytest import outcomes
from _pytest._io.saferepr import _pformat_dispatch
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
# loaded and in turn call the hooks defined here as part of the
# DebugInterpreter.
_reprcompare = None # type: Optional[Callable[[str, object, object], Optional[str]]]
_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None
# Works similarly as _reprcompare attribute. Is populated with the hook call
# when pytest_runtest_setup is called.
_assertion_pass = None # type: Optional[Callable[[int, str, str], None]]
_assertion_pass: Optional[Callable[[int, str, str], None]] = None
def format_explanation(explanation: str) -> str:
@@ -112,6 +110,10 @@ def isset(x: Any) -> bool:
return isinstance(x, (set, frozenset))
def isnamedtuple(obj: Any) -> bool:
return isinstance(obj, tuple) and getattr(obj, "_fields", None) is not None
def isdatacls(obj: Any) -> bool:
return getattr(obj, "__dataclass_fields__", None) is not None
@@ -143,7 +145,7 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
left_repr = saferepr(left, maxsize=maxsize)
right_repr = saferepr(right, maxsize=maxsize)
summary = "{} {} {}".format(left_repr, op, right_repr)
summary = f"{left_repr} {op} {right_repr}"
explanation = None
try:
@@ -173,15 +175,20 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
if istext(left) and istext(right):
explanation = _diff_text(left, right, verbose)
else:
if issequence(left) and issequence(right):
if type(left) == type(right) and (
isdatacls(left) or isattrs(left) or isnamedtuple(left)
):
# Note: unlike dataclasses/attrs, namedtuples compare only the
# field values, not the type or field names. But this branch
# intentionally only handles the same-type case, which was often
# used in older code bases before dataclasses/attrs were available.
explanation = _compare_eq_cls(left, right, verbose)
elif issequence(left) and issequence(right):
explanation = _compare_eq_sequence(left, right, verbose)
elif isset(left) and isset(right):
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
type_fn = (isdatacls, isattrs)
explanation = _compare_eq_cls(left, right, verbose, type_fn)
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
@@ -198,7 +205,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
"""
from difflib import ndiff
explanation = [] # type: List[str]
explanation: List[str] = []
if verbose < 1:
i = 0 # just in case left or right has zero length
@@ -243,7 +250,7 @@ def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends)
explanation = [] # type: List[str]
explanation: List[str] = []
explanation += ["+" + line for line in left_lines]
explanation += ["-" + line for line in right_lines]
@@ -297,7 +304,7 @@ def _compare_eq_sequence(
left: Sequence[Any], right: Sequence[Any], verbose: int = 0
) -> List[str]:
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
explanation = [] # type: List[str]
explanation: List[str] = []
len_left = len(left)
len_right = len(right)
for i in range(min(len_left, len_right)):
@@ -317,9 +324,7 @@ def _compare_eq_sequence(
left_value = left[i]
right_value = right[i]
explanation += [
"At index {} diff: {!r} != {!r}".format(i, left_value, right_value)
]
explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"]
break
if comparing_bytes:
@@ -339,9 +344,7 @@ def _compare_eq_sequence(
extra = saferepr(right[len_left])
if len_diff == 1:
explanation += [
"{} contains one more item: {}".format(dir_with_more, extra)
]
explanation += [f"{dir_with_more} contains one more item: {extra}"]
else:
explanation += [
"%s contains %d more items, first extra item: %s"
@@ -370,7 +373,7 @@ def _compare_eq_set(
def _compare_eq_dict(
left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
) -> List[str]:
explanation = [] # type: List[str]
explanation: List[str] = []
set_left = set(left)
set_right = set(right)
common = set_left.intersection(set_right)
@@ -408,21 +411,17 @@ def _compare_eq_dict(
return explanation
def _compare_eq_cls(
left: Any,
right: Any,
verbose: int,
type_fns: Tuple[Callable[[Any], bool], Callable[[Any], bool]],
) -> List[str]:
isdatacls, isattrs = type_fns
def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
elif isattrs(left):
all_fields = left.__attrs_attrs__
fields_to_check = [
field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD)
]
fields_to_check = [field.name for field in all_fields if getattr(field, "eq")]
elif isnamedtuple(left):
fields_to_check = left._fields
else:
assert False
indent = " "
same = []

View File

@@ -3,6 +3,7 @@
# pytest-cache version.
import json
import os
from pathlib import Path
from typing import Dict
from typing import Generator
from typing import Iterable
@@ -14,21 +15,22 @@ from typing import Union
import attr
import py
import pytest
from .pathlib import Path
from .pathlib import resolve_from_str
from .pathlib import rm_rf
from .reports import CollectReport
from _pytest import nodes
from _pytest._io import TerminalWriter
from _pytest.compat import final
from _pytest.compat import order_preserving_dict
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.python import Module
from _pytest.python import Package
from _pytest.reports import TestReport
@@ -52,7 +54,7 @@ Signature: 8a477f597d28d172789f06886806bc55
@final
@attr.s
@attr.s(init=False)
class Cache:
_cachedir = attr.ib(type=Path, repr=False)
_config = attr.ib(type=Config, repr=False)
@@ -63,26 +65,52 @@ class Cache:
# sub-directory under cache-dir for values created by "set"
_CACHE_PREFIX_VALUES = "v"
@classmethod
def for_config(cls, config: Config) -> "Cache":
cachedir = cls.cache_dir_from_config(config)
if config.getoption("cacheclear") and cachedir.is_dir():
cls.clear_cache(cachedir)
return cls(cachedir, config)
def __init__(
self, cachedir: Path, config: Config, *, _ispytest: bool = False
) -> None:
check_ispytest(_ispytest)
self._cachedir = cachedir
self._config = config
@classmethod
def clear_cache(cls, cachedir: Path) -> None:
"""Clear the sub-directories used to hold cached directories and values."""
def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache":
"""Create the Cache instance for a Config.
:meta private:
"""
check_ispytest(_ispytest)
cachedir = cls.cache_dir_from_config(config, _ispytest=True)
if config.getoption("cacheclear") and cachedir.is_dir():
cls.clear_cache(cachedir, _ispytest=True)
return cls(cachedir, config, _ispytest=True)
@classmethod
def clear_cache(cls, cachedir: Path, _ispytest: bool = False) -> None:
"""Clear the sub-directories used to hold cached directories and values.
:meta private:
"""
check_ispytest(_ispytest)
for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES):
d = cachedir / prefix
if d.is_dir():
rm_rf(d)
@staticmethod
def cache_dir_from_config(config: Config) -> Path:
def cache_dir_from_config(config: Config, *, _ispytest: bool = False) -> Path:
"""Get the path to the cache directory for a Config.
:meta private:
"""
check_ispytest(_ispytest)
return resolve_from_str(config.getini("cache_dir"), config.rootpath)
def warn(self, fmt: str, **args: object) -> None:
def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
"""Issue a cache warning.
:meta private:
"""
check_ispytest(_ispytest)
import warnings
from _pytest.warning_types import PytestCacheWarning
@@ -151,7 +179,7 @@ class Cache:
cache_dir_exists_already = self._cachedir.exists()
path.parent.mkdir(exist_ok=True, parents=True)
except OSError:
self.warn("could not create cache path {path}", path=path)
self.warn("could not create cache path {path}", path=path, _ispytest=True)
return
if not cache_dir_exists_already:
self._ensure_supporting_files()
@@ -159,7 +187,7 @@ class Cache:
try:
f = path.open("w")
except OSError:
self.warn("cache could not write path {path}", path=path)
self.warn("cache could not write path {path}", path=path, _ispytest=True)
else:
with f:
f.write(data)
@@ -182,11 +210,11 @@ class LFPluginCollWrapper:
self.lfplugin = lfplugin
self._collected_at_least_one_failure = False
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector: nodes.Collector):
if isinstance(collector, Session):
out = yield
res = out.get_result() # type: CollectReport
res: CollectReport = out.get_result()
# Sort any lf-paths to the beginning.
lf_paths = self.lfplugin._last_failed_paths
@@ -229,11 +257,14 @@ class LFPluginCollSkipfiles:
def __init__(self, lfplugin: "LFPlugin") -> None:
self.lfplugin = lfplugin
@pytest.hookimpl
@hookimpl
def pytest_make_collect_report(
self, collector: nodes.Collector
) -> Optional[CollectReport]:
if isinstance(collector, Module):
# Packages are Modules, but _last_failed_paths only contains
# test-bearing paths and doesn't try to include the paths of their
# packages, so don't filter them.
if isinstance(collector, Module) and not isinstance(collector, Package):
if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths:
self.lfplugin._skipped_files += 1
@@ -251,11 +282,9 @@ class LFPlugin:
active_keys = "lf", "failedfirst"
self.active = any(config.getoption(key) for key in active_keys)
assert config.cache
self.lastfailed = config.cache.get(
"cache/lastfailed", {}
) # type: Dict[str, bool]
self._previously_failed_count = None # type: Optional[int]
self._report_status = None # type: Optional[str]
self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {})
self._previously_failed_count: Optional[int] = None
self._report_status: Optional[str] = None
self._skipped_files = 0 # count skipped files during collection due to --lf
if config.getoption("lf"):
@@ -290,7 +319,7 @@ class LFPlugin:
else:
self.lastfailed[report.nodeid] = True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(
self, config: Config, items: List[nodes.Item]
) -> Generator[None, None, None]:
@@ -362,15 +391,15 @@ class NFPlugin:
assert config.cache is not None
self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
@hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(
self, items: List[nodes.Item]
) -> Generator[None, None, None]:
yield
if self.active:
new_items = order_preserving_dict() # type: Dict[str, nodes.Item]
other_items = order_preserving_dict() # type: Dict[str, nodes.Item]
new_items: Dict[str, nodes.Item] = {}
other_items: Dict[str, nodes.Item] = {}
for item in items:
if item.nodeid not in self.cached_nodeids:
new_items[item.nodeid] = item
@@ -385,7 +414,7 @@ class NFPlugin:
self.cached_nodeids.update(item.nodeid for item in items)
def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True) # type: ignore[no-any-return]
def pytest_sessionfinish(self) -> None:
config = self.config
@@ -465,14 +494,14 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
return None
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_configure(config: Config) -> None:
config.cache = Cache.for_config(config)
config.cache = Cache.for_config(config, _ispytest=True)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")
@pytest.fixture
@fixture
def cache(request: FixtureRequest) -> Cache:
"""Return a cache object that can persist state between testing sessions.
@@ -500,7 +529,7 @@ def pytest_report_header(config: Config) -> Optional[str]:
displaypath = cachedir.relative_to(config.rootpath)
except ValueError:
displaypath = cachedir
return "cachedir: {}".format(displaypath)
return f"cachedir: {displaypath}"
return None
@@ -542,5 +571,5 @@ def cacheshow(config: Config, session: Session) -> int:
# print("%s/" % p.relto(basedir))
if p.is_file():
key = str(p.relative_to(basedir))
tw.line("{} is a file of length {:d}".format(key, p.stat().st_size))
tw.line(f"{key} is a file of length {p.stat().st_size:d}")
return 0

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