Compare commits

...

287 Commits
4.0.2 ... 4.2.0

Author SHA1 Message Date
Bruno Oliveira
fa979a4290 Preparing release version 4.2.0 2019-01-30 14:25:38 -02:00
Bruno Oliveira
e2a15c79e7 Merge pull request #4692 from nicoddemus/merge-master-into-features
Merge master into features
2019-01-30 14:16:09 -02:00
Bruno Oliveira
02962fabda Merge remote-tracking branch 'upstream/features' into merge-master-into-features 2019-01-30 09:37:46 -02:00
Bruno Oliveira
b77d168d58 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2019-01-30 09:37:29 -02:00
Bruno Oliveira
c0e6543b5a Fix pytest_report_teststatus call to pass new config object (#4691) 2019-01-30 09:36:54 -02:00
Bruno Oliveira
b96e162131 Merge pull request #4693 from asottile/flake8_3_7_0
fixes for flake8 3.7
2019-01-30 09:18:09 -02:00
Bruno Oliveira
1dc16ad77b Merge pull request #4690 from nicoddemus/deprecated-python-summary
Show deprecation message when running under Python 2.7 and 3.4
2019-01-30 09:07:59 -02:00
Anthony Sottile
acece23697 pre-commit autoupdate 2019-01-29 21:13:32 -08:00
Anthony Sottile
e5f823a3a7 fixes for flake8 3.7 2019-01-29 21:11:15 -08:00
Bruno Oliveira
b41dc03930 Merge pull request #4691 from nicoddemus/config-terminal-summary-hook
Add config to pytest_terminal_summary hook
2019-01-29 21:11:37 -02:00
Bruno Oliveira
ade5f2c8c5 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2019-01-29 19:36:56 -02:00
Bruno Oliveira
3e0e819158 Merge pull request #4280 from blueyed/trace-quit
pdb: improve quitting from debugger
2019-01-29 19:14:20 -02:00
Bruno Oliveira
eb92e57509 Show deprecation message when running under Python 2.7 and 3.4
Fix #4627
2019-01-29 19:02:41 -02:00
Bruno Oliveira
7ad499ad76 Merge pull request #4681 from RonnyPfannschmidt/fix-4680-tmppath-is-tmpdir
Fix 4680 - `tmp_path` and `tmpdir` now share the same temporary directory
2019-01-29 19:00:56 -02:00
Ronny Pfannschmidt
2d7582bd92 flip around basetemp folder reset to see if it helps on windows 2019-01-29 18:58:00 +01:00
Bruno Oliveira
7e8e593a45 Add CHANGELOG entry for #4691 2019-01-29 15:52:45 -02:00
Bruno Oliveira
6c3b86369f Add config to pytest_terminal_summary hook
The docs stated that this hook got the 'config' parameter in 3.5, but the docs
wre probably changed by mistake.
2019-01-29 15:41:13 -02:00
Bruno Oliveira
6aba60ab08 Merge pull request #4688 from nicoddemus/add-config-to-hooks
Add config to pytest_report_teststatus
2019-01-28 21:45:58 -02:00
Bruno Oliveira
d720312df0 Add CHANGELOG entry for #4688 2019-01-28 19:03:19 -02:00
Bruno Oliveira
5b09eb1d74 Add config parameter to pytest_report_teststatus hook 2019-01-28 19:00:50 -02:00
Bruno Oliveira
5119abe498 Merge pull request #4683 from nicoddemus/skip-unicode
Handle unittest.SkipTest exception with non-ascii characters
2019-01-28 15:50:53 -02:00
Bruno Oliveira
1c5009c3fb Handle unittest.SkipTest exception with non-ascii characters
Fix #4669
2019-01-28 12:50:04 -02:00
Ronny Pfannschmidt
5567c772cd quick&dirty fix fixture tests that rely on tmppath fixture structure 2019-01-27 14:19:23 +01:00
Ronny Pfannschmidt
c75bd08807 ensure tmp_path is always a realpath 2019-01-27 14:08:43 +01:00
Ronny Pfannschmidt
f7d7555521 fix #4680 - ensure tmpdir and tmp_path are the same 2019-01-27 13:05:34 +01:00
Anthony Sottile
16f8cdac95 Merge pull request #4678 from nicoddemus/raises-docs
Improve pytest.raises 'message' deprecation docs
2019-01-26 19:01:21 -08:00
Bruno Oliveira
9905a73ae0 Merge pull request #4511 from jhunkeler/junit-strict
Toggle JUnit behavior with INI option
2019-01-24 20:54:32 -02:00
Bruno Oliveira
51dd738b1a Merge pull request #4673 from kown7/count-tests
Count tests
2019-01-24 20:46:29 -02:00
Bruno Oliveira
067f2c6148 Improve pytest.raises 'message' deprecation docs
Based on recent discussions in #3974
2019-01-24 20:41:18 -02:00
Kristoffer Nordstroem
e2cd2cd409 vanity commit 2019-01-24 23:18:21 +01:00
Bruno Oliveira
37aab5dd6b Add CHANGELOG entry for #4660 2019-01-24 20:07:21 -02:00
Bruno Oliveira
7ddfc04793 Merge pull request #4665 from nicoddemus/group-warnings-by-message
Group warnings by message instead of by test id
2019-01-24 20:00:27 -02:00
Bruno Oliveira
7b10474fed Merge pull request #4668 from wimglenn/issues/4667
short report summary to use report teststatus data
2019-01-24 19:59:52 -02:00
wim glenn
8cf097635e Fixed one weird test that creates a class instead of using mocks.. ¯\_(ツ)_/¯ 2019-01-24 12:59:36 -06:00
wim glenn
2d18546870 resolving report.when attribute should be reliable now 2019-01-24 11:12:59 -06:00
wim glenn
0f546c4670 pytest_terminal_summary uses result from pytest_report_teststatus hook, rather than hardcoded strings
Less hacky way to make XPASS yellow markup. Make sure collect reports still have a "when" attribute.

xfail changed to XFAIL in the test report, for consistency with other outcomes which are all CAPS
2019-01-24 10:17:29 -06:00
Kristoffer Nordstroem
6d38868950 fix tests by adding additional output to expected responses 2019-01-24 00:08:43 +01:00
Kristoffer Nordstroem
8723eb16ea only print selected if any have been selected 2019-01-24 00:00:29 +01:00
Bruno Oliveira
daf39112e7 Merge pull request #4091 from nicoddemus/setup-methods-as-fixtures-3094
Use fixtures to invoke xunit-style fixtures
2019-01-23 19:23:31 -02:00
Bruno Oliveira
9543d1901f Group warnings by message instead of by test id 2019-01-22 19:42:22 -02:00
Anthony Sottile
ba452dbcf0 Merge pull request #4664 from thisch/stdlibtest
Remove stdlib test
2019-01-22 11:28:51 -08:00
Thomas Hisch
a2954578aa Remove stdlib test 2019-01-22 20:25:51 +01:00
Anthony Sottile
1bcb2f91cc Merge pull request #4661 from asottile/more_py
remove and ban py.io.BytesIO, py.process, py.path.local.sysfind
2019-01-21 22:58:43 -08:00
Anthony Sottile
92a2c1a9c4 remove and ban py.io.BytesIO, py.process, py.path.local.sysfind 2019-01-21 19:51:16 -08:00
Kristoffer Nordstroem
9f86e83478 count selected tests 2019-01-21 23:56:39 +01:00
Anthony Sottile
f01f434311 Merge pull request #4657 from asottile/py_io_saferepr
copy saferepr from pylib
2019-01-21 13:25:02 -08:00
Anthony Sottile
0c6ca0da62 Fix usages of py.io.saferepr 2019-01-20 16:36:23 -08:00
Anthony Sottile
095ce2ca7f Fix linting errors and py references in saferepr.py 2019-01-20 16:36:14 -08:00
Anthony Sottile
dbb6c18c44 copy saferepr from pylib verbatim
Copied from b9da2ed6178cd37d4ed6b41f9fa8234dce96973f
2019-01-20 16:30:31 -08:00
Anthony Sottile
653c685667 Merge pull request #4658 from asottile/py_deprecated_builtin
Remove and ban use of py.builtin
2019-01-20 14:29:00 -08:00
Anthony Sottile
ec5e279f93 Remove and ban use of py.builtin 2019-01-20 11:59:48 -08:00
Daniel Hahler
e69b1255d7 Improve quitting from pdb
Regarding tests: it merges ``test_pdb_interaction``,
``test_pdb_print_captured_stdout``, and
``test_pdb_print_captured_stderr`` into
``test_pdb_print_captured_stdout_and_stderr`` (clarity and performance,
especially since pexpect tests are slow).
2019-01-20 00:34:16 +01:00
Bruno Oliveira
57bf9d6740 Merge pull request #4654 from AuHau/fix/#4653-Fix_tmp_path_symlink_resolution
Fixes #4653 - tmp_path provides real path
2019-01-19 09:41:47 -02:00
Bruno Oliveira
677a7d06da Merge pull request #4655 from RonnyPfannschmidt/fix-4649
fix #4649 - also transfer markers to keywordmapping
2019-01-19 09:40:03 -02:00
Ronny Pfannschmidt
f28b834426 fix #4649 - also transfer markers to keywordmapping
as it turns out it is distinct from nodekeywords and behaves completely different
2019-01-18 23:18:51 +01:00
Adam Uhlir
04bd147d46 Fixes #4653 - tmp_path provides real path 2019-01-18 12:54:00 -08:00
Bruno Oliveira
6154a5ac02 Merge pull request #4646 from nicoddemus/2.7-deprecation-docs
Add docs page about plans for dropping py27 and py34
2019-01-16 12:00:09 -02:00
Bruno Oliveira
1a04e8903a Merge pull request #4537 from chdsbd/master
Bugfix: monkeypatch.delattr handles class descriptors
2019-01-16 08:09:59 -02:00
Joseph Hunkeler
85c5fa9f64 Update changelog 2019-01-14 22:58:41 -05:00
Joseph Hunkeler
8967976443 Ensure xml object is viable before testing family type 2019-01-14 22:58:41 -05:00
Joseph Hunkeler
bcacc40775 Update comment text 2019-01-14 22:58:41 -05:00
Joseph Hunkeler
4ecf29380a Adds xunit2 version of test_record_attribute 2019-01-14 22:58:41 -05:00
Joseph Hunkeler
aaa7d36bc9 Change family behavior:
* "legacy" is no longer a copy of "xunit1"
* Attempts to use "legacy" will redirect to "xunit1"
* record_xml_attribute is not compatible outside of legacy family
* Replace call to method/override raw() with to_xml()
2019-01-14 22:58:40 -05:00
Joseph Hunkeler
8937e39afd Raise TypeError instead of NotImplementedError if not list type 2019-01-14 22:58:40 -05:00
Joseph Hunkeler
343430c537 Replace family "old" with "legacy" 2019-01-14 22:58:40 -05:00
Joseph Hunkeler
335cc5d651 Handle backwards-compatiblity 2019-01-14 22:58:40 -05:00
Joseph Hunkeler
2e551c32b6 Add junit_family config option 2019-01-14 22:58:40 -05:00
Joseph Hunkeler
af2ee1e80a Emit JUnit compatible XML
* Remove non-standard testcase elements: 'file' and 'line'
* Replace testcase element 'skips' with 'skipped'
* Time resolution uses the standard format: 0.000
* Tests use corrected XML output with proper attributes
2019-01-14 22:58:40 -05:00
Bruno Oliveira
e2a9aaf24b Add docs page about plans for dropping py27 and py34
Fix #4635
2019-01-14 22:10:15 -02:00
Anthony Sottile
4947eb85c0 Merge pull request #4645 from blueyed/merge-master-into-features
Merge master into features
2019-01-14 14:41:27 -08:00
Daniel Hahler
1a358df998 Merge master into features 2019-01-14 17:15:39 +01:00
Anthony Sottile
5903f4596a Merge pull request #4643 from nicoddemus/asscalar-deprecated
Use a.item() instead of deprecated np.asscalar(a)
2019-01-14 07:36:13 -08:00
Anthony Sottile
5bb0be1e24 Merge pull request #4615 from asottile/some_dead_code
Remove some dead code
2019-01-14 07:35:21 -08:00
Bruno Oliveira
6504746652 Add CHANGELOG entry for #4643 2019-01-14 09:03:23 -02:00
Bruno Oliveira
42bb0b3904 Use a.item() instead of deprecated np.asscalar(a)
np.asscalar() has been deprecated in numpy 1.16:

https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations
2019-01-14 08:59:09 -02:00
Bruno Oliveira
1bb463a980 Merge pull request #4642 from nicoddemus/rtd-bot-config
Add rtd-bot configuration file
2019-01-14 05:57:50 -02:00
Anthony Sottile
16546b7342 Remove some dead code
- I wrote a thing: https://github.com/asottile/dead
- wanted to try it out, there's lots of false positives and I didn't look
  through all the things it pointed out but here's some
2019-01-13 20:41:30 -08:00
Bruno Oliveira
f2174c16cc Add rtd-bot configuration file [skip appveyor] [skip travis]
Part of the configuration necessary for https://github.com/apps/rtd-helper
2019-01-13 20:38:19 -02:00
Bruno Oliveira
e48f68953d Merge pull request #4638 from nicoddemus/release-4.1.1
Preparing release version 4.1.1
2019-01-12 12:51:32 -02:00
Bruno Oliveira
25081d8e30 Merge pull request #4570 from nicoddemus/sphinx-removed-in
Use sphinx removed in extension in the documentation
2019-01-12 08:34:29 -02:00
Bruno Oliveira
34eeda1c09 Preparing release version 4.1.1 2019-01-12 00:55:12 +00:00
Bruno Oliveira
3efb26ae7f Merge pull request #4632 from AnjoMan/dont-rewrite-objects-with-failing-getattr
Assertion rewrite breaks for objects that reimplement `__getattr__`
2019-01-11 14:07:22 -02:00
Anton Lodder
77da4f118c Add changelog 4631 and AUTHOR credits for Anton Lodder 2019-01-10 20:48:51 -05:00
Anton Lodder
3241fc3103 Don't fail if hasattr fails when rewriting assertions
When rewriting assertions, pytest makes a call to
`__name__` on each object in a comparision. If one of
the objects has reimplemented `__getattr__`, they could
fail trying to fetch `__name__` with an error other than
`AttributeError`, which is what `hasattr` catches.

In this case, the stack trace for the failed `__getattr__`
call will show up in the pytest output, even though
it isn't related to the test failing.

This change fixes that by catching exceptions
that `hasattr` throws.
2019-01-10 20:45:15 -05:00
Anton Lodder
acb3e8e8a7 Test rewriting assertion when __name__ fails
Pytest rewrites assertions so that the items on each
side of a comoparison will have easier-to-read names
in case of an assertion error.

Before doing this, it checks to make sure the object
doesn't have a __name__ attribute; however, it uses
`hasattr` so if the objects __getattr__ is broken then
the test failure message will be the stack trace
for this failure instead of a rewritten assertion.
2019-01-10 20:45:15 -05:00
Bruno Oliveira
5f16ff3acc Merge pull request #4212 from RonnyPfannschmidt/doctest-testmod-has-call
Doctest: hack in handling mock style objects
2019-01-10 19:13:45 -02:00
Bruno Oliveira
71a745270a Merge pull request #4607 from oscarbenjamin/long_output
Show full repr with assert a==b and -vv
2019-01-10 12:30:46 -02:00
Bruno Oliveira
5dcb370f78 Merge pull request #4624 from nicoddemus/merge-master-into-features
Merge master into features
2019-01-10 12:11:20 -02:00
Bruno Oliveira
0f918b1a9d xunit-style functions and methods are invoked by autouse fixtures
Fix #3094, Fix #517
2019-01-10 12:10:04 -02:00
Ronny Pfannschmidt
a6988aa0b9 fix doctest module when a mock object is around
fixes #3456
2019-01-10 12:50:22 +01:00
Bruno Oliveira
2359663437 Merge pull request #4622 from nicoddemus/warningschecker-unittest
Change test_warningschecker_twice to a unittest
2019-01-10 06:04:25 -02:00
Anthony Sottile
bace28517e Merge pull request #4626 from nicoddemus/goodpractices-pythonpath
Mention PYTHONPATH semantics in goodpractices.rst
2019-01-09 20:41:53 -08:00
Bruno Oliveira
9f6d9efc1d Mention PYTHONPATH semantics in goodpractices.rst
Fix #4625
2019-01-09 22:45:09 -02:00
Bruno Oliveira
a0ab5a7cd8 Merge pull request #4609 from yoavcaspi/documentation-configuration-files
Documentation configuration files
2019-01-09 22:32:02 -02:00
Bruno Oliveira
ba8b3be61a Improve CHANGELOG 2019-01-09 20:27:18 -02:00
Yoav Caspi
2467831913 rephrase warning section to explain better the issue. 2019-01-09 23:52:08 +02:00
Bruno Oliveira
e4a21b11d5 Change test_warningschecker_twice to a unittest 2019-01-09 18:58:51 -02:00
Daniel Hahler
f3b6425324 Merge pull request #4620 from Sup3rGeo/bugfix/warningschecker-twice
Bugfix/warningschecker twice
2019-01-09 15:36:29 +01:00
Daniel Hahler
7ee03e0996 Punctuation [ci skip] 2019-01-09 15:35:52 +01:00
Victor Maryama
081accb62c Fixed linting in changelog. 2019-01-09 13:11:22 +01:00
Victor Maryama
fe4835c15e Added changelog entry. 2019-01-09 12:55:15 +01:00
Victor Maryama
df3b5557d1 Reseting entered state in WarningsRecorder (fixes 4617) 2019-01-09 12:51:04 +01:00
Victor Maryama
948a5d5ac6 Added test for Issue 4617 2019-01-09 12:31:26 +01:00
Oscar Benjamin
85055a9efe Show full repr with assert a==b and -vv 2019-01-08 01:25:05 +00:00
Yoav Caspi
149620f858 update changelog 2019-01-06 23:11:27 +02:00
Yoav Caspi
6ee5d431a0 add warning for using non canonical configuration files. 2019-01-06 23:11:24 +02:00
Anthony Sottile
a4c426b1a8 Merge pull request #4604 from asottile/uninstall_hypothesis
Uninstall hypothesis in regen tox env
2019-01-06 10:28:11 -08:00
Anthony Sottile
4f38c610c3 Uninstall hypothesis in regen tox env 2019-01-06 07:53:39 -08:00
Bruno Oliveira
38adb23bd2 Merge pull request #4600 from nicoddemus/release-4.1.0
Release 4.1.0
2019-01-06 13:09:11 -02:00
Bruno Oliveira
e24031fb36 Regendocs again, without hypothesis 2019-01-05 20:42:44 +00:00
Bruno Oliveira
99ef8c6d16 Fix typo in Makefile: PYTEST_ADDOPT -> PYTEST_ADDOPTS 2019-01-05 20:31:01 +00:00
Bruno Oliveira
e8152207c4 Merge pull request #4601 from Stranger6667/fix-typo
Fix typo in the Changelog for `get_closest_marker`
2019-01-05 18:25:50 -02:00
Bruno Oliveira
d7465895d0 Regendoc again 2019-01-05 19:19:40 +00:00
dmitry.dygalo
5d0bcb4419 Fix typo in the Changelog for get_closest_marker 2019-01-05 20:08:09 +01:00
Bruno Oliveira
01151ff566 Add example for -ra usage to the docs 2019-01-05 16:53:12 -02:00
Bruno Oliveira
d0e9b4812f Regendocs 2019-01-05 16:38:59 -02:00
Bruno Oliveira
5a8e674e92 Add dataclasses as a regendoc dependency 2019-01-05 16:38:59 -02:00
Bruno Oliveira
e380d4306b Anonimize attrs links 2019-01-05 16:38:59 -02:00
Bruno Oliveira
9d297c06e8 Preparing release version 4.1.0 2019-01-05 16:38:59 -02:00
Anthony Sottile
e24fdb138d Merge pull request #4598 from nicoddemus/license-year
Update copyright year
2019-01-05 09:09:55 -08:00
Bruno Oliveira
0da5531c7c Merge pull request #4599 from s0undt3ch/feature/skiporfail-reason
Custom reason support for "importorskip"
2019-01-05 13:15:17 -02:00
Bruno Oliveira
0c4898670c Add changelog entry and update docs for importorskip 2019-01-05 12:55:39 -02:00
Pedro Algarvio
be7eb22e88 Add test case for importorskip custom reason 2019-01-04 19:06:23 +00:00
Pedro Algarvio
8b48621687 Allow providing a custom reason for importorskip 2019-01-04 18:05:28 +00:00
Anthony Sottile
56aecfc081 Merge pull request #4587 from nicoddemus/merge-master-into-features
Merge master into features
2019-01-04 09:57:08 -08:00
Anthony Sottile
82a0308bc6 Merge pull request #4595 from auscompgeek/patch-1
Fix PEP 565 typo in warnings.rst
2019-01-04 09:56:23 -08:00
Bruno Oliveira
7f671586b0 Update copyright year 2019-01-04 10:37:07 -02:00
David Vo
9e62f9d64e Fix PEP 565 typo in warnings.rst 2019-01-04 11:50:36 +11:00
Bruno Oliveira
81c2780d2b Move Node.get_marker and markname deprecations next to the other 4.0 ones 2019-01-02 19:37:17 -02:00
Bruno Oliveira
b39b69a730 Use sphinx-removed-in extension in the documentation
Fix #4568
2019-01-02 19:34:46 -02:00
Bruno Oliveira
30c7a7bd69 Merge remote-tracking branch 'upstream/master' into features 2019-01-02 19:16:18 -02:00
Bruno Oliveira
cf5a9aebb2 Merge pull request #4580 from adamchainz/improve_detailed_summary_report_docs
Improve detailed summary report docs
2019-01-01 16:28:27 -02:00
Bruno Oliveira
1a9979a803 Merge pull request #4582 from jeffreyrack/4371-display-test-descriptions
4371: Update --collect-only to display test descriptions when ran in verbose
2018-12-30 13:56:30 -02:00
Jeffrey Rackauckas
1eef53b6fe Update --collect-only to display test descriptions when ran in verbose mode. 2018-12-29 22:46:46 -08:00
Adam Johnson
388aff16c8 Improve detailed summary report docs
The existing examples had 0 tests collected so didn't show the actual summary report. Also I added a section explaining the difference between `p` and `P`.
2018-12-29 11:47:39 +00:00
wim glenn
83ec0228d1 Merge pull request #4577 from pytest-dev/asottile-patch-1
Remove out of date dependencies list in docs
2018-12-27 00:02:42 -06:00
Anthony Sottile
2dc8cc1e48 Remove out of date dependencies list in docs
Was scrolling through the docs and noticed this, I figure it's easier to remove this than try and keep it in sync with setup.py
2018-12-25 20:46:48 -05:00
Anthony Sottile
658fa35642 Merge pull request #4572 from bowlofeggs/rm-comma
Remove an extraneous comma from the docs.
2018-12-22 23:13:45 -05:00
Randy Barlow
b2c4ed9a2b Remove an extraneous comma from the docs.
Signed-off-by: Randy Barlow <randy@electronsweatshop.com>
2018-12-21 17:53:28 -05:00
Bruno Oliveira
b5cd43bc95 Merge pull request #4558 from hyunchel/fix-incorrect-example-in-cache-doc
Update cache doc with correct example
2018-12-21 14:33:41 -02:00
Bruno Oliveira
134ace98d9 Merge pull request #4564 from RonnyPfannschmidt/remove-markinfo
Remove MarkInfo
2018-12-21 14:32:15 -02:00
Ronny Pfannschmidt
134641fcb5 add first set of changelog entries for marker removal 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
8f8d3114dd apply suggested enhancements in deprecations.rst 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
102ffc69e8 add issue references to the todos 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
64a353f2b6 update deprecation docs 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
b258764ffe fix docs 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
3947b859dc fix hookspec parsing 2018-12-21 14:02:38 +01:00
Ronny Pfannschmidt
9f9f6ee48b remove most of markertransfer
keywords are still a big issue
2018-12-21 14:02:38 +01:00
Bruno Oliveira
58fc918d0a Merge pull request #4571 from nicoddemus/remove-parametrize-marks
Remove support for applying marks to values in parametrize
2018-12-21 08:24:47 -02:00
Hyunchel Kim
ece01b0f56 Update cache documentation example to correctly show cache hit and miss 2018-12-21 04:43:26 +00:00
Bruno Oliveira
c378cb4793 Remove support for applying marks to values in parametrize
Fix #3082
2018-12-20 16:16:13 -02:00
Ronny Pfannschmidt
d888d5c933 Merge pull request #4565 from nicoddemus/flaky-test
Use mocking to make test_junit_duration_report deterministic
2018-12-20 08:52:34 +01:00
Ronny Pfannschmidt
b2d3ae257a Merge pull request #4566 from nicoddemus/remove-setup-cfg-non-top-level-pytest-plugins
Remove support for [pytest] in setup.cfg files and pytest_plugins in non-top-level conftests
2018-12-20 08:47:25 +01:00
Bruno Oliveira
a93f41233a Raise an error if pytest_plugins is defined in a non-top-level conftest.py file
Fix #4548
2018-12-19 18:09:47 -02:00
Bruno Oliveira
9138419379 Remove support for '[pytest]' section in setup.cfg file
Fix #3086
2018-12-19 17:43:17 -02:00
Bruno Oliveira
197fd69ddc Use mocking to make test_junit_duration_report deterministic
Fix #4563
2018-12-19 17:13:12 -02:00
Bruno Oliveira
c400d8b2d8 Merge pull request #4561 from nicoddemus/remove-calling-fixtures-directly
Calling fixtures directly is now an error instead of a warning
2018-12-19 11:37:14 -02:00
Bruno Oliveira
0115766df3 Calling fixtures directly is now an error instead of a warning
Fix #4545
2018-12-19 09:26:29 -02:00
Bruno Oliveira
8563364d8b Merge pull request #4542 from nicoddemus/remove-legacy-warn
Remove config.warn, Node.warn and pytest_logwarning
2018-12-18 19:11:36 -02:00
Bruno Oliveira
0a40ae4c6a Merge pull request #4557 from Kanguros/master
Updating markers example to newest pytest version
2018-12-17 12:45:27 -02:00
Ronny Pfannschmidt
e63c7a13ff Merge pull request #4327 from ndevenish/approx
Let approx() work on more generic sequences
2018-12-17 15:21:12 +01:00
Bruno Oliveira
b7e8171cf8 Merge branch 'features' into remove-legacy-warn 2018-12-17 10:37:31 -02:00
Bruno Oliveira
75e93e5168 Merge pull request #4556 from nicoddemus/idfunc-failure
Errors in parametrize id functions now propagate the error instead of issuing a warning
2018-12-17 10:36:15 -02:00
Bruno Oliveira
843d00c219 Fix linting 2018-12-17 10:35:17 -02:00
Bruno Oliveira
c6d27d8224 Merge pull request #4555 from nicoddemus/remove-record-xml-pytest-main-str
Remove record_xml_property and support for strings in pytest.main()
2018-12-17 10:08:51 -02:00
Bruno Oliveira
84390acccc Merge pull request #4553 from nicoddemus/junit-durations
Rename "junit_time" to "junit_duration_report" option
2018-12-17 10:08:20 -02:00
Kanguros
f04d3c8b7d Adding change log entry 2018-12-16 21:27:59 +01:00
Kanguros
60773e0a97 Updating markers example to newest pytest version 2018-12-16 21:13:14 +01:00
Daniel Hahler
3cf44b3037 Merge pull request #4554 from blueyed/merge-master
Merge master into features
2018-12-15 02:44:38 +01:00
Bruno Oliveira
1499778d5e Errors in parametrize id functions now propagate the error instead of issuing a warning
Fix #2169
2018-12-14 21:07:54 -02:00
Bruno Oliveira
a7e401656e Remove support to pass strings to pytest.main()
Fix #3085
2018-12-14 15:45:47 -02:00
Bruno Oliveira
6e1b1abfa7 Remove deprecated record_xml_property
Fix #4547
2018-12-14 15:10:08 -02:00
Daniel Hahler
8e287c5c77 Merge master into features 2018-12-14 16:44:43 +01:00
Bruno Oliveira
231863b133 Rename "junit_time" to "junit_duration_report" option
Just realized while reading the changelog that "junit_time" is not a very good
name, so I decided to open this PR renaming it to "junit_duration_report" which
I believe conveys the meaning of the option better
2018-12-14 12:56:26 -02:00
Bruno Oliveira
ae5d5b8f59 Merge pull request #4552 from nicoddemus/review-changelog-entries
Review changelog entries for features branch
2018-12-14 12:52:55 -02:00
Bruno Oliveira
fd48cd57f9 Remove config.warn, Node.warn; pytest_logwarning issues a warning when implemented
Fix #3078
2018-12-14 12:50:18 -02:00
Bruno Oliveira
98987177a0 Review changelog entries for features branch
I used `towncrier --draft` to see the full changelog, and decided to "nitpick" it
so it reads better as a whole.
2018-12-14 11:17:24 -02:00
Bruno Oliveira
437f44a1f4 Merge pull request #4544 from nicoddemus/remove-pytest-funcarg-prefix
Remove pytest_funcarg__ prefix support for defining fixtures
2018-12-14 08:36:09 -02:00
Bruno Oliveira
b76104e722 Merge pull request #4550 from nicoddemus/release-4.0.2
Release version 4.0.2
2018-12-14 08:28:18 -02:00
Bruno Oliveira
1e80a9cb34 Remove pytest_funcarg__ prefix support for defining fixtures
Fix #4543
2018-12-13 21:41:38 -02:00
Bruno Oliveira
26d202a7bd Merge pull request #4529 from aparamon/jxmlunit-call-time
Add --junittime=call option
2018-12-13 13:51:06 -02:00
Bruno Oliveira
b390c66dc4 Merge pull request #4292 from blueyed/exit-outcome
Derive outcomes.exit.Exit from SystemExit instead of KeyboardInterrupt
2018-12-13 13:43:39 -02:00
Bruno Oliveira
f96e1b6f3e Merge pull request #4532 from nicoddemus/failure-summary
Change -ra to show errors and failures last, instead of first
2018-12-13 10:30:28 -02:00
Bruno Oliveira
15b0a89fb1 Merge pull request #4539 from nicoddemus/deprecate-pytest-raises-message
Deprecate the 'message' parameter of pytest.raises
2018-12-13 06:00:59 -02:00
Bruno Oliveira
5b83417afc Deprecate the 'message' parameter of pytest.raises
Fix #3974
2018-12-12 22:26:30 -02:00
Daniel Hahler
9b3be870dc Improve comment 2018-12-12 23:29:43 +01:00
Daniel Hahler
7a600ea3eb Improve changelog 2018-12-12 23:28:47 +01:00
Bruno Oliveira
110fe2473f Merge pull request #4531 from nicoddemus/remove-custom-collection-types
Remove PyCollector.makeitem
2018-12-12 18:27:50 -02:00
Christopher Dignam
f8d31d2400 Bugfix: monkeypatch.delattr handles class descriptors
Correct monkeypatch.delattr to match the correct behavior of
monkeypatch.setattr when changing class descriptors
2018-12-12 15:20:24 -05:00
Bruno Oliveira
e3d30f8ebf Remove deprecated PyCollector.makeitem
Fix #4535
2018-12-12 14:15:52 -02:00
Andrey Paramonov
5d79baf3f8 Fix flaky test attempt 2 2018-12-12 15:33:29 +03:00
Andrey Paramonov
ec4507d12a Fix doc formatting 2018-12-12 14:33:02 +03:00
Andrey Paramonov
b1e766c30e Update docs 2018-12-12 13:27:44 +03:00
Andrey Paramonov
316cca204f Switch to ini config parameter 2018-12-12 13:19:39 +03:00
Andrey Paramonov
0bccfc44a7 Fix flaky test 2018-12-12 12:14:14 +03:00
Bruno Oliveira
3cd11617ea Add CHANGELOG 2018-12-11 20:40:06 -02:00
Bruno Oliveira
9839ceffe0 Change -ra to show errors and failures last, instead of first
Often in large test suites (like pytest's), the -ra summary is very useful
to obtain a list of failures so we can execute each test at once to fix them.

Problem is the default shows errors and failures first, which leads to a lot
of scrolling to get to them.
2018-12-11 20:36:57 -02:00
Bruno Oliveira
a44776ed48 Fix linting 2018-12-11 15:16:11 -02:00
Andrey Paramonov
cfbd387a5d Add --junittime=call option 2018-12-11 19:29:31 +03:00
Daniel Hahler
bb363c8ff2 Merge pull request #4528 from blueyed/parser-prog
argparsing: Parser: allow to forward prog to argparse
2018-12-11 13:33:40 +01:00
Daniel Hahler
ebe0a88226 Merge pull request #4522 from blueyed/p-no-space
Handle missing space with -p
2018-12-11 13:33:18 +01:00
Daniel Hahler
3445eae737 argparsing: Parser: allow to forward prog to argparse
Ref: https://github.com/pytest-dev/pytest-xdist/pull/388.
2018-12-11 06:22:35 +01:00
Daniel Hahler
8152b6837e Merge pull request #4419 from blueyed/set_trace-kwargs
pdb: support kwargs with `pdb.set_trace`
2018-12-11 04:28:24 +01:00
Daniel Hahler
0e4e8e00a9 Merge pull request #4504 from feuillemorte/4278_add_cachedir_tag
#4278 Added a CACHEDIR.TAG file to the cache directory
2018-12-11 04:27:35 +01:00
Daniel Hahler
7b1cb885c7 Handle missing space with -p
This still does not use an actual argument parser, which only gets
instantiated below, and it does not appear to make sense instantiating
it just for this pre-parsing it seems.

`-p` without the required value is being handled before already though,
so it could potentially be passed down from somewhere already?!

Fixes https://github.com/pytest-dev/pytest/issues/3532.
2018-12-11 04:27:03 +01:00
Daniel Hahler
fc4aa27cae Derive outcomes.exit.Exception from SystemExit instead of KeyboardInterrupt
This is required for properly getting out of pdb, where
KeyboardInterrupt is caught in py36 at least.

Ref: https://github.com/pytest-dev/pytest/issues/1865#issuecomment-242599949
2018-12-11 04:22:33 +01:00
Daniel Hahler
038f1f94c2 Merge pull request #4524 from blueyed/merge-master
Merge master into features
2018-12-10 10:17:23 +01:00
Daniel Hahler
539d3dc34d Merge branch 'fix-test_raises_exception_looks_iterable' into merge-master 2018-12-10 06:30:36 +01:00
Daniel Hahler
0db5ccb0dd Merge master into features 2018-12-10 05:42:07 +01:00
Bruno Oliveira
76884c73bf Merge pull request #4146 from Tadaboody/give_hints_when_an_assertion_value_is_None_instead_of_a_boolean_3191
[#3191] Give hints when an assertion value is None instead of a boolean
2018-12-05 20:04:52 -02:00
Daniel Hahler
5ebacc49c6 Harden tests, fix doc/msg 2018-12-05 19:22:44 +01:00
Daniel Hahler
8a0ed7e2b3 Revisit changelog entry [ci skip] 2018-12-05 19:14:41 +01:00
Daniel Hahler
62b8f2f731 Update changelog [ci skip] 2018-12-05 19:07:10 +01:00
Tomer Keren
8fd60483ef Don't insert warnings when not in a module 2018-12-05 19:49:54 +02:00
Tomer Keren
7a7ad0c120 Shorten docstring for warn_about_none_ast 2018-12-05 17:47:34 +02:00
Tomer Keren
41031fce2f Address code review 2018-12-05 17:18:57 +02:00
Tomer Keren
e1e4b226c6 👌 Address code review
Edited the changelog for extra clarity, and to fire off auto-formatting

Oddly enough, keeping `filename='{filename!r}'` caused an error while
collecting tests, but getting rid of the single ticks fixed it
Hopefully closes #3191
2018-12-05 10:52:12 +02:00
Bruno Oliveira
26d27df6fc Improve changelog message 2018-12-05 10:41:30 +02:00
Tomer Keren
3e6f1fa2db Simplify warning creation by using ast.parse
in py2 it's a ast.Name where in py3 it's a ast.NamedConstant

Fixes namespace by using import from
2018-12-05 10:42:05 +02:00
Tomer Keren
aaf7f7fcca Update changelog 2018-12-05 10:41:30 +02:00
Tomer Keren
e0c2ab1901 Fix tests not to assert a function that already asserts
Maybe there should be a warning about that too?
2018-12-05 10:41:30 +02:00
Tomer Keren
59a11b6a5d Check for 'assert None' and warn appropriately
:bug:Fix warn ast bugs

:bug:Fix inner-ast imports by using importFrom

Alternetavly ast_call_helper could be retooled to use ast.attribute(...)
2018-12-05 10:41:42 +02:00
Tomer Keren
9fc9b2926f Fix tests and add aditional cases
As requested by review.

👌 Address code review for tests
2018-12-05 10:41:30 +02:00
Tomer Keren
1654b77ca0 [#3191] Set up tests to confirm warnings 2018-12-05 10:41:18 +02:00
feuillemorte
d237197de3 #4278 Added a CACHEDIR.TAG file to the cache directory 2018-12-04 13:49:08 +01:00
Anthony Sottile
5db46d2087 Merge pull request #4498 from asottile/deprecate_pytest_config
Deprecate pytest.config
2018-12-03 11:10:15 -08:00
Anthony Sottile
b88c3f8f82 Deprecate pytest.config 2018-12-03 09:01:42 -08:00
Anthony Sottile
4a3c8e22d7 Merge pull request #4491 from nicoddemus/removal-links
Add links to the deprecations docs for the "removal" changelog entries
2018-12-01 21:05:32 -08:00
Bruno Oliveira
a131f0acf6 Merge pull request #4490 from nicoddemus/remove-cached-setup-add-call
Remove cached_setup and Metafunc.addcall
2018-12-01 18:44:51 -02:00
Bruno Oliveira
4ffa13728d Add links to the deprecations docs for the "removal" changelog entries 2018-12-01 17:33:48 -02:00
Bruno Oliveira
44b74c8c25 Merge pull request #4447 from nicoddemus/remove-result-log
Change resultlog deprecation to PytestDeprecatedWarning
2018-12-01 17:26:17 -02:00
Bruno Oliveira
40b85d7ee8 Remove Metafunc.addcall
Fix #3083
2018-12-01 16:41:59 -02:00
Bruno Oliveira
090f7ff449 Remove request.cached_setup
Fix #4489
2018-12-01 15:59:44 -02:00
Bruno Oliveira
b05061dcd2 Change deprecation type of --result-log to PytestDeprecationWarning
It was decided to deprecate this on a later date (see discussion in #4447)
2018-12-01 14:29:50 -02:00
Bruno Oliveira
06dc6e3490 Merge pull request #4446 from nicoddemus/update-deprecations-docs
Update deprecations.rst now that we have removed a few features
2018-12-01 14:22:19 -02:00
Bruno Oliveira
63f38de38e Merge remote-tracking branch 'upstream/features' into update-deprecations-docs 2018-12-01 14:16:20 -02:00
Bruno Oliveira
e0ba1cbf8d Merge pull request #4299 from RonnyPfannschmidt/callinfo-sanity
refactor callinfo to simplify ctor magic
2018-11-30 14:07:40 -02:00
Ronny Pfannschmidt
847eacea19 refactor CallInfo constructor magic into named constructor 2018-11-30 13:21:54 +01:00
Ronny Pfannschmidt
b531f7d585 Merge pull request #3898 from RonnyPfannschmidt/remove-yield-and-compatprop
Remove yield and compatprop
2018-11-30 13:19:23 +01:00
Ronny Pfannschmidt
7eb28f9eb7 remove yield tests and compat properties 2018-11-30 10:40:13 +01:00
Anthony Sottile
1d86247b2c Merge pull request #4443 from asottile/deprecate_raises_exec
Deprecate `raises(..., 'code(as_a_string)')` / `warns(..., 'code(as_a_string)')`.
2018-11-29 11:57:56 -08:00
Anthony Sottile
1bba0a9714 Deprecate raises(..., 'code(as_a_string)') / `warns(..., 'code(as_a_string)') 2018-11-29 09:34:51 -08:00
Bruno Oliveira
5cf69fae7d Merge pull request #4466 from nicoddemus/merge-master-into-features
Merge master into features
2018-11-27 05:37:15 -02:00
Bruno Oliveira
b73e083d9d Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-11-26 12:55:05 -02:00
Anthony Sottile
7d3ca68be6 Merge pull request #4464 from asottile/dash_q_escaped
Display actual test ids in `--collect-only`
2018-11-25 11:30:26 -08:00
Anthony Sottile
e9b2475e29 Display actual test ids in --collect-only 2018-11-25 09:33:18 -08:00
Ronny Pfannschmidt
59f65230b5 Merge pull request #4454 from nicoddemus/merge-master-into-features
Merge master into features
2018-11-24 16:34:20 +01:00
Daniel Hahler
5bee396e4b Merge master into merge-master-into-features 2018-11-24 13:47:10 +01:00
Bruno Oliveira
33b877cc01 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-11-23 23:27:41 -02:00
Bruno Oliveira
63f90a2bcd Merge pull request #4438 from RonnyPfannschmidt/fix-4386-raises-partial-object
fix #4386 - restructure construction and partial state of ExceptionInfo
2018-11-23 12:41:14 -02:00
Bruno Oliveira
f987b368e8 Merge pull request #3776 from alysivji/attrs-n-dataclasses
Detailed assert failure introspection for attrs and dataclasses objects
2018-11-22 21:25:35 -02:00
Bruno Oliveira
5d2e2377ff Update deprecations.rst now that we have removed a few features
* yield tests
* compat properties
* pytest_namespace

All merciless cut down by Ronny :'(
2018-11-22 20:38:47 -02:00
Bruno Oliveira
2eaf3db6ae Fix docstring indentation (docs env) 2018-11-22 20:21:22 -02:00
Ronny Pfannschmidt
88bf01a31e fix #4386 - restructure construction and partial state of ExceptionInfo 2018-11-22 21:20:32 +01:00
Ronny Pfannschmidt
d894bf4535 Merge pull request #4407 from blueyed/sys-dunder
capture: do not overwrite `sys.__stdin__` etc
2018-11-22 07:26:06 +01:00
Daniel Hahler
9ed63c607e capture: do not overwrite sys.__stdin__ etc
Ref: https://github.com/pytest-dev/pytest/pull/4398#discussion_r234333053
2018-11-20 16:50:15 +01:00
Bruno Oliveira
d52ea4b6cf Use python 3 in 'doctesting' environment
We some examples now use type annotations
2018-11-19 20:06:06 -02:00
Ronny Pfannschmidt
0ffb8ddd7f Merge pull request #4418 from asottile/printable_ids
Ensure that node ids are printable
2018-11-19 21:40:07 +01:00
Anthony Sottile
95c6d591f7 Properly escape \r \n \t bytes 2018-11-19 11:03:52 -08:00
Anthony Sottile
9a1e518cc3 Add changelog entry for printable node ids 2018-11-19 11:03:52 -08:00
Anthony Sottile
9ca0ab6e2b Ensure printable manually-specified param(id=...) 2018-11-19 11:03:52 -08:00
Anthony Sottile
8395b9e25d Require id=... to be a string
This was documented before, but never enforced.  Passing non-strings could
have strange side-effects and enforcing a string simplifies other
implementation.
2018-11-19 11:03:52 -08:00
Anthony Sottile
3d92d5a659 Make sure parametrize ids are printable 2018-11-19 11:03:52 -08:00
Anthony Sottile
50e3783f07 Merge pull request #4421 from RonnyPfannschmidt/remove-pytest-namespace
remove pytest namespace hook
2018-11-19 09:35:33 -08:00
Daniel Hahler
6e85febf20 Merge pull request #4420 from blueyed/merge-master
Merge master into features
2018-11-19 14:53:01 +01:00
Ronny Pfannschmidt
ba17363d75 remove pytest namespace hook 2018-11-19 14:04:41 +01:00
Daniel Hahler
92a2884b09 pdb: support kwargs with pdb.set_trace
This handles `header` similar to Python 3.7 does it, and forwards any
other keyword arguments to the Pdb constructor.

This allows for `__import__("pdb").set_trace(skip=["foo.*"])`.

Fixes https://github.com/pytest-dev/pytest/issues/4416.
2018-11-19 13:04:14 +01:00
Daniel Hahler
2754a13f86 Merge master into features 2018-11-19 12:55:29 +01:00
Nicholas Devenish
4eddf634e7 Rename and split out the testing, and reword the changelog. 2018-11-18 20:01:11 +00:00
Nicholas Devenish
1a8d9bf254 Let approx() work on more generic sequences
approx() was updated in 9f3122fe to work better with numpy arrays,
however at the same time the requirements were tightened from
requiring an Iterable to requiring a Sequence - the former being
tested only on interface, while the latter requires subclassing or
registration with the abc.

Since the ApproxSequence only used __iter__ and __len__ this commit
reduces the requirement to only what's used, and allows unregistered
Sequence-like containers to be used.

Since numpy arrays qualify for the new criteria, reorder the checks so
that generic sequences are checked for after numpy arrays.
2018-11-18 20:01:11 +00:00
Daniel Hahler
62967b3110 Merge pull request #4367 from blueyed/determine_setup
Optimize/revisit determine_setup
2018-11-18 12:52:07 +01:00
Ronny Pfannschmidt
5872e1c35a Merge pull request #4392 from nicoddemus/merge-master-into-features
Merge master into features
2018-11-15 06:59:42 +01:00
Daniel Hahler
ba457f5feb Optimize/revisit determine_setup 2018-11-13 20:14:24 +01:00
Daniel Hahler
ed91d5f086 config: set invocation_dir in the constructor already
This allows to make use of it when determining the rootdir etc.
2018-11-13 20:14:24 +01:00
Aly Sivji
b83e97802e improve failure output 2018-11-13 09:37:02 -06:00
Aly Sivji
2bffd6829e Move dataclass tests for 3.7 to separate file 2018-11-12 15:36:16 -06:00
Aly Sivji
4e99c80425 have tests pass in python37; move to separate file 2018-11-12 11:24:15 -06:00
Aly Sivji
a663f60b05 cr 2/n -- refactor compare eq class 2018-11-12 09:28:47 -06:00
Aly Sivji
e1e81e315e code review 1/n -- change hasattr to getattr 2018-11-12 09:28:47 -06:00
Aly Sivji
025d160dfc Update tests to pass in py27 2018-11-12 09:28:47 -06:00
Aly Sivji
a3e388a73a Improve changelog 2018-11-12 09:28:47 -06:00
Aly Sivji
1847cc7420 adding docs and cleaning up 2018-11-12 09:28:47 -06:00
Aly Sivji
87b019d5f9 fix gitignore 2018-11-12 09:28:47 -06:00
Aly Sivji
1184db8273 cleaning up 2018-11-12 09:28:47 -06:00
Aly Sivji
a0ba881c22 Add change to log; name to AUTHORS 2018-11-12 09:28:47 -06:00
Aly Sivji
d42f1e87c3 Add tests for attrs and dataclasses 2018-11-12 09:28:47 -06:00
Aly Sivji
9769bc05c6 moving plugin inside pytest first pass 2018-11-12 09:20:56 -06:00
130 changed files with 3671 additions and 3483 deletions

2
.github/config.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
rtd:
project: pytest

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ coverage.xml
.pydevproject
.project
.settings
.vscode

View File

@@ -1,7 +1,7 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/ambv/black
rev: 18.6b4
rev: 18.9b0
hooks:
- id: black
args: [--safe, --quiet]
@@ -13,7 +13,7 @@ repos:
additional_dependencies: [black==18.9b0]
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.0.0
rev: v2.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -21,20 +21,23 @@ repos:
- id: debug-statements
exclude: _pytest/debugging.py
language_version: python3
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.0
hooks:
- id: flake8
language_version: python3
- repo: https://github.com/asottile/reorder_python_imports
rev: v1.3.3
rev: v1.3.5
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src']
- repo: https://github.com/asottile/pyupgrade
rev: v1.10.1
rev: v1.11.1
hooks:
- id: pyupgrade
args: [--keep-percent-format]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.1.0
rev: v1.2.0
hooks:
- id: rst-backticks
- repo: local
@@ -51,3 +54,17 @@ repos:
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
files: ^changelog/
- id: py-deprecated
name: py library is deprecated
language: pygrep
entry: >
(?x)\bpy\.(
_code\.|
builtin\.|
code\.|
io\.(BytesIO|saferepr)|
path\.local\.sysfind|
process\.|
std\.
)
types: [python]

10
AUTHORS
View File

@@ -6,21 +6,26 @@ Contributors include::
Aaron Coleman
Abdeali JK
Abhijeet Kasurde
Adam Johnson
Adam Uhlir
Ahn Ki-Wook
Alan Velasco
Alexander Johnson
Alexei Kozlenok
Allan Feldman
Aly Sivji
Anatoly Bubenkoff
Anders Hovmöller
Andras Tim
Andrea Cimatoribus
Andreas Zeidler
Andrey Paramonov
Andrzej Ostrowski
Andy Freeland
Anthon van der Neut
Anthony Shaw
Anthony Sottile
Anton Lodder
Antony Lee
Armin Rigo
Aron Coyle
@@ -48,6 +53,7 @@ Christian Boelsen
Christian Theunert
Christian Tismer
Christopher Gilling
Christopher Dignam
CrazyMerlyn
Cyrus Maden
Dhiren Serai
@@ -114,6 +120,7 @@ Jonas Obrist
Jordan Guymon
Jordan Moldow
Jordan Speicher
Joseph Hunkeler
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@@ -123,6 +130,7 @@ Katerina Koukiou
Kevin Cox
Kodi B. Arfer
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
Lawrence Mitchell
Lee Kamentsky
@@ -164,6 +172,7 @@ Miro Hrončok
Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
Nicholas Devenish
Niclas Olofsson
Nicolas Delaby
Oleg Pidsadnyi
@@ -172,6 +181,7 @@ Oliver Bestwalter
Omar Kohl
Omer Hadari
Ondřej Súkup
Oscar Benjamin
Patrick Hayes
Paweł Adamczak
Pedro Algarvio

View File

@@ -18,6 +18,342 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 4.2.0 (2019-01-30)
=========================
Features
--------
- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Class xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods
now obey the scope of *autouse* fixtures.
This fixes a number of surprising issues like ``setup_method`` being called before session-scoped
autouse fixtures (see `#517 <https://github.com/pytest-dev/pytest/issues/517>`__ for an example).
- `#4627 <https://github.com/pytest-dev/pytest/issues/4627>`_: Display a message at the end of the test session when running under Python 2.7 and 3.4 that pytest 5.0 will no longer
support those Python versions.
- `#4660 <https://github.com/pytest-dev/pytest/issues/4660>`_: The number of *selected* tests now are also displayed when the ``-k`` or ``-m`` flags are used.
- `#4688 <https://github.com/pytest-dev/pytest/issues/4688>`_: ``pytest_report_teststatus`` hook now can also receive a ``config`` parameter.
- `#4691 <https://github.com/pytest-dev/pytest/issues/4691>`_: ``pytest_terminal_summary`` hook now can also receive a ``config`` parameter.
Bug Fixes
---------
- `#3547 <https://github.com/pytest-dev/pytest/issues/3547>`_: ``--junitxml`` can emit XML compatible with Jenkins xUnit.
``junit_family`` INI option accepts ``legacy|xunit1``, which produces old style output, and ``xunit2`` that conforms more strictly to https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
- `#4280 <https://github.com/pytest-dev/pytest/issues/4280>`_: Improve quitting from pdb, especially with ``--trace``.
Using ``q[quit]`` after ``pdb.set_trace()`` will quit pytest also.
- `#4402 <https://github.com/pytest-dev/pytest/issues/4402>`_: Warning summary now groups warnings by message instead of by test id.
This makes the output more compact and better conveys the general idea of how much code is
actually generating warnings, instead of how many tests call that code.
- `#4536 <https://github.com/pytest-dev/pytest/issues/4536>`_: ``monkeypatch.delattr`` handles class descriptors like ``staticmethod``/``classmethod``.
- `#4649 <https://github.com/pytest-dev/pytest/issues/4649>`_: Restore marks being considered keywords for keyword expressions.
- `#4653 <https://github.com/pytest-dev/pytest/issues/4653>`_: ``tmp_path`` fixture and other related ones provides resolved path (a.k.a real path)
- `#4667 <https://github.com/pytest-dev/pytest/issues/4667>`_: ``pytest_terminal_summary`` uses result from ``pytest_report_teststatus`` hook, rather than hardcoded strings.
- `#4669 <https://github.com/pytest-dev/pytest/issues/4669>`_: Correctly handle ``unittest.SkipTest`` exception containing non-ascii characters on Python 2.
- `#4680 <https://github.com/pytest-dev/pytest/issues/4680>`_: Ensure the ``tmpdir`` and the ``tmp_path`` fixtures are the same folder.
- `#4681 <https://github.com/pytest-dev/pytest/issues/4681>`_: Ensure ``tmp_path`` is always a real path.
Trivial/Internal Changes
------------------------
- `#4643 <https://github.com/pytest-dev/pytest/issues/4643>`_: Use ``a.item()`` instead of the deprecated ``np.asscalar(a)`` in ``pytest.approx``.
``np.asscalar`` has been `deprecated <https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations>`__ in ``numpy 1.16.``.
- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib
pytest 4.1.1 (2019-01-12)
=========================
Bug Fixes
---------
- `#2256 <https://github.com/pytest-dev/pytest/issues/2256>`_: Show full repr with ``assert a==b`` and ``-vv``.
- `#3456 <https://github.com/pytest-dev/pytest/issues/3456>`_: Extend Doctest-modules to ignore mock objects.
- `#4617 <https://github.com/pytest-dev/pytest/issues/4617>`_: Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization).
- `#4631 <https://github.com/pytest-dev/pytest/issues/4631>`_: Don't rewrite assertion when ``__getattr__`` is broken
Improved Documentation
----------------------
- `#3375 <https://github.com/pytest-dev/pytest/issues/3375>`_: Document that using ``setup.cfg`` may crash other tools or cause hard to track down problems because it uses a different parser than ``pytest.ini`` or ``tox.ini`` files.
Trivial/Internal Changes
------------------------
- `#4602 <https://github.com/pytest-dev/pytest/issues/4602>`_: Uninstall ``hypothesis`` in regen tox env.
pytest 4.1.0 (2019-01-05)
=========================
Removals
--------
- `#2169 <https://github.com/pytest-dev/pytest/issues/2169>`_: ``pytest.mark.parametrize``: in previous versions, errors raised by id functions were suppressed and changed into warnings. Now the exceptions are propagated, along with a pytest message informing the node, parameter value and index where the exception occurred.
- `#3078 <https://github.com/pytest-dev/pytest/issues/3078>`_: Remove legacy internal warnings system: ``config.warn``, ``Node.warn``. The ``pytest_logwarning`` now issues a warning when implemented.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`__ on information on how to update your code.
- `#3079 <https://github.com/pytest-dev/pytest/issues/3079>`_: Removed support for yield tests - they are fundamentally broken because they don't support fixtures properly since collection and test execution were separated.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#yield-tests>`__ on information on how to update your code.
- `#3082 <https://github.com/pytest-dev/pytest/issues/3082>`_: Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code.
- `#3083 <https://github.com/pytest-dev/pytest/issues/3083>`_: Removed ``Metafunc.addcall``. This was the predecessor mechanism to ``@pytest.mark.parametrize``.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#metafunc-addcall>`__ on information on how to update your code.
- `#3085 <https://github.com/pytest-dev/pytest/issues/3085>`_: Removed support for passing strings to ``pytest.main``. Now, always pass a list of strings instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#passing-command-line-string-to-pytest-main>`__ on information on how to update your code.
- `#3086 <https://github.com/pytest-dev/pytest/issues/3086>`_: ``[pytest]`` section in **setup.cfg** files is not longer supported, use ``[tool:pytest]`` instead. ``setup.cfg`` files
are meant for use with ``distutils``, and a section named ``pytest`` has notoriously been a source of conflicts and bugs.
Note that for **pytest.ini** and **tox.ini** files the section remains ``[pytest]``.
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: Removed the deprecated compat properties for ``node.Class/Function/Module`` - use ``pytest.Class/Function/Module`` now.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#internal-classes-accessed-through-node>`__ on information on how to update your code.
- `#4421 <https://github.com/pytest-dev/pytest/issues/4421>`_: Removed the implementation of the ``pytest_namespace`` hook.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`__ on information on how to update your code.
- `#4489 <https://github.com/pytest-dev/pytest/issues/4489>`_: Removed ``request.cached_setup``. This was the predecessor mechanism to modern fixtures.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#cached-setup>`__ on information on how to update your code.
- `#4535 <https://github.com/pytest-dev/pytest/issues/4535>`_: Removed the deprecated ``PyCollector.makeitem`` method. This method was made public by mistake a long time ago.
- `#4543 <https://github.com/pytest-dev/pytest/issues/4543>`_: Removed support to define fixtures using the ``pytest_funcarg__`` prefix. Use the ``@pytest.fixture`` decorator instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-funcarg-prefix>`__ on information on how to update your code.
- `#4545 <https://github.com/pytest-dev/pytest/issues/4545>`_: Calling fixtures directly is now always an error instead of a warning.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`__ on information on how to update your code.
- `#4546 <https://github.com/pytest-dev/pytest/issues/4546>`_: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check.
Use ``Node.get_closest_marker(name)`` as a replacement.
- `#4547 <https://github.com/pytest-dev/pytest/issues/4547>`_: The deprecated ``record_xml_property`` fixture has been removed, use the more generic ``record_property`` instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#record-xml-property>`__ for more information.
- `#4548 <https://github.com/pytest-dev/pytest/issues/4548>`_: An error is now raised if the ``pytest_plugins`` variable is defined in a non-top-level ``conftest.py`` file (i.e., not residing in the ``rootdir``).
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`__ for more information.
- `#891 <https://github.com/pytest-dev/pytest/issues/891>`_: Remove ``testfunction.markername`` attributes - use ``Node.iter_markers(name=None)`` to iterate them.
Deprecations
------------
- `#3050 <https://github.com/pytest-dev/pytest/issues/3050>`_: Deprecated the ``pytest.config`` global.
See https://docs.pytest.org/en/latest/deprecations.html#pytest-config-global for rationale.
- `#3974 <https://github.com/pytest-dev/pytest/issues/3974>`_: Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``.
It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.
If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Deprecated ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``.
See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec for rationale and examples.
Features
--------
- `#3191 <https://github.com/pytest-dev/pytest/issues/3191>`_: A warning is now issued when assertions are made for ``None``.
This is a common source of confusion among new users, which write:
.. code-block:: python
assert mocked_object.assert_called_with(3, 4, 5, key="value")
When they should write:
.. code-block:: python
mocked_object.assert_called_with(3, 4, 5, key="value")
Because the ``assert_called_with`` method of mock objects already executes an assertion.
This warning will not be issued when ``None`` is explicitly checked. An assertion like:
.. code-block:: python
assert variable is None
will not issue the warning.
- `#3632 <https://github.com/pytest-dev/pytest/issues/3632>`_: Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs <http://www.attrs.org/en/stable/>`__ or `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (Python 3.7+, `backported to 3.6 <https://pypi.org/project/dataclasses>`__).
- `#4278 <https://github.com/pytest-dev/pytest/issues/4278>`_: ``CACHEDIR.TAG`` files are now created inside cache directories.
Those files are part of the `Cache Directory Tagging Standard <http://www.bford.info/cachedir/spec.html>`__, and can
be used by backup or synchronization programs to identify pytest's cache directory as such.
- `#4292 <https://github.com/pytest-dev/pytest/issues/4292>`_: ``pytest.outcomes.Exit`` is derived from ``SystemExit`` instead of ``KeyboardInterrupt``. This allows us to better handle ``pdb`` exiting.
- `#4371 <https://github.com/pytest-dev/pytest/issues/4371>`_: Updated the ``--collect-only`` option to display test descriptions when ran using ``--verbose``.
- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Restructured ``ExceptionInfo`` object construction and ensure incomplete instances have a ``repr``/``str``.
- `#4416 <https://github.com/pytest-dev/pytest/issues/4416>`_: pdb: added support for keyword arguments with ``pdb.set_trace``.
It handles ``header`` similar to Python 3.7 does it, and forwards any
other keyword arguments to the ``Pdb`` constructor.
This allows for ``__import__("pdb").set_trace(skip=["foo.*"])``.
- `#4483 <https://github.com/pytest-dev/pytest/issues/4483>`_: Added ini parameter ``junit_duration_report`` to optionally report test call durations, excluding setup and teardown times.
The JUnit XML specification and the default pytest behavior is to include setup and teardown times in the test duration
report. You can include just the call durations instead (excluding setup and teardown) by adding this to your ``pytest.ini`` file:
.. code-block:: ini
[pytest]
junit_duration_report = call
- `#4532 <https://github.com/pytest-dev/pytest/issues/4532>`_: ``-ra`` now will show errors and failures last, instead of as the first items in the summary.
This makes it easier to obtain a list of errors and failures to run tests selectively.
- `#4599 <https://github.com/pytest-dev/pytest/issues/4599>`_: ``pytest.importorskip`` now supports a ``reason`` parameter, which will be shown when the
requested module cannot be imported.
Bug Fixes
---------
- `#3532 <https://github.com/pytest-dev/pytest/issues/3532>`_: ``-p`` now accepts its argument without a space between the value, for example ``-pmyplugin``.
- `#4327 <https://github.com/pytest-dev/pytest/issues/4327>`_: ``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``.
- `#4397 <https://github.com/pytest-dev/pytest/issues/4397>`_: Ensure that node ids are printable.
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fixed ``raises(..., 'code(string)')`` frame filename.
- `#4458 <https://github.com/pytest-dev/pytest/issues/4458>`_: Display actual test ids in ``--collect-only``.
Improved Documentation
----------------------
- `#4557 <https://github.com/pytest-dev/pytest/issues/4557>`_: Markers example documentation page updated to support latest pytest version.
- `#4558 <https://github.com/pytest-dev/pytest/issues/4558>`_: Update cache documentation example to correctly show cache hit and miss.
- `#4580 <https://github.com/pytest-dev/pytest/issues/4580>`_: Improved detailed summary report documentation.
Trivial/Internal Changes
------------------------
- `#4447 <https://github.com/pytest-dev/pytest/issues/4447>`_: Changed the deprecation type of ``--result-log`` to ``PytestDeprecationWarning``.
It was decided to remove this feature at the next major revision.
pytest 4.0.2 (2018-12-13)
=========================
@@ -1022,7 +1358,7 @@ Features
- Revamp the internals of the ``pytest.mark`` implementation with correct per
node handling which fixes a number of long standing bugs caused by the old
design. This introduces new ``Node.iter_markers(name)`` and
``Node.get_closest_mark(name)`` APIs. Users are **strongly encouraged** to
``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to
read the `reasons for the revamp in the docs
<https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_,
or jump over to details about `updating existing code to use the new APIs
@@ -1757,7 +2093,7 @@ Bug Fixes
Trivial/Internal Changes
------------------------
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`__ for internal
structures to ease code maintainability. (`#2641
<https://github.com/pytest-dev/pytest/issues/2641>`_)

View File

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

View File

@@ -111,7 +111,7 @@ Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ pag
License
-------
Copyright Holger Krekel and others, 2004-2018.
Copyright Holger Krekel and others, 2004-2019.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@@ -39,7 +39,7 @@ clean:
-rm -rf $(BUILDDIR)/*
regen:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-4.2.0
release-4.1.1
release-4.1.0
release-4.0.2
release-4.0.1
release-4.0.0

View File

@@ -0,0 +1,44 @@
pytest-4.1.0
=======================================
The pytest team is proud to announce the 4.1.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 bugs 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:
* Adam Johnson
* Aly Sivji
* Andrey Paramonov
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* David Vo
* Hyunchel Kim
* Jeffrey Rackauckas
* Kanguros
* Nicholas Devenish
* Pedro Algarvio
* Randy Barlow
* Ronny Pfannschmidt
* Tomer Keren
* feuillemorte
* wim glenn
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,27 @@
pytest-4.1.1
=======================================
pytest 4.1.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Anton Lodder
* Bruno Oliveira
* Daniel Hahler
* David Vo
* Oscar Benjamin
* Ronny Pfannschmidt
* Victor Maryama
* Yoav Caspi
* dmitry.dygalo
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,37 @@
pytest-4.2.0
=======================================
The pytest team is proud to announce the 4.2.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 bugs 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:
* Adam Uhlir
* Anthony Sottile
* Bruno Oliveira
* Christopher Dignam
* Daniel Hahler
* Joseph Hunkeler
* Kristoffer Nordstroem
* Ronny Pfannschmidt
* Thomas Hisch
* wim glenn
Happy testing,
The Pytest Development Team

View File

@@ -29,6 +29,7 @@ you will see the return value of the function call:
$ pytest test_assert1.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -100,10 +101,9 @@ If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception::
pytest.raises(ExpectedException, func, *args, **kwargs)
pytest.raises(ExpectedException, "func(*args, **kwargs)")
both of which execute the specified function with args and kwargs and
asserts that the given ``ExpectedException`` is raised. The reporter will
which will execute the specified function with args and kwargs and
assert that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.
@@ -174,6 +174,7 @@ if you run this module:
$ pytest test_assert2.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

View File

@@ -68,8 +68,6 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
def test_function(record_property):
record_property("example_key", 1)
record_xml_property
(Deprecated) use record_property.
record_xml_attribute
Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being

View File

@@ -81,8 +81,9 @@ If you then run it with ``--lf``:
$ pytest --lf
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items / 48 deselected
collected 50 items / 48 deselected / 2 selected
run-last-failure: rerun previous 2 failures
test_50.py FF [100%]
@@ -124,6 +125,7 @@ of ``FF`` and dots):
$ pytest --ff
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
run-last-failure: rerun previous 2 failures first
@@ -185,11 +187,14 @@ across pytest invocations::
import pytest
import time
def expensive_computation():
print("running expensive computation...")
@pytest.fixture
def mydata(request):
val = request.config.cache.get("example/value", None)
if val is None:
time.sleep(9*0.6) # expensive computation :)
expensive_computation()
val = 42
request.config.cache.set("example/value", val)
return val
@@ -197,8 +202,7 @@ across pytest invocations::
def test_function(mydata):
assert mydata == 23
If you run this command once, it will take a while because
of the sleep:
If you run this command for the first time, you can see the print statement:
.. code-block:: pytest
@@ -212,12 +216,16 @@ of the sleep:
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
E -42
E +23
test_caching.py:14: AssertionError
test_caching.py:17: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
1 failed in 0.12 seconds
If you run it a second time the value will be retrieved from
the cache and this will be quick:
the cache and nothing will be printed:
.. code-block:: pytest
@@ -231,8 +239,10 @@ the cache and this will be quick:
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
E -42
E +23
test_caching.py:14: AssertionError
test_caching.py:17: AssertionError
1 failed in 0.12 seconds
See the :ref:`cache-api` for more details.
@@ -249,11 +259,17 @@ You can always peek at the content of the cache using the
$ pytest --cache-show
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
cachedir: $REGENDOC_TMPDIR/.pytest_cache
cachedir: $PYTHON_PREFIX/.pytest_cache
------------------------------- cache values -------------------------------
cache/lastfailed contains:
{'test_caching.py::test_function': True}
{'test_50.py::test_num[17]': True,
'test_50.py::test_num[25]': True,
'test_assert1.py::test_function': True,
'test_assert2.py::test_set_comparison': True,
'test_caching.py::test_function': True,
'test_foocompare.py::test_compare': True}
cache/nodeids contains:
['test_caching.py::test_function']
cache/stepwise contains:

View File

@@ -68,6 +68,7 @@ of the failing function and hide the other one:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items

View File

@@ -42,10 +42,11 @@ todo_include_todos = 1
extensions = [
"pygments_pytest",
"sphinx.ext.autodoc",
"sphinx.ext.todo",
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
"sphinx_removed_in",
"sphinxcontrib_trio",
]
@@ -64,7 +65,7 @@ master_doc = "contents"
# General information about the project.
project = u"pytest"
year = datetime.datetime.utcnow().year
copyright = u"20152018 , holger krekel and pytest-dev team"
copyright = u"20152019 , holger krekel and pytest-dev team"
# The language for content autogenerated by Sphinx. Refer to documentation

View File

@@ -41,6 +41,7 @@ Full pytest documentation
backwards-compatibility
deprecations
py27-py34-deprecation
historical-notes
license
contributing

View File

@@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
should be used instead.
.. contents::
:depth: 3
:local:
Deprecated Features
-------------------
@@ -14,24 +19,226 @@ 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>`.
Internal classes accessed through ``Node``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``"message"`` parameter of ``pytest.raises``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.9
.. deprecated:: 4.1
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
this warning::
It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To prevent
users from making this mistake, and because it is believed to be little used, pytest is
deprecating it without providing an alternative for the moment.
usage of Function.Module is deprecated, please use pytest.Module instead
If you have a valid use case for this parameter, consider that to obtain the same results
you can just call ``pytest.fail`` manually at the end of the ``with`` statement.
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
For example:
.. code-block:: python
with pytest.raises(TimeoutError, message="Client got unexpected message"):
wait_for(websocket.recv(), 0.5)
Becomes:
.. code-block:: python
with pytest.raises(TimeoutError):
wait_for(websocket.recv(), 0.5)
pytest.fail("Client got unexpected message")
If you still have concerns about this deprecation and future removal, please comment on
`issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
``pytest.config`` global
~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 4.1
The ``pytest.config`` global object is deprecated. Instead use
``request.config`` (via the ``request`` fixture) or if you are a plugin author
use the ``pytest_configure(config)`` hook.
.. _raises-warns-exec:
``raises`` / ``warns`` with a string as the second argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 4.1
Use the context manager form of these instead. When necessary, invoke ``exec``
directly.
Example:
.. code-block:: python
pytest.raises(ZeroDivisionError, "1 / 0")
pytest.raises(SyntaxError, "a $ b")
pytest.warns(DeprecationWarning, "my_function()")
pytest.warns(SyntaxWarning, "assert(1, 2)")
Becomes:
.. code-block:: python
with pytest.raises(ZeroDivisionError):
1 / 0
with pytest.raises(SyntaxError):
exec("a $ b") # exec is required for invalid syntax
with pytest.warns(DeprecationWarning):
my_function()
with pytest.warns(SyntaxWarning):
exec("assert(1, 2)") # exec is used to avoid a top-level warning
Result log (``--result-log``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
The ``--resultlog`` command line option has been deprecated: it is little used
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
This feature will be effectively removed in pytest 4.0 as the team intends to include a better alternative in the core.
If you have any concerns, please don't hesitate to `open an issue <https://github.com/pytest-dev/pytest/issues>`__.
Removed Features
----------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
Using ``Class`` in custom Collectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during
collection.
This issue should affect only advanced plugins who create new collection types, so if you see this warning
message please contact the authors so they can change the code.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.mark.xfail(reason="flaky")(6, 36),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.param(6, 36, marks=pytest.mark.xfail(reason="flaky")),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
``pytest_funcarg__`` prefix
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix:
.. code-block:: python
def pytest_funcarg__data():
return SomeData()
Switch over to the ``@pytest.fixture`` decorator:
.. code-block:: python
@pytest.fixture
def data():
return SomeData()
[pytest] section in setup.cfg files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
to avoid conflicts with other distutils commands.
Metafunc.addcall
~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
:meth:`_pytest.python.Metafunc.parametrize` instead.
Example:
.. code-block:: python
def pytest_generate_tests(metafunc):
metafunc.addcall({"i": 1}, id="1")
metafunc.addcall({"i": 2}, id="2")
Becomes:
.. code-block:: python
def pytest_generate_tests(metafunc):
metafunc.parametrize("i", [1, 2], ids=["1", "2"])
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
``cached_setup``
~~~~~~~~~~~~~~~~
.. deprecated:: 3.9
.. versionremoved:: 4.0
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
@@ -59,26 +266,21 @@ This should be updated to make use of standard fixture mechanisms:
You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for
more information.
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
pytest_plugins in non-top-level conftest files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using ``Class`` in custom Collectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
.. deprecated:: 3.9
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during
collection.
This issue should affect only advanced plugins who create new collection types, so if you see this warning
message please contact the authors so they can change the code.
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
features ``conftest.py`` files are only *active* for tests at or below it.
``Config.warn`` and ``Node.warn``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.8
.. versionremoved:: 4.0
Those methods were part of the internal pytest warnings system, but since ``3.8`` pytest is using the builtin warning
system for its own warnings, so those two functions are now deprecated.
@@ -100,47 +302,57 @@ Becomes:
* ``node.warn(PytestWarning("some message"))``: is now the **recommended** way to call this function.
The warning instance must be a PytestWarning or subclass.
* ``node.warn("CI", "some message")``: this code/message form is now **deprecated** and should be converted to the warning instance form above.
* ``node.warn("CI", "some message")``: this code/message form has been **removed** and should be converted to the warning instance form above.
record_xml_property
~~~~~~~~~~~~~~~~~~~
``pytest_namespace``
~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
.. deprecated:: 3.7
The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some
bug fixes and refactorings impossible.
Example of usage:
This is just a matter of renaming the fixture as the API is the same:
.. code-block:: python
class MySymbol:
def test_foo(record_xml_property):
...
Change to:
.. code-block:: python
def test_foo(record_property):
...
def pytest_namespace():
return {"my_symbol": MySymbol()}
Passing command-line string to ``pytest.main()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API).
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
Passing a command-line string to ``pytest.main()`` is deprecated:
.. code-block:: python
import pytest
pytest.main("-v -s")
Pass a list instead:
.. code-block:: python
pytest.main(["-v", "-s"])
def pytest_configure():
pytest.my_symbol = MySymbol()
By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
Calling fixtures directly
~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.7
.. versionremoved:: 4.0
Calling a fixture function directly, as opposed to request them in a test function, is deprecated.
@@ -175,116 +387,27 @@ In those cases just request the function directly in the dependent fixture:
cell.make_full()
return cell
``Node.get_marker``
~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.6
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
:ref:`the documentation <update marker code>` on tips on how to update your code.
record_xml_property
~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.5
The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
This is just a matter of renaming the fixture as the API is the same:
Alternatively if the fixture function is called multiple times inside a test (making it hard to apply the above pattern) or
if you would like to make minimal changes to the code, you can create a fixture which calls the original function together
with the ``name`` parameter:
.. code-block:: python
def test_foo(record_xml_property):
...
Change to:
.. code-block:: python
def test_foo(record_property):
...
pytest_plugins in non-top-level conftest files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.5
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
features ``conftest.py`` files are only *active* for tests at or below it.
Metafunc.addcall
~~~~~~~~~~~~~~~~
.. deprecated:: 3.3
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
:meth:`_pytest.python.Metafunc.parametrize` instead.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.2
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b", [(3, 9), pytest.mark.xfail(reason="flaky")(6, 36), (10, 100)]
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[(3, 9), pytest.param((6, 36), marks=pytest.mark.xfail(reason="flaky")), (10, 100)],
)
def test_foo(a, b):
...
def cell():
return ...
Passing command-line string to ``pytest.main()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
Passing a command-line string to ``pytest.main()`` is deprecated:
.. code-block:: python
pytest.main("-v -s")
Pass a list instead:
.. code-block:: python
pytest.main(["-v", "-s"])
By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
@pytest.fixture(name="cell")
def cell_fixture():
return cell()
``yield`` tests
~~~~~~~~~~~~~~~
.. deprecated:: 3.0
.. versionremoved:: 4.0
pytest supports ``yield``-style tests, where a test function actually ``yield`` functions and values
pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values
that are then turned into proper test methods. Example:
.. code-block:: python
@@ -307,54 +430,77 @@ This form of test function doesn't support fixtures properly, and users should s
def test_squared(x, y):
assert x ** x == y
Internal classes accessed through ``Node``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pytest_funcarg__`` prefix
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
.. deprecated:: 3.0
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
this warning::
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix:
usage of Function.Module is deprecated, please use pytest.Module instead
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
``Node.get_marker``
~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
:ref:`the documentation <update marker code>` on tips on how to update your code.
``somefunction.markname``
~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
As part of a large :ref:`marker-revamp` we already deprecated using ``MarkInfo``
the only correct way to get markers of an element is via ``node.iter_markers(name)``.
``pytest_namespace``
~~~~~~~~~~~~~~~~~~~~
.. versionremoved:: 4.0
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some
bug fixes and refactorings impossible.
Example of usage:
.. code-block:: python
def pytest_funcarg__data():
return SomeData()
class MySymbol:
...
Switch over to the ``@pytest.fixture`` decorator:
def pytest_namespace():
return {"my_symbol": MySymbol()}
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API).
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
.. code-block:: python
@pytest.fixture
def data():
return SomeData()
import pytest
[pytest] section in setup.cfg files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
def pytest_configure():
pytest.my_symbol = MySymbol()
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
to avoid conflicts with other distutils commands.
Result log (``--result-log``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
The ``--resultlog`` command line option has been deprecated: it is little used
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
Removed Features
----------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
Reinterpretation mode (``--assert=reinterp``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 3.0.*
.. versionremoved:: 3.0
Reinterpretation mode has now been removed and only plain and rewrite
mode are available, consequently the ``--assert=reinterp`` option is
@@ -366,7 +512,7 @@ explicitly turn on assertion rewriting for those files.
Removed command-line options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 3.0.*
.. versionremoved:: 3.0
The following deprecated commandline options were removed:
@@ -378,7 +524,7 @@ The following deprecated commandline options were removed:
py.test-X* entry points
~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 3.0.*
.. versionremoved:: 3.0
Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
were never documented and a leftover from a pre-virtualenv era. These entry

View File

@@ -65,6 +65,7 @@ then you can just invoke ``pytest`` without command line options:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 1 item

View File

@@ -98,6 +98,30 @@ class TestSpecialisedExplanations(object):
text = "head " * 50 + "f" * 70 + "tail " * 20
assert "f" * 70 not in text
def test_eq_dataclass(self):
from dataclasses import dataclass
@dataclass
class Foo(object):
a: int
b: str
left = Foo(1, "b")
right = Foo(1, "c")
assert left == right
def test_eq_attrs(self):
import attr
@attr.s
class Foo(object):
a = attr.ib()
b = attr.ib()
left = Foo(1, "b")
right = Foo(1, "c")
assert left == right
def test_attribute():
class Foo(object):
@@ -141,11 +165,11 @@ def globf(x):
class TestRaises(object):
def test_raises(self):
s = "qwe" # NOQA
raises(TypeError, "int(s)")
s = "qwe"
raises(TypeError, int, s)
def test_raises_doesnt(self):
raises(IOError, "int('3')")
raises(IOError, int, "3")
def test_raise(self):
raise ValueError("demo error")

View File

@@ -9,5 +9,5 @@ def test_failure_demo_fails_properly(testdir):
failure_demo.copy(target)
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target, syspathinsert=True)
result.stdout.fnmatch_lines(["*42 failed*"])
result.stdout.fnmatch_lines(["*44 failed*"])
assert result.ret != 0

View File

@@ -24,10 +24,10 @@ example: specifying and selecting acceptance tests
pytest.skip("specify -A to run acceptance tests")
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
def run(self, cmd):
def run(self, *cmd):
""" called by test code to execute an acceptance test. """
self.tmpdir.chdir()
return py.process.cmdexec(cmd)
return subprocess.check_output(cmd).decode()
and the actual test function example:
@@ -36,7 +36,7 @@ and the actual test function example:
def test_some_acceptance_aspect(accept):
accept.tmpdir.mkdir("somesub")
result = accept.run("ls -la")
result = accept.run("ls", "-la")
assert "somesub" in result
If you run this test without specifying a command line option

View File

@@ -33,10 +33,10 @@ You can then restrict a test run to only run tests marked with ``webtest``:
$ pytest -v -m webtest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected
collecting ... collected 4 items / 3 deselected / 1 selected
test_server.py::test_send_http PASSED [100%]
@@ -48,10 +48,10 @@ Or the inverse, running all tests except the webtest ones:
$ pytest -v -m "not webtest"
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected
collecting ... collected 4 items / 1 deselected / 3 selected
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
@@ -70,8 +70,8 @@ tests based on their module, class, method, or function name:
$ pytest -v test_server.py::TestClass::test_method
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -85,8 +85,8 @@ You can also select on the class:
$ pytest -v test_server.py::TestClass
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -100,8 +100,8 @@ Or select multiple nodes:
$ pytest -v test_server.py::TestClass test_server.py::test_send_http
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
@@ -140,10 +140,10 @@ select tests based on their names:
$ pytest -v -k http # running with the above defined example module
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected
collecting ... collected 4 items / 3 deselected / 1 selected
test_server.py::test_send_http PASSED [100%]
@@ -155,10 +155,10 @@ And you can also run all tests except the ones that match the keyword:
$ pytest -k "not send_http" -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected
collecting ... collected 4 items / 1 deselected / 3 selected
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
@@ -172,10 +172,10 @@ Or to select "http" and "quick" tests:
$ pytest -k "http or quick" -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 2 deselected
collecting ... collected 4 items / 2 deselected / 2 selected
test_server.py::test_send_http PASSED [ 50%]
test_server.py::test_something_quick PASSED [100%]
@@ -308,7 +308,7 @@ apply a marker to an individual test instance::
@pytest.mark.foo
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.bar((1, 3)),
pytest.param((1, 3), marks=pytest.mark.bar),
(2, 3),
])
def test_increment(n, expected):
@@ -318,15 +318,6 @@ In this example the mark "foo" will apply to each of the three
tests, whereas the "bar" mark is only applied to the second test.
Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with parametrize`.
.. note::
If the data you are parametrizing happen to be single callables, you need to be careful
when marking these items. ``pytest.mark.xfail(my_func)`` won't work because it's also the
signature of a function being decorated. To resolve this ambiguity, you need to pass a
reason argument:
``pytest.mark.xfail(func_bar, reason="Issue#7")``.
.. _`adding a custom marker from a plugin`:
Custom marker and command line option to control test runs
@@ -374,6 +365,7 @@ the test needs:
$ pytest -E stage2
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -388,6 +380,7 @@ and here is one that specifies exactly the environment needed:
$ pytest -E stage1
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -555,12 +548,13 @@ then you will see two tests skipped and two executed tests as expected:
$ pytest -rs # this option reports skip reasons
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_plat.py s.s. [100%]
========================= short test summary info ==========================
SKIP [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
=================== 2 passed, 2 skipped in 0.12 seconds ====================
@@ -571,8 +565,9 @@ Note that if you specify a platform via the marker-command line option like this
$ pytest -m linux
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items / 3 deselected
collected 4 items / 3 deselected / 1 selected
test_plat.py . [100%]
@@ -624,8 +619,9 @@ We can now use the ``-m option`` to select one set:
$ pytest -m interface --tb=short
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items / 2 deselected
collected 4 items / 2 deselected / 2 selected
test_module.py FF [100%]
@@ -647,8 +643,9 @@ or to select both "event" and "interface" tests:
$ pytest -m "interface or event" --tb=short
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items / 1 deselected
collected 4 items / 1 deselected / 3 selected
test_module.py FFF [100%]

View File

@@ -2,10 +2,10 @@
module containing a parametrized tests testing cross-python
serialization via the pickle module.
"""
import distutils.spawn
import subprocess
import textwrap
import py
import pytest
pythonlist = ["python2.7", "python3.4", "python3.5"]
@@ -24,7 +24,7 @@ def python2(request, python1):
class Python(object):
def __init__(self, version, picklefile):
self.pythonpath = py.path.local.sysfind(version)
self.pythonpath = distutils.spawn.find_executable(version)
if not self.pythonpath:
pytest.skip("{!r} not found".format(version))
self.picklefile = picklefile
@@ -43,7 +43,7 @@ class Python(object):
)
)
)
py.process.cmdexec("{} {}".format(self.pythonpath, dumpfile))
subprocess.check_call((self.pythonpath, str(dumpfile)))
def load_and_is_true(self, expression):
loadfile = self.picklefile.dirpath("load.py")
@@ -63,7 +63,7 @@ class Python(object):
)
)
print(loadfile)
py.process.cmdexec("{} {}".format(self.pythonpath, loadfile))
subprocess.check_call((self.pythonpath, str(loadfile)))
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])

View File

@@ -30,6 +30,7 @@ now execute the test specification:
nonpython $ pytest test_simple.yml
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items
@@ -63,8 +64,8 @@ consulted when reporting in ``verbose`` mode:
nonpython $ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items
@@ -88,11 +89,12 @@ interesting to just look at the collection tree:
nonpython $ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items
<Package '$REGENDOC_TMPDIR/nonpython'>
<YamlFile 'test_simple.yml'>
<YamlItem 'hello'>
<YamlItem 'ok'>
<Package $REGENDOC_TMPDIR/nonpython>
<YamlFile test_simple.yml>
<YamlItem hello>
<YamlItem ok>
======================= no tests ran in 0.12 seconds =======================

View File

@@ -145,17 +145,18 @@ objects, they are still using the default pytest representation:
$ pytest test_time.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 8 items
<Module 'test_time.py'>
<Function 'test_timedistance_v0[a0-b0-expected0]'>
<Function 'test_timedistance_v0[a1-b1-expected1]'>
<Function 'test_timedistance_v1[forward]'>
<Function 'test_timedistance_v1[backward]'>
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
<Function 'test_timedistance_v3[forward]'>
<Function 'test_timedistance_v3[backward]'>
<Module test_time.py>
<Function test_timedistance_v0[a0-b0-expected0]>
<Function test_timedistance_v0[a1-b1-expected1]>
<Function test_timedistance_v1[forward]>
<Function test_timedistance_v1[backward]>
<Function test_timedistance_v2[20011212-20011211-expected0]>
<Function test_timedistance_v2[20011211-20011212-expected1]>
<Function test_timedistance_v3[forward]>
<Function test_timedistance_v3[backward]>
======================= no tests ran in 0.12 seconds =======================
@@ -203,6 +204,7 @@ this is a fully self-contained example which you can run with:
$ pytest test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
@@ -217,14 +219,15 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
$ pytest --collect-only test_scenarios.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
<Module 'test_scenarios.py'>
<Class 'TestSampleWithScenarios'>
<Function 'test_demo1[basic]'>
<Function 'test_demo2[basic]'>
<Function 'test_demo1[advanced]'>
<Function 'test_demo2[advanced]'>
<Module test_scenarios.py>
<Class TestSampleWithScenarios>
<Function test_demo1[basic]>
<Function test_demo2[basic]>
<Function test_demo1[advanced]>
<Function test_demo2[advanced]>
======================= no tests ran in 0.12 seconds =======================
@@ -283,11 +286,12 @@ Let's first see how it looks like at collection time:
$ pytest test_backends.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[d1]'>
<Function 'test_db_initialized[d2]'>
<Module test_backends.py>
<Function test_db_initialized[d1]>
<Function test_db_initialized[d2]>
======================= no tests ran in 0.12 seconds =======================
@@ -348,10 +352,11 @@ The result of this test will be successful:
$ pytest test_indirect_list.py --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
<Module 'test_indirect_list.py'>
<Function 'test_indirect[a-b]'>
<Module test_indirect_list.py>
<Function test_indirect[a-b]>
======================= no tests ran in 0.12 seconds =======================
@@ -388,7 +393,8 @@ parametrizer`_ but in a lot less code::
assert a == b
def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b")
with pytest.raises(ZeroDivisionError):
a / b
Our test generator looks up a class-level definition which specifies which
argument sets to use for each test function. Let's run it:
@@ -405,6 +411,8 @@ argument sets to use for each test function. Let's run it:
def test_equals(self, a, b):
> assert a == b
E assert 1 == 2
E -1
E +2
test_parametrize.py:18: AssertionError
1 failed, 2 passed in 0.12 seconds
@@ -430,7 +438,7 @@ Running it results in some skips if we don't have all the python interpreters in
. $ pytest -rs -q multipython.py
...sss...sssssssss...sss... [100%]
========================= short test summary info ==========================
SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
12 passed, 15 skipped in 0.12 seconds
Indirect parametrization of optional implementations/imports
@@ -481,12 +489,13 @@ If you run this with reporting for skips enabled:
$ pytest -rs test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .s [100%]
========================= short test summary info ==========================
SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
=================== 1 passed, 1 skipped in 0.12 seconds ====================
@@ -537,14 +546,14 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
$ pytest -v -m basic
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 17 items / 14 deselected
collecting ... collected 17 items / 14 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%]
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============

View File

@@ -132,12 +132,13 @@ The test collection would look like this:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 2 items
<Module 'check_myapp.py'>
<Class 'CheckMyApp'>
<Function 'simple_check'>
<Function 'complex_check'>
<Module check_myapp.py>
<Class CheckMyApp>
<Function simple_check>
<Function complex_check>
======================= no tests ran in 0.12 seconds =======================
@@ -187,13 +188,14 @@ You can always peek at the collection tree without running tests like this:
. $ pytest --collect-only pythoncollection.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 3 items
<Module 'CWD/pythoncollection.py'>
<Function 'test_function'>
<Class 'TestClass'>
<Function 'test_method'>
<Function 'test_anothermethod'>
<Module CWD/pythoncollection.py>
<Function test_function>
<Class TestClass>
<Function test_method>
<Function test_anothermethod>
======================= no tests ran in 0.12 seconds =======================
@@ -259,6 +261,7 @@ file will be left out:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 0 items

View File

@@ -14,10 +14,11 @@ get on the terminal - we are working on that):
assertion $ pytest failure_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
collected 42 items
collected 44 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
================================= FAILURES =================================
___________________________ test_generative[3-6] ___________________________
@@ -289,6 +290,48 @@ get on the terminal - we are working on that):
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:99: AssertionError
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
def test_eq_dataclass(self):
from dataclasses import dataclass
@dataclass
class Foo(object):
a: int
b: str
left = Foo(1, "b")
right = Foo(1, "c")
> assert left == right
E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialise...oo(a=1, b='c')
E Omitting 1 identical items, use -vv to show
E Differing attributes:
E b: 'b' != 'c'
failure_demo.py:111: AssertionError
________________ TestSpecialisedExplanations.test_eq_attrs _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
def test_eq_attrs(self):
import attr
@attr.s
class Foo(object):
a = attr.ib()
b = attr.ib()
left = Foo(1, "b")
right = Foo(1, "c")
> assert left == right
E AssertionError: assert Foo(a=1, b='b') == Foo(a=1, b='c')
E Omitting 1 identical items, use -vv to show
E Differing attributes:
E b: 'b' != 'c'
failure_demo.py:123: AssertionError
______________________________ test_attribute ______________________________
def test_attribute():
@@ -300,7 +343,7 @@ get on the terminal - we are working on that):
E assert 1 == 2
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
failure_demo.py:107: AssertionError
failure_demo.py:131: AssertionError
_________________________ test_attribute_instance __________________________
def test_attribute_instance():
@@ -312,7 +355,7 @@ get on the terminal - we are working on that):
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
failure_demo.py:114: AssertionError
failure_demo.py:138: AssertionError
__________________________ test_attribute_failure __________________________
def test_attribute_failure():
@@ -325,7 +368,7 @@ get on the terminal - we are working on that):
i = Foo()
> assert i.b == 2
failure_demo.py:125:
failure_demo.py:149:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
@@ -334,7 +377,7 @@ get on the terminal - we are working on that):
> raise Exception("Failed to get attrib")
E Exception: Failed to get attrib
failure_demo.py:120: Exception
failure_demo.py:144: Exception
_________________________ test_attribute_multiple __________________________
def test_attribute_multiple():
@@ -351,31 +394,26 @@ get on the terminal - we are working on that):
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
failure_demo.py:135: AssertionError
failure_demo.py:159: AssertionError
__________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
def test_raises(self):
s = "qwe" # NOQA
> raises(TypeError, "int(s)")
s = "qwe"
> raises(TypeError, int, s)
E ValueError: invalid literal for int() with base 10: 'qwe'
failure_demo.py:145:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen $REGENDOC_TMPDIR/assertion/failure_demo.py:145>:1: ValueError
failure_demo.py:169: ValueError
______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
def test_raises_doesnt(self):
> raises(IOError, "int('3')")
> raises(IOError, int, "3")
E Failed: DID NOT RAISE <class 'OSError'>
failure_demo.py:148: Failed
failure_demo.py:172: Failed
__________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -384,7 +422,7 @@ get on the terminal - we are working on that):
> raise ValueError("demo error")
E ValueError: demo error
failure_demo.py:151: ValueError
failure_demo.py:175: ValueError
________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -393,7 +431,7 @@ get on the terminal - we are working on that):
> a, b = [1] # NOQA
E ValueError: not enough values to unpack (expected 2, got 1)
failure_demo.py:154: ValueError
failure_demo.py:178: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -404,7 +442,7 @@ get on the terminal - we are working on that):
> a, b = items.pop()
E TypeError: 'int' object is not iterable
failure_demo.py:159: TypeError
failure_demo.py:183: TypeError
--------------------------- Captured stdout call ---------------------------
items is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________
@@ -415,7 +453,7 @@ get on the terminal - we are working on that):
> if namenotexi: # NOQA
E NameError: name 'namenotexi' is not defined
failure_demo.py:162: NameError
failure_demo.py:186: NameError
____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely():
@@ -430,14 +468,14 @@ get on the terminal - we are working on that):
sys.modules[name] = module
> module.foo()
failure_demo.py:180:
failure_demo.py:204:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def foo():
> assert 1 == 0
E AssertionError
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:177>:2: AssertionError
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:201>:2: AssertionError
____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -451,7 +489,7 @@ get on the terminal - we are working on that):
> somefunc(f(), g())
failure_demo.py:191:
failure_demo.py:215:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:13: in somefunc
otherfunc(x, y)
@@ -473,7 +511,7 @@ get on the terminal - we are working on that):
> a, b = items
E ValueError: not enough values to unpack (expected 2, got 0)
failure_demo.py:195: ValueError
failure_demo.py:219: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -483,7 +521,7 @@ get on the terminal - we are working on that):
> a, b = items
E TypeError: 'int' object is not iterable
failure_demo.py:199: TypeError
failure_demo.py:223: TypeError
______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -496,7 +534,7 @@ get on the terminal - we are working on that):
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
failure_demo.py:204: AssertionError
failure_demo.py:228: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -515,7 +553,7 @@ get on the terminal - we are working on that):
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
failure_demo.py:213: AssertionError
failure_demo.py:237: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -526,7 +564,7 @@ get on the terminal - we are working on that):
E + where False = isinstance(43, float)
E + where 43 = globf(42)
failure_demo.py:216: AssertionError
failure_demo.py:240: AssertionError
_______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -537,7 +575,7 @@ get on the terminal - we are working on that):
E assert 42 != 42
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
failure_demo.py:220: AssertionError
failure_demo.py:244: AssertionError
_______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -547,7 +585,7 @@ get on the terminal - we are working on that):
E assert 11 < 5
E + where 11 = globf(10)
failure_demo.py:223: AssertionError
failure_demo.py:247: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -558,7 +596,7 @@ get on the terminal - we are working on that):
> assert x == 0
E assert 1 == 0
failure_demo.py:228: AssertionError
failure_demo.py:252: AssertionError
___________________ TestCustomAssertMsg.test_single_line ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -573,7 +611,7 @@ get on the terminal - we are working on that):
E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
failure_demo.py:239: AssertionError
failure_demo.py:263: AssertionError
____________________ TestCustomAssertMsg.test_multiline ____________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -592,7 +630,7 @@ get on the terminal - we are working on that):
E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
failure_demo.py:246: AssertionError
failure_demo.py:270: AssertionError
___________________ TestCustomAssertMsg.test_custom_repr ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -614,5 +652,5 @@ get on the terminal - we are working on that):
E assert 1 == 2
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
failure_demo.py:259: AssertionError
======================== 42 failed in 0.12 seconds =========================
failure_demo.py:283: AssertionError
======================== 44 failed in 0.12 seconds =========================

View File

@@ -128,6 +128,7 @@ directory with the above conftest.py:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
@@ -188,12 +189,13 @@ and when running it will see a skipped "slow" test:
$ pytest -rs # "-rs" means report details on the little 's'
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .s [100%]
========================= short test summary info ==========================
SKIP [1] test_module.py:8: need --runslow option to run
SKIPPED [1] test_module.py:8: need --runslow option to run
=================== 1 passed, 1 skipped in 0.12 seconds ====================
@@ -204,6 +206,7 @@ Or run it including the ``slow`` marked test:
$ pytest --runslow
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
@@ -346,6 +349,7 @@ which will add the string to the test header accordingly:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
project deps: mylib-1.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
@@ -373,8 +377,8 @@ which will add info only when run with "--v":
$ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
info1: did you know that ...
did you?
rootdir: $REGENDOC_TMPDIR, inifile:
@@ -389,6 +393,7 @@ and nothing when run plainly:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
@@ -428,6 +433,7 @@ Now we can profile which test functions execute the slowest:
$ pytest --durations=3
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
@@ -502,6 +508,7 @@ If we run this:
$ pytest -rx
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
@@ -585,6 +592,7 @@ We can run this:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 7 items
@@ -598,7 +606,7 @@ We can run this:
file $REGENDOC_TMPDIR/b/test_error.py, line 1
def test_root(db): # no db here, will error out
E fixture 'db' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
$REGENDOC_TMPDIR/b/test_error.py:1
@@ -698,6 +706,7 @@ and run them:
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
@@ -799,6 +808,7 @@ and run it:
$ pytest -s test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items

View File

@@ -73,6 +73,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
$ pytest test_smtpsimple.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -213,6 +214,7 @@ inspect what is going on and can now run the tests:
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
@@ -628,7 +630,7 @@ So let's just do another run:
response, msg = smtp_connection.ehlo()
assert response == 250
> assert b"smtp.gmail.com" in msg
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'
test_module.py:5: AssertionError
-------------------------- Captured stdout setup ---------------------------
@@ -701,21 +703,22 @@ Running the above tests results in the following test IDs being used:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 10 items
<Module 'test_anothersmtp.py'>
<Function 'test_showhelo[smtp.gmail.com]'>
<Function 'test_showhelo[mail.python.org]'>
<Module 'test_ids.py'>
<Function 'test_a[spam]'>
<Function 'test_a[ham]'>
<Function 'test_b[eggs]'>
<Function 'test_b[1]'>
<Module 'test_module.py'>
<Function 'test_ehlo[smtp.gmail.com]'>
<Function 'test_noop[smtp.gmail.com]'>
<Function 'test_ehlo[mail.python.org]'>
<Function 'test_noop[mail.python.org]'>
<Module test_anothersmtp.py>
<Function test_showhelo[smtp.gmail.com]>
<Function test_showhelo[mail.python.org]>
<Module test_ids.py>
<Function test_a[spam]>
<Function test_a[ham]>
<Function test_b[eggs]>
<Function test_b[1]>
<Module test_module.py>
<Function test_ehlo[smtp.gmail.com]>
<Function test_noop[smtp.gmail.com]>
<Function test_ehlo[mail.python.org]>
<Function test_noop[mail.python.org]>
======================= no tests ran in 0.12 seconds =======================
@@ -744,8 +747,8 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
$ pytest test_fixture_marks.py -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 3 items
@@ -789,8 +792,8 @@ Here we declare an ``app`` fixture which receives the previously defined
$ pytest -v test_appsetup.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
@@ -804,7 +807,7 @@ different ``App`` instances and respective smtp servers. There is no
need for the ``app`` fixture to be aware of the ``smtp_connection``
parametrization because pytest will fully analyse the fixture dependency graph.
Note, that the ``app`` fixture has a scope of ``module`` and uses a
Note that the ``app`` fixture has a scope of ``module`` and uses a
module-scoped ``smtp_connection`` fixture. The example would still work if
``smtp_connection`` was cached on a ``session`` scope: it is fine for fixtures to use
"broader" scoped fixtures but not the other way round:
@@ -860,8 +863,8 @@ Let's run the tests in verbose mode and with looking at the print-output:
$ pytest -v -s test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
cachedir: .pytest_cache
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items

View File

@@ -7,9 +7,6 @@ Installation and Getting Started
**PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_
**Dependencies**: `py <https://pypi.org/project/py/>`_,
`colorama (Windows) <https://pypi.org/project/colorama/>`_,
**Documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
``pytest`` is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or library.
@@ -50,6 +47,7 @@ Thats it. You can now execute the test function:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

View File

@@ -72,8 +72,18 @@ to keep tests separate from actual application code (often a good idea)::
test_view.py
...
This way your tests can run easily against an installed version
of ``mypkg``.
This has the following benefits:
* Your tests can run against an installed version after executing ``pip install .``.
* Your tests can run against the local copy with an editable install after executing ``pip install --editable .``.
* If you don't have a ``setup.py`` file and are relying on the fact that Python by default puts the current
directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the
local copy directly, without using ``pip``.
.. note::
See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and
``python -m pytest``.
Note that using this scheme your test files must have **unique names**, because
``pytest`` will import them as *top-level* modules since there are no packages

View File

@@ -29,6 +29,7 @@ To execute it:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

View File

@@ -57,6 +57,7 @@ them in turn:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
@@ -108,6 +109,7 @@ Let's run this:
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
@@ -207,7 +209,7 @@ list:
$ pytest -q -rs test_strings.py
s [100%]
========================= short test summary info ==========================
SKIP [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
1 skipped in 0.12 seconds
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across

View File

@@ -0,0 +1,22 @@
Python 2.7 and 3.4 support plan
===============================
Python 2.7 EOL is fast approaching, with
upstream support `ending in 2020 <https://legacy.python.org/dev/peps/pep-0373/#id4>`__.
Python 3.4's last release is scheduled for
`March 2019 <https://www.python.org/dev/peps/pep-0429/#release-schedule>`__. pytest is one of
the participating projects of the https://python3statement.org.
We plan to drop support for Python 2.7 and 3.4 at the same time with the release of **pytest 5.0**,
scheduled to be released by **mid-2019**. Thanks to the `python_requires <https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires>`__ ``setuptools`` option,
Python 2.7 and Python 3.4 users using a modern ``pip`` version
will install the last compatible pytest ``4.X`` version automatically even if ``5.0`` or later
are available on PyPI.
During the period **from mid-2019 and 2020**, the pytest core team plans to make
bug-fix releases of the pytest ``4.X`` series by back-porting patches to the ``4.x-maintenance``
branch.
**After 2020**, the core team will no longer actively back port-patches, but the ``4.x-maintenance``
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.X`` releases **until mid-2020**.

View File

@@ -618,7 +618,6 @@ Session related reporting hooks:
.. autofunction:: pytest_terminal_summary
.. autofunction:: pytest_fixture_setup
.. autofunction:: pytest_fixture_post_finalizer
.. autofunction:: pytest_logwarning
.. autofunction:: pytest_warning_captured
And here is the central hook for reporting about
@@ -725,13 +724,6 @@ MarkGenerator
:members:
MarkInfo
~~~~~~~~
.. autoclass:: _pytest.mark.MarkInfo
:members:
Mark
~~~~
@@ -897,6 +889,12 @@ Here is a list of builtin configuration options that may be written in a ``pytes
file, usually located at the root of your repository. All options must be under a ``[pytest]`` section
(``[tool:pytest]`` for ``setup.cfg`` files).
.. warning::
Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg``
files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track
down problems.
When possible, it is recommended to use the latter files to hold your pytest configuration.
Configuration file options may be overwritten in the command-line by using ``-o/--override``, which can also be
passed multiple times. The expected format is ``name=value``. For example::

View File

@@ -1,3 +1,4 @@
pygments-pytest>=1.1.0
sphinx>=1.8.2
sphinxcontrib-trio
sphinx-removed-in>=0.1.3

View File

@@ -330,6 +330,7 @@ Running it with the report-on-xfail option gives this output:
example $ pytest -rx xfail_demo.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example, inifile:
collected 7 items

View File

@@ -42,6 +42,7 @@ Running this would result in a passed test except for the last
$ pytest test_tmp_path.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -104,6 +105,7 @@ Running this would result in a passed test except for the last
$ pytest test_tmpdir.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

View File

@@ -129,6 +129,7 @@ the ``self.db`` values in the traceback:
$ pytest test_unittest_db.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items

View File

@@ -147,20 +147,83 @@ Detailed summary report
.. versionadded:: 2.9
The ``-r`` flag can be used to display test results summary at the end of the test session,
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.
Example:
.. code-block:: python
# content of test_example.py
import pytest
@pytest.fixture
def error_fixture():
assert 0
def test_ok():
print("ok")
def test_fail():
assert 0
def test_error(error_fixture):
pass
def test_skip():
pytest.skip("skipping this test")
def test_xfail():
pytest.xfail("xfailing this test")
@pytest.mark.xfail(reason="always xfail")
def test_xpass():
pass
.. code-block:: pytest
$ pytest -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
collected 6 items
======================= no tests ran in 0.12 seconds =======================
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
XFAIL test_example.py::test_xfail
reason: xfailing this test
XPASS test_example.py::test_xpass always xfail
ERROR test_example.py::test_error
FAILED test_example.py::test_fail
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
@@ -182,10 +245,72 @@ More than one character can be used, so for example to only see failed and skipp
$ pytest -rfs
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
collected 6 items
======================= no tests ran in 0.12 seconds =======================
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
captured output:
.. code-block:: pytest
$ pytest -rpP
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 6 items
test_example.py .FEsxX [100%]
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
PASSED test_example.py::test_ok
================================== PASSES ==================================
_________________________________ test_ok __________________________________
--------------------------- Captured stdout call ---------------------------
ok
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
.. _pdb-option:
@@ -294,6 +419,20 @@ To set the name of the root test suite xml item, you can configure the ``junit_s
[pytest]
junit_suite_name = my_suite
.. versionadded:: 4.0
JUnit XML specification seems to indicate that ``"time"`` attribute
should report total test execution times, including setup and teardown
(`1 <http://windyroad.com.au/dl/Open%20Source/JUnit.xsd>`_, `2
<https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html>`_).
It is the default pytest behavior. To report just call durations
instead, configure the ``junit_duration_report`` option like this:
.. code-block:: ini
[pytest]
junit_duration_report = call
.. _record_property example:
record_property
@@ -483,14 +622,10 @@ Creating resultlog format files
.. deprecated:: 3.0
This option is rarely used and is scheduled for removal in 4.0.
This option is rarely used and is scheduled for removal in 5.0.
An alternative for users which still need similar functionality is to use the
`pytest-tap <https://pypi.org/project/pytest-tap/>`_ plugin which provides
a stream of test data.
If you have any concerns, please don't hesitate to
`open an issue <https://github.com/pytest-dev/pytest/issues>`_.
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
for more information.
To create plain-text machine-readable result files you can issue::
@@ -561,8 +696,25 @@ Running it will show that ``MyPlugin`` was added and its
hook was invoked::
$ python myinvoke.py
. [100%]*** test run reporting finishing
.FEsxX. [100%]*** test run reporting finishing
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
.. note::

View File

@@ -25,6 +25,7 @@ Running pytest now produces this output:
$ pytest test_show_warnings.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
@@ -155,7 +156,7 @@ DeprecationWarning and PendingDeprecationWarning
.. versionchanged:: 3.9
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from
user code and third-party libraries, as recommended by `PEP-0506 <https://www.python.org/dev/peps/pep-0565>`_.
user code and third-party libraries, as recommended by `PEP-0565 <https://www.python.org/dev/peps/pep-0565>`_.
This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed.
Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over

View File

@@ -413,6 +413,7 @@ additionally it is possible to copy examples for an example folder before runnin
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 2 items

View File

@@ -93,7 +93,15 @@ Remarks:
* It is possible for setup/teardown pairs to be invoked multiple times
per testing process.
* teardown functions are not called if the corresponding setup function existed
and failed/was skipped.
* Prior to pytest-4.2, xunit-style functions did not obey the scope rules of fixtures, so
it was possible, for example, for a ``setup_method`` to be called before a
session-scoped autouse fixture.
Now the xunit-style functions are integrated with the fixture mechanism and obey the proper
scope rules of fixtures involved in the call.
.. _`unittest.py module`: http://docs.python.org/library/unittest.html

View File

@@ -36,10 +36,11 @@ platforms = unix, linux, osx, cygwin, win32
zip_safe = no
packages =
_pytest
_pytest.assertion
_pytest._code
_pytest.mark
_pytest._io
_pytest.assertion
_pytest.config
_pytest.mark
py_modules = pytest
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*

View File

@@ -18,13 +18,12 @@ import six
from six import text_type
import _pytest
from _pytest._io.saferepr import saferepr
from _pytest.compat import _PY2
from _pytest.compat import _PY3
from _pytest.compat import PY35
from _pytest.compat import safe_str
builtin_repr = repr
if _PY3:
from traceback import format_exception_only
else:
@@ -144,7 +143,7 @@ class Frame(object):
def repr(self, object):
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
"""
return py.io.saferepr(object)
return saferepr(object)
def is_true(self, object):
return object
@@ -391,44 +390,85 @@ co_equal = compile(
)
@attr.s(repr=False)
class ExceptionInfo(object):
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
"""
_striptext = ""
_assert_start_repr = (
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
)
def __init__(self, tup=None, exprinfo=None):
import _pytest._code
_excinfo = attr.ib()
_striptext = attr.ib(default="")
_traceback = attr.ib(default=None)
if tup is None:
tup = sys.exc_info()
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], "msg", None)
if exprinfo is None:
exprinfo = py.io.saferepr(tup[1])
if exprinfo and exprinfo.startswith(self._assert_start_repr):
self._striptext = "AssertionError: "
self._excinfo = tup
#: the exception class
self.type = tup[0]
#: the exception instance
self.value = tup[1]
#: the exception raw traceback
self.tb = tup[2]
#: the exception type name
self.typename = self.type.__name__
#: the exception traceback (_pytest._code.Traceback instance)
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
@classmethod
def from_current(cls, exprinfo=None):
"""returns an ExceptionInfo matching the current traceback
.. warning::
Experimental API
:param exprinfo: a text string helping to determine if we should
strip ``AssertionError`` from the output, defaults
to the exception message/``__str__()``
"""
tup = sys.exc_info()
_striptext = ""
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], "msg", None)
if exprinfo is None:
exprinfo = saferepr(tup[1])
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
_striptext = "AssertionError: "
return cls(tup, _striptext)
@classmethod
def for_later(cls):
"""return an unfilled ExceptionInfo
"""
return cls(None)
@property
def type(self):
"""the exception class"""
return self._excinfo[0]
@property
def value(self):
"""the exception value"""
return self._excinfo[1]
@property
def tb(self):
"""the exception raw traceback"""
return self._excinfo[2]
@property
def typename(self):
"""the type name of the exception"""
return self.type.__name__
@property
def traceback(self):
"""the traceback"""
if self._traceback is None:
self._traceback = Traceback(self.tb, excinfo=ref(self))
return self._traceback
@traceback.setter
def traceback(self, value):
self._traceback = value
def __repr__(self):
try:
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
except AttributeError:
return "<ExceptionInfo uninitialized>"
if self._excinfo is None:
return "<ExceptionInfo for raises contextmanager>"
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
def exconly(self, tryshort=False):
""" return the exception as a string
@@ -516,13 +556,11 @@ class ExceptionInfo(object):
return fmt.repr_excinfo(self)
def __str__(self):
try:
entry = self.traceback[-1]
except AttributeError:
if self._excinfo is None:
return repr(self)
else:
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
def __unicode__(self):
entry = self.traceback[-1]
@@ -581,7 +619,7 @@ class FormattedExcinfo(object):
return source
def _saferepr(self, obj):
return py.io.saferepr(obj)
return saferepr(obj)
def repr_args(self, entry):
if self.funcargs:
@@ -908,8 +946,6 @@ class ReprEntryNative(TerminalRepr):
class ReprEntry(TerminalRepr):
localssep = "_ "
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
self.lines = lines
self.reprfuncargs = reprfuncargs
@@ -931,7 +967,6 @@ class ReprEntry(TerminalRepr):
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
if self.reprlocals:
# tw.sep(self.localssep, "Locals")
tw.line("")
self.reprlocals.toterminal(tw)
if self.reprfileloc:

View File

@@ -237,9 +237,7 @@ def getfslineno(obj):
def findsource(obj):
try:
sourcelines, lineno = inspect.findsource(obj)
except py.builtin._sysex:
raise
except: # noqa
except Exception:
return None, -1
source = Source()
source.lines = [line.rstrip() for line in sourcelines]

View File

View File

@@ -0,0 +1,72 @@
import sys
from six.moves import reprlib
class SafeRepr(reprlib.Repr):
"""subclass of repr.Repr that limits the resulting size of repr()
and includes information on exceptions raised during the call.
"""
def repr(self, x):
return self._callhelper(reprlib.Repr.repr, self, x)
def repr_unicode(self, x, level):
# Strictly speaking wrong on narrow builds
def repr(u):
if "'" not in u:
return u"'%s'" % u
elif '"' not in u:
return u'"%s"' % u
else:
return u"'%s'" % u.replace("'", r"\'")
s = repr(x[: self.maxstring])
if len(s) > self.maxstring:
i = max(0, (self.maxstring - 3) // 2)
j = max(0, self.maxstring - 3 - i)
s = repr(x[:i] + x[len(x) - j :])
s = s[:i] + "..." + s[len(s) - j :]
return s
def repr_instance(self, x, level):
return self._callhelper(repr, x)
def _callhelper(self, call, x, *args):
try:
# Try the vanilla repr and make sure that the result is a string
s = call(x, *args)
except Exception:
cls, e, tb = sys.exc_info()
exc_name = getattr(cls, "__name__", "unknown")
try:
exc_info = str(e)
except Exception:
exc_info = "unknown"
return '<[%s("%s") raised in repr()] %s object at 0x%x>' % (
exc_name,
exc_info,
x.__class__.__name__,
id(x),
)
else:
if len(s) > self.maxsize:
i = max(0, (self.maxsize - 3) // 2)
j = max(0, self.maxsize - 3 - i)
s = s[:i] + "..." + s[len(s) - j :]
return s
def saferepr(obj, maxsize=240):
"""return a size-limited safe repr-string for the given object.
Failing __repr__ functions of user instances will be represented
with a short exception info and 'saferepr' generally takes
care to never raise exceptions itself. This function is a wrapper
around the Repr/reprlib functionality of the standard 2.6 lib.
"""
# review exception handling
srepr = SafeRepr()
srepr.maxstring = maxsize
srepr.maxsize = maxsize
srepr.maxother = 160
return srepr.repr(obj)

View File

@@ -19,6 +19,7 @@ import atomicwrites
import py
import six
from _pytest._io.saferepr import saferepr
from _pytest.assertion import util
from _pytest.compat import spec_from_file_location
from _pytest.pathlib import fnmatch_ex
@@ -265,11 +266,11 @@ class AssertionRewritingHook(object):
def _warn_already_imported(self, name):
from _pytest.warning_types import PytestWarning
from _pytest.warnings import _issue_config_warning
from _pytest.warnings import _issue_warning_captured
_issue_config_warning(
_issue_warning_captured(
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
self.config,
self.config.hook,
stacklevel=5,
)
@@ -471,7 +472,7 @@ def _saferepr(obj):
JSON reprs.
"""
r = py.io.saferepr(obj)
r = saferepr(obj)
# only occurs in python2.x, repr must return text in python3+
if isinstance(r, bytes):
# Represent unprintable bytes as `\x##`
@@ -490,7 +491,7 @@ def _format_assertmsg(obj):
For strings this simply replaces newlines with '\n~' so that
util.format_explanation() will preserve them instead of escaping
newlines. For other objects py.io.saferepr() is used first.
newlines. For other objects saferepr() is used first.
"""
# reprlib appears to have a bug which means that if a string
@@ -499,7 +500,7 @@ def _format_assertmsg(obj):
# However in either case we want to preserve the newline.
replaces = [(u"\n", u"\n~"), (u"%", u"%%")]
if not isinstance(obj, six.string_types):
obj = py.io.saferepr(obj)
obj = saferepr(obj)
replaces.append((u"\\n", u"\n~"))
if isinstance(obj, bytes):
@@ -512,7 +513,13 @@ def _format_assertmsg(obj):
def _should_repr_global_name(obj):
return not hasattr(obj, "__name__") and not callable(obj)
if callable(obj):
return False
try:
return not hasattr(obj, "__name__")
except Exception:
return True
def _format_boolop(explanations, is_or):
@@ -659,7 +666,7 @@ class AssertionRewriter(ast.NodeVisitor):
# Insert some special imports at the top of the module but after any
# docstrings and __future__ imports.
aliases = [
ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
ast.alias(six.moves.builtins.__name__, "@py_builtins"),
ast.alias("_pytest.assertion.rewrite", "@pytest_ar"),
]
doc = getattr(mod, "docstring", None)
@@ -734,7 +741,7 @@ class AssertionRewriter(ast.NodeVisitor):
return ast.Name(name, ast.Load())
def display(self, expr):
"""Call py.io.saferepr on the expression."""
"""Call saferepr on the expression."""
return self.helper("saferepr", expr)
def helper(self, name, *args):
@@ -828,6 +835,13 @@ class AssertionRewriter(ast.NodeVisitor):
self.push_format_context()
# Rewrite assert into a bunch of statements.
top_condition, explanation = self.visit(assert_.test)
# If in a test module, check if directly asserting None, in order to warn [Issue #3191]
if self.module_path is not None:
self.statements.append(
self.warn_about_none_ast(
top_condition, module_path=self.module_path, lineno=assert_.lineno
)
)
# Create failure message.
body = self.on_failure
negation = ast.UnaryOp(ast.Not(), top_condition)
@@ -858,6 +872,33 @@ class AssertionRewriter(ast.NodeVisitor):
set_location(stmt, assert_.lineno, assert_.col_offset)
return self.statements
def warn_about_none_ast(self, node, module_path, lineno):
"""
Returns an AST issuing a warning if the value of node is `None`.
This is used to warn the user when asserting a function that asserts
internally already.
See issue #3191 for more details.
"""
# Using parse because it is different between py2 and py3.
AST_NONE = ast.parse("None").body[0].value
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
send_warning = ast.parse(
"""
from _pytest.warning_types import PytestWarning
from warnings import warn_explicit
warn_explicit(
PytestWarning('asserting the value None, please use "assert is None"'),
category=None,
filename={filename!r},
lineno={lineno},
)
""".format(
filename=module_path.strpath, lineno=lineno
)
).body
return ast.If(val_is_none, send_warning, [])
def visit_Name(self, name):
# Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable.

View File

@@ -5,11 +5,11 @@ from __future__ import print_function
import pprint
import py
import six
import _pytest._code
from ..compat import Sequence
from _pytest._io.saferepr import saferepr
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
@@ -105,8 +105,8 @@ except NameError:
def assertrepr_compare(config, op, left, right):
"""Return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width // 2))
right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
left_repr = saferepr(left, maxsize=int(width // 2))
right_repr = saferepr(right, maxsize=width - len(left_repr))
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
@@ -122,6 +122,12 @@ def assertrepr_compare(config, op, left, right):
def isset(x):
return isinstance(x, (set, frozenset))
def isdatacls(obj):
return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj):
return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj):
try:
iter(obj)
@@ -142,6 +148,11 @@ def assertrepr_compare(config, op, left, right):
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
type_fn = (isdatacls, isattrs)
explanation = _compare_eq_cls(left, right, verbose, type_fn)
elif verbose:
explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
if explanation is not None:
@@ -155,7 +166,7 @@ def assertrepr_compare(config, op, left, right):
explanation = [
u"(pytest_assertion plugin: representation of details failed. "
u"Probably an object has a faulty __repr__.)",
six.text_type(_pytest._code.ExceptionInfo()),
six.text_type(_pytest._code.ExceptionInfo.from_current()),
]
if not explanation:
@@ -227,6 +238,18 @@ def _diff_text(left, right, verbose=False):
return explanation
def _compare_eq_verbose(left, right):
keepends = True
left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends)
explanation = []
explanation += [u"-" + line for line in left_lines]
explanation += [u"+" + line for line in right_lines]
return explanation
def _compare_eq_iterable(left, right, verbose=False):
if not verbose:
return [u"Use -v to get the full diff"]
@@ -259,12 +282,12 @@ def _compare_eq_sequence(left, right, verbose=False):
if len(left) > len(right):
explanation += [
u"Left contains more items, first extra item: %s"
% py.io.saferepr(left[len(right)])
% saferepr(left[len(right)])
]
elif len(left) < len(right):
explanation += [
u"Right contains more items, first extra item: %s"
% py.io.saferepr(right[len(left)])
% saferepr(right[len(left)])
]
return explanation
@@ -276,11 +299,11 @@ def _compare_eq_set(left, right, verbose=False):
if diff_left:
explanation.append(u"Extra items in the left set:")
for item in diff_left:
explanation.append(py.io.saferepr(item))
explanation.append(saferepr(item))
if diff_right:
explanation.append(u"Extra items in the right set:")
for item in diff_right:
explanation.append(py.io.saferepr(item))
explanation.append(saferepr(item))
return explanation
@@ -297,9 +320,7 @@ def _compare_eq_dict(left, right, verbose=False):
if diff:
explanation += [u"Differing items:"]
for k in diff:
explanation += [
py.io.saferepr({k: left[k]}) + " != " + py.io.saferepr({k: right[k]})
]
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
extra_left = set(left) - set(right)
if extra_left:
explanation.append(u"Left contains more items:")
@@ -315,13 +336,45 @@ def _compare_eq_dict(left, right, verbose=False):
return explanation
def _compare_eq_cls(left, right, verbose, type_fns):
isdatacls, isattrs = type_fns
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
elif isattrs(left):
all_fields = left.__attrs_attrs__
fields_to_check = [field.name for field in all_fields if field.cmp]
same = []
diff = []
for field in fields_to_check:
if getattr(left, field) == getattr(right, field):
same.append(field)
else:
diff.append(field)
explanation = []
if same and verbose < 2:
explanation.append(u"Omitting %s identical items, use -vv to show" % len(same))
elif same:
explanation += [u"Matching attributes:"]
explanation += pprint.pformat(same).splitlines()
if diff:
explanation += [u"Differing attributes:"]
for field in diff:
explanation += [
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
]
return explanation
def _notin_text(term, text, verbose=False):
index = text.find(term)
head = text[:index]
tail = text[index + len(term) :]
correct_text = head + tail
diff = _diff_text(correct_text, text, verbose)
newdiff = [u"%s is contained here:" % py.io.saferepr(term, maxsize=42)]
newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)]
for line in diff:
if line.startswith(u"Skipping"):
continue

View File

@@ -33,6 +33,13 @@ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
"""
CACHEDIR_TAG_CONTENT = b"""\
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by pytest.
# For information about cache directory tags, see:
# http://www.bford.info/cachedir/spec.html
"""
@attr.s
class Cache(object):
@@ -52,12 +59,12 @@ class Cache(object):
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args):
from _pytest.warnings import _issue_config_warning
from _pytest.warnings import _issue_warning_captured
from _pytest.warning_types import PytestWarning
_issue_config_warning(
_issue_warning_captured(
PytestWarning(fmt.format(**args) if args else fmt),
self._config,
self._config.hook,
stacklevel=3,
)
@@ -140,6 +147,10 @@ class Cache(object):
msg = u"# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8")
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
if not cachedir_tag_path.is_file():
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
class LFPlugin(object):
""" Plugin which implements the --lf (run last-failing) option """

View File

@@ -773,9 +773,9 @@ def _py36_windowsconsoleio_workaround(stream):
f.line_buffering,
)
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, "rb")
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, "wb")
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, "wb")
sys.stdin = _reopen_stdio(sys.stdin, "rb")
sys.stdout = _reopen_stdio(sys.stdout, "wb")
sys.stderr = _reopen_stdio(sys.stderr, "wb")
def _attempt_to_close_capture_file(f):

View File

@@ -17,6 +17,7 @@ import six
from six import text_type
import _pytest
from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
@@ -45,11 +46,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
if _PY3:
from collections.abc import MutableMapping as MappingMixin
from collections.abc import Mapping, Sequence
from collections.abc import Iterable, Mapping, Sequence, Sized
else:
# those raise DeprecationWarnings in Python >=3.7
from collections import MutableMapping as MappingMixin # noqa
from collections import Mapping, Sequence # noqa
from collections import Iterable, Mapping, Sequence, Sized # noqa
if sys.version_info >= (3, 4):
@@ -182,6 +183,18 @@ def get_default_arg_names(function):
)
_non_printable_ascii_translate_table = {
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
}
_non_printable_ascii_translate_table.update(
{ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"}
)
def _translate_non_printable(s):
return s.translate(_non_printable_ascii_translate_table)
if _PY3:
STRING_TYPES = bytes, str
UNICODE_TYPES = six.text_type
@@ -221,9 +234,10 @@ if _PY3:
"""
if isinstance(val, bytes):
return _bytes_to_ascii(val)
ret = _bytes_to_ascii(val)
else:
return val.encode("unicode_escape").decode("ascii")
ret = val.encode("unicode_escape").decode("ascii")
return _translate_non_printable(ret)
else:
@@ -241,11 +255,12 @@ else:
"""
if isinstance(val, bytes):
try:
return val.encode("ascii")
ret = val.decode("ascii")
except UnicodeDecodeError:
return val.encode("string-escape")
ret = val.encode("string-escape").decode("ascii")
else:
return val.encode("unicode-escape")
ret = val.encode("unicode-escape").decode("ascii")
return _translate_non_printable(ret)
class _PytestWrapper(object):
@@ -280,7 +295,7 @@ def get_real_func(obj):
else:
raise ValueError(
("could not find real function of {start}\nstopped at {current}").format(
start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
start=saferepr(start_obj), current=saferepr(obj)
)
)
if isinstance(obj, functools.partial):
@@ -375,7 +390,6 @@ else:
COLLECT_FAKEMODULE_ATTRIBUTES = (
"Collector",
"Module",
"Generator",
"Function",
"Instance",
"Session",

View File

@@ -26,11 +26,14 @@ from .exceptions import PrintHelp
from .exceptions import UsageError
from .findpaths import determine_setup
from .findpaths import exists
from _pytest import deprecated
from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback
from _pytest.compat import lru_cache
from _pytest.compat import safe_str
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestWarning
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
@@ -173,12 +176,9 @@ def _prepareconfig(args=None, plugins=None):
elif isinstance(args, py.path.local):
args = [str(args)]
elif not isinstance(args, (tuple, list)):
if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args, posix=sys.platform != "win32")
from _pytest import deprecated
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args)))
warning = deprecated.MAIN_STR_ARGS
config = get_config()
pluginmanager = config.pluginmanager
try:
@@ -189,9 +189,9 @@ def _prepareconfig(args=None, plugins=None):
else:
pluginmanager.register(plugin)
if warning:
from _pytest.warnings import _issue_config_warning
from _pytest.warnings import _issue_warning_captured
_issue_config_warning(warning, config=config, stacklevel=4)
_issue_warning_captured(warning, hook=config.hook, stacklevel=4)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
@@ -245,14 +245,7 @@ class PytestPluginManager(PluginManager):
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
instead.
"""
warning = dict(
code="I2",
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
nodeid=None,
message="use pluginmanager.add_hookspecs instead of "
"deprecated addhooks() method.",
)
self._warn(warning)
warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
return self.add_hookspecs(module_or_class)
def parse_hookimpl_opts(self, plugin, name):
@@ -261,8 +254,8 @@ class PytestPluginManager(PluginManager):
# (see issue #1073)
if not name.startswith("pytest_"):
return
# ignore some historic special names which can not be hooks anyway
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
# ignore names which can not be hooks
if name == "pytest_plugins":
return
method = getattr(plugin, name)
@@ -275,10 +268,14 @@ class PytestPluginManager(PluginManager):
# collect unmarked hooks as long as they have the `pytest_' prefix
if opts is None and name.startswith("pytest_"):
opts = {}
if opts is not None:
# TODO: DeprecationWarning, people should use hookimpl
# https://github.com/pytest-dev/pytest/issues/4562
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
opts.setdefault(name, hasattr(method, name))
opts.setdefault(name, hasattr(method, name) or name in known_marks)
return opts
def parse_hookspec_opts(self, module_or_class, name):
@@ -287,19 +284,27 @@ class PytestPluginManager(PluginManager):
)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
# todo: deprecate hookspec hacks
# https://github.com/pytest-dev/pytest/issues/4562
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
opts = {
"firstresult": hasattr(method, "firstresult"),
"historic": hasattr(method, "historic"),
"firstresult": hasattr(method, "firstresult")
or "firstresult" in known_marks,
"historic": hasattr(method, "historic")
or "historic" in known_marks,
}
return opts
def register(self, plugin, name=None):
if name in ["pytest_catchlog", "pytest_capturelog"]:
self._warn(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
warnings.warn(
PytestWarning(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
)
)
)
return
@@ -336,14 +341,6 @@ class PytestPluginManager(PluginManager):
)
self._configured = True
def _warn(self, message):
kwargs = (
message
if isinstance(message, dict)
else {"code": "I1", "message": message, "fslocation": None, "nodeid": None}
)
self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
#
# internal API for local conftest plugin handling
#
@@ -440,14 +437,14 @@ class PytestPluginManager(PluginManager):
and not self._using_pyargs
):
from _pytest.deprecated import (
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
)
warnings.warn_explicit(
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
category=None,
filename=str(conftestpath),
lineno=0,
fail(
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
conftestpath, self._confcutdir
),
pytrace=False,
)
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
@@ -470,9 +467,20 @@ class PytestPluginManager(PluginManager):
#
def consider_preparse(self, args):
for opt1, opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
i = 0
n = len(args)
while i < n:
opt = args[i]
i += 1
if isinstance(opt, six.string_types):
if opt == "-p":
parg = args[i]
i += 1
elif opt.startswith("-p"):
parg = opt[2:]
else:
continue
self.consider_pluginarg(parg)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
@@ -507,7 +515,7 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, (six.text_type, str)), (
assert isinstance(modname, six.string_types), (
"module name as text required, got %r" % modname
)
modname = str(modname)
@@ -531,7 +539,13 @@ class PytestPluginManager(PluginManager):
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
except Skipped as e:
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning("skipped plugin %r: %s" % (modname, e.msg)),
self.hook,
stacklevel=1,
)
else:
mod = sys.modules[importspec]
self.register(mod, modname)
@@ -606,16 +620,9 @@ class Config(object):
self._override_ini = ()
self._opt2dest = {}
self._cleanup = []
self._warn = self.pluginmanager._warn
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
def do_setns(dic):
import pytest
setns(pytest, dic)
self.hook.pytest_namespace.call_historic(do_setns, {})
self.invocation_dir = py.path.local()
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
def add_cleanup(self, func):
@@ -637,36 +644,6 @@ class Config(object):
fin = self._cleanup.pop()
fin()
def warn(self, code, message, fslocation=None, nodeid=None):
"""
.. deprecated:: 3.8
Use :py:func:`warnings.warn` or :py:func:`warnings.warn_explicit` directly instead.
Generate a warning for this test session.
"""
from _pytest.warning_types import RemovedInPytest4Warning
if isinstance(fslocation, (tuple, list)) and len(fslocation) > 2:
filename, lineno = fslocation[:2]
else:
filename = "unknown file"
lineno = 0
msg = "config.warn has been deprecated, use warnings.warn instead"
if nodeid:
msg = "{}: {}".format(nodeid, msg)
warnings.warn_explicit(
RemovedInPytest4Warning(msg),
category=None,
filename=filename,
lineno=lineno,
)
self.hook.pytest_logwarning.call_historic(
kwargs=dict(
code=code, message=message, fslocation=fslocation, nodeid=nodeid
)
)
def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw
@@ -731,7 +708,6 @@ class Config(object):
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile
self.invocation_dir = py.path.local()
self._parser.addini("addopts", "extra command line options", "args")
self._parser.addini("minversion", "minimally required pytest version")
self._override_ini = ns.override_ini or ()
@@ -822,7 +798,15 @@ class Config(object):
if ns.help or ns.version:
# we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end
self._warn("could not load initial conftests (%s)\n" % e.path)
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning(
"could not load initial conftests: {}".format(e.path)
),
self.hook,
stacklevel=2,
)
else:
raise

View File

@@ -18,6 +18,8 @@ class Parser(object):
there's an error processing the command line arguments.
"""
prog = None
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
self._groups = []
@@ -82,7 +84,7 @@ class Parser(object):
def _getparser(self):
from _pytest._argcomplete import filescompleter
optparser = MyOptionParser(self, self.extra_info)
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@@ -319,12 +321,13 @@ class OptionGroup(object):
class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser, extra_info=None):
def __init__(self, parser, extra_info=None, prog=None):
if not extra_info:
extra_info = {}
self._parser = parser
argparse.ArgumentParser.__init__(
self,
prog=prog,
usage=parser._usage,
add_help=False,
formatter_class=DropShorterLongHelpFormatter,

View File

@@ -3,6 +3,7 @@ import os
import py
from .exceptions import UsageError
from _pytest.outcomes import fail
def exists(path, ignore=EnvironmentError):
@@ -34,15 +35,10 @@ def getcfg(args, config=None):
iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and config is not None:
from _pytest.warnings import _issue_config_warning
from _pytest.warning_types import RemovedInPytest4Warning
_issue_config_warning(
RemovedInPytest4Warning(
CFG_PYTEST_SECTION.format(filename=inibasename)
),
config=config,
stacklevel=2,
fail(
CFG_PYTEST_SECTION.format(filename=inibasename),
pytrace=False,
)
return base, p, iniconfig["pytest"]
if (
@@ -112,40 +108,41 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
inicfg = iniconfig[section]
if is_cfg_file and section == "pytest" and config is not None:
from _pytest.deprecated import CFG_PYTEST_SECTION
from _pytest.warnings import _issue_config_warning
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once
# the deprecation expires.
_issue_config_warning(
CFG_PYTEST_SECTION.format(filename=str(inifile)),
config,
stacklevel=2,
fail(
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
)
break
except KeyError:
inicfg = None
rootdir = get_common_ancestor(dirs)
if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
if rootdir is None:
for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists():
if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True):
if possible_rootdir.join("setup.py").exists():
rootdir = possible_rootdir
break
else:
rootdir, inifile, inicfg = getcfg(dirs, config=config)
if dirs != [ancestor]:
rootdir, inifile, inicfg = getcfg(dirs, config=config)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
if config is not None:
cwd = config.invocation_dir
else:
cwd = py.path.local()
rootdir = get_common_ancestor([cwd, ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
if is_fs_root:
rootdir = ancestor
if rootdir_cmd_arg:
rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg))
if not os.path.isdir(str(rootdir_abs_path)):
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
if not rootdir.isdir():
raise UsageError(
"Directory '{}' not found. Check your '--rootdir' option.".format(
rootdir_abs_path
rootdir
)
)
rootdir = rootdir_abs_path
return rootdir, inifile, inicfg or {}

View File

@@ -77,18 +77,21 @@ class pytestPDB(object):
_saved = []
@classmethod
def set_trace(cls, set_break=True):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
def _init_pdb(cls, *args, **kwargs):
""" Initialize PDB debugging, dropping any IO capturing. """
import _pytest.config
frame = sys._getframe().f_back
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspend_global_capture(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
if capman and capman.is_globally_capturing():
# Handle header similar to pdb.set_trace in py37+.
header = kwargs.pop("header", None)
if header is not None:
tw.sep(">", header)
elif capman and capman.is_globally_capturing():
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else:
tw.sep(">", "PDB set_trace")
@@ -115,6 +118,10 @@ class pytestPDB(object):
do_c = do_cont = do_continue
def set_quit(self):
super(_PdbWrapper, self).set_quit()
outcomes.exit("Quitting debugger")
def setup(self, f, tb):
"""Suspend on setup().
@@ -129,13 +136,18 @@ class pytestPDB(object):
self._pytest_capman.suspend_global_capture(in_=True)
return ret
_pdb = _PdbWrapper()
_pdb = _PdbWrapper(**kwargs)
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
else:
_pdb = cls._pdb_cls()
_pdb = cls._pdb_cls(**kwargs)
return _pdb
if set_break:
_pdb.set_trace(frame)
@classmethod
def set_trace(cls, *args, **kwargs):
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
frame = sys._getframe().f_back
_pdb = cls._init_pdb(*args, **kwargs)
_pdb.set_trace(frame)
class PdbInvoke(object):
@@ -161,9 +173,9 @@ class PdbTrace(object):
def _test_pytest_function(pyfuncitem):
pytestPDB.set_trace(set_break=False)
_pdb = pytestPDB._init_pdb()
testfunction = pyfuncitem.obj
pyfuncitem.obj = pdb.runcall
pyfuncitem.obj = _pdb.runcall
if pyfuncitem._isyieldedfunction():
arg_list = list(pyfuncitem._args)
arg_list.insert(0, testfunction)
@@ -202,8 +214,7 @@ def _enter_pdb(node, excinfo, rep):
tw.sep(">", "entering PDB")
tb = _postmortem_traceback(excinfo)
rep._pdbshown = True
if post_mortem(tb):
outcomes.exit("Quitting debugger")
post_mortem(tb)
return rep
@@ -234,4 +245,5 @@ def post_mortem(t):
p = Pdb()
p.reset()
p.interaction(None, t)
return p.quitting
if p.quitting:
outcomes.exit("Quitting debugger")

View File

@@ -14,66 +14,38 @@ from __future__ import print_function
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warning_types import UnformattedWarning
MAIN_STR_ARGS = RemovedInPytest4Warning(
"passing a string to pytest.main() is deprecated, "
"pass a list of arguments instead."
)
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
YIELD_TESTS = RemovedInPytest4Warning(
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
)
CACHED_SETUP = RemovedInPytest4Warning(
"cached_setup is deprecated and will be removed in a future release. "
"Use standard fixture functions instead."
)
COMPAT_PROPERTY = UnformattedWarning(
RemovedInPytest4Warning,
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
)
CUSTOM_CLASS = UnformattedWarning(
RemovedInPytest4Warning,
'use of special named "{name}" objects in collectors of type "{type_name}" to '
"customize the created nodes is deprecated. "
"Use pytest_pycollect_makeitem(...) to create custom "
"collection nodes instead.",
)
FUNCARG_PREFIX = UnformattedWarning(
RemovedInPytest4Warning,
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
"and scheduled to be removed in pytest 4.0. "
"Please remove the prefix and use the @pytest.fixture decorator instead.",
)
FIXTURE_FUNCTION_CALL = UnformattedWarning(
RemovedInPytest4Warning,
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
"are created automatically when test functions request them as parameters. "
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
FIXTURE_FUNCTION_CALL = (
'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
"but are created automatically when test functions request them as parameters.\n"
"See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n"
"https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code."
)
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
"'request' is a reserved name for fixtures and will raise an error in future versions"
)
CFG_PYTEST_SECTION = UnformattedWarning(
RemovedInPytest4Warning,
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
)
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
GETFUNCARGVALUE = RemovedInPytest4Warning(
"getfuncargvalue is deprecated, use getfixturevalue"
)
RESULT_LOG = RemovedInPytest4Warning(
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"The 'message' parameter is deprecated.\n"
"(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
"if you have concerns about removal of this parameter."
)
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
)
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
@@ -82,42 +54,36 @@ MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
)
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"Applying marks directly to parameters is deprecated,"
" please use pytest.param(..., marks=...) instead.\n"
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
RAISES_EXEC = PytestDeprecationWarning(
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
)
WARNS_EXEC = PytestDeprecationWarning(
"warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
)
NODE_WARN = RemovedInPytest4Warning(
"Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead."
)
RECORD_XML_PROPERTY = RemovedInPytest4Warning(
'Fixture renamed from "record_xml_property" to "record_property" as user '
"properties are now available to all reporters.\n"
'"record_xml_property" is now deprecated.'
)
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
"pycollector makeitem was removed as it is an accidentially leaked internal api"
)
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
"Please use Metafunc.parametrize instead."
)
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
"Defining pytest_plugins in a non-top-level conftest is deprecated, "
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
"because it affects the entire directory tree in a non-explicit way.\n"
"Please move it to the top level conftest file instead."
" {}\n"
"Please move it to a top level conftest file at the rootdir:\n"
" {}\n"
"For more information, visit:\n"
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
)
PYTEST_NAMESPACE = RemovedInPytest4Warning(
"pytest_namespace is deprecated and will be removed soon"
PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning(
"the `pytest.config` global is deprecated. Please use `request.config` "
"or `pytest_configure` (if you're a pytest plugin) instead."
)
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
"please use the tmp_path fixture or tmp_path_factory.mktemp"
)
PYTEST_LOGWARNING = PytestDeprecationWarning(
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
"please use pytest_warning_captured instead"
)

View File

@@ -3,17 +3,19 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import inspect
import platform
import sys
import traceback
from contextlib import contextmanager
import pytest
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
from _pytest.compat import safe_getattr
from _pytest.fixtures import FixtureRequest
DOCTEST_REPORT_CHOICE_NONE = "none"
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
@@ -346,10 +348,61 @@ def _check_all_skipped(test):
pytest.skip("all tests skipped by +SKIP option")
def _is_mocked(obj):
"""
returns if a object is possibly a mock object by checking the existence of a highly improbable attribute
"""
return (
safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
is not None
)
@contextmanager
def _patch_unwrap_mock_aware():
"""
contextmanager which replaces ``inspect.unwrap`` with a version
that's aware of mock objects and doesn't recurse on them
"""
real_unwrap = getattr(inspect, "unwrap", None)
if real_unwrap is None:
yield
else:
def _mock_aware_unwrap(obj, stop=None):
if stop is None:
return real_unwrap(obj, stop=_is_mocked)
else:
return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj))
inspect.unwrap = _mock_aware_unwrap
try:
yield
finally:
inspect.unwrap = real_unwrap
class DoctestModule(pytest.Module):
def collect(self):
import doctest
class MockAwareDocTestFinder(doctest.DocTestFinder):
"""
a hackish doctest finder that overrides stdlib internals to fix a stdlib bug
https://github.com/pytest-dev/pytest/issues/3456
https://bugs.python.org/issue25532
"""
def _find(self, tests, obj, name, module, source_lines, globs, seen):
if _is_mocked(obj):
return
with _patch_unwrap_mock_aware():
doctest.DocTestFinder._find(
self, tests, obj, name, module, source_lines, globs, seen
)
if self.fspath.basename == "conftest.py":
module = self.config.pluginmanager._importconftest(self.fspath)
else:
@@ -361,7 +414,7 @@ class DoctestModule(pytest.Module):
else:
raise
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
finder = MockAwareDocTestFinder()
optionflags = get_optionflags(self)
runner = _get_runner(
verbose=0,

View File

@@ -14,10 +14,10 @@ import attr
import py
import six
from more_itertools import flatten
from py._code.code import FormattedExcinfo
import _pytest
from _pytest import nodes
from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
@@ -38,8 +38,6 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
@attr.s(frozen=True)
class PseudoFixtureDef(object):
@@ -309,8 +307,8 @@ class FuncFixtureInfo(object):
# fixture names specified via usefixtures and via autouse=True in fixture
# definitions.
initialnames = attr.ib(type=tuple)
names_closure = attr.ib() # type: List[str]
name2fixturedefs = attr.ib() # type: List[str, List[FixtureDef]]
names_closure = attr.ib() # List[str]
name2fixturedefs = attr.ib() # List[str, List[FixtureDef]]
def prune_dependency_tree(self):
"""Recompute names_closure from initialnames and name2fixturedefs
@@ -469,43 +467,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
if argname not in item.funcargs:
item.funcargs[argname] = self.getfixturevalue(argname)
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" (deprecated) Return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource. With pytest-2.3 you often
do not need ``cached_setup()`` as you can directly declare a scope
on a fixture function and register a finalizer through
``request.addfinalizer()``.
:arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource.
:arg scope: a string value out of ``function``, ``class``, ``module``
or ``session`` indicating the caching lifecycle of the resource.
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
from _pytest.deprecated import CACHED_SETUP
warnings.warn(CACHED_SETUP, stacklevel=2)
if not hasattr(self.config, "_setupcache"):
self.config._setupcache = {} # XXX weakref?
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
self._check_scope(self.fixturename, self.scope, scope)
val = setup()
cache[cachekey] = val
if teardown is not None:
def finalizer():
del cache[cachekey]
teardown(val)
self._addfinalizer(finalizer, scope=scope)
return val
def getfixturevalue(self, argname):
""" Dynamically run a named fixture function.
@@ -605,8 +566,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
)
fail(msg, pytrace=False)
else:
# indices might not be set if old-style metafunc.addcall() was used
param_index = funcitem.callspec.indices.get(argname, 0)
param_index = funcitem.callspec.indices[argname]
# if a parametrize invocation set a scope it will override
# the static scope defined with the fixture function
paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
@@ -700,12 +660,6 @@ class SubRequest(FixtureRequest):
self._fixturedef.addfinalizer(finalizer)
class ScopeMismatchError(Exception):
""" A fixture function tries to use a different fixture function which
which has a lower scope (e.g. a Session one calls a function one)
"""
scopes = "session package module class function".split()
scopenum_function = scopes.index("function")
@@ -982,34 +936,17 @@ def _ensure_immutable_ids(ids):
return tuple(ids)
def wrap_function_to_warning_if_called_directly(function, fixture_marker):
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of
used as an argument in a test function.
def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
"""Wrap the given fixture function so we can raise an error about it being called directly,
instead of used as an argument in a test function.
"""
is_yield_function = is_generator(function)
warning = FIXTURE_FUNCTION_CALL.format(
message = FIXTURE_FUNCTION_CALL.format(
name=fixture_marker.name or function.__name__
)
if is_yield_function:
@functools.wraps(function)
def result(*args, **kwargs):
__tracebackhide__ = True
warnings.warn(warning, stacklevel=3)
for x in function(*args, **kwargs):
yield x
else:
@functools.wraps(function)
def result(*args, **kwargs):
__tracebackhide__ = True
warnings.warn(warning, stacklevel=3)
return function(*args, **kwargs)
if six.PY2:
result.__wrapped__ = function
@six.wraps(function)
def result(*args, **kwargs):
fail(message, pytrace=False)
# keep reference to the original function in our own custom attribute so we don't unwrap
# further than this point and lose useful wrappings like @mock.patch (#3774)
@@ -1035,7 +972,7 @@ class FixtureFunctionMarker(object):
"fixture is being applied more than once to the same function"
)
function = wrap_function_to_warning_if_called_directly(function, self)
function = wrap_function_to_error_out_if_called_directly(function, self)
name = self.name or function.__name__
if name == "request":
@@ -1155,7 +1092,6 @@ class FixtureManager(object):
by a lookup of their FuncFixtureInfo.
"""
_argprefix = "pytest_funcarg__"
FixtureLookupError = FixtureLookupError
FixtureLookupErrorRepr = FixtureLookupErrorRepr
@@ -1265,19 +1201,20 @@ class FixtureManager(object):
if faclist:
fixturedef = faclist[-1]
if fixturedef.params is not None:
parametrize_func = getattr(metafunc.function, "parametrize", None)
if parametrize_func is not None:
parametrize_func = parametrize_func.combined
func_params = getattr(parametrize_func, "args", [[None]])
func_kwargs = getattr(parametrize_func, "kwargs", {})
# skip directly parametrized arguments
if "argnames" in func_kwargs:
argnames = parametrize_func.kwargs["argnames"]
markers = list(metafunc.definition.iter_markers("parametrize"))
for parametrize_mark in markers:
if "argnames" in parametrize_mark.kwargs:
argnames = parametrize_mark.kwargs["argnames"]
else:
argnames = parametrize_mark.args[0]
if not isinstance(argnames, (tuple, list)):
argnames = [
x.strip() for x in argnames.split(",") if x.strip()
]
if argname in argnames:
break
else:
argnames = func_params[0]
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if argname not in func_params and argname not in argnames:
metafunc.parametrize(
argname,
fixturedef.params,
@@ -1293,8 +1230,6 @@ class FixtureManager(object):
items[:] = reorder_items(items)
def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
from _pytest import deprecated
if nodeid is not NOTSET:
holderobj = node_or_obj
else:
@@ -1303,44 +1238,20 @@ class FixtureManager(object):
if holderobj in self._holderobjseen:
return
from _pytest.nodes import _CompatProperty
self._holderobjseen.add(holderobj)
autousenames = []
for name in dir(holderobj):
# The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions.
maybe_property = safe_getattr(type(holderobj), name, None)
if isinstance(maybe_property, _CompatProperty):
# deprecated
continue
obj = safe_getattr(holderobj, name, None)
marker = getfixturemarker(obj)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked
if marker is None:
if not name.startswith(self._argprefix):
continue
if not callable(obj):
continue
marker = defaultfuncargprefixmarker
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
deprecated.FUNCARG_PREFIX.format(name=name),
category=None,
filename=str(filename),
lineno=lineno + 1,
)
name = name[len(self._argprefix) :]
elif not isinstance(marker, FixtureFunctionMarker):
if not isinstance(marker, FixtureFunctionMarker):
# magic globals with __getattr__ might have got us a wrong
# fixture attribute
continue
else:
if marker.name:
name = marker.name
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)
if marker.name:
name = marker.name
# during fixture definition we wrap the original fixture function
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning

View File

@@ -1,7 +1,7 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from pluggy import HookspecMarker
from .deprecated import PYTEST_NAMESPACE
from _pytest.deprecated import PYTEST_LOGWARNING
hookspec = HookspecMarker("pytest")
@@ -24,32 +24,6 @@ def pytest_addhooks(pluginmanager):
"""
@hookspec(historic=True, warn_on_impl=PYTEST_NAMESPACE)
def pytest_namespace():
"""
return dict of name->object to be made globally available in
the pytest namespace.
This hook is called at plugin registration time.
.. note::
This hook is incompatible with ``hookwrapper=True``.
.. warning::
This hook has been **deprecated** and will be removed in pytest 4.0.
Plugins whose users depend on the current namespace functionality should prepare to migrate to a
namespace they actually own.
To support the migration it's suggested to trigger ``DeprecationWarnings`` for objects they put into the
pytest namespace.
A stopgap measure to avoid the warning is to monkeypatch the ``pytest`` module, but just as the
``pytest_namespace`` hook this should be seen as a temporary measure to be removed in future versions after
an appropriate transition period.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered.
@@ -507,24 +481,27 @@ def pytest_report_collectionfinish(config, startdir, items):
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
def pytest_report_teststatus(report, config):
""" return result-category, shortletter and verbose word for reporting.
:param _pytest.config.Config config: pytest config object
Stops at first non-None result, see :ref:`firstresult` """
def pytest_terminal_summary(terminalreporter, exitstatus):
def pytest_terminal_summary(terminalreporter, exitstatus, config):
"""Add a section to terminal summary reporting.
:param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object
:param int exitstatus: the exit status that will be reported back to the OS
:param _pytest.config.Config config: pytest config object
.. versionadded:: 3.5
.. versionadded:: 4.2
The ``config`` parameter.
"""
@hookspec(historic=True)
@hookspec(historic=True, warn_on_impl=PYTEST_LOGWARNING)
def pytest_logwarning(message, code, nodeid, fslocation):
"""
.. deprecated:: 3.8

View File

@@ -19,6 +19,7 @@ import sys
import time
import py
import six
import pytest
from _pytest import nodes
@@ -27,10 +28,6 @@ from _pytest.config import filename_arg
# Python 2.X and 3.X compatibility
if sys.version_info[0] < 3:
from codecs import open
else:
unichr = chr
unicode = str
long = int
class Junit(py.xml.Namespace):
@@ -45,12 +42,12 @@ class Junit(py.xml.Namespace):
_legal_chars = (0x09, 0x0A, 0x0D)
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
_legal_xml_re = [
unicode("%s-%s") % (unichr(low), unichr(high))
u"%s-%s" % (six.unichr(low), six.unichr(high))
for (low, high) in _legal_ranges
if low < sys.maxunicode
]
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(unicode("[^%s]") % unicode("").join(_legal_xml_re))
_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re))
del _legal_chars
del _legal_ranges
del _legal_xml_re
@@ -62,19 +59,41 @@ def bin_xml_escape(arg):
def repl(matchobj):
i = ord(matchobj.group())
if i <= 0xFF:
return unicode("#x%02X") % i
return u"#x%02X" % i
else:
return unicode("#x%04X") % i
return u"#x%04X" % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
def merge_family(left, right):
result = {}
for kl, vl in left.items():
for kr, vr in right.items():
if not isinstance(vl, list):
raise TypeError(type(vl))
result[kl] = vl + vr
left.update(result)
families = {}
families["_base"] = {"testcase": ["classname", "name"]}
families["_base_legacy"] = {"testcase": ["file", "line", "url"]}
# xUnit 1.x inherits legacy attributes
families["xunit1"] = families["_base"].copy()
merge_family(families["xunit1"], families["_base_legacy"])
# xUnit 2.x uses strict base attributes
families["xunit2"] = families["_base"]
class _NodeReporter(object):
def __init__(self, nodeid, xml):
self.id = nodeid
self.xml = xml
self.add_stats = self.xml.add_stats
self.family = self.xml.family
self.duration = 0
self.properties = []
self.nodes = []
@@ -122,8 +141,20 @@ class _NodeReporter(object):
self.attrs = attrs
self.attrs.update(existing_attrs) # restore any user-defined attributes
# Preserve legacy testcase behavior
if self.family == "xunit1":
return
# Filter out attributes not permitted by this test family.
# Including custom attributes because they are not valid here.
temp_attrs = {}
for key in self.attrs.keys():
if key in families[self.family]["testcase"]:
temp_attrs[key] = self.attrs[key]
self.attrs = temp_attrs
def to_xml(self):
testcase = Junit.testcase(time=self.duration, **self.attrs)
testcase = Junit.testcase(time="%.3f" % self.duration, **self.attrs)
testcase.append(self.make_properties_node())
for node in self.nodes:
testcase.append(node)
@@ -194,7 +225,7 @@ class _NodeReporter(object):
else:
if hasattr(report.longrepr, "reprcrash"):
message = report.longrepr.reprcrash.message
elif isinstance(report.longrepr, (unicode, str)):
elif isinstance(report.longrepr, six.string_types):
message = report.longrepr
else:
message = str(report.longrepr)
@@ -213,7 +244,7 @@ class _NodeReporter(object):
self._add_simple(Junit.skipped, "collection skipped", report.longrepr)
def append_error(self, report):
if getattr(report, "when", None) == "teardown":
if report.when == "teardown":
msg = "test teardown failure"
else:
msg = "test setup failure"
@@ -263,16 +294,6 @@ def record_property(request):
return append_property
@pytest.fixture
def record_xml_property(record_property, request):
"""(Deprecated) use record_property."""
from _pytest import deprecated
request.node.warn(deprecated.RECORD_XML_PROPERTY)
return record_property
@pytest.fixture
def record_xml_attribute(request):
"""Add extra xml attributes to the tag for the calling test.
@@ -282,16 +303,26 @@ def record_xml_attribute(request):
from _pytest.warning_types import PytestWarning
request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
# Declare noop
def add_attr_noop(name, value):
pass
attr_func = add_attr_noop
xml = getattr(request.config, "_xml", None)
if xml is not None:
if xml is not None and xml.family != "xunit1":
request.node.warn(
PytestWarning(
"record_xml_attribute is incompatible with junit_family: "
"%s (use: legacy|xunit1)" % xml.family
)
)
elif xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)
return node_reporter.add_attribute
else:
attr_func = node_reporter.add_attribute
def add_attr_noop(name, value):
pass
return add_attr_noop
return attr_func
def pytest_addoption(parser):
@@ -323,6 +354,16 @@ def pytest_addoption(parser):
"one of no|system-out|system-err",
default="no",
) # choices=['no', 'stdout', 'stderr'])
parser.addini(
"junit_duration_report",
"Duration time to report: one of total|call",
default="total",
) # choices=['total', 'call'])
parser.addini(
"junit_family",
"Emit XML for schema: one of legacy|xunit1|xunit2",
default="xunit1",
)
def pytest_configure(config):
@@ -334,6 +375,8 @@ def pytest_configure(config):
config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"),
config.getini("junit_duration_report"),
config.getini("junit_family"),
)
config.pluginmanager.register(config._xml)
@@ -361,12 +404,22 @@ def mangle_test_address(address):
class LogXML(object):
def __init__(self, logfile, prefix, suite_name="pytest", logging="no"):
def __init__(
self,
logfile,
prefix,
suite_name="pytest",
logging="no",
report_duration="total",
family="xunit1",
):
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix
self.suite_name = suite_name
self.logging = logging
self.report_duration = report_duration
self.family = family
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = []
@@ -375,6 +428,10 @@ class LogXML(object):
self.open_reports = []
self.cnt_double_fail_tests = 0
# Replaces convenience family with real family
if self.family == "legacy":
self.family = "xunit1"
def finalize(self, report):
nodeid = getattr(report, "nodeid", report)
# local hack to handle xdist report order
@@ -500,8 +557,9 @@ class LogXML(object):
"""accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created.
"""
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
if self.report_duration == "total" or report.when == self.report_duration:
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
def pytest_collectreport(self, report):
if not report.passed:
@@ -543,7 +601,7 @@ class LogXML(object):
name=self.suite_name,
errors=self.stats["error"],
failures=self.stats["failure"],
skips=self.stats["skipped"],
skipped=self.stats["skipped"],
tests=numtests,
time="%.3f" % suite_time_delta,
).unicode(indent=0)

View File

@@ -217,7 +217,7 @@ class LogCaptureFixture(object):
"""Creates a new funcarg."""
self._item = item
# dict of log name -> log level
self._initial_log_levels = {} # type: Dict[str, int]
self._initial_log_levels = {} # Dict[str, int]
def _finalize(self):
"""Finalizes the fixture.

View File

@@ -8,6 +8,7 @@ import functools
import os
import pkgutil
import sys
import warnings
import attr
import py
@@ -18,6 +19,7 @@ from _pytest import nodes
from _pytest.config import directory_arg
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
@@ -167,8 +169,24 @@ def pytest_addoption(parser):
)
class _ConfigDeprecated(object):
def __init__(self, config):
self.__dict__["_config"] = config
def __getattr__(self, attr):
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
return getattr(self._config, attr)
def __setattr__(self, attr, val):
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
return setattr(self._config, attr, val)
def __repr__(self):
return "{}({!r})".format(type(self).__name__, self._config)
def pytest_configure(config):
__import__("pytest").config = config # compatibility
__import__("pytest").config = _ConfigDeprecated(config) # compatibility
def wrap_session(config, doit):
@@ -187,8 +205,8 @@ def wrap_session(config, doit):
raise
except Failed:
session.exitstatus = EXIT_TESTSFAILED
except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo()
except (KeyboardInterrupt, exit.Exception):
excinfo = _pytest._code.ExceptionInfo.from_current()
exitstatus = EXIT_INTERRUPTED
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
@@ -197,7 +215,7 @@ def wrap_session(config, doit):
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = exitstatus
except: # noqa
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):

View File

@@ -11,19 +11,10 @@ from .structures import Mark
from .structures import MARK_GEN
from .structures import MarkDecorator
from .structures import MarkGenerator
from .structures import MarkInfo
from .structures import ParameterSet
from .structures import transfer_markers
from _pytest.config import UsageError
__all__ = [
"Mark",
"MarkInfo",
"MarkDecorator",
"MarkGenerator",
"transfer_markers",
"get_empty_parameterset_mark",
]
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
def param(*values, **kw):

View File

@@ -45,13 +45,14 @@ class KeywordMapping(object):
mapped_names.add(item.name)
# Add the names added as extra keywords to current or parent items
for name in item.listextrakeywords():
mapped_names.add(name)
mapped_names.update(item.listextrakeywords())
# Add the names attached to the current function through direct assignment
if hasattr(item, "function"):
for name in item.function.__dict__:
mapped_names.add(name)
mapped_names.update(item.function.__dict__)
# add the markers to the keywords as we no longer handle them correctly
mapped_names.update(mark.name for mark in item.iter_markers())
return cls(mapped_names)

View File

@@ -1,17 +1,15 @@
import inspect
import warnings
from collections import namedtuple
from functools import reduce
from operator import attrgetter
import attr
from six.moves import map
import six
from ..compat import ascii_escaped
from ..compat import getfslineno
from ..compat import MappingMixin
from ..compat import NOTSET
from ..deprecated import MARK_INFO_ATTRIBUTE
from ..deprecated import MARK_PARAMETERSET_UNPACKING
from _pytest.outcomes import fail
@@ -70,46 +68,33 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
else:
assert isinstance(marks, (tuple, list, set))
def param_extract_id(id=None):
return id
id_ = param_extract_id(**kw)
id_ = kw.pop("id", None)
if id_ is not None:
if not isinstance(id_, six.string_types):
raise TypeError(
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
)
id_ = ascii_escaped(id_)
return cls(values, marks, id_)
@classmethod
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
def extract_from(cls, parameterset, force_tuple=False):
"""
:param parameterset:
a legacy style parameterset that may or may not be a tuple,
and may or may not be wrapped into a mess of mark objects
:param legacy_force_tuple:
:param force_tuple:
enforce tuple wrapping so single argument tuple values
don't get decomposed and break tests
:param belonging_definition: the item that we will be extracting the parameters from.
"""
if isinstance(parameterset, cls):
return parameterset
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
if force_tuple:
return cls.param(parameterset)
newmarks = []
argval = parameterset
while isinstance(argval, MarkDecorator):
newmarks.append(
MarkDecorator(Mark(argval.markname, argval.args[:-1], argval.kwargs))
)
argval = argval.args[-1]
assert not isinstance(argval, ParameterSet)
if legacy_force_tuple:
argval = (argval,)
if newmarks and belonging_definition is not None:
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
else:
return cls(parameterset, marks=[], id=None)
@classmethod
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
@@ -119,12 +104,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
else:
force_tuple = False
parameters = [
ParameterSet.extract_from(
x,
legacy_force_tuple=force_tuple,
belonging_definition=function_definition,
)
for x in argvalues
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
]
del argvalues
@@ -132,11 +112,21 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
# check all parameter sets have the correct number of values
for param in parameters:
if len(param.values) != len(argnames):
raise ValueError(
'In "parametrize" the number of values ({}) must be '
"equal to the number of names ({})".format(
param.values, argnames
)
msg = (
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
" {names}\n"
"must be equal to the number of values ({values_len}):\n"
" {values}"
)
fail(
msg.format(
nodeid=function_definition.nodeid,
values=param.values,
names=argnames,
names_len=len(argnames),
values_len=len(param.values),
),
pytrace=False,
)
else:
# empty parameter set (likely computed at runtime): create a single
@@ -153,9 +143,9 @@ class Mark(object):
#: name of the mark
name = attr.ib(type=str)
#: positional arguments of the mark decorator
args = attr.ib() # type: List[object]
args = attr.ib() # List[object]
#: keyword arguments of the mark decorator
kwargs = attr.ib() # type: Dict[str, object]
kwargs = attr.ib() # Dict[str, object]
def combined_with(self, other):
"""
@@ -240,11 +230,7 @@ class MarkDecorator(object):
func = args[0]
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
store_mark(func, self.mark)
else:
store_legacy_markinfo(func, self.mark)
store_mark(func, self.mark)
store_mark(func, self.mark)
return func
return self.with_args(*args, **kwargs)
@@ -266,7 +252,13 @@ def normalize_mark_list(mark_list):
:type mark_list: List[Union[Mark, Markdecorator]]
:rtype: List[Mark]
"""
return [getattr(mark, "mark", mark) for mark in mark_list] # unpack MarkDecorator
extracted = [
getattr(mark, "mark", mark) for mark in mark_list
] # unpack MarkDecorator
for mark in extracted:
if not isinstance(mark, Mark):
raise TypeError("got {!r} instead of Mark".format(mark))
return [x for x in extracted if isinstance(x, Mark)]
def store_mark(obj, mark):
@@ -279,90 +271,6 @@ def store_mark(obj, mark):
obj.pytestmark = get_unpacked_marks(obj) + [mark]
def store_legacy_markinfo(func, mark):
"""create the legacy MarkInfo objects and put them onto the function
"""
if not isinstance(mark, Mark):
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
holder = getattr(func, mark.name, None)
if holder is None:
holder = MarkInfo.for_mark(mark)
setattr(func, mark.name, holder)
elif isinstance(holder, MarkInfo):
holder.add_mark(mark)
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, getattr(mark, "combined", mark).name)
except AttributeError:
return False
return any(mark == info.combined for info in func_mark)
@attr.s(repr=False)
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
_marks = attr.ib(converter=list)
@_marks.validator
def validate_marks(self, attribute, value):
for item in value:
if not isinstance(item, Mark):
raise ValueError(
"MarkInfo expects Mark instances, got {!r} ({!r})".format(
item, type(item)
)
)
combined = attr.ib(
repr=False,
default=attr.Factory(
lambda self: reduce(Mark.combined_with, self._marks), takes_self=True
),
)
name = alias("combined.name", warning=MARK_INFO_ATTRIBUTE)
args = alias("combined.args", warning=MARK_INFO_ATTRIBUTE)
kwargs = alias("combined.kwargs", warning=MARK_INFO_ATTRIBUTE)
@classmethod
def for_mark(cls, mark):
return cls([mark])
def __repr__(self):
return "<MarkInfo {!r}>".format(self.combined)
def add_mark(self, mark):
""" add a MarkInfo with the given args and kwargs. """
self._marks.append(mark)
self.combined = self.combined.combined_with(mark)
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
return map(MarkInfo.for_mark, self._marks)
class MarkGenerator(object):
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance. Example::

View File

@@ -181,6 +181,8 @@ class MonkeyPatch(object):
attribute is missing.
"""
__tracebackhide__ = True
import inspect
if name is notset:
if not isinstance(target, six.string_types):
raise TypeError(
@@ -194,7 +196,11 @@ class MonkeyPatch(object):
if raising:
raise AttributeError(name)
else:
self._setattr.append((target, name, getattr(target, name, notset)))
oldval = getattr(target, name, notset)
# Avoid class descriptors like staticmethod/classmethod.
if inspect.isclass(target):
oldval = target.__dict__.get(name, notset)
self._setattr.append((target, name, oldval))
delattr(target, name)
def setitem(self, dic, name, value):

View File

@@ -5,13 +5,11 @@ from __future__ import print_function
import os
import warnings
import attr
import py
import six
import _pytest._code
from _pytest.compat import getfslineno
from _pytest.mark.structures import MarkInfo
from _pytest.mark.structures import NodeKeywords
from _pytest.outcomes import fail
@@ -56,22 +54,6 @@ def ischildnode(baseid, nodeid):
return node_parts[: len(base_parts)] == base_parts
@attr.s
class _CompatProperty(object):
name = attr.ib()
def __get__(self, obj, owner):
if obj is None:
return self
from _pytest.deprecated import COMPAT_PROPERTY
warnings.warn(
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
)
return getattr(__import__("pytest"), self.name)
class Node(object):
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
@@ -119,95 +101,10 @@ class Node(object):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")
def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty):
return getattr(__import__("pytest"), name)
else:
from _pytest.deprecated import CUSTOM_CLASS
cls = getattr(self, name)
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
return cls
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None))
return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))
def warn(self, _code_or_warning=None, message=None, code=None):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed.
This can be called in two forms:
**Warning instance**
This was introduced in pytest 3.8 and uses the standard warning mechanism to issue warnings.
.. code-block:: python
node.warn(PytestWarning("some message"))
The warning instance must be a subclass of :class:`pytest.PytestWarning`.
**code/message (deprecated)**
This form was used in pytest prior to 3.8 and is considered deprecated. Using this form will emit another
warning about the deprecation:
.. code-block:: python
node.warn("CI", "some message")
:param Union[Warning,str] _code_or_warning:
warning instance or warning code (legacy). This parameter receives an underscore for backward
compatibility with the legacy code/message form, and will be replaced for something
more usual when the legacy form is removed.
:param Union[str,None] message: message to display when called in the legacy form.
:param str code: code for the warning, in legacy form when using keyword arguments.
:return:
"""
if message is None:
if _code_or_warning is None:
raise ValueError("code_or_warning must be given")
self._std_warn(_code_or_warning)
else:
if _code_or_warning and code:
raise ValueError(
"code_or_warning and code cannot both be passed to this function"
)
code = _code_or_warning or code
self._legacy_warn(code, message)
def _legacy_warn(self, code, message):
"""
.. deprecated:: 3.8
Use :meth:`Node.std_warn <_pytest.nodes.Node.std_warn>` instead.
Generate a warning with the given code and message for this item.
"""
from _pytest.deprecated import NODE_WARN
self._std_warn(NODE_WARN)
assert isinstance(code, str)
fslocation = get_fslocation_from_item(self)
self.ihook.pytest_logwarning.call_historic(
kwargs=dict(
code=code, message=message, nodeid=self.nodeid, fslocation=fslocation
)
)
def _std_warn(self, warning):
def warn(self, warning):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed
@@ -215,6 +112,12 @@ class Node(object):
:param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning.
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
Example usage::
.. code-block:: python
node.warn(PytestWarning("some message"))
"""
from _pytest.warning_types import PytestWarning
@@ -307,20 +210,6 @@ class Node(object):
"""
return next(self.iter_markers(name=name), default)
def get_marker(self, name):
""" get a marker object from this node or None if
the node doesn't have a marker with that name.
.. deprecated:: 3.6
This function has been deprecated in favor of
:meth:`Node.get_closest_marker <_pytest.nodes.Node.get_closest_marker>` and
:meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`, see :ref:`update marker code`
for more details.
"""
markers = list(self.iter_markers(name=name))
if markers:
return MarkInfo(markers)
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()

View File

@@ -5,6 +5,8 @@ from __future__ import print_function
import sys
import six
from _pytest import python
from _pytest import runner
from _pytest import unittest
@@ -23,20 +25,15 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when)
call2 = runner.CallInfo.from_call(
lambda: runner.skip(six.text_type(call.excinfo.value)), call.when
)
call.excinfo = call2.excinfo
@hookimpl(trylast=True)
def pytest_runtest_setup(item):
if is_potential_nosetest(item):
if isinstance(item.parent, python.Generator):
gen = item.parent
if not hasattr(gen, "_nosegensetup"):
call_optional(gen.obj, "setup")
if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, "setup")
gen._nosegensetup = True
if not call_optional(item.obj, "setup"):
# call module level setup if there is no object level one
call_optional(item.parent.obj, "setup")
@@ -53,11 +50,6 @@ def teardown_nose(item):
# del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, python.Generator):
call_optional(collector.obj, "setup")
def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes

View File

@@ -49,13 +49,13 @@ class Failed(OutcomeException):
__module__ = "builtins"
class Exit(KeyboardInterrupt):
class Exit(Exception):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason", returncode=None):
self.msg = msg
self.returncode = returncode
KeyboardInterrupt.__init__(self, msg)
super(Exit, self).__init__(msg)
# exposed helper methods
@@ -63,7 +63,7 @@ class Exit(KeyboardInterrupt):
def exit(msg, returncode=None):
"""
Exit testing process as if KeyboardInterrupt was triggered.
Exit testing process.
:param str msg: message to display upon exit.
:param int returncode: return code to be used when exiting pytest.
@@ -137,10 +137,15 @@ def xfail(reason=""):
xfail.Exception = XFailed
def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
def importorskip(modname, minversion=None, reason=None):
"""Imports and returns the requested module ``modname``, or skip the current test
if the module cannot be imported.
:param str modname: the name of the module to import
:param str minversion: if given, the imported module ``__version__`` attribute must be
at least this minimal version, otherwise the test is still skipped.
:param str reason: if given, this reason is shown as the message when the module
cannot be imported.
"""
import warnings
@@ -159,7 +164,9 @@ def importorskip(modname, minversion=None):
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
if reason is None:
reason = "could not import %r" % (modname,)
raise Skipped(reason, allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod

View File

@@ -4,6 +4,7 @@ from __future__ import division
from __future__ import print_function
import codecs
import distutils.spawn
import gc
import os
import platform
@@ -20,6 +21,7 @@ import six
import pytest
from _pytest._code import Source
from _pytest._io.saferepr import saferepr
from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.capture import MultiCapture
from _pytest.capture import SysCapture
@@ -79,7 +81,7 @@ class LsofFdLeakChecker(object):
def _exec_lsof(self):
pid = os.getpid()
return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
return subprocess.check_output(("lsof", "-Ffn0", "-p", str(pid))).decode()
def _parse_lsof_output(self, out):
def isopen(line):
@@ -106,11 +108,8 @@ class LsofFdLeakChecker(object):
def matching_platform(self):
try:
py.process.cmdexec("lsof -v")
except (py.process.cmdexec.Error, UnicodeDecodeError):
# cmdexec may raise UnicodeDecodeError on Windows systems with
# locale other than English:
# https://bitbucket.org/pytest-dev/py/issues/66
subprocess.check_output(("lsof", "-v"))
except (OSError, subprocess.CalledProcessError):
return False
else:
return True
@@ -152,7 +151,7 @@ def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = py.path.local.sysfind(name)
executable = distutils.spawn.find_executable(name)
if executable:
import subprocess
@@ -306,13 +305,10 @@ class HookRecorder(object):
"""return a testreport whose dotted import path matches"""
values = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
continue
except AttributeError:
pass
if when and getattr(rep, "when", None) != when:
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
continue
if when and rep.when != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):
values.append(rep)
@@ -339,7 +335,7 @@ class HookRecorder(object):
failed = []
for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"):
if rep.passed:
if getattr(rep, "when", None) == "call":
if rep.when == "call":
passed.append(rep)
elif rep.skipped:
skipped.append(rep)
@@ -1225,9 +1221,7 @@ def getdecoded(out):
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),
)
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),)
class LineComp(object):

View File

@@ -9,6 +9,7 @@ import inspect
import os
import sys
import warnings
from functools import partial
from textwrap import dedent
import py
@@ -38,13 +39,12 @@ from _pytest.compat import safe_str
from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl
from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import normalize_mark_list
from _pytest.mark.structures import transfer_markers
from _pytest.outcomes import fail
from _pytest.pathlib import parts
from _pytest.warning_types import PytestWarning
from _pytest.warning_types import RemovedInPytest4Warning
def pyobj_property(name):
@@ -125,10 +125,10 @@ def pytest_generate_tests(metafunc):
# those alternative spellings are common - raise a specific error to alert
# the user
alt_spellings = ["parameterize", "parametrise", "parameterise"]
for attr in alt_spellings:
if hasattr(metafunc.function, attr):
for mark_name in alt_spellings:
if metafunc.definition.get_closest_marker(mark_name):
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
for marker in metafunc.definition.iter_markers(name="parametrize"):
metafunc.parametrize(*marker.args, **marker.kwargs)
@@ -199,7 +199,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
# nothing was collected elsewhere, let's do it here
if safe_isclass(obj):
if collector.istestclass(obj, name):
Class = collector._getcustomclass("Class")
outcome.force_result(Class(name, parent=collector))
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
@@ -219,7 +218,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Generator(name, parent=collector)
res = Function(name, parent=collector)
reason = deprecated.YIELD_TESTS.format(name=name)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestWarning(reason))
else:
res = list(collector._genfunctions(name, obj))
outcome.force_result(res)
@@ -282,9 +284,6 @@ class PyobjMixin(PyobjContext):
s = ".".join(parts)
return s.replace(".[", "[")
def _getfslineno(self):
return getfslineno(self.obj)
def reportinfo(self):
# XXX caching?
obj = self.obj
@@ -375,10 +374,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
values.sort(key=lambda item: item.reportinfo()[:2])
return values
def makeitem(self, name, obj):
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
self._makeitem(name, obj)
def _makeitem(self, name, obj):
# assert self.ihook.fspath == self.fspath, self
return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
@@ -387,7 +382,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
module = self.getparent(Module).obj
clscol = self.getparent(Class)
cls = clscol and clscol.obj or None
transfer_markers(funcobj, cls, module)
fm = self.session._fixturemanager
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
@@ -408,7 +402,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
else:
self.ihook.pytest_generate_tests(metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls:
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
else:
@@ -440,9 +433,66 @@ class Module(nodes.File, PyCollector):
return self._importtestmodule()
def collect(self):
self._inject_setup_module_fixture()
self._inject_setup_function_fixture()
self.session._fixturemanager.parsefactories(self)
return super(Module, self).collect()
def _inject_setup_module_fixture(self):
"""Injects a hidden autouse, module scoped fixture into the collected module object
that invokes setUpModule/tearDownModule if either or both are available.
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
if setup_module is None:
setup_module = _get_non_fixture_func(self.obj, "setup_module")
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
if teardown_module is None:
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
if setup_module is None and teardown_module is None:
return
@fixtures.fixture(autouse=True, scope="module")
def xunit_setup_module_fixture(request):
if setup_module is not None:
_call_with_optional_argument(setup_module, request.module)
yield
if teardown_module is not None:
_call_with_optional_argument(teardown_module, request.module)
self.obj.__pytest_setup_module = xunit_setup_module_fixture
def _inject_setup_function_fixture(self):
"""Injects a hidden autouse, function scoped fixture into the collected module object
that invokes setup_function/teardown_function if either or both are available.
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
setup_function = _get_non_fixture_func(self.obj, "setup_function")
teardown_function = _get_non_fixture_func(self.obj, "teardown_function")
if setup_function is None and teardown_function is None:
return
@fixtures.fixture(autouse=True, scope="function")
def xunit_setup_function_fixture(request):
if request.instance is not None:
# in this case we are bound to an instance, so we need to let
# setup_method handle this
yield
return
if setup_function is not None:
_call_with_optional_argument(setup_function, request.function)
yield
if teardown_function is not None:
_call_with_optional_argument(teardown_function, request.function)
self.obj.__pytest_setup_function = xunit_setup_function_fixture
def _importtestmodule(self):
# we assume we are only called once per module
importmode = self.config.getoption("--import-mode")
@@ -450,7 +500,7 @@ class Module(nodes.File, PyCollector):
mod = self.fspath.pyimport(ensuresyspath=importmode)
except SyntaxError:
raise self.CollectError(
_pytest._code.ExceptionInfo().getrepr(style="short")
_pytest._code.ExceptionInfo.from_current().getrepr(style="short")
)
except self.fspath.ImportMismatchError:
e = sys.exc_info()[1]
@@ -466,7 +516,7 @@ class Module(nodes.File, PyCollector):
except ImportError:
from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo()
exc_info = ExceptionInfo.from_current()
if self.config.getoption("verbose") < 2:
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
exc_repr = (
@@ -493,19 +543,6 @@ class Module(nodes.File, PyCollector):
self.config.pluginmanager.consider_module(mod)
return mod
def setup(self):
setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
if setup_module is None:
setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
if setup_module is not None:
setup_module()
teardown_module = _get_xunit_setup_teardown(self.obj, "tearDownModule")
if teardown_module is None:
teardown_module = _get_xunit_setup_teardown(self.obj, "teardown_module")
if teardown_module is not None:
self.addfinalizer(teardown_module)
class Package(Module):
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
@@ -518,6 +555,22 @@ class Package(Module):
self._norecursepatterns = session._norecursepatterns
self.fspath = fspath
def setup(self):
# not using fixtures to call setup_module here because autouse fixtures
# from packages are not called automatically (#4085)
setup_module = _get_non_fixture_func(self.obj, "setUpModule")
if setup_module is None:
setup_module = _get_non_fixture_func(self.obj, "setup_module")
if setup_module is not None:
_call_with_optional_argument(setup_module, self.obj)
teardown_module = _get_non_fixture_func(self.obj, "tearDownModule")
if teardown_module is None:
teardown_module = _get_non_fixture_func(self.obj, "teardown_module")
if teardown_module is not None:
func = partial(_call_with_optional_argument, teardown_module, self.obj)
self.addfinalizer(func)
def _recurse(self, dirpath):
if dirpath.basename == "__pycache__":
return False
@@ -604,8 +657,9 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
when the callable is called without arguments, defaults to the ``holder`` object.
Return ``None`` if a suitable callable is not found.
"""
# TODO: only needed because of Package!
param_obj = param_obj if param_obj is not None else holder
result = _get_xunit_func(holder, attr_name)
result = _get_non_fixture_func(holder, attr_name)
if result is not None:
arg_count = result.__code__.co_argcount
if inspect.ismethod(result):
@@ -616,7 +670,19 @@ def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
return result
def _get_xunit_func(obj, name):
def _call_with_optional_argument(func, arg):
"""Call the given function with the given argument if func accepts one argument, otherwise
calls func without arguments"""
arg_count = func.__code__.co_argcount
if inspect.ismethod(func):
arg_count -= 1
if arg_count:
func(arg)
else:
func()
def _get_non_fixture_func(obj, name):
"""Return the attribute from the given object to be used as a setup/teardown
xunit-style function, but only if not marked as a fixture to
avoid calling it twice.
@@ -648,18 +714,60 @@ class Class(PyCollector):
)
)
return []
return [self._getcustomclass("Instance")(name="()", parent=self)]
def setup(self):
setup_class = _get_xunit_func(self.obj, "setup_class")
if setup_class is not None:
setup_class = getimfunc(setup_class)
setup_class(self.obj)
self._inject_setup_class_fixture()
self._inject_setup_method_fixture()
fin_class = getattr(self.obj, "teardown_class", None)
if fin_class is not None:
fin_class = getimfunc(fin_class)
self.addfinalizer(lambda: fin_class(self.obj))
return [Instance(name="()", parent=self)]
def _inject_setup_class_fixture(self):
"""Injects a hidden autouse, class scoped fixture into the collected class object
that invokes setup_class/teardown_class if either or both are available.
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
setup_class = _get_non_fixture_func(self.obj, "setup_class")
teardown_class = getattr(self.obj, "teardown_class", None)
if setup_class is None and teardown_class is None:
return
@fixtures.fixture(autouse=True, scope="class")
def xunit_setup_class_fixture(cls):
if setup_class is not None:
func = getimfunc(setup_class)
_call_with_optional_argument(func, self.obj)
yield
if teardown_class is not None:
func = getimfunc(teardown_class)
_call_with_optional_argument(func, self.obj)
self.obj.__pytest_setup_class = xunit_setup_class_fixture
def _inject_setup_method_fixture(self):
"""Injects a hidden autouse, function scoped fixture into the collected class object
that invokes setup_method/teardown_method if either or both are available.
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
setup_method = _get_non_fixture_func(self.obj, "setup_method")
teardown_method = getattr(self.obj, "teardown_method", None)
if setup_method is None and teardown_method is None:
return
@fixtures.fixture(autouse=True, scope="function")
def xunit_setup_method_fixture(self, request):
method = request.function
if setup_method is not None:
func = getattr(self, "setup_method")
_call_with_optional_argument(func, method)
yield
if teardown_method is not None:
func = getattr(self, "teardown_method")
_call_with_optional_argument(func, method)
self.obj.__pytest_setup_method = xunit_setup_method_fixture
class Instance(PyCollector):
@@ -686,29 +794,9 @@ class FunctionMixin(PyobjMixin):
def setup(self):
""" perform setup for this test function. """
if hasattr(self, "_preservedparent"):
obj = self._preservedparent
elif isinstance(self.parent, Instance):
obj = self.parent.newinstance()
if isinstance(self.parent, Instance):
self.parent.newinstance()
self.obj = self._getobj()
else:
obj = self.parent.obj
if inspect.ismethod(self.obj):
setup_name = "setup_method"
teardown_name = "teardown_method"
else:
setup_name = "setup_function"
teardown_name = "teardown_function"
setup_func_or_method = _get_xunit_setup_teardown(
obj, setup_name, param_obj=self.obj
)
if setup_func_or_method is not None:
setup_func_or_method()
teardown_func_or_method = _get_xunit_setup_teardown(
obj, teardown_name, param_obj=self.obj
)
if teardown_func_or_method is not None:
self.addfinalizer(teardown_func_or_method)
def _prunetraceback(self, excinfo):
if hasattr(self, "_obj") and not self.config.option.fulltrace:
@@ -739,51 +827,6 @@ class FunctionMixin(PyobjMixin):
return self._repr_failure_py(excinfo, style=style)
class Generator(FunctionMixin, PyCollector):
def collect(self):
# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
from _pytest import deprecated
self.warn(deprecated.YIELD_TESTS)
self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
values = []
seen = {}
_Function = self._getcustomclass("Function")
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not callable(call):
raise TypeError("%r yielded non callable test %r" % (self.obj, call))
if name is None:
name = "[%d]" % i
else:
name = "['%s']" % name
if name in seen:
raise ValueError(
"%r generated tests with non-unique name %r" % (self, name)
)
seen[name] = True
values.append(_Function(name, self, args=args, callobj=call))
return values
def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
# explicit naming
if isinstance(obj[0], six.string_types):
name = obj[0]
obj = obj[1:]
else:
name = None
call, args = obj[0], obj[1:]
return name, call, args
def hasinit(obj):
init = getattr(obj, "__init__", None)
if init:
@@ -986,7 +1029,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
:rtype: List[str]
:return: the list of ids for each argname given
"""
from py.io import saferepr
from _pytest._io.saferepr import saferepr
idfn = None
if callable(ids):
@@ -1065,48 +1108,6 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
pytrace=False,
)
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
""" Add a new call to the underlying test function during the collection phase of a test run.
.. deprecated:: 3.3
Use :meth:`parametrize` instead.
Note that request.addcall() is called during the test collection phase prior and
independently to actual test execution. You should only use addcall()
if you need to specify multiple arguments of a test function.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` an automatic unique id will be generated.
:arg param: a parameter which will be exposed to a later fixture function
invocation through the ``request.param`` attribute.
"""
warnings.warn(deprecated.METAFUNC_ADD_CALL, stacklevel=2)
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
if name not in self.fixturenames:
fail("funcarg %r not used in this function." % name)
else:
funcargs = {}
if id is None:
raise ValueError("id=None not allowed")
if id is NOTSET:
id = len(self._calls)
id = str(id)
if id in self._ids:
raise ValueError("duplicate id %r" % id)
self._ids.add(id)
cs = CallSpec2(self)
cs.setall(funcargs, id, param)
self._calls.append(cs)
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
"""Find the most appropriate scope for a parametrized call based on its arguments.
@@ -1148,13 +1149,11 @@ def _idval(val, argname, idx, idfn, item, config):
s = idfn(val)
except Exception as e:
# See issue https://github.com/pytest-dev/pytest/issues/2169
msg = (
"While trying to determine id of parameter {} at position "
"{} the following exception was raised:\n".format(argname, idx)
)
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
msg = msg.format(item.nodeid, argname, idx)
# we only append the exception type and message because on Python 2 reraise does nothing
msg += " {}: {}\n".format(type(e).__name__, e)
msg += "This warning will be an error error in pytest-4.0."
item.warn(RemovedInPytest4Warning(msg))
six.raise_from(ValueError(msg), e)
if s:
return ascii_escaped(s)
@@ -1326,8 +1325,7 @@ def _showfixtures_main(config, session):
tw.line(" %s: no docstring available" % (loc,), red=True)
def write_docstring(tw, doc):
INDENT = " "
def write_docstring(tw, doc, indent=" "):
doc = doc.rstrip()
if "\n" in doc:
firstline, rest = doc.split("\n", 1)
@@ -1335,11 +1333,11 @@ def write_docstring(tw, doc):
firstline, rest = doc, ""
if firstline.strip():
tw.line(INDENT + firstline.strip())
tw.line(indent + firstline.strip())
if rest:
for line in dedent(rest).split("\n"):
tw.write(INDENT + line + "\n")
tw.write(indent + line + "\n")
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
@@ -1347,7 +1345,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
Python test function.
"""
_genid = None
# disable since functions handle it themselves
_ALLOW_MARKERS = False
@@ -1384,6 +1381,20 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
if keywords:
self.keywords.update(keywords)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
self.keywords.update(
dict.fromkeys(
[
mark.name
for mark in self.iter_markers()
if mark.name not in self.keywords
],
True,
)
)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()
@@ -1408,7 +1419,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
if hasattr(self, "callspec"):
callspec = self.callspec
assert not callspec.funcargs
self._genid = callspec.id
if hasattr(callspec, "param"):
self.param = callspec.param
self._request = fixtures.FixtureRequest(self)

View File

@@ -1,6 +1,9 @@
from __future__ import absolute_import
import math
import pprint
import sys
import warnings
from decimal import Decimal
from numbers import Number
@@ -10,9 +13,11 @@ from six.moves import filterfalse
from six.moves import zip
import _pytest._code
from _pytest import deprecated
from _pytest.compat import isclass
from _pytest.compat import Iterable
from _pytest.compat import Mapping
from _pytest.compat import Sequence
from _pytest.compat import Sized
from _pytest.compat import STRING_TYPES
from _pytest.outcomes import fail
@@ -145,10 +150,10 @@ class ApproxNumpy(ApproxBase):
if np.isscalar(actual):
for i in np.ndindex(self.expected.shape):
yield actual, np.asscalar(self.expected[i])
yield actual, self.expected[i].item()
else:
for i in np.ndindex(self.expected.shape):
yield np.asscalar(actual[i]), np.asscalar(self.expected[i])
yield actual[i].item(), self.expected[i].item()
class ApproxMapping(ApproxBase):
@@ -182,7 +187,7 @@ class ApproxMapping(ApproxBase):
raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
class ApproxSequence(ApproxBase):
class ApproxSequencelike(ApproxBase):
"""
Perform approximate comparisons where the expected value is a sequence of
numbers.
@@ -518,10 +523,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
cls = ApproxScalar
elif isinstance(expected, Mapping):
cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
cls = ApproxSequence
elif _is_numpy_array(expected):
cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
and isinstance(expected, Sized)
and not isinstance(expected, STRING_TYPES)
):
cls = ApproxSequencelike
else:
raise _non_numeric_type_error(expected, at=None)
@@ -547,29 +556,47 @@ def _is_numpy_array(obj):
def raises(expected_exception, *args, **kwargs):
r"""
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
or raise a failure exception otherwise.
:arg message: if specified, provides a custom failure message if the
exception is not raised
:arg match: if specified, asserts that the exception matches a text or regex
:kwparam match: if specified, asserts that the exception matches a text or regex
This helper produces a ``ExceptionInfo()`` object (see below).
:kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
if the exception is not raised
You may use this function as a context manager::
.. currentmodule:: _pytest._code
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
type::
>>> with raises(ZeroDivisionError):
... 1/0
.. versionchanged:: 2.10
If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
above), or no exception at all, the check will fail instead.
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
You can also use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
details of the captured exception::
>>> with raises(ValueError) as exc_info:
... raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
>>> assert exc_info.value.args[0] == "value must be 42"
.. deprecated:: 4.1
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message that will be displayed
in case the ``pytest.raises`` check fails. This has been deprecated as it
is considered error prone as users often mean to use ``match`` instead.
.. note::
@@ -583,7 +610,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute
... assert exc_info.type is ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
@@ -592,22 +619,9 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert exc_info.type == ValueError
>>> assert exc_info.type is ValueError
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
**Legacy forms**
The forms below are fully supported but are discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
**Legacy form**
It is possible to specify a callable by passing a to-be-called lambda::
@@ -623,17 +637,8 @@ def raises(expected_exception, *args, **kwargs):
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
It is also possible to pass a string to be evaluated at runtime::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
The string will be evaluated using the same ``locals()`` and ``globals()``
at the moment of the ``raises`` call.
.. currentmodule:: _pytest._code
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
The form above is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
.. note::
Similar to caught exception objects in Python, explicitly clearing
@@ -664,6 +669,7 @@ def raises(expected_exception, *args, **kwargs):
if not args:
if "message" in kwargs:
message = kwargs.pop("message")
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
if "match" in kwargs:
match_expr = kwargs.pop("match")
if kwargs:
@@ -672,6 +678,7 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
@@ -684,13 +691,13 @@ def raises(expected_exception, *args, **kwargs):
# XXX didn't mean f_globals == f_locals something special?
# this is destroyed here ...
except expected_exception:
return _pytest._code.ExceptionInfo()
return _pytest._code.ExceptionInfo.from_current()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except expected_exception:
return _pytest._code.ExceptionInfo()
return _pytest._code.ExceptionInfo.from_current()
fail(message)
@@ -705,7 +712,7 @@ class RaisesContext(object):
self.excinfo = None
def __enter__(self):
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
self.excinfo = _pytest._code.ExceptionInfo.for_later()
return self.excinfo
def __exit__(self, *tp):

View File

@@ -11,6 +11,7 @@ import warnings
import six
import _pytest._code
from _pytest.deprecated import WARNS_EXEC
from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail
@@ -89,6 +90,7 @@ def warns(expected_warning, *args, **kwargs):
match_expr = kwargs.pop("match")
return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str):
warnings.warn(WARNS_EXEC, stacklevel=2)
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
@@ -190,6 +192,10 @@ class WarningsRecorder(warnings.catch_warnings):
warnings.warn = self._saved_warn
super(WarningsRecorder, self).__exit__(*exc_info)
# Built-in catch_warnings does not reset entered state so we do it
# manually here for this context manager to become reusable.
self._entered = False
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, match_expr=None):

View File

@@ -19,6 +19,8 @@ def getslaveinfoline(node):
class BaseReport(object):
when = None
def __init__(self, **kw):
self.__dict__.update(kw)
@@ -158,17 +160,9 @@ class TestReport(BaseReport):
)
class TeardownErrorReport(BaseReport):
outcome = "failed"
when = "teardown"
def __init__(self, longrepr, **extra):
self.longrepr = longrepr
self.sections = []
self.__dict__.update(extra)
class CollectReport(BaseReport):
when = "collect"
def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
self.nodeid = nodeid
self.outcome = outcome

View File

@@ -34,9 +34,9 @@ def pytest_configure(config):
config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG
from _pytest.warnings import _issue_config_warning
from _pytest.warnings import _issue_warning_captured
_issue_config_warning(RESULT_LOG, config, stacklevel=2)
_issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2)
def pytest_unconfigure(config):
@@ -47,30 +47,6 @@ def pytest_unconfigure(config):
config.pluginmanager.unregister(resultlog)
def generic_path(item):
chain = item.listchain()
gpath = [chain[0].name]
fspath = chain[0].fspath
fspart = False
for node in chain[1:]:
newfspath = node.fspath
if newfspath == fspath:
if fspart:
gpath.append(":")
fspart = False
else:
gpath.append(".")
else:
gpath.append("/")
fspart = True
name = node.name
if name[0] in "([":
gpath.pop()
gpath.append(name)
fspath = newfspath
return "".join(gpath)
class ResultLog(object):
def __init__(self, config, logfile):
self.config = config
@@ -90,7 +66,9 @@ class ResultLog(object):
def pytest_runtest_logreport(self, report):
if report.when != "call" and report.passed:
return
res = self.config.hook.pytest_report_teststatus(report=report)
res = self.config.hook.pytest_report_teststatus(
report=report, config=self.config
)
code = res[1]
if code == "x":
longrepr = str(report.longrepr)

View File

@@ -8,12 +8,14 @@ import os
import sys
from time import time
import attr
import six
from .reports import CollectErrorRepr
from .reports import CollectReport
from .reports import TestReport
from _pytest._code.code import ExceptionInfo
from _pytest.outcomes import Exit
from _pytest.outcomes import skip
from _pytest.outcomes import Skipped
from _pytest.outcomes import TEST_OUTCOME
@@ -189,43 +191,58 @@ def check_interactive_exception(call, report):
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(
lambda: ihook(item=item, **kwds),
when=when,
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"),
reraise = (Exit,)
if not item.config.getvalue("usepdb"):
reraise += (KeyboardInterrupt,)
return CallInfo.from_call(
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
)
@attr.s(repr=False)
class CallInfo(object):
""" Result/Exception info a function invocation. """
#: None or ExceptionInfo object.
excinfo = None
_result = attr.ib()
# Optional[ExceptionInfo]
excinfo = attr.ib()
start = attr.ib()
stop = attr.ib()
when = attr.ib()
def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False):
@property
def result(self):
if self.excinfo is not None:
raise AttributeError("{!r} has no valid result".format(self))
return self._result
@classmethod
def from_call(cls, func, when, reraise=None):
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
self.when = when
self.start = time()
start = time()
excinfo = None
try:
self.result = func()
except KeyboardInterrupt:
if treat_keyboard_interrupt_as_exception:
self.excinfo = ExceptionInfo()
else:
self.stop = time()
raise
result = func()
except: # noqa
self.excinfo = ExceptionInfo()
self.stop = time()
excinfo = ExceptionInfo.from_current()
if reraise is not None and excinfo.errisinstance(reraise):
raise
result = None
stop = time()
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
def __repr__(self):
if self.excinfo:
status = "exception: %s" % str(self.excinfo.value)
if self.excinfo is not None:
status = "exception"
value = self.excinfo.value
else:
result = getattr(self, "result", "<NOTSET>")
status = "result: %r" % (result,)
return "<CallInfo when=%r %s>" % (self.when, status)
# TODO: investigate unification
value = repr(self._result)
status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=value, status=status
)
def pytest_runtest_makereport(item, call):
@@ -269,7 +286,7 @@ def pytest_runtest_makereport(item, call):
def pytest_make_collect_report(collector):
call = CallInfo(lambda: list(collector.collect()), "collect")
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
longrepr = None
if not call.excinfo:
outcome = "passed"

View File

@@ -180,9 +180,9 @@ def pytest_runtest_makereport(item, call):
def pytest_report_teststatus(report):
if hasattr(report, "wasxfail"):
if report.skipped:
return "xfailed", "x", "xfail"
return "xfailed", "x", "XFAIL"
elif report.passed:
return "xpassed", "X", ("XPASS", {"yellow": True})
return "xpassed", "X", "XPASS"
# called by the terminalreporter instance/plugin
@@ -191,11 +191,6 @@ def pytest_report_teststatus(report):
def pytest_terminal_summary(terminalreporter):
tr = terminalreporter
if not tr.reportchars:
# for name in "xfailed skipped failed xpassed":
# if not tr.stats.get(name, 0):
# tr.write_line("HINT: use '-r' option to see extra "
# "summary info about tests")
# break
return
lines = []
@@ -209,21 +204,23 @@ def pytest_terminal_summary(terminalreporter):
tr._tw.line(line)
def show_simple(terminalreporter, lines, stat, format):
def show_simple(terminalreporter, lines, stat):
failed = terminalreporter.stats.get(stat)
if failed:
for rep in failed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
lines.append(format % (pos,))
lines.append("%s %s" % (verbose_word, pos))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")
if xfailed:
for rep in xfailed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
lines.append("XFAIL %s" % (pos,))
lines.append("%s %s" % (verbose_word, pos))
if reason:
lines.append(" " + str(reason))
@@ -232,9 +229,10 @@ def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed")
if xpassed:
for rep in xpassed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
lines.append("XPASS %s %s" % (pos, reason))
lines.append("%s %s %s" % (verbose_word, pos, reason))
def folded_skips(skipped):
@@ -246,8 +244,11 @@ def folded_skips(skipped):
# folding reports with global pytestmark variable
# this is workaround, because for now we cannot identify the scope of a skip marker
# TODO: revisit after marks scope would be fixed
when = getattr(event, "when", None)
if when == "setup" and "skip" in keywords and "pytestmark" not in keywords:
if (
event.when == "setup"
and "skip" in keywords
and "pytestmark" not in keywords
):
key = (key[0], None, key[2])
d.setdefault(key, []).append(event)
values = []
@@ -260,39 +261,42 @@ def show_skipped(terminalreporter, lines):
tr = terminalreporter
skipped = tr.stats.get("skipped", [])
if skipped:
# if not tr.hasopt('skipped'):
# tr.write_line(
# "%d skipped tests, specify -rs for more info" %
# len(skipped))
# return
verbose_word = _get_report_str(terminalreporter, report=skipped[0])
fskips = folded_skips(skipped)
if fskips:
# tr.write_sep("_", "skipped test summary")
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
if lineno is not None:
lines.append(
"SKIP [%d] %s:%d: %s" % (num, fspath, lineno + 1, reason)
"%s [%d] %s:%d: %s"
% (verbose_word, num, fspath, lineno + 1, reason)
)
else:
lines.append("SKIP [%d] %s: %s" % (num, fspath, reason))
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
def shower(stat, format):
def shower(stat):
def show_(terminalreporter, lines):
return show_simple(terminalreporter, lines, stat, format)
return show_simple(terminalreporter, lines, stat)
return show_
def _get_report_str(terminalreporter, report):
_category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus(
report=report, config=terminalreporter.config
)
return verbose
REPORTCHAR_ACTIONS = {
"x": show_xfailed,
"X": show_xpassed,
"f": shower("failed", "FAIL %s"),
"F": shower("failed", "FAIL %s"),
"f": shower("failed"),
"F": shower("failed"),
"s": show_skipped,
"S": show_skipped,
"p": shower("passed", "PASSED %s"),
"E": shower("error", "ERROR %s"),
"p": shower("passed"),
"E": shower("error"),
}

View File

@@ -7,7 +7,7 @@ from __future__ import division
from __future__ import print_function
import argparse
import itertools
import collections
import platform
import sys
import time
@@ -167,7 +167,7 @@ def getreportopt(config):
if char not in reportopts and char != "a":
reportopts += char
elif char == "a":
reportopts = "fEsxXw"
reportopts = "sxXwEf"
return reportopts
@@ -186,20 +186,17 @@ def pytest_report_teststatus(report):
@attr.s
class WarningReport(object):
"""
Simple structure to hold warnings information captured by ``pytest_logwarning`` and ``pytest_warning_captured``.
Simple structure to hold warnings information captured by ``pytest_warning_captured``.
:ivar str message: user friendly message about the warning
:ivar str|None nodeid: node id that generated the warning (see ``get_location``).
:ivar tuple|py.path.local fslocation:
file system location of the source of the warning (see ``get_location``).
:ivar bool legacy: if this warning report was generated from the deprecated ``pytest_logwarning`` hook.
"""
message = attr.ib()
nodeid = attr.ib(default=None)
fslocation = attr.ib(default=None)
legacy = attr.ib(default=False)
def get_location(self, config):
"""
@@ -329,13 +326,6 @@ class TerminalReporter(object):
self.write_line("INTERNALERROR> " + line)
return 1
def pytest_logwarning(self, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
warning = WarningReport(
fslocation=fslocation, message=message, nodeid=nodeid, legacy=True
)
warnings.append(warning)
def pytest_warning_captured(self, warning_message, item):
# from _pytest.nodes import get_fslocation_from_item
from _pytest.warnings import warning_record_to_str
@@ -373,7 +363,7 @@ class TerminalReporter(object):
def pytest_runtest_logreport(self, report):
rep = report
res = self.config.hook.pytest_report_teststatus(report=rep)
res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
category, letter, word = res
if isinstance(word, tuple):
word, markup = word
@@ -386,8 +376,11 @@ class TerminalReporter(object):
return
running_xdist = hasattr(rep, "node")
if markup is None:
if rep.passed:
was_xfail = hasattr(report, "wasxfail")
if rep.passed and not was_xfail:
markup = {"green": True}
elif rep.passed and was_xfail:
markup = {"yellow": True}
elif rep.failed:
markup = {"red": True}
elif rep.skipped:
@@ -507,6 +500,7 @@ class TerminalReporter(object):
errors = len(self.stats.get("error", []))
skipped = len(self.stats.get("skipped", []))
deselected = len(self.stats.get("deselected", []))
selected = self._numcollected - errors - skipped - deselected
if final:
line = "collected "
else:
@@ -520,6 +514,8 @@ class TerminalReporter(object):
line += " / %d deselected" % deselected
if skipped:
line += " / %d skipped" % skipped
if self._numcollected > selected > 0:
line += " / %d selected" % selected
if self.isatty:
self.rewrite(line, bold=True, erase=True)
if final:
@@ -621,6 +617,10 @@ class TerminalReporter(object):
continue
indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col))
if self.config.option.verbose >= 1:
if hasattr(col, "_obj") and col._obj.__doc__:
for line in col._obj.__doc__.strip().splitlines():
self._tw.line("%s%s" % (indent + " ", line.strip()))
@pytest.hookimpl(hookwrapper=True)
def pytest_sessionfinish(self, exitstatus):
@@ -636,7 +636,7 @@ class TerminalReporter(object):
)
if exitstatus in summary_exit_codes:
self.config.hook.pytest_terminal_summary(
terminalreporter=self, exitstatus=exitstatus
terminalreporter=self, exitstatus=exitstatus, config=self.config
)
if exitstatus == EXIT_INTERRUPTED:
self._report_keyboardinterrupt()
@@ -652,6 +652,7 @@ class TerminalReporter(object):
self.summary_passes()
# Display any extra warnings from teardown here (if any).
self.summary_warnings()
self.summary_deprecated_python()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
@@ -730,33 +731,33 @@ class TerminalReporter(object):
final = hasattr(self, "_already_displayed_warnings")
if final:
warnings = all_warnings[self._already_displayed_warnings :]
warning_reports = all_warnings[self._already_displayed_warnings :]
else:
warnings = all_warnings
self._already_displayed_warnings = len(warnings)
if not warnings:
warning_reports = all_warnings
self._already_displayed_warnings = len(warning_reports)
if not warning_reports:
return
grouped = itertools.groupby(
warnings, key=lambda wr: wr.get_location(self.config)
)
reports_grouped_by_message = collections.OrderedDict()
for wr in warning_reports:
reports_grouped_by_message.setdefault(wr.message, []).append(wr)
title = "warnings summary (final)" if final else "warnings summary"
self.write_sep("=", title, yellow=True, bold=False)
for location, warning_records in grouped:
# legacy warnings show their location explicitly, while standard warnings look better without
# it because the location is already formatted into the message
warning_records = list(warning_records)
if location:
self._tw.line(str(location))
for w in warning_records:
for message, warning_reports in reports_grouped_by_message.items():
has_any_location = False
for w in warning_reports:
location = w.get_location(self.config)
if location:
lines = w.message.splitlines()
indented = "\n".join(" " + x for x in lines)
message = indented.rstrip()
else:
message = w.message.rstrip()
self._tw.line(message)
self._tw.line(str(location))
has_any_location = True
if has_any_location:
lines = message.splitlines()
indented = "\n".join(" " + x for x in lines)
message = indented.rstrip()
else:
message = message.rstrip()
self._tw.line(message)
self._tw.line()
self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html")
@@ -773,6 +774,20 @@ class TerminalReporter(object):
self.write_sep("_", msg)
self._outrep_summary(rep)
def summary_deprecated_python(self):
if sys.version_info[:2] <= (3, 4) and self.verbosity >= 0:
self.write_sep("=", "deprecated python version", yellow=True, bold=False)
using_version = ".".join(str(x) for x in sys.version_info[:3])
self.line(
"You are using Python {}, which will no longer be supported in pytest 5.0".format(
using_version
),
yellow=True,
bold=False,
)
self.line("For more information, please read:")
self.line(" https://docs.pytest.org/en/latest/py27-py34-deprecation.html")
def print_teardown_sections(self, rep):
showcapture = self.config.option.showcapture
if showcapture == "no":
@@ -812,8 +827,7 @@ class TerminalReporter(object):
self.write_sep("=", "ERRORS")
for rep in self.stats["error"]:
msg = self._getfailureheadline(rep)
if not hasattr(rep, "when"):
# collect
if rep.when == "collect":
msg = "ERROR collecting " + msg
elif rep.when == "setup":
msg = "ERROR at setup of " + msg
@@ -847,15 +861,6 @@ class TerminalReporter(object):
self.write_line(msg, **markup)
def repr_pythonversion(v=None):
if v is None:
v = sys.version_info
try:
return "%s.%s.%s-%s-%s" % v
except (TypeError, ValueError):
return str(v)
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
unknown_key_seen = False

View File

@@ -63,9 +63,10 @@ class TempPathFactory(object):
if self._given_basetemp is not None:
basetemp = self._given_basetemp
ensure_reset_dir(basetemp)
basetemp = basetemp.resolve()
else:
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
temproot = Path(from_env or tempfile.gettempdir())
temproot = Path(from_env or tempfile.gettempdir()).resolve()
user = get_user() or "unknown"
# use a sub-directory in the temproot to speed-up
# make_numbered_dir() call
@@ -167,7 +168,7 @@ def _mk_tmp(request, factory):
@pytest.fixture
def tmpdir(request, tmpdir_factory):
def tmpdir(tmp_path):
"""Return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
@@ -176,7 +177,7 @@ def tmpdir(request, tmpdir_factory):
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
"""
return _mk_tmp(request, tmpdir_factory)
return py.path.local(tmp_path)
@pytest.fixture

View File

@@ -7,6 +7,7 @@ import sys
import traceback
import _pytest._code
import pytest
from _pytest.compat import getimfunc
from _pytest.config import hookimpl
from _pytest.outcomes import fail
@@ -14,8 +15,6 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Module
from _pytest.python import transfer_markers
def pytest_pycollect_makeitem(collector, name, obj):
@@ -34,34 +33,26 @@ class UnitTestCase(Class):
# to declare that our children do not support funcargs
nofuncargs = True
def setup(self):
cls = self.obj
if getattr(cls, "__unittest_skip__", False):
return # skipped
setup = getattr(cls, "setUpClass", None)
if setup is not None:
setup()
teardown = getattr(cls, "tearDownClass", None)
if teardown is not None:
self.addfinalizer(teardown)
super(UnitTestCase, self).setup()
def collect(self):
from unittest import TestLoader
cls = self.obj
if not getattr(cls, "__test__", True):
return
skipped = getattr(cls, "__unittest_skip__", False)
if not skipped:
self._inject_setup_teardown_fixtures(cls)
self._inject_setup_class_fixture()
self.session._fixturemanager.parsefactories(self, unittest=True)
loader = TestLoader()
module = self.getparent(Module).obj
foundsomething = False
for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name)
if not getattr(x, "__test__", True):
continue
funcobj = getimfunc(x)
transfer_markers(funcobj, cls, module)
yield TestCaseFunction(name, parent=self, callobj=funcobj)
foundsomething = True
@@ -72,6 +63,44 @@ class UnitTestCase(Class):
if ut is None or runtest != ut.TestCase.runTest:
yield TestCaseFunction("runTest", parent=self)
def _inject_setup_teardown_fixtures(self, cls):
"""Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
teardown functions (#517)"""
class_fixture = _make_xunit_fixture(
cls, "setUpClass", "tearDownClass", scope="class", pass_self=False
)
if class_fixture:
cls.__pytest_class_setup = class_fixture
method_fixture = _make_xunit_fixture(
cls, "setup_method", "teardown_method", scope="function", pass_self=True
)
if method_fixture:
cls.__pytest_method_setup = method_fixture
def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self):
setup = getattr(obj, setup_name, None)
teardown = getattr(obj, teardown_name, None)
if setup is None and teardown is None:
return None
@pytest.fixture(scope=scope, autouse=True)
def fixture(self, request):
if setup is not None:
if pass_self:
setup(self, request.function)
else:
setup()
yield
if teardown is not None:
if pass_self:
teardown(self, request.function)
else:
teardown()
return fixture
class TestCaseFunction(Function):
nofuncargs = True
@@ -81,9 +110,6 @@ class TestCaseFunction(Function):
def setup(self):
self._testcase = self.parent.obj(self.name)
self._fix_unittest_skip_decorator()
self._obj = getattr(self._testcase, self.name)
if hasattr(self._testcase, "setup_method"):
self._testcase.setup_method(self._obj)
if hasattr(self, "_request"):
self._request._fillfixtures()
@@ -101,11 +127,7 @@ class TestCaseFunction(Function):
setattr(self._testcase, "__name__", self.name)
def teardown(self):
if hasattr(self._testcase, "teardown_method"):
self._testcase.teardown_method(self._obj)
# Allow garbage collection on TestCase instance attributes.
self._testcase = None
self._obj = None
def startTest(self, testcase):
pass
@@ -115,6 +137,10 @@ class TestCaseFunction(Function):
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
try:
excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
# invoke the attributes to trigger storing the traceback
# trial causes some issue there
excinfo.value
excinfo.traceback
except TypeError:
try:
try:
@@ -136,7 +162,7 @@ class TestCaseFunction(Function):
except KeyboardInterrupt:
raise
except fail.Exception:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
self.__dict__.setdefault("_excinfo", []).append(excinfo)
def addError(self, testcase, rawexcinfo):

View File

@@ -160,19 +160,19 @@ def pytest_terminal_summary(terminalreporter):
yield
def _issue_config_warning(warning, config, stacklevel):
def _issue_warning_captured(warning, hook, stacklevel):
"""
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
:param warning: the warning instance.
:param config:
:param hook: the hook caller
:param stacklevel: stacklevel forwarded to warnings.warn
"""
with warnings.catch_warnings(record=True) as records:
warnings.simplefilter("always", type(warning))
warnings.warn(warning, stacklevel=stacklevel)
config.hook.pytest_warning_captured.call_historic(
hook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=records[0], when="config", item=None)
)

View File

@@ -28,7 +28,6 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail
from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import Generator
from _pytest.python import Instance
from _pytest.python import Module
from _pytest.python import Package
@@ -57,7 +56,6 @@ __all__ = [
"fixture",
"freeze_includes",
"Function",
"Generator",
"hookimpl",
"hookspec",
"importorskip",

View File

@@ -146,6 +146,7 @@ class TestGeneralUsage(object):
assert result.ret
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
@pytest.mark.filterwarnings("default")
def test_better_reporting_on_conftest_load_failure(self, testdir, request):
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
testdir.makepyfile("")
@@ -299,7 +300,7 @@ class TestGeneralUsage(object):
"""
import pytest
def pytest_generate_tests(metafunc):
metafunc.addcall({'x': 3}, id='hello-123')
metafunc.parametrize('x', [3], ids=['hello-123'])
def pytest_runtest_setup(item):
print(item.keywords)
if 'hello-123' in item.keywords:
@@ -316,8 +317,7 @@ class TestGeneralUsage(object):
p = testdir.makepyfile(
"""
def pytest_generate_tests(metafunc):
metafunc.addcall({'i': 1}, id="1")
metafunc.addcall({'i': 2}, id="2")
metafunc.parametrize('i', [1, 2], ids=["1", "2"])
def test_func(i):
pass
"""
@@ -560,12 +560,11 @@ class TestInvocationVariants(object):
def test_equivalence_pytest_pytest(self):
assert pytest.main == py.test.cmdline.main
def test_invoke_with_string(self, capsys):
retcode = pytest.main("-h")
assert not retcode
out, err = capsys.readouterr()
assert "--help" in out
pytest.raises(ValueError, lambda: pytest.main(0))
def test_invoke_with_invalid_type(self, capsys):
with pytest.raises(
TypeError, match="expected to be a list or tuple of strings, got: '-h'"
):
pytest.main("-h")
def test_invoke_with_path(self, tmpdir, capsys):
retcode = pytest.main(tmpdir)
@@ -805,8 +804,8 @@ class TestInvocationVariants(object):
result = testdir.runpytest("-rf")
lines = result.stdout.str().splitlines()
for line in lines:
if line.startswith("FAIL "):
testid = line[5:].strip()
if line.startswith(("FAIL ", "FAILED ")):
_fail, _sep, testid = line.partition(" ")
break
result = testdir.runpytest(testid, "-rf")
result.stdout.fnmatch_lines([line, "*1 failed*"])
@@ -855,7 +854,9 @@ class TestDurations(object):
result = testdir.runpytest("--durations=2")
assert result.ret == 0
lines = result.stdout.get_lines_after("*slowest*durations*")
assert "4 passed" in lines[2]
# account for the "deprecated python version" header
index = 2 if sys.version_info[:2] > (3, 4) else 6
assert "4 passed" in lines[index]
def test_calls_showall(self, testdir):
testdir.makepyfile(self.source)

View File

@@ -37,7 +37,7 @@ def test_code_with_class():
class A(object):
pass
pytest.raises(TypeError, "_pytest._code.Code(A)")
pytest.raises(TypeError, _pytest._code.Code, A)
def x():
@@ -169,7 +169,7 @@ class TestExceptionInfo(object):
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo()
exci = _pytest._code.ExceptionInfo.from_current()
assert exci.getrepr()
@@ -181,7 +181,7 @@ class TestTracebackEntry(object):
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo()
exci = _pytest._code.ExceptionInfo.from_current()
entry = exci.traceback[0]
source = entry.getsource()
assert len(source) == 6

View File

@@ -71,7 +71,7 @@ def test_excinfo_simple():
try:
raise ValueError
except ValueError:
info = _pytest._code.ExceptionInfo()
info = _pytest._code.ExceptionInfo.from_current()
assert info.type == ValueError
@@ -85,7 +85,7 @@ def test_excinfo_getstatement():
try:
f()
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
linenumbers = [
_pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
@@ -126,7 +126,7 @@ class TestTraceback_f_g_h(object):
try:
h()
except ValueError:
self.excinfo = _pytest._code.ExceptionInfo()
self.excinfo = _pytest._code.ExceptionInfo.from_current()
def test_traceback_entries(self):
tb = self.excinfo.traceback
@@ -163,7 +163,7 @@ class TestTraceback_f_g_h(object):
try:
exec(source.compile())
except NameError:
tb = _pytest._code.ExceptionInfo().traceback
tb = _pytest._code.ExceptionInfo.from_current().traceback
print(tb[-1].getsource())
s = str(tb[-1].getsource())
assert s.startswith("def xyz():\n try:")
@@ -180,7 +180,8 @@ class TestTraceback_f_g_h(object):
def test_traceback_cut_excludepath(self, testdir):
p = testdir.makepyfile("def f(): raise ValueError")
excinfo = pytest.raises(ValueError, "p.pyimport().f()")
with pytest.raises(ValueError) as excinfo:
p.pyimport().f()
basedir = py.path.local(pytest.__file__).dirpath()
newtraceback = excinfo.traceback.cut(excludepath=basedir)
for x in newtraceback:
@@ -336,7 +337,8 @@ class TestTraceback_f_g_h(object):
def test_excinfo_exconly():
excinfo = pytest.raises(ValueError, h)
assert excinfo.exconly().startswith("ValueError")
excinfo = pytest.raises(ValueError, "raise ValueError('hello\\nworld')")
with pytest.raises(ValueError) as excinfo:
raise ValueError("hello\nworld")
msg = excinfo.exconly(tryshort=True)
assert msg.startswith("ValueError")
assert msg.endswith("world")
@@ -356,6 +358,12 @@ def test_excinfo_str():
assert len(s.split(":")) >= 3 # on windows it's 4
def test_excinfo_for_later():
e = ExceptionInfo.for_later()
assert "for raises" in repr(e)
assert "for raises" in str(e)
def test_excinfo_errisinstance():
excinfo = pytest.raises(ValueError, h)
assert excinfo.errisinstance(ValueError)
@@ -365,7 +373,7 @@ def test_excinfo_no_sourcecode():
try:
exec("raise ValueError()")
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
s = str(excinfo.traceback[-1])
assert s == " File '<string>':1 in <module>\n ???\n"
@@ -390,7 +398,7 @@ def test_entrysource_Queue_example():
try:
queue.Queue().get(timeout=0.001)
except queue.Empty:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
entry = excinfo.traceback[-1]
source = entry.getsource()
assert source is not None
@@ -402,7 +410,7 @@ def test_codepath_Queue_example():
try:
queue.Queue().get(timeout=0.001)
except queue.Empty:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
entry = excinfo.traceback[-1]
path = entry.path
assert isinstance(path, py.path.local)
@@ -453,7 +461,7 @@ class TestFormattedExcinfo(object):
except KeyboardInterrupt:
raise
except: # noqa
return _pytest._code.ExceptionInfo()
return _pytest._code.ExceptionInfo.from_current()
assert 0, "did not raise"
def test_repr_source(self):
@@ -491,7 +499,7 @@ class TestFormattedExcinfo(object):
try:
exec(co)
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
if sys.version_info[0] >= 3:
@@ -510,7 +518,7 @@ raise ValueError()
try:
exec(co)
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo.from_current()
repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
if sys.version_info[0] >= 3:
@@ -1340,7 +1348,7 @@ def test_repr_traceback_with_unicode(style, encoding):
try:
raise RuntimeError(msg)
except RuntimeError:
e_info = ExceptionInfo()
e_info = ExceptionInfo.from_current()
formatter = FormattedExcinfo(style=style)
repr_traceback = formatter.repr_traceback(e_info)
assert repr_traceback is not None

View File

@@ -6,6 +6,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import ast
import inspect
import sys
@@ -14,7 +15,6 @@ import six
import _pytest._code
import pytest
from _pytest._code import Source
from _pytest._code.source import ast
astonly = pytest.mark.nothing
@@ -306,8 +306,6 @@ class TestSourceParsingAndCompiling(object):
pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
def test_compile_to_ast(self):
import ast
source = Source("x = 4")
mod = source.compile(flag=ast.PyCF_ONLY_AST)
assert isinstance(mod, ast.Module)
@@ -317,10 +315,9 @@ class TestSourceParsingAndCompiling(object):
co = self.source.compile()
six.exec_(co, globals())
f(7)
excinfo = pytest.raises(AssertionError, "f(6)")
excinfo = pytest.raises(AssertionError, f, 6)
frame = excinfo.traceback[-1].frame
stmt = frame.code.fullsource.getstatement(frame.lineno)
# print "block", str(block)
assert str(stmt).strip().startswith("assert")
@pytest.mark.parametrize("name", ["", None, "my"])
@@ -361,17 +358,13 @@ def test_getline_finally():
def c():
pass
excinfo = pytest.raises(
TypeError,
"""
teardown = None
try:
c(1)
finally:
if teardown:
teardown()
""",
)
with pytest.raises(TypeError) as excinfo:
teardown = None
try:
c(1)
finally:
if teardown:
teardown()
source = excinfo.traceback[-1].statement
assert str(source).strip() == "c(1)"

View File

@@ -3,6 +3,7 @@ from __future__ import division
from __future__ import print_function
import os
import sys
import pytest
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
@@ -10,122 +11,7 @@ from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
pytestmark = pytest.mark.pytester_example_path("deprecated")
def test_yield_tests_deprecation(testdir):
testdir.makepyfile(
"""
def func1(arg, arg2):
assert arg == arg2
def test_gen():
yield "m1", func1, 15, 3*5
yield "m2", func1, 42, 6*7
def test_gen2():
for k in range(10):
yield func1, 1, 1
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_yield_tests_deprecation.py:3:*yield tests are deprecated*",
"*test_yield_tests_deprecation.py:6:*yield tests are deprecated*",
"*2 passed*",
]
)
assert result.stdout.str().count("yield tests are deprecated") == 2
def test_compat_properties_deprecation(testdir):
testdir.makepyfile(
"""
def test_foo(request):
print(request.node.Module)
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
"please use pytest.Module instead*",
"*1 passed, 1 warnings in*",
]
)
def test_cached_setup_deprecation(testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fix(request):
return request.cached_setup(lambda: 1)
def test_foo(fix):
assert fix == 1
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_cached_setup_deprecation.py:4:*cached_setup is deprecated*",
"*1 passed, 1 warnings in*",
]
)
def test_custom_class_deprecation(testdir):
testdir.makeconftest(
"""
import pytest
class MyModule(pytest.Module):
class Class(pytest.Class):
pass
def pytest_pycollect_makemodule(path, parent):
return MyModule(path, parent)
"""
)
testdir.makepyfile(
"""
class Test:
def test_foo(self):
pass
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
"*1 passed, 1 warnings in*",
]
)
def test_funcarg_prefix_deprecation(testdir):
testdir.makepyfile(
"""
def pytest_funcarg__value():
return 10
def test_funcarg_prefix(value):
assert value == 10
"""
)
result = testdir.runpytest("-ra", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
(
"*test_funcarg_prefix_deprecation.py:1: *pytest_funcarg__value: "
'declaring fixtures using "pytest_funcarg__" prefix is deprecated*'
),
"*1 passed*",
]
)
@pytest.mark.filterwarnings("default")
def test_pytest_setup_cfg_deprecated(testdir):
def test_pytest_setup_cfg_unsupported(testdir):
testdir.makefile(
".cfg",
setup="""
@@ -133,14 +19,11 @@ def test_pytest_setup_cfg_deprecated(testdir):
addopts = --verbose
""",
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*"]
)
with pytest.raises(pytest.fail.Exception):
testdir.runpytest()
@pytest.mark.filterwarnings("default")
def test_pytest_custom_cfg_deprecated(testdir):
def test_pytest_custom_cfg_unsupported(testdir):
testdir.makefile(
".cfg",
custom="""
@@ -148,29 +31,8 @@ def test_pytest_custom_cfg_deprecated(testdir):
addopts = --verbose
""",
)
result = testdir.runpytest("-c", "custom.cfg")
result.stdout.fnmatch_lines(
["*pytest*section in custom.cfg files is deprecated*use*tool:pytest*instead*"]
)
def test_str_args_deprecated(tmpdir):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
from _pytest.main import EXIT_NOTESTSCOLLECTED
warnings = []
class Collect(object):
def pytest_warning_captured(self, warning_message):
warnings.append(str(warning_message.message))
ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
msg = (
"passing a string to pytest.main() is deprecated, "
"pass a list of arguments instead."
)
assert msg in warnings
assert ret == EXIT_NOTESTSCOLLECTED
with pytest.raises(pytest.fail.Exception):
testdir.runpytest("-c", "custom.cfg")
def test_getfuncargvalue_is_deprecated(request):
@@ -191,29 +53,12 @@ def test_resultlog_is_deprecated(testdir):
result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log"))
result.stdout.fnmatch_lines(
[
"*--result-log is deprecated and scheduled for removal in pytest 4.0*",
"*See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information*",
"*--result-log is deprecated and scheduled for removal in pytest 5.0*",
"*See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information*",
]
)
def test_metafunc_addcall_deprecated(testdir):
testdir.makepyfile(
"""
def pytest_generate_tests(metafunc):
metafunc.addcall({'i': 1})
metafunc.addcall({'i': 2})
def test_func(i):
pass
"""
)
res = testdir.runpytest("-s", SHOW_PYTEST_WARNINGS_ARG)
assert res.ret == 0
res.stdout.fnmatch_lines(
["*Metafunc.addcall is deprecated*", "*2 passed, 2 warnings*"]
)
def test_terminal_reporter_writer_attr(pytestconfig):
"""Check that TerminalReporter._tw is also available as 'writer' (#2984)
This attribute is planned to be deprecated in 3.4.
@@ -229,6 +74,7 @@ def test_terminal_reporter_writer_attr(pytestconfig):
@pytest.mark.parametrize("plugin", ["catchlog", "capturelog"])
@pytest.mark.filterwarnings("default")
def test_pytest_catchlog_deprecated(testdir, plugin):
testdir.makepyfile(
"""
@@ -245,6 +91,12 @@ def test_pytest_catchlog_deprecated(testdir, plugin):
)
def test_raises_message_argument_deprecated():
with pytest.warns(pytest.PytestDeprecationWarning):
with pytest.raises(RuntimeError, message="foobar"):
raise RuntimeError
def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@@ -262,17 +114,15 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
"""
)
res = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
assert res.ret == 0
assert res.ret == 2
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
res.stdout.fnmatch_lines(
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format(
sep=os.sep, msg=msg
)
["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
)
@pytest.mark.parametrize("use_pyargs", [True, False])
def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
testdir, use_pyargs
):
"""When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)"""
@@ -292,7 +142,7 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
args = ("--pyargs", "pkg") if use_pyargs else ()
args += (SHOW_PYTEST_WARNINGS_ARG,)
res = testdir.runpytest(*args)
assert res.ret == 0
assert res.ret == (0 if use_pyargs else 2)
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
if use_pyargs:
assert msg not in res.stdout.str()
@@ -300,7 +150,7 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
res.stdout.fnmatch_lines("*{msg}*".format(msg=msg))
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_conftest(
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest(
testdir
):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@@ -309,8 +159,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte
subdirectory.mkdir()
testdir.makeconftest(
"""
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
pytest_plugins=['capture']
"""
)
@@ -324,16 +172,14 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte
)
res = testdir.runpytest_subprocess()
assert res.ret == 0
assert res.ret == 2
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
res.stdout.fnmatch_lines(
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format(
sep=os.sep, msg=msg
)
["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
)
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives(
testdir
):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@@ -366,37 +212,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(
assert msg not in res.stdout.str()
def test_call_fixture_function_deprecated():
"""Check if a warning is raised if a fixture function is called directly (#3661)"""
@pytest.fixture
def fix():
return 1
with pytest.deprecated_call():
assert fix() == 1
def test_pycollector_makeitem_is_deprecated():
from _pytest.python import PyCollector
from _pytest.warning_types import RemovedInPytest4Warning
class PyCollectorMock(PyCollector):
"""evil hack"""
def __init__(self):
self.called = False
def _makeitem(self, *k):
"""hack to disable the actual behaviour"""
self.called = True
collector = PyCollectorMock()
with pytest.warns(RemovedInPytest4Warning):
collector.makeitem("foo", "bar")
assert collector.called
def test_fixture_named_request(testdir):
testdir.copy_example()
result = testdir.runpytest()
@@ -405,3 +220,21 @@ def test_fixture_named_request(testdir):
"*'request' is a reserved name for fixtures and will raise an error in future versions"
]
)
def test_python_deprecation(testdir):
result = testdir.runpytest()
python_ver = ".".join(str(x) for x in sys.version_info[:3])
msg = "You are using Python {}, which will no longer be supported in pytest 5.0".format(
python_ver
)
if sys.version_info[:2] <= (3, 4):
result.stdout.fnmatch_lines(
[
msg,
"For more information, please read:",
" https://docs.pytest.org/en/latest/py27-py34-deprecation.html",
]
)
else:
assert msg not in result.stdout.str()

View File

@@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses_with_attribute_comparison_off():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field(compare=False)
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses_verbose():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@@ -0,0 +1,19 @@
from dataclasses import dataclass
from dataclasses import field
def test_comparing_two_different_data_classes():
@dataclass
class SimpleDataObjectOne(object):
field_a: int = field()
field_b: int = field()
@dataclass
class SimpleDataObjectTwo(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObjectOne(1, "b")
right = SimpleDataObjectTwo(1, "c")
assert left != right

View File

@@ -3,4 +3,4 @@ import pytest
@pytest.fixture
def arg2(request):
pytest.raises(Exception, "request.getfixturevalue('arg1')")
pytest.raises(Exception, request.getfixturevalue, "arg1")

View File

@@ -0,0 +1,6 @@
import pytest
@pytest.mark.foo
def test_mark():
pass

View File

@@ -0,0 +1,16 @@
import warnings
import pytest
def func():
warnings.warn(UserWarning("foo"))
@pytest.mark.parametrize("i", range(5))
def test_foo(i):
func()
def test_bar():
func()

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
from _pytest._io.saferepr import saferepr
def test_simple_repr():
assert saferepr(1) == "1"
assert saferepr(None) == "None"
def test_maxsize():
s = saferepr("x" * 50, maxsize=25)
assert len(s) == 25
expected = repr("x" * 10 + "..." + "x" * 10)
assert s == expected
def test_maxsize_error_on_instance():
class A:
def __repr__():
raise ValueError("...")
s = saferepr(("*" * 50, A()), maxsize=25)
assert len(s) == 25
assert s[0] == "(" and s[-1] == ")"
def test_exceptions():
class BrokenRepr:
def __init__(self, ex):
self.ex = ex
def __repr__(self):
raise self.ex
class BrokenReprException(Exception):
__str__ = None
__repr__ = None
assert "Exception" in saferepr(BrokenRepr(Exception("broken")))
s = saferepr(BrokenReprException("really broken"))
assert "TypeError" in s
assert "TypeError" in saferepr(BrokenRepr("string"))
s2 = saferepr(BrokenRepr(BrokenReprException("omg even worse")))
assert "NameError" not in s2
assert "unknown" in s2
def test_big_repr():
from _pytest._io.saferepr import SafeRepr
assert len(saferepr(range(1000))) <= len("[" + SafeRepr().maxlist * "1000" + "]")
def test_repr_on_newstyle():
class Function(object):
def __repr__(self):
return "<%s>" % (self.name)
assert saferepr(Function())
def test_unicode():
val = u"£€"
reprval = u"'£€'"
assert saferepr(val) == reprval

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