Compare commits

...

539 Commits
5.3.3 ... 5.4.3

Author SHA1 Message Date
pytest bot
b322004047 Preparing release version 5.4.3 2020-06-02 15:22:35 +00:00
Bruno Oliveira
2d795dc07b Merge pull request #7298 from nicoddemus/backport-6755
[5.4.x] Fix removal of very long paths on Windows (#6755)
2020-06-02 09:48:49 -03:00
Ran Benita
2d6b846978 Merge pull request #7299 from nicoddemus/backport-7294
[5.4.x] Merge pull request #7294 from nicoddemus/codecov-adjustments
2020-06-02 15:29:26 +03:00
Bruno Oliveira
703d0f50d8 Merge pull request #7294 from nicoddemus/codecov-adjustments 2020-06-02 09:02:48 -03:00
Tor Colvin
56e6482405 Fix removal of very long paths on Windows (#6755)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-06-02 08:58:01 -03:00
Bruno Oliveira
589176e9fe Merge pull request #7285 from nicoddemus/backport-7220
[5.4] Merge pull request #7220 from nicoddemus/issue-6428
2020-05-30 21:00:53 -03:00
Bruno Oliveira
3734a27733 [5.4] Do not call TestCase.tearDown for skipped tests (#7236) (#7283)
[5.4] Do not call TestCase.tearDown for skipped tests (#7236)
2020-05-30 21:00:08 -03:00
Bruno Oliveira
e1a21e46b0 Merge pull request #7220 from nicoddemus/issue-6428 2020-05-30 20:14:57 -03:00
Bruno Oliveira
551400e8d6 Do not call TestCase.tearDown for skipped tests (#7236)
Fix #7215
2020-05-30 14:34:45 -03:00
Anthony Sottile
b7b729298c Merge pull request #7271 from asottile/backport-7257
[5.4.x] Merge pull request #7257 from DahlitzFlorian/fix-issue-6956
2020-05-27 15:02:10 -07:00
Anthony Sottile
21ca38b932 Merge pull request #7257 from DahlitzFlorian/fix-issue-6956
Prevent pytest from printing ConftestImportFailure traceback

(cherry picked from commit b3db440d4c)
2020-05-27 13:52:10 -07:00
Anthony Sottile
565f4cb4ad Merge pull request #7248 from asottile/backport-7244
[5.4.x] Merge pull request #7244 from DahlitzFlorian/fix-issue-7150
2020-05-23 12:45:35 -07:00
Anthony Sottile
af6548a4e7 Merge pull request #7244 from DahlitzFlorian/fix-issue-7150
Prevent hiding underlying exception when ConfTestImportFailure is raised

(cherry picked from commit 45f53266e6)
2020-05-23 12:13:34 -07:00
Bruno Oliveira
2fb2962df4 Merge pull request #7193 from pytest-dev/release-5.4.2 2020-05-08 09:01:11 -03:00
pytest bot
f838c7b7eb Preparing release version 5.4.2 2020-05-08 11:14:08 +00:00
Bruno Oliveira
25b53c4196 Merge pull request #7188 from asottile/backport_7179 2020-05-08 06:58:08 -03:00
Bruno Oliveira
fc27171d57 Merge pull request #7189 from asottile/backport-7186 2020-05-08 06:57:25 -03:00
Anthony Sottile
d18e426b24 Merge pull request #7186 from asottile/is_setup_py_encoding_agnostic
Fix _is_setup_py for files encoded differently than locale
2020-05-07 14:02:00 -07:00
Bruno Oliveira
e83fa48dd1 Merge pull request #7179 from asottile/py39 2020-05-07 13:57:10 -07:00
Bruno Oliveira
c53d52c3f2 Merge pull request #7174 from nicoddemus/backport-7168 2020-05-06 18:24:43 -03:00
Bruno Oliveira
3886c6d735 Merge pull request #7168 from nicoddemus/saferepr-getattr-fail-7145 2020-05-06 17:59:42 -03:00
Bruno Oliveira
80936b6762 Merge pull request #7156 from nicoddemus/backport-7151 2020-05-02 16:01:15 -03:00
Bruno Oliveira
5ca08e9b8a Merge pull request #7151 from nicoddemus/unittest-cleanup-6947 2020-05-02 15:44:39 -03:00
Bruno Oliveira
ba2c49e71e Merge pull request #7149 from nicoddemus/backport-7144 2020-05-01 17:08:55 -03:00
Bruno Oliveira
da615a4fbe Merge pull request #7144 from nicoddemus/async-testcase-7110 2020-05-01 16:45:01 -03:00
Bruno Oliveira
32562881d4 Merge pull request #7146 from nicoddemus/backport-7143 2020-05-01 13:31:24 -03:00
Bruno Oliveira
2a0bbfe63f Merge pull request #7143 from nicoddemus/file-from-parent 2020-05-01 13:08:42 -03:00
Ronny Pfannschmidt
9aed656ec7 Merge pull request #7066 from RonnyPfannschmidt/backport-6992-restore-tmpdir-indirection
backport #6992 to 5.4.x - restores tmpdir indirection
2020-04-10 13:11:17 +02:00
Ronny Pfannschmidt
a600e7a2a4 Merge pull request #7068 from RonnyPfannschmidt/backport-6927-run-async-unittest
backport #6927: run async testcase methods
2020-04-10 13:11:05 +02:00
Ronny Pfannschmidt
57a95b3a2c Merge pull request #7067 from RonnyPfannschmidt/backport-6986-terminalreporter.writer
backport #6986: writable TerminalWriter.writer for deprecation
2020-04-10 11:25:42 +02:00
Ronny Pfannschmidt
f5e430fd8f Merge pull request #6927 from RonnyPfannschmidt/fix-6924-run-async-stdlib-unittests
running stdlib asyncio unittests again
2020-04-10 08:16:58 +02:00
Ronny Pfannschmidt
38a4c7e56c Merge pull request #6986 from RonnyPfannschmidt/fix-6951-tw.writer-writable
fix #6951: allow to write TerminalReporter.writer
2020-04-10 08:12:34 +02:00
Ronny Pfannschmidt
40f02d72b0 Merge pull request #6992 from pytest-dev/revert-6767-tmpdir-cleanup-upstream
Revert "tmpdir: clean up indirection via config for factories"
2020-04-10 08:07:53 +02:00
Daniel Hahler
554f600fb4 Fix TerminalRepr instances to be hashable (#6988) (#7006)
pytest-xdist assumes `ExceptionChainRepr` is hashable.

Fixes https://github.com/pytest-dev/pytest/issues/6925.
Fixes https://github.com/pytest-dev/pytest-xdist/issues/515.

(cherry picked from commit 20f6331afd)
2020-04-03 10:59:23 +02:00
Ran Benita
e3a3c90d94 [5.4.x] Fix crash when printing while capsysbinary is active (#7002)
Backport of 1fda861190 from master.
2020-04-02 14:31:32 +03:00
Ran Benita
f7327759e8 ci: twisted and oldattrs tox envs are now incompatible, don't run them together
twisted started to use `attr.s(eq)` argument which was added recently,
so it fails with oldattrs. One of the CI jobs ran twisted and oldattrs
together, so it started to fail.

Move the twisted code to be covered by another job, and remove it from
the job with the oldattrs.

(cherry picked from commit 2cc3227f6a)
2020-03-31 09:48:11 +02:00
Bruno Oliveira
ee1950af77 Merge pull request #6918 from pytest-dev/release-5.4.1
Prepare release 5.4.1
2020-03-13 11:11:05 -03:00
pytest bot
3d0f3baa2b Preparing release version 5.4.1 2020-03-13 13:34:24 +00:00
Bruno Oliveira
8da758b93a Merge pull request #6917 from nicoddemus/backport-6916
[Backport] Skip link checks when doing releases through the bot (#6916)
2020-03-13 10:34:10 -03:00
Bruno Oliveira
59e5d1bfbf Merge pull request #6916 from nicoddemus/no-link-checks
Skip link checks when doing releases through the bot
2020-03-13 10:32:25 -03:00
Bruno Oliveira
b9e2cd0a81 Merge pull request #6914 from nicoddemus/revert-6330
Revert "[parametrize] enforce explicit argnames declaration (#6330)"
2020-03-13 10:18:06 -03:00
Bruno Oliveira
a84fcbf5b2 Revert "[parametrize] enforce explicit argnames declaration (#6330)"
This reverts commit 9e262038c8.

Fix #6909
2020-03-13 09:59:53 -03:00
Bruno Oliveira
59c1bfada7 Merge pull request #6913 from nicoddemus/backport-6910
[Backport #6910] Handle unknown stats in pytest_report_teststatus hook
2020-03-13 09:48:11 -03:00
Bruno Oliveira
3267f64724 Merge pull request #6910 from nicoddemus/resultlog-logreport
Handle unknown stats in pytest_report_teststatus hook
2020-03-13 09:30:14 -03:00
pytest bot
c9fd1bdbd6 Preparing release version 5.4.0 2020-03-12 14:14:35 +00:00
Ronny Pfannschmidt
93aa988e01 Merge pull request #6901 from RonnyPfannschmidt/regendoc-fix-simple
run and fix tox -e regen to prepare 5.4
2020-03-12 15:09:10 +01:00
Ran Benita
7996724f23 Merge pull request #6902 from RoyalTS/filterwarnings-docfix
Minor docfix
2020-03-12 12:39:33 +02:00
Tobias Schmidt
90ee8a7599 docfix 2020-03-12 09:38:00 +01:00
Ronny Pfannschmidt
378a75ddf6 run and fix tox -e regen to prepare 5.4
* no longer trigger deprecation warnings when looking up fixtures
* fix missed imports in a test example
2020-03-11 22:25:51 +01:00
Bruno Oliveira
e1b3a68462 Merge pull request #6896 from nicoddemus/release-trigger
Fix bot trigger event
2020-03-11 10:04:41 -03:00
Ronny Pfannschmidt
fb7dbc9fa3 Merge pull request #6893 from RonnyPfannschmidt/url-fixes
fix urls that fail linkcheck
2020-03-11 14:02:46 +01:00
Bruno Oliveira
a0ea300e96 Fix bot trigger event
Issue events don't contain a 'comment' entry:
https://developer.github.com/v3/issues/events/#response-2

Issue comments also contain the original issue body:
https://developer.github.com/v3/activity/events/types/#issuecommentevent

That's why it was triggering even for comments on the issue.

Also changed to startsWith because there's no need to support
the command anywhere in the body IMO.

Fix #6895
2020-03-11 09:54:56 -03:00
Ronny Pfannschmidt
09b289e286 fix mozilla qa project link 2020-03-11 11:10:33 +01:00
Ronny Pfannschmidt
694dbe5bd4 fix pydanny.com url to the one with ssl set up 2020-03-11 10:52:16 +01:00
Ran Benita
4f8fff9cab Merge pull request #6868 from bluetech/simplify-exc
Simplify some exception handling code
2020-03-10 20:32:08 +02:00
Daniel Hahler
ac7ebfa22e doc: internal: fix MultiCapture.readouterr (#6878)
Remove wrong docstring: it might actually return bytes.
Replace it with a type annotation which is clear enough.
2020-03-08 12:38:21 +01:00
Daniel Hahler
db92cea14c black: --target-version py35 (#6872) 2020-03-08 01:24:31 +01:00
Daniel Hahler
bce1d40fb0 tests: harden test_reprcompare_notin, factor out callop (#6764)
* tests: assertion: factor out `callop`, typing

* tests: harden test_reprcompare_notin
2020-03-08 01:23:19 +01:00
Daniel Hahler
dc86fb6758 pre-commit run --all-files 2020-03-07 23:47:55 +01:00
Daniel Hahler
2df4f63149 Add config for black (target-version) 2020-03-07 23:47:55 +01:00
Daniel Hahler
e3cf4fc258 doc/en/Makefile: use Sphinx's make mode (#6873)
Syncs it from the output of current sphinx-quickstart.
2020-03-07 23:45:05 +01:00
Ran Benita
978b315861 Simplify some exception handling code
Mostly avoid unnecessary usage of sys.exc_info(). Since Python3,
exception objects themselves have all that's needed. They are also
easier to type.
2020-03-07 13:18:54 +02:00
Ran Benita
580edc13e7 Merge pull request #6867 from bluetech/inline-pyobjmixin
Inline PyobjContext
2020-03-07 11:58:38 +02:00
Ran Benita
1df593f978 Merge pull request #6865 from bluetech/more-config-store
Convert a couple of places to use config store
2020-03-06 15:49:01 +02:00
Ran Benita
b7f2e3d4f5 Inline PyobjContext
When it was introduced in 8adac2878f it
seems to have had some use, but now it doesn't.
2020-03-06 14:55:20 +02:00
Ran Benita
f011bc642c Store mark's evalcache in config's store instead of attribute
Part of moving away from ad-hoc attributes to using the config's store.
2020-03-06 11:42:20 +02:00
Ran Benita
b1d7a187f2 Add setdefault() method to Store
Can be useful in some cases.
2020-03-06 11:42:20 +02:00
Ran Benita
678d65f051 Store AssertionState in config's store instead of attribute
Part of moving away from ad-hoc attributes to using the config's store.
2020-03-06 11:27:01 +02:00
Daniel Hahler
fcd3fad03d Minor internal improvements to logging's log_level (#6849) 2020-03-06 03:12:57 +01:00
Daniel Hahler
9e8540f25f docs: remove doc/en/links.inc (#6853) 2020-03-06 03:11:24 +01:00
Bruno Oliveira
19bb2c6235 Merge pull request #6859 from nicoddemus/kw-mapping-attr-typing
Use attrs in KeywordMapping
2020-03-05 08:47:22 -03:00
Bruno Oliveira
bc8e52c3c2 Use attrs in KeywordMapping
Also added type hinting.
2020-03-05 08:33:20 -03:00
Bruno Oliveira
333bb0883a Merge pull request #6860 from pytest-dev/pytest_twisted_in_async_warning
Add pytest-twisted to list of async def handling plugins
2020-03-04 21:48:20 -03:00
Kyle Altendorf
dce2621710 Add pytest-twisted to list of async def handling plugins
https://github.com/pytest-dev/pytest-twisted/tree/v1.12#ensuredeferred

[pytest-twisted](https://github.com/pytest-dev/pytest-twisted) supports `async def` test functions and fixtures as well as `async def`/`yield` fixtures.
2020-03-04 17:42:08 -05:00
Daniel Hahler
acec0b688f Fix usage of pytester with doctests (#6802)
Use `request.node.name` instead of `request.function.__name__`:
`request.function` is `None` with `DoctestItem`s.
2020-03-04 05:33:50 +01:00
Daniel Hahler
197b7c3bce Add .gitblameignore (#6848) 2020-03-04 05:32:30 +01:00
Daniel Hahler
37d074efc8 Fix (internal) documentation for pytester's LineComp (#6850) 2020-03-04 05:25:32 +01:00
Ronny Pfannschmidt
a5a8d53dfe remove myself from the list of coc handlers (#6857) 2020-03-03 19:08:59 -03:00
Ronny Pfannschmidt
9fd71d6fe0 fix #571: deprecate pytest_collect_directory as ... (#6847)
Deprecate pytest_collect_directory

Fix #571

Co-authored-by: Daniel Hahler <github@thequod.de>
2020-03-03 18:58:14 -03:00
Daniel Hahler
b11bfa106c Use attrs with all Repr classes (#6739)
Co-authored-by: Ran Benita <ran234@gmail.com>
2020-03-03 21:53:28 +01:00
Daniel Hahler
bd7e33277b docs: remove tracking through Google Analytics (#6843)
This might not even be monitored by anyone, but if it is useful in
general should probably get replaced with a less controversial provider.
2020-03-03 01:35:14 +01:00
Daniel Hahler
ddc8edffbc logging: minor typing, rename function
`get_log_level_for_setting` reflects better what the function does, and
it does not require a (trivial) docstring.
2020-03-01 22:02:46 +01:00
Daniel Hahler
bdd22fdd52 logging: improve help for log_level 2020-03-01 22:02:46 +01:00
Ronny Pfannschmidt
194b52145b Merge pull request #6834 from RonnyPfannschmidt/fix-6833-summarize-warning-item-locations
summarize warning summaries if the number of locations is high
2020-03-01 20:21:36 +01:00
Bruno Oliveira
15e1dd0f87 Automate release by comment notifications (#6823)
* Automate release by comment notifications

* Only run if @pytestbot is mentioned
2020-03-01 14:46:35 -03:00
Ronny Pfannschmidt
23c43a37e0 summarize warning summaries if the number of locations is high 2020-03-01 14:08:15 +01:00
Bruno Oliveira
92767fec51 Merge pull request #6836 from bluetech/store
Add a typing-compatible mechanism for ad-hoc attributes on various objects
2020-03-01 09:30:10 -03:00
Ran Benita
5fc80d8bc3 Merge pull request #6844 from bluetech/asserthook-abc-loader
Mark AssertionRewritingHook as implementing importlib.abc.Loader
2020-02-29 23:21:11 +02:00
Ran Benita
2f60548e08 Mark AssertionRewritingHook as implementing importlib.abc.Loader
It implements the required methods. This is useful for typing but also a
nice indicator.
2020-02-29 16:55:56 +02:00
Daniel Hahler
f10ab021e2 Move _collectfile to FSCollector (#6830)
Previously this was implemented both on `Session` and `Package`, where
the extra code in `Package._collectfile` was not covered/used.

Ref: https://github.com/pytest-dev/pytest/pull/6830#issuecomment-592663236
2020-02-29 11:40:11 +01:00
Bruno Oliveira
ff7b5dbbde Merge pull request #6784 from nicoddemus/deprecate-terminal-writer
Deprecate TerminalReporter.writer
2020-02-28 21:07:02 -03:00
Daniel Hahler
4b53bbc0a9 docs: use doc/en/_build/html for html output (#6842)
This will use the same directory as `make -C doc/en` then, and reflects
the default location that Sphinx uses.
2020-02-29 00:47:30 +01:00
Daniel Hahler
769ffc32bf Add Read The Docs badge to README (#6841) 2020-02-29 00:29:56 +01:00
Daniel Hahler
5819536f00 Improve UsageError with invalid -o style (#6795)
This started from fixing the test, where `"xdist_strict True"` was used
as a single argument, although you typically would see `["xdist_strict",
"True"]`.

Improves the error message to mention the option that caused the error.
2020-02-28 19:41:56 +01:00
Daniel Hahler
952cab2d85 Simplify test_rootdir_wrong_option_arg (#6812) 2020-02-28 18:38:07 +01:00
Ran Benita
d636fcd557 Add a typing-compatible mechanism for ad-hoc attributes on various objects
pytest has several instances where plugins set their own attributes on
objects they receive in hooks, like nodes and config. Since plugins are
detached from these object's definition by design, this causes a problem
for type checking because these attributes are not defined and mypy
complains.

Fix this by giving these objects a "store" which can be used by plugins
in a type-safe manner.

Currently this mechanism is private. We can consider exposing it at a
later point.
2020-02-28 14:34:44 +02:00
Daniel Hahler
f77d606d4e docs: generate index and add it to globaltoc sidebar (#6808) 2020-02-27 00:34:18 +01:00
Daniel Hahler
16c683dff9 Remove unused setns (#6804)
Last usage of it was removed in ba17363d7.
2020-02-26 15:35:14 +01:00
Daniel Hahler
2e48c32dea docs: fix reference to config.cache fixture (#6807) 2020-02-26 01:24:18 +01:00
Stefano Mazzucco
2451716746 Remove reference to old konira plugin from docs (#6819)
The pytest-konira plugin has not seen an update since 2011, moreover
the "project description" on PyPI points to a dubious website that
does not actually correspond to the project and instead redirects to
advertising content.
2020-02-26 01:19:40 +01:00
Daniel Hahler
6a7df7f031 test_assertion: harden/improve test_{text_diff,unicode} (#6806) 2020-02-24 15:19:08 +01:00
Daniel Hahler
ac3a42bafd doctest: strip newlines with unexpected exceptions (#6801) 2020-02-24 15:18:08 +01:00
Daniel Hahler
be23aeb989 Remove unused pytester.getdecoded (#6805)
Last usage was removed in 22dc47d9f.
2020-02-24 14:33:26 +01:00
Daniel Hahler
bfd0d18371 tests: test_config: move tests (#6796)
Originally added in a2891420d (with `TestRootdir`), but then
accidentally moved to `TestOverrideIniArgs` in 802755cee.
2020-02-23 17:12:55 +01:00
Daniel Hahler
9928c7794b minor: fix trace msg: s/loaded/loading (#6798) 2020-02-23 17:12:30 +01:00
Daniel Hahler
706ea86bba capture: factor out _get_multicapture (#6788)
Ref: https://github.com/pytest-dev/pytest/pull/6671#issuecomment-588408992
2020-02-22 23:39:20 +01:00
Daniel Hahler
1d5a0ef284 Fix pytest_ignore_collect hooks: do not return False (#6778)
It should only return `True` when something is to be ignored, not
`False` otherwise typically.

This caused e.g. bad interaction with the cacheprovider (before
https://github.com/pytest-dev/pytest/pull/6448).
2020-02-22 23:35:47 +01:00
Daniel Hahler
c8b4a1a471 Handle match with pytest.raises() (#6753)
Fixes https://github.com/pytest-dev/pytest/issues/6752.
2020-02-22 23:32:55 +01:00
Daniel Hahler
68fe0eb8f3 Minor cosmetic fixes (#6792) 2020-02-22 19:24:41 +01:00
Daniel Hahler
de854c6ee1 doc: pytest.raises: remove mention of removed message (#6793)
Follow-up to 13f7f27fd.
2020-02-22 19:24:00 +01:00
Daniel Hahler
04126feea7 doc: pytest.raises: link to python:try (#6794) 2020-02-22 19:23:35 +01:00
kpinc
eeebcd77dd doc: add list of fixtures to start of fixture chapter (#6696)
- Add list of fixtures to start of fixture chapter
- Add "fixture" cross ref type
2020-02-22 14:56:19 +01:00
Daniel Hahler
478a244f5e main: args must be a list, not tuple (#6791)
Passing in a tuple crashes in `_prepareconfig`:

        def test_invoke_with_tuple(self):
    >       pytest.main(("-h",))

    src/_pytest/config/__init__.py:82: in main
        config = _prepareconfig(args, plugins)
    src/_pytest/config/__init__.py:229: in _prepareconfig
        return pluginmanager.hook.pytest_cmdline_parse(
    …
    src/_pytest/helpconfig.py:98: in pytest_cmdline_parse
        config = outcome.get_result()  # type: Config
    src/_pytest/config/__init__.py:808: in pytest_cmdline_parse
        self.parse(args)
    src/_pytest/config/__init__.py:1017: in parse
        self._preparse(args, addopts=addopts)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

        def _preparse(self, args: List[str], addopts: bool = True) -> None:
            …
            if addopts:
                ini_addopts = self.getini("addopts")
                if ini_addopts:
    >               args[:] = self._validate_args(ini_addopts, "via addopts config") + args
    E               TypeError: can only concatenate list (not "tuple") to list

    addopts    = True
    args       = ('-h',)
    env_addopts = ''
    ini_addopts = ['-rfEX', …]

    src/_pytest/config/__init__.py:956: TypeError: can only concatenate list (not "tuple") to list

Might be worth handling (converting it to a list for example), but it
was documented to be a list to begin with when removing support for
strings (a7e401656).
2020-02-22 13:16:46 +01:00
Daniel Hahler
47ccd58fb4 Fix typo in doc with syspath_prepend 2020-02-22 13:15:17 +01:00
Daniel Hahler
2277817176 Fix grammar in changelog/6737.breaking.rst 2020-02-22 13:15:17 +01:00
Daniel Hahler
1baeefc2fd test_reprcompare_whitespaces: use callequal 2020-02-22 13:15:17 +01:00
Daniel Hahler
260f848c05 minor: remove outdated, superfluous comment 2020-02-22 13:15:17 +01:00
Bruno Oliveira
7c0d1cad40 Merge pull request #6790 from TWood67/master
Updated Dynamic Scopes Documentation
2020-02-21 16:47:22 -03:00
TWood67
b26e60c2da updated documentation
added a default value so a runtime exception does not occur
2020-02-21 11:45:24 -06:00
Daniel Hahler
2be06ba67e Improve doc/typing/message for ExceptionInfo.match (#6776) 2020-02-21 16:41:57 +01:00
Daniel Hahler
8e991a622c tests: harden/improve test_itemreport_subclasses_show_subclassed_file (#6467)
* tests: harden test_itemreport_subclasses_show_subclassed_file

* extend test_itemreport_subclasses_show_subclassed_file
2020-02-21 15:24:12 +01:00
Daniel Hahler
b099fcfa33 tests: runner: improve/ignore coverage (#6781) 2020-02-21 14:37:56 +01:00
Daniel Hahler
4c9b850e13 help: --deselect: mention that this uses node id prefixes (#6770)
Ref: https://github.com/pytest-dev/pytest/issues/6751
2020-02-21 12:53:27 +01:00
Daniel Hahler
81a9df6ed1 tests: fix flaky test_timeout (#6773)
Use a longer timeout on CI - seen a failure with it with GHA (Windows).

Ref: a4554e666a
2020-02-21 12:52:31 +01:00
Daniel Hahler
58ef95ed4d ci: fix Travis (#236) (#6786) 2020-02-21 12:50:54 +01:00
Bruno Oliveira
435ad221f9 Deprecate TerminalReporter.writer
Fix #6779
2020-02-20 21:04:49 -03:00
Daniel Hahler
d1b50526fa tests: harden some UsageError tests (matching the error msg) (#6775) 2020-02-20 13:29:59 +01:00
Daniel Hahler
4d633a29be cacheprovider: fix typing with Path(py.path.local) (#6774)
Fixes:

> Argument 1 to "Path" has incompatible type "Union[local, Any]";
> expected "Union[str, _PathLike[str]]"  [arg-type]

Ref: https://github.com/pytest-dev/py/pull/232#pullrequestreview-349549522
2020-02-20 13:23:41 +01:00
Daniel Hahler
8a1633c3b4 tmpdir: clean up indirection via config for factories (#6767)
Remove `_tmp_path_factory` and `_tmpdirhandler` from the config object.

- `_tmpdirhandler` has been deprecated since 2.8.0 (0f52856f9), when
  `tmpdir_factory` has been added.
- `_tmp_path_factory` should have probably never been added there in the
  first place, but maybe just used the same pattern (16e2737da).
2020-02-20 12:48:33 +01:00
Daniel Hahler
82f5986424 capture: re-order classes (#6768)
This better reflects the inheritance / smartness with regard to raw or
encoded.

- FDCaptureBinary
- FDCapture
- SysCaptureBinary
- SysCapture
- TeeSysCapture
2020-02-20 11:00:19 +01:00
Daniel Hahler
fb16d3e27a capture: revisit/fix __repr__, define _in_suspended (#6749) 2020-02-20 00:51:57 +01:00
Daniel Hahler
2b13a9b95d Use TYPE_CHECKING (#6771) 2020-02-19 22:07:54 +01:00
Daniel Hahler
1b30514783 LFPlugin: use sub-plugins to deselect during collection (#6448)
Fixes https://github.com/pytest-dev/pytest/issues/5301.

Refactor/steps:

- use var
- harden test_lastfailed_usecase
- harden test_failedfirst_order
- revisit last_failed_paths
- harden test_lastfailed_with_known_failures_not_being_selected
2020-02-19 21:33:03 +01:00
Ran Benita
af2b0e1174 Merge pull request #6758 from bluetech/outcome-exception-callable-2
Use a hack to make typing of pytest.fail.Exception & co work
2020-02-19 22:08:21 +02:00
Daniel Hahler
781a730bea terminal: remove confusing comment about _tw (#6763)
Ref: https://github.com/blueyed/pytest/commit/cf0cac3b7#r37373455
2020-02-19 20:35:50 +01:00
Nathaniel Compton
7c09d88b72 #6289: Add new example to XFAIL documentation (#6685)
Fix #6289

Co-authored-by: Ran Benita <ran234@gmail.com>
2020-02-19 15:04:37 -03:00
Daniel Hahler
4021770688 pytester: LineMatcher: use _fail with _match_lines_random (#6747) 2020-02-19 13:16:57 +01:00
Daniel Hahler
f95c7f5803 doctest: handle any OutcomeException (#6669)
Fixes using `pytest.xfail()` and `pytest.importorskip()` in doctests.

Ref: https://github.com/pytest-dev/pytest/issues/310
2020-02-19 13:16:37 +01:00
Ran Benita
24dcc76495 Use a hack to make typing of pytest.fail.Exception & co work
Mypy currently is unable to handle assigning attributes on function:
https://github.com/python/mypy/issues/2087.
pytest uses this for the outcome exceptions -- `pytest.fail.Exception`,
`pytest.exit.Exception` etc, and this is the canonical name by which they
are referred.

Initially we started working around this with type: ignores, and later
by switching e.g. `pytest.fail.Exception` with the direct exception
`Failed`. But this causes a lot of churn and is not as nice. And I also
found that some code relies on it, in skipping.py:

    def pytest_configure(config):
        if config.option.runxfail:
            # yay a hack
            import pytest

            old = pytest.xfail
            config._cleanup.append(lambda: setattr(pytest, "xfail", old))

            def nop(*args, **kwargs):
                pass

            nop.Exception = xfail.Exception
            setattr(pytest, "xfail", nop)
        ...

So it seems better to support it. Use a hack to make it work. The rest
of the commit rolls back all of the workarounds we added up to now.

`pytest.raises.Exception` also exists, but it's not used much so I kept
it as-is for now.

Hopefully in the future mypy supports this and this ugliness can be
removed.
2020-02-18 23:17:27 +02:00
Daniel Hahler
442f7a7706 Do not use fixed line number with test_cache_failure_warns (#6748)
It was not previously checking for the line number also (02aa8ad), and this is
obviously wrong (affected by changes to the file).
2020-02-18 09:27:33 +01:00
Daniel Hahler
d18c75baa3 doc: fix test with link to python_files (#6741)
Follow-up to b09762d (#6705).

Ref: https://github.com/pytest-dev/pytest/pull/6705#discussion_r379819573
2020-02-16 10:30:25 +01:00
Daniel Hahler
bc976dca3b pytester: add __tracebackhide__ for matching randomly (#6746) 2020-02-16 10:29:59 +01:00
Ran Benita
7b8968ff80 Merge pull request #6735 from bluetech/metafunc-annotate
Type annotate Metafunc
2020-02-15 23:32:14 +02:00
Ran Benita
0c68e7a2c9 Merge pull request #6744 from bluetech/item-runtest-stub
Add Item.runtest stub implementation
2020-02-15 23:27:57 +02:00
Daniel Hahler
369284752e terminal: refactor, no yellow ("boring") for non-last item (#6409) 2020-02-15 19:00:24 +01:00
Daniel Hahler
e872532d0c Merge branch 'master' into term-color
Conflicts:
	src/_pytest/terminal.py
	testing/test_debugging.py
	testing/test_terminal.py
2020-02-15 18:46:29 +01:00
kpinc
9785ee438d doc: expand first sentence on fixtures into a paragraph (#6742) 2020-02-15 18:35:36 +01:00
Ran Benita
959e6b4f44 Merge pull request #6743 from bluetech/runtest-getattr
Avoid getattr when dispatching pytest_runtest_* hooks
2020-02-15 17:22:00 +02:00
Ran Benita
5945c3fe88 Type annotate Metafunc 2020-02-15 17:13:18 +02:00
Ran Benita
7155b2277c Ignore "assert False" statements in coverage 2020-02-15 17:08:01 +02:00
Ran Benita
a7a1686433 Add Item.runtest stub implementation
Every Item must implement this method (called on all items collected in
a session). Add a stub for typing and clarity.
2020-02-15 16:58:55 +02:00
Ran Benita
371939fb86 Avoid getattr when dispatching pytest_runtest_* hooks
Using getattr doesn't work with typing, and also breaks grep. It took me
a while to find where these hooks are called.
2020-02-15 15:34:21 +02:00
Daniel Hahler
7fc9d4c976 Use warnings module directly with cacheprovider (#6740) 2020-02-15 05:31:18 +01:00
Daniel Hahler
2b5adc88a7 Move test_issue4445_cacheprovider_set into test_cache_failure_warns
Would need to be adjusted anyway non-trivially, and we can just harden
`test_cache_failure_warns` instead.
2020-02-15 02:01:22 +01:00
Daniel Hahler
02aa8adae1 cacheprovider: use warnings directly
Allows for filtering of PytestCacheWarning.

Using `_issue_warning_captured` is not necessary here, and was probably
only used because the cacheprovider misses warnings during
`pytest_sessionfinish`, which is also fixed here.

I think the usage of `_issue_warning_captured` can be removed/reduced
further, but also that this is good enough for now.

Ref: https://github.com/pytest-dev/pytest/issues/6681.
2020-02-15 01:36:57 +01:00
Daniel Hahler
67e69a7e49 tests: harden test_xdist_verbose (#6700) 2020-02-15 01:22:01 +01:00
Daniel Hahler
b09762df27 doc: link to python_files from "Assertion Rewriting" (#6705)
Ref: https://github.com/pytest-dev/pytest/issues/6377
Ref: https://github.com/blueyed/pytest/pull/145
2020-02-15 00:32:16 +01:00
Daniel Hahler
9631b3c166 reports: use attr.asdict with serialize_repr_{crash,traceback} (#6732)
* Turn ReprTraceback into attrs class

* Use attr.asdict with serialize_repr_{crash,traceback}

* Turn ReprFileLocation into attrs class, convert py.path.local
2020-02-14 19:39:34 +01:00
Daniel Hahler
4b70ba2c21 tests: harden test_better_reporting_on_conftest_load_failure (#6713) 2020-02-14 17:00:01 +01:00
Daniel Hahler
de3353aac1 test_load_initial_conftest_last_ordering: handle testing package (#6706) 2020-02-14 16:58:17 +01:00
Bruno Oliveira
ef73a56032 Merge pull request #6737 from bluetech/fixture-cached-result
Don't delete FixtureDef.cached_result, set it to None instead
2020-02-14 12:33:03 -03:00
Ran Benita
d839686c7b Don't delete FixtureDef.cached_result, set it to None instead
Previously `cached_result` was either set or deleted. Type annotations
cannot handle this, so use `None` for the non-set state instead.
2020-02-14 14:18:58 +02:00
Daniel Hahler
d89b5057ca assertrepr_compare: provide more info (location) with exceptions (#6728) 2020-02-14 02:17:05 +01:00
Daniel Hahler
83137c89e9 tests: test_unicode_plus_minus: use unicode sign directly (#6727)
Was globbed for Python 2 before (57c448991).
2020-02-14 02:16:25 +01:00
Daniel Hahler
dbae5a7ff8 Recognize -V as a short option for --version (#6721) 2020-02-13 12:30:22 +01:00
Daniel Hahler
07b7b6fa7d doc: add docstring for CaptureManager._capturing_for_request (#6698)
Based on the removed doc for `_install_capture_fixture_on_item`.

Follow-up to https://github.com/pytest-dev/pytest/pull/6663.

Co-authored-by: Ran Benita <ran234@gmail.com>
2020-02-13 12:09:32 +01:00
Ran Benita
56a5dbe252 Merge pull request #6722 from bluetech/rm-callspec2-global
Remove unused CallSpec2 fields _globalid, _globalparam
2020-02-13 12:49:41 +02:00
Ran Benita
8a4d5227e2 Remove unused CallSpec2 fields _globalid, _globalparam 2020-02-13 12:31:48 +02:00
Bruno Oliveira
e6ea9edffe Merge pull request #6673 from sscherfke/features
Reverse / fix meaning of "+/-" in error diffs
2020-02-12 15:05:38 -03:00
Daniel Hahler
b7ad4c2bed _pformat_dispatch: pass through args (#6715) 2020-02-12 16:07:57 +01:00
Bruno Oliveira
d79179a239 Merge pull request #6716 from bluetech/features-to-master-for-real
Merge the features branch into master, before stopping to use it
2020-02-12 11:52:32 -03:00
Ran Benita
c9f9664336 Merge branch 'features' into master
The features branch is no more. Development of features is now also done
on master.

See https://github.com/pytest-dev/pytest/pull/6571.
2020-02-12 13:50:02 +02:00
Bruno Oliveira
aa4d80cad9 RFC: Streamlining pytest's git workflow (#6571)
RFC: Streamlining pytest's git workflow
2020-02-12 08:37:14 -03:00
Bruno Oliveira
4209ad6fca Use code highlighting if pygments is installed (#6658)
* Use code highlighting if pygments is installed

* Use colorama constants instead of bare ascii codes

Could not find the exact equivalent of 'hl-reset' code using colorama
constants though.

* Refactor ASCII color handling into a fixture

* Revert back to using explicit color codes

* In Python 3.5 skip rest of tests that require ordered markup in colored output
2020-02-12 08:32:37 -03:00
Bruno Oliveira
3ea74310d7 Fix crash when faulthandler starts initialized (#6598)
Use suggestion in review and use a subplugin so hooks will only be active
if we enable faulthandler ourselves.

Fix #6575

Co-authored-by: Daniel Hahler <git@thequod.de>
2020-02-12 11:06:20 +01:00
Ran Benita
c3e53a072d Switch to new git workflow
Co-Authored-By: Daniel Hahler <git@thequod.de>
2020-02-12 09:49:21 +02:00
Daniel Hahler
fa877665ad _set_main_color: does not need to return 2020-02-12 03:11:54 +01:00
Bruno Oliveira
3b582858f3 Merge pull request #6712 from nicoddemus/mm
Merge master into features
2020-02-11 20:22:20 -03:00
Bruno Oliveira
78baa7b575 Merge remote-tracking branch 'upstream/master' into mm
Conflicts:
	src/_pytest/main.py
	src/_pytest/mark/structures.py
	src/_pytest/python.py
	testing/test_main.py
	testing/test_parseopt.py
2020-02-11 19:22:28 -03:00
Ran Benita
7484e346f9 Merge pull request #6711 from bluetech/mv-exitcode
Move ExitCode's definition from _pytest.main to _pytest.config
2020-02-11 09:49:38 +02:00
Ran Benita
d25123eb01 Merge pull request #6710 from bluetech/rm-nodemarkers
Remove unused NodeMarkers
2020-02-10 23:57:40 +02:00
Ran Benita
d33da078a8 Move ExitCode's definition from _pytest.main to _pytest.config
ExitCode is used in several internal modules and hooks and so with type
annotations added, needs to be imported a lot.

_pytest.main, being the entry point, generally sits at the top of the
import tree.

So, it's not great to have ExitCode defined in _pytest.main, because it
will cause a lot of import cycles once type annotations are added (in
fact there is already one, which this change removes).

Move it to _pytest.config instead.

_pytest.main still imports ExitCode, so importing from there still
works, although external users should really be importing from `pytest`.
2020-02-10 23:55:06 +02:00
Stefan Scherfke
d59adc61f9 Reverse / fix meaning of "+/-" in error diffs
The convention is "assert result is expected".  Pytest's error diffs now
reflect this. "-" means that sth. expected is missing in the result and
"+" means that there are unexpected extras in the result.

Fixes: #3333
2020-02-10 21:12:37 +01:00
Ran Benita
7cc513b2af Remove unused NodeMarkers
This class was both added and became unused during the development of a
PR:
https://github.com/pytest-dev/pytest/pull/3317
2020-02-10 22:05:25 +02:00
Daniel Hahler
0ee007ca33 Fix CI (Travis) (#6704) 2020-02-10 20:52:21 +01:00
Ran Benita
1ebca37689 Merge pull request #6708 from bluetech/redundant-commas-2
Remove some redundant commas
2020-02-10 21:26:55 +02:00
Ran Benita
b59d32a5c7 Merge pull request #6709 from bluetech/rm-arg2finish
Remove unused field FixtureManager._arg2finish
2020-02-10 21:26:30 +02:00
Ran Benita
3e4e6297ce Remove unused field FixtureManager._arg2finish
Not used since 4f0879ff9b.
2020-02-10 17:30:20 +02:00
Ran Benita
0b2b40e35d Remove some redundant commas
Fix mypy errors:

src/_pytest/runner.py:36: error: "addoption" of "OptionGroup" does not return a value  [func-returns-value]
src/_pytest/helpconfig.py:64: error: "addoption" of "OptionGroup" does not return a value  [func-returns-value]
src/_pytest/terminal.py:67: error: "_addoption" of "OptionGroup" does not return a value  [func-returns-value]
src/_pytest/terminal.py:75: error: "_addoption" of "OptionGroup" does not return a value  [func-returns-value]
2020-02-10 17:28:05 +02:00
Daniel Hahler
f9dd58000a Fix CaptureManager.__repr__ (#6697) 2020-02-10 13:03:05 +01:00
Daniel Hahler
449290406c test_argcomplete: remove usage of distutils.spawn (#6703)
Fixes collection error with Python 3.5.3 (Travis):

    testing/test_parseopt.py:2: in <module>
        import distutils.spawn
    .tox/py35-coverage/lib/python3.5/distutils/__init__.py:4: in <module>
        import imp
    .tox/py35-coverage/lib/python3.5/imp.py:33: in <module>
        PendingDeprecationWarning, stacklevel=2)
    E   PendingDeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

Build log: https://travis-ci.org/blueyed/pytest/builds/648305304
2020-02-10 11:52:19 +01:00
Daniel Hahler
12824e6279 ci: Travis: remove non-py35 jobs 2020-02-10 10:59:28 +01:00
Daniel Hahler
a62d9a40e7 ci: Travis: 3.5.1: upgrade pip, setuptools, virtualenv
Ref: https://github.com/jaraco/zipp/issues/40
2020-02-10 10:53:42 +01:00
Daniel Hahler
30cb598e9c Typing around/from types in docs (#6699) 2020-02-09 11:42:07 +01:00
Daniel Hahler
114de91ab7 factor out _determine_main_color
(cherry picked from commit f36b9f7fa4fa1af7130406166df5c2be668e50c9)
2020-02-09 00:26:38 +01:00
Daniel Hahler
7a7c634e33 factor out KNOWN_TYPES
(cherry picked from commit 1180aedee727f7e8468fdc5561a87e12865622c6)
2020-02-09 00:26:26 +01:00
Daniel Hahler
b4ace46c42 capture: cleanup item fixture handling (#6663)
This started by looking at how to get the current test item in general,
and then I noticed that it is not necessary for the capture plugin to
track it manually in the first place.
2020-02-07 19:23:37 +01:00
Daniel Hahler
a8fc056aad Handle Exit exception in pytest_sessionfinish (#6660) 2020-02-07 00:40:10 +01:00
Vladyslav Rachek
9e262038c8 [parametrize] enforce explicit argnames declaration (#6330)
Every argname used in `parametrize` either must
be declared explicitly in the python test function, or via
`indirect` list

Fix #5712
2020-02-06 20:20:25 -03:00
Minuddin Ahmed Rana
ef437ea448 Remove incorrect choices comment (#6677) 2020-02-05 20:45:21 +01:00
Daniel Hahler
cdc7e13067 pytester: clarify _makefile signature (#6675) 2020-02-05 20:42:57 +01:00
Daniel Hahler
39d9f7cff5 pytester: LineMatcher: typing, docs, consecutive line matching (#6653) 2020-02-04 22:47:18 +01:00
Daniel Hahler
632800add5 internal: clean up getfslineno (#6656) 2020-02-04 22:46:00 +01:00
Daniel Hahler
bc494661ad Remove testing/test_modimport.py (#6666) 2020-02-04 08:26:40 +01:00
Daniel Hahler
5a4c1b628b Use inspect.getdoc to massage fixture docstrings (#6668)
Ref: https://github.com/pytest-dev/pytest/pull/2575
2020-02-04 03:07:53 +01:00
Daniel Hahler
4316fe8a92 testing/conftest.py: testdir: set PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 (#6655)
Fixes https://github.com/pytest-dev/pytest/pull/4518.
2020-02-04 02:59:20 +01:00
Daniel Hahler
aa0328782f assertion: save/restore hooks on item (#6646) 2020-02-04 02:56:23 +01:00
Daniel Hahler
9c7f1d9b32 Remove compat.getfslineno 2020-02-04 02:40:59 +01:00
Daniel Hahler
dab90ef726 typing: fix getfslineno
Closes https://github.com/pytest-dev/pytest/pull/6590.
2020-02-03 19:09:08 +01:00
Daniel Hahler
61f2a26675 Code/getfslineno: keep empty co_filename
Previously this would be turned via `py.path.local("")` into the current
working directory.

This appears to be what `fspath = fn and py.path.local(fn) or None`
tries to avoid in `getfslineno`'s `TypeError` handling already, if
`Code` would raise it.
2020-02-03 19:09:08 +01:00
Daniel Hahler
b0d45267c5 internal: clean up getfslineno
Everything was using `_pytest.compat.getfslineno` basically, which
wrapped `_pytest._code.source.getfslineno`.

This moves the extra code from there into it directly, and uses the
latter everywhere.

This helps to eventually remove the one in compat eventually, and also
causes less cyclic imports.
2020-02-03 19:09:08 +01:00
Bruno Oliveira
1480aa31a7 Explicitly state on the PR template that we can squash commits (#6662)
* Explicitly state on the PR template that we can squash commits

This way we don't need to ask every time, and users who for some reason
would not like us to squash their commits can explicitly state so.
2020-02-03 14:35:50 -03:00
Daniel Hahler
75714ee707 pluginmanager.consider_preparse: add exclude_only kwarg (#6443)
Plugins specified with ``-p`` are now loaded after internal plugins, which
results in their hooks being called *before* the internal ones.

This makes the ``-p`` behavior consistent with ``PYTEST_PLUGINS``.

* fix/adjust test_disable_plugin_autoload
* adjust test_plugin_loading_order
2020-02-03 14:10:54 +01:00
Daniel Hahler
abffd16ce6 Keep (revisited) comment from https://github.com/pytest-dev/pytest/commit/4d31ea831 2020-02-03 14:04:16 +01:00
Daniel Hahler
fb289667e3 Remove testing/test_modimport.py
testing/test_meta.py ensures this already as a side effect
(+ tests a few more (`__init__.py` files) and should have been
combined with it right away [1].

1: https://github.com/pytest-dev/pytest/pull/4510#discussion_r289123446

Ref: https://github.com/pytest-dev/pytest/commit/eaa05531e
Ref: https://github.com/pytest-dev/pytest/commit/4d31ea831
2020-02-03 13:58:32 +01:00
Daniel Hahler
8ec4d03c91 Inline FunctionMixin with Function (#6664)
`Generator` was removed in 7eb28f9eb, and this pleases mypy to correctly
complain that `FunctionMixin` has no `config` (within
`_prunetraceback`).

* typing: _prunetraceback

* minor: imports, typing
2020-02-03 10:25:32 +01:00
rebecca-palmer
c55bf23cbe doc: s/pytest_mark/pytestmark (#6661) 2020-02-03 08:56:37 +01:00
Daniel Hahler
99d162e44a Handle Exit exception in pytest_sessionfinish
Similar to a7268aa (https://github.com/pytest-dev/pytest/pull/6258).
2020-02-02 23:13:23 +01:00
Ran Benita
8bd612b367 typing: wrap_session
Pulled out of https://github.com/pytest-dev/pytest/pull/6556.
2020-02-02 23:12:48 +01:00
Daniel Hahler
5256542ea4 pytester.LineMatcher: add support for matching lines consecutively 2020-02-01 23:48:58 +01:00
Daniel Hahler
50f81db817 revisit/improve docstrings 2020-02-01 23:48:58 +01:00
Daniel Hahler
2681b0aed7 typing: pytester: LineMatcher 2020-02-01 23:48:57 +01:00
Daniel Hahler
b10ab0211c Use TypeError instead of AssertionError for no sequence
Improve/extends tests.
2020-02-01 23:48:57 +01:00
Daniel Hahler
09a0e45492 testing/test_pytester.py: cosmetics 2020-02-01 23:48:57 +01:00
Daniel Hahler
a9c5d31806 PyCollector._genfunctions: use already created fixtureinfo (#6636)
`Function` creates a `_fixtureinfo` already:
https://github.com/pytest-dev/pytest/blob/fed535694/src/_pytest/python.py#L1392-L1395
2020-02-01 06:27:41 +01:00
Hugo van Kemenade
c9eeafade5 Fix favicon for Chrome and Opera (#6639)
* Fix favicon for Chrome and Opera

* Delete pytest1favi.ico

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2020-01-31 20:56:45 -03:00
Bruno Oliveira
39b25ddcf3 Remove deprecated 'pytest_itemstart' hook (#6637)
Remove deprecated 'pytest_itemstart' hook
2020-01-31 16:12:27 -03:00
Ran Benita
4038d6c773 Merge pull request #6640 from bluetech/master-to-features
Merge master to features - one last time
2020-01-31 12:11:14 +02:00
Ran Benita
a435faad5c Merge branch 'master' into master-to-features 2020-01-31 11:27:06 +02:00
Daniel Hahler
3645ba3072 Merge pull request #6638 from blueyed/rfEX
tests: use `-rfEX`
2020-01-31 01:06:32 +01:00
Bruno Oliveira
70739296e1 Remove deprecated 'pytest_itemstart' hook
This hook has been deprecated/removed for more than 10 years
in a2fe6714f8.
2020-01-30 21:03:29 -03:00
Daniel Hahler
8301993e5e tests: use -rfEX
`-fE` is the default in `features` now [1], but the idea is to add `X`
also to it in the long run, so let's dogfood it ourselves.

1: https://github.com/pytest-dev/pytest/pull/6524
2: https://github.com/pytest-dev/pytest/pull/6524#issuecomment-577650703
2020-01-31 00:18:51 +01:00
Daniel Hahler
1dc265e34a Merge pull request #6633 from blueyed/async_warn
python: factor out async_warn
2020-01-30 23:39:06 +01:00
Daniel Hahler
e7a833635d Merge pull request #6635 from blueyed/fix-todo
minor: doc: getfuncargnames: move TODO out of docstring
2020-01-30 23:37:41 +01:00
Daniel Hahler
fed5356941 Merge pull request #6634 from blueyed/fixtures-move-import
fixtures: move import of ParameterSet to top level
2020-01-30 23:22:39 +01:00
Daniel Hahler
5b81bd862c minor: doc: getfuncargnames: move TODO out of docstring 2020-01-30 23:22:10 +01:00
Daniel Hahler
2902c7263c fixtures: move import of ParameterSet to top level
This gets typically used always (via `getfixtureinfo`).
2020-01-30 23:03:02 +01:00
Daniel Hahler
442dccef65 python: factor out async_warn 2020-01-30 22:55:23 +01:00
Daniel Hahler
e830432592 Merge pull request #6601 from blueyed/typing-pytest_collection
typing: pytest_collection
2020-01-30 20:47:06 +01:00
Daniel Hahler
ae788997f2 Merge pull request #6630 from blueyed/revert-curl
Revert "ci: codecov: use `--retry-connrefused` with curl"
2020-01-30 20:46:46 +01:00
Daniel Hahler
88b800355a typing: pytest_collection 2020-01-30 19:58:43 +01:00
Daniel Hahler
864338de71 Revert "ci: codecov: use --retry-connrefused with curl"
Not known with `curl` on Travis at least.

Reverts https://github.com/pytest-dev/pytest/pull/6573.

This reverts commit df1f43ee28.
2020-01-30 19:56:25 +01:00
Bruno Oliveira
eb5e651d7e Fix typo (#6629)
Fix typo
2020-01-30 13:41:47 -03:00
Hugo van Kemenade
55bffb7c15 Fix typo 2020-01-30 18:30:51 +02:00
Bruno Oliveira
be18f85a6e Reword fixture docs for clarity (#6627)
Reword fixture docs for clarity
2020-01-30 12:56:45 -03:00
Daniel Hahler
28b8f3ca3a Merge pull request #6461 from blueyed/test-package-upstream
Package: typing, cleanup
2020-01-30 16:41:47 +01:00
Daniel Arndt
d91459fc75 Reword fixture docs for clarity 2020-01-30 09:32:54 -04:00
Daniel Hahler
4de8e680e3 Merge pull request #6625 from blueyed/merge-master-into-features
Merge master into features
2020-01-30 02:53:56 +01:00
Bruno Oliveira
ef283efc42 Merge pull request #6624 from nicoddemus/gh-notes-wrap
Use --wrap=preserve in release notes script
2020-01-29 22:36:42 -03:00
Daniel Hahler
b5b6e051ed Merge master into features 2020-01-30 02:29:02 +01:00
Daniel Hahler
10b1b79f4e Merge pull request #6623 from blueyed/move-back-test_getfslineno
tests: move test_getfslineno back
2020-01-30 02:17:25 +01:00
Bruno Oliveira
6d7e06e6be Use --wrap=preserve in release notes script
Follow up to #6621 after premature merge
2020-01-29 20:28:04 -03:00
Daniel Hahler
78eddcb5b1 tests: move test_getfslineno back
Reverts https://github.com/pytest-dev/pytest/pull/6610.

The tested `getfslineno` is `src/_pytest/_code/source.py` actually,
exported via `src/_pytest/_code/__init__.py`.
I've confused it with the one in `src/_pytest/compat.py` apparently.
2020-01-29 23:47:39 +01:00
Bruno Oliveira
757873edb3 Docs: Don't wrap the markdown for GitHub releases (#6621)
Docs: Don't wrap the markdown for GitHub releases
2020-01-29 19:38:17 -03:00
Bruno Oliveira
64ab68ff0a Fix 6341 disallow session config in fromparent (#6387)
Fix 6341 disallow session config in fromparent
2020-01-29 19:21:02 -03:00
Daniel Hahler
66330444a3 Merge pull request #6611 from blueyed/test_code-imports
tests: test_code: improve/clarify imports
2020-01-29 21:23:37 +01:00
Hugo van Kemenade
97f1645993 Don't wrap the markdown for GitHub releases 2020-01-29 21:56:01 +02:00
Daniel Hahler
3dbc61dd80 tests: test_code: improve/clarify imports 2020-01-29 19:44:37 +01:00
Daniel Hahler
b42938421e Merge pull request #6610 from blueyed/tests-move-test_getfslineno
tests: move test_getfslineno
2020-01-29 19:43:28 +01:00
Daniel Hahler
87fecce77b Merge pull request #6600 from blueyed/harden-test_teardown_many_verbose
tests: harden test_teardown_many_verbose
2020-01-29 18:56:10 +01:00
Daniel Hahler
05d953d9e4 Merge pull request #6620 from blueyed/cp-release
doc: release-5.3.5
2020-01-29 18:42:27 +01:00
Daniel Hahler
d478e2bbca doc: release-5.3.4
(cherry picked from commit fd1a51a23f)
2020-01-29 18:28:11 +01:00
Anthony Sottile
595d62bc3e Merge pull request #6607 from asottile/empty_string_parametrize_nodeid
Fix node ids which contain a parametrized empty-string variable
2020-01-29 08:42:04 -08:00
Bruno Oliveira
8c41236c66 doc: getting-started.rst: mention "Test" prefix for classes (#6583)
doc: getting-started.rst: mention "Test" prefix for classes
2020-01-29 12:49:49 -03:00
Bruno Oliveira
99b90f45d0 extend the incremental marker for parametrize (#6582)
extend the incremental marker for parametrize
2020-01-29 08:37:33 -03:00
sdementen
d848a20563 Extend the incremental marker for parametrize
The incremental marker is adapted to handle properly test classes with parametrize defined at class level.

Fix #3125
2020-01-29 08:25:17 -03:00
Daniel Hahler
06a7fef00f Merge pull request #6616 from blueyed/merge-master-into-features
Merge master into features
2020-01-29 06:57:45 +01:00
Daniel Hahler
55e5817570 Merge master into features 2020-01-29 03:07:35 +01:00
Daniel Hahler
3f4b8d3aec test_code: improve coverage 2020-01-29 02:54:12 +01:00
Daniel Hahler
a3f482ceba tests: move test_getfslineno
It should be in `test_code` when testing `_pytest._code.getfslineno`,
not to be confused with `_pytest._code.source.getfslineno`.

Adds an extra assert (via https://github.com/pytest-dev/pytest/pull/6590).
2020-01-29 01:26:10 +01:00
Daniel Hahler
3ccf2a5e61 Merge pull request #6524 from blueyed/reportchars-default
terminal: default to `fE` with `-r` (reportchars)
2020-01-29 01:00:41 +01:00
Daniel Hahler
8e1d59a8dd Merge pull request #6579 from blueyed/pytester-makefile-joins-abspath
pytester: test for _makefile joining an absolute path
2020-01-29 00:58:43 +01:00
Daniel Hahler
7eaf98af4b Merge pull request #6589 from blueyed/ci-less-coverage
ci: GHA: run less jobs with coverage
2020-01-29 00:57:29 +01:00
Bruno Oliveira
d282424589 Fix unguarded == comparison in fixtures. (#6541)
Fix unguarded `==` comparison in fixtures.
2020-01-28 20:54:14 -03:00
Daniel Hahler
ddaa5d88ac terminal: default to fE with -r (reportchars)
Adds handling of `N` to reset `reportchars`, which can be used to get
the old behavior (`-rN`), and also allows for an alternative to
`--disable-warnings` (https://github.com/pytest-dev/pytest/issues/5066),
since `w` was included by default (without `--disable-warnings`).

Fixes https://github.com/pytest-dev/pytest/issues/6454
2020-01-29 00:33:15 +01:00
Daniel Hahler
4ff90b1fcf Merge pull request #6606 from blueyed/typing-monkeypatch-context
typing: MonkeyPatch.context
2020-01-29 00:12:53 +01:00
Daniel Hahler
2d29c3e7d1 Merge pull request #6604 from blueyed/tests-_compute_fixture_value-cover-abs-source_path_str
tests: cover absolute path handling in _compute_fixture_value
2020-01-29 00:11:58 +01:00
Anthony Sottile
abd5fc80e8 Fix node ids which contain a parametrized empty-string variable 2020-01-28 13:27:54 -08:00
Holger Kohr
80d4dd6f0b Replace == with is for comparison of cache keys
Closes #6497
2020-01-28 18:05:53 -03:00
Ran Benita
e440b43258 Merge pull request #6555 from bluetech/nodes-cache-split
Split Session._collection_node_cache to 3 mutually exclusive parts
2020-01-28 22:49:42 +02:00
Daniel Hahler
e25d46aae6 typing: MonkeyPatch.context 2020-01-28 21:44:30 +01:00
Daniel Hahler
7c87874277 source_path: py.path.local directly
Via bc7282576.
2020-01-28 19:02:41 +01:00
Daniel Hahler
1cf9e68dbc tests: cover absolute path handling in _compute_fixture_value 2020-01-28 18:53:28 +01:00
Daniel Hahler
b2e6f66438 ci: GHA: run less jobs with coverage
This often might be causing for jobs to take longer than 10 minutes,
which is a timeout Codecov uses to wait for successful CI.

Also it is good in general to have CI finish faster, of course.
2020-01-28 17:38:53 +01:00
Daniel Hahler
b01e379428 tests: harden test_teardown_many_verbose 2020-01-28 16:37:18 +01:00
Daniel Hahler
1586653102 Merge pull request #6594 from blueyed/merge-master-into-features
Merge master into features
2020-01-28 14:44:22 +01:00
Daniel Hahler
18ac7e0b79 Merge pull request #6593 from blueyed/typing-ignore-more-itertools
typing: ignore false positive with more-itertools
2020-01-28 14:24:24 +01:00
Ronny Pfannschmidt
c854daa234 Merge pull request #6595 from blueyed/fix-typing-code-path
typing: fix Code.path
2020-01-28 11:23:49 +01:00
Daniel Hahler
12c5a6af64 typing: fix Code.path
Fixes:

> src/_pytest/_code/code.py:83: error: Incompatible types in assignment
>   (expression has type "str", variable has type "local")  [assignment]
2020-01-28 01:49:45 +01:00
Daniel Hahler
30922ee694 Merge master into features 2020-01-28 01:40:14 +01:00
Daniel Hahler
35ba053f00 typing: ignore false positive with more-itertools
Fixed in https://github.com/erikrose/more-itertools/pull/374.
2020-01-28 01:23:44 +01:00
Daniel Hahler
d0cb16010b Merge pull request #6580 from blueyed/typing-testdir-init
typing: Testdir.__init__
2020-01-28 00:58:11 +01:00
Daniel Hahler
ad0f4f0ac0 tests: cover collect_by_name with non-existing 2020-01-28 00:41:46 +01:00
Daniel Hahler
9c716e4d74 typing: Testdir.plugins 2020-01-28 00:41:46 +01:00
Daniel Hahler
94ac0f7e6b typing: self._mod_collections, collect_by_name 2020-01-28 00:41:46 +01:00
Daniel Hahler
440881d63a typing: Testdir.__init__ 2020-01-28 00:41:46 +01:00
Daniel Hahler
aa318e9adf Merge pull request #6587 from blueyed/mypy-show_error_codes
mypy: show_error_codes=True
2020-01-27 22:58:48 +01:00
Daniel Hahler
20b66e60c0 Merge pull request #6566 from blueyed/rm-encodedfile-writelines
Fix `EncodedFile.writelines`
2020-01-27 22:58:31 +01:00
Daniel Hahler
d017b69f38 mypy: show_error_codes=True 2020-01-27 22:18:35 +01:00
Ran Benita
ae5d16be10 typing: FSHookProxy/gethookproxy
Taken out of https://github.com/pytest-dev/pytest/pull/6556.
2020-01-27 20:57:44 +01:00
ParetoLife
cbad319736 Update getting-started.rst
From the description it seemed to me as if just prefixing your methods with ``test_`` was enough, but you also need to prefix your class with ``Test``. Of course, in the reference material this is clearly stated, but I think it makes sense to mention it here as well, since you also mention the part about the methods' prefix.
2020-01-27 10:50:05 +01:00
Zac Hatfield-Dodds
a9eab07739 Merge pull request #6576 from blueyed/test_via_exec
tests: add test_via_exec
2020-01-27 17:57:28 +11:00
Daniel Hahler
c2980eb80f pytester: test for _makefile joining an absolute path
Ref: https://github.com/pytest-dev/pytest/pull/6578#discussion_r371035867
2020-01-27 01:00:55 +01:00
Daniel Hahler
bf5c76359c fixup! typing: tests: tmpfile 2020-01-26 23:14:32 +01:00
Daniel Hahler
40758e86ca tests: add test_via_exec
Via https://github.com/pytest-dev/pytest/issues/6574.
2020-01-26 23:04:18 +01:00
Daniel Hahler
d678d380cb typing: tests: tmpfile 2020-01-25 19:21:19 +01:00
Daniel Hahler
3f8f395210 typing: EncodedFile 2020-01-25 19:20:48 +01:00
Daniel Hahler
7c52a37d46 Merge pull request #6572 from blueyed/fix-test_collection_collect_only_live_logging
tests: test_collection_collect_only_live_logging: allow for 1+s
2020-01-25 19:05:37 +01:00
Daniel Hahler
198b1dcffd Merge pull request #6573 from blueyed/codecov-curl-retry-connrefused
ci: codecov: use `--retry-connrefused` with curl
2020-01-25 19:05:13 +01:00
Daniel Hahler
e2934c3f8c Move common code between Session and Package to FSCollector 2020-01-25 19:04:01 +01:00
Daniel Hahler
6b7e1a246c Sync {Session,Package}.gethookproxy
Only copy'n'paste error from c416b1d935.
2020-01-25 19:04:01 +01:00
Daniel Hahler
817c094ce6 Clean up Package.__init__
Makes `parent` a required arg, which would have failed before via
`parent.session` anyway.

Keeps calling/passing unused args for B/C.
2020-01-25 19:04:01 +01:00
Daniel Hahler
df1f43ee28 ci: codecov: use --retry-connrefused with curl
While it might not help with the following, it certainly might happen as
well.

```
+ curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
curl: (16) Error in the HTTP2 framing layer
```
2020-01-25 18:22:03 +01:00
Daniel Hahler
778d4364fa tests: test_collection_collect_only_live_logging: allow for 1s
Might be slow on CI.

Ref: https://github.com/pytest-dev/pytest/pull/6570/checks?check_run_id=408752475#step:6:109
2020-01-25 18:14:49 +01:00
Daniel Hahler
039d582b52 Fix EncodedFile.writelines
This is implemented by the underlying stream already, which additionally
checks if the stream is not closed, and calls `write` per line.

Ref/via: https://github.com/pytest-dev/pytest/pull/6558#issuecomment-578210807
2020-01-25 18:06:50 +01:00
Daniel Hahler
9b8039cf09 Sync {Session,Package}._recurse 2020-01-25 16:44:20 +01:00
Daniel Hahler
b687f20d25 Merge pull request #6375 from hugovk/rm-deprecated-license_file-metadata
Remove deprecated license_file from setup.cfg
2020-01-25 16:20:48 +01:00
Daniel Hahler
510be29db8 Merge pull request #6534 from blueyed/test_plugin_loading_order
tests: add test_plugin_loading_order
2020-01-25 16:20:06 +01:00
Hugo
fe343a79f8 Remove deprecated license_file from setup.cfg
Starting with wheel 0.32.0 (2018-09-29), the `license_file` option is deprecated.

* https://wheel.readthedocs.io/en/stable/news.html

The wheel will continue to include `LICENSE`, it is now included automatically:

* https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file

And `LICENSE` is still included in sdists thanks to setuptools-scm:

* https://github.com/pytest-dev/pytest/pull/6348#issuecomment-567836331
2020-01-25 17:08:00 +02:00
Daniel Hahler
1e3bc1814d typing for test_summary_stats 2020-01-25 15:09:02 +01:00
Daniel Hahler
57512aa997 _get_main_color: no yellow ("boring") for non-last item
- refactor _get_main_color/build_summary_stats_line
- factor out property _is_last_item; test_summary_stats: tr._is_last_item
- _write_progress_information_filling_space: remove color arg
- use setter for stats, handling main color
- _get_main_color: skip cache for last item
- Handle random order in test for py35.
2020-01-25 15:09:02 +01:00
Daniel Hahler
2e8f7ef31b Merge pull request #6569 from blueyed/merge-master-into-features
Merge master into features
2020-01-25 14:40:24 +01:00
Daniel Hahler
c51173d426 Merge master into features 2020-01-25 14:18:02 +01:00
Daniel Hahler
2da331ea9c Merge pull request #6568 from bluetech/redundant-commas
Remove a couple of redundant commas
2020-01-25 14:17:01 +01:00
Ran Benita
94d8c071b6 Remove a couple of redundant commas
Mypy complains about this once the function is typed:

src/_pytest/main.py:85: error: "_addoption" of "OptionGroup" does not return a value
src/_pytest/main.py:133: error: "addoption" of "OptionGroup" does not return a value
2020-01-25 14:12:47 +01:00
Daniel Hahler
0d55fb3797 Merge pull request #6567 from blueyed/codecov-config
ci: codecov: only use "comment: off"
2020-01-25 14:00:32 +01:00
Ran Benita
f3967333a1 Split Session._collection_node_cache to 3 mutually exclusive parts
Previously, this cache was used with 3 different and mutually exclusive
key-type -> value-type combinations. Mypy can't properly type this. It's
also quite confusing.

Split to 3 different dicts instead.
2020-01-25 14:31:12 +02:00
Ran Benita
a76bc64c54 Merge pull request #6547 from bluetech/session-initialparts
Refactor Session._initialparts to have a more explicit type
2020-01-25 14:30:26 +02:00
Ran Benita
dd5c2b22bd Refactor Session._initialparts to have a more explicit type
Previously, _initialparts was a list whose first item was a
`py.path.local` and the rest were `str`s. This is not something that
mypy is capable of modeling. The type `List[Union[str, py.path.local]]`
is too broad and would require asserts for every access.

Instead, make each item a `Tuple[py.path.local, List[str]]`. This way
the structure is clear and the types are accurate.

To make sure any users who might have been accessing this (private)
field will not break silently, change the name to _initial_parts.
2020-01-25 13:57:49 +02:00
Daniel Hahler
a29d6194f5 ci: codecov: only use "comment: off"
The changes status is quite buggy, remove it for now.
This just uses "comment: off" then.
2020-01-25 11:14:00 +01:00
Daniel Hahler
6f2943c7b3 Merge pull request #6558 from gavento/patch-1
Make EncodedFile.write() return the return value from inner write()
2020-01-25 11:04:12 +01:00
Tomáš Gavenčiak
5e15c86cc6 Fix EncodedFile.write return value
Make EncodedFile, used for captured output streams, method .write return
the number of characters written. Add test for captured stderr write.
Fixes #6557.

Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2020-01-25 10:36:23 +01:00
Daniel Hahler
498884a2a0 Merge pull request #6563 from blueyed/merge-master-into-features
Merge master into features
2020-01-25 00:31:58 +01:00
Daniel Hahler
09bdbffbde Merge master into features
Conflicts:
	src/_pytest/_code/code.py
	src/_pytest/main.py
2020-01-24 23:44:50 +01:00
Daniel Hahler
09ab5fd7e9 Merge pull request #6529 from blueyed/fix-test_repr_traceback_with_invalid_cwd
tests: fix test_repr_traceback_with_invalid_cwd
2020-01-24 23:41:31 +01:00
Daniel Hahler
040a61e22c Merge pull request #6550 from blueyed/doc-cleandir-cd-back
doc/en/fixture.rst: chdir back to previous directory
2020-01-24 23:41:08 +01:00
Daniel Hahler
2c32dad343 Merge pull request #6554 from blueyed/test_fixture_arg_ordering
tests: add test_fixture_arg_ordering
2020-01-24 23:40:58 +01:00
Daniel Hahler
8a8f9bd751 Merge pull request #6562 from blueyed/ci-rm-azure
ci: remove Azure config/scripts
2020-01-24 23:39:57 +01:00
Daniel Hahler
192d3adda3 tests: add test_fixture_arg_ordering
This is a regression test for part of
https://github.com/pytest-dev/pytest/issues/6492, testing one of the
fixes in https://github.com/pytest-dev/pytest/pull/6551.
2020-01-24 17:48:08 -03:00
Daniel Hahler
5865520c51 Merge pull request #6561 from blueyed/ci-gha-name-flag
ci: GHA: codecov: set CODECOV_NAME, OS in flags
2020-01-24 20:04:12 +01:00
Daniel Hahler
e2fa78c99f ci: remove Azure config/scripts
This is covered by GitHub Actions now.
2020-01-24 19:49:57 +01:00
Daniel Hahler
fe895a40b6 Merge pull request #6559 from blueyed/ci-travis-remove-linting
ci: Travis: removing linting,docs,doctesting
2020-01-24 19:45:42 +01:00
Daniel Hahler
934f38995a ci: GHA: codecov: set CODECOV_NAME, OS in flags 2020-01-24 19:42:51 +01:00
Daniel Hahler
5dcd24fecb Merge pull request #6553 from blueyed/ci-gha-use-report-coverage.sh
ci: GHA: use scripts/report-coverage.sh
2020-01-24 19:20:23 +01:00
Daniel Hahler
e5a362d0f5 ci: GHA: use scripts/report-coverage.sh 2020-01-24 18:00:38 +01:00
Daniel Hahler
f8654e6656 ci: Travis: removing linting,docs,doctesting
This is handled by GHA already, and not affected/required for coverage.
2020-01-24 17:26:10 +01:00
Daniel Hahler
0d4f479aa8 Merge pull request #6552 from blueyed/ci-linting
ci: GHA: separate jobs for linting, docs, doctesting
2020-01-24 13:36:59 +01:00
Daniel Hahler
79d00ab35a Merge pull request #6549 from blueyed/fix-release-minor
doc: minor fixes for the release process
2020-01-24 13:36:48 +01:00
Daniel Hahler
79ae86cc3f tests: fix test_repr_traceback_with_invalid_cwd
This never worked as expected (since a912d3745), and only py38-windows
triggered the mocked `os.getcwd` unintentionally, via `inspect`.
2020-01-24 12:52:12 +01:00
Daniel Hahler
c051a9e7b9 ci: GHA: separate jobs for linting, docs, doctesting
It helps to know upfront that e.g. linting failed, and makes finding the
error easier.
2020-01-24 12:29:35 +01:00
Daniel Hahler
cdd6f86e43 bug-fix fixes 2020-01-23 20:34:21 +01:00
Daniel Hahler
83451b548f doc/en/fixture.rst: chdir back to previous directory
This is considered to be best practice, and should be used in docs
therefore.
2020-01-23 19:09:18 +01:00
Daniel Hahler
38538c6c6d Merge pull request #6548 from blueyed/ci-codecov-flag-gha
ci: codecov: add flag for GHA
2020-01-23 18:39:50 +01:00
Daniel Hahler
55ebd9f803 doc: minor fixes for the release process 2020-01-23 18:31:48 +01:00
Daniel Hahler
8ca8d25202 Merge pull request #6545 from blueyed/terminalwriter
config: typing for create_terminal_writer, re-export TerminalWriter
2020-01-23 15:42:18 +01:00
Daniel Hahler
a8d67f5e7b ci: codecov: add flags for GHA/Travis
This would help with debugging missing coverage when removing Travis
jobs.
2020-01-23 15:41:41 +01:00
Daniel Hahler
03bc8aba4e config: typing for create_terminal_writer, re-export TerminalWriter
This also imports `TerminalWriter` explicitly via `_pytest._io`,
allowing for easier extending / replacing it.
2020-01-23 14:09:37 +01:00
Daniel Hahler
6b13379f37 Merge pull request #6521 from blueyed/harden-nose-raises
tests: improve test for `nose.raises`
2020-01-23 13:42:14 +01:00
Ran Benita
9dcdea5de7 Rewrite Item.location to be clearer with regard to types 2020-01-23 13:25:15 +01:00
Daniel Hahler
863bab5326 Merge pull request #6544 from blueyed/doc-rm-costlysetup
doc: remove costlysetup example
2020-01-23 13:21:32 +01:00
Daniel Hahler
8521503246 Merge pull request #6527 from blueyed/typing-getfslineno
typing: fix/adjust _code.source.getfslineno
2020-01-23 13:17:47 +01:00
Ran Benita
0b6258ab5b PyCollector.collect: use explicit cast to str
Ref: https://github.com/pytest-dev/pytest/pull/6521#pullrequestreview-347234792
2020-01-23 12:54:52 +01:00
Daniel Hahler
b63cb18776 doc: remove costlysetup example
It is not included with docs, and
`example/costlysetup/sub_a/test_quick.py::test_quick` sleeps for 5s,
slowing down `doctesting` unnecessarily.
2020-01-23 12:23:30 +01:00
Daniel Hahler
00097df5cd tests: add test_plugin_loading_order
Ref: https://github.com/pytest-dev/pytest/pull/6443
2020-01-23 11:57:12 +01:00
Daniel Hahler
10e243d206 Merge pull request #6543 from blueyed/rm-rw
tests: remove unnecessary `-rw` option
2020-01-23 11:56:07 +01:00
Daniel Hahler
e7444bbd5e tests: remove unnecessary -rw option
Warnings are enabled by default, which is tested by `test_getreportopt`.
2020-01-23 11:37:19 +01:00
Daniel Hahler
eb7a57f965 Merge pull request #6542 from blueyed/fix-test_record_property
tests: fix/harden test_record_property
2020-01-23 11:14:37 +01:00
Daniel Hahler
252eae5bc8 tests: fix/harden test_record_property
`-rv` is not a recognized reportchar.  Probably `-v` was meant, but is
not necessary to check that there are no warnings.

Followup to 2018cf12b (https://github.com/pytest-dev/pytest/pull/3360).
2020-01-23 10:49:59 +01:00
Daniel Hahler
9c7b3c57d7 typing: PyobjMixin.reportinfo, getfslineno 2020-01-23 10:45:31 +01:00
Daniel Hahler
1350c601dc Node.location: handle str with _node_location_to_relpath 2020-01-23 10:45:31 +01:00
Daniel Hahler
ef112fd7dd Revert "Revert "Fix type errors after adding types to the py dependency""
Without changes to test_itemreport_reportinfo.

This reverts commit fb99b5c66e.

Conflicts:
	testing/test_nose.py
2020-01-23 10:45:27 +01:00
Daniel Hahler
8fa57c8384 tests: improve test for nose.raises
This should probably get transferred into a `pytest.fail` really, but
tests/documents the current behavior.
2020-01-23 10:45:26 +01:00
Daniel Hahler
ad02f6f879 Merge pull request #6525 from blueyed/typing-session
typing: Session.__init__
2020-01-23 00:16:08 +01:00
Daniel Hahler
bd6ba3f3e1 typing: Session.__init__
Pulled out of https://github.com/pytest-dev/pytest/pull/6491.
2020-01-23 00:07:59 +01:00
Bruno Oliveira
e17f5fad14 Clear node collection cache after collection is done (#6491)
Clear node collection cache after collection is done
2020-01-22 16:08:59 -03:00
Daniel Hahler
72d06e6dec Merge pull request #6539 from blueyed/merge-master-into-features
Merge master into features
2020-01-22 19:32:49 +01:00
Daniel Hahler
aca1723d45 Merge master into features 2020-01-22 19:18:13 +01:00
Daniel Hahler
85df6bbe26 Merge pull request #6536 from blueyed/fix-test_cwd_snapshot
tests: fix test_cwd_snapshot
2020-01-22 16:46:04 +01:00
Daniel Hahler
ba49581510 Merge pull request #6538 from blueyed/test-coverage
tests: use NotImplementedError with uncovered code
2020-01-22 16:45:20 +01:00
Bruno Oliveira
0bf9628e62 Merge master into features (#6537)
Merge master into features
2020-01-22 12:43:19 -03:00
Daniel Hahler
d878d9d4d5 tests: use NotImplementedError with uncovered code 2020-01-22 15:38:12 +01:00
Bruno Oliveira
93b74d28d2 Merge remote-tracking branch 'upstream/master' into mm
Conflicts:
 * 	src/_pytest/_code/code.py
 * 	src/_pytest/main.py
 * 	testing/python/metafunc.py
 * 	testing/test_parseopt.py
 * 	testing/test_pytester.py
2020-01-22 11:03:45 -03:00
Daniel Hahler
8c4dfca0c1 Merge pull request #6535 from blueyed/tox-doctesting
tox: move doctesting into main testenv
2020-01-22 14:38:34 +01:00
Daniel Hahler
b8787b8732 tests: fix test_cwd_snapshot
Without restoring the cwd, successive tests might fail to parse the
config (via `_pytest.config._prepareconfig()`, for when `--lsof` is
used).

And it is good practice to restore the cwd in any case anyway.
2020-01-22 14:34:11 +01:00
Daniel Hahler
ef294fc727 tox: move doctesting into main testenv
This allows for `tox -e doctesting-coverage`, which would otherwise not
use the "doctesting" testenv.
2020-01-22 13:32:30 +01:00
Daniel Hahler
0f78ef8e02 Merge pull request #6532 from blueyed/harden-test
--cache-clear: add test for keeping non-supporting files
2020-01-22 12:20:24 +01:00
Daniel Hahler
1cd4bafda7 Merge pull request #6531 from blueyed/ci-gha-success
ci: GHA: remove unnecessary check for `success()`
2020-01-21 22:38:50 +01:00
Daniel Hahler
8f5fd537d8 --cache-clear: add test for keeping non-supporting files
Ref: https://github.com/pytest-dev/pytest/pull/6296
2020-01-21 22:16:56 +01:00
Daniel Hahler
9869a3d9e1 ci: GHA: remove unnecessary check for success()
Following jobs get aborted on failure.

Ref: https://github.com/pytest-dev/pytest/pull/6530
2020-01-21 21:44:49 +01:00
Daniel Hahler
2f00b041e4 Merge pull request #6526 from blueyed/typing-code-path
doc: fix Code.path: might return str
2020-01-21 21:06:26 +01:00
Daniel Hahler
62db3f7abc typing: fix/adjust _code.source.getfslineno 2020-01-21 19:48:17 +01:00
Daniel Hahler
7ee27fedee doc: fix Code.path: might return str 2020-01-21 19:46:43 +01:00
Bruno Oliveira
7b1e3d1c9a Clear collection caches after collection is done
Also rename the involved variables to convey its intent better and
add type hints
2020-01-21 07:29:13 -03:00
Ran Benita
a52f791461 Merge pull request #6518 from bluetech/fix-py-typed-fixes-regression
Fix internal assert failure regression in 5.3.4
2020-01-21 00:01:07 +02:00
Ran Benita
fb99b5c66e Revert "Fix type errors after adding types to the py dependency"
This reverts commit 930a158a6a.

Regression test from Bruno Oliveira.
2020-01-20 23:44:56 +02:00
Bruno Oliveira
ddfa41b5a7 Preparing release version 5.3.4 (#6516)
Preparing release version 5.3.4
2020-01-20 14:26:52 -03:00
Bruno Oliveira
6a26ac4125 Preparing release version 5.3.4 2020-01-20 13:53:31 -03:00
Bruno Oliveira
cdaa9c06e1 Revert "fixtures register finalizers with all fixtures before t… (#6496)
Revert "fixtures register finalizers with all fixtures before them in the stack"
2020-01-20 13:49:00 -03:00
Bruno Oliveira
0dc82e8501 Add CHANGELOG entry for #6496 2020-01-20 13:32:27 -03:00
Daniel Hahler
f9bed82c4d Merge pull request #6515 from blueyed/tox-mypy-diff
tox: add mypy-diff testenv
2020-01-20 14:29:15 +01:00
Daniel Hahler
2406076611 tox: add mypy-diff testenv 2020-01-20 13:42:07 +01:00
Ran Benita
44eb1f580c Merge pull request #6311 from bluetech/type-annotations-10
Some type annotation & check_untyped_defs fixes
2020-01-19 20:17:32 +02:00
Ran Benita
3392be37e1 Fix check_untyped_defs in test_runner 2020-01-19 20:01:07 +02:00
Ran Benita
3d2680b31b Fix type of pytest.warns, and fix check_untyped_defs in test_recwarn
The expected_warning is optional.
2020-01-19 19:39:14 +02:00
Ran Benita
0b603156b9 Fix check_untyped_defs errors in test_pytester 2020-01-19 19:39:14 +02:00
Ran Benita
0c247be769 Add a few missing type annotations in _pytest._code
These are more "dirty" than the previous batch (that's why they were
left out). The trouble is that `compile` can return either a code object
or an AST depending on a flag, so we need to add an overload to make the
common case Union free. But it's still worthwhile.
2020-01-19 19:39:14 +02:00
Ran Benita
3e6f0f34ff Cleanup unhelpful alias _AST_FLAG
Also replace one direct call to `compile` with this flag with the
equivalent wrapper `ast.parse`. This function can have a more precise
type.
2020-01-19 19:19:34 +02:00
Ran Benita
4fb9cc3bf0 Merge pull request #6511 from bluetech/py-typed-fixes
Fix type errors after adding types to the `py` dependency
2020-01-19 15:23:14 +02:00
Ran Benita
930a158a6a Fix type errors after adding types to the py dependency 2020-01-19 14:48:24 +02:00
Daniel Hahler
d347a30656 Merge pull request #6510 from blueyed/typing-fixes
typing: fix some "incompatible types in assignment" with py
2020-01-19 13:01:35 +01:00
Daniel Hahler
32b62f770f Merge pull request #6509 from blueyed/typing-minor
typing: minor improvements
2020-01-19 11:33:41 +01:00
Daniel Hahler
aaae43e0ba typing: fix some "incompatible types in assignment" with py 2020-01-19 11:32:45 +01:00
Daniel Hahler
1a75a3c08e Merge pull request #6482 from blueyed/fix-_TracebackStyle
typing: fix _TracebackStyle
2020-01-19 11:24:05 +01:00
Daniel Hahler
09e9a01df3 typing: fix _TracebackStyle: add "line" 2020-01-19 11:22:47 +01:00
Daniel Hahler
4b974b051d Merge pull request #6507 from blueyed/fix-test_excinfo
tests: test_excinfo: remove unused pytest_version_info
2020-01-19 11:21:46 +01:00
Daniel Hahler
5c445b05e7 typing: py.io.TerminalWriter for tw arguments 2020-01-19 11:21:16 +01:00
Daniel Hahler
4e0dbe92dd Node.name: str 2020-01-19 11:21:16 +01:00
Daniel Hahler
956389fa8c Session._fixturemanager 2020-01-19 11:21:16 +01:00
Daniel Hahler
61f985f3c7 tests: test_excinfo: remove unused pytest_version_info
This might fail unnecessarily with a (wrong) determined version of e.g.
"4.7.dev307+ge98176cf5" (no patch version).
Ref: https://github.com/pytest-dev/pytest/pull/6506
2020-01-19 08:11:13 +01:00
Daniel Hahler
2f0d0fb349 Merge pull request #6503 from blueyed/ci-travis-features
[features] ci: Travis: drop deploy and non-coverage jobs
2020-01-18 22:22:21 +01:00
Daniel Hahler
e98176cf50 Merge pull request #6501 from blueyed/test_color_yes
tests: terminal: harden test_color_yes
2020-01-18 16:11:27 +01:00
Daniel Hahler
38fc208205 tests: terminal: harden/improve test_color_yes 2020-01-18 15:49:59 +01:00
Daniel Hahler
89f92a459a ci: Travis: remove non-coverage jobs
This helps with regard to slowness until
https://github.com/pytest-dev/pytest/pull/6470 is resolved.

(cherry picked from commit a7292a0544)
2020-01-18 15:12:44 +01:00
Bruno Oliveira
40d0031cce Drop deploy from Travis in favor of GitHub actions
GitHub actions already should deploy.

This is a stop gap while we figure out why coverage dropped
when removing Travis builds in #6470

(cherry picked from commit d1d7e5d41b)
2020-01-18 15:12:44 +01:00
Daniel Hahler
d0811c1f3d Merge pull request #6502 from blueyed/fix-ci-features
[features] fix CI
2020-01-18 15:12:13 +01:00
Daniel Hahler
ac41f36a02 Merge pull request #6500 from blueyed/ci-travis
ci: Travis: remove non-coverage jobs
2020-01-18 14:53:03 +01:00
Daniel Hahler
7a626921c0 [features] tests: fix test_crash_on_closing_tmpfile_py27
(cherry picked from commit 4f0eec2022)
2020-01-18 14:52:18 +01:00
Daniel Hahler
59bc6efbf2 Merge pull request #6498 from blueyed/test_terminal_colors
test_terminal: improve color handling
2020-01-18 13:36:42 +01:00
Daniel Hahler
a7292a0544 ci: Travis: remove non-coverage jobs
This helps with regard to slowness until
https://github.com/pytest-dev/pytest/pull/6470 is resolved.
2020-01-18 13:35:44 +01:00
Daniel Hahler
1971033051 Merge pull request #6499 from blueyed/fix-ci
tests: fix test_crash_on_closing_tmpfile_py27
2020-01-18 13:35:11 +01:00
Daniel Hahler
d4d04e7f25 test_terminal: improve color handling 2020-01-18 13:16:27 +01:00
Daniel Hahler
4f0eec2022 tests: fix test_crash_on_closing_tmpfile_py27 2020-01-18 13:15:40 +01:00
Bruno Oliveira
cc7f294cfe Revert "fixtures register finalizers with all fixtures before them in the stack"
This reverts commit 99180939fe.
2020-01-17 12:55:57 -03:00
Bruno Oliveira
e1298faef9 Merge pull request #6481 from nicoddemus/release-5.3.3
Release 5.3.3
2020-01-17 08:07:57 -03:00
Daniel Hahler
e211d6fe2a Merge pull request #6488 from blueyed/typing-fix-reportinfo
typing: Node.reportinfo: might return py.path.local via fspath
2020-01-17 11:52:01 +01:00
Daniel Hahler
4a42deee7e typing: Node.reportinfo: might return py.path.local via fspath 2020-01-17 11:05:46 +01:00
Daniel Hahler
2cce026766 Merge pull request #6476 from blueyed/fix-test_config
Fix test_config: wrong assertions, lint, unused fixtures
2020-01-17 10:31:41 +01:00
Daniel Hahler
e16cb2fdd0 Merge pull request #6444 from blueyed/fix-test_xfail_handling
tests: test_xfail_handling: use sys.dont_write_bytecode
2020-01-17 10:28:23 +01:00
Daniel Hahler
dcb94d8f31 Merge pull request #6485 from blueyed/lint-fix-f999
scripts/release.py: lint: fix F999
2020-01-17 09:13:04 +01:00
Daniel Hahler
19f66cb824 Merge pull request #6434 from blueyed/pytester-typing-spawn
pytester: typing for `spawn`/`spawn_pytest`
2020-01-17 06:57:40 +01:00
Daniel Hahler
36944157f8 pytester: typing for spawn/spawn_pytest 2020-01-17 05:58:25 +01:00
Daniel Hahler
2cfee583db scripts/release.py: lint: fix F999 2020-01-17 05:54:17 +01:00
Daniel Hahler
1667cf3350 Merge pull request #6384 from pv/showlocals-short
Make --showlocals work together with --tb=short

Fixes https://github.com/pytest-dev/pytest/issues/494
Ref: https://github.com/pytest-dev/pytest/issues/1715
2020-01-16 22:04:14 +01:00
Daniel Hahler
a4f5b8a4d6 Merge pull request #6478 from blueyed/merge-master-into-features
Merge master into features
2020-01-16 21:14:30 +01:00
Daniel Hahler
5f4cd536f9 Use _pytest.compat.TYPE_CHECKING 2020-01-16 19:47:23 +01:00
Daniel Hahler
83813bf515 Merge master into features
Conflicts:
	.github/workflows/main.yml
2020-01-16 19:45:52 +01:00
Daniel Hahler
118cb3d3be Fix test_config: wrong assertions, lint, unused fixtures 2020-01-16 19:27:46 +01:00
Bruno Oliveira
3789bb53a7 junit_logging options (follow up to #6469) (#6473)
junit_logging options (follow up to #6469)
2020-01-16 08:13:48 -03:00
Bruno Oliveira
9298f7e4a9 Improve CHANGELOG and docs for junit_logging 2020-01-16 07:47:00 -03:00
Jakub Mitoraj
ab6406b42e Update junit_logging with no,log,system-out,system-err,out-err,all 2020-01-16 08:14:46 +01:00
Ronny Pfannschmidt
8ba0b7bc2a fix #6341 - disallow session/config in Node.from_parent 2020-01-15 13:00:46 +01:00
Bruno Oliveira
f2659f77be Merge master into features (#6458)
Merge master into features
2020-01-14 20:06:51 -03:00
Bruno Oliveira
b9c136b809 Use a dummy RemoteTraceback for test in Python 3.5 Windows
Somehow in Python 3.5 on Windows this test fails with:
   File "c:\hostedtoolcache\windows\python\3.5.4\x64\Lib\multiprocessing\connection.py", line 302, in _recv_bytes
     overlapped=True)
OSError: [WinError 6] The handle is invalid

This only happens in this platform and Python version, decided to use
a dummy traceback as originally done in #6412.
2020-01-14 18:31:21 -03:00
Pauli Virtanen
fd1691a2b3 Make --showlocals work together with --tb=short
Enable showing local variables when asked to do so in the short
traceback mode.

Fixes #494
2020-01-14 21:30:58 +02:00
Bruno Oliveira
21d189eb52 Enable GitHub actions for 'features' 2020-01-14 09:18:34 -03:00
Bruno Oliveira
e9d9f71374 Merge remote-tracking branch 'upstream/master' into mm 2020-01-14 09:17:52 -03:00
Daniel Hahler
a136111dcc tests: test_xfail_handling: use sys.dont_write_bytecode 2020-01-11 23:05:12 +01:00
Daniel Hahler
2d488f7615 Merge pull request #6432 from blueyed/merge-master-into-features
Merge master into features
2020-01-10 14:44:30 +01:00
Daniel Hahler
1356d20e90 Merge master into features 2020-01-10 05:20:41 +01:00
Daniel Hahler
1e28cb855d Merge pull request #6425 from blueyed/xfail-yellow
terminal: use "yellow" with any "xpassed" tests
2020-01-09 23:22:58 +01:00
Bruno Oliveira
29db2da9a7 tmpdir_factory.mktemp now fails given absolute and non-normaliz… (#6323)
tmpdir_factory.mktemp now fails given absolute and non-normalized paths.
2020-01-09 19:10:03 -03:00
Bruno Oliveira
5b295ec68e Merge remote-tracking branch 'upstream/features' into gftea/features 2020-01-09 18:41:47 -03:00
Bruno Oliveira
fa645a7003 Improve docstrings for mktemp 2020-01-09 18:20:46 -03:00
Daniel Hahler
13baab746d terminal: use "yellow" with any "xpassed" tests
Closes https://github.com/pytest-dev/pytest/issues/449.
2020-01-09 22:20:41 +01:00
Daniel Hahler
9f1ade9acf Merge pull request #6411 from blueyed/cacheprovider-copy-items
cacheprovider: pytest_collection_modifyitems: copy items
2020-01-09 22:07:20 +01:00
Daniel Hahler
2d2c67d7c0 cacheprovider: pytest_collection_modifyitems: copy items 2020-01-09 21:48:54 +01:00
Bruno Oliveira
e8c8559efa Remove usage of parser module (deprecated in Python 3.9) (#6407)
Remove usage of parser module (deprecated in Python 3.9)
2020-01-06 13:07:56 -03:00
Bruno Oliveira
91a96ec3d6 Remove usage of parser module, deprecated in Python 3.9
Fix #6404
2020-01-06 08:57:07 -03:00
Bruno Oliveira
12f74a28fa Revert "Remove unused _pytest.code.Source.isparseable function"
This reverts commit c627ac4e59.
2020-01-05 14:12:40 -03:00
Bruno Oliveira
0fa35960ba Remove unused _pytest.code.Source.isparseable function (#6405)
Remove unused _pytest.code.Source.isparseable function
2020-01-05 12:52:23 -03:00
Bruno Oliveira
c627ac4e59 Remove unused _pytest.code.Source.isparseable function
Besides unused, it uses the (deprecated in Python 3.9) parser module

Fix #6404
2020-01-05 12:33:12 -03:00
Ran Benita
0375c1b728 Merge pull request #6388 from bluetech/mypy-761
Update mypy 0.750 -> 0.761
2020-01-01 16:07:55 +02:00
Ran Benita
4848bbdf9a Update mypy 0.750 -> 0.761
This fixes some type: ignores due to typeshed update.

Newer mypy seem to ignore unannotated functions better, so add a few
minor annotations so that existing correct type:ignores make sense.
2020-01-01 15:22:16 +02:00
Daniel Hahler
8656c1a61d Merge pull request #6381 from blueyed/fix-revert-formatting
Revert black formatting of essential_plugins
2020-01-01 14:16:35 +01:00
Daniel Hahler
a5224f7490 Revert black formatting of essential_plugins
Done in a02310a140 (likely automatic), but loses information of the
comment obviously.
2019-12-30 15:28:37 +01:00
Bruno Oliveira
466bbbf8e8 invocation in last section 'pythonpath.rst' title swapped (#6357)
invocation in last section 'pythonpath.rst' title swapped
2019-12-26 08:55:55 -03:00
Bruno Oliveira
a9608d54e0 Switch the order of the commands back and update the text
As suggested during review
2019-12-26 08:19:11 -03:00
captainCapitalism
ed57b8e08a invocation in last section 'pythonpath.rst' title swapped
The order of invocations 'python -m pytest' and 'pytest' are different in the header and the explanation. Me being lazy reading about the behaviour of 'former' looked up quickly the title and rushed to implementation to discover it actually works the other way - as stated in the documentation. So I propose to switch the order in the title to achieve consistent ordering and not confusing somebody like me again! :)
2019-12-19 11:35:52 +01:00
Bruno Oliveira
75493f78bf Merge master into features (#6346)
Merge master into features
2019-12-16 21:57:26 -03:00
Bruno Oliveira
c487cf9dd5 Deprecate no print logs (#6333)
Deprecate no print logs
2019-12-16 19:01:16 -03:00
Vinay Calastry
afbaee7649 Deprecate --no-print-logs option 2019-12-14 16:46:30 -08:00
Bruno Oliveira
853889e5db Merge remote-tracking branch 'upstream/master' into mm 2019-12-14 10:45:44 -03:00
Bruno Oliveira
ecd1e43afb Make 'S' and 'F' aliases to 's' and 'f' respectively (#6337)
Make 'S' and 'F' aliases to 's' and 'f' respectively
2019-12-13 07:47:48 -03:00
gftea
226f0c48bf fix #5686, mktemp now fails given absolute and non-normalized paths. 2019-12-12 13:00:23 +01:00
Bruno Oliveira
9b74bf1e0c Add CHANGELOG entry for #6334 2019-12-12 08:05:22 -03:00
Bruno Oliveira
fa51a26743 Make -r letters "s" and "S" aliases
Similar reasons as the previous commit
2019-12-12 07:48:07 -03:00
Bruno Oliveira
59067ad33d Make -r letters "f" and "F" aliases
As far as the output is concerned, they are both identical so it doesn't
make sense to have both.

setup, teardown, and collect failures are already reported as "errors", "E".
2019-12-12 07:43:37 -03:00
Bruno Oliveira
cbb2f9541b Merge pull request #6316 from cb109/make-keyword-expression-matching-case-insensitive
Make keyword expression matching case-insensitive
2019-12-12 06:47:59 -03:00
Anthony Sottile
b29ae03cb6 Merge pull request #6329 from nicoddemus/4639-improv
Change 4639 from feature to improvement
2019-12-11 13:13:31 -08:00
Bruno Oliveira
0711060422 Change 4639 from feature to improvement
An improvement seems more adequate here.
2019-12-09 15:57:19 -03:00
Bruno Oliveira
1ef29ab548 #4597: tee-stdio capture method (#6315)
#4597: tee-stdio capture method
2019-12-09 15:55:17 -03:00
cmachalo
e13ad22364 Include new --capture-mode=tee-sys option
Fix #4597
2019-12-09 13:05:23 -03:00
Ronny Pfannschmidt
30f2729684 Merge pull request #6298 from RonnyPfannschmidt/deprecation-breakage
Deprecation policy - insert considerations for breaking changes
2019-12-07 19:50:44 +01:00
Ronny Pfannschmidt
3812985ed4 update backward compatibility policy to allow for breakage
Co-Authored-By: Anthony Sottile <asottile@umich.edu>
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
Co-Authored-By: Hugo van Kemenade <hugovk@users.noreply.github.com>
2019-12-07 18:59:21 +01:00
Bruno Oliveira
c6ed69a666 Replace 'removal' by 'breaking' changelog category
As discussed, sometimes we will need to introduce changes
which are not necessarily removals but might break existing
suites
2019-12-06 08:50:35 -03:00
Christoph Bülter
3a0f436c1a Iterate a generator expression instead of a temporary list
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2019-12-06 08:40:49 +01:00
Christoph Buelter
a326fa22c6 Add a failing test to ensure not everything matches by accident 2019-12-05 17:02:48 +01:00
Christoph Bülter
623b3982b0 Update doc/en/example/markers.rst
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2019-12-05 16:59:08 +01:00
Christoph Buelter
5a7de2c2cb Add changelog file for PR 6316 2019-12-05 14:28:21 +01:00
Christoph Buelter
24d4882d82 Update authors file and sort the list 2019-12-05 14:20:07 +01:00
Christoph Buelter
ac5929eef3 Update docs about case-insensitive expression matching 2019-12-05 14:13:22 +01:00
Christoph Buelter
e24b6b0388 Change -k EXPRESSION matching to be case-insensitive 2019-12-05 14:03:10 +01:00
Bruno Oliveira
985ac09048 Merge master into features (#6312)
Merge master into features
2019-12-03 13:36:58 -03:00
Bruno Oliveira
1c4a672a52 unittest: do not use TestCase.debug() with --pdb (#5996)
unittest: do not use TestCase.debug() with `--pdb`
2019-12-03 11:15:06 -03:00
Bruno Oliveira
59f95b7f59 Merge remote-tracking branch 'upstream/master' into mm 2019-12-03 11:07:34 -03:00
Bruno Oliveira
41b7b109e9 Merge branch 'features' into unittest-debug 2019-12-03 10:52:53 -03:00
Ran Benita
c9ec724886 Merge pull request #6307 from bluetech/mypy-750
Update mypy 0.740 -> 0.750
2019-12-03 12:14:19 +02:00
Ran Benita
16ff9f591e Update mypy 0.740 -> 0.750
Release notes:
https://mypy-lang.blogspot.com/2019/11/mypy-0.html
2019-12-02 21:52:11 +02:00
Bruno Oliveira
da091b832d rename test_pdb to test_debugging (#6282)
rename test_pdb to test_debugging
2019-11-27 09:08:02 -03:00
Ronny Pfannschmidt
8feeb09398 fixes #5065 2019-11-27 11:25:23 +01:00
Daniel Hahler
b6b0bc03f3 Merge pull request #6164 from blueyed/pytester-no-color
pytester: use no colors with inline runs by default
2019-11-26 03:30:45 +01:00
Daniel Hahler
a1219ab8fc pytester: use no colors with inline runs by default
Sets `PY_COLORS=0` in the environment by default, which is used by pylib.

Via https://github.com/blueyed/pytest/pull/58

(initially cherry picked from commit f153ad33d10)
2019-11-25 23:48:11 +01:00
Daniel Hahler
47ae1fb36b Merge pull request #6271 from blueyed/typing
typing: minor improvements
2019-11-25 01:49:34 +01:00
Daniel Hahler
bc7282576f typing: minor improvements 2019-11-24 23:20:03 +01:00
Daniel Hahler
d7d5cf4136 Merge pull request #6215 from blueyed/cleanup-exc
_idval: remove trailing newline from exception
2019-11-24 20:29:46 +01:00
Bruno Oliveira
a3c8246b60 Node from parent (#5975)
Node from parent
2019-11-23 20:34:48 -03:00
Ronny Pfannschmidt
15ffe63204 update doc examples **untested** 2019-11-23 21:54:11 +01:00
Ronny Pfannschmidt
c99c7d0f95 deprecate direct node construction and introduce Node.from_parent 2019-11-23 21:54:11 +01:00
Bruno Oliveira
886b8d27c6 Merge pull request #5984 from phloose/acceptance-tests_pytest-dev/pytest/#4445
Add acceptance tests for "config warnings" stacklevel (#4445)
2019-11-22 19:45:41 -03:00
Daniel Hahler
a4408eb9c1 Merge pull request #6219 from blueyed/testdir-use-monkeypatch
pytester: remove special handling of env during inner runs
2019-11-22 22:41:48 +01:00
Daniel Hahler
b0ebcfb785 pytester: remove special handling of env during inner runs
Closes https://github.com/pytest-dev/pytest/issues/6213.
2019-11-22 21:50:31 +01:00
Philipp Loose
a02310a140 Add stacklevel tests for warnings, 'location' to pytest_warning_captured
Resolves #4445 and #5928 (thanks to allanlewis)

Add CHANGELOG for location parameter
2019-11-22 17:50:00 -03:00
Daniel Hahler
2fa0518e89 Merge pull request #6259 from blueyed/merge-master-into-features
Merge master into features
2019-11-21 22:48:53 +01:00
Daniel Hahler
df0c652333 Merge master into features 2019-11-21 20:31:52 +01:00
Ran Benita
5820c5c5a6 Merge pull request #6241 from bluetech/type-annotations-9
Add type annotations to _pytest.config.argparsing and corresponding Config code
2019-11-21 13:33:13 +02:00
Daniel Hahler
ed012c808a Merge pull request #6174 from blueyed/ids-iter
parametrized: ids: support generator/iterator
2019-11-21 00:37:25 +01:00
Daniel Hahler
6b75a7733b Merge pull request #6247 from blueyed/collecterror-fulltrace
Respect --fulltrace with collection errors
2019-11-21 00:27:23 +01:00
Daniel Hahler
2d449e95e4 Respect --fulltrace with collection errors 2019-11-20 23:35:33 +01:00
Ran Benita
dac16cd9e5 Add type annotations to _pytest.config.argparsing and corresponding Config code 2019-11-20 22:02:27 +02:00
Daniel Hahler
2c941b5d13 parametrized: ids: support generator/iterator
Fixes https://github.com/pytest-dev/pytest/issues/759

- Adjust test_parametrized_ids_invalid_type, create list to convert tuples
  Ref: https://github.com/pytest-dev/pytest/issues/1857#issuecomment-552922498

- Changelog for int to str conversion
  Ref: https://github.com/pytest-dev/pytest/issues/1857#issuecomment-552932952
2019-11-20 19:02:17 +01:00
Daniel Hahler
98c899c9b0 Merge pull request #6245 from blueyed/tests-_idval
testing/python/metafunc.py: import _idval once
2019-11-20 19:00:39 +01:00
Daniel Hahler
f1224a0e85 Merge pull request #6243 from blueyed/move-_pformat_dispatch
minor: move internal _pformat_dispatch function
2019-11-20 18:42:21 +01:00
Daniel Hahler
ccb3ef3b33 testing/python/metafunc.py: import _idval once 2019-11-20 18:02:30 +01:00
Daniel Hahler
c0b1a39192 minor: move internal _pformat_dispatch function 2019-11-20 17:57:14 +01:00
Ran Benita
51f9cd0e02 argparsing: remove "map_long_option" Action attribute support
This feature was added in commit
007a77c2ba, but was never used in pytest
itself. A GitHub code search doesn't find any users either (only pytest
repo copies). It seems safe to clean up.
2019-11-20 17:09:24 +02:00
Daniel Hahler
426a4cdca9 _idval: remove trailing newline from exception 2019-11-18 01:10:00 +01:00
Bruno Oliveira
59369651db Bring back explicit tear down
Otherwise 'normal' failures won't call teardown explicitly
2019-11-12 14:03:40 -03:00
Bruno Oliveira
f7b1de70c0 No need to call tearDown on expected failures
- Isolate logic for getting expected exceptions
- Use original method name, as users see it when entering the debugger
2019-11-12 12:50:55 -03:00
Daniel Hahler
04f27d4eb4 unittest: do not use TestCase.debug() with --pdb
Fixes https://github.com/pytest-dev/pytest/issues/5991
Fixes https://github.com/pytest-dev/pytest/issues/3823

Ref: https://github.com/pytest-dev/pytest-django/issues/772
Ref: https://github.com/pytest-dev/pytest/pull/1890
Ref: https://github.com/pytest-dev/pytest-django/pull/782

- inject wrapped testMethod

- adjust test_trial_error

- add test for `--trace` with unittests
2019-11-10 00:21:51 +01:00
181 changed files with 7012 additions and 3137 deletions

View File

@@ -24,5 +24,6 @@ exclude_lines =
\#\s*pragma: no cover
^\s*raise NotImplementedError\b
^\s*return NotImplemented\b
^\s*assert False(,|$)
^\s*if TYPE_CHECKING:

28
.gitblameignore Normal file
View File

@@ -0,0 +1,28 @@
# List of revisions that can be ignored with git-blame(1).
#
# See `blame.ignoreRevsFile` in git-config(1) to enable it by default, or
# use it with `--ignore-revs-file` manually with git-blame.
#
# To "install" it:
#
# git config --local blame.ignoreRevsFile .gitblameignore
# run black
703e4b11ba76171eccd3f13e723c47b810ded7ef
# switched to src layout
eaa882f3d5340956beb176aa1753e07e3f3f2190
# pre-commit run pyupgrade --all-files
a91fe1feddbded535a4322ab854429e3a3961fb4
# move node base classes from main to nodes
afc607cfd81458d4e4f3b1f3cf8cc931b933907e
# [?] split most fixture related code into own plugin
8c49561470708761f7321504f5e8343811be87ac
# run pyupgrade
9aacb4635e81edd6ecf281d4f6c0cfc8e94ab301
# run blacken-docs
5f95dce95602921a70bfbc7d8de2f7712c5e4505
# ran pyupgrade-docs again
75d0b899bbb56d6849e9d69d83a9426ed3f43f8b
# move argument parser to own file
c9df77cbd6a365dcb73c39618e4842711817e871

View File

@@ -3,10 +3,9 @@ Thanks for submitting a PR, your contribution is really appreciated!
Here is a quick checklist that should be present in PRs.
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
- [ ] Include documentation when adding new features.
- [ ] Include new tests or update existing tests when applicable.
- [X] Allow maintainers to push and squash when merging my commits. Please uncheck this if you prefer to squash the commits yourself.
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:

View File

@@ -10,12 +10,14 @@ on:
push:
branches:
- master
- "[0-9]+.[0-9]+.x"
tags:
- "*"
pull_request:
branches:
- master
- "[0-9]+.[0-9]+.x"
jobs:
build:
@@ -37,12 +39,15 @@ jobs:
"ubuntu-py37-pluggy",
"ubuntu-py37-freeze",
"ubuntu-py38",
"ubuntu-py39",
"ubuntu-pypy3",
"macos-py37",
"macos-py38",
"linting",
"docs",
"doctesting",
]
include:
@@ -50,6 +55,7 @@ jobs:
python: "3.5"
os: windows-latest
tox_env: "py35-xdist"
use_coverage: true
- name: "windows-py36"
python: "3.6"
os: windows-latest
@@ -57,7 +63,7 @@ jobs:
- name: "windows-py37"
python: "3.7"
os: windows-latest
tox_env: "py37-twisted-numpy"
tox_env: "py37-numpy"
- name: "windows-py37-pluggy"
python: "3.7"
os: windows-latest
@@ -65,7 +71,8 @@ jobs:
- name: "windows-py38"
python: "3.8"
os: windows-latest
tox_env: "py38"
tox_env: "py38-unittestextras"
use_coverage: true
- name: "ubuntu-py35"
python: "3.5"
@@ -78,7 +85,8 @@ jobs:
- name: "ubuntu-py37"
python: "3.7"
os: ubuntu-latest
tox_env: "py37-lsof-numpy-oldattrs-pexpect-twisted"
tox_env: "py37-lsof-numpy-oldattrs-pexpect"
use_coverage: true
- name: "ubuntu-py37-pluggy"
python: "3.7"
os: ubuntu-latest
@@ -87,18 +95,18 @@ jobs:
python: "3.7"
os: ubuntu-latest
tox_env: "py37-freeze"
# coverage does not apply for freeze test, skip it
skip_coverage: true
- name: "ubuntu-py38"
python: "3.8"
os: ubuntu-latest
tox_env: "py38-xdist"
- name: "ubuntu-py39"
python: "3.8"
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-pypy3"
python: "pypy3"
os: ubuntu-latest
tox_env: "pypy3-xdist"
# coverage too slow with pypy3, skip it
skip_coverage: true
- name: "macos-py37"
python: "3.7"
@@ -108,11 +116,21 @@ jobs:
python: "3.8"
os: macos-latest
tox_env: "py38-xdist"
use_coverage: true
- name: "linting"
python: "3.7"
os: ubuntu-latest
tox_env: "linting,docs,doctesting"
tox_env: "linting"
- name: "docs"
python: "3.7"
os: ubuntu-latest
tox_env: "docs"
- name: "doctesting"
python: "3.7"
os: ubuntu-latest
tox_env: "doctesting"
use_coverage: true
steps:
- uses: actions/checkout@v1
@@ -120,17 +138,23 @@ jobs:
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python }}
- name: install python3.9
if: matrix.tox_env == 'py39-xdist'
run: |
sudo add-apt-repository ppa:deadsnakes/nightly
sudo apt-get update
sudo apt-get install -y --no-install-recommends python3.9-dev python3.9-distutils
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox coverage
- name: Test without coverage
if: "matrix.skip_coverage"
if: "! matrix.use_coverage"
run: "tox -e ${{ matrix.tox_env }}"
- name: Test with coverage
if: "! matrix.skip_coverage"
if: "matrix.use_coverage"
env:
_PYTEST_TOX_COVERAGE_RUN: "coverage run -m"
COVERAGE_PROCESS_START: ".coveragerc"
@@ -138,25 +162,15 @@ jobs:
run: "tox -e ${{ matrix.tox_env }}"
- name: Prepare coverage token
if: success() && !matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' )
if: (matrix.use_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' ))
run: |
python scripts/append_codecov_token.py
- name: Combine coverage
if: success() && !matrix.skip_coverage
run: |
python -m coverage combine
python -m coverage xml
- name: Codecov upload
if: success() && !matrix.skip_coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.codecov }}
file: ./coverage.xml
flags: ${{ runner.os }}
fail_ci_if_error: false
name: ${{ matrix.name }}
- name: Report coverage
if: (matrix.use_coverage)
env:
CODECOV_NAME: ${{ matrix.name }}
run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }}
deploy:
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'

View File

@@ -0,0 +1,28 @@
# part of our release process, see `release-on-comment.py`
name: release on comment
on:
issues:
types: [opened, edited]
issue_comment:
types: [created, edited]
jobs:
build:
runs-on: ubuntu-latest
if: (github.event.comment && startsWith(github.event.comment.body, '@pytestbot please')) || (github.event.issue && !github.event.comment && startsWith(github.event.issue.body, '@pytestbot please'))
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: "3.8"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade setuptools tox
- name: Prepare release
run: |
tox -e release-on-comment -- $GITHUB_EVENT_PATH ${{ secrets.chatops }}

View File

@@ -47,14 +47,14 @@ repos:
- id: rst
name: rst
entry: rst-lint --encoding utf-8
files: ^(HOWTORELEASE.rst|README.rst|TIDELIFT.rst)$
files: ^(RELEASING.rst|README.rst|TIDELIFT.rst)$
language: python
additional_dependencies: [pygments, restructuredtext_lint]
- id: changelogs-rst
name: changelog filenames
language: fail
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
exclude: changelog/(\d+\.(feature|improvement|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
files: ^changelog/
- id: py-deprecated
name: py library is deprecated
@@ -64,7 +64,7 @@ repos:
_code\.|
builtin\.|
code\.|
io\.(BytesIO|saferepr)|
io\.(BytesIO|saferepr|TerminalWriter)|
path\.local\.sysfind|
process\.|
std\.

View File

@@ -1,10 +1,6 @@
language: python
dist: xenial
stages:
- baseline
- test
python: '3.7'
dist: trusty
python: '3.5.1'
cache: false
env:
@@ -20,55 +16,11 @@ install:
jobs:
include:
# OSX tests - first (in test stage), since they are the slower ones.
# Coverage for:
# - osx
# - verbose=1
- os: osx
osx_image: xcode10.1
language: generic
env: TOXENV=py37-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=-v
before_install:
- which python3
- python3 -V
- ln -sfn "$(which python3)" /usr/local/bin/python
- python -V
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
# Full run of latest supported version, without xdist.
# Coverage for:
# - pytester's LsofFdLeakChecker
# - TestArgComplete (linux only)
# - numpy
# - old attrs
# - verbose=0
# - test_sys_breakpoint_interception (via pexpect).
- env: TOXENV=py37-lsof-numpy-oldattrs-pexpect-twisted PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
python: '3.7'
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy3-xdist
python: 'pypy3'
# 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"
python: '3.5.1'
dist: trusty
# Specialized factors for py37.
- env: TOXENV=py37-pluggymaster-xdist
- env: TOXENV=py37-freeze
- env: TOXENV=py38-xdist
python: '3.8'
- stage: baseline
env: TOXENV=py36-xdist
python: '3.6'
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
cache:
directories:
- $HOME/.cache/pre-commit
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:
- |
@@ -89,7 +41,7 @@ script: tox
after_success:
- |
if [[ "$PYTEST_COVERAGE" = 1 ]]; then
env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh
env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh -F Travis
fi
notifications:
@@ -105,6 +57,4 @@ notifications:
branches:
only:
- master
- features
- 4.6-maintenance
- /^\d+(\.\d+)+$/
- /^\d+\.\d+\.x$/

17
AUTHORS
View File

@@ -52,21 +52,21 @@ Carl Friedrich Bolz
Carlos Jenkins
Ceridwen
Charles Cloud
Charles Machalow
Charnjit SiNGH (CCSJ)
Chris Lamb
Chris NeJame
Christian Boelsen
Christian Fetzer
Christian Neumüller
Christian Theunert
Christian Tismer
Christopher Gilling
Christoph Buelter
Christopher Dignam
Christopher Gilling
Claudio Madotto
CrazyMerlyn
Cyrus Maden
Damian Skrzypczak
Dhiren Serai
Daniel Grana
Daniel Hahler
Daniel Nuri
@@ -81,6 +81,7 @@ David Szotten
David Vierra
Daw-Ran Liou
Denis Kirisov
Dhiren Serai
Diego Russo
Dmitry Dygalo
Dmitry Pribysh
@@ -113,6 +114,7 @@ Guido Wesdorp
Guoqiang Zhang
Harald Armin Massa
Henk-Jaap Wagenaar
Holger Kohr
Hugo van Kemenade
Hui Wang (coldnight)
Ian Bicking
@@ -121,6 +123,7 @@ Ilya Konstantinov
Ionuț Turturică
Iwan Briquemont
Jaap Broekhuizen
Jakub Mitoraj
Jan Balster
Janne Vanhala
Jason R. Coombs
@@ -142,6 +145,7 @@ Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
Kale Kundert
Karl O. Pinc
Katarzyna Jachim
Katerina Koukiou
Kevin Cox
@@ -191,6 +195,7 @@ Mihai Capotă
Mike Hoyle (hoylemd)
Mike Lundy
Miro Hrončok
Nathaniel Compton
Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
@@ -207,8 +212,10 @@ Omer Hadari
Ondřej Súkup
Oscar Benjamin
Patrick Hayes
Pauli Virtanen
Paweł Adamczak
Pedro Algarvio
Philipp Loose
Pieter Mulder
Piotr Banaszkiewicz
Pulkit Goyal
@@ -241,6 +248,7 @@ Simon Gomizelj
Skylar Downes
Srinivas Reddy Thatiparthy
Stefan Farmbauer
Stefan Scherfke
Stefan Zimmermann
Stefano Taschini
Steffen Allner
@@ -257,7 +265,9 @@ Tim Hoffmann
Tim Strazny
Tom Dalton
Tom Viner
Tomáš Gavenčiak
Tomer Keren
Tor Colvin
Trevor Bekolay
Tyler Goodlet
Tzu-ping Chung
@@ -268,6 +278,7 @@ Vidar T. Fauske
Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Vladyslav Rachek
Volodymyr Piskun
Wei Lin
Wil Cooley

View File

@@ -71,7 +71,6 @@ contacted individually:
- Brianna Laugher ([@pfctdayelise](https://github.com/pfctdayelise)): brianna@laugher.id.au
- Bruno Oliveira ([@nicoddemus](https://github.com/nicoddemus)): nicoddemus@gmail.com
- Florian Bruhin ([@the-compiler](https://github.com/the-compiler)): pytest@the-compiler.org
- Ronny Pfannschmidt ([@RonnyPfannschmidt](https://github.com/RonnyPfannschmidt)): ich@ronnypfannschmidt.de
## Attribution

View File

@@ -86,7 +86,7 @@ without using a local copy. This can be convenient for small fixes.
$ tox -e docs
The built documentation should be available in the ``doc/en/_build/``.
The built documentation should be available in ``doc/en/_build/html``.
Where 'en' refers to the documentation language.
@@ -166,8 +166,6 @@ Short version
#. Fork the repository.
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
#. Target ``master`` for bugfixes and doc changes.
#. Target ``features`` for new features or functionality changes.
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
#. Tests are run using ``tox``::
@@ -204,15 +202,11 @@ Here is a simple overview, with pytest-specific bits:
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
$ cd pytest
# now, to fix a bug create your own branch off "master":
# now, create your own branch off "master":
$ git checkout -b your-bugfix-branch-name master
# or to instead add a feature create your own branch off "features":
$ git checkout -b your-feature-branch-name features
Given we have "major.minor.micro" version numbers, bugfixes will usually
Given we have "major.minor.micro" version numbers, bug fixes will usually
be released in micro releases whereas features will be released in
minor releases and incompatible changes in major releases.
@@ -294,8 +288,7 @@ Here is a simple overview, with pytest-specific bits:
compare: your-branch-name
base-fork: pytest-dev/pytest
base: master # if it's a bugfix
base: features # if it's a feature
base: master
Writing Tests

View File

@@ -1,60 +0,0 @@
Release Procedure
-----------------
Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea
is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
taking a lot of time to make a new one.
.. important::
pytest releases must be prepared on **Linux** because the docs and examples expect
to be executed in that platform.
#. Create a branch ``release-X.Y.Z`` with the version for the release.
* **maintenance releases**: from ``4.6-maintenance``;
* **patch releases**: from the latest ``master``;
* **minor releases**: from the latest ``features``; then merge with the latest ``master``;
Ensure your are in a clean work tree.
#. Using ``tox``, generate docs, changelog, announcements::
$ tox -e release -- <VERSION>
This will generate a commit with all the changes ready for pushing.
#. Open a PR for this branch targeting ``master`` (or ``4.6-maintenance`` for
maintenance releases).
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
git tag <VERSION>
git push git@github.com:pytest-dev/pytest.git <VERSION>
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR.
#. If this is a maintenance release, cherry-pick the CHANGELOG / announce
files to the ``master`` branch::
git fetch --all --prune
git checkout origin/master -b cherry-pick-maintenance-release
git cherry-pick --no-commit -m1 origin/4.6-maintenance
git checkout origin/master -- changelog
git commit # no arguments
#. Send an email announcement with the contents from::
doc/en/announce/release-<VERSION>.rst
To the following mailing lists:
* pytest-dev@python.org (all releases)
* python-announce-list@python.org (all releases)
* testing-in-python@lists.idyll.org (only major/minor releases)
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.

View File

@@ -31,6 +31,10 @@
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
:target: https://www.codetriage.com/pytest-dev/pytest
.. image:: https://readthedocs.org/projects/pytest/badge/?version=latest
:target: https://pytest.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
The ``pytest`` framework makes it easy to write small tests, yet
scales to support complex functional testing for applications and libraries.

82
RELEASING.rst Normal file
View File

@@ -0,0 +1,82 @@
Release Procedure
-----------------
Our current policy for releasing is to aim for a bug-fix release every few weeks and a minor release every 2-3 months. The idea
is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
taking a lot of time to make a new one.
Preparing: Automatic Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~
We have developed an automated workflow for releases, that uses GitHub workflows and is triggered
by opening an issue or issuing a comment one.
The comment must be in the form::
@pytestbot please prepare release from BRANCH
Where ``BRANCH`` is ``master`` or one of the maintenance branches.
After that, the workflow should publish a PR and notify that it has done so as a comment
in the original issue.
Preparing: Manual Method
~~~~~~~~~~~~~~~~~~~~~~~~
.. important::
pytest releases must be prepared on **Linux** because the docs and examples expect
to be executed on that platform.
To release a version ``MAJOR.MINOR.PATCH``, follow these steps:
#. For major and minor releases, create a new branch ``MAJOR.MINOR.x`` from the
latest ``master`` and push it to the ``pytest-dev/pytest`` repo.
#. Create a branch ``release-MAJOR.MINOR.PATCH`` from the ``MAJOR.MINOR.x`` branch.
Ensure your are updated and in a clean working tree.
#. Using ``tox``, generate docs, changelog, announcements::
$ tox -e release -- MAJOR.MINOR.PATCH
This will generate a commit with all the changes ready for pushing.
#. Open a PR for the ``release-MAJOR.MINOR.PATCH`` branch targeting ``MAJOR.MINOR.x``.
Releasing
~~~~~~~~~
Both automatic and manual processes described above follow the same steps from this point onward.
#. After all tests pass and the PR has been approved, tag the release commit
in the ``MAJOR.MINOR.x`` branch and push it. This will publish to PyPI::
git tag MAJOR.MINOR.PATCH
git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR.
#. Cherry-pick the CHANGELOG / announce files to the ``master`` branch::
git fetch --all --prune
git checkout origin/master -b cherry-pick-release
git cherry-pick --no-commit -m1 origin/MAJOR.MINOR.x
git checkout origin/master -- changelog
git commit # no arguments
#. Send an email announcement with the contents from::
doc/en/announce/release-<VERSION>.rst
To the following mailing lists:
* pytest-dev@python.org (all releases)
* python-announce-list@python.org (all releases)
* testing-in-python@lists.idyll.org (only major/minor releases)
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.

View File

@@ -1,80 +0,0 @@
trigger:
- master
- features
variables:
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
PYTEST_COVERAGE: '0'
jobs:
- job: 'Test'
pool:
vmImage: "vs2017-win2016"
strategy:
matrix:
# -- pypy3 disabled for now: #5279 --
# pypy3:
# python.version: 'pypy3'
# tox.env: 'pypy3'
py35-xdist:
python.version: '3.5'
tox.env: 'py35-xdist'
# Coverage for:
# - test_supports_breakpoint_module_global
PYTEST_COVERAGE: '1'
py36-xdist:
python.version: '3.6'
tox.env: 'py36-xdist'
py37:
python.version: '3.7'
tox.env: 'py37-twisted-numpy'
# Coverage for:
# - _py36_windowsconsoleio_workaround (with py36+)
# - test_request_garbage (no xdist)
PYTEST_COVERAGE: '1'
py37-linting/docs/doctesting:
python.version: '3.7'
tox.env: 'linting,docs,doctesting'
py37-pluggymaster-xdist:
python.version: '3.7'
tox.env: 'py37-pluggymaster-xdist'
py38-xdist:
python.version: '3.8'
tox.env: 'py38-xdist'
maxParallel: 10
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
architecture: 'x64'
- script: python -m pip install --upgrade pip && python -m pip install tox
displayName: 'Install tox'
- bash: |
if [[ "$PYTEST_COVERAGE" == "1" ]]; then
export _PYTEST_TOX_COVERAGE_RUN="coverage run -m"
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
export COVERAGE_FILE="$PWD/.coverage"
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
fi
python -m tox -e $(tox.env)
displayName: 'Run tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: 'build/test-results/$(tox.env).xml'
testRunTitle: '$(tox.env)'
condition: succeededOrFailed()
- bash: |
if [[ "$PYTEST_COVERAGE" == 1 ]]; then
scripts/report-coverage.sh
fi
env:
CODECOV_NAME: $(tox.env)
CODECOV_TOKEN: $(CODECOV_TOKEN)
displayName: Report and upload coverage
condition: eq(variables['PYTEST_COVERAGE'], '1')

View File

@@ -15,10 +15,10 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
* ``feature``: new user facing features, like new command-line options and new behavior.
* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
* ``bugfix``: fixes a reported bug.
* ``bugfix``: fixes a bug.
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
* ``deprecation``: feature deprecation.
* ``removal``: feature removal.
* ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
* ``vendor``: changes in packages vendored in pytest.
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
@@ -34,4 +34,4 @@ If you are not sure what issue type to use, don't hesitate to ask in your PR.
other than ``features`` it is usually better to stick to a single paragraph to keep it concise.
You can also run ``tox -e docs`` to build the documentation
with the draft changelog (``doc/en/_build/changelog.html``) if you want to get a preview of how your change will look in the final release notes.
with the draft changelog (``doc/en/_build/html/changelog.html``) if you want to get a preview of how your change will look in the final release notes.

View File

@@ -1,7 +1,6 @@
# reference: https://docs.codecov.io/docs/codecovyml-reference
coverage:
status:
project: true
patch: true
changes: true
comment: off
project: false
comment: false

View File

@@ -1,16 +1,24 @@
# Makefile for Sphinx documentation
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
REGENDOC_ARGS := \
--normalize "/[ \t]+\n/\n/" \
@@ -25,130 +33,8 @@ REGENDOC_ARGS := \
--normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \
--normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@"
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " showtarget to show the pytest.org target directory"
@echo " install to install docs to pytest.org/SITETARGET"
@echo " install-ldf to install the doc pdf to pytest.org/SITETARGET"
@echo " regen to regenerate pytest examples using the installed pytest"
@echo " linkcheck to check all external links for integrity"
clean:
-rm -rf $(BUILDDIR)/*
regen: REGENDOC_FILES:=*.rst */*.rst
regen:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS="-pno:hypothesis -Wignore::pytest.PytestUnknownMarkWarning" COLUMNS=76 regendoc --update ${REGENDOC_FILES} ${REGENDOC_ARGS}
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/pytest"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
texinfo:
mkdir -p $(BUILDDIR)/texinfo
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
mkdir -p $(BUILDDIR)/texinfo
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: regen

View File

@@ -21,3 +21,7 @@
<hr>
{{ toc }}
{%- endif %}
<hr>
<a href="{{ pathto('genindex') }}">Index</a>
<hr>

View File

@@ -1,20 +0,0 @@
{% extends "!layout.html" %}
{% block header %}
{{super()}}
{% endblock %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-7597274-13']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
{% endblock %}

View File

@@ -6,6 +6,12 @@ Release announcements
:maxdepth: 2
release-5.4.3
release-5.4.2
release-5.4.1
release-5.4.0
release-5.3.5
release-5.3.4
release-5.3.3
release-5.3.2
release-5.3.1

View File

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

View File

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

View File

@@ -0,0 +1,59 @@
pytest-5.4.0
=======================================
The pytest team is proud to announce the 5.4.0 release!
pytest is a mature Python testing tool with more than a 2000 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bug fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
https://docs.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/latest/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Bruno Oliveira
* Christoph Buelter
* Christoph Bülter
* Daniel Arndt
* Daniel Hahler
* Holger Kohr
* Hugo
* Hugo van Kemenade
* Jakub Mitoraj
* Kyle Altendorf
* Minuddin Ahmed Rana
* Nathaniel Compton
* ParetoLife
* Pauli Virtanen
* Philipp Loose
* Ran Benita
* Ronny Pfannschmidt
* Stefan Scherfke
* Stefano Mazzucco
* TWood67
* Tobias Schmidt
* Tomáš Gavenčiak
* Vinay Calastry
* Vladyslav Rachek
* Zac Hatfield-Dodds
* captainCapitalism
* cmachalo
* gftea
* kpinc
* rebecca-palmer
* sdementen
Happy testing,
The pytest Development Team

View File

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

View File

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

View File

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

View File

@@ -47,6 +47,8 @@ you will see the return value of the function call:
E + where 3 = f()
test_assert1.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert1.py::test_function - assert 3 == 4
============================ 1 failed in 0.12s =============================
``pytest`` has support for showing the values of the most common subexpressions
@@ -208,6 +210,8 @@ if you run this module:
E Use -v to get the full diff
test_assert2.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert2.py::test_set_comparison - AssertionError: assert {'0'...
============================ 1 failed in 0.12s =============================
Special comparisons are done for a number of cases:
@@ -279,6 +283,8 @@ the conftest file:
E vals: 1 != 2
test_foocompare.py:12: AssertionError
========================= short test summary info ==========================
FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
1 failed in 0.12s
.. _assert-details:

View File

@@ -3,6 +3,61 @@
Backwards Compatibility Policy
==============================
.. versionadded: 6.0
pytest is actively evolving and is a project that has been decades in the making,
we keep learning about new and better structures to express different details about testing.
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:
a) trivial: APIs which trivially translate to the new mechanism,
and do not cause problematic changes.
We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation.
b) transitional: the old and new API don't conflict
and we can help users transition by using warnings, while supporting both for a prolonged time.
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
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.
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:
* removal of ``pytest_runtest_protocol/nextitem`` - `#895`_
* rearranging of the node tree to include ``FunctionDefinition``
* rearranging of ``SetupState`` `#895`_
True breakages must be announced first in an issue containing:
* Detailed description of the change
* Rationale
* Expected impact on users and plugin authors (example in `#895`_)
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.
After a reasonable amount of time the PR can be merged to base a new major release.
For the PR to mature from POC to acceptance, it must contain:
* Setup of deprecation errors/warnings that help users fix and port their code. If it is possible to introduce a deprecation period under the current series, before the true breakage, it should be introduced in a separate PR and be part of the current release stream.
* Detailed description of the rationale and examples on how to port code in ``doc/en/deprecations.rst``.
History
=========
Focus primary on smooth transition - stance (pre 6.0)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
@@ -20,3 +75,6 @@ Deprecation Roadmap
Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`.
We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.
.. _`#895`: https://github.com/pytest-dev/pytest/issues/895

View File

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

View File

@@ -75,6 +75,9 @@ If you run this for the first time you will see two failures:
E Failed: bad luck
test_50.py:7: Failed
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
2 failed, 48 passed in 0.12s
If you then run it with ``--lf``:
@@ -86,7 +89,7 @@ If you then run it with ``--lf``:
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 50 items / 48 deselected / 2 selected
collected 2 items
run-last-failure: rerun previous 2 failures
test_50.py FF [100%]
@@ -114,7 +117,10 @@ If you then run it with ``--lf``:
E Failed: bad luck
test_50.py:7: Failed
===================== 2 failed, 48 deselected in 0.12s =====================
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
============================ 2 failed in 0.12s =============================
You have run only the two failing tests from the last run, while the 48 passing
tests have not been run ("deselected").
@@ -158,6 +164,9 @@ of ``FF`` and dots):
E Failed: bad luck
test_50.py:7: Failed
========================= short test summary info ==========================
FAILED test_50.py::test_num[17] - Failed: bad luck
FAILED test_50.py::test_num[25] - Failed: bad luck
======================= 2 failed, 48 passed in 0.12s =======================
.. _`config.cache`:
@@ -230,6 +239,8 @@ If you run this command for the first time, you can see the print statement:
test_caching.py:20: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
========================= short test summary info ==========================
FAILED test_caching.py::test_function - assert 42 == 23
1 failed in 0.12s
If you run it a second time, the value will be retrieved from
@@ -249,9 +260,11 @@ the cache and nothing will be printed:
E assert 42 == 23
test_caching.py:20: AssertionError
========================= short test summary info ==========================
FAILED test_caching.py::test_function - assert 42 == 23
1 failed in 0.12s
See the :ref:`cache-api` for more details.
See the :fixture:`config.cache fixture <config.cache>` for more details.
Inspecting Cache content

View File

@@ -21,27 +21,36 @@ file descriptors. This allows to capture output from simple
print statements as well as output from a subprocess started by
a test.
.. _capture-method:
Setting capturing methods or disabling capturing
-------------------------------------------------
There are two ways in which ``pytest`` can perform capturing:
There are three ways in which ``pytest`` can perform capturing:
* file descriptor (FD) level capturing (default): All writes going to the
* ``fd`` (file descriptor) level capturing (default): All writes going to the
operating system file descriptors 1 and 2 will be captured.
* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
and ``sys.stderr`` will be captured. No capturing of writes to
filedescriptors is performed.
* ``tee-sys`` capturing: Python writes to ``sys.stdout`` and ``sys.stderr``
will be captured, however the writes will also be passed-through to
the actual ``sys.stdout`` and ``sys.stderr``. This allows output to be
'live printed' and captured for plugin use, such as junitxml (new in pytest 5.4).
.. _`disable capturing`:
You can influence output capturing mechanisms from the command line:
.. code-block:: bash
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
pytest --capture=fd # also point filedescriptors 1 and 2 to temp file
pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr
# and passing it along to the actual sys.stdout/stderr
.. _printdebugging:
@@ -91,6 +100,8 @@ of the failing function and hide the other one:
test_module.py:12: AssertionError
-------------------------- Captured stdout setup ---------------------------
setting up <function test_func2 at 0xdeadbeef>
========================= short test summary info ==========================
FAILED test_module.py::test_func2 - assert False
======================= 1 failed, 1 passed in 0.12s ========================
Accessing captured output from a test function
@@ -159,5 +170,3 @@ as a context manager, disabling capture inside the ``with`` block:
with capsys.disabled():
print("output not captured, going directly to sys.stdout")
print("this output is also captured")
.. include:: links.inc

View File

@@ -28,6 +28,315 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 5.4.3 (2020-06-02)
=========================
Bug Fixes
---------
- `#6428 <https://github.com/pytest-dev/pytest/issues/6428>`_: Paths appearing in error messages are now correct in case the current working directory has
changed since the start of the session.
- `#6755 <https://github.com/pytest-dev/pytest/issues/6755>`_: Support deleting paths longer than 260 characters on windows created inside tmpdir.
- `#6956 <https://github.com/pytest-dev/pytest/issues/6956>`_: Prevent pytest from printing ConftestImportFailure traceback to stdout.
- `#7150 <https://github.com/pytest-dev/pytest/issues/7150>`_: Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised.
- `#7215 <https://github.com/pytest-dev/pytest/issues/7215>`_: Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase``
subclasses for skipped tests.
pytest 5.4.2 (2020-05-08)
=========================
Bug Fixes
---------
- `#6871 <https://github.com/pytest-dev/pytest/issues/6871>`_: Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`.
- `#6924 <https://github.com/pytest-dev/pytest/issues/6924>`_: Ensure a ``unittest.IsolatedAsyncioTestCase`` is actually awaited.
- `#6925 <https://github.com/pytest-dev/pytest/issues/6925>`_: Fix TerminalRepr instances to be hashable again.
- `#6947 <https://github.com/pytest-dev/pytest/issues/6947>`_: Fix regression where functions registered with ``TestCase.addCleanup`` were not being called on test failures.
- `#6951 <https://github.com/pytest-dev/pytest/issues/6951>`_: Allow users to still set the deprecated ``TerminalReporter.writer`` attribute.
- `#6992 <https://github.com/pytest-dev/pytest/issues/6992>`_: Revert "tmpdir: clean up indirection via config for factories" #6767 as it breaks pytest-xdist.
- `#7110 <https://github.com/pytest-dev/pytest/issues/7110>`_: Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again.
- `#7143 <https://github.com/pytest-dev/pytest/issues/7143>`_: Fix ``File.from_constructor`` so it forwards extra keyword arguments to the constructor.
- `#7145 <https://github.com/pytest-dev/pytest/issues/7145>`_: Classes with broken ``__getattribute__`` methods are displayed correctly during failures.
- `#7180 <https://github.com/pytest-dev/pytest/issues/7180>`_: Fix ``_is_setup_py`` for files encoded differently than locale.
pytest 5.4.1 (2020-03-13)
=========================
Bug Fixes
---------
- `#6909 <https://github.com/pytest-dev/pytest/issues/6909>`_: Revert the change introduced by `#6330 <https://github.com/pytest-dev/pytest/pull/6330>`_, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
- `#6910 <https://github.com/pytest-dev/pytest/issues/6910>`_: Fix crash when plugins return an unknown stats while using the ``--reportlog`` option.
pytest 5.4.0 (2020-03-12)
=========================
Breaking Changes
----------------
- `#6316 <https://github.com/pytest-dev/pytest/issues/6316>`_: Matching of ``-k EXPRESSION`` to test names is now case-insensitive.
- `#6443 <https://github.com/pytest-dev/pytest/issues/6443>`_: Plugins specified with ``-p`` are now loaded after internal plugins, which results in their hooks being called *before* the internal ones.
This makes the ``-p`` behavior consistent with ``PYTEST_PLUGINS``.
- `#6637 <https://github.com/pytest-dev/pytest/issues/6637>`_: Removed the long-deprecated ``pytest_itemstart`` hook.
This hook has been marked as deprecated and not been even called by pytest for over 10 years now.
- `#6673 <https://github.com/pytest-dev/pytest/issues/6673>`_: Reversed / fix meaning of "+/-" in error diffs. "-" means that sth. expected is missing in the result and "+" means that there are unexpected extras in the result.
- `#6737 <https://github.com/pytest-dev/pytest/issues/6737>`_: The ``cached_result`` attribute of ``FixtureDef`` is now set to ``None`` when
the result is unavailable, instead of being deleted.
If your plugin performs checks like ``hasattr(fixturedef, 'cached_result')``,
for example in a ``pytest_fixture_post_finalizer`` hook implementation, replace
it with ``fixturedef.cached_result is not None``. If you ``del`` the attribute,
set it to ``None`` instead.
Deprecations
------------
- `#3238 <https://github.com/pytest-dev/pytest/issues/3238>`_: Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
provide feedback.
``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
- `#571 <https://github.com/pytest-dev/pytest/issues/571>`_: Deprecate the unused/broken `pytest_collect_directory` hook.
It was misaligned since the removal of the ``Directory`` collector in 2010
and incorrect/unusable as soon as collection was split from test execution.
- `#5975 <https://github.com/pytest-dev/pytest/issues/5975>`_: Deprecate using direct constructors for ``Nodes``.
Instead they are new constructed via ``Node.from_parent``.
This transitional mechanism enables us to detangle the very intensely
entangled ``Node`` relationships by enforcing more controlled creation/configruation patterns.
As part of that session/config are already disallowed parameters and as we work on the details we might need disallow a few more as well.
Subclasses are expected to use `super().from_parent` if they intend to expand the creation of `Nodes`.
- `#6779 <https://github.com/pytest-dev/pytest/issues/6779>`_: The ``TerminalReporter.writer`` attribute has been deprecated and should no longer be used. This
was inadvertently exposed as part of the public API of that plugin and ties it too much
with ``py.io.TerminalWriter``.
Features
--------
- `#4597 <https://github.com/pytest-dev/pytest/issues/4597>`_: New :ref:`--capture=tee-sys <capture-method>` option to allow both live printing and capturing of test output.
- `#5712 <https://github.com/pytest-dev/pytest/issues/5712>`_: Now all arguments to ``@pytest.mark.parametrize`` need to be explicitly declared in the function signature or via ``indirect``.
Previously it was possible to omit an argument if a fixture with the same name existed, which was just an accident of implementation and was not meant to be a part of the API.
- `#6454 <https://github.com/pytest-dev/pytest/issues/6454>`_: Changed default for `-r` to `fE`, which displays failures and errors in the :ref:`short test summary <pytest.detailed_failed_tests_usage>`. `-rN` can be used to disable it (the old behavior).
- `#6469 <https://github.com/pytest-dev/pytest/issues/6469>`_: New options have been added to the :confval:`junit_logging` option: ``log``, ``out-err``, and ``all``.
- `#6834 <https://github.com/pytest-dev/pytest/issues/6834>`_: Excess warning summaries are now collapsed per file to ensure readable display of warning summaries.
Improvements
------------
- `#1857 <https://github.com/pytest-dev/pytest/issues/1857>`_: ``pytest.mark.parametrize`` accepts integers for ``ids`` again, converting it to strings.
- `#449 <https://github.com/pytest-dev/pytest/issues/449>`_: Use "yellow" main color with any XPASSED tests.
- `#4639 <https://github.com/pytest-dev/pytest/issues/4639>`_: Revert "A warning is now issued when assertions are made for ``None``".
The warning proved to be less useful than initially expected and had quite a
few false positive cases.
- `#5686 <https://github.com/pytest-dev/pytest/issues/5686>`_: ``tmpdir_factory.mktemp`` now fails when given absolute and non-normalized paths.
- `#5984 <https://github.com/pytest-dev/pytest/issues/5984>`_: The ``pytest_warning_captured`` hook now receives a ``location`` parameter with the code location that generated the warning.
- `#6213 <https://github.com/pytest-dev/pytest/issues/6213>`_: pytester: the ``testdir`` fixture respects environment settings from the ``monkeypatch`` fixture for inner runs.
- `#6247 <https://github.com/pytest-dev/pytest/issues/6247>`_: ``--fulltrace`` is honored with collection errors.
- `#6384 <https://github.com/pytest-dev/pytest/issues/6384>`_: Make `--showlocals` work also with `--tb=short`.
- `#6653 <https://github.com/pytest-dev/pytest/issues/6653>`_: Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`.
- `#6658 <https://github.com/pytest-dev/pytest/issues/6658>`_: Code is now highlighted in tracebacks when ``pygments`` is installed.
Users are encouraged to install ``pygments`` into their environment and provide feedback, because
the plan is to make ``pygments`` a regular dependency in the future.
- `#6795 <https://github.com/pytest-dev/pytest/issues/6795>`_: Import usage error message with invalid `-o` option.
- `#759 <https://github.com/pytest-dev/pytest/issues/759>`_: ``pytest.mark.parametrize`` supports iterators and generators for ``ids``.
Bug Fixes
---------
- `#310 <https://github.com/pytest-dev/pytest/issues/310>`_: Add support for calling `pytest.xfail()` and `pytest.importorskip()` with doctests.
- `#3823 <https://github.com/pytest-dev/pytest/issues/3823>`_: ``--trace`` now works with unittests.
- `#4445 <https://github.com/pytest-dev/pytest/issues/4445>`_: Fixed some warning reports produced by pytest to point to the correct location of the warning in the user's code.
- `#5301 <https://github.com/pytest-dev/pytest/issues/5301>`_: Fix ``--last-failed`` to collect new tests from files with known failures.
- `#5928 <https://github.com/pytest-dev/pytest/issues/5928>`_: Report ``PytestUnknownMarkWarning`` at the level of the user's code, not ``pytest``'s.
- `#5991 <https://github.com/pytest-dev/pytest/issues/5991>`_: Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``.
- `#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.
- `#6409 <https://github.com/pytest-dev/pytest/issues/6409>`_: Fallback to green (instead of yellow) for non-last items without previous passes with colored terminal progress indicator.
- `#6454 <https://github.com/pytest-dev/pytest/issues/6454>`_: `--disable-warnings` is honored with `-ra` and `-rA`.
- `#6497 <https://github.com/pytest-dev/pytest/issues/6497>`_: Fix bug in the comparison of request key with cached key in fixture.
A construct ``if key == cached_key:`` can fail either because ``==`` is explicitly disallowed, or for, e.g., NumPy arrays, where the result of ``a == b`` cannot generally be converted to `bool`.
The implemented fix replaces `==` with ``is``.
- `#6557 <https://github.com/pytest-dev/pytest/issues/6557>`_: Make capture output streams ``.write()`` method return the same return value from original streams.
- `#6566 <https://github.com/pytest-dev/pytest/issues/6566>`_: Fix ``EncodedFile.writelines`` to call the underlying buffer's ``writelines`` method.
- `#6575 <https://github.com/pytest-dev/pytest/issues/6575>`_: Fix internal crash when ``faulthandler`` starts initialized
(for example with ``PYTHONFAULTHANDLER=1`` environment variable set) and ``faulthandler_timeout`` defined
in the configuration file.
- `#6597 <https://github.com/pytest-dev/pytest/issues/6597>`_: Fix node ids which contain a parametrized empty-string variable.
- `#6646 <https://github.com/pytest-dev/pytest/issues/6646>`_: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
- `#6660 <https://github.com/pytest-dev/pytest/issues/6660>`_: :func:`pytest.exit() <_pytest.outcomes.exit>` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger.
- `#6752 <https://github.com/pytest-dev/pytest/issues/6752>`_: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager),
a `match` keyword argument is now passed through to the tested function. Previously
it was swallowed and ignored (regression in pytest 5.1.0).
- `#6801 <https://github.com/pytest-dev/pytest/issues/6801>`_: Do not display empty lines inbetween traceback for unexpected exceptions with doctests.
- `#6802 <https://github.com/pytest-dev/pytest/issues/6802>`_: The :fixture:`testdir fixture <testdir>` works within doctests now.
Improved Documentation
----------------------
- `#6696 <https://github.com/pytest-dev/pytest/issues/6696>`_: Add list of fixtures to start of fixture chapter.
- `#6742 <https://github.com/pytest-dev/pytest/issues/6742>`_: Expand first sentence on fixtures into a paragraph.
Trivial/Internal Changes
------------------------
- `#6404 <https://github.com/pytest-dev/pytest/issues/6404>`_: Remove usage of ``parser`` module, deprecated in Python 3.9.
pytest 5.3.5 (2020-01-29)
=========================
Bug Fixes
---------
- `#6517 <https://github.com/pytest-dev/pytest/issues/6517>`_: Fix regression in pytest 5.3.4 causing an INTERNALERROR due to a wrong assertion.
pytest 5.3.4 (2020-01-20)
=========================
Bug Fixes
---------
- `#6496 <https://github.com/pytest-dev/pytest/issues/6496>`_: Revert `#6436 <https://github.com/pytest-dev/pytest/issues/6436>`__: unfortunately this change has caused a number of regressions in many suites,
so the team decided to revert this change and make a new release while we continue to look for a solution.
pytest 5.3.3 (2020-01-16)
=========================
@@ -161,7 +470,7 @@ Features
rather than implicitly.
- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: :ref:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and
- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: :fixture:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and
:py:func:`~_pytest.pytester.LineMatcher.no_re_match_line`.
The functions are used to ensure the captured text *does not* match the given
@@ -2347,7 +2656,7 @@ Deprecations and Removals
- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after
the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``.
Our policy is to not deprecate features during bugfix releases, but in this case we believe it makes sense as we are
Our policy is to not deprecate features during bug-fix releases, but in this case we believe it makes sense as we are
only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get
the word out that hook implementers should not use this parameter at all.
@@ -5370,7 +5679,7 @@ time or change existing behaviors in order to make them less surprising/more use
Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
- (experimental) adapt more SEMVER style versioning and change meaning of
master branch in git repo: "master" branch now keeps the bugfixes, changes
master branch in git repo: "master" branch now keeps the bug fixes, changes
aimed for micro releases. "features" branch will only be released
with minor or major pytest releases.

View File

@@ -83,7 +83,6 @@ copyright = "20152020, holger krekel and pytest-dev team"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = [
"links.inc",
"_build",
"naming20.rst",
"test/*",
@@ -162,7 +161,7 @@ html_logo = "img/pytest1.png"
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "img/pytest1favi.ico"
html_favicon = "img/favicon.png"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -208,7 +207,7 @@ html_sidebars = {
html_domain_indices = True
# If false, no index is generated.
html_use_index = False
html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
@@ -373,10 +372,18 @@ def configure_logging(app: "sphinx.application.Sphinx") -> None:
def setup(app: "sphinx.application.Sphinx") -> None:
# from sphinx.ext.autodoc import cut_lines
# app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
app.add_crossref_type(
"fixture",
"fixture",
objname="built-in fixture",
indextemplate="pair: %s; fixture",
)
app.add_object_type(
"confval",
"confval",
objname="configuration value",
indextemplate="pair: %s; configuration value",
)
configure_logging(app)

View File

@@ -19,6 +19,30 @@ Below is a complete list of all pytest features which are considered deprecated.
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
:ref:`standard warning filters <warnings>`.
``--no-print-logs`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.4
Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and
provide feedback.
``--show-capture`` command-line option was added in ``pytest 3.5.0` and allows to specify how to
display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).
Node Construction changed to ``Node.from_parent``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.4
The construction of nodes new should use the named constructor ``from_parent``.
This limitation in api surface intends to enable better/simpler refactoring of the collection tree.
``junit_family`` default value change to "xunit2"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -71,6 +95,18 @@ The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportl
to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core
at some point, depending on the plans for the plugins and number of users using it.
TerminalReporter.writer
~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.4
The ``TerminalReporter.writer`` attribute has been deprecated and should no longer be used. This
was inadvertently exposed as part of the public API of that plugin and ties it too much
with ``py.io.TerminalWriter``.
Plugins that used ``TerminalReporter.writer`` directly should instead use ``TerminalReporter``
methods that provide the same functionality.
Removed Features
----------------

View File

@@ -19,7 +19,7 @@ Branches
We have two long term branches:
* ``master``: contains the code for the next bugfix release.
* ``master``: contains the code for the next bug-fix release.
* ``features``: contains the code with new features for the next minor release.
The official repository usually does not contain topic branches, developers and contributors should create topic
@@ -57,4 +57,4 @@ Issues created at those events should have other relevant labels added as well.
Those labels should be removed after they are no longer relevant.
.. include:: ../../HOWTORELEASE.rst
.. include:: ../../RELEASING.rst

View File

@@ -1,20 +0,0 @@
import pytest
@pytest.fixture(scope="session")
def setup(request):
setup = CostlySetup()
yield setup
setup.finalize()
class CostlySetup:
def __init__(self):
import time
print("performing costly setup")
time.sleep(5)
self.timecostly = 1
def finalize(self):
del self.timecostly

View File

@@ -1 +0,0 @@
#

View File

@@ -1,2 +0,0 @@
def test_quick(setup):
pass

View File

@@ -1 +0,0 @@
#

View File

@@ -1,6 +0,0 @@
def test_something(setup):
assert setup.timecostly == 1
def test_something_more(setup):
assert setup.timecostly == 1

View File

@@ -148,6 +148,10 @@ which implements a substring match on the test names instead of the
exact match on markers that ``-m`` provides. This makes it easy to
select tests based on their names:
.. versionadded: 5.4
The expression matching is now case-insensitive.
.. code-block:: pytest
$ pytest -v -k http # running with the above defined example module
@@ -711,6 +715,9 @@ We can now use the ``-m option`` to select one set:
test_module.py:8: in test_interface_complex
assert 0
E assert 0
========================= short test summary info ==========================
FAILED test_module.py::test_interface_simple - assert 0
FAILED test_module.py::test_interface_complex - assert 0
===================== 2 failed, 2 deselected in 0.12s ======================
or to select both "event" and "interface" tests:
@@ -739,4 +746,8 @@ or to select both "event" and "interface" tests:
test_module.py:12: in test_event_simple
assert 0
E assert 0
========================= short test summary info ==========================
FAILED test_module.py::test_interface_simple - assert 0
FAILED test_module.py::test_interface_complex - assert 0
FAILED test_module.py::test_event_simple - assert 0
===================== 3 failed, 1 deselected in 0.12s ======================

View File

@@ -41,6 +41,8 @@ now execute the test specification:
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAILED test_simple.yaml::hello
======================= 1 failed, 1 passed in 0.12s ========================
.. regendoc:wipe
@@ -77,6 +79,8 @@ consulted when reporting in ``verbose`` mode:
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAILED test_simple.yaml::hello
======================= 1 failed, 1 passed in 0.12s ========================
.. regendoc:wipe

View File

@@ -4,7 +4,7 @@ import pytest
def pytest_collect_file(parent, path):
if path.ext == ".yaml" and path.basename.startswith("test"):
return YamlFile(path, parent)
return YamlFile.from_parent(parent, fspath=path)
class YamlFile(pytest.File):
@@ -13,7 +13,7 @@ class YamlFile(pytest.File):
raw = yaml.safe_load(self.fspath.open())
for name, spec in sorted(raw.items()):
yield YamlItem(name, self, spec)
yield YamlItem.from_parent(self, name=name, spec=spec)
class YamlItem(pytest.Item):

View File

@@ -73,6 +73,8 @@ let's run the full monty:
E assert 4 < 4
test_compute.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_compute.py::test_compute[4] - assert 4 < 4
1 failed, 4 passed in 0.12s
As expected when running the full range of ``param1`` values
@@ -343,6 +345,8 @@ And then when we run the test:
E Failed: deliberately failing for demo purposes
test_backends.py:8: Failed
========================= short test summary info ==========================
FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately f...
1 failed, 1 passed in 0.12s
The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
@@ -454,6 +458,8 @@ argument sets to use for each test function. Let's run it:
E assert 1 == 2
test_parametrize.py:21: AssertionError
========================= short test summary info ==========================
FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2
1 failed, 2 passed in 0.12s
Indirect parametrization with multiple fixtures
@@ -475,11 +481,10 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
ssssssssssssssssssssssss... [100%]
ssssssssssss......sss...... [100%]
========================= short test summary info ==========================
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.6' not found
3 passed, 24 skipped in 0.12s
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
12 passed, 15 skipped in 0.12s
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------
@@ -604,13 +609,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collecting ... collected 17 items / 14 deselected / 3 selected
collecting ... collected 14 items / 11 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, 14 deselected, 1 xfailed in 0.12s ================
=============== 2 passed, 11 deselected, 1 xfailed in 0.12s ================
As the result:

View File

@@ -81,8 +81,8 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_text(self):
> assert "spam" == "eggs"
E AssertionError: assert 'spam' == 'eggs'
E - spam
E + eggs
E - eggs
E + spam
failure_demo.py:45: AssertionError
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
@@ -92,9 +92,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_similar_text(self):
> assert "foo 1 bar" == "foo 2 bar"
E AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
E - foo 1 bar
E - foo 2 bar
E ? ^
E + foo 2 bar
E + foo 1 bar
E ? ^
failure_demo.py:48: AssertionError
@@ -106,8 +106,8 @@ Here is a nice run of several failures and how ``pytest`` presents things:
> assert "foo\nspam\nbar" == "foo\neggs\nbar"
E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
E foo
E - spam
E + eggs
E - eggs
E + spam
E bar
failure_demo.py:51: AssertionError
@@ -122,9 +122,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222'
E Skipping 90 identical leading characters in diff, use -v to show
E Skipping 91 identical trailing characters in diff, use -v to show
E - 1111111111a222222222
E - 1111111111b222222222
E ? ^
E + 1111111111b222222222
E + 1111111111a222222222
E ? ^
failure_demo.py:56: AssertionError
@@ -650,4 +650,49 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
failure_demo.py:282: AssertionError
========================= short test summary info ==========================
FAILED failure_demo.py::test_generative[3-6] - assert (3 * 2) < 6
FAILED failure_demo.py::TestFailing::test_simple - assert 42 == 43
FAILED failure_demo.py::TestFailing::test_simple_multiline - assert 42 == 54
FAILED failure_demo.py::TestFailing::test_not - assert not 42
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_text - Asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_similar_text
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_multiline_text
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_long_text - ...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_long_text_multiline
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list
FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline
FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single
FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long
FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long_term
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dataclass - ...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_attrs - Asse...
FAILED failure_demo.py::test_attribute - assert 1 == 2
FAILED failure_demo.py::test_attribute_instance - AssertionError: assert ...
FAILED failure_demo.py::test_attribute_failure - Exception: Failed to get...
FAILED failure_demo.py::test_attribute_multiple - AssertionError: assert ...
FAILED failure_demo.py::TestRaises::test_raises - ValueError: invalid lit...
FAILED failure_demo.py::TestRaises::test_raises_doesnt - Failed: DID NOT ...
FAILED failure_demo.py::TestRaises::test_raise - ValueError: demo error
FAILED failure_demo.py::TestRaises::test_tupleerror - ValueError: not eno...
FAILED failure_demo.py::TestRaises::test_reinterpret_fails_with_print_for_the_fun_of_it
FAILED failure_demo.py::TestRaises::test_some_error - NameError: name 'na...
FAILED failure_demo.py::test_dynamic_compile_shows_nicely - AssertionError
FAILED failure_demo.py::TestMoreErrors::test_complex_error - assert 44 == 43
FAILED failure_demo.py::TestMoreErrors::test_z1_unpack_error - ValueError...
FAILED failure_demo.py::TestMoreErrors::test_z2_type_error - TypeError: c...
FAILED failure_demo.py::TestMoreErrors::test_startswith - AssertionError:...
FAILED failure_demo.py::TestMoreErrors::test_startswith_nested - Assertio...
FAILED failure_demo.py::TestMoreErrors::test_global_func - assert False
FAILED failure_demo.py::TestMoreErrors::test_instance - assert 42 != 42
FAILED failure_demo.py::TestMoreErrors::test_compare - assert 11 < 5
FAILED failure_demo.py::TestMoreErrors::test_try_finally - assert 1 == 0
FAILED failure_demo.py::TestCustomAssertMsg::test_single_line - Assertion...
FAILED failure_demo.py::TestCustomAssertMsg::test_multiline - AssertionEr...
FAILED failure_demo.py::TestCustomAssertMsg::test_custom_repr - Assertion...
============================ 44 failed in 0.12s ============================

View File

@@ -65,6 +65,8 @@ Let's run this without supplying our new option:
test_sample.py:6: AssertionError
--------------------------- Captured stdout call ---------------------------
first
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 0
1 failed in 0.12s
And now with supplying a command line option:
@@ -89,6 +91,8 @@ And now with supplying a command line option:
test_sample.py:6: AssertionError
--------------------------- Captured stdout call ---------------------------
second
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 0
1 failed in 0.12s
You can see that the command line option arrived in our test. This
@@ -261,6 +265,8 @@ Let's run our little function:
E Failed: not configured: 42
test_checkconfig.py:11: Failed
========================= short test summary info ==========================
FAILED test_checkconfig.py::test_something - Failed: not configured: 42
1 failed in 0.12s
If you only want to hide certain exceptions, you can set ``__tracebackhide__``
@@ -442,8 +448,8 @@ Now we can profile which test functions execute the slowest:
========================= slowest 3 test durations =========================
0.30s call test_some_are_slow.py::test_funcslow2
0.21s call test_some_are_slow.py::test_funcslow1
0.11s call test_some_are_slow.py::test_funcfast
0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
============================ 3 passed in 0.12s =============================
incremental testing - test steps
@@ -461,21 +467,52 @@ an ``incremental`` marker which is to be used on classes:
# content of conftest.py
from typing import Dict, Tuple
import pytest
# store history of failures per test class name and per index in parametrize (if parametrize used)
_test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {}
def pytest_runtest_makereport(item, call):
if "incremental" in item.keywords:
# incremental marker is used
if call.excinfo is not None:
parent = item.parent
parent._previousfailed = item
# the test has failed
# retrieve the class name of the test
cls_name = str(item.cls)
# retrieve the index of the test (if parametrize is used in combination with incremental)
parametrize_index = (
tuple(item.callspec.indices.values())
if hasattr(item, "callspec")
else ()
)
# retrieve the name of the test function
test_name = item.originalname or item.name
# store in _test_failed_incremental the original name of the failed test
_test_failed_incremental.setdefault(cls_name, {}).setdefault(
parametrize_index, test_name
)
def pytest_runtest_setup(item):
if "incremental" in item.keywords:
previousfailed = getattr(item.parent, "_previousfailed", None)
if previousfailed is not None:
pytest.xfail("previous test failed ({})".format(previousfailed.name))
# retrieve the class name of the test
cls_name = str(item.cls)
# check if a previous test has failed for this class
if cls_name in _test_failed_incremental:
# retrieve the index of the test (if parametrize is used in combination with incremental)
parametrize_index = (
tuple(item.callspec.indices.values())
if hasattr(item, "callspec")
else ()
)
# retrieve the name of the first test function to fail for this class name and index
test_name = _test_failed_incremental[cls_name].get(parametrize_index, None)
# if name found, test has failed for the combination of class name & test name
if test_name is not None:
pytest.xfail("previous test failed ({})".format(test_name))
These two hook implementations work together to abort incremental-marked
tests in a class. Here is a test module example:
@@ -641,6 +678,11 @@ We can run this:
E assert 0
a/test_db2.py:2: AssertionError
========================= short test summary info ==========================
FAILED test_step.py::TestUserHandling::test_modification - assert 0
FAILED a/test_db.py::test_a1 - AssertionError: <conftest.DB object at 0x7...
FAILED a/test_db2.py::test_a2 - AssertionError: <conftest.DB object at 0x...
ERROR b/test_error.py::test_root
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
The two test modules in the ``a`` directory see the same ``db`` fixture instance
@@ -730,6 +772,9 @@ and run them:
E assert 0
test_module.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_module.py::test_fail1 - assert 0
FAILED test_module.py::test_fail2 - assert 0
============================ 2 failed in 0.12s =============================
you will have a "failures" file which contains the failing test ids:
@@ -845,6 +890,10 @@ and run it:
E assert 0
test_module.py:19: AssertionError
========================= short test summary info ==========================
FAILED test_module.py::test_call_fails - assert 0
FAILED test_module.py::test_fail2 - assert 0
ERROR test_module.py::test_setup_fails - assert 0
======================== 2 failed, 1 error in 0.12s ========================
You'll see that the fixture finalizers could use the precise reporting

View File

@@ -32,5 +32,3 @@ reinstall every time you want to run your tests, and is less brittle than
mucking about with sys.path to point your tests at local code.
Also consider using :ref:`tox <use tox>`.
.. include:: links.inc

View File

@@ -153,4 +153,6 @@ As of mid-2013, there shouldn't be a problem anymore when you
use the standard setuptools (note that distribute has been merged
back into setuptools which is now shipped directly with virtualenv).
.. include:: links.inc
.. _nose: https://nose.readthedocs.io/en/latest/
.. _pylib: https://py.readthedocs.io/en/latest/
.. _`xUnit style setup`: xunit_setup.html

View File

@@ -10,13 +10,19 @@ pytest fixtures: explicit, modular, scalable
.. _`xUnit`: https://en.wikipedia.org/wiki/XUnit
.. _`purpose of test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Software test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
The `purpose of test fixtures`_ is to provide a fixed baseline
upon which tests can reliably and repeatedly execute. pytest fixtures
offer dramatic improvements over the classic xUnit style of setup/teardown
functions:
`Software test fixtures`_ initialize test functions. They provide a
fixed baseline so that tests execute reliably and produce consistent,
repeatable, results. Initialization may setup services, state, or
other operating environments. These are accessed by test functions
through arguments; for each fixture used by a test function there is
typically a parameter (named after the fixture) in the test function's
definition.
pytest fixtures offer dramatic improvements over the classic xUnit
style of setup/teardown functions:
* fixtures have explicit names and are activated by declaring their use
from test functions, modules, classes or whole projects.
@@ -34,6 +40,74 @@ both styles, moving incrementally from classic to new style, as you
prefer. You can also start out from existing :ref:`unittest.TestCase
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
:ref:`Fixtures <fixtures-api>` are defined using the
:ref:`@pytest.fixture <pytest.fixture-api>` decorator, :ref:`described
below <funcargs>`. Pytest has useful built-in fixtures, listed here
for reference:
:fixture:`capfd`
Capture, as text, output to file descriptors ``1`` and ``2``.
:fixture:`capfdbinary`
Capture, as bytes, output to file descriptors ``1`` and ``2``.
:fixture:`caplog`
Control logging and access log entries.
:fixture:`capsys`
Capture, as text, output to ``sys.stdout`` and ``sys.stderr``.
:fixture:`capsysbinary`
Capture, as bytes, output to ``sys.stdout`` and ``sys.stderr``.
:fixture:`cache`
Store and retrieve values across pytest runs.
:fixture:`doctest_namespace`
Provide a dict injected into the docstests namespace.
:fixture:`monkeypatch`
Temporarily modify classes, functions, dictionaries,
``os.environ``, and other objects.
:fixture:`pytestconfig`
Access to configuration values, pluginmanager and plugin hooks.
:fixture:`record_property`
Add extra properties to the test.
:fixture:`record_testsuite_property`
Add extra properties to the test suite.
:fixture:`recwarn`
Record warnings emitted by test functions.
:fixture:`request`
Provide information on the executing test function.
:fixture:`testdir`
Provide a temporary test directory to aid in running, and
testing, pytest plugins.
:fixture:`tmp_path`
Provide a :class:`pathlib.Path` object to a temporary directory
which is unique to each test function.
:fixture:`tmp_path_factory`
Make session-scoped temporary directories and return
:class:`pathlib.Path` objects.
:fixture:`tmpdir`
Provide a :class:`py.path.local` object to a temporary
directory which is unique to each test function;
replaced by :fixture:`tmp_path`.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
:fixture:`tmpdir_factory`
Make session-scoped temporary directories and return
:class:`py.path.local` objects;
replaced by :fixture:`tmp_path_factory`.
.. _`funcargs`:
.. _`funcarg mechanism`:
@@ -96,6 +170,8 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
E assert 0
test_smtpsimple.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_smtpsimple.py::test_ehlo - assert 0
============================ 1 failed in 0.12s =============================
In the failure traceback we see that the test function was called with a
@@ -258,6 +334,9 @@ inspect what is going on and can now run the tests:
E assert 0
test_module.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
============================ 2 failed in 0.12s =============================
You see the two ``assert 0`` failing and more importantly you can also see
@@ -320,7 +399,7 @@ containers for different environments. See the example below.
.. code-block:: python
def determine_scope(fixture_name, config):
if config.getoption("--keep-containers"):
if config.getoption("--keep-containers", None):
return "session"
return "function"
@@ -391,6 +470,9 @@ Let's execute it:
$ pytest -s -q --tb=no
FFteardown smtp
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
2 failed in 0.12s
We see that the ``smtp_connection`` instance is finalized after the two
@@ -545,6 +627,9 @@ again, nothing much has changed:
$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo - assert 0
FAILED test_module.py::test_noop - assert 0
2 failed in 0.12s
Let's quickly create another test module that actually sets the
@@ -574,6 +659,8 @@ Running it:
E assert 0
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org)
========================= short test summary info ==========================
FAILED test_anothersmtp.py::test_showhelo - AssertionError: (250, b'mail....
voila! The ``smtp_connection`` fixture function picked up our mail server name
from the module namespace.
@@ -722,6 +809,11 @@ So let's just do another run:
test_module.py:13: AssertionError
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
========================= short test summary info ==========================
FAILED test_module.py::test_ehlo[smtp.gmail.com] - assert 0
FAILED test_module.py::test_noop[smtp.gmail.com] - assert 0
FAILED test_module.py::test_ehlo[mail.python.org] - AssertionError: asser...
FAILED test_module.py::test_noop[mail.python.org] - assert 0
4 failed in 0.12s
We see that our two test functions each ran twice, against the different
@@ -849,7 +941,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
Modularity: using fixtures from a fixture function
----------------------------------------------------------
You can not only use fixtures in test functions but fixture functions
In addition to using fixtures in test functions, fixture functions
can use other fixtures themselves. This contributes to a modular design
of your fixtures and allows re-use of framework-specific fixtures across
many projects. As a simple example, we can extend the previous example
@@ -1042,11 +1134,13 @@ file:
import pytest
@pytest.fixture()
@pytest.fixture
def cleandir():
old_cwd = os.getcwd()
newpath = tempfile.mkdtemp()
os.chdir(newpath)
yield
os.chdir(old_cwd)
shutil.rmtree(newpath)
and declare its use in a test module via a ``usefixtures`` marker:

View File

@@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest/__init__.py
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.8/site-packages/pytest/__init__.py
.. _`simpletest`:
@@ -69,6 +69,8 @@ Thats it. You can now execute the test function:
E + where 4 = func(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
This test returns a failure report because ``func(3)`` does not return ``5``.
@@ -127,7 +129,7 @@ Once you develop multiple tests, you may want to group them into a class. pytest
x = "hello"
assert hasattr(x, "check")
``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename:
``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything, but make sure to prefix your class with ``Test`` otherwise the class will be skipped. We can simply run the module by passing its filename:
.. code-block:: pytest
@@ -145,6 +147,8 @@ Once you develop multiple tests, you may want to group them into a class. pytest
E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError
========================= short test summary info ==========================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.12s
The first test passed and the second failed. You can easily see the intermediate values in the assertion to help you understand the reason for the failure.
@@ -180,6 +184,8 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
test_tmpdir.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
========================= short test summary info ==========================
FAILED test_tmpdir.py::test_needsfiles - assert 0
1 failed in 0.12s
More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`.
@@ -203,5 +209,3 @@ Check out additional pytest resources to help you customize tests for your uniqu
* ":ref:`fixtures`" for providing a functional baseline to your tests
* ":ref:`plugins`" for managing and writing plugins
* ":ref:`goodpractices`" for virtualenv and test layouts
.. include:: links.inc

View File

@@ -232,5 +232,4 @@ options. It will run tests against the installed package and not
against your source code checkout, helping to detect packaging
glitches.
.. include:: links.inc
.. _`venv`: https://docs.python.org/3/library/venv.html

BIN
doc/en/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -44,6 +44,8 @@ To execute it:
E + where 4 = inc(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.

View File

@@ -1,22 +0,0 @@
.. _`skipping plugin`: plugin/skipping.html
.. _`funcargs mechanism`: funcargs.html
.. _`doctest.py`: http://docs.python.org/library/doctest.html
.. _`xUnit style setup`: xunit_setup.html
.. _`pytest_nose`: plugin/nose.html
.. _`reStructured Text`: http://docutils.sourceforge.net
.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html
.. _nose: https://nose.readthedocs.io/en/latest/
.. _pytest: https://pypi.org/project/pytest/
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _`setuptools`: https://pypi.org/project/setuptools/
.. _`easy_install`:
.. _`distribute docs`:
.. _`distribute`: https://pypi.org/project/distribute/
.. _`pip`: https://pypi.org/project/pip/
.. _`venv`: https://docs.python.org/3/library/venv.html
.. _`virtualenv`: https://pypi.org/project/virtualenv/
.. _hudson: http://hudson-ci.org/
.. _jenkins: http://jenkins-ci.org/
.. _tox: http://testrun.org/tox
.. _pylib: https://py.readthedocs.io/en/latest/

View File

@@ -3,8 +3,6 @@
Running tests written for nose
=======================================
.. include:: links.inc
``pytest`` has basic support for running tests written for nose_.
.. _nosestyle:
@@ -72,3 +70,5 @@ Unsupported idioms / known issues
There are no plans to fix this currently because ``yield``-tests
are deprecated in pytest 3.0, with ``pytest.mark.parametrize``
being the recommended alternative.
.. _nose: https://nose.readthedocs.io/en/latest/

View File

@@ -75,6 +75,8 @@ them in turn:
E + where 54 = eval('6*9')
test_expectation.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54...
======================= 1 failed, 2 passed in 0.12s ========================
.. note::
@@ -225,6 +227,8 @@ Let's also run with a stringinput that will lead to a failing test:
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
test_strings.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
1 failed in 0.12s
As expected our test function fails.

View File

@@ -41,8 +41,7 @@ Here is a little annotated list for some popular plugins:
* `pytest-instafail <https://pypi.org/project/pytest-instafail/>`_:
to report failures while the test run is happening.
* `pytest-bdd <https://pypi.org/project/pytest-bdd/>`_ and
`pytest-konira <https://pypi.org/project/pytest-konira/>`_
* `pytest-bdd <https://pypi.org/project/pytest-bdd/>`_:
to write tests using behaviour-driven testing.
* `pytest-timeout <https://pypi.org/project/pytest-timeout/>`_:

View File

@@ -72,7 +72,7 @@ Some organisations using pytest
-----------------------------------
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
* `Some Mozilla QA people <https://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin/>`_ use pytest to distribute their Selenium tests
* `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* cellzome

View File

@@ -29,9 +29,9 @@ Maintenance of 4.6.X versions
-----------------------------
Until January 2020, the pytest core team ported many bug-fixes from the main release into the
``4.6-maintenance`` branch, with several 4.6.X releases being made along the year.
``4.6.x`` branch, with several 4.6.X releases being made along the year.
From now on, the core team will **no longer actively backport patches**, but the ``4.6-maintenance``
From now on, the core team will **no longer actively backport patches**, but the ``4.6.x``
branch will continue to exist so the community itself can contribute patches.
The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020**
@@ -74,7 +74,7 @@ Please follow these instructions:
#. ``git fetch --all --prune``
#. ``git checkout origin/4.6-maintenance -b backport-XXXX`` # use the PR number here
#. ``git checkout origin/4.6.x -b backport-XXXX`` # use the PR number here
#. Locate the merge commit on the PR, in the *merged* message, for example:
@@ -82,14 +82,14 @@ Please follow these instructions:
#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``).
#. Open a PR targeting ``4.6-maintenance``:
#. Open a PR targeting ``4.6.x``:
* Prefix the message with ``[4.6]`` so it is an obvious backport
* Delete the PR body, it usually contains a duplicate commit message.
**Providing new PRs to 4.6**
Fresh pull requests to ``4.6-maintenance`` will be accepted provided that
Fresh pull requests to ``4.6.x`` will be accepted provided that
the equivalent code in the active branches does not contain that bug (for example, a bug is specific
to Python 2 only).

View File

@@ -77,6 +77,8 @@ This is also discussed in details in :ref:`test discovery`.
Invoking ``pytest`` versus ``python -m pytest``
-----------------------------------------------
Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly
equivalent behaviour, except that the former call will add the current directory to ``sys.path``.
Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly
equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which
is standard ``python`` behavior.
See also :ref:`cmdline`.

View File

@@ -126,7 +126,7 @@ Add warning filters to marked test items.
.. code-block:: python
@pytest.mark.warnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
@pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
def test_foo():
...
@@ -243,6 +243,8 @@ Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to
mark.kwargs == {"method": "thread"}
.. _`fixtures-api`:
Fixtures
--------
@@ -273,6 +275,8 @@ Example of a fixture requiring another fixture:
For more details, consult the full :ref:`fixtures docs <fixture>`.
.. _`pytest.fixture-api`:
@pytest.fixture
~~~~~~~~~~~~~~~
@@ -280,7 +284,7 @@ For more details, consult the full :ref:`fixtures docs <fixture>`.
:decorator:
.. _`cache-api`:
.. fixture:: config.cache
config.cache
~~~~~~~~~~~~
@@ -301,6 +305,8 @@ Under the hood, the cache plugin uses the simple
.. automethod:: Cache.makedir
.. fixture:: capsys
capsys
~~~~~~
@@ -326,6 +332,8 @@ capsys
:members:
.. fixture:: capsysbinary
capsysbinary
~~~~~~~~~~~~
@@ -346,6 +354,8 @@ capsysbinary
assert captured.out == b"hello\n"
.. fixture:: capfd
capfd
~~~~~~
@@ -366,6 +376,8 @@ capfd
assert captured.out == "hello\n"
.. fixture:: capfdbinary
capfdbinary
~~~~~~~~~~~~
@@ -386,6 +398,8 @@ capfdbinary
assert captured.out == b"hello\n"
.. fixture:: doctest_namespace
doctest_namespace
~~~~~~~~~~~~~~~~~
@@ -404,6 +418,8 @@ doctest_namespace
For more details: :ref:`doctest_namespace`.
.. fixture:: request
request
~~~~~~~
@@ -415,12 +431,16 @@ The ``request`` fixture is a special fixture providing information of the reques
:members:
.. fixture:: pytestconfig
pytestconfig
~~~~~~~~~~~~
.. autofunction:: _pytest.fixtures.pytestconfig()
.. fixture:: record_property
record_property
~~~~~~~~~~~~~~~~~~~
@@ -429,6 +449,8 @@ record_property
.. autofunction:: _pytest.junitxml.record_property()
.. fixture:: record_testsuite_property
record_testsuite_property
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -436,6 +458,9 @@ record_testsuite_property
.. autofunction:: _pytest.junitxml.record_testsuite_property()
.. fixture:: caplog
caplog
~~~~~~
@@ -450,6 +475,8 @@ caplog
:members:
.. fixture:: monkeypatch
monkeypatch
~~~~~~~~~~~
@@ -465,7 +492,8 @@ monkeypatch
.. autoclass:: _pytest.monkeypatch.MonkeyPatch
:members:
.. _testdir:
.. fixture:: testdir
testdir
~~~~~~~
@@ -493,6 +521,8 @@ To use it, include in your top-most ``conftest.py`` file:
:members:
.. fixture:: recwarn
recwarn
~~~~~~~
@@ -516,6 +546,8 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`.
differently; see :ref:`ensuring_function_triggers`.
.. fixture:: tmp_path
tmp_path
~~~~~~~~
@@ -527,6 +559,8 @@ tmp_path
:no-auto-options:
.. fixture:: tmp_path_factory
tmp_path_factory
~~~~~~~~~~~~~~~~
@@ -542,6 +576,8 @@ tmp_path_factory
.. automethod:: TempPathFactory.getbasetemp
.. fixture:: tmpdir
tmpdir
~~~~~~
@@ -553,6 +589,8 @@ tmpdir
:no-auto-options:
.. fixture:: tmpdir_factory
tmpdir_factory
~~~~~~~~~~~~~~
@@ -738,7 +776,7 @@ ExceptionInfo
pytest.ExitCode
~~~~~~~~~~~~~~~
.. autoclass:: _pytest.main.ExitCode
.. autoclass:: _pytest.config.ExitCode
:members:
@@ -901,8 +939,8 @@ Can be either a ``str`` or ``Sequence[str]``.
pytest_plugins = ("myapp.testsupport.tools", "myapp.testsupport.regression")
pytest_mark
~~~~~~~~~~~
pytestmark
~~~~~~~~~~
**Tutorial**: :ref:`scoped-marking`
@@ -1164,9 +1202,17 @@ passed multiple times. The expected format is ``name=value``. For example::
.. confval:: junit_logging
.. versionadded:: 3.5
.. versionchanged:: 5.4
``log``, ``all``, ``out-err`` options added.
Configures if stdout/stderr should be written to the JUnit XML file. Valid values are
``system-out``, ``system-err``, and ``no`` (the default).
Configures if captured output should be written to the JUnit XML file. Valid values are:
* ``log``: write only ``logging`` captured output.
* ``system-out``: write captured ``stdout`` contents.
* ``system-err``: write captured ``stderr`` contents.
* ``out-err``: write both captured ``stdout`` and ``stderr`` contents.
* ``all``: write captured ``logging``, ``stdout`` and ``stderr`` contents.
* ``no`` (the default): no captured output is written.
.. code-block:: ini

View File

@@ -234,11 +234,11 @@ expect a test to fail:
def test_function():
...
This test will be run but no traceback will be reported
when it fails. Instead terminal reporting will list it in the
"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections.
This test will run but no traceback will be reported when it fails. Instead, terminal
reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly
passing" (``XPASS``) sections.
Alternatively, you can also mark a test as ``XFAIL`` from within a test or setup function
Alternatively, you can also mark a test as ``XFAIL`` from within the test or its setup function
imperatively:
.. code-block:: python
@@ -247,8 +247,19 @@ imperatively:
if not valid_config():
pytest.xfail("failing configuration (but should work)")
This will unconditionally make ``test_function`` ``XFAIL``. Note that no other code is executed
after ``pytest.xfail`` call, differently from the marker. That's because it is implemented
.. code-block:: python
def test_function2():
import slow_module
if slow_module.slow_function():
pytest.xfail("slow_module taking too long")
These two examples illustrate situations where you don't want to check for a condition
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
internally by raising a known exception.
**Reference**: :ref:`pytest.mark.xfail ref`
@@ -261,8 +272,8 @@ internally by raising a known exception.
Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only
parameter is passed as ``True``:
Both ``XFAIL`` and ``XPASS`` don't fail the test suite by default.
You can change this by setting the ``strict`` keyword-only parameter to ``True``:
.. code-block:: python

View File

@@ -42,7 +42,7 @@ Talks and blog postings
<https://www.youtube.com/watch?v=P-AhpukDIik>`_
- `3-part blog series about pytest from @pydanny alias Daniel Greenfeld (January
2014) <http://pydanny.com/pytest-no-boilerplate-testing.html>`_
2014) <https://daniel.roygreenfeld.com/pytest-no-boilerplate-testing.html>`_
- `pytest: helps you write better Django apps, Andreas Pelme, DjangoCon
Europe 2014 <https://www.youtube.com/watch?v=aaArYVh6XSM>`_.

View File

@@ -64,6 +64,8 @@ Running this would result in a passed test except for the last
E assert 0
test_tmp_path.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_tmp_path.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================
.. _`tmp_path_factory example`:
@@ -133,6 +135,8 @@ Running this would result in a passed test except for the last
E assert 0
test_tmpdir.py:9: AssertionError
========================= short test summary info ==========================
FAILED test_tmpdir.py::test_create_file - assert 0
============================ 1 failed in 0.12s =============================
.. _`tmpdir factory example`:

View File

@@ -166,6 +166,9 @@ the ``self.db`` values in the traceback:
E assert 0
test_unittest_db.py:13: AssertionError
========================= short test summary info ==========================
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
============================ 2 failed in 0.12s =============================
This default pytest traceback shows that the two test methods
@@ -238,17 +241,6 @@ was executed ahead of the ``test_method``.
.. _pdb-unittest-note:
.. note::
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
disable tearDown and cleanup methods for the case that an Exception
occurs. This allows proper post mortem debugging for all applications
which have significant logic in their tearDown machinery. However,
supporting this feature has the following side effect: If people
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
to overwrite ``debug`` in the same way (this is also true for standard
unittest).
.. note::
Due to architectural differences between the two frameworks, setup and

View File

@@ -33,7 +33,7 @@ Running ``pytest`` can result in six different exit codes:
:Exit code 4: pytest command line usage error
:Exit code 5: No tests were collected
They are represented by the :class:`_pytest.main.ExitCode` enum. The exit codes being a part of the public API can be imported and accessed directly using:
They are represented by the :class:`_pytest.config.ExitCode` enum. The exit codes being a part of the public API can be imported and accessed directly using:
.. code-block:: python
@@ -94,8 +94,8 @@ Pytest supports several ways to run and select tests from the command-line.
pytest -k "MyClass and not method"
This will run tests which contain names that match the given *string expression*, which can
include Python operators that use filenames, class names and function names as variables.
This will run tests which contain names that match the given *string expression* (case-insensitive),
which can include Python operators that use filenames, class names and function names as variables.
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
.. _nodeids:
@@ -169,11 +169,11 @@ option you make sure a trace is shown.
Detailed summary report
-----------------------
The ``-r`` flag can be used to display a "short test summary info" at the end of the test session,
making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc.
It defaults to ``fE`` to list failures and errors.
Example:
.. code-block:: python
@@ -261,8 +261,12 @@ Here is the full list of available characters that can be used:
- ``X`` - xpassed
- ``p`` - passed
- ``P`` - passed with output
Special characters for (de)selection of groups:
- ``a`` - all except ``pP``
- ``A`` - all
- ``N`` - none, this can be used to display nothing (since ``fE`` is the default)
More than one character can be used, so for example to only see failed and skipped tests, you can execute:
@@ -817,6 +821,9 @@ hook was invoked:
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
ERROR test_example.py::test_error - assert 0
.. note::
@@ -827,5 +834,4 @@ hook was invoked:
multiple calls to ``pytest.main()`` from the same process (in order to re-run
tests, for example) is not recommended.
.. include:: links.inc
.. _jenkins: http://jenkins-ci.org/

View File

@@ -64,6 +64,8 @@ them into errors:
E UserWarning: api v1, should use functions from v2
test_show_warnings.py:5: UserWarning
========================= short test summary info ==========================
FAILED test_show_warnings.py::test_one - UserWarning: api v1, should use ...
1 failed in 0.12s
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.

View File

@@ -179,11 +179,12 @@ assertion failures. This is provided by "assertion rewriting" which
modifies the parsed AST before it gets compiled to bytecode. This is
done via a :pep:`302` import hook which gets installed early on when
``pytest`` starts up and will perform this rewriting when modules get
imported. However since we do not want to test different bytecode
then you will run in production this hook only rewrites test modules
themselves as well as any modules which are part of plugins. Any
other imported module will not be rewritten and normal assertion
behaviour will happen.
imported. However, since we do not want to test different bytecode
from what you will run in production, this hook only rewrites test modules
themselves (as defined by the :confval:`python_files` configuration option),
and any modules which are part of plugins.
Any other imported module will not be rewritten and normal assertion behaviour
will happen.
If you have assertion helpers in other modules where you would need
assertion rewriting to be enabled you need to ask ``pytest``
@@ -441,8 +442,13 @@ additionally it is possible to copy examples for an example folder before runnin
$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")
test_example.py::test_plugin
$PYTHON_PREFIX/lib/python3.8/site-packages/_pytest/compat.py:333: PytestDeprecationWarning: The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.
See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information.
return getattr(object, name, default)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
======================= 2 passed, 1 warning in 0.12s =======================
====================== 2 passed, 2 warnings 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

@@ -16,8 +16,8 @@ title_format = "pytest {version} ({project_date})"
template = "changelog/_template.rst"
[[tool.towncrier.type]]
directory = "removal"
name = "Removals"
directory = "breaking"
name = "Breaking Changes"
showcontent = true
[[tool.towncrier.type]]
@@ -54,3 +54,6 @@ template = "changelog/_template.rst"
directory = "trivial"
name = "Trivial/Internal Changes"
showcontent = true
[tool.black]
target-version = ['py35']

View File

@@ -61,7 +61,9 @@ def parse_changelog(tag_name):
def convert_rst_to_md(text):
return pypandoc.convert_text(text, "md", format="rst")
return pypandoc.convert_text(
text, "md", format="rst", extra_args=["--wrap=preserve"]
)
def main(argv):

View File

@@ -0,0 +1,215 @@
"""
This script is part of the pytest release process which is triggered by comments
in issues.
This script is started by the `prepare_release.yml` workflow, which is triggered by two comment
related events:
* https://help.github.com/en/actions/reference/events-that-trigger-workflows#issue-comment-event-issue_comment
* https://help.github.com/en/actions/reference/events-that-trigger-workflows#issues-event-issues
This script receives the payload and a secrets on the command line.
The payload must contain a comment with a phrase matching this regular expression:
@pytestbot please prepare release from <branch name>
Then the appropriate version will be obtained based on the given branch name:
* a feature or bug fix release from master (based if there are features in the current changelog
folder)
* a bug fix from a maintenance branch
After that, it will create a release using the `release` tox environment, and push a new PR.
**Secret**: currently the secret is defined in the @pytestbot account, which the core maintainers
have access to. There we created a new secret named `chatops` with write access to the repository.
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from subprocess import check_call
from subprocess import check_output
from textwrap import dedent
from typing import Dict
from typing import Optional
from typing import Tuple
from colorama import Fore
from colorama import init
from github3.repos import Repository
class InvalidFeatureRelease(Exception):
pass
SLUG = "pytest-dev/pytest"
PR_BODY = """\
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.
"""
def login(token: str) -> Repository:
import github3
github = github3.login(token=token)
owner, repo = SLUG.split("/")
return github.repository(owner, repo)
def get_comment_data(payload: Dict) -> str:
if "comment" in payload:
return payload["comment"]
else:
return payload["issue"]
def validate_and_get_issue_comment_payload(
issue_payload_path: Optional[Path],
) -> Tuple[str, str]:
payload = json.loads(issue_payload_path.read_text(encoding="UTF-8"))
body = get_comment_data(payload)["body"]
m = re.match(r"@pytestbot please prepare release from ([\w\-_\.]+)", body)
if m:
base_branch = m.group(1)
else:
base_branch = None
return payload, base_branch
def print_and_exit(msg) -> None:
print(msg)
raise SystemExit(1)
def trigger_release(payload_path: Path, token: str) -> None:
payload, base_branch = validate_and_get_issue_comment_payload(payload_path)
if base_branch is None:
url = get_comment_data(payload)["html_url"]
print_and_exit(
f"Comment {Fore.CYAN}{url}{Fore.RESET} did not match the trigger command."
)
print()
print(f"Precessing release for branch {Fore.CYAN}{base_branch}")
repo = login(token)
issue_number = payload["issue"]["number"]
issue = repo.issue(issue_number)
check_call(["git", "checkout", f"origin/{base_branch}"])
print("DEBUG:", check_output(["git", "rev-parse", "HEAD"]))
try:
version = find_next_version(base_branch)
except InvalidFeatureRelease as e:
issue.create_comment(str(e))
print_and_exit(f"{Fore.RED}{e}")
try:
print(f"Version: {Fore.CYAN}{version}")
release_branch = f"release-{version}"
check_call(["git", "config", "user.name", "pytest bot"])
check_call(["git", "config", "user.email", "pytestbot@gmail.com"])
check_call(["git", "checkout", "-b", release_branch, f"origin/{base_branch}"])
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
check_call(
[sys.executable, "scripts/release.py", version, "--skip-check-links"]
)
oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
check_call(["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"])
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.")
body = PR_BODY.format(
comment_url=get_comment_data(payload)["html_url"], version=version
)
pr = repo.create_pull(
f"Prepare release {version}",
base=base_branch,
head=release_branch,
body=body,
)
print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.")
comment = issue.create_comment(
f"As requested, opened a PR for release `{version}`: #{pr.number}."
)
print(f"Notified in original comment {Fore.CYAN}{comment.url}{Fore.RESET}.")
print(f"{Fore.GREEN}Success.")
except Exception as e:
link = f"https://github.com/{SLUG}/actions/runs/{os.environ['GITHUB_RUN_ID']}"
issue.create_comment(
dedent(
f"""
Sorry, the request to prepare release `{version}` from {base_branch} failed with:
```
{e}
```
See: {link}.
"""
)
)
print_and_exit(f"{Fore.RED}{e}")
def find_next_version(base_branch: str) -> str:
output = check_output(["git", "tag"], encoding="UTF-8")
valid_versions = []
for v in output.splitlines():
m = re.match(r"\d.\d.\d+$", v.strip())
if m:
valid_versions.append(tuple(int(x) for x in v.split(".")))
valid_versions.sort()
last_version = valid_versions[-1]
changelog = Path("changelog")
features = list(changelog.glob("*.feature.rst"))
breaking = list(changelog.glob("*.breaking.rst"))
is_feature_release = features or breaking
if is_feature_release and base_branch != "master":
msg = dedent(
f"""
Found features or breaking changes in `{base_branch}`, and feature releases can only be
created from `master`.":
"""
)
msg += "\n".join(f"* `{x.name}`" for x in sorted(features + breaking))
raise InvalidFeatureRelease(msg)
if is_feature_release:
return f"{last_version[0]}.{last_version[1] + 1}.0"
else:
return f"{last_version[0]}.{last_version[1]}.{last_version[2] + 1}"
def main() -> None:
init(autoreset=True)
parser = argparse.ArgumentParser()
parser.add_argument("payload")
parser.add_argument("token")
options = parser.parse_args()
trigger_release(Path(options.payload), options.token)
if __name__ == "__main__":
main()

View File

@@ -6,7 +6,7 @@ The pytest team is proud to announce the {version} release!
pytest is a mature Python testing tool with more than a 2000 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bugs fixes and improvements, so users are encouraged
This release contains a number of bug fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
https://docs.pytest.org/en/latest/changelog.html
@@ -15,7 +15,7 @@ For complete documentation, please visit:
https://docs.pytest.org/en/latest/
As usual, you can upgrade from pypi via:
As usual, you can upgrade from PyPI via:
pip install -U pytest
@@ -24,4 +24,4 @@ Thanks to all who contributed to this release, among them:
{contributors}
Happy testing,
The Pytest Development Team
The pytest Development Team

View File

@@ -100,7 +100,7 @@ def pre_release(version, *, skip_check_links):
print()
print(f"{Fore.CYAN}[generate.pre_release] {Fore.GREEN}All done!")
print()
print(f"Please push your branch and open a PR.")
print("Please push your branch and open a PR.")
def changelog(version, write_out=False):

View File

@@ -15,4 +15,4 @@ python -m coverage xml
python -m coverage report -m
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
bash codecov-upload.sh -Z -X fix -f coverage.xml
bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"

View File

@@ -1,21 +0,0 @@
@echo off
rem Source: https://github.com/appveyor/ci/blob/master/scripts/appveyor-retry.cmd
rem initiate the retry number
set retryNumber=0
set maxRetries=3
:RUN
%*
set LastErrorLevel=%ERRORLEVEL%
IF %LastErrorLevel% == 0 GOTO :EOF
set /a retryNumber=%retryNumber%+1
IF %reTryNumber% == %maxRetries% (GOTO :FAILED)
:RETRY
set /a retryNumberDisp=%retryNumber%+1
@echo Command "%*" failed with exit code %LastErrorLevel%. Retrying %retryNumberDisp% of %maxRetries%
GOTO :RUN
: FAILED
@echo Sorry, we tried running command for %maxRetries% times and all attempts were unsuccessful!
EXIT /B %LastErrorLevel%

View File

@@ -10,7 +10,6 @@ project_urls =
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
license = MIT license
license_file = LICENSE
keywords = test, unittest
classifiers =
Development Status :: 6 - Mature
@@ -27,6 +26,7 @@ classifiers =
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
platforms = unix, linux, osx, cygwin, win32
[options]
@@ -66,6 +66,7 @@ formats = sdist.tgz,bdist_wheel
mypy_path = src
ignore_missing_imports = True
no_implicit_optional = True
show_error_codes = True
strict_equality = True
warn_redundant_casts = True
warn_return_any = True

View File

@@ -53,19 +53,22 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script).
"""
import argparse
import os
import sys
from glob import glob
from typing import Any
from typing import List
from typing import Optional
class FastFilesCompleter:
"Fast file completer class"
def __init__(self, directories=True):
def __init__(self, directories: bool = True) -> None:
self.directories = directories
def __call__(self, prefix, **kwargs):
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
"""only called on non option completions"""
if os.path.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
@@ -94,13 +97,13 @@ if os.environ.get("_ARGCOMPLETE"):
sys.exit(-1)
filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter]
def try_argcomplete(parser):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
argcomplete.autocomplete(parser, always_complete_options=False)
else:
def try_argcomplete(parser):
def try_argcomplete(parser: argparse.ArgumentParser) -> None:
pass
filescompleter = None

View File

@@ -29,8 +29,10 @@ import pluggy
import py
import _pytest
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 overload
from _pytest.compat import TYPE_CHECKING
@@ -41,7 +43,7 @@ if TYPE_CHECKING:
from _pytest._code import Source
_TracebackStyle = Literal["long", "short", "no", "native"]
_TracebackStyle = Literal["long", "short", "line", "no", "native"]
class Code:
@@ -67,20 +69,22 @@ class Code:
return not self == other
@property
def path(self):
""" return a path object pointing to source code (note that it
might not point to an actually existing file). """
def path(self) -> Union[py.path.local, str]:
""" return a path object pointing to source code (or a str in case
of OSError / non-existing file).
"""
if not self.raw.co_filename:
return ""
try:
p = py.path.local(self.raw.co_filename)
# maybe don't try this checking
if not p.check():
raise OSError("py.path check failed.")
return p
except OSError:
# XXX maybe try harder like the weird logic
# in the standard lib [linecache.updatecache] does?
p = self.raw.co_filename
return p
return self.raw.co_filename
@property
def fullsource(self) -> Optional["Source"]:
@@ -335,7 +339,7 @@ class Traceback(List[TracebackEntry]):
(path is None or codepath == path)
and (
excludepath is None
or not hasattr(codepath, "relto")
or not isinstance(codepath, py.path.local)
or not codepath.relto(excludepath)
)
and (lineno is None or x.lineno == lineno)
@@ -630,17 +634,18 @@ class ExceptionInfo(Generic[_E]):
)
return fmt.repr_excinfo(self)
def match(self, regexp: "Union[str, Pattern]") -> bool:
def match(self, regexp: "Union[str, Pattern]") -> "Literal[True]":
"""
Check whether the regular expression 'regexp' is found in the string
representation of the exception using ``re.search``. If it matches
then True is returned (so that it is possible to write
``assert excinfo.match()``). If it doesn't match an AssertionError is
raised.
Check whether the regular expression `regexp` matches the string
representation of the exception using :func:`python:re.search`.
If it matches `True` is returned.
If it doesn't match an `AssertionError` is raised.
"""
__tracebackhide__ = True
if not re.search(regexp, str(self.value)):
assert 0, "Pattern {!r} not found in {!r}".format(regexp, str(self.value))
assert re.search(
regexp, str(self.value)
), "Pattern {!r} does not match {!r}".format(regexp, str(self.value))
# Return True to allow for "assert excinfo.match()".
return True
@@ -785,11 +790,9 @@ class FormattedExcinfo:
else:
message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path)
filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = None
if not short:
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
if excinfo:
lines.extend(self.get_exconly(excinfo, indent=4))
return ReprEntry(lines, None, None, None, style)
@@ -909,50 +912,53 @@ class FormattedExcinfo:
return ExceptionChainRepr(repr_chain)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class TerminalRepr:
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
# information.
io = StringIO()
tw = py.io.TerminalWriter(file=io)
tw = TerminalWriter(file=io)
self.toterminal(tw)
return io.getvalue().strip()
def __repr__(self) -> str:
return "<{} instance at {:0x}>".format(self.__class__, id(self))
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
raise NotImplementedError()
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionRepr(TerminalRepr):
def __init__(self) -> None:
def __attrs_post_init__(self):
self.sections = [] # type: List[Tuple[str, str, str]]
def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep))
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
for name, content, sep in self.sections:
tw.sep(sep, name)
tw.line(content)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionChainRepr(ExceptionRepr):
def __init__(
self,
chain: Sequence[
chain = attr.ib(
type=Sequence[
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
],
) -> None:
super().__init__()
self.chain = chain
]
)
def __attrs_post_init__(self):
super().__attrs_post_init__()
# reprcrash and reprtraceback of the outermost (the newest) exception
# in the chain
self.reprtraceback = chain[-1][0]
self.reprcrash = chain[-1][1]
self.reprtraceback = self.chain[-1][0]
self.reprcrash = self.chain[-1][1]
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
for element in self.chain:
element[0].toterminal(tw)
if element[2] is not None:
@@ -961,33 +967,25 @@ class ExceptionChainRepr(ExceptionRepr):
super().toterminal(tw)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprExceptionInfo(ExceptionRepr):
def __init__(
self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation"
) -> None:
super().__init__()
self.reprtraceback = reprtraceback
self.reprcrash = reprcrash
reprtraceback = attr.ib(type="ReprTraceback")
reprcrash = attr.ib(type="ReprFileLocation")
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
self.reprtraceback.toterminal(tw)
super().toterminal(tw)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprTraceback(TerminalRepr):
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
extraline = attr.ib(type=Optional[str])
style = attr.ib(type="_TracebackStyle")
entrysep = "_ "
def __init__(
self,
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]],
extraline: Optional[str],
style: "_TracebackStyle",
) -> None:
self.reprentries = reprentries
self.extraline = extraline
self.style = style
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
# the entries might have different styles
for i, entry in enumerate(self.reprentries):
if entry.style == "long":
@@ -1013,44 +1011,75 @@ class ReprTracebackNative(ReprTraceback):
self.extraline = None
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntryNative(TerminalRepr):
lines = attr.ib(type=Sequence[str])
style = "native" # type: _TracebackStyle
def __init__(self, tblines: Sequence[str]) -> None:
self.lines = tblines
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntry(TerminalRepr):
def __init__(
self,
lines: Sequence[str],
reprfuncargs: Optional["ReprFuncArgs"],
reprlocals: Optional["ReprLocals"],
filelocrepr: Optional["ReprFileLocation"],
style: "_TracebackStyle",
) -> None:
self.lines = lines
self.reprfuncargs = reprfuncargs
self.reprlocals = reprlocals
self.reprfileloc = filelocrepr
self.style = style
lines = attr.ib(type=Sequence[str])
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
reprlocals = attr.ib(type=Optional["ReprLocals"])
reprfileloc = attr.ib(type=Optional["ReprFileLocation"])
style = attr.ib(type="_TracebackStyle")
def toterminal(self, tw) -> None:
def _write_entry_lines(self, tw: TerminalWriter) -> None:
"""Writes the source code portions of a list of traceback entries with syntax highlighting.
Usually entries are lines like these:
" x = 1"
"> assert x == 2"
"E assert 1 == 2"
This function takes care of rendering the "source" portions of it (the lines without
the "E" prefix) using syntax highlighting, taking care to not highlighting the ">"
character, as doing so might break line continuations.
"""
indent_size = 4
def is_fail(line):
return line.startswith("{} ".format(FormattedExcinfo.fail_marker))
if not self.lines:
return
# 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"
indents = []
source_lines = []
for line in self.lines:
if not is_fail(line):
indents.append(line[:indent_size])
source_lines.append(line[indent_size:])
tw._write_source(source_lines, indents)
# failure lines are always completely red and bold
for line in (x for x in self.lines if is_fail(x)):
tw.line(line, bold=True, red=True)
def toterminal(self, tw: TerminalWriter) -> None:
if self.style == "short":
assert self.reprfileloc is not None
self.reprfileloc.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
self._write_entry_lines(tw)
if self.reprlocals:
self.reprlocals.toterminal(tw, indent=" " * 8)
return
if self.reprfuncargs:
self.reprfuncargs.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
self._write_entry_lines(tw)
if self.reprlocals:
tw.line("")
self.reprlocals.toterminal(tw)
@@ -1065,13 +1094,13 @@ class ReprEntry(TerminalRepr):
)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno: int, message: str) -> None:
self.path = str(path)
self.lineno = lineno
self.message = message
path = attr.ib(type=str, converter=str)
lineno = attr.ib(type=int)
message = attr.ib(type=str)
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
# filename and lineno output for each entry,
# using an output format that most editors understand
msg = self.message
@@ -1082,20 +1111,20 @@ class ReprFileLocation(TerminalRepr):
tw.line(":{}: {}".format(self.lineno, msg))
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprLocals(TerminalRepr):
def __init__(self, lines: Sequence[str]) -> None:
self.lines = lines
lines = attr.ib(type=Sequence[str])
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter, indent="") -> None:
for line in self.lines:
tw.line(line)
tw.line(indent + line)
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprFuncArgs(TerminalRepr):
def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
self.args = args
args = attr.ib(type=Sequence[Tuple[str, object]])
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
if self.args:
linesofar = ""
for name, value in self.args:

View File

@@ -5,9 +5,10 @@ import sys
import textwrap
import tokenize
import warnings
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
from types import CodeType
from types import FrameType
from typing import Any
from typing import Iterator
from typing import List
from typing import Optional
@@ -17,7 +18,12 @@ from typing import Union
import py
from _pytest.compat import get_real_func
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import Literal
class Source:
@@ -121,7 +127,7 @@ class Source:
start, end = self.getstatementrange(lineno)
return self[start:end]
def getstatementrange(self, lineno: int):
def getstatementrange(self, lineno: int) -> Tuple[int, int]:
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
"""
@@ -140,18 +146,13 @@ class Source:
""" return True if source is parseable, heuristically
deindenting it by default.
"""
from parser import suite as syntax_checker
if deindent:
source = str(self.deindent())
else:
source = str(self)
try:
# compile(source+'\n', "x", "exec")
syntax_checker(source + "\n")
except KeyboardInterrupt:
raise
except Exception:
ast.parse(source)
except (SyntaxError, ValueError, TypeError):
return False
else:
return True
@@ -159,14 +160,36 @@ class Source:
def __str__(self) -> str:
return "\n".join(self.lines)
@overload
def compile(
self,
filename=None,
mode="exec",
filename: Optional[str] = ...,
mode: str = ...,
flag: "Literal[0]" = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile( # noqa: F811
self,
filename: Optional[str] = ...,
mode: str = ...,
flag: int = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile( # noqa: F811
self,
filename: Optional[str] = None,
mode: str = "exec",
flag: int = 0,
dont_inherit: int = 0,
_genframe: Optional[FrameType] = None,
):
) -> Union[CodeType, ast.AST]:
""" return compiled code object. if filename is None
invent an artificial filename which displays
the source/line position of the caller frame.
@@ -196,8 +219,10 @@ class Source:
newex.text = ex.text
raise newex
else:
if flag & _AST_FLAG:
if flag & ast.PyCF_ONLY_AST:
assert isinstance(co, ast.AST)
return co
assert isinstance(co, CodeType)
lines = [(x + "\n") for x in self.lines]
# Type ignored because linecache.cache is private.
linecache.cache[filename] = (1, None, lines, filename) # type: ignore
@@ -209,7 +234,35 @@ class Source:
#
def compile_(source, filename=None, mode="exec", flags: int = 0, dont_inherit: int = 0):
@overload
def compile_(
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: "Literal[0]" = ...,
dont_inherit: int = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: int = ...,
dont_inherit: int = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = None,
mode: str = "exec",
flags: int = 0,
dont_inherit: int = 0,
) -> Union[CodeType, ast.AST]:
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object
@@ -217,14 +270,16 @@ def compile_(source, filename=None, mode="exec", flags: int = 0, dont_inherit: i
"""
if isinstance(source, ast.AST):
# XXX should Source support having AST?
return compile(source, filename, mode, flags, dont_inherit)
assert filename is not None
co = compile(source, filename, mode, flags, dont_inherit)
assert isinstance(co, (CodeType, ast.AST))
return co
_genframe = sys._getframe(1) # the caller
s = Source(source)
co = s.compile(filename, mode, flags, _genframe=_genframe)
return co
return s.compile(filename, mode, flags, _genframe=_genframe)
def getfslineno(obj):
def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
""" Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1).
@@ -232,6 +287,13 @@ def getfslineno(obj):
"""
from .code import Code
# xxx let decorators etc specify a sane ordering
# NOTE: this used to be done in _pytest.compat.getfslineno, initially added
# in 6ec13a2b9. It ("place_as") appears to be something very custom.
obj = get_real_func(obj)
if hasattr(obj, "place_as"):
obj = obj.place_as
try:
code = Code(obj)
except TypeError:
@@ -240,18 +302,16 @@ def getfslineno(obj):
except TypeError:
return "", -1
fspath = fn and py.path.local(fn) or None
fspath = fn and py.path.local(fn) or ""
lineno = -1
if fspath:
try:
_, lineno = findsource(obj)
except IOError:
pass
return fspath, lineno
else:
fspath = code.path
lineno = code.firstlineno
assert isinstance(lineno, int)
return fspath, lineno
return code.path, code.firstlineno
#
@@ -321,7 +381,7 @@ def getstatementrange_ast(
# don't produce duplicate warnings when compiling source to find ast
with warnings.catch_warnings():
warnings.simplefilter("ignore")
astnode = compile(content, "source", "exec", _AST_FLAG)
astnode = ast.parse(content, "source", "exec")
start, end = get_statement_startend2(lineno, astnode)
# we need to correct the end:

View File

@@ -0,0 +1,39 @@
from typing import List
from typing import Sequence
from py.io import TerminalWriter as BaseTerminalWriter # noqa: F401
class TerminalWriter(BaseTerminalWriter):
def _write_source(self, lines: List[str], indents: Sequence[str] = ()) -> None:
"""Write lines of source code possibly highlighted.
Keeping this private for now because the API is clunky. We should discuss how
to evolve the terminal writer so we can have more precise color support, for example
being able to write part of a line in one color and the rest in another, and so on.
"""
if indents and len(indents) != len(lines):
raise ValueError(
"indents size ({}) should have same size as lines ({})".format(
len(indents), len(lines)
)
)
if not indents:
indents = [""] * len(lines)
source = "\n".join(lines)
new_lines = self._highlight(source).splitlines()
for indent, new_line in zip(indents, new_lines):
self.line(indent + new_line)
def _highlight(self, source):
"""Highlight the given source code according to the "code_highlight" option"""
if not self.hasmarkup:
return source
try:
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexers.python import PythonLexer
from pygments import highlight
except ImportError:
return source
else:
return highlight(source, PythonLexer(), TerminalFormatter(bg="dark"))

View File

@@ -20,7 +20,7 @@ def _format_repr_exception(exc: BaseException, obj: Any) -> str:
except BaseException as exc:
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
exc_info, obj.__class__.__name__, id(obj)
exc_info, type(obj).__name__, id(obj)
)
@@ -80,3 +80,24 @@ def saferepr(obj: Any, maxsize: int = 240) -> str:
around the Repr/reprlib functionality of the standard 2.6 lib.
"""
return SafeRepr(maxsize).repr(obj)
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
"""PrettyPrinter that always dispatches (regardless of width)."""
def _format(self, object, stream, indent, allowance, context, level):
p = self._dispatch.get(type(object).__repr__, None)
objid = id(object)
if objid in context or p is None:
return super()._format(object, stream, indent, allowance, context, level)
context[objid] = 1
p(self, object, stream, indent, allowance, context, level + 1)
del context[objid]
def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False):
return AlwaysDispatchingPrettyPrinter(
indent=indent, width=width, depth=depth, compact=compact
).pformat(object)

View File

@@ -2,11 +2,20 @@
support for presenting detailed information in failing assertions.
"""
import sys
from typing import Any
from typing import List
from typing import Optional
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
if TYPE_CHECKING:
from _pytest.main import Session
def pytest_addoption(parser):
@@ -77,13 +86,13 @@ class AssertionState:
def install_importhook(config):
"""Try to install the rewrite hook, raise SystemError if it fails."""
config._assertstate = AssertionState(config, "rewrite")
config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
config._store[assertstate_key] = AssertionState(config, "rewrite")
config._store[assertstate_key].hook = hook = rewrite.AssertionRewritingHook(config)
sys.meta_path.insert(0, hook)
config._assertstate.trace("installed rewrite import hook")
config._store[assertstate_key].trace("installed rewrite import hook")
def undo():
hook = config._assertstate.hook
hook = config._store[assertstate_key].hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
@@ -91,17 +100,18 @@ def install_importhook(config):
return hook
def pytest_collection(session):
def pytest_collection(session: "Session") -> None:
# this hook is only called when test modules are collected
# so for example not in the master process of pytest-xdist
# (which does not collect test modules)
assertstate = getattr(session.config, "_assertstate", None)
assertstate = session.config._store.get(assertstate_key, None)
if assertstate:
if assertstate.hook is not None:
assertstate.hook.set_session(session)
def pytest_runtest_setup(item):
@hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_protocol(item):
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks
The newinterpret and rewrite modules will use util._reprcompare if
@@ -139,6 +149,7 @@ def pytest_runtest_setup(item):
return res
return None
saved_assert_hooks = util._reprcompare, util._assertion_pass
util._reprcompare = callbinrepr
if item.ihook.pytest_assertion_pass.get_hookimpls():
@@ -150,18 +161,19 @@ def pytest_runtest_setup(item):
util._assertion_pass = call_assertion_pass_hook
yield
def pytest_runtest_teardown(item):
util._reprcompare = None
util._assertion_pass = None
util._reprcompare, util._assertion_pass = saved_assert_hooks
def pytest_sessionfinish(session):
assertstate = getattr(session.config, "_assertstate", None)
assertstate = session.config._store.get(assertstate_key, None)
if assertstate:
if assertstate.hook is not None:
assertstate.hook.set_session(None)
def pytest_assertrepr_compare(config, op, left, right):
def pytest_assertrepr_compare(
config: Config, op: str, left: Any, right: Any
) -> Optional[List[str]]:
return util.assertrepr_compare(config=config, op=op, left=left, right=right)

View File

@@ -26,9 +26,18 @@ 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.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
assertstate_key = StoreKey["AssertionState"]()
# pytest caches rewritten pycs in pycache dirs
PYTEST_TAG = "{}-pytest-{}".format(sys.implementation.cache_tag, version)
@@ -36,7 +45,7 @@ PYC_EXT = ".py" + (__debug__ and "c" or "o")
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
class AssertionRewritingHook(importlib.abc.MetaPathFinder):
class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
"""PEP302/PEP451 import hook which rewrites asserts."""
def __init__(self, config):
@@ -65,7 +74,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder):
def find_spec(self, name, path=None, target=None):
if self._writing_pyc:
return None
state = self.config._assertstate
state = self.config._store[assertstate_key]
if self._early_rewrite_bailout(name, state):
return None
state.trace("find_module called for: %s" % name)
@@ -104,7 +113,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder):
def exec_module(self, module):
fn = Path(module.__spec__.origin)
state = self.config._assertstate
state = self.config._store[assertstate_key]
self._rewritten_names.add(module.__name__)

View File

@@ -13,6 +13,7 @@ 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
@@ -28,27 +29,6 @@ _reprcompare = None # type: Optional[Callable[[str, object, object], Optional[s
_assertion_pass = None # type: Optional[Callable[[int, str, str], None]]
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
"""PrettyPrinter that always dispatches (regardless of width)."""
def _format(self, object, stream, indent, allowance, context, level):
p = self._dispatch.get(type(object).__repr__, None)
objid = id(object)
if objid in context or p is None:
return super()._format(object, stream, indent, allowance, context, level)
context[objid] = 1
p(self, object, stream, indent, allowance, context, level + 1)
del context[objid]
def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False):
return AlwaysDispatchingPrettyPrinter(
indent=1, width=80, depth=None, compact=False
).pformat(object)
def format_explanation(explanation: str) -> str:
"""This formats an explanation
@@ -195,9 +175,10 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
raise
except Exception:
explanation = [
"(pytest_assertion plugin: representation of details failed. "
"Probably an object has a faulty __repr__.)",
str(_pytest._code.ExceptionInfo.from_current()),
"(pytest_assertion plugin: representation of details failed: {}.".format(
_pytest._code.ExceptionInfo.from_current()._getreprcrash()
),
" Probably an object has a faulty __repr__.)",
]
if not explanation:
@@ -245,9 +226,11 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
left = repr(str(left))
right = repr(str(right))
explanation += ["Strings contain only whitespace, escaping them using repr()"]
# "right" is the expected base against which we compare "left",
# see https://github.com/pytest-dev/pytest/issues/3333
explanation += [
line.strip("\n")
for line in ndiff(left.splitlines(keepends), right.splitlines(keepends))
for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
]
return explanation
@@ -258,8 +241,8 @@ def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
right_lines = repr(right).splitlines(keepends)
explanation = [] # type: List[str]
explanation += ["-" + line for line in left_lines]
explanation += ["+" + line for line in right_lines]
explanation += ["+" + line for line in left_lines]
explanation += ["-" + line for line in right_lines]
return explanation
@@ -299,8 +282,10 @@ def _compare_eq_iterable(
_surrounding_parens_on_own_lines(right_formatting)
explanation = ["Full diff:"]
# "right" is the expected base against which we compare "left",
# see https://github.com/pytest-dev/pytest/issues/3333
explanation.extend(
line.rstrip() for line in difflib.ndiff(left_formatting, right_formatting)
line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting)
)
return explanation
@@ -335,8 +320,9 @@ def _compare_eq_sequence(
break
if comparing_bytes:
# when comparing bytes, it doesn't help to show the "sides contain one or more items"
# longer explanation, so skip it
# when comparing bytes, it doesn't help to show the "sides contain one or more
# items" longer explanation, so skip it
return explanation
len_diff = len_left - len_right
@@ -463,7 +449,7 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
head = text[:index]
tail = text[index + len(term) :]
correct_text = head + tail
diff = _diff_text(correct_text, text, verbose)
diff = _diff_text(text, correct_text, verbose)
newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)]
for line in diff:
if line.startswith("Skipping"):

View File

@@ -7,7 +7,11 @@ ignores the external pytest-cache
import json
import os
from collections import OrderedDict
from typing import Dict
from typing import Generator
from typing import List
from typing import Optional
from typing import Set
import attr
import py
@@ -16,9 +20,12 @@ 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.config import Config
from _pytest.main import Session
from _pytest.python import Module
README_CONTENT = """\
# pytest cache directory #
@@ -70,10 +77,10 @@ class Cache:
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args):
from _pytest.warnings import _issue_warning_captured
import warnings
from _pytest.warning_types import PytestCacheWarning
_issue_warning_captured(
warnings.warn(
PytestCacheWarning(fmt.format(**args) if args else fmt),
self._config.hook,
stacklevel=3,
@@ -160,42 +167,88 @@ class Cache:
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
class LFPluginCollWrapper:
def __init__(self, lfplugin: "LFPlugin"):
self.lfplugin = lfplugin
self._collected_at_least_one_failure = False
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector) -> Generator:
if isinstance(collector, Session):
out = yield
res = out.get_result() # type: CollectReport
# Sort any lf-paths to the beginning.
lf_paths = self.lfplugin._last_failed_paths
res.result = sorted(
res.result, key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1,
)
out.force_result(res)
return
elif isinstance(collector, Module):
if Path(str(collector.fspath)) in self.lfplugin._last_failed_paths:
out = yield
res = out.get_result()
filtered_result = [
x for x in res.result if x.nodeid in self.lfplugin.lastfailed
]
if filtered_result:
res.result = filtered_result
out.force_result(res)
if not self._collected_at_least_one_failure:
self.lfplugin.config.pluginmanager.register(
LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip"
)
self._collected_at_least_one_failure = True
return res
yield
class LFPluginCollSkipfiles:
def __init__(self, lfplugin: "LFPlugin"):
self.lfplugin = lfplugin
@pytest.hookimpl
def pytest_make_collect_report(self, collector) -> Optional[CollectReport]:
if isinstance(collector, Module):
if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths:
self.lfplugin._skipped_files += 1
return CollectReport(
collector.nodeid, "passed", longrepr=None, result=[]
)
return None
class LFPlugin:
""" Plugin which implements the --lf (run last-failing) option """
def __init__(self, config):
def __init__(self, config: Config) -> None:
self.config = config
active_keys = "lf", "failedfirst"
self.active = any(config.getoption(key) for key in active_keys)
self.lastfailed = config.cache.get("cache/lastfailed", {})
assert config.cache
self.lastfailed = config.cache.get(
"cache/lastfailed", {}
) # type: Dict[str, bool]
self._previously_failed_count = None
self._report_status = None
self._skipped_files = 0 # count skipped files during collection due to --lf
def last_failed_paths(self):
"""Returns a set with all Paths()s of the previously failed nodeids (cached).
"""
try:
return self._last_failed_paths
except AttributeError:
rootpath = Path(self.config.rootdir)
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
result = {x for x in result if x.exists()}
self._last_failed_paths = result
return result
if config.getoption("lf"):
self._last_failed_paths = self.get_last_failed_paths()
config.pluginmanager.register(
LFPluginCollWrapper(self), "lfplugin-collwrapper"
)
def pytest_ignore_collect(self, path):
"""
Ignore this file path if we are in --lf mode and it is not in the list of
previously failed files.
"""
if self.active and self.config.getoption("lf") and path.isfile():
last_failed_paths = self.last_failed_paths()
if last_failed_paths:
skip_it = Path(path) not in self.last_failed_paths()
if skip_it:
self._skipped_files += 1
return skip_it
def get_last_failed_paths(self) -> Set[Path]:
"""Returns a set with all Paths()s of the previously failed nodeids."""
rootpath = Path(str(self.config.rootdir))
result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
return {x for x in result if x.exists()}
def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0:
@@ -258,7 +311,7 @@ class LFPlugin:
self._report_status = "no previously failed tests, "
if self.config.getoption("last_failed_no_failures") == "none":
self._report_status += "deselecting all items."
config.hook.pytest_deselected(items=items)
config.hook.pytest_deselected(items=items[:])
items[:] = []
else:
self._report_status += "not deselecting items."
@@ -379,7 +432,7 @@ def pytest_cmdline_main(config):
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
def pytest_configure(config: Config) -> None:
config.cache = Cache.for_config(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")
@@ -418,7 +471,7 @@ def pytest_report_header(config):
def cacheshow(config, session):
from pprint import pformat
tw = py.io.TerminalWriter()
tw = TerminalWriter()
tw.line("cachedir: " + str(config.cache._cachedir))
if not config.cache._cachedir.is_dir():
tw.line("cache is empty")

View File

@@ -9,11 +9,23 @@ import os
import sys
from io import UnsupportedOperation
from tempfile import TemporaryFile
from typing import BinaryIO
from typing import Generator
from typing import Iterable
from typing import Optional
import pytest
from _pytest.compat import CaptureAndPassthroughIO
from _pytest.compat import CaptureIO
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.fixtures import FixtureRequest
if TYPE_CHECKING:
from typing_extensions import Literal
_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
@@ -24,8 +36,8 @@ def pytest_addoption(parser):
action="store",
default="fd" if hasattr(os, "dup") else "sys",
metavar="method",
choices=["fd", "sys", "no"],
help="per-test capturing method: one of fd|sys|no.",
choices=["fd", "sys", "no", "tee-sys"],
help="per-test capturing method: one of fd|sys|no|tee-sys.",
)
group._addoption(
"-s",
@@ -37,7 +49,7 @@ def pytest_addoption(parser):
@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args):
def pytest_load_initial_conftests(early_config: Config):
ns = early_config.known_args_namespace
if ns.capture == "fd":
_py36_windowsconsoleio_workaround(sys.stdout)
@@ -60,6 +72,18 @@ def pytest_load_initial_conftests(early_config, parser, args):
sys.stderr.write(err)
def _get_multicapture(method: "_CaptureMethod") -> "MultiCapture":
if method == "fd":
return MultiCapture(out=True, err=True, Capture=FDCapture)
elif method == "sys":
return MultiCapture(out=True, err=True, Capture=SysCapture)
elif method == "no":
return MultiCapture(out=False, err=False, in_=False)
elif method == "tee-sys":
return MultiCapture(out=True, err=True, in_=False, Capture=TeeSysCapture)
raise ValueError("unknown capturing method: {!r}".format(method))
class CaptureManager:
"""
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
@@ -73,33 +97,21 @@ class CaptureManager:
case special handling is needed to ensure the fixtures take precedence over the global capture.
"""
def __init__(self, method):
def __init__(self, method: "_CaptureMethod") -> None:
self._method = method
self._global_capturing = None
self._current_item = None
self._capture_fixture = None # type: Optional[CaptureFixture]
def __repr__(self):
return "<CaptureManager _method={!r} _global_capturing={!r} _current_item={!r}>".format(
self._method, self._global_capturing, self._current_item
return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
self._method, self._global_capturing, self._capture_fixture
)
def _getcapture(self, method):
if method == "fd":
return MultiCapture(out=True, err=True, Capture=FDCapture)
elif method == "sys":
return MultiCapture(out=True, err=True, Capture=SysCapture)
elif method == "no":
return MultiCapture(out=False, err=False, in_=False)
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
def is_capturing(self):
if self.is_globally_capturing():
return "global"
capture_fixture = getattr(self._current_item, "_capture_fixture", None)
if capture_fixture is not None:
return (
"fixture %s" % self._current_item._capture_fixture.request.fixturename
)
if self._capture_fixture:
return "fixture %s" % self._capture_fixture.request.fixturename
return False
# Global capturing control
@@ -109,7 +121,7 @@ class CaptureManager:
def start_global_capturing(self):
assert self._global_capturing is None
self._global_capturing = self._getcapture(self._method)
self._global_capturing = _get_multicapture(self._method)
self._global_capturing.start_capturing()
def stop_global_capturing(self):
@@ -131,41 +143,66 @@ class CaptureManager:
def suspend(self, in_=False):
# Need to undo local capsys-et-al if it exists before disabling global capture.
self.suspend_fixture(self._current_item)
self.suspend_fixture()
self.suspend_global_capture(in_)
def resume(self):
self.resume_global_capture()
self.resume_fixture(self._current_item)
self.resume_fixture()
def read_global_capture(self):
return self._global_capturing.readouterr()
# Fixture Control (it's just forwarding, think about removing this later)
def activate_fixture(self, item):
@contextlib.contextmanager
def _capturing_for_request(
self, request: FixtureRequest
) -> Generator["CaptureFixture", None, None]:
"""
Context manager that creates a ``CaptureFixture`` instance for the
given ``request``, ensuring there is only a single one being requested
at the same time.
This is used as a helper with ``capsys``, ``capfd`` etc.
"""
if self._capture_fixture:
other_name = next(
k
for k, v in map_fixname_class.items()
if v is self._capture_fixture.captureclass
)
raise request.raiseerror(
"cannot use {} and {} at the same time".format(
request.fixturename, other_name
)
)
capture_class = map_fixname_class[request.fixturename]
self._capture_fixture = CaptureFixture(capture_class, request)
self.activate_fixture()
yield self._capture_fixture
self._capture_fixture.close()
self._capture_fixture = None
def activate_fixture(self):
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
the global capture.
"""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture._start()
if self._capture_fixture:
self._capture_fixture._start()
def deactivate_fixture(self, item):
def deactivate_fixture(self):
"""Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture.close()
if self._capture_fixture:
self._capture_fixture.close()
def suspend_fixture(self, item):
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture._suspend()
def suspend_fixture(self):
if self._capture_fixture:
self._capture_fixture._suspend()
def resume_fixture(self, item):
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture._resume()
def resume_fixture(self):
if self._capture_fixture:
self._capture_fixture._resume()
# Helper context managers
@@ -181,11 +218,11 @@ class CaptureManager:
@contextlib.contextmanager
def item_capture(self, when, item):
self.resume_global_capture()
self.activate_fixture(item)
self.activate_fixture()
try:
yield
finally:
self.deactivate_fixture(item)
self.deactivate_fixture()
self.suspend_global_capture(in_=False)
out, err = self.read_global_capture()
@@ -209,12 +246,6 @@ class CaptureManager:
else:
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(self, item):
self._current_item = item
yield
self._current_item = None
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item):
with self.item_capture("setup", item):
@@ -239,18 +270,6 @@ class CaptureManager:
self.stop_global_capturing()
capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
def _ensure_only_one_capture_fixture(request: FixtureRequest, name):
fixtures = sorted(set(request.fixturenames) & capture_fixtures - {name})
if fixtures:
arg = fixtures[0] if len(fixtures) == 1 else fixtures
raise request.raiseerror(
"cannot use {} and {} at the same time".format(arg, name)
)
@pytest.fixture
def capsys(request):
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
@@ -259,8 +278,8 @@ def capsys(request):
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
"""
_ensure_only_one_capture_fixture(request, "capsys")
with _install_capture_fixture_on_item(request, SysCapture) as fixture:
capman = request.config.pluginmanager.getplugin("capturemanager")
with capman._capturing_for_request(request) as fixture:
yield fixture
@@ -272,8 +291,8 @@ def capsysbinary(request):
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
"""
_ensure_only_one_capture_fixture(request, "capsysbinary")
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
capman = request.config.pluginmanager.getplugin("capturemanager")
with capman._capturing_for_request(request) as fixture:
yield fixture
@@ -285,12 +304,12 @@ def capfd(request):
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
"""
_ensure_only_one_capture_fixture(request, "capfd")
if not hasattr(os, "dup"):
pytest.skip(
"capfd fixture needs os.dup function which is not available in this system"
)
with _install_capture_fixture_on_item(request, FDCapture) as fixture:
capman = request.config.pluginmanager.getplugin("capturemanager")
with capman._capturing_for_request(request) as fixture:
yield fixture
@@ -302,35 +321,15 @@ def capfdbinary(request):
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
"""
_ensure_only_one_capture_fixture(request, "capfdbinary")
if not hasattr(os, "dup"):
pytest.skip(
"capfdbinary fixture needs os.dup function which is not available in this system"
)
with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
capman = request.config.pluginmanager.getplugin("capturemanager")
with capman._capturing_for_request(request) as fixture:
yield fixture
@contextlib.contextmanager
def _install_capture_fixture_on_item(request, capture_class):
"""
Context manager which creates a ``CaptureFixture`` instance and "installs" it on
the item/node of the given request. Used by ``capsys`` and ``capfd``.
The CaptureFixture is added as attribute of the item because it needs to accessed
by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
"""
request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
capmanager = request.config.pluginmanager.getplugin("capturemanager")
# Need to active this fixture right away in case it is being used by another fixture (setup phase).
# If this fixture is being used only by a test function (call phase), then we wouldn't need this
# activation, but it doesn't hurt.
capmanager.activate_fixture(request.node)
yield fixture
fixture.close()
del request.node._capture_fixture
class CaptureFixture:
"""
Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
@@ -413,30 +412,27 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
class EncodedFile:
errors = "strict" # possibly needed by py3 code (issue555)
def __init__(self, buffer, encoding):
def __init__(self, buffer: BinaryIO, encoding: str) -> None:
self.buffer = buffer
self.encoding = encoding
def write(self, obj):
if isinstance(obj, str):
obj = obj.encode(self.encoding, "replace")
else:
def write(self, s: str) -> int:
if not isinstance(s, str):
raise TypeError(
"write() argument must be str, not {}".format(type(obj).__name__)
"write() argument must be str, not {}".format(type(s).__name__)
)
self.buffer.write(obj)
return self.buffer.write(s.encode(self.encoding, "replace"))
def writelines(self, linelist):
data = "".join(linelist)
self.write(data)
def writelines(self, lines: Iterable[str]) -> None:
self.buffer.writelines(x.encode(self.encoding, "replace") for x in lines)
@property
def name(self):
def name(self) -> str:
"""Ensure that file.name is a string."""
return repr(self.buffer)
@property
def mode(self):
def mode(self) -> str:
return self.buffer.mode.replace("b", "")
def __getattr__(self, name):
@@ -449,6 +445,7 @@ CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
class MultiCapture:
out = err = in_ = None
_state = None
_in_suspended = False
def __init__(self, out=True, err=True, in_=True, Capture=None):
if in_:
@@ -460,11 +457,7 @@ class MultiCapture:
def __repr__(self):
return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
self.out,
self.err,
self.in_,
self._state,
getattr(self, "_in_suspended", "<UNSET>"),
self.out, self.err, self.in_, self._state, self._in_suspended,
)
def start_capturing(self):
@@ -501,9 +494,9 @@ class MultiCapture:
self.out.resume()
if self.err:
self.err.resume()
if hasattr(self, "_in_suspended"):
if self._in_suspended:
self.in_.resume()
del self._in_suspended
self._in_suspended = False
def stop_capturing(self):
""" stop capturing and reset capturing streams """
@@ -517,12 +510,16 @@ class MultiCapture:
if self.in_:
self.in_.done()
def readouterr(self):
""" return snapshot unicode value of stdout/stderr capturings. """
return CaptureResult(
self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "",
)
def readouterr(self) -> CaptureResult:
if self.out:
out = self.out.snap()
else:
out = ""
if self.err:
err = self.err.snap()
else:
err = ""
return CaptureResult(out, err)
class NoCapture:
@@ -566,8 +563,12 @@ class FDCaptureBinary:
self.tmpfile_fd = tmpfile.fileno()
def __repr__(self):
return "<FDCapture {} oldfd={} _state={!r}>".format(
self.targetfd, getattr(self, "targetfd_save", None), self._state
return "<{} {} oldfd={} _state={!r} tmpfile={}>".format(
self.__class__.__name__,
self.targetfd,
getattr(self, "targetfd_save", "<UNSET>"),
self._state,
hasattr(self, "tmpfile") and repr(self.tmpfile) or "<UNSET>",
)
def _start(self):
@@ -609,8 +610,6 @@ class FDCaptureBinary:
def writeorg(self, data):
""" write to original file descriptor. """
if isinstance(data, str):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
@@ -630,10 +629,15 @@ class FDCapture(FDCaptureBinary):
res = str(res, enc, "replace")
return res
def writeorg(self, data):
""" write to original file descriptor. """
data = data.encode("utf-8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
class SysCapture:
EMPTY_BUFFER = str()
class SysCaptureBinary:
EMPTY_BUFFER = b""
_state = None
def __init__(self, fd, tmpfile=None):
@@ -648,8 +652,12 @@ class SysCapture:
self.tmpfile = tmpfile
def __repr__(self):
return "<SysCapture {} _old={!r}, tmpfile={!r} _state={!r}>".format(
self.name, self._old, self.tmpfile, self._state
return "<{} {} _old={} _state={!r} tmpfile={!r}>".format(
self.__class__.__name__,
self.name,
hasattr(self, "_old") and repr(self._old) or "<UNSET>",
self._state,
self.tmpfile,
)
def start(self):
@@ -657,7 +665,7 @@ class SysCapture:
self._state = "started"
def snap(self):
res = self.tmpfile.getvalue()
res = self.tmpfile.buffer.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
@@ -676,20 +684,45 @@ class SysCapture:
setattr(sys, self.name, self.tmpfile)
self._state = "resumed"
def writeorg(self, data):
self._old.flush()
self._old.buffer.write(data)
self._old.buffer.flush()
class SysCapture(SysCaptureBinary):
EMPTY_BUFFER = str() # type: ignore[assignment] # noqa: F821
def snap(self):
res = self.tmpfile.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
def writeorg(self, data):
self._old.write(data)
self._old.flush()
class SysCaptureBinary(SysCapture):
# Ignore type because it doesn't match the type in the superclass (str).
EMPTY_BUFFER = b"" # type: ignore
class TeeSysCapture(SysCapture):
def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
self._old = getattr(sys, name)
self.name = name
if tmpfile is None:
if name == "stdin":
tmpfile = DontReadFromInput()
else:
tmpfile = CaptureAndPassthroughIO(self._old)
self.tmpfile = tmpfile
def snap(self):
res = self.tmpfile.buffer.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
map_fixname_class = {
"capfd": FDCapture,
"capfdbinary": FDCaptureBinary,
"capsys": SysCapture,
"capsysbinary": SysCaptureBinary,
}
class DontReadFromInput:

View File

@@ -13,6 +13,7 @@ from inspect import signature
from typing import Any
from typing import Callable
from typing import Generic
from typing import IO
from typing import Optional
from typing import overload
from typing import Tuple
@@ -22,7 +23,6 @@ from typing import Union
import attr
import py
import _pytest
from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
@@ -93,12 +93,21 @@ def iscoroutinefunction(func: object) -> bool:
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
def is_async_function(func: object) -> bool:
"""Return True if the given function seems to be an async function or async generator"""
return iscoroutinefunction(func) or (
sys.version_info >= (3, 6) and inspect.isasyncgenfunction(func)
)
def getlocation(function, curdir=None) -> str:
function = get_real_func(function)
fn = py.path.local(inspect.getfile(function))
lineno = function.__code__.co_firstlineno
if curdir is not None and fn.relto(curdir):
fn = fn.relto(curdir)
if curdir is not None:
relfn = fn.relto(curdir)
if relfn:
return "%s:%d" % (relfn, lineno + 1)
return "%s:%d" % (fn, lineno + 1)
@@ -141,12 +150,12 @@ def getfuncargnames(
the case of cls, the function is a static method.
The name parameter should be the original name in which the function was collected.
@RonnyPfannschmidt: This function should be refactored when we
revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the
function object well after collection has occurred.
"""
# TODO(RonnyPfannschmidt): This function should be refactored when we
# revisit fixtures. The fixture mechanism should ask the node for
# the fixture names, and not try to obtain directly from the
# function object well after collection has occurred.
# The parameters attribute of a Signature object contains an
# ordered mapping of parameter names to Parameter instances. This
# creates a tuple of the names of the parameters that don't have
@@ -225,7 +234,7 @@ def _bytes_to_ascii(val: bytes) -> str:
return val.decode("ascii", "backslashreplace")
def ascii_escaped(val: Union[bytes, str]):
def ascii_escaped(val: Union[bytes, str]) -> str:
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
@@ -305,16 +314,6 @@ def get_real_method(obj, holder):
return obj
def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
obj = get_real_func(obj)
if hasattr(obj, "place_as"):
obj = obj.place_as
fslineno = _pytest._code.getfslineno(obj)
assert isinstance(fslineno[1], int), obj
return fslineno
def getimfunc(func):
try:
return func.__func__
@@ -377,6 +376,16 @@ class CaptureIO(io.TextIOWrapper):
return self.buffer.getvalue().decode("UTF-8")
class CaptureAndPassthroughIO(CaptureIO):
def __init__(self, other: IO) -> None:
self._other = other
super().__init__()
def write(self, s) -> int:
super().write(s)
return self._other.write(s)
if sys.version_info < (3, 5, 2):
def overload(f): # noqa: F811

View File

@@ -1,6 +1,7 @@
""" command line options, ini-file and conftest.py processing. """
import argparse
import copy
import enum
import inspect
import os
import shlex
@@ -27,7 +28,6 @@ from pluggy import HookspecMarker
from pluggy import PluginManager
import _pytest._code
import _pytest.assertion
import _pytest.deprecated
import _pytest.hookspec # the extension point definitions
from .exceptions import PrintHelp
@@ -36,21 +36,55 @@ from .findpaths import determine_setup
from .findpaths import exists
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
from _pytest._io import TerminalWriter
from _pytest.compat import importlib_metadata
from _pytest.compat import TYPE_CHECKING
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import Path
from _pytest.store import Store
from _pytest.warning_types import PytestConfigWarning
if TYPE_CHECKING:
from typing import Type
from .argparsing import Argument
_PluggyPlugin = object
"""A type to represent plugin objects.
Plugins can be any namespace, so we can't narrow it down much, but we use an
alias to make the intent clear.
Ideally this type would be provided by pluggy itself."""
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
class ExitCode(enum.IntEnum):
"""
.. versionadded:: 5.0
Encodes the valid exit codes by pytest.
Currently users and plugins may supply other exit codes as well.
"""
#: tests passed
OK = 0
#: tests failed
TESTS_FAILED = 1
#: pytest was interrupted
INTERRUPTED = 2
#: an internal error got in the way
INTERNAL_ERROR = 3
#: pytest was misused
USAGE_ERROR = 4
#: pytest couldn't find tests
NO_TESTS_COLLECTED = 5
class ConftestImportFailure(Exception):
def __init__(self, path, excinfo):
Exception.__init__(self, path, excinfo)
@@ -58,7 +92,7 @@ class ConftestImportFailure(Exception):
self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType]
def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
def main(args=None, plugins=None) -> Union[int, ExitCode]:
""" return exit code, after performing an in-process test run.
:arg args: list of command line arguments.
@@ -66,14 +100,12 @@ def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
:arg plugins: list of plugin objects to be auto-registered during
initialization.
"""
from _pytest.main import ExitCode
try:
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
exc_info = ExceptionInfo(e.excinfo)
tw = py.io.TerminalWriter(sys.stderr)
tw = TerminalWriter(sys.stderr)
tw.line(
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
)
@@ -99,7 +131,7 @@ def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
finally:
config._ensure_unconfigure()
except UsageError as e:
tw = py.io.TerminalWriter(sys.stderr)
tw = TerminalWriter(sys.stderr)
for msg in e.args:
tw.line("ERROR: {}\n".format(msg), red=True)
return ExitCode.USAGE_ERROR
@@ -183,7 +215,7 @@ def get_config(args=None, plugins=None):
if args is not None:
# Handle any "-p no:plugin" args.
pluginmanager.consider_preparse(args)
pluginmanager.consider_preparse(args, exclude_only=True)
for spec in default_plugins:
pluginmanager.import_plugin(spec)
@@ -202,13 +234,15 @@ def get_plugin_manager():
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
def _prepareconfig(
args: Optional[Union[py.path.local, List[str]]] = None, plugins=None
):
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
args = [str(args)]
elif not isinstance(args, (tuple, list)):
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
elif not isinstance(args, list):
msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args)))
config = get_config(args, plugins)
@@ -252,6 +286,8 @@ class PytestPluginManager(PluginManager):
"""
def __init__(self):
import _pytest.assertion
super().__init__("pytest")
# The objects are module objects, only used generically.
self._conftest_plugins = set() # type: Set[object]
@@ -480,7 +516,7 @@ class PytestPluginManager(PluginManager):
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" % (mod))
self.trace("loading conftestmodule {!r}".format(mod))
self.consider_conftest(mod)
return mod
@@ -489,7 +525,7 @@ class PytestPluginManager(PluginManager):
#
#
def consider_preparse(self, args):
def consider_preparse(self, args, *, exclude_only=False):
i = 0
n = len(args)
while i < n:
@@ -506,6 +542,8 @@ class PytestPluginManager(PluginManager):
parg = opt[2:]
else:
continue
if exclude_only and not parg.startswith("no:"):
continue
self.consider_pluginarg(parg)
def consider_pluginarg(self, arg):
@@ -574,13 +612,9 @@ class PytestPluginManager(PluginManager):
try:
__import__(importspec)
except ImportError as e:
new_exc_message = 'Error importing plugin "{}": {}'.format(
modname, str(e.args[0])
)
new_exc = ImportError(new_exc_message)
tb = sys.exc_info()[2]
raise new_exc.with_traceback(tb)
raise ImportError(
'Error importing plugin "{}": {}'.format(modname, str(e.args[0]))
).with_traceback(e.__traceback__)
except Skipped as e:
from _pytest.warnings import _issue_warning_captured
@@ -588,7 +622,7 @@ class PytestPluginManager(PluginManager):
_issue_warning_captured(
PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)),
self.hook,
stacklevel=1,
stacklevel=2,
)
else:
mod = sys.modules[importspec]
@@ -731,7 +765,7 @@ class Config:
plugins = attr.ib()
dir = attr.ib(type=Path)
def __init__(self, pluginmanager, *, invocation_params=None):
def __init__(self, pluginmanager, *, invocation_params=None) -> None:
from .argparsing import Parser, FILE_OR_DIR
if invocation_params is None:
@@ -754,12 +788,20 @@ class Config:
self._override_ini = () # type: Sequence[str]
self._opt2dest = {} # type: Dict[str, str]
self._cleanup = [] # type: List[Callable[[], None]]
# A place where plugins can store information on the config for their
# own use. Currently only intended for internal plugins.
self._store = Store()
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
self.hook.pytest_addoption.call_historic(
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
)
if TYPE_CHECKING:
from _pytest.cacheprovider import Cache
self.cache = None # type: Optional[Cache]
@property
def invocation_dir(self):
"""Backward compatibility"""
@@ -844,11 +886,11 @@ class Config:
config.pluginmanager.consider_pluginarg(x)
return config
def _processopt(self, opt):
def _processopt(self, opt: "Argument") -> None:
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
if hasattr(opt, "default") and opt.dest:
if hasattr(opt, "default"):
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
@@ -856,7 +898,7 @@ class Config:
def pytest_load_initial_conftests(self, early_config):
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args) -> None:
def _initini(self, args: Sequence[str]) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
@@ -873,7 +915,7 @@ class Config:
self._parser.addini("minversion", "minimally required pytest version")
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args):
def _consider_importhook(self, args: Sequence[str]) -> None:
"""Install the PEP 302 import hook if using assertion rewriting.
Needs to parse the --assert=<mode> option from the commandline
@@ -883,6 +925,8 @@ class Config:
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
mode = getattr(ns, "assertmode", "plain")
if mode == "rewrite":
import _pytest.assertion
try:
hook = _pytest.assertion.install_importhook(self)
except SystemError:
@@ -913,19 +957,19 @@ class Config:
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _validate_args(self, args, via):
def _validate_args(self, args: List[str], via: str) -> List[str]:
"""Validate known args."""
self._parser._config_source_hint = via
self._parser._config_source_hint = via # type: ignore
try:
self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
finally:
del self._parser._config_source_hint
del self._parser._config_source_hint # type: ignore
return args
def _preparse(self, args, addopts=True):
def _preparse(self, args: List[str], addopts: bool = True) -> None:
if addopts:
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
if len(env_addopts):
@@ -941,7 +985,7 @@ class Config:
self._checkversion()
self._consider_importhook(args)
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_preparse(args, exclude_only=False)
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
# Don't autoload from setuptools entry point. Only explicitly specified
# plugins are going to be loaded.
@@ -989,7 +1033,7 @@ class Config:
)
)
def parse(self, args, addopts=True):
def parse(self, args: List[str], addopts: bool = True) -> None:
# parse given cmdline arguments into this config object.
assert not hasattr(
self, "args"
@@ -1000,7 +1044,7 @@ class Config:
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
self._parser.after_preparse = True
self._parser.after_preparse = True # type: ignore
try:
args = self._parser.parse_setoption(
args, self.option, namespace=self.option
@@ -1087,7 +1131,11 @@ class Config:
try:
key, user_ini_value = ini_config.split("=", 1)
except ValueError:
raise UsageError("-o/--override-ini expects option=value style.")
raise UsageError(
"-o/--override-ini expects option=value style (got: {!r}).".format(
ini_config
)
)
else:
if key == name:
value = user_ini_value
@@ -1153,34 +1201,12 @@ def _warn_about_missing_assertion(mode):
)
def setns(obj, dic):
import pytest
for name, value in dic.items():
if isinstance(value, dict):
mod = getattr(obj, name, None)
if mod is None:
modname = "pytest.%s" % name
mod = types.ModuleType(modname)
sys.modules[modname] = mod
mod.__all__ = []
setattr(obj, name, mod)
obj.__all__.append(name)
setns(mod, value)
else:
setattr(obj, name, value)
obj.__all__.append(name)
# if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)
def create_terminal_writer(config, *args, **kwargs):
def create_terminal_writer(config: Config, *args, **kwargs) -> TerminalWriter:
"""Create a TerminalWriter instance configured according to the options
in the config object. Every code which requires a TerminalWriter object
and has access to a config object should use this function.
"""
tw = py.io.TerminalWriter(*args, **kwargs)
tw = TerminalWriter(*args, **kwargs)
if config.option.color == "yes":
tw.hasmarkup = True
if config.option.color == "no":

View File

@@ -3,15 +3,25 @@ import sys
import warnings
from gettext import gettext
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
import py
from _pytest.compat import TYPE_CHECKING
from _pytest.config.exceptions import UsageError
if TYPE_CHECKING:
from typing import NoReturn
from typing_extensions import Literal # noqa: F401
FILE_OR_DIR = "file_or_dir"
@@ -22,9 +32,13 @@ class Parser:
there's an error processing the command line arguments.
"""
prog = None
prog = None # type: Optional[str]
def __init__(self, usage=None, processopt=None):
def __init__(
self,
usage: Optional[str] = None,
processopt: Optional[Callable[["Argument"], None]] = None,
) -> None:
self._anonymous = OptionGroup("custom options", parser=self)
self._groups = [] # type: List[OptionGroup]
self._processopt = processopt
@@ -33,12 +47,14 @@ class Parser:
self._ininames = [] # type: List[str]
self.extra_info = {} # type: Dict[str, Any]
def processoption(self, option):
def processoption(self, option: "Argument") -> None:
if self._processopt:
if option.dest:
self._processopt(option)
def getgroup(self, name, description="", after=None):
def getgroup(
self, name: str, description: str = "", after: Optional[str] = None
) -> "OptionGroup":
""" get (or create) a named option Group.
:name: name of the option group.
@@ -61,13 +77,13 @@ class Parser:
self._groups.insert(i + 1, group)
return group
def addoption(self, *opts, **attrs):
def addoption(self, *opts: str, **attrs: Any) -> None:
""" register a command line option.
:opts: option names, can be short or long options.
:attrs: same attributes which the ``add_option()`` function of the
:attrs: same attributes which the ``add_argument()`` function of the
`argparse library
<http://docs.python.org/2/library/argparse.html>`_
<https://docs.python.org/library/argparse.html>`_
accepts.
After command line parsing options are available on the pytest config
@@ -77,13 +93,17 @@ class Parser:
"""
self._anonymous.addoption(*opts, **attrs)
def parse(self, args, namespace=None):
def parse(
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
from _pytest._argcomplete import try_argcomplete
self.optparser = self._getparser()
try_argcomplete(self.optparser)
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return self.optparser.parse_args(args, namespace=namespace)
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
return self.optparser.parse_args(strargs, namespace=namespace)
def _getparser(self) -> "MyOptionParser":
from _pytest._argcomplete import filescompleter
@@ -98,36 +118,52 @@ class Parser:
n = option.names()
a = option.attrs()
arggroup.add_argument(*n, **a)
file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*")
# bash like autocompletion for dirs (appending '/')
# Type ignored because typeshed doesn't know about argcomplete.
optparser.add_argument( # type: ignore
FILE_OR_DIR, nargs="*"
).completer = filescompleter
file_or_dir_arg.completer = filescompleter # type: ignore
return optparser
def parse_setoption(self, args, option, namespace=None):
def parse_setoption(
self,
args: Sequence[Union[str, py.path.local]],
option: argparse.Namespace,
namespace: Optional[argparse.Namespace] = None,
) -> List[str]:
parsedoption = self.parse(args, namespace=namespace)
for name, value in parsedoption.__dict__.items():
setattr(option, name, value)
return getattr(parsedoption, FILE_OR_DIR)
return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
def parse_known_args(self, args, namespace=None) -> argparse.Namespace:
def parse_known_args(
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
"""parses and returns a namespace object with known arguments at this
point.
"""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
def parse_known_and_unknown_args(
self, args, namespace=None
self,
args: Sequence[Union[str, py.path.local]],
namespace: Optional[argparse.Namespace] = None,
) -> Tuple[argparse.Namespace, List[str]]:
"""parses and returns a namespace object with known arguments, and
the remaining arguments unknown at this point.
"""
optparser = self._getparser()
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return optparser.parse_known_args(args, namespace=namespace)
strargs = [str(x) if isinstance(x, py.path.local) else x for x in args]
return optparser.parse_known_args(strargs, namespace=namespace)
def addini(self, name, help, type=None, default=None):
def addini(
self,
name: str,
help: str,
type: Optional["Literal['pathlist', 'args', 'linelist', 'bool']"] = None,
default=None,
) -> None:
""" register an ini-file option.
:name: name of the ini-variable
@@ -149,11 +185,11 @@ class ArgumentError(Exception):
inconsistent arguments.
"""
def __init__(self, msg, option):
def __init__(self, msg: str, option: Union["Argument", str]) -> None:
self.msg = msg
self.option_id = str(option)
def __str__(self):
def __str__(self) -> str:
if self.option_id:
return "option {}: {}".format(self.option_id, self.msg)
else:
@@ -170,12 +206,11 @@ class Argument:
_typ_map = {"int": int, "string": str, "float": float, "complex": complex}
def __init__(self, *names, **attrs):
def __init__(self, *names: str, **attrs: Any) -> None:
"""store parms in private vars for use in add_argument"""
self._attrs = attrs
self._short_opts = [] # type: List[str]
self._long_opts = [] # type: List[str]
self.dest = attrs.get("dest")
if "%default" in (attrs.get("help") or ""):
warnings.warn(
'pytest now uses argparse. "%default" should be'
@@ -221,23 +256,25 @@ class Argument:
except KeyError:
pass
self._set_opt_strings(names)
if not self.dest:
if self._long_opts:
self.dest = self._long_opts[0][2:].replace("-", "_")
else:
try:
self.dest = self._short_opts[0][1:]
except IndexError:
raise ArgumentError("need a long or short option", self)
dest = attrs.get("dest") # type: Optional[str]
if dest:
self.dest = dest
elif self._long_opts:
self.dest = self._long_opts[0][2:].replace("-", "_")
else:
try:
self.dest = self._short_opts[0][1:]
except IndexError:
self.dest = "???" # Needed for the error repr.
raise ArgumentError("need a long or short option", self)
def names(self):
def names(self) -> List[str]:
return self._short_opts + self._long_opts
def attrs(self):
def attrs(self) -> Mapping[str, Any]:
# update any attributes set by processopt
attrs = "default dest help".split()
if self.dest:
attrs.append(self.dest)
attrs.append(self.dest)
for attr in attrs:
try:
self._attrs[attr] = getattr(self, attr)
@@ -250,7 +287,7 @@ class Argument:
self._attrs["help"] = a
return self._attrs
def _set_opt_strings(self, opts):
def _set_opt_strings(self, opts: Sequence[str]) -> None:
"""directly from optparse
might not be necessary as this is passed to argparse later on"""
@@ -293,13 +330,15 @@ class Argument:
class OptionGroup:
def __init__(self, name, description="", parser=None):
def __init__(
self, name: str, description: str = "", parser: Optional[Parser] = None
) -> None:
self.name = name
self.description = description
self.options = [] # type: List[Argument]
self.parser = parser
def addoption(self, *optnames, **attrs):
def addoption(self, *optnames: str, **attrs: Any) -> None:
""" add an option to this group.
if a shortened version of a long option is specified it will
@@ -315,11 +354,11 @@ class OptionGroup:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=False)
def _addoption(self, *optnames, **attrs):
def _addoption(self, *optnames: str, **attrs: Any) -> None:
option = Argument(*optnames, **attrs)
self._addoption_instance(option, shortupper=True)
def _addoption_instance(self, option, shortupper=False):
def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
if not shortupper:
for opt in option._short_opts:
if opt[0] == "-" and opt[1].islower():
@@ -330,9 +369,12 @@ class OptionGroup:
class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser, extra_info=None, prog=None):
if not extra_info:
extra_info = {}
def __init__(
self,
parser: Parser,
extra_info: Optional[Dict[str, Any]] = None,
prog: Optional[str] = None,
) -> None:
self._parser = parser
argparse.ArgumentParser.__init__(
self,
@@ -344,34 +386,42 @@ class MyOptionParser(argparse.ArgumentParser):
)
# extra_info is a dict of (param -> value) to display if there's
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
self.extra_info = extra_info if extra_info else {}
def error(self, message):
def error(self, message: str) -> "NoReturn":
"""Transform argparse error message into UsageError."""
msg = "{}: error: {}".format(self.prog, message)
if hasattr(self._parser, "_config_source_hint"):
msg = "{} ({})".format(msg, self._parser._config_source_hint)
# Type ignored because the attribute is set dynamically.
msg = "{} ({})".format(msg, self._parser._config_source_hint) # type: ignore
raise UsageError(self.format_usage() + msg)
def parse_args(self, args=None, namespace=None):
# Type ignored because typeshed has a very complex type in the superclass.
def parse_args( # type: ignore
self,
args: Optional[Sequence[str]] = None,
namespace: Optional[argparse.Namespace] = None,
) -> argparse.Namespace:
"""allow splitting of positional arguments"""
args, argv = self.parse_known_args(args, namespace)
if argv:
for arg in argv:
parsed, unrecognized = self.parse_known_args(args, namespace)
if unrecognized:
for arg in unrecognized:
if arg and arg[0] == "-":
lines = ["unrecognized arguments: %s" % (" ".join(argv))]
lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))]
for k, v in sorted(self.extra_info.items()):
lines.append(" {}: {}".format(k, v))
self.error("\n".join(lines))
getattr(args, FILE_OR_DIR).extend(argv)
return args
getattr(parsed, FILE_OR_DIR).extend(unrecognized)
return parsed
if sys.version_info[:2] < (3, 9): # pragma: no cover
# Backport of https://github.com/python/cpython/pull/14316 so we can
# disable long --argument abbreviations without breaking short flags.
def _parse_optional(self, arg_string):
def _parse_optional(
self, arg_string: str
) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
if not arg_string:
return None
if not arg_string[0] in self.prefix_chars:
@@ -409,49 +459,45 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
"""shorten help for long options that differ only in extra hyphens
- collapse **long** options that are the same except for extra hyphens
- special action attribute map_long_option allows suppressing additional
long options
- shortcut if there are only two options and one of them is a short one
- cache result on action object as this is called at least 2 times
"""
def __init__(self, *args, **kwargs):
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Use more accurate terminal width via pylib."""
if "width" not in kwargs:
kwargs["width"] = py.io.get_terminal_width()
super().__init__(*args, **kwargs)
def _format_action_invocation(self, action):
def _format_action_invocation(self, action: argparse.Action) -> str:
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != "-": # only optional arguments
return orgstr
res = getattr(action, "_formatted_action_invocation", None)
res = getattr(
action, "_formatted_action_invocation", None
) # type: Optional[str]
if res:
return res
options = orgstr.split(", ")
if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2):
# a shortcut for '-h, --help' or '--abc', '-a'
action._formatted_action_invocation = orgstr
action._formatted_action_invocation = orgstr # type: ignore
return orgstr
return_list = []
option_map = getattr(action, "map_long_option", {})
if option_map is None:
option_map = {}
short_long = {} # type: Dict[str, str]
for option in options:
if len(option) == 2 or option[2] == " ":
continue
if not option.startswith("--"):
raise ArgumentError(
'long optional argument without "--": [%s]' % (option), self
'long optional argument without "--": [%s]' % (option), option
)
xxoption = option[2:]
if xxoption.split()[0] not in option_map:
shortened = xxoption.replace("-", "")
if shortened not in short_long or len(short_long[shortened]) < len(
xxoption
):
short_long[shortened] = xxoption
shortened = xxoption.replace("-", "")
if shortened not in short_long or len(short_long[shortened]) < len(
xxoption
):
short_long[shortened] = xxoption
# now short_long has been filled out to the longest with dashes
# **and** we keep the right option ordering from add_argument
for option in options:
@@ -459,5 +505,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return_list.append(option)
if option[2:] == short_long.get(option.replace("-", "")):
return_list.append(option.replace(" ", "=", 1))
action._formatted_action_invocation = ", ".join(return_list)
return action._formatted_action_invocation
formatted_action_invocation = ", ".join(return_list)
action._formatted_action_invocation = formatted_action_invocation # type: ignore
return formatted_action_invocation

View File

@@ -1,6 +1,9 @@
import os
from typing import Any
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
import py
@@ -60,7 +63,7 @@ def getcfg(args, config=None):
return None, None, None
def get_common_ancestor(paths):
def get_common_ancestor(paths: Iterable[py.path.local]) -> py.path.local:
common_ancestor = None
for path in paths:
if not path.exists():
@@ -113,7 +116,7 @@ def determine_setup(
args: List[str],
rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = None,
):
) -> Tuple[py.path.local, Optional[str], Any]:
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
@@ -121,7 +124,9 @@ def determine_setup(
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
for section in sections:
try:
inicfg = iniconfig[section]
inicfg = iniconfig[
section
] # type: Optional[py.iniconfig._SectionWrapper]
if is_cfg_file and section == "pytest" and config is not None:
fail(
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False

View File

@@ -4,6 +4,7 @@ import functools
import sys
from _pytest import outcomes
from _pytest.config import ConftestImportFailure
from _pytest.config import hookimpl
from _pytest.config.exceptions import UsageError
@@ -272,11 +273,15 @@ class PdbInvoke:
class PdbTrace:
@hookimpl(hookwrapper=True)
def pytest_pyfunc_call(self, pyfuncitem):
_test_pytest_function(pyfuncitem)
wrap_pytest_function_for_tracing(pyfuncitem)
yield
def _test_pytest_function(pyfuncitem):
def wrap_pytest_function_for_tracing(pyfuncitem):
"""Changes the python function object of the given Function item by a wrapper which actually
enters pdb before calling the python function itself, effectively leaving the user
in the pdb prompt in the first statement of the function.
"""
_pdb = pytestPDB._init_pdb("runcall")
testfunction = pyfuncitem.obj
@@ -291,6 +296,13 @@ def _test_pytest_function(pyfuncitem):
pyfuncitem.obj = wrapper
def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
"""Wrap the given pytestfunct item for tracing support if --trace was given in
the command line"""
if pyfuncitem.config.getvalue("trace"):
wrap_pytest_function_for_tracing(pyfuncitem)
def _enter_pdb(node, excinfo, rep):
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
@@ -327,6 +339,10 @@ def _postmortem_traceback(excinfo):
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:
return excinfo.value.exc_info[2]
elif isinstance(excinfo.value, ConftestImportFailure):
# A config.ConftestImportFailure is not useful for post_mortem.
# Use the underlying exception instead:
return excinfo.value.excinfo[2]
else:
return excinfo._excinfo[2]

View File

@@ -9,6 +9,7 @@ All constants defined in this module should be either PytestWarning instances or
in case of warnings which need to format their messages.
"""
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import UnformattedWarning
# set of plugins which have been integrated into the core; we use this list to ignore
# them during registration to avoid conflicts
@@ -18,13 +19,11 @@ DEPRECATED_EXTERNAL_PLUGINS = {
"pytest_faulthandler",
}
FUNCARGNAMES = PytestDeprecationWarning(
"The `funcargnames` attribute was an alias for `fixturenames`, "
"since pytest 2.3 - use the newer attribute instead."
)
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated, please try the new pytest-reportlog plugin.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
@@ -35,8 +34,29 @@ FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning(
"as a keyword argument instead."
)
NODE_USE_FROM_PARENT = UnformattedWarning(
PytestDeprecationWarning,
"direct construction of {name} has been deprecated, please use {name}.from_parent",
)
JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning(
"The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n"
"Add 'junit_family=xunit1' to your pytest.ini file to keep the current format "
"in future versions of pytest and silence this warning."
)
NO_PRINT_LOGS = PytestDeprecationWarning(
"--no-print-logs is deprecated and scheduled for removal in pytest 6.0.\n"
"Please use --show-capture instead."
)
COLLECT_DIRECTORY_HOOK = PytestDeprecationWarning(
"The pytest_collect_directory hook is not working.\n"
"Please use collect_ignore in conftests or pytest_collection_modifyitems."
)
TERMINALWRITER_WRITER = PytestDeprecationWarning(
"The TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information."
)

View File

@@ -13,15 +13,18 @@ from typing import Sequence
from typing import Tuple
from typing import Union
import py.path
import pytest
from _pytest import outcomes
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest.compat import safe_getattr
from _pytest.compat import TYPE_CHECKING
from _pytest.fixtures import FixtureRequest
from _pytest.outcomes import Skipped
from _pytest.outcomes import OutcomeException
from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning
@@ -105,20 +108,20 @@ def pytest_unconfigure():
RUNNER_CLASS = None
def pytest_collect_file(path, parent):
def pytest_collect_file(path: py.path.local, parent):
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
return DoctestModule(path, parent)
if config.option.doctestmodules and not _is_setup_py(path):
return DoctestModule.from_parent(parent, fspath=path)
elif _is_doctest(config, path, parent):
return DoctestTextfile(path, parent)
return DoctestTextfile.from_parent(parent, fspath=path)
def _is_setup_py(config, path, parent):
def _is_setup_py(path: py.path.local) -> bool:
if path.basename != "setup.py":
return False
contents = path.read()
return "setuptools" in contents or "distutils" in contents
contents = path.read_binary()
return b"setuptools" in contents or b"distutils" in contents
def _is_doctest(config, path, parent):
@@ -137,7 +140,7 @@ class ReprFailDoctest(TerminalRepr):
):
self.reprlocation_lines = reprlocation_lines
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
for reprlocation, lines in self.reprlocation_lines:
for line in lines:
tw.line(line)
@@ -175,7 +178,7 @@ def _init_runner_class() -> "Type[doctest.DocTestRunner]":
raise failure
def report_unexpected_exception(self, out, test, example, exc_info):
if isinstance(exc_info[1], Skipped):
if isinstance(exc_info[1], OutcomeException):
raise exc_info[1]
if isinstance(exc_info[1], bdb.BdbQuit):
outcomes.exit("Quitting debugger")
@@ -216,6 +219,16 @@ class DoctestItem(pytest.Item):
self.obj = None
self.fixture_request = None
@classmethod
def from_parent( # type: ignore
cls, parent: "Union[DoctestTextfile, DoctestModule]", *, name, runner, dtest
):
# incompatible signature due to to imposed limits on sublcass
"""
the public named constructor
"""
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
def setup(self):
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
@@ -226,7 +239,7 @@ class DoctestItem(pytest.Item):
globs[name] = value
self.dtest.globs.update(globs)
def runtest(self):
def runtest(self) -> None:
_check_all_skipped(self.dtest)
self._disable_output_capturing_for_darwin()
failures = [] # type: List[doctest.DocTestFailure]
@@ -300,13 +313,16 @@ class DoctestItem(pytest.Item):
else:
inner_excinfo = ExceptionInfo(failure.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
lines += traceback.format_exception(*failure.exc_info)
lines += [
x.strip("\n")
for x in traceback.format_exception(*failure.exc_info)
]
reprlocation_lines.append((reprlocation, lines))
return ReprFailDoctest(reprlocation_lines)
else:
return super().repr_failure(excinfo)
def reportinfo(self) -> Tuple[str, int, str]:
def reportinfo(self) -> Tuple[py.path.local, int, str]:
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
@@ -371,7 +387,9 @@ class DoctestTextfile(pytest.Module):
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
if test.examples:
yield DoctestItem(test.name, self, runner, test)
yield DoctestItem.from_parent(
self, name=test.name, runner=runner, dtest=test
)
def _check_all_skipped(test):
@@ -480,7 +498,9 @@ class DoctestModule(pytest.Module):
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
yield DoctestItem.from_parent(
self, name=test.name, runner=runner, dtest=test
)
def _setup_fixtures(doctest_item):

View File

@@ -1,8 +1,13 @@
import io
import os
import sys
from typing import TextIO
import pytest
from _pytest.store import StoreKey
fault_handler_stderr_key = StoreKey[TextIO]()
def pytest_addoption(parser):
@@ -17,70 +22,92 @@ def pytest_addoption(parser):
def pytest_configure(config):
import faulthandler
# avoid trying to dup sys.stderr if faulthandler is already enabled
if faulthandler.is_enabled():
return
if not faulthandler.is_enabled():
# faulthhandler is not enabled, so install plugin that does the actual work
# of enabling faulthandler before each test executes.
config.pluginmanager.register(FaultHandlerHooks(), "faulthandler-hooks")
else:
from _pytest.warnings import _issue_warning_captured
stderr_fd_copy = os.dup(_get_stderr_fileno())
config.fault_handler_stderr = os.fdopen(stderr_fd_copy, "w")
faulthandler.enable(file=config.fault_handler_stderr)
# Do not handle dumping to stderr if faulthandler is already enabled, so warn
# users that the option is being ignored.
timeout = FaultHandlerHooks.get_timeout_config_value(config)
if timeout > 0:
_issue_warning_captured(
pytest.PytestConfigWarning(
"faulthandler module enabled before pytest configuration step, "
"'faulthandler_timeout' option ignored"
),
config.hook,
stacklevel=2,
)
def _get_stderr_fileno():
try:
return sys.stderr.fileno()
except (AttributeError, io.UnsupportedOperation):
# python-xdist monkeypatches sys.stderr with an object that is not an actual file.
# https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
# This is potentially dangerous, but the best we can do.
return sys.__stderr__.fileno()
class FaultHandlerHooks:
"""Implements hooks that will actually install fault handler before tests execute,
as well as correctly handle pdb and internal errors."""
def pytest_configure(self, config):
import faulthandler
def pytest_unconfigure(config):
import faulthandler
stderr_fd_copy = os.dup(self._get_stderr_fileno())
config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w")
faulthandler.enable(file=config._store[fault_handler_stderr_key])
faulthandler.disable()
# close our dup file installed during pytest_configure
f = getattr(config, "fault_handler_stderr", None)
if f is not None:
def pytest_unconfigure(self, config):
import faulthandler
faulthandler.disable()
# close our dup file installed during pytest_configure
# re-enable the faulthandler, attaching it to the default sys.stderr
# so we can see crashes after pytest has finished, usually during
# garbage collection during interpreter shutdown
config.fault_handler_stderr.close()
del config.fault_handler_stderr
faulthandler.enable(file=_get_stderr_fileno())
config._store[fault_handler_stderr_key].close()
del config._store[fault_handler_stderr_key]
faulthandler.enable(file=self._get_stderr_fileno())
@staticmethod
def _get_stderr_fileno():
try:
return sys.stderr.fileno()
except (AttributeError, io.UnsupportedOperation):
# pytest-xdist monkeypatches sys.stderr with an object that is not an actual file.
# https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
# This is potentially dangerous, but the best we can do.
return sys.__stderr__.fileno()
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
timeout = float(item.config.getini("faulthandler_timeout") or 0.0)
if timeout > 0:
@staticmethod
def get_timeout_config_value(config):
return float(config.getini("faulthandler_timeout") or 0.0)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(self, item):
timeout = self.get_timeout_config_value(item.config)
stderr = item.config._store[fault_handler_stderr_key]
if timeout > 0 and stderr is not None:
import faulthandler
faulthandler.dump_traceback_later(timeout, file=stderr)
try:
yield
finally:
faulthandler.cancel_dump_traceback_later()
else:
yield
@pytest.hookimpl(tryfirst=True)
def pytest_enter_pdb(self):
"""Cancel any traceback dumping due to timeout before entering pdb.
"""
import faulthandler
stderr = item.config.fault_handler_stderr
faulthandler.dump_traceback_later(timeout, file=stderr)
try:
yield
finally:
faulthandler.cancel_dump_traceback_later()
else:
yield
faulthandler.cancel_dump_traceback_later()
@pytest.hookimpl(tryfirst=True)
def pytest_exception_interact(self):
"""Cancel any traceback dumping due to an interactive exception being
raised.
"""
import faulthandler
@pytest.hookimpl(tryfirst=True)
def pytest_enter_pdb():
"""Cancel any traceback dumping due to timeout before entering pdb.
"""
import faulthandler
faulthandler.cancel_dump_traceback_later()
@pytest.hookimpl(tryfirst=True)
def pytest_exception_interact():
"""Cancel any traceback dumping due to an interactive exception being
raised.
"""
import faulthandler
faulthandler.cancel_dump_traceback_later()
faulthandler.cancel_dump_traceback_later()

View File

@@ -16,11 +16,12 @@ import py
import _pytest
from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest._code.source import getfslineno
from _pytest._io import TerminalWriter
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
from _pytest.compat import get_real_func
from _pytest.compat import get_real_method
from _pytest.compat import getfslineno
from _pytest.compat import getfuncargnames
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
@@ -30,6 +31,7 @@ from _pytest.compat import safe_getattr
from _pytest.compat import TYPE_CHECKING
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
from _pytest.deprecated import FUNCARGNAMES
from _pytest.mark import ParameterSet
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
@@ -37,6 +39,7 @@ if TYPE_CHECKING:
from typing import Type
from _pytest import nodes
from _pytest.main import Session
@attr.s(frozen=True)
@@ -45,7 +48,7 @@ class PseudoFixtureDef:
scope = attr.ib()
def pytest_sessionstart(session):
def pytest_sessionstart(session: "Session"):
import _pytest.python
import _pytest.nodes
@@ -351,7 +354,7 @@ class FixtureRequest:
self.fixturename = None
#: Scope string, one of "function", "class", "module", "session"
self.scope = "function"
self._fixture_defs = {} # argname -> FixtureDef
self._fixture_defs = {} # type: Dict[str, FixtureDef]
fixtureinfo = pyfuncitem._fixtureinfo
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
self._arg2index = {}
@@ -424,9 +427,10 @@ class FixtureRequest:
return self._pyfuncitem.getparent(_pytest.python.Module).obj
@scopeproperty()
def fspath(self):
def fspath(self) -> py.path.local:
""" the file system path of the test module which collected this test. """
return self._pyfuncitem.fspath
# TODO: Remove ignore once _pyfuncitem is properly typed.
return self._pyfuncitem.fspath # type: ignore
@property
def keywords(self):
@@ -511,13 +515,11 @@ class FixtureRequest:
values.append(fixturedef)
current = current._parent_request
def _compute_fixture_value(self, fixturedef):
def _compute_fixture_value(self, fixturedef: "FixtureDef") -> None:
"""
Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will
force the FixtureDef object to throw away any previous results and compute a new fixture value, which
will be stored into the FixtureDef object itself.
:param FixtureDef fixturedef:
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
@@ -545,11 +547,13 @@ class FixtureRequest:
if has_params:
frame = inspect.stack()[3]
frameinfo = inspect.getframeinfo(frame[0])
source_path = frameinfo.filename
source_path = py.path.local(frameinfo.filename)
source_lineno = frameinfo.lineno
source_path = py.path.local(source_path)
if source_path.relto(funcitem.config.rootdir):
source_path = source_path.relto(funcitem.config.rootdir)
rel_source_path = source_path.relto(funcitem.config.rootdir)
if rel_source_path:
source_path_str = rel_source_path
else:
source_path_str = str(source_path)
msg = (
"The requested fixture has no parameter defined for test:\n"
" {}\n\n"
@@ -558,7 +562,7 @@ class FixtureRequest:
funcitem.nodeid,
fixturedef.argname,
getlocation(fixturedef.func, funcitem.config.rootdir),
source_path,
source_path_str,
source_lineno,
)
)
@@ -751,7 +755,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
self.firstlineno = firstlineno
self.argname = argname
def toterminal(self, tw) -> None:
def toterminal(self, tw: TerminalWriter) -> None:
# tw.line("FixtureLookupError: %s" %(self.argname), red=True)
for tbline in self.tblines:
tw.line(tbline.rstrip())
@@ -852,25 +856,26 @@ class FixtureDef:
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
self.unittest = unittest
self.ids = ids
self.cached_result = None
self._finalizers = []
def addfinalizer(self, finalizer):
self._finalizers.append(finalizer)
def finish(self, request):
exceptions = []
exc = None
try:
while self._finalizers:
try:
func = self._finalizers.pop()
func()
except: # noqa
exceptions.append(sys.exc_info())
if exceptions:
_, val, tb = exceptions[0]
# Ensure to not keep frame references through traceback.
del exceptions
raise val.with_traceback(tb)
except BaseException as e:
# XXX Only first exception will be seen by user,
# ideally all should be reported.
if exc is None:
exc = e
if exc:
raise exc
finally:
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
@@ -878,21 +883,23 @@ class FixtureDef:
# the cached fixture value and remove
# all finalizers because they may be bound methods which will
# keep instances alive
if hasattr(self, "cached_result"):
del self.cached_result
self.cached_result = None
self._finalizers = []
def execute(self, request):
for argname in self._dependee_fixture_argnames(request):
# get required arguments and register our own finish()
# with their finalization
for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname)
if argname != "request":
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
my_cache_key = self.cache_key(request)
cached_result = getattr(self, "cached_result", None)
if cached_result is not None:
result, cache_key, err = cached_result
if my_cache_key == cache_key:
if self.cached_result is not None:
result, cache_key, err = self.cached_result
# note: comparison with `==` can fail (or be expensive) for e.g.
# numpy arrays (#6497)
if my_cache_key is cache_key:
if err is not None:
_, val, tb = err
raise val.with_traceback(tb)
@@ -901,66 +908,11 @@ class FixtureDef:
# we have a previous but differently parametrized fixture instance
# so we need to tear it down before creating a new one
self.finish(request)
assert not hasattr(self, "cached_result")
assert self.cached_result is None
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)
def _dependee_fixture_argnames(self, request):
"""A list of argnames for fixtures that this fixture depends on.
Given a request, this looks at the currently known list of fixture argnames, and
attempts to determine what slice of the list contains fixtures that it can know
should execute before it. This information is necessary so that this fixture can
know what fixtures to register its finalizer with to make sure that if they
would be torn down, they would tear down this fixture before themselves. It's
crucial for fixtures to be torn down in the inverse order that they were set up
in so that they don't try to clean up something that another fixture is still
depending on.
When autouse fixtures are involved, it can be tricky to figure out when fixtures
should be torn down. To solve this, this method leverages the ``fixturenames``
list provided by the ``request`` object, as this list is at least somewhat
sorted (in terms of the order fixtures are set up in) by the time this method is
reached. It's sorted enough that the starting point of fixtures that depend on
this one can be found using the ``self._parent_request`` stack.
If a request in the ``self._parent_request`` stack has a ``:class:FixtureDef``
associated with it, then that fixture is dependent on this one, so any fixture
names that appear in the list of fixture argnames that come after it can also be
ruled out. The argnames of all fixtures associated with a request in the
``self._parent_request`` stack are found, and the lowest index argname is
considered the earliest point in the list of fixture argnames where everything
from that point onward can be considered to execute after this fixture.
Everything before this point can be considered fixtures that this fixture
depends on, and so this fixture should register its finalizer with all of them
to ensure that if any of them are to be torn down, they will tear this fixture
down first.
This is the first part of the list of fixture argnames that is returned. The last
part of the list is everything in ``self.argnames`` as those are explicit
dependees of this fixture, so this fixture should definitely register its
finalizer with them.
"""
all_fix_names = request.fixturenames
try:
current_fix_index = all_fix_names.index(self.argname)
except ValueError:
current_fix_index = len(request.fixturenames)
parent_fixture_indexes = set()
parent_request = request._parent_request
while hasattr(parent_request, "_parent_request"):
if hasattr(parent_request, "_fixturedef"):
parent_fix_name = parent_request._fixturedef.argname
if parent_fix_name in all_fix_names:
parent_fixture_indexes.add(all_fix_names.index(parent_fix_name))
parent_request = parent_request._parent_request
stack_slice_index = min([current_fix_index, *parent_fixture_indexes])
active_fixture_argnames = all_fix_names[:stack_slice_index]
return {*active_fixture_argnames, *self.argnames}
def cache_key(self, request):
return request.param_index if not hasattr(request, "param") else request.param
@@ -1001,6 +953,7 @@ def pytest_fixture_setup(fixturedef, request):
kwargs = {}
for argname in fixturedef.argnames:
fixdef = request._get_active_fixturedef(argname)
assert fixdef.cached_result is not None
result, arg_cache_key, exc = fixdef.cached_result
request._check_scope(argname, request.scope, fixdef.scope)
kwargs[argname] = result
@@ -1189,7 +1142,7 @@ def fixture(
params=params,
autouse=autouse,
ids=ids,
name=name
name=name,
)
scope = arguments.get("scope")
params = arguments.get("params")
@@ -1227,7 +1180,7 @@ def yield_fixture(
params=params,
autouse=autouse,
ids=ids,
name=name
name=name,
)
@@ -1297,7 +1250,6 @@ class FixtureManager:
self.config = session.config
self._arg2fixturedefs = {}
self._holderobjseen = set()
self._arg2finish = {}
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
session.config.pluginmanager.register(self, "funcmanage")
@@ -1310,8 +1262,6 @@ class FixtureManager:
This things are done later as well when dealing with parametrization
so this could be improved
"""
from _pytest.mark import ParameterSet
parametrize_argnames = []
for marker in node.iter_markers(name="parametrize"):
if not marker.kwargs.get("indirect", False):

View File

@@ -40,8 +40,9 @@ def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption(
"--version",
"-V",
action="store_true",
help="display pytest lib version and import information.",
help="display pytest version and information about plugins.",
)
group._addoption(
"-h",
@@ -66,7 +67,7 @@ def pytest_addoption(parser):
action="store_true",
default=False,
help="trace considerations of conftest.py files.",
),
)
group.addoption(
"--debug",
action="store_true",

View File

@@ -1,6 +1,15 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from typing import Any
from typing import Optional
from pluggy import HookspecMarker
from .deprecated import COLLECT_DIRECTORY_HOOK
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from _pytest.main import Session
hookspec = HookspecMarker("pytest")
@@ -158,7 +167,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
def pytest_collection(session: "Session") -> Optional[Any]:
"""Perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult`.
@@ -197,7 +206,7 @@ def pytest_ignore_collect(path, config):
"""
@hookspec(firstresult=True)
@hookspec(firstresult=True, warn_on_impl=COLLECT_DIRECTORY_HOOK)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files.
@@ -307,10 +316,6 @@ def pytest_runtestloop(session):
"""
def pytest_itemstart(item, node):
"""(**Deprecated**) use pytest_runtest_logstart. """
@hookspec(firstresult=True)
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
@@ -419,9 +424,9 @@ def pytest_fixture_setup(fixturedef, request):
def pytest_fixture_post_finalizer(fixturedef, request):
""" called after fixture teardown, but before the cache is cleared so
the fixture result cache ``fixturedef.cached_result`` can
still be accessed."""
"""Called after fixture teardown, but before the cache is cleared, so
the fixture result ``fixturedef.cached_result`` is still available (not
``None``)."""
# -------------------------------------------------------------------------
@@ -562,7 +567,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
@hookspec(historic=True)
def pytest_warning_captured(warning_message, when, item):
def pytest_warning_captured(warning_message, when, item, location):
"""
Process a warning captured by the internal pytest warnings plugin.
@@ -582,6 +587,10 @@ def pytest_warning_captured(warning_message, when, item):
in a future release.
The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
:param tuple location:
Holds information about the execution context of the captured warning (filename, linenumber, function).
``function`` evaluates to <module> when the execution context is at the module level.
"""

View File

@@ -22,9 +22,13 @@ import pytest
from _pytest import deprecated
from _pytest import nodes
from _pytest.config import filename_arg
from _pytest.store import StoreKey
from _pytest.warnings import _issue_warning_captured
xml_key = StoreKey["LogXML"]()
class Junit(py.xml.Namespace):
pass
@@ -167,51 +171,28 @@ class _NodeReporter:
content_out = report.capstdout
content_log = report.caplog
content_err = report.capstderr
if self.xml.logging == "no":
return
content_all = ""
if self.xml.logging in ["log", "all"]:
content_all = self._prepare_content(content_log, " Captured Log ")
if self.xml.logging in ["system-out", "out-err", "all"]:
content_all += self._prepare_content(content_out, " Captured Out ")
self._write_content(report, content_all, "system-out")
content_all = ""
if self.xml.logging in ["system-err", "out-err", "all"]:
content_all += self._prepare_content(content_err, " Captured Err ")
self._write_content(report, content_all, "system-err")
content_all = ""
if content_all:
self._write_content(report, content_all, "system-out")
if content_log or content_out:
if content_log and self.xml.logging == "system-out":
if content_out:
# syncing stdout and the log-output is not done yet. It's
# probably not worth the effort. Therefore, first the captured
# stdout is shown and then the captured logs.
content = "\n".join(
[
" Captured Stdout ".center(80, "-"),
content_out,
"",
" Captured Log ".center(80, "-"),
content_log,
]
)
else:
content = content_log
else:
content = content_out
def _prepare_content(self, content, header):
return "\n".join([header.center(80, "-"), content, ""])
if content:
tag = getattr(Junit, "system-out")
self.append(tag(bin_xml_escape(content)))
if content_log or content_err:
if content_log and self.xml.logging == "system-err":
if content_err:
content = "\n".join(
[
" Captured Stderr ".center(80, "-"),
content_err,
"",
" Captured Log ".center(80, "-"),
content_log,
]
)
else:
content = content_log
else:
content = content_err
if content:
tag = getattr(Junit, "system-err")
self.append(tag(bin_xml_escape(content)))
def _write_content(self, report, content, jheader):
tag = getattr(Junit, jheader)
self.append(tag(bin_xml_escape(content)))
def append_pass(self, report):
self.add_stats("passed")
@@ -283,7 +264,7 @@ def _warn_incompatibility_with_xunit2(request, fixture_name):
"""Emits a PytestWarning about the given fixture being incompatible with newer xunit revisions"""
from _pytest.warning_types import PytestWarning
xml = getattr(request.config, "_xml", None)
xml = request.config._store.get(xml_key, None)
if xml is not None and xml.family not in ("xunit1", "legacy"):
request.node.warn(
PytestWarning(
@@ -335,7 +316,7 @@ def record_xml_attribute(request):
attr_func = add_attr_noop
xml = getattr(request.config, "_xml", None)
xml = request.config._store.get(xml_key, None)
if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)
attr_func = node_reporter.add_attribute
@@ -376,7 +357,7 @@ def record_testsuite_property(request):
__tracebackhide__ = True
_check_record_param_type("name", name)
xml = getattr(request.config, "_xml", None)
xml = request.config._store.get(xml_key, None)
if xml is not None:
record_func = xml.add_global_property # noqa
return record_func
@@ -408,9 +389,9 @@ def pytest_addoption(parser):
parser.addini(
"junit_logging",
"Write captured log messages to JUnit report: "
"one of no|system-out|system-err",
"one of no|log|system-out|system-err|out-err|all",
default="no",
) # choices=['no', 'stdout', 'stderr'])
)
parser.addini(
"junit_log_passing_tests",
"Capture log information for passing tests to JUnit report: ",
@@ -435,7 +416,7 @@ def pytest_configure(config):
if not junit_family:
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
junit_family = "xunit1"
config._xml = LogXML(
config._store[xml_key] = LogXML(
xmlpath,
config.option.junitprefix,
config.getini("junit_suite_name"),
@@ -444,13 +425,13 @@ def pytest_configure(config):
junit_family,
config.getini("junit_log_passing_tests"),
)
config.pluginmanager.register(config._xml)
config.pluginmanager.register(config._store[xml_key])
def pytest_unconfigure(config):
xml = getattr(config, "_xml", None)
xml = config._store.get(xml_key, None)
if xml:
del config._xml
del config._store[xml_key]
config.pluginmanager.unregister(xml)

View File

@@ -5,12 +5,16 @@ from contextlib import contextmanager
from io import StringIO
from typing import AbstractSet
from typing import Dict
from typing import Generator
from typing import List
from typing import Mapping
from typing import Optional
import pytest
from _pytest import nodes
from _pytest.compat import nullcontext
from _pytest.config import _strtobool
from _pytest.config import Config
from _pytest.config import create_terminal_writer
from _pytest.pathlib import Path
@@ -192,7 +196,12 @@ def pytest_addoption(parser):
"--log-level",
dest="log_level",
default=None,
help="logging level used by the logging module",
metavar="LEVEL",
help=(
"level of messages to catch/display.\n"
"Not set by default, so it depends on the root/parent log handler's"
' effective level, where it is "WARNING" by default.'
),
)
add_option_ini(
"--log-format",
@@ -325,13 +334,13 @@ class LogCaptureFixture:
logger.setLevel(level)
@property
def handler(self):
def handler(self) -> LogCaptureHandler:
"""
:rtype: LogCaptureHandler
"""
return self._item.catch_log_handler
return self._item.catch_log_handler # type: ignore[no-any-return] # noqa: F723
def get_records(self, when):
def get_records(self, when: str) -> List[logging.LogRecord]:
"""
Get the logging records for one of the possible test phases.
@@ -345,7 +354,7 @@ class LogCaptureFixture:
"""
handler = self._item.catch_log_handlers.get(when)
if handler:
return handler.records
return handler.records # type: ignore[no-any-return] # noqa: F723
else:
return []
@@ -441,9 +450,7 @@ def caplog(request):
result._finalize()
def get_actual_log_level(config, *setting_names):
"""Return the actual logging level."""
def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]:
for setting_name in setting_names:
log_level = config.getoption(setting_name)
if log_level is None:
@@ -451,7 +458,7 @@ def get_actual_log_level(config, *setting_names):
if log_level:
break
else:
return
return None
if isinstance(log_level, str):
log_level = log_level.upper()
@@ -476,7 +483,7 @@ class LoggingPlugin:
"""Attaches to the logging module and captures log messages for each test.
"""
def __init__(self, config):
def __init__(self, config: Config) -> None:
"""Creates a new plugin to capture log messages.
The formatter can be safely shared across all handlers so
@@ -485,14 +492,20 @@ class LoggingPlugin:
self._config = config
self.print_logs = get_option_ini(config, "log_print")
if not self.print_logs:
from _pytest.warnings import _issue_warning_captured
from _pytest.deprecated import NO_PRINT_LOGS
_issue_warning_captured(NO_PRINT_LOGS, self._config.hook, stacklevel=2)
self.formatter = self._create_formatter(
get_option_ini(config, "log_format"),
get_option_ini(config, "log_date_format"),
get_option_ini(config, "log_auto_indent"),
)
self.log_level = get_actual_log_level(config, "log_level")
self.log_level = get_log_level_for_setting(config, "log_level")
self.log_file_level = get_actual_log_level(config, "log_file_level")
self.log_file_level = get_log_level_for_setting(config, "log_file_level")
self.log_file_format = get_option_ini(config, "log_file_format", "log_format")
self.log_file_date_format = get_option_ini(
config, "log_file_date_format", "log_date_format"
@@ -505,7 +518,7 @@ class LoggingPlugin:
if log_file:
self.log_file_handler = logging.FileHandler(
log_file, mode="w", encoding="UTF-8"
)
) # type: Optional[logging.FileHandler]
self.log_file_handler.setFormatter(self.log_file_formatter)
else:
self.log_file_handler = None
@@ -555,7 +568,7 @@ class LoggingPlugin:
get_option_ini(config, "log_auto_indent"),
)
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
log_cli_level = get_log_level_for_setting(config, "log_cli_level", "log_level")
self.log_cli_handler = log_cli_handler
self.live_logs_context = lambda: catching_logs(
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
@@ -591,7 +604,7 @@ class LoggingPlugin:
) is not None or self._config.getini("log_cli")
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(self):
def pytest_collection(self) -> Generator[None, None, None]:
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("collection")
@@ -612,7 +625,9 @@ class LoggingPlugin:
yield
@contextmanager
def _runtest_for_main(self, item, when):
def _runtest_for_main(
self, item: nodes.Item, when: str
) -> Generator[None, None, None]:
"""Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
@@ -625,15 +640,15 @@ class LoggingPlugin:
return
if not hasattr(item, "catch_log_handlers"):
item.catch_log_handlers = {}
item.catch_log_handlers[when] = log_handler
item.catch_log_handler = log_handler
item.catch_log_handlers = {} # type: ignore[attr-defined] # noqa: F821
item.catch_log_handlers[when] = log_handler # type: ignore[attr-defined] # noqa: F821
item.catch_log_handler = log_handler # type: ignore[attr-defined] # noqa: F821
try:
yield # run test
finally:
if when == "teardown":
del item.catch_log_handler
del item.catch_log_handlers
del item.catch_log_handler # type: ignore[attr-defined] # noqa: F821
del item.catch_log_handlers # type: ignore[attr-defined] # noqa: F821
if self.print_logs:
# Add a captured log section to the report.

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