Compare commits

...

241 Commits
3.7.3 ... 3.8.2

Author SHA1 Message Date
Anthony Sottile
d3673c7429 Preparing release version 3.8.2 2018-10-02 07:46:02 -07:00
Bruno Oliveira
25fe3706a4 Merge pull request #4056 from nicoddemus/unicode-vars
Ensure Monkeypatch setenv and delenv use bytes keys in Python 2
2018-10-02 07:56:32 -03:00
Bruno Oliveira
1a323fbd3c Show a warning when non-str is given to Monkeypatch.setenv 2018-10-01 20:07:07 -03:00
Bruno Oliveira
9d971d33be Hide internal pytest.warns traceback 2018-10-01 18:45:08 -03:00
Bruno Oliveira
bc009a8582 Fix test to comply with pypy 6.0 2018-10-01 18:37:27 -03:00
Bruno Oliveira
5e7d427df1 Merge pull request #4060 from nicoddemus/4051-changelog
Improve changelog for #4051
2018-10-01 18:12:52 -03:00
Bruno Oliveira
dd59ed3b18 Merge pull request #4059 from labcodes/documenting-scopes
Add possible values for fixture scope to docs
2018-10-01 17:25:42 -03:00
Bruno Oliveira
20d0f0e56b Improve changelog for #4051 2018-10-01 17:12:05 -03:00
Bruno Oliveira
d24a7e6c5a Issue warning if Monkeypatch.setenv/delenv receive non-strings in Python 2
Fixes the bug described in:

	https://github.com/tox-dev/tox/pull/1025#discussion_r221273830

Which is more evident when using `unicode_literals`.
2018-10-01 17:05:30 -03:00
Jose Carlos Menezes
4dc73bda45 Update changelog 2018-10-01 14:41:30 -03:00
Jose Carlos Menezes
732cc2687d Add possible values for fixture scope to docs 2018-10-01 14:31:28 -03:00
Ronny Pfannschmidt
5d2d64c190 Merge pull request #4057 from jeffreyrack/4051-improve-error-messaging
Improve error messaging when invalid syntax is passed to the -m option
2018-10-01 07:16:59 +02:00
Jeffrey Rackauckas
7a6d16c1eb Adding .rst to end of changelog fragment filename. 2018-09-30 18:45:49 -07:00
Jeffrey Rackauckas
c2179c3127 Improve error messaging when invalid syntax is passed to the -m option 2018-09-30 18:34:56 -07:00
Bruno Oliveira
d8d7f73e1c Merge pull request #4045 from nicoddemus/root-conftest-warning-workaround-4039
Do not issue non-top-level conftest warning when --pyargs is used
2018-09-27 09:26:11 -03:00
Ronny Pfannschmidt
3c23b5b010 Merge pull request #4037 from nicoddemus/deprecate-item-warnings-captured-hook
Document that item parameter of pytest_warning_captured hook is deprecated
2018-09-27 13:20:26 +02:00
Bruno Oliveira
783019a8e6 Rename 4040.trivial.rst to 4040.bugfix.rst 2018-09-27 08:08:03 -03:00
Bruno Oliveira
d2fc7ca6e0 Merge pull request #4041 from Zac-HD/user-properties-type
Ensure user_properties is a list
2018-09-27 08:04:30 -03:00
Bruno Oliveira
2d06927a06 Merge pull request #4040 from ods/summary_passes_less_noisy
Exclude empty reports for passed tests
2018-09-27 08:01:58 -03:00
Denis Otkidach
44d29d887e Changelog entry on excluding empty reports for passed tests 2018-09-26 19:02:35 +03:00
Bruno Oliveira
32c5a113e2 Do not issue non-top-level conftest warning when --pyargs is used
Fix #4039
2018-09-26 12:02:08 -03:00
Bruno Oliveira
ba5630e0f8 Simplify test_pytest_plugins_in_non_top_level_conftest_deprecated 2018-09-26 10:49:14 -03:00
Denis Otkidach
808df48ee8 Test for excluding empty reports for passed tests 2018-09-26 16:44:00 +03:00
Zac-HD
a089a9577e Succinct definition of user_properties 2018-09-26 22:40:11 +10:00
Zac-HD
6be2136f20 Ensure user_properties is a list 2018-09-26 22:04:50 +10:00
Denis Otkidach
f9ab81a493 Exclude empty reports for passed tests 2018-09-26 11:55:39 +03:00
Bruno Oliveira
1636522563 Document that item parameter of pytest_warning_captured hook is deprecated
Our policy is to not deprecate features during bugfix releases, but in this
case I believe it makes sense as we are only documenting it as deprecated,
without issuing warnings which might potentially break test suites.

This will get the word out that hook implementers should not use this parameter
at all.

Fix #4036
2018-09-25 17:38:22 -03:00
Bruno Oliveira
b1fbb2ab92 Merge pull request #4032 from hjwp/patch-1
add documentation of register_assert_rewrite
2018-09-25 08:43:29 -03:00
Daniel Hahler
e85edf5212 Merge pull request #4029 from nicoddemus/warnings-example-deprecation-docs
Add an example on how to update config.warn calls
2018-09-25 13:25:21 +02:00
Bruno Oliveira
b03bad5dbb Fix linting 2018-09-25 08:12:55 -03:00
Harry Percival
19ec300b2a fix rst syntax again 2018-09-25 06:58:47 +01:00
Harry Percival
11442f2ad7 fix rst syntax thing 2018-09-25 06:57:33 +01:00
Harry Percival
97748b6605 mention conftest.py as a good place to do it. 2018-09-25 06:55:28 +01:00
Harry Percival
2b762337bd add documentation of register_assert_rewrite
wip
2018-09-25 06:49:50 +01:00
Bruno Oliveira
9899b8f1fb Add an example on how to update config.warn calls
As commented in https://github.com/pytest-dev/pytest-cov/pull/230#pullrequestreview-157958838
2018-09-23 22:42:09 -03:00
Anthony Sottile
4474beeb82 Typo fix [ci skip] 2018-09-23 18:07:28 -07:00
Ronny Pfannschmidt
eca3e781b6 Merge pull request #4022 from iwanb/fix_reload
Fix #3539: reload module with assertion rewrite import hook
2018-09-23 18:17:56 +02:00
iwanb
c61ff31ffa Fix #3539: reload module with assertion rewrite import hook 2018-09-23 13:05:55 +02:00
Ankit Goel
ec57cbf82d Merge pull request #4020 from crazymerlyn/release-3.8.1
Preparing release version 3.8.1
2018-09-23 00:56:27 +05:30
CrazyMerlyn
3f6a46c2a4 Preparing release version 3.8.1 2018-09-22 16:34:06 +00:00
Bruno Oliveira
4ba3cb25b0 Merge pull request #4016 from williamjamir/patch-1
Include Python 3.7 on getting started doc
2018-09-21 20:02:25 -03:00
William Jamir Silva
650c458df9 Include Python 3.7 on getting started doc
Close #3932
2018-09-21 19:18:51 -03:00
Ankit Goel
58aa4f91f5 Merge pull request #4012 from maxalbert/fix-docstring-typo
Fix typo in docstring
2018-09-21 22:07:57 +05:30
Maximilian Albert
9b382ed16c Fix typo in docstring 2018-09-21 17:11:15 +01:00
Ronny Pfannschmidt
f02dbaf97f Merge pull request #4010 from nicoddemus/package-len-error-3749
Fix 'Package has no len()' error during collection
2018-09-21 07:40:20 +02:00
Bruno Oliveira
41f6ea13ce Fix 'Package has no len()' error during collection
Fix #3749
2018-09-20 17:53:21 -03:00
Ronny Pfannschmidt
f6eb39df33 Merge pull request #4001 from asottile/fix_bytes_repr_text_mix_python_2
Fix UnicodeDecodeError in assertion with mixed non-ascii bytes repr + text
2018-09-20 22:16:37 +02:00
Bruno Oliveira
7a5e11bbcf Merge pull request #3997 from nicoddemus/deprecation-docs
Introduce deprecations page
2018-09-20 08:22:51 -03:00
Anthony Sottile
7122fa5613 Fix UnicodeDecodeError in assertion with mixed non-ascii bytes repr + text 2018-09-19 20:24:00 -07:00
Bruno Oliveira
7aff81739e Merge pull request #3998 from blueyed/logging-del
logging: del item.catch_log_handler only in teardown
2018-09-19 20:07:19 -03:00
Bruno Oliveira
27772f67c0 Merge pull request #3987 from nicoddemus/fix-find-scope-3941
Fix scope determination with indirect parameters
2018-09-19 20:06:45 -03:00
Bruno Oliveira
10b3b2dc68 Merge pull request #3990 from blueyed/coverage-source
coverage: use modules
2018-09-19 19:55:11 -03:00
Bruno Oliveira
c2841542af Introduce deprecations page
fix #3996
2018-09-19 19:51:29 -03:00
wim glenn
1f28096587 Merge pull request #4003 from pytest-dev/wimglenn-patch-1
seems this subdir is now .pytest_cache not .cache
2018-09-19 14:49:38 -05:00
wim glenn
e86b01e831 Update customize.rst 2018-09-19 14:06:36 -05:00
Bruno Oliveira
d1fa8ae08e Improve CHANGELOG entry 2018-09-19 12:52:10 -03:00
Ronny Pfannschmidt
29dac03314 Merge pull request #3980 from nicoddemus/rewrite-cwd-changed
Fix assertion rewriter crash if cwd changes mid-testing
2018-09-19 17:45:26 +02:00
Daniel Hahler
e7eb7e799b logging: del item.catch_log_handler only in teardown
Without this caplog.record_tuples etc is not available anymore when using
`--pdb`.
2018-09-19 17:17:47 +02:00
Bruno Oliveira
7f48f552c1 Fix linting 2018-09-19 10:18:05 -03:00
Bruno Oliveira
1e2e65f0fa Add references to the relevant Python issues 2018-09-19 08:20:23 -03:00
Daniel Hahler
28c9cc7321 coverage: use modules for source
This should increase coverage for subprocesses, where previously
`source` paths were used only from the config file, but not the initial
`--source` argument.
2018-09-19 07:46:19 +02:00
Bruno Oliveira
ccb90b5c46 [WIP] Introduce deprecations page
fix #3996
2018-09-18 20:56:40 -03:00
Bruno Oliveira
37d2469266 Use a PurePath instance to do matching against patterns in assertion rewrite
This way we don't need to have real file system path, which prevents the
original #3973 bug.
2018-09-17 20:29:09 -03:00
Bruno Oliveira
1df6d28080 Fix assertion rewriter crash if cwd changes mid-testing
Unfortunately we need to get a `py.path.local` object to perform the fnmatch
operation, it is different from the standard `fnmatch` module because it
implements its own custom logic. So we need to use `py.path` to perform
the fnmatch for backward compatibility reasons.

Ideally we should be able to use a "pure path" in `pathlib` terms (a path
not bound to the file system), but we don't have those in pylib.

Fix #3973
2018-09-17 20:04:44 -03:00
Daniel Hahler
03eaad376b tox: coverage factor: combine and report 2018-09-17 14:44:47 +02:00
Daniel Hahler
739f9a4a4b Travis: use codecov-bash
Faster to install and will retry uploads on connection errors.
2018-09-17 14:44:47 +02:00
Daniel Hahler
93224f8cf9 tox: remove obsolete whitelist_externals 2018-09-17 14:44:47 +02:00
Ronny Pfannschmidt
bb57186dd4 Merge pull request #3983 from nicoddemus/update-backward-policy
Review backward compatibility policy
2018-09-16 20:43:55 +02:00
Bruno Oliveira
2803eb9fbb Merge pull request #3984 from nicoddemus/fix-docs-formatting
Fix rendering of the ini example for python_files
2018-09-15 12:05:59 -03:00
Ronny Pfannschmidt
f53eff93db Merge pull request #3982 from nicoddemus/ignore-pytest-cache
Ignore pytest cache
2018-09-15 07:21:45 +02:00
Bruno Oliveira
86a14d007d Fix scope determination with indirect parameters
Fix #3941
2018-09-14 21:33:59 -03:00
Bruno Oliveira
a4dd6ee3ce Fix linting 2018-09-14 17:31:01 -03:00
Bruno Oliveira
130cf7e0db Fix rendering of the ini example for python_files
Also added an example using one pattern per line
2018-09-14 17:27:25 -03:00
Bruno Oliveira
cbb41f1ae2 Ignore Sphinx's .doctrees folder 2018-09-14 16:00:35 -03:00
Bruno Oliveira
fa78da3c03 Update backward compatibility policy with new practices 2018-09-14 15:58:22 -03:00
Bruno Oliveira
87ddb2dbd5 Change flaky test_request_garbage to provide more debug information
This test fails *very* rarely when running in xdist.
2018-09-14 15:25:45 -03:00
Bruno Oliveira
9aa6b0903b .pytest_cache is now automatically ignored by Git 2018-09-14 15:16:40 -03:00
Bruno Oliveira
49800ea134 Merge pull request #3977 from RonnyPfannschmidt/remove-im-func
Remove im_func
2018-09-13 15:46:33 -03:00
Ronny Pfannschmidt
8fe55b1d18 add changelog for fix #3975 2018-09-13 15:40:45 +02:00
Ronny Pfannschmidt
a0ce9a4441 remove the legacy code about im_func and generalize using fix and compat.getimfunc 2018-09-13 15:38:36 +02:00
Daniel Hahler
2cf2dc3d95 Merge pull request #3967 from blueyed/optional-mock
tests: use unittest.mock with py34+
2018-09-13 07:36:42 +02:00
Daniel Hahler
7537e94ddf tests: use unittest.mock with py34+
Fixes https://github.com/pytest-dev/pytest/issues/3965.

Has to work around https://github.com/tox-dev/tox/issues/706.

No coverage for pluggymaster builds is OK though anyway.
2018-09-12 23:21:47 +02:00
Anthony Sottile
2c90b3db9e Merge pull request #3957 from asottile/changelog_files
Improve pre-commit detection for changelog filenames
2018-09-08 10:58:50 -07:00
Anthony Sottile
826adafe2e Improve pre-commit detection for changelog filenames 2018-09-07 09:36:09 -07:00
Anthony Sottile
3dd2933dbd Merge pull request #3948 from nicoddemus/fix-changelog
Amend CHANGELOG with missing #3251
2018-09-07 09:15:09 -07:00
Bruno Oliveira
b55351274e Amend CHANGELOG with missing #3251 2018-09-06 18:56:08 -03:00
Bruno Oliveira
c00d934b21 Merge pull request #3933 from nicoddemus/idval-hypothesis-flaky-3707
Use -n auto now that xdist behaves well in Travis and AppVeyor
2018-09-06 15:15:55 -03:00
Bruno Oliveira
6b526cbe6a Merge pull request #3943 from nicoddemus/no-coverage-on-deploy
Disable coverage during deployment stage
2018-09-06 15:15:32 -03:00
Bruno Oliveira
e0539e6ede Merge pull request #3942 from nicoddemus/merge-features-into-master
Merge features into master
2018-09-06 09:35:28 -03:00
Bruno Oliveira
5eb85efa14 Use -n auto now that xdist behaves well in Travis and AppVeyor
This hopefully fixes the flaky test_idval_hypothesis on AppVeyor

Fix #3707
2018-09-06 09:22:13 -03:00
Bruno Oliveira
9ee8d72fd2 Disable coverage during deployment stage
Otherwise it will fail as there's no coverage data to combine/publish
2018-09-06 09:18:47 -03:00
Bruno Oliveira
8c4ca383ca Merge remote-tracking branch 'upstream/features' into merge-features-into-master 2018-09-06 09:15:56 -03:00
Bruno Oliveira
f2a427da25 Merge pull request #3940 from nicoddemus/release-3.8.0
Release 3.8.0
2018-09-06 09:15:00 -03:00
Bruno Oliveira
e0466d0ad8 Merge pull request #3923 from nicoddemus/codecov
Add support for codecov in AppVeyor
2018-09-06 07:02:56 -03:00
Bruno Oliveira
418a66a09f Replace coveralls' badge by codecov's 2018-09-05 22:37:32 -03:00
Bruno Oliveira
5e2bd17d18 White list external "env" used by Travis for coverage 2018-09-05 22:37:32 -03:00
Bruno Oliveira
ec6fca4aa7 Add codecov support to AppVeyor and remove coveralls 2018-09-05 19:50:06 -03:00
Bruno Oliveira
1f20626618 Preparing release version 3.8.0 2018-09-05 21:06:32 +00:00
Bruno Oliveira
69b34f7658 Merge remote-tracking branch 'upstream/master' into release-3.8.0 2018-09-05 18:02:02 -03:00
Bruno Oliveira
531b76a513 Merge pull request #3931 from nicoddemus/internal-warnings
Use standard warnings for internal pytest warnings
2018-09-05 14:05:52 -03:00
Bruno Oliveira
f63c683faa No longer escape regex in pytest.mark.filterwarnings
Fix #3936
2018-09-05 10:20:25 -03:00
Ronny Pfannschmidt
410d5762c0 Merge pull request #3919 from fabioz/master
Improve import performance of assertion rewrite. Fixes #3918.
2018-09-05 14:33:40 +02:00
Bruno Oliveira
ddb308455a Make sure warn is called in test_parameterset_extractfrom 2018-09-05 09:01:29 -03:00
Bruno Oliveira
f42b5019ec Make code_or_warning parameter private for backward-compatibility 2018-09-04 18:53:58 -03:00
Bruno Oliveira
adc9ed85bc Fix test_idval_hypothesis 2018-09-04 18:49:20 -03:00
Bruno Oliveira
4592def14d Improve test_rewarn_functional 2018-09-04 17:02:56 -03:00
Bruno Oliveira
2e0a7cf78d Revert to having just "runtest" as "when" parameter of the pytest_warning_captured hook 2018-09-04 17:01:23 -03:00
Bruno Oliveira
5a52acaa92 Make config no longer optional in parametrize id functions 2018-09-04 16:55:52 -03:00
Bruno Oliveira
6d497f2c77 Fix stacklevel for warning about Metafunc.addcall 2018-09-04 16:50:24 -03:00
Bruno Oliveira
b7560a8808 Keep backward compatibility for code as kw in Node.warn 2018-09-04 16:48:21 -03:00
Bruno Oliveira
d3ca739c00 Use explicit instances when calling warnings.warn_explicit 2018-09-04 16:29:48 -03:00
Bruno Oliveira
3db76ccf3d Fix Cache.warn function to issue a "config" warning 2018-09-04 15:53:17 -03:00
Bruno Oliveira
438f7a1254 Add "setup", "call" and "teardown" values to "when" parameter of pytest_warning_captured hook 2018-09-04 15:53:17 -03:00
Bruno Oliveira
47bf58d69e Make Node.warn support two forms, new and deprecated
As suggested during review, it now accepts two forms:

Node.warn(warning_instance)  (recommended)

Node.warn(code, message)  (deprecated)
2018-09-04 15:53:17 -03:00
Bruno Oliveira
5ef51262f7 Fix reference to PytestWarning in warningsfilter mark 2018-09-04 15:06:14 -03:00
Bruno Oliveira
a054aa4797 Issue assert rewrite warning if tuple >=1 as suggested in review 2018-09-04 14:45:48 -03:00
Bruno Oliveira
f1cfd10c94 Handle cache warnings in tests 2018-09-04 14:44:02 -03:00
Bruno Oliveira
d3f72ca202 Fix linting for warnings.rst 2018-09-04 14:33:41 -03:00
Bruno Oliveira
022c58bf64 Revert pytest_terminal_summary(tryfirst) in warnings module as this breaks tests 2018-09-04 14:26:34 -03:00
Bruno Oliveira
b42518acd5 Change std_warn to receive a single warning instance, addressed review suggestions 2018-09-04 14:20:42 -03:00
Bruno Oliveira
284a2d110f Move warnings import to top level 2018-09-04 13:46:33 -03:00
Bruno Oliveira
9ae0a3cd85 Do not trigger warning about tuples being always True if the tuple has size != 2 2018-09-04 13:41:11 -03:00
Bruno Oliveira
615c671434 Connect string literals 2018-09-04 13:34:05 -03:00
Bruno Oliveira
29bfa5efa4 Merge pull request #3925 from crazymerlyn/fix-exit-code
Fix exit code for command line errors
2018-09-04 11:53:00 -03:00
Bruno Oliveira
016f8f1536 Improve get_fslocation_from_item's docstring 2018-09-04 11:48:11 -03:00
Bruno Oliveira
e9417be9df Add comment about deprecation warnings being shown by default 2018-09-04 11:35:35 -03:00
Bruno Oliveira
c304998ed7 Remove commented out code 2018-09-04 11:35:35 -03:00
Bruno Oliveira
415a62e373 Fix typo in PytestExperimentalApiWarning 2018-09-04 11:35:35 -03:00
Bruno Oliveira
8ce3aeadbf Move PytestExerimentalApiWarning to warning_types 2018-09-04 11:35:35 -03:00
Bruno Oliveira
b818314045 Improve docs for warnings capture and PEP-0506 remarks 2018-09-04 11:35:34 -03:00
Bruno Oliveira
56d414177a Remove nodeid from messages for warnings generated by standard warnings
Standard warnings already contain the proper location, so we don't need
to also print the node id
2018-09-04 11:35:34 -03:00
Bruno Oliveira
0fffa6ba2f Implement hack to issue warnings during config
Once we can capture warnings during the config stage, we can
then get rid of this function

Related to #2891
2018-09-04 11:35:34 -03:00
Bruno Oliveira
60499d221e Add test to ensure that users can suppress internal warnings 2018-09-04 11:35:34 -03:00
Bruno Oliveira
9965ed84da Show deprecation warnings by default if no other filters are configured
Fix #2908
2018-09-04 11:35:34 -03:00
Bruno Oliveira
7e13593452 Add CHANGELOG entries for #2452
Fix #2452
Fix #2684
2018-09-04 11:35:34 -03:00
Bruno Oliveira
208dd3aad1 Add docs for internal warnings and introduce PytestDeprecationWarning
Fix #2477
2018-09-04 11:35:34 -03:00
Bruno Oliveira
19a01c9849 Make PytestWarning and RemovedInPytest4Warning part of the public API 2018-09-04 11:35:34 -03:00
Bruno Oliveira
78ac7d99f5 Deprecate Config.warn and Node.warn, replaced by standard warnings 2018-09-04 11:35:34 -03:00
Bruno Oliveira
0c8dbdcd92 Fix existing tests now that we are using standard warnings 2018-09-04 11:35:34 -03:00
Bruno Oliveira
8e4501ee29 Use std_warn for warning about applying marks directly to parameters 2018-09-04 11:35:34 -03:00
Bruno Oliveira
0100f61b62 Start the laywork to capture standard warnings 2018-09-04 11:35:15 -03:00
Bruno Oliveira
1a9d913ee1 Capture and display warnings during collection
Fix #3251
2018-09-04 11:35:06 -03:00
Bruno Oliveira
51e32cf7cc Remove Python 2.6 specific warning 2018-09-04 11:35:06 -03:00
Bruno Oliveira
3fcc4cdbd5 Make terminal capture pytest_warning_capture
pytest_logwarning is no longer emitted by the warnings plugin,
only ever emitted from .warn() functions in config and item
2018-09-04 11:35:06 -03:00
Bruno Oliveira
ffd47ceefc Implement new pytest_warning_captured hook 2018-09-04 11:35:00 -03:00
Bruno Oliveira
10f21b423a Remove assert for "reprec" because this is no longer set on the pluginmanager
It seems this has no effect since `pluggy` was developed as a separate
library.
2018-09-04 11:35:00 -03:00
Bruno Oliveira
eec7081b8d Make AssertionRewritingrHook use imp_find_module 2018-09-03 10:18:25 -03:00
CrazyMerlyn
b01704cce1 Fix exit code for command line errors
Fixes #3913
2018-09-03 04:16:35 +00:00
Ronny Pfannschmidt
15ede8aab8 Merge pull request #3924 from nicoddemus/enable-pypy
Reenable pypy now that scandir can be installed without a compiler
2018-09-02 17:34:21 +02:00
Bruno Oliveira
f7dc9b9fef Merge pull request #3922 from nicoddemus/skip-xfail-docs-3219
Mention explicitly when pytest.skip and pytest.xfail can be called
2018-09-02 11:40:34 -03:00
Bruno Oliveira
dc13f0b469 Reenable pypy now that scandir can be installed without a compiler
Ref: benhoyt/scandir#105
Ref: #3111
2018-09-02 11:17:06 -03:00
Bruno Oliveira
a13c6a84df Mention explicitly when pytest.skip and pytest.xfail can be called
Fix #3219
2018-09-02 10:42:05 -03:00
Bruno Oliveira
dfa713163a Merge pull request #3921 from nicoddemus/use-constant
Use EXIT_USAGEERROR instead of magic number
2018-09-01 20:09:39 -03:00
Bruno Oliveira
90c00dfd54 Use EXIT_USAGEERROR instead of magic number 2018-09-01 12:03:28 -03:00
Daniel Hahler
f3b9b21996 Merge pull request #3920 from blueyed/branch
tests/CI: enable branch coverage
2018-09-01 16:52:12 +02:00
Bruno Oliveira
885b8a3b4c Fix linting 2018-09-01 11:13:40 -03:00
Bruno Oliveira
4675912d89 Add tests for early rewrite bailout code and handle patterns with subdirectories 2018-09-01 10:59:21 -03:00
Bruno Oliveira
495b44198f Merge pull request #3917 from dhirensr/docs_for_detailed_info
T3566,T3546: added a blurb in usage.rst for usage of flag -r
2018-09-01 10:09:29 -03:00
Bruno Oliveira
8d8e68cf90 Merge pull request #3911 from wimglenn/i18n_width
improve line width estimate
2018-09-01 08:57:44 -03:00
Bruno Oliveira
f3b0caf299 Improve docs for summary report and move it further up in the doc 2018-09-01 08:54:00 -03:00
Bruno Oliveira
75d29acc06 Fix reference to inter-sphinx objects database 2018-09-01 08:48:47 -03:00
Daniel Hahler
cbbb36fc9b tests/CI: enable branch coverage 2018-08-31 19:26:47 +02:00
Fabio Zadrozny
d53e449296 Improve performance of assertion rewriting. Fixes #3918 2018-08-31 12:27:08 -03:00
Ronny Pfannschmidt
01df368d93 Merge pull request #3914 from nicoddemus/merge-master-into-features
Merge master into features
2018-08-31 15:31:00 +02:00
Bruno Oliveira
2256f2f04d Remove test which is no longer required and improve test_lf_and_ff_prints_no_needless_message
* test_lf_and_ff_obey_verbosity is no longer necessary because
  test_lf_and_ff_prints_no_needless_message already checks if the proper messages
  are displayed when -q is used.

* Improve test_lf_and_ff_prints_no_needless_message so we also check that
  the correct message is displayed when there are failures to run
2018-08-31 08:01:55 -03:00
dhirensr
95881c870e T3566,T3546: added a blurb in usage.rst for usage of flag -r 2018-08-31 11:20:15 +05:30
Ronny Pfannschmidt
019e33ee3f Merge pull request #3915 from nicoddemus/quickstart-book-docs
Add pytest Quick Start Guide to the books section in the docs
2018-08-31 07:40:13 +02:00
Bruno Oliveira
19fa01b91d Tweak changelog 2018-08-30 21:17:14 -03:00
Bruno Oliveira
96aad2983b Move code to get width of current line to a function 2018-08-30 21:16:35 -03:00
wim glenn
c18a5b5179 try to be backwards compat 2018-08-30 19:06:20 -05:00
wim glenn
ed4b94a180 add changelog entry 2018-08-30 18:59:58 -05:00
wim glenn
29c5ac71bc improve line width estimate 2018-08-30 18:59:58 -05:00
Bruno Oliveira
84a9f7a263 Add pytest Quick Start Guide to the books section in the docs 2018-08-30 20:18:51 -03:00
Bruno Oliveira
11e591e442 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-08-30 19:55:23 -03:00
Bruno Oliveira
64f00683f2 Merge pull request #3912 from dhirensr/needless_message
Needless message printed with --failed-first and no failed tests #3853
2018-08-30 19:51:49 -03:00
Bruno Oliveira
84a033fd97 Remove extra newline 2018-08-30 19:48:47 -03:00
Bruno Oliveira
0183d46275 Improve CHANGELOG a bit 2018-08-30 19:44:04 -03:00
Bruno Oliveira
9bd4b0a05e Merge pull request #3910 from hoefling/docfix
doc fix: raises accepts tuples instead of lists
2018-08-30 19:41:44 -03:00
Bruno Oliveira
f0e852b4db Merge pull request #2800 from blueyed/coverage
Travis: report coverage with all builds
2018-08-30 19:41:22 -03:00
dhirensr
ade01b1f5b T3853:Added changelog file 2018-08-30 16:03:18 +05:30
dhirensr
3035b2724d T3853:removed needless message printed with --failed-first,--last-failed and no failed tests 2018-08-30 16:01:42 +05:30
hoefling
8c96eea583 doc fix: raises accepts tuples instead of lists (fixes #3907) 2018-08-30 00:19:59 +02:00
Bruno Oliveira
338953a25d Merge pull request #3908 from nicoddemus/optimize-release
Skip the "test" stage when making a release
2018-08-29 18:53:36 -03:00
Anthony Sottile
f1bd46266b Merge pull request #3909 from asottile/update_release
Update release procedure
2018-08-29 14:37:52 -07:00
Anthony Sottile
77cad3c436 Update release procedure [ci skip] 2018-08-29 14:13:08 -07:00
Bruno Oliveira
3ca70692de Skip the "test" stage when making a release
Given that our guidelines demand that the CI have already passed, it seems
wasteful to run all those jobs again for the exact same commit.

As discussed in https://github.com/pytest-dev/pytest/pull/3906#issuecomment-417094481,
this will skip the "test" stage when building a tag for deployment.
2018-08-29 17:54:58 -03:00
Daniel Hahler
417516c378 squash! Travis: report coverage with all builds
doctesting: remove changedir

With coverage 5 we could use COVERAGE_RCFILE to make it find the
.coveragerc, or we could add `--rcfile` to _PYTEST_TOX_COVERAGE_RUN, but
I've thought that this should not be the job that has to test if
`--pyargs` actually works.
2018-08-29 22:53:20 +02:00
Daniel Hahler
f730291e67 Travis: report coverage with all builds
- Skips pypy for coverage, reports only py37 to coveralls
- tox: allow for TOXENV=py37-coverage
- tracks coverage in subprocesses, using coverage-enable-subprocess, and
  parallel=1
- removes usedevelop with doctesting to match `--source` being used with
  coverage
- keep coveralls for now, used with AppVeyor
2018-08-29 22:30:28 +02:00
Anthony Sottile
d76fb8345c Merge pull request #3906 from asottile/release-3.7.4
Preparing release version 3.7.4
2018-08-29 13:03:34 -07:00
Anthony Sottile
aea962dc21 Preparing release version 3.7.4 2018-08-29 08:57:54 -07:00
Bruno Oliveira
4345efaffc Merge pull request #3902 from stevepiercy/fix-pytest.org-links
Fix pytest.org links
2018-08-29 08:20:01 -03:00
Bruno Oliveira
bf47033169 Fix linting 2018-08-28 21:05:34 -03:00
Steve Piercy
37a65684d6 add changelog entry 2018-08-28 14:51:27 -07:00
Steve Piercy
eab5020e24 Fix hostname 2018-08-28 14:45:04 -07:00
Steve Piercy
8ef21f56d3 Fix 404 2018-08-28 14:42:16 -07:00
Steve Piercy
103d980b2d Use https, save a redirect 2018-08-28 14:41:13 -07:00
Steve Piercy
28c3ef1c77 Use https, save a redirect, fix hostname 2018-08-28 14:40:20 -07:00
Steve Piercy
67c3c28877 Use https, save a redirect 2018-08-28 14:39:32 -07:00
Steve Piercy
e040fd20a3 Use https, save a redirect 2018-08-28 14:38:55 -07:00
Steve Piercy
00e0b43010 Use https, save a redirect 2018-08-28 14:36:47 -07:00
Steve Piercy
f19cfbb825 Fix 404 to a somewhat better historical note 2018-08-28 14:35:08 -07:00
Steve Piercy
bde3d1a0cd Use https; save a redirect 2018-08-28 14:34:39 -07:00
Steve Piercy
2e090896d5 Use https 2018-08-28 14:34:22 -07:00
Steve Piercy
b0a32da0b5 Use https; save a redirect 2018-08-28 14:27:11 -07:00
Bruno Oliveira
10c1c7c41a Merge pull request #3895 from nicoddemus/issue-3506
Avoid possible infinite recursion when writing pyc files in assert rewrite
2018-08-28 18:16:10 -03:00
Daniel Hahler
16f452ef98 Merge pull request #3894 from blueyed/baseline
Travis: add baseline stage
2018-08-28 22:19:08 +02:00
Bruno Oliveira
b77e533693 Merge pull request #3893 from jirikuncar/3892-macos
travis: run tests on macOS
2018-08-28 17:06:17 -03:00
Bruno Oliveira
a605ad4d11 Merge pull request #3880 from jeffreyrack/3829-progress_display_mode
#3829 -- Add the ability to show test progress as number of tests completed instead of a percent.
2018-08-28 16:54:14 -03:00
Jeffrey Rackauckas
4b94760c8e Removed spacing in count display. 2018-08-27 20:23:17 -07:00
Bruno Oliveira
82a7ca9615 Avoid possible infinite recursion when writing pyc files in assert rewrite
What happens is that atomic_write on Python 2.7 on Windows will try
to convert the paths to unicode, but this triggers the import of
the encoding module for the file system codec, which in turn triggers
the rewrite, which in turn again tries to import the module, and so on.

This short-circuits the cases where we try to import another file when
writing a pyc file; I don't expect this to affect anything because
the only modules that could be affected are those imported by
atomic_writes.

Fix #3506
2018-08-27 21:29:45 -03:00
Bruno Oliveira
23295e1e98 Fix docs linting 2018-08-27 20:21:08 -03:00
Bruno Oliveira
32575f92c9 set TOXENV in test-macos template otherwise it will inherit "coveralls" 2018-08-27 20:07:51 -03:00
Bruno Oliveira
a260e58020 Drop 3.6 from OS-X to reduce build time 2018-08-27 20:03:12 -03:00
Bruno Oliveira
b2f7e02a02 Reorganize osx environments to avoid repetition as suggested in review 2018-08-27 20:02:16 -03:00
Bruno Oliveira
29e114b463 Try to fix test in MacOS-X 2018-08-27 19:27:51 -03:00
Bruno Oliveira
2a059b1c1b Merge pull request #3885 from nicoddemus/bad-output-classic
Fix bad console output when using console_output_style=classic
2018-08-27 19:07:02 -03:00
Daniel Hahler
cdc72bf5a3 Travis: add baseline stage
Fixes https://github.com/pytest-dev/pytest/issues/3876.
2018-08-27 23:46:24 +02:00
Jiri Kuncar
f786335dbb travis: run tests on macOS
closes #3892
2018-08-27 17:22:27 +02:00
Jiri Kuncar
ab5af524a4 Fix macOS specific code that uses capturemanager.
https://github.com/pytest-dev/pytest/issues/3888#issuecomment-416206606

closes #3888

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2018-08-27 16:07:59 +02:00
Bruno Oliveira
9620b167d9 Merge pull request #3887 from asottile/improve_test_code
Improve the coverage of testing/code
2018-08-27 07:20:45 -03:00
Jeffrey Rackauckas
8f4685e024 Move count display style to be part of console_output_style, fixed test progress for count console output style. 2018-08-26 19:21:00 -07:00
Anthony Sottile
10544c4cb8 Merge pull request #3886 from nicoddemus/ff-quiet
Cache now obeys -q when showing summary for --lf and --ff
2018-08-26 18:32:22 -07:00
Anthony Sottile
1e8e17c01e Improve the coverage of testing/code 2018-08-26 16:13:22 -07:00
Bruno Oliveira
80eef29681 Merge pull request #3884 from nicoddemus/merge-master-into-features
Merge master into features
2018-08-26 19:41:30 -03:00
Bruno Oliveira
47bb53f5cb Cache now obeys -q when showing summary for --lf and --ff
Related to #3853
2018-08-26 18:08:19 -03:00
Bruno Oliveira
6991a16edb Fix bad console output when using console_output_style=classic
Fix #3883
2018-08-26 17:12:55 -03:00
Bruno Oliveira
2f2d5861bb Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-08-26 16:45:00 -03:00
Bruno Oliveira
a31967431f Merge pull request #3882 from nicoddemus/release-3.7.3
Release 3.7.3
2018-08-26 16:41:40 -03:00
Bruno Oliveira
e74ad4ff9b Fix typo in CHANGELOG 2018-08-26 12:27:02 -03:00
Jeffrey Rackauckas
2a917a582e Removing accidental change to test 2018-08-25 22:21:50 -07:00
Jeffrey Rackauckas
325319dc3b Fixing xdist test to properly configure an ini file. 2018-08-25 22:18:29 -07:00
Jeffrey Rackauckas
dda5e5ea32 Fixing backticks in changelog file. 2018-08-25 21:55:00 -07:00
Jeffrey Rackauckas
5e260c4d34 Fixing changelog file. 2018-08-25 21:50:19 -07:00
Jeffrey Rackauckas
5fefc48f33 Fixing pre-commit hooks 2018-08-23 23:00:02 -07:00
Jeffrey Rackauckas
93f783228c Add the progress_display_mode ini option 2018-08-23 22:56:25 -07:00
Bruno Oliveira
044d2b8e6e Merge pull request #3838 from wimglenn/runresult_xfail
Support xfailed and xpassed outcomes in RunResult.
2018-08-20 20:01:51 -03:00
wim glenn
539a22c750 Added support for xfailed and xpassed outcomes to the `pytester.RunResult.assert_outcomes` signature. 2018-08-20 01:24:19 -05:00
Bruno Oliveira
a6cdd0d9da Merge pull request #3831 from nicoddemus/merge-master-into-features
Merge master into features
2018-08-18 19:43:36 -03:00
Bruno Oliveira
c64a8c9c7f Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-08-18 15:54:53 -03:00
Bruno Oliveira
5f97711377 Merge pull request #3787 from hsoft/no-plugin-autoload
Add option to disable plugin auto-loading
2018-08-08 21:30:50 -03:00
Virgil Dupras
126896f69d Add option to disable plugin auto-loading
If `PYTEST_DISABLE_PLUGIN_AUTOLOAD` is set, disable auto-loading of
plugins through setuptools entrypoints. Only plugins that have been
explicitly specified are loaded.

ref #3784.
2018-08-07 13:16:28 -04:00
102 changed files with 3054 additions and 771 deletions

View File

@@ -1,4 +1,9 @@
[run]
omit =
# standlonetemplate is read dynamically and tested by test_genscript
*standalonetemplate.py
source = pytest,_pytest,testing/
parallel = 1
branch = 1
[paths]
source = src/
.tox/*/lib/python*/site-packages/
.tox\*\Lib\site-packages\

2
.gitattributes vendored
View File

@@ -1 +1 @@
CHANGELOG merge=union
*.bat text eol=crlf

6
.gitignore vendored
View File

@@ -24,6 +24,7 @@ src/_pytest/_version.py
.eggs/
doc/*/_build
doc/*/.doctrees
build/
dist/
*.egg-info
@@ -35,6 +36,11 @@ env/
.cache
.pytest_cache
.coverage
.coverage.*
coverage.xml
.ropeproject
.idea
.hypothesis
.pydevproject
.project
.settings

View File

@@ -38,7 +38,8 @@ repos:
language: python
additional_dependencies: [pygments, restructuredtext_lint]
- id: changelogs-rst
name: changelog files must end in .rst
entry: ./scripts/fail
language: script
files: 'changelog/.*(?<!\.rst)$'
name: changelog filenames
language: fail
entry: 'changelog files must be named ####.(feature|bugfix|doc|removal|vendor|trivial).rst'
exclude: changelog/(\d+\.(feature|bugfix|doc|removal|vendor|trivial).rst|README.rst|_template.rst)
files: ^changelog/

View File

@@ -1,8 +1,9 @@
sudo: false
language: python
stages:
- linting
- test
- baseline
- name: test
if: repo = pytest-dev/pytest AND tag IS NOT present
- name: deploy
if: repo = pytest-dev/pytest AND tag IS present
python:
@@ -11,43 +12,60 @@ install:
- pip install --upgrade --pre tox
env:
matrix:
# coveralls is not listed in tox's envlist, but should run in travis
- TOXENV=coveralls
# note: please use "tox --listenvs" to populate the build matrix below
# please remove the linting env in all cases
- TOXENV=py27
- TOXENV=py34
- TOXENV=py36
- TOXENV=py27-pexpect
- TOXENV=py27-xdist
- TOXENV=py27-trial
- TOXENV=py27-numpy
- TOXENV=py27-pluggymaster
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
- TOXENV=py36-pexpect
- TOXENV=py36-xdist
- TOXENV=py36-trial
- TOXENV=py36-numpy
- TOXENV=py36-pluggymaster
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
- TOXENV=py27-nobyte
- TOXENV=doctesting
- TOXENV=docs
- TOXENV=docs PYTEST_NO_COVERAGE=1
jobs:
include:
- env: TOXENV=pypy
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
python: 'pypy-5.4'
- env: TOXENV=py35
python: '3.5'
- env: TOXENV=py36-freeze
- env: TOXENV=py36-freeze PYTEST_NO_COVERAGE=1
python: '3.6'
- env: TOXENV=py37
python: '3.7'
sudo: required
dist: xenial
- &test-macos
language: generic
os: osx
osx_image: xcode9.4
sudo: required
install:
- python -m pip install --pre tox
env: TOXENV=py27
- <<: *test-macos
env: TOXENV=py37
before_install:
- brew update
- brew upgrade python
- brew unlink python
- brew link python
- stage: baseline
env: TOXENV=py27
- env: TOXENV=py34
- env: TOXENV=py36
- env: TOXENV=linting PYTEST_NO_COVERAGE=1
- stage: deploy
python: '3.6'
env:
env: PYTEST_NO_COVERAGE=1
install: pip install -U setuptools setuptools_scm
script: skip
deploy:
@@ -60,12 +78,35 @@ jobs:
on:
tags: true
repo: pytest-dev/pytest
- stage: linting
python: '3.6'
env: TOXENV=linting
before_script:
- |
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
export COVERAGE_FILE="$PWD/.coverage"
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
export _PYTEST_TOX_COVERAGE_RUN="coverage run -m"
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
fi
script: tox --recreate
after_success:
- |
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
set -e
pip install coverage
coverage combine
coverage xml --ignore-errors
coverage report -m --ignore-errors
bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -F "${TOXENV//-/,},linux"
# Coveralls does not support merged reports.
if [[ "$TOXENV" = py37 ]]; then
pip install coveralls
coveralls
fi
fi
notifications:
irc:
channels:

11
AUTHORS
View File

@@ -10,6 +10,7 @@ Ahn Ki-Wook
Alan Velasco
Alexander Johnson
Alexei Kozlenok
Allan Feldman
Anatoly Bubenkoff
Anders Hovmöller
Andras Tim
@@ -46,7 +47,9 @@ Christian Boelsen
Christian Theunert
Christian Tismer
Christopher Gilling
CrazyMerlyn
Cyrus Maden
Dhiren Serai
Daniel Grana
Daniel Hahler
Daniel Nuri
@@ -71,6 +74,7 @@ Endre Galaczi
Eric Hunsberger
Eric Siegerman
Erik M. Bray
Fabio Zadrozny
Feng Ma
Florian Bruhin
Floris Bruynooghe
@@ -90,6 +94,7 @@ Hui Wang (coldnight)
Ian Bicking
Ian Lesperance
Ionuț Turturică
Iwan Briquemont
Jaap Broekhuizen
Jan Balster
Janne Vanhala
@@ -175,6 +180,7 @@ Raphael Pierzina
Raquel Alegre
Ravi Chandra
Roberto Polli
Roland Puntaier
Romain Dorgueil
Roman Bolshakov
Ronny Pfannschmidt
@@ -210,13 +216,14 @@ Vasily Kuznetsov
Victor Maryama
Victor Uriarte
Vidar T. Fauske
Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Wil Cooley
William Lee
Wim Glenn
Wouter van Ackooy
Xuan Luong
Xuecong Liao
Zac Hatfield-Dodds
Zoltán Máté
Roland Puntaier
Allan Feldman

View File

@@ -18,13 +18,212 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 3.8.2 (2018-10-02)
=========================
Deprecations and Removals
-------------------------
- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after
the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``.
Our policy is to not deprecate features during bugfix releases, but in this case we believe it makes sense as we are
only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get
the word out that hook implementers should not use this parameter at all.
In a future release ``item`` will always be ``None`` and will emit a proper warning when a hook implementation
makes use of it.
Bug Fixes
---------
- `#3539 <https://github.com/pytest-dev/pytest/issues/3539>`_: Fix reload on assertion rewritten modules.
- `#4034 <https://github.com/pytest-dev/pytest/issues/4034>`_: The ``.user_properties`` attribute of ``TestReport`` objects is a list
of (name, value) tuples, but could sometimes be instantiated as a tuple
of tuples. It is now always a list.
- `#4039 <https://github.com/pytest-dev/pytest/issues/4039>`_: No longer issue warnings about using ``pytest_plugins`` in non-top-level directories when using ``--pyargs``: the
current ``--pyargs`` mechanism is not reliable and might give false negatives.
- `#4040 <https://github.com/pytest-dev/pytest/issues/4040>`_: Exclude empty reports for passed tests when ``-rP`` option is used.
- `#4051 <https://github.com/pytest-dev/pytest/issues/4051>`_: Improve error message when an invalid Python expression is passed to the ``-m`` option.
- `#4056 <https://github.com/pytest-dev/pytest/issues/4056>`_: ``MonkeyPatch.setenv`` and ``MonkeyPatch.delenv`` issue a warning if the environment variable name is not ``str`` on Python 2.
In Python 2, adding ``unicode`` keys to ``os.environ`` causes problems with ``subprocess`` (and possible other modules),
making this a subtle bug specially susceptible when used with ``from __future__ import unicode_literals``.
Improved Documentation
----------------------
- `#3928 <https://github.com/pytest-dev/pytest/issues/3928>`_: Add possible values for fixture scope to docs.
pytest 3.8.1 (2018-09-22)
=========================
Bug Fixes
---------
- `#3286 <https://github.com/pytest-dev/pytest/issues/3286>`_: ``.pytest_cache`` directory is now automatically ignored by Git. Users who would like to contribute a solution for other SCMs please consult/comment on this issue.
- `#3749 <https://github.com/pytest-dev/pytest/issues/3749>`_: Fix the following error during collection of tests inside packages::
TypeError: object of type 'Package' has no len()
- `#3941 <https://github.com/pytest-dev/pytest/issues/3941>`_: Fix bug where indirect parametrization would consider the scope of all fixtures used by the test function to determine the parametrization scope, and not only the scope of the fixtures being parametrized.
- `#3973 <https://github.com/pytest-dev/pytest/issues/3973>`_: Fix crash of the assertion rewriter if a test changed the current working directory without restoring it afterwards.
- `#3998 <https://github.com/pytest-dev/pytest/issues/3998>`_: Fix issue that prevented some caplog properties (for example ``record_tuples``) from being available when entering the debugger with ``--pdb``.
- `#3999 <https://github.com/pytest-dev/pytest/issues/3999>`_: Fix ``UnicodeDecodeError`` in python2.x when a class returns a non-ascii binary ``__repr__`` in an assertion which also contains non-ascii text.
Improved Documentation
----------------------
- `#3996 <https://github.com/pytest-dev/pytest/issues/3996>`_: New `Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`_ page shows all currently
deprecated features, the rationale to do so, and alternatives to update your code. It also list features removed
from pytest in past major releases to help those with ancient pytest versions to upgrade.
Trivial/Internal Changes
------------------------
- `#3955 <https://github.com/pytest-dev/pytest/issues/3955>`_: Improve pre-commit detection for changelog filenames
- `#3975 <https://github.com/pytest-dev/pytest/issues/3975>`_: Remove legacy code around im_func as that was python2 only
pytest 3.8.0 (2018-09-05)
=========================
Deprecations and Removals
-------------------------
- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: ``Config.warn`` and ``Node.warn`` have been
deprecated, see `<https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`_ for rationale and
examples.
- `#3936 <https://github.com/pytest-dev/pytest/issues/3936>`_: ``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped,
making it possible to actually use regular expressions to check the warning message.
**Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend
on the old behavior.
Features
--------
- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use
the standard warnings filters to manage those warnings. This introduces ``PytestWarning``,
``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API.
Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`_ for more info.
- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is
configured. This makes pytest more compliant with
`PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See
`the docs <https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for
more info.
- `#3251 <https://github.com/pytest-dev/pytest/issues/3251>`_: Warnings are now captured and displayed during test collection.
- `#3784 <https://github.com/pytest-dev/pytest/issues/3784>`_: ``PYTEST_DISABLE_PLUGIN_AUTOLOAD`` environment variable disables plugin auto-loading when set.
- `#3829 <https://github.com/pytest-dev/pytest/issues/3829>`_: Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage.
- `#3837 <https://github.com/pytest-dev/pytest/issues/3837>`_: Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature.
Bug Fixes
---------
- `#3911 <https://github.com/pytest-dev/pytest/issues/3911>`_: Terminal writer now takes into account unicode character width when writing out progress.
- `#3913 <https://github.com/pytest-dev/pytest/issues/3913>`_: Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments.
- `#3918 <https://github.com/pytest-dev/pytest/issues/3918>`_: Improve performance of assertion rewriting.
Improved Documentation
----------------------
- `#3566 <https://github.com/pytest-dev/pytest/issues/3566>`_: Added a blurb in usage.rst for the usage of -r flag which is used to show an extra test summary info.
- `#3907 <https://github.com/pytest-dev/pytest/issues/3907>`_: Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``.
Trivial/Internal Changes
------------------------
- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests.
pytest 3.7.4 (2018-08-29)
=========================
Bug Fixes
---------
- `#3506 <https://github.com/pytest-dev/pytest/issues/3506>`_: Fix possible infinite recursion when writing ``.pyc`` files.
- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Cache plugin now obeys the ``-q`` flag when ``--last-failed`` and ``--failed-first`` flags are used.
- `#3883 <https://github.com/pytest-dev/pytest/issues/3883>`_: Fix bad console output when using ``console_output_style=classic``.
- `#3888 <https://github.com/pytest-dev/pytest/issues/3888>`_: Fix macOS specific code using ``capturemanager`` plugin in doctests.
Improved Documentation
----------------------
- `#3902 <https://github.com/pytest-dev/pytest/issues/3902>`_: Fix pytest.org links
pytest 3.7.3 (2018-08-26)
=========================
Bug Fixes
---------
- `#3033 <https://github.com/pytest-dev/pytest/issues/3033>`_: Fixtures during teardown can again use ``capsys`` and ``cafd`` to inspect output captured during tests.
- `#3033 <https://github.com/pytest-dev/pytest/issues/3033>`_: Fixtures during teardown can again use ``capsys`` and ``capfd`` to inspect output captured during tests.
- `#3773 <https://github.com/pytest-dev/pytest/issues/3773>`_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option.
@@ -141,15 +340,10 @@ pytest 3.7.0 (2018-07-30)
Deprecations and Removals
-------------------------
- `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_: ``pytest_namespace`` has been deprecated.
See the documentation for ``pytest_namespace`` hook for suggestions on how to deal
with this in plugins which use this functionality.
- `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_: ``pytest_namespace`` has been `deprecated <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`_.
- `#3661 <https://github.com/pytest-dev/pytest/issues/3661>`_: Calling a fixture function directly, as opposed to request them in a test function, now issues a ``RemovedInPytest4Warning``. It will be changed into an error in pytest ``4.0``.
This is a great source of confusion to new users, which will often call the fixture functions and request them from test functions interchangeably, which breaks the fixture resolution model.
- `#3661 <https://github.com/pytest-dev/pytest/issues/3661>`_: Calling a fixture function directly, as opposed to request them in a test function, now issues a ``RemovedInPytest4Warning``. See `the documentation for rationale and examples <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`_.
@@ -511,7 +705,7 @@ Deprecations and Removals
<https://github.com/pytest-dev/pytest/issues/2770>`_)
- Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
files, because they "leak" to the entire directory tree. (`#3084
files, because they "leak" to the entire directory tree. `See the docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`_ for the rationale behind this decision (`#3084
<https://github.com/pytest-dev/pytest/issues/3084>`_)

View File

@@ -28,10 +28,13 @@ taking a lot of time to make a new one.
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
git tag <VERSION>
git push git@github.com:pytest-dev/pytest.git <VERSION>
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR into ``master``.
#. Send an email announcement with the contents from::
doc/en/announce/release-<VERSION>.rst

View File

@@ -1,5 +1,5 @@
.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png
:target: http://docs.pytest.org
.. image:: https://docs.pytest.org/en/latest/_static/pytest1.png
:target: https://docs.pytest.org/en/latest/
:align: center
:alt: pytest
@@ -15,8 +15,9 @@
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
:target: https://pypi.org/project/pytest/
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
:target: https://coveralls.io/r/pytest-dev/pytest
.. image:: https://codecov.io/gh/pytest-dev/pytest/branch/master/graph/badge.svg
:target: https://codecov.io/gh/pytest-dev/pytest
:alt: Code coverage Status
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest
@@ -25,7 +26,7 @@
:target: https://ci.appveyor.com/project/pytestbot/pytest
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:target: https://github.com/ambv/black
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
:target: https://www.codetriage.com/pytest-dev/pytest
@@ -66,23 +67,23 @@ To execute it::
========================== 1 failed in 0.04 seconds ===========================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <https://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.
Features
--------
- Detailed info on failing `assert statements <http://docs.pytest.org/en/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
- Detailed info on failing `assert statements <https://docs.pytest.org/en/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
- `Auto-discovery
<http://docs.pytest.org/en/latest/goodpractices.html#python-test-discovery>`_
<https://docs.pytest.org/en/latest/goodpractices.html#python-test-discovery>`_
of test modules and functions;
- `Modular fixtures <http://docs.pytest.org/en/latest/fixture.html>`_ for
- `Modular fixtures <https://docs.pytest.org/en/latest/fixture.html>`_ for
managing small or parametrized long-lived test resources;
- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
`nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
- Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
`nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
@@ -92,7 +93,7 @@ Features
Documentation
-------------
For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org.
For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/latest/.
Bugs/Requests
@@ -104,7 +105,7 @@ Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issue
Changelog
---------
Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version.
Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version.
License

View File

@@ -1,35 +1,30 @@
environment:
COVERALLS_REPO_TOKEN:
secure: 2NJ5Ct55cHJ9WEg3xbSqCuv0rdgzzb6pnzOIG5OkMbTndw3wOBrXntWFoQrXiMFi
# this is pytest's token in coveralls.io, encrypted
# using pytestbot account as detailed here:
# https://www.appveyor.com/docs/build-configuration#secure-variables
matrix:
# coveralls is not in the default env list
- TOXENV: "coveralls"
# note: please use "tox --listenvs" to populate the build matrix below
- TOXENV: "linting"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "py27"
- TOXENV: "py34"
- TOXENV: "py35"
- TOXENV: "py36"
- TOXENV: "py37"
# - TOXENV: "pypy" reenable when we are able to provide a scandir wheel or build scandir
- TOXENV: "py27-pexpect"
- TOXENV: "pypy"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "py27-xdist"
- TOXENV: "py27-trial"
- TOXENV: "py27-numpy"
- TOXENV: "py27-pluggymaster"
- TOXENV: "py36-pexpect"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "py36-xdist"
- TOXENV: "py36-trial"
- TOXENV: "py36-numpy"
- TOXENV: "py36-pluggymaster"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "py27-nobyte"
- TOXENV: "doctesting"
- TOXENV: "py36-freeze"
PYTEST_NO_COVERAGE: "1"
- TOXENV: "docs"
PYTEST_NO_COVERAGE: "1"
install:
- echo Installed Pythons
@@ -37,12 +32,19 @@ install:
- if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
- C:\Python36\python -m pip install --upgrade pip
- C:\Python36\python -m pip install --upgrade --pre tox
build: false # Not a C# project, build stuff at the test step instead.
before_test:
- call scripts\prepare-coverage.bat
test_script:
- call scripts\call-tox.bat
- C:\Python36\python -m tox
on_success:
- call scripts\upload-coverage.bat
cache:
- '%LOCALAPPDATA%\pip\cache'

View File

@@ -6,6 +6,10 @@ Release announcements
:maxdepth: 2
release-3.8.2
release-3.8.1
release-3.8.0
release-3.7.4
release-3.7.3
release-3.7.2
release-3.7.1

View File

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

View File

@@ -0,0 +1,38 @@
pytest-3.8.0
=======================================
The pytest team is proud to announce the 3.8.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:
* Anthony Sottile
* Bruno Oliveira
* CrazyMerlyn
* Daniel Hahler
* Fabio Zadrozny
* Jeffrey Rackauckas
* Ronny Pfannschmidt
* Virgil Dupras
* dhirensr
* hoefling
* wim glenn
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,25 @@
pytest-3.8.1
=======================================
pytest 3.8.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:
* Ankit Goel
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Maximilian Albert
* Ronny Pfannschmidt
* William Jamir Silva
* wim glenn
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-3.8.2
=======================================
pytest 3.8.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Ankit Goel
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Denis Otkidach
* Harry Percival
* Jeffrey Rackauckas
* Jose Carlos Menezes
* Ronny Pfannschmidt
* Zac-HD
* iwanb
Happy testing,
The pytest Development Team

View File

@@ -264,8 +264,12 @@ Advanced assertion introspection
Reporting details about a failing assertion is achieved by rewriting assert
statements before they are run. Rewritten assert statements put introspection
information into the assertion failure message. ``pytest`` only rewrites test
modules directly discovered by its test collection process, so asserts in
supporting modules which are not themselves test modules will not be rewritten.
modules directly discovered by its test collection process, so **asserts in
supporting modules which are not themselves test modules will not be rewritten**.
You can manually enable assertion rewriting for an imported module by calling
`register_assert_rewrite <https://docs.pytest.org/en/latest/writing_plugins.html#assertion-rewriting>`_
before you import it (a good place to do that is in ``conftest.py``).
.. note::

View File

@@ -7,14 +7,16 @@ Keeping backwards compatibility has a very high priority in the pytest project.
With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
To communicate changes we are already issuing deprecation warnings, but they are not displayed by default. In pytest 3.0 we changed the default setting so that pytest deprecation warnings are displayed if not explicitly silenced (with ``--disable-pytest-warnings``).
To communicate changes we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible.
We will only remove deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we will not remove it in 4.0 but in 5.0).
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
Deprecation Roadmap
-------------------
We track deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.
Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`.
Following our deprecation policy, after starting issuing deprecation warnings we keep features for *at least* two minor versions before considering removal.
We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.

View File

@@ -329,7 +329,7 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"python": ("http://docs.python.org/3", None)}
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
def setup(app):

View File

@@ -39,6 +39,7 @@ Full pytest documentation
bash-completion
backwards-compatibility
deprecations
historical-notes
license
contributing

View File

@@ -32,7 +32,7 @@ Here's a summary what ``pytest`` uses ``rootdir`` for:
class name, function name and parametrization (if any).
* Is used by plugins as a stable location to store project/test run specific information;
for example, the internal :ref:`cache <cache>` plugin creates a ``.cache`` subdirectory
for example, the internal :ref:`cache <cache>` plugin creates a ``.pytest_cache`` subdirectory
in ``rootdir`` to store its cross-test run state.
Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or

325
doc/en/deprecations.rst Normal file
View File

@@ -0,0 +1,325 @@
.. _deprecations:
Deprecations and Removals
=========================
This page lists all pytest features that are currently deprecated or have been removed in past major releases.
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
should be used instead.
Deprecated Features
-------------------
Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
:ref:`standard warning filters <warnings>`.
``Config.warn`` and ``Node.warn``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.8
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.
``Config.warn`` should be replaced by calls to the standard ``warnings.warn``, example:
.. code-block:: python
config.warn("C1", "some warning")
Becomes:
.. code-block:: python
warnings.warn(pytest.PytestWarning("some warning"))
``Node.warn`` now supports two signatures:
* ``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.
``pytest_namespace``
~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.7
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
class MySymbol:
...
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
import pytest
def pytest_configure():
pytest.my_symbol = MySymbol()
Calling fixtures directly
~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.7
Calling a fixture function directly, as opposed to request them in a test function, is deprecated.
For example:
.. code-block:: python
@pytest.fixture
def cell():
return ...
@pytest.fixture
def full_cell():
cell = cell()
cell.make_full()
return cell
This is a great source of confusion to new users, which will often call the fixture functions and request them from test functions interchangeably, which breaks the fixture resolution model.
In those cases just request the function directly in the dependent fixture:
.. code-block:: python
@pytest.fixture
def cell():
return ...
@pytest.fixture
def full_cell(cell):
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:
.. 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):
...
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.
``yield`` tests
~~~~~~~~~~~~~~~
.. deprecated:: 3.0
pytest supports ``yield``-style tests, where a test function actually ``yield`` functions and values
that are then turned into proper test methods. Example:
.. code-block:: python
def check(x, y):
assert x ** x == y
def test_squared():
yield check, 2, 4
yield check, 3, 9
This would result into two actual test functions being generated.
This form of test function doesn't support fixtures properly, and users should switch to ``pytest.mark.parametrize``:
.. code-block:: python
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
def test_squared():
assert x ** x == y
``pytest_funcarg__`` prefix
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
``[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.*
Reinterpretation mode has now been removed and only plain and rewrite
mode are available, consequently the ``--assert=reinterp`` option is
no longer available. This also means files imported from plugins or
``conftest.py`` will not benefit from improved assertions by
default, you should use ``pytest.register_assert_rewrite()`` to
explicitly turn on assertion rewriting for those files.
Removed command-line options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 3.0.*
The following deprecated commandline options were removed:
* ``--genscript``: no longer supported;
* ``--no-assert``: use ``--assert=plain`` instead;
* ``--nomagic``: use ``--assert=plain`` instead;
* ``--report``: use ``-r`` instead;
py.test-X* entry points
~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 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
points also created broken entry points in wheels, so removing them also
removes a source of confusion for users.

View File

@@ -31,7 +31,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected
@@ -44,7 +44,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected
@@ -64,7 +64,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -77,7 +77,7 @@ You can also select on the class::
$ pytest -v test_server.py::TestClass
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -90,7 +90,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
@@ -128,7 +128,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 3 deselected
@@ -141,7 +141,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 1 deselected
@@ -156,7 +156,7 @@ Or to select "http" and "quick" tests::
$ pytest -k "http or quick" -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items / 2 deselected
@@ -200,17 +200,17 @@ You can ask which markers exist for your test suite - the list includes our just
$ pytest --markers
@pytest.mark.webtest: mark a test as a webtest.
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see https://docs.pytest.org/en/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/latest/skipping.html
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/latest/parametrize.html for more info and examples.
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@@ -376,17 +376,17 @@ The ``--markers`` option always gives you a list of available markers::
$ pytest --markers
@pytest.mark.env(name): mark test to run only on named environment
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings
@pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings
@pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test.
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
@pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see https://docs.pytest.org/en/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/latest/skipping.html
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples.
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/latest/parametrize.html for more info and examples.
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.

View File

@@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode::
nonpython $ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items

View File

@@ -613,9 +613,9 @@ get on the terminal - we are working on that)::
failure_demo.py:261: AssertionError
============================= warnings summary =============================
<undetermined location>
Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
Please use Metafunc.parametrize instead.
$REGENDOC_TMPDIR/assertion/failure_demo.py:24: RemovedInPytest4Warning: Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
Please use Metafunc.parametrize instead.
metafunc.addcall(funcargs=dict(param1=3, param2=6))
-- Docs: http://doc.pytest.org/en/latest/warnings.html
-- Docs: https://docs.pytest.org/en/latest/warnings.html
================== 42 failed, 1 warnings in 0.12 seconds ===================

View File

@@ -357,7 +357,7 @@ which will add info only when run with "--v"::
$ pytest -v
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
info1: did you know that ...
did you?

View File

@@ -171,6 +171,7 @@ to cause the decorated ``smtp_connection`` fixture function to only be invoked
once per test *module* (the default is to invoke once per test *function*).
Multiple test functions in a test module will thus
each receive the same ``smtp_connection`` fixture instance, thus saving time.
Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``package`` or ``session``.
The next example puts the fixture function into a separate ``conftest.py`` file
so that tests from multiple test modules in the directory can
@@ -726,7 +727,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 3 items
@@ -769,7 +770,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
@@ -838,7 +839,7 @@ 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-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.6
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items

View File

@@ -7,7 +7,7 @@ pytest-2.3: reasoning for fixture/funcarg evolution
**Target audience**: Reading this document requires basic knowledge of
python testing, xUnit setup methods and the (previous) basic pytest
funcarg mechanism, see http://pytest.org/2.2.4/funcargs.html
funcarg mechanism, see https://docs.pytest.org/en/latest/historical-notes.html#funcargs-and-pytest-funcarg.
If you are new to pytest, then you can simply ignore this
section and read the other sections.

View File

@@ -1,7 +1,7 @@
Installation and Getting Started
===================================
**Pythons**: Python 2.7, 3.4, 3.5, 3.6, Jython, PyPy-2.3
**Pythons**: Python 2.7, 3.4, 3.5, 3.6, 3.7, Jython, PyPy-2.3
**Platforms**: Unix/Posix and Windows

View File

@@ -52,6 +52,8 @@ should add ``--strict`` to ``addopts``:
serial
.. _marker-revamp:
Marker revamp and iteration
---------------------------

View File

@@ -611,6 +611,8 @@ 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
test execution:
@@ -866,6 +868,11 @@ Contains comma-separated list of modules that should be loaded as plugins:
export PYTEST_PLUGINS=mymodule.plugin,xdist
PYTEST_DISABLE_PLUGIN_AUTOLOAD
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be
loaded.
PYTEST_CURRENT_TEST
~~~~~~~~~~~~~~~~~~~
@@ -935,6 +942,7 @@ passed multiple times. The expected format is ``name=value``. For example::
* ``classic``: classic pytest output.
* ``progress``: like classic pytest output, but with a progress indicator.
* ``count``: like progress, but shows progress as the number of tests completed instead of a percent.
The default is ``progress``, but you can fallback to ``classic`` if you prefer or
the new mode is causing unexpected problems:
@@ -1248,15 +1256,25 @@ passed multiple times. The expected format is ``name=value``. For example::
One or more Glob-style file patterns determining which python files
are considered as test modules. Search for multiple glob patterns by
adding a space between patterns::
adding a space between patterns:
.. code-block:: ini
[pytest]
python_files = test_*.py check_*.py example_*.py
By default, pytest will consider any file matching with ``test_*.py``
and ``*_test.py`` globs as a test module.
Or one per line:
.. code-block:: ini
[pytest]
python_files =
test_*.py
check_*.py
example_*.py
By default, files matching ``test_*.py`` and ``*_test.py`` will be considered
test modules.
.. confval:: python_functions

View File

@@ -277,7 +277,7 @@ on a particular platform::
~~~~~~~~~~~~~~~~~~~~
If you want to be more specific as to why the test is failing, you can specify
a single exception, or a list of exceptions, in the ``raises`` argument.
a single exception, or a tuple of exceptions, in the ``raises`` argument.
.. code-block:: python

View File

@@ -14,6 +14,9 @@ Talks and Tutorials
Books
---------------------------------------------
- `pytest Quick Start Guide, by Bruno Oliveira (2018)
<https://www.packtpub.com/web-development/pytest-quick-start-guide>`_.
- `Python Testing with pytest, by Brian Okken (2017)
<https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.

View File

@@ -140,6 +140,49 @@ will be shown (because KeyboardInterrupt is caught by pytest). By using this
option you make sure a trace is shown.
.. _`pytest.detailed_failed_tests_usage`:
Detailed summary report
-----------------------
.. versionadded:: 2.9
The ``-r`` flag can be used to display test results summary 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::
$ pytest -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
Here is the full list of available characters that can be used:
- ``f`` - failed
- ``E`` - error
- ``s`` - skipped
- ``x`` - xfailed
- ``X`` - xpassed
- ``p`` - passed
- ``P`` - passed with output
- ``a`` - all except ``pP``
More than one character can be used, so for example to only see failed and skipped tests, you can execute::
$ pytest -rfs
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
.. _pdb-option:
Dropping to PDB_ (Python Debugger) on failures
@@ -212,7 +255,7 @@ Pytest supports the use of ``breakpoint()`` with the following behaviours:
- When ``breakpoint()`` is called and ``PYTHONBREAKPOINT`` is set to the default value, pytest will use the custom internal PDB trace UI instead of the system default ``Pdb``.
- When tests are complete, the system will default back to the system ``Pdb`` trace UI.
- If ``--pdb`` is called on execution of pytest, the custom internal Pdb trace UI is used on ``bothbreakpoint()`` and failed tests/unhandled exceptions.
- If ``--pdb`` is called on execution of pytest, the custom internal Pdb trace UI is used on both ``breakpoint()`` and failed tests/unhandled exceptions.
- If ``--pdbcls`` is used, the custom class debugger will be executed when a test fails (as expected within existing behaviour), but also when ``breakpoint()`` is called from within a test, the custom class debugger will be instantiated.
.. _durations:

View File

@@ -29,15 +29,12 @@ Running pytest now produces this output::
test_show_warnings.py . [100%]
============================= warnings summary =============================
test_show_warnings.py::test_one
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
warnings.warn(UserWarning("api v1, should use functions from v2"))
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
warnings.warn(UserWarning("api v1, should use functions from v2"))
-- Docs: http://doc.pytest.org/en/latest/warnings.html
-- Docs: https://docs.pytest.org/en/latest/warnings.html
=================== 1 passed, 1 warnings in 0.12 seconds ===================
Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors::
@@ -78,6 +75,53 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
documentation for other examples and advanced usage.
Disabling warning summary
-------------------------
Although not recommended, you can use the ``--disable-warnings`` command-line option to suppress the
warning summary entirely from the test run output.
Disabling warning capture entirely
----------------------------------
This plugin is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
.. code-block:: ini
[pytest]
addopts = -p no:warnings
Or passing ``-p no:warnings`` in the command-line. This might be useful if your test suites handles warnings
using an external system.
.. _`deprecation-warnings`:
DeprecationWarning and PendingDeprecationWarning
------------------------------------------------
.. versionadded:: 3.8
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` if no other warning filters
are configured.
To disable showing ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings, you might define any warnings
filter either in the command-line or in the ini file, or you can use:
.. code-block:: ini
[pytest]
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
.. note::
This makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
for an example of that).
.. _`filterwarnings`:
@@ -144,18 +188,6 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
Disabling warning capture
-------------------------
This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
.. code-block:: ini
[pytest]
addopts = -p no:warnings
Or passing ``-p no:warnings`` in the command-line.
.. _`asserting warnings`:
.. _assertwarnings:
@@ -296,3 +328,53 @@ You can also use it as a contextmanager::
def test_global():
with pytest.deprecated_call():
myobject.deprecated_method()
.. _internal-warnings:
Internal pytest warnings
------------------------
.. versionadded:: 3.8
pytest may generate its own warnings in some situations, such as improper usage or deprecated features.
For example, pytest will emit a warning if it encounters a class that matches :confval:`python_classes` but also
defines an ``__init__`` constructor, as this prevents the class from being instantiated:
.. code-block:: python
# content of test_pytest_warnings.py
class Test:
def __init__(self):
pass
def test_foo(self):
assert 1 == 1
::
$ pytest test_pytest_warnings.py -q
============================= warnings summary =============================
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
class Test:
-- Docs: https://docs.pytest.org/en/latest/warnings.html
1 warnings in 0.12 seconds
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.
Please read our :ref:`backwards-compatibility` to learn how we proceed about deprecating and eventually removing
features.
The following warning types ares used by pytest and are part of the public API:
.. autoclass:: pytest.PytestWarning
.. autoclass:: pytest.PytestDeprecationWarning
.. autoclass:: pytest.RemovedInPytest4Warning
.. autoclass:: pytest.PytestExperimentalApiWarning

View File

@@ -418,11 +418,10 @@ additionally it is possible to copy examples for a example folder before running
test_example.py .. [100%]
============================= warnings summary =============================
test_example.py::test_plugin
$REGENDOC_TMPDIR/test_example.py:4: PytestExerimentalApiWarning: testdir.copy_example is an experimental api that may change over time
testdir.copy_example("test_example.py")
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
testdir.copy_example("test_example.py")
-- Docs: http://doc.pytest.org/en/latest/warnings.html
-- Docs: https://docs.pytest.org/en/latest/warnings.html
=================== 2 passed, 1 warnings in 0.12 seconds ===================
For more information about the result object that ``runpytest()`` returns, and

View File

@@ -1,8 +0,0 @@
REM skip "coveralls" run in PRs or forks
if "%TOXENV%" == "coveralls" (
if not defined COVERALLS_REPO_TOKEN (
echo skipping coveralls run because COVERALLS_REPO_TOKEN is not defined
exit /b 0
)
)
C:\Python36\python -m tox

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env python
"""Used by .pre-commit-config.yaml"""
import sys
if __name__ == "__main__":
print(" ".join(sys.argv[1:]))
sys.exit(1)

View File

@@ -0,0 +1,10 @@
REM scripts called by AppVeyor to setup the environment variables to enable coverage
if not defined PYTEST_NO_COVERAGE (
set "COVERAGE_FILE=%CD%\.coverage"
set "COVERAGE_PROCESS_START=%CD%\.coveragerc"
set "_PYTEST_TOX_COVERAGE_RUN=coverage run -m"
set "_PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess"
echo Coverage setup completed
) else (
echo Skipping coverage setup, PYTEST_NO_COVERAGE is set
)

View File

@@ -9,11 +9,11 @@ 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:
http://doc.pytest.org/en/latest/changelog.html
https://docs.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
https://docs.pytest.org/en/latest/
As usual, you can upgrade from pypi via:

View File

@@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:

View File

@@ -0,0 +1,11 @@
REM script called by AppVeyor to combine and upload coverage information to codecov
if not defined PYTEST_NO_COVERAGE (
echo Prepare to upload coverage information
C:\Python36\Scripts\pip install codecov
C:\Python36\Scripts\coverage combine
C:\Python36\Scripts\coverage xml --ignore-errors
C:\Python36\Scripts\coverage report -m --ignore-errors
C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows
) else (
echo Skipping coverage upload, PYTEST_NO_COVERAGE is set
)

View File

@@ -59,7 +59,7 @@ def get_environment_marker_support_level():
def main():
extras_require = {}
install_requires = [
"py>=1.5.0",
"py>=1.5.0", # if py gets upgrade to >=1.6, remove _width_of_current_line in terminal.py
"six>=1.10.0",
"setuptools",
"attrs>=17.4.0",
@@ -92,7 +92,7 @@ def main():
description="pytest: simple powerful testing with Python",
long_description=long_description,
use_scm_version={"write_to": "src/_pytest/_version.py"},
url="http://pytest.org",
url="https://docs.pytest.org/en/latest/",
project_urls={
"Source": "https://github.com/pytest-dev/pytest",
"Tracker": "https://github.com/pytest-dev/pytest/issues",

View File

@@ -8,6 +8,7 @@ import marshal
import os
import re
import six
import string
import struct
import sys
import types
@@ -16,7 +17,8 @@ import atomicwrites
import py
from _pytest.assertion import util
from _pytest.compat import PurePath, spec_from_file_location
from _pytest.paths import fnmatch_ex
# pytest caches rewritten pycs in __pycache__.
if hasattr(imp, "get_tag"):
@@ -45,14 +47,6 @@ else:
return ast.Call(a, b, c, None, None)
if sys.version_info >= (3, 4):
from importlib.util import spec_from_file_location
else:
def spec_from_file_location(*_, **__):
return None
class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts."""
@@ -64,12 +58,27 @@ class AssertionRewritingHook(object):
self._rewritten_names = set()
self._register_with_pkg_resources()
self._must_rewrite = set()
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
# which might result in infinite recursion (#3506)
self._writing_pyc = False
self._basenames_to_check_rewrite = {"conftest"}
self._marked_for_rewrite_cache = {}
self._session_paths_checked = False
def set_session(self, session):
self.session = session
self._session_paths_checked = False
def _imp_find_module(self, name, path=None):
"""Indirection so we can mock calls to find_module originated from the hook during testing"""
return imp.find_module(name, path)
def find_module(self, name, path=None):
if self._writing_pyc:
return None
state = self.config._assertstate
if self._early_rewrite_bailout(name, state):
return None
state.trace("find_module called for: %s" % name)
names = name.rsplit(".", 1)
lastname = names[-1]
@@ -82,7 +91,7 @@ class AssertionRewritingHook(object):
pth = path[0]
if pth is None:
try:
fd, fn, desc = imp.find_module(lastname, path)
fd, fn, desc = self._imp_find_module(lastname, path)
except ImportError:
return None
if fd is not None:
@@ -151,12 +160,54 @@ class AssertionRewritingHook(object):
# Probably a SyntaxError in the test.
return None
if write:
_write_pyc(state, co, source_stat, pyc)
self._writing_pyc = True
try:
_write_pyc(state, co, source_stat, pyc)
finally:
self._writing_pyc = False
else:
state.trace("found cached rewritten pyc for %r" % (fn,))
self.modules[name] = co, pyc
return self
def _early_rewrite_bailout(self, name, state):
"""
This is a fast way to get out of rewriting modules. Profiling has
shown that the call to imp.find_module (inside of the find_module
from this class) is a major slowdown, so, this method tries to
filter what we're sure won't be rewritten before getting to it.
"""
if self.session is not None and not self._session_paths_checked:
self._session_paths_checked = True
for path in self.session._initialpaths:
# Make something as c:/projects/my_project/path.py ->
# ['c:', 'projects', 'my_project', 'path.py']
parts = str(path).split(os.path.sep)
# add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
# Note: conftest already by default in _basenames_to_check_rewrite.
parts = name.split(".")
if parts[-1] in self._basenames_to_check_rewrite:
return False
# For matching the name it must be as if it was a filename.
path = PurePath(os.path.sep.join(parts) + ".py")
for pat in self.fnpats:
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
# on the name alone because we need to match against the full path
if os.path.dirname(pat):
return False
if fnmatch_ex(pat, path):
return False
if self._is_marked_for_rewrite(name, state):
return False
state.trace("early skip of rewriting module: %s" % (name,))
return True
def _should_rewrite(self, name, fn_pypath, state):
# always rewrite conftest files
fn = str(fn_pypath)
@@ -176,12 +227,20 @@ class AssertionRewritingHook(object):
state.trace("matched test file %r" % (fn,))
return True
for marked in self._must_rewrite:
if name == marked or name.startswith(marked + "."):
state.trace("matched marked file %r (from %r)" % (name, marked))
return True
return self._is_marked_for_rewrite(name, state)
return False
def _is_marked_for_rewrite(self, name, state):
try:
return self._marked_for_rewrite_cache[name]
except KeyError:
for marked in self._must_rewrite:
if name == marked or name.startswith(marked + "."):
state.trace("matched marked file %r (from %r)" % (name, marked))
self._marked_for_rewrite_cache[name] = True
return True
self._marked_for_rewrite_cache[name] = False
return False
def mark_rewrite(self, *names):
"""Mark import names as needing to be rewritten.
@@ -198,24 +257,29 @@ class AssertionRewritingHook(object):
):
self._warn_already_imported(name)
self._must_rewrite.update(names)
self._marked_for_rewrite_cache.clear()
def _warn_already_imported(self, name):
self.config.warn(
"P1", "Module already imported so cannot be rewritten: %s" % name
from _pytest.warning_types import PytestWarning
from _pytest.warnings import _issue_config_warning
_issue_config_warning(
PytestWarning("Module already imported so cannot be rewritten: %s" % name),
self.config,
)
def load_module(self, name):
# If there is an existing module object named 'fullname' in
# sys.modules, the loader must use that existing module. (Otherwise,
# the reload() builtin will not work correctly.)
if name in sys.modules:
return sys.modules[name]
co, pyc = self.modules.pop(name)
# I wish I could just call imp.load_compiled here, but __file__ has to
# be set properly. In Python 3.2+, this all would be handled correctly
# by load_compiled.
mod = sys.modules[name] = imp.new_module(name)
if name in sys.modules:
# If there is an existing module object named 'fullname' in
# sys.modules, the loader must use that existing module. (Otherwise,
# the reload() builtin will not work correctly.)
mod = sys.modules[name]
else:
# I wish I could just call imp.load_compiled here, but __file__ has to
# be set properly. In Python 3.2+, this all would be handled correctly
# by load_compiled.
mod = sys.modules[name] = imp.new_module(name)
try:
mod.__file__ = co.co_filename
# Normally, this attribute is 3.2+.
@@ -232,7 +296,7 @@ class AssertionRewritingHook(object):
def is_package(self, name):
try:
fd, fn, desc = imp.find_module(name)
fd, fn, desc = self._imp_find_module(name)
except ImportError:
return False
if fd is not None:
@@ -403,10 +467,14 @@ def _saferepr(obj):
"""
r = py.io.saferepr(obj)
if isinstance(r, six.text_type):
return r.replace(u"\n", u"\\n")
else:
return r.replace(b"\n", b"\\n")
# only occurs in python2.x, repr must return text in python3+
if isinstance(r, bytes):
# Represent unprintable bytes as `\x##`
r = u"".join(
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
for c in r
)
return r.replace(u"\n", u"\\n")
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
@@ -737,13 +805,17 @@ class AssertionRewriter(ast.NodeVisitor):
the expression is false.
"""
if isinstance(assert_.test, ast.Tuple) and self.config is not None:
fslocation = (self.module_path, assert_.lineno)
self.config.warn(
"R1",
"assertion is always true, perhaps " "remove parentheses?",
fslocation=fslocation,
if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
from _pytest.warning_types import PytestWarning
import warnings
warnings.warn_explicit(
PytestWarning("assertion is always true, perhaps remove parentheses?"),
category=None,
filename=str(self.module_path),
lineno=assert_.lineno,
)
self.statements = []
self.variables = []
self.variable_counter = itertools.count()

View File

@@ -33,7 +33,7 @@ See [the docs](https://docs.pytest.org/en/latest/cache.html) for more informatio
@attr.s
class Cache(object):
_cachedir = attr.ib(repr=False)
_warn = attr.ib(repr=False)
_config = attr.ib(repr=False)
@classmethod
def for_config(cls, config):
@@ -41,14 +41,19 @@ class Cache(object):
if config.getoption("cacheclear") and cachedir.exists():
shutil.rmtree(str(cachedir))
cachedir.mkdir()
return cls(cachedir, config.warn)
return cls(cachedir, config)
@staticmethod
def cache_dir_from_config(config):
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args):
self._warn(code="I9", message=fmt.format(**args) if args else fmt)
from _pytest.warnings import _issue_config_warning
from _pytest.warning_types import PytestWarning
_issue_config_warning(
PytestWarning(fmt.format(**args) if args else fmt), self._config
)
def makedir(self, name):
""" return a directory path object with the given name. If the
@@ -110,15 +115,18 @@ class Cache(object):
else:
with f:
json.dump(value, f, indent=2, sort_keys=True)
self._ensure_readme()
def _ensure_readme(self):
self._ensure_supporting_files()
def _ensure_supporting_files(self):
"""Create supporting files in the cache dir that are not really part of the cache."""
if self._cachedir.is_dir():
readme_path = self._cachedir / "README.md"
if not readme_path.is_file():
readme_path.write_text(README_CONTENT)
msg = u"# created by pytest automatically, do not change\n*"
self._cachedir.joinpath(".gitignore").write_text(msg, encoding="UTF-8")
class LFPlugin(object):
""" Plugin which implements the --lf (run last-failing) option """
@@ -132,17 +140,14 @@ class LFPlugin(object):
self._no_failures_behavior = self.config.getoption("last_failed_no_failures")
def pytest_report_collectionfinish(self):
if self.active:
if self.active and self.config.getoption("verbose") >= 0:
if not self._previously_failed_count:
mode = "run {} (no recorded failures)".format(
self._no_failures_behavior
)
else:
noun = "failure" if self._previously_failed_count == 1 else "failures"
suffix = " first" if self.config.getoption("failedfirst") else ""
mode = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
)
return None
noun = "failure" if self._previously_failed_count == 1 else "failures"
suffix = " first" if self.config.getoption("failedfirst") else ""
mode = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
)
return "run-last-failure: %s" % mode
def pytest_runtest_logreport(self, report):

View File

@@ -23,7 +23,7 @@ except ImportError: # pragma: no cover
# Only available in Python 3.4+ or as a backport
enum = None
__all__ = ["Path"]
__all__ = ["Path", "PurePath"]
_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
@@ -42,9 +42,9 @@ PY36 = sys.version_info[:2] >= (3, 6)
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
if PY36:
from pathlib import Path
from pathlib import Path, PurePath
else:
from pathlib2 import Path
from pathlib2 import Path, PurePath
if _PY3:
@@ -56,6 +56,14 @@ else:
from collections import Mapping, Sequence # noqa
if sys.version_info >= (3, 4):
from importlib.util import spec_from_file_location
else:
def spec_from_file_location(*_, **__):
return None
def _format_args(func):
return str(signature(func))

View File

@@ -51,6 +51,8 @@ def main(args=None, plugins=None):
:arg plugins: list of plugin objects to be auto-registered during
initialization.
"""
from _pytest.main import EXIT_USAGEERROR
try:
try:
config = _prepareconfig(args, plugins)
@@ -69,7 +71,7 @@ def main(args=None, plugins=None):
tw = py.io.TerminalWriter(sys.stderr)
for msg in e.args:
tw.line("ERROR: {}\n".format(msg), red=True)
return 4
return EXIT_USAGEERROR
class cmdline(object): # compatibility namespace
@@ -176,7 +178,9 @@ def _prepareconfig(args=None, plugins=None):
else:
pluginmanager.register(plugin)
if warning:
config.warn("C1", warning)
from _pytest.warnings import _issue_config_warning
_issue_config_warning(warning, config=config)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
@@ -347,6 +351,7 @@ class PytestPluginManager(PluginManager):
else None
)
self._noconftest = namespace.noconftest
self._using_pyargs = namespace.pyargs
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
@@ -412,12 +417,21 @@ class PytestPluginManager(PluginManager):
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
if hasattr(mod, "pytest_plugins") and self._configured:
if (
hasattr(mod, "pytest_plugins")
and self._configured
and not self._using_pyargs
):
from _pytest.deprecated import (
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
)
warnings.warn(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST)
warnings.warn_explicit(
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
category=None,
filename=str(conftestpath),
lineno=0,
)
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
@@ -602,7 +616,29 @@ class Config(object):
fin()
def warn(self, code, message, fslocation=None, nodeid=None):
""" generate a warning for this test session. """
"""
.. 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
@@ -667,8 +703,8 @@ class Config(object):
r = determine_setup(
ns.inifilename,
ns.file_or_dir + unknown_args,
warnfunc=self.warn,
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir
@@ -706,6 +742,10 @@ class Config(object):
self.pluginmanager.rewrite_hook = hook
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
# We don't autoload from setuptools entry points, no need to continue.
return
# 'RECORD' available for plugins installed normally (pip install)
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
@@ -731,7 +771,10 @@ class Config(object):
self._checkversion()
self._consider_importhook(args)
self.pluginmanager.consider_preparse(args)
self.pluginmanager.load_setuptools_entrypoints("pytest11")
if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
# Don't autoload from setuptools entry point. Only explicitly specified
# plugins are going to be loaded.
self.pluginmanager.load_setuptools_entrypoints("pytest11")
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(
args, namespace=copy.copy(self.option)

View File

@@ -2,8 +2,13 @@ import six
import warnings
import argparse
from gettext import gettext as _
import sys as _sys
import py
from ..main import EXIT_USAGEERROR
FILE_OR_DIR = "file_or_dir"
@@ -329,6 +334,16 @@ class MyOptionParser(argparse.ArgumentParser):
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
def error(self, message):
"""error(message: string)
Prints a usage message incorporating the message to stderr and
exits.
Overrides the method in parent class to change exit code"""
self.print_usage(_sys.stderr)
args = {"prog": self.prog, "message": message}
self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args)
def parse_args(self, args=None, namespace=None):
"""allow splitting of positional arguments"""
args, argv = self.parse_known_args(args, namespace)

View File

@@ -10,15 +10,12 @@ def exists(path, ignore=EnvironmentError):
return False
def getcfg(args, warnfunc=None):
def getcfg(args, config=None):
"""
Search the list of arguments for a valid ini-file for pytest,
and return a tuple of (rootdir, inifile, cfg-dict).
note: warnfunc is an optional function used to warn
about ini-files that use deprecated features.
This parameter should be removed when pytest
adopts standard deprecation warnings (#1804).
note: config is optional and used only to issue warnings explicitly (#2891).
"""
from _pytest.deprecated import CFG_PYTEST_SECTION
@@ -34,9 +31,15 @@ def getcfg(args, warnfunc=None):
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and warnfunc:
warnfunc(
"C1", CFG_PYTEST_SECTION.format(filename=inibasename)
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,
)
return base, p, iniconfig["pytest"]
if (
@@ -95,7 +98,7 @@ def get_dirs_from_args(args):
return [get_dir_from_path(path) for path in possible_paths if path.exists()]
def determine_setup(inifile, args, warnfunc=None, rootdir_cmd_arg=None):
def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
@@ -105,23 +108,30 @@ def determine_setup(inifile, args, warnfunc=None, rootdir_cmd_arg=None):
for section in sections:
try:
inicfg = iniconfig[section]
if is_cfg_file and section == "pytest" and warnfunc:
if is_cfg_file and section == "pytest" and config is not None:
from _pytest.deprecated import CFG_PYTEST_SECTION
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warnings import _issue_config_warning
warnfunc("C1", CFG_PYTEST_SECTION.format(filename=str(inifile)))
_issue_config_warning(
RemovedInPytest4Warning(
CFG_PYTEST_SECTION.format(filename=str(inifile))
),
config,
)
break
except KeyError:
inicfg = None
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc)
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
if rootdir is None:
for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists():
break
else:
rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
rootdir, inifile, inicfg = getcfg(dirs, config=config)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"

View File

@@ -7,14 +7,16 @@ be removed when the time comes.
"""
from __future__ import absolute_import, division, print_function
from _pytest.warning_types import RemovedInPytest4Warning
class RemovedInPytest4Warning(DeprecationWarning):
"""warning class for features removed in pytest 4.0"""
MAIN_STR_ARGS = RemovedInPytest4Warning(
"passing a string to pytest.main() is deprecated, "
"pass a list of arguments instead."
)
MAIN_STR_ARGS = "passing a string to pytest.main() is deprecated, " "pass a list of arguments instead."
YIELD_TESTS = "yield tests are deprecated, and scheduled to be removed in pytest 4.0"
YIELD_TESTS = RemovedInPytest4Warning(
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
)
FUNCARG_PREFIX = (
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
@@ -23,7 +25,7 @@ FUNCARG_PREFIX = (
)
FIXTURE_FUNCTION_CALL = (
"Fixture {name} called directly. Fixtures are not meant to be called directly, "
'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."
)
@@ -32,7 +34,7 @@ CFG_PYTEST_SECTION = (
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead."
)
GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
GETFUNCARGVALUE = "getfuncargvalue is deprecated, use getfixturevalue"
RESULT_LOG = (
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
@@ -51,7 +53,11 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)
RECORD_XML_PROPERTY = (
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.'
@@ -61,7 +67,7 @@ COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
"pycollector makeitem was removed " "as it is an accidentially leaked internal api"
)
METAFUNC_ADD_CALL = (
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
"Please use Metafunc.parametrize instead."
)

View File

@@ -203,7 +203,8 @@ class DoctestItem(pytest.Item):
return
capman = self.config.pluginmanager.getplugin("capturemanager")
if capman:
out, err = capman.suspend_global_capture(in_=True)
capman.suspend_global_capture(in_=True)
out, err = capman.read_global_capture()
sys.stdout.write(out)
sys.stderr.write(err)

View File

@@ -1,13 +0,0 @@
class PytestExerimentalApiWarning(FutureWarning):
"warning category used to denote experiments in pytest"
@classmethod
def simple(cls, apiname):
return cls(
"{apiname} is an experimental api that may change over time".format(
apiname=apiname
)
)
PYTESTER_COPY_EXAMPLE = PytestExerimentalApiWarning.simple("testdir.copy_example")

View File

@@ -1257,6 +1257,8 @@ 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:
@@ -1279,10 +1281,15 @@ class FixtureManager(object):
if not callable(obj):
continue
marker = defaultfuncargprefixmarker
from _pytest import deprecated
self.config.warn(
"C1", deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
RemovedInPytest4Warning(
deprecated.FUNCARG_PREFIX.format(name=name)
),
category=None,
filename=str(filename),
lineno=lineno + 1,
)
name = name[len(self._argprefix) :]
elif not isinstance(marker, FixtureFunctionMarker):

View File

@@ -156,6 +156,7 @@ def showhelp(config):
vars = [
("PYTEST_ADDOPTS", "extra command line options"),
("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "set to disable plugin auto-loading"),
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
]
for name, help in vars:

View File

@@ -526,7 +526,17 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
@hookspec(historic=True)
def pytest_logwarning(message, code, nodeid, fslocation):
""" process a warning specified by a message, a code string,
"""
.. deprecated:: 3.8
This hook is will stop working in a future release.
pytest no longer triggers this hook, but the
terminal writer still implements it to display warnings issued by
:meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be
an error in future releases.
process a warning specified by a message, a code string,
a nodeid and fslocation (both of which may be None
if the warning is not tied to a particular node/location).
@@ -535,6 +545,30 @@ def pytest_logwarning(message, code, nodeid, fslocation):
"""
@hookspec(historic=True)
def pytest_warning_captured(warning_message, when, item):
"""
Process a warning captured by the internal pytest warnings plugin.
:param warnings.WarningMessage warning_message:
The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
the same attributes as the parameters of :py:func:`warnings.showwarning`.
:param str when:
Indicates when the warning was captured. Possible values:
* ``"config"``: during pytest configuration/initialization stage.
* ``"collect"``: during test collection.
* ``"runtest"``: during test execution.
:param pytest.Item|None item:
**DEPRECATED**: This parameter is incompatible with ``pytest-xdist``, and will always receive ``None``
in a future release.
The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
"""
# -------------------------------------------------------------------------
# doctest hooks
# -------------------------------------------------------------------------

View File

@@ -258,12 +258,11 @@ def record_property(request):
@pytest.fixture
def record_xml_property(record_property):
def record_xml_property(record_property, request):
"""(Deprecated) use record_property."""
import warnings
from _pytest import deprecated
warnings.warn(deprecated.RECORD_XML_PROPERTY, DeprecationWarning, stacklevel=2)
request.node.warn(deprecated.RECORD_XML_PROPERTY)
return record_property
@@ -274,9 +273,9 @@ def record_xml_attribute(request):
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
"""
request.node.warn(
code="C3", message="record_xml_attribute is an experimental feature"
)
from _pytest.warning_types import PytestWarning
request.node.warn(PytestWarning("record_xml_attribute is an experimental feature"))
xml = getattr(request.config, "_xml", None)
if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)

View File

@@ -445,8 +445,8 @@ class LoggingPlugin(object):
try:
yield # run test
finally:
del item.catch_log_handler
if when == "teardown":
del item.catch_log_handler
del item.catch_log_handlers
if self.print_logs:

View File

@@ -383,6 +383,7 @@ class Session(nodes.FSCollector):
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
self._initialpaths = frozenset()
# Keep track of any collected nodes in here, so we don't duplicate fixtures
self._node_cache = {}
@@ -441,13 +442,14 @@ class Session(nodes.FSCollector):
self.trace("perform_collect", self, args)
self.trace.root.indent += 1
self._notfound = []
self._initialpaths = set()
initialpaths = []
self._initialparts = []
self.items = items = []
for arg in args:
parts = self._parsearg(arg)
self._initialparts.append(parts)
self._initialpaths.add(parts[0])
initialpaths.append(parts[0])
self._initialpaths = frozenset(initialpaths)
rep = collect_one_node(self)
self.ihook.pytest_collectreport(report=rep)
self.trace.root.indent -= 1
@@ -502,13 +504,14 @@ class Session(nodes.FSCollector):
pkginit = parent.join("__init__.py")
if pkginit.isfile():
if pkginit in self._node_cache:
root = self._node_cache[pkginit]
root = self._node_cache[pkginit][0]
else:
col = root._collectfile(pkginit)
if col:
if isinstance(col[0], Package):
root = col[0]
self._node_cache[root.fspath] = root
# always store a list in the cache, matchnodes expects it
self._node_cache[root.fspath] = [root]
# If it's a directory argument, recurse and look for any Subpackages.
# Let the Package collector deal with subnodes, don't collect here.
@@ -528,8 +531,8 @@ class Session(nodes.FSCollector):
if (type(x), x.fspath) in self._node_cache:
yield self._node_cache[(type(x), x.fspath)]
else:
yield x
self._node_cache[(type(x), x.fspath)] = x
yield x
else:
assert argpath.check(file=1)
@@ -564,7 +567,6 @@ class Session(nodes.FSCollector):
"""Convert a dotted module name to path.
"""
try:
with _patched_find_module():
loader = pkgutil.find_loader(x)

View File

@@ -66,7 +66,10 @@ python_keywords_allowed_list = ["or", "and", "not"]
def matchmark(colitem, markexpr):
"""Tries to match on any marker names, attached to the given colitem."""
return eval(markexpr, {}, MarkMapping.from_item(colitem))
try:
return eval(markexpr, {}, MarkMapping.from_item(colitem))
except SyntaxError as e:
raise SyntaxError(str(e) + "\nMarker expression must be valid Python!")
def matchkeyword(colitem, keywordexpr):

View File

@@ -65,7 +65,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
return cls(values, marks, id_)
@classmethod
def extract_from(cls, parameterset, legacy_force_tuple=False):
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
"""
:param parameterset:
a legacy style parameterset that may or may not be a tuple,
@@ -75,6 +75,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
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):
@@ -93,20 +94,24 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
if legacy_force_tuple:
argval = (argval,)
if newmarks:
warnings.warn(MARK_PARAMETERSET_UNPACKING)
if newmarks and belonging_definition is not None:
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
@classmethod
def _for_parametrize(cls, argnames, argvalues, func, config):
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
force_tuple = len(argnames) == 1
else:
force_tuple = False
parameters = [
ParameterSet.extract_from(x, legacy_force_tuple=force_tuple)
ParameterSet.extract_from(
x,
legacy_force_tuple=force_tuple,
belonging_definition=function_definition,
)
for x in argvalues
]
del argvalues

View File

@@ -4,9 +4,12 @@ from __future__ import absolute_import, division, print_function
import os
import sys
import re
import warnings
from contextlib import contextmanager
import six
import pytest
from _pytest.fixtures import fixture
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@@ -209,22 +212,41 @@ class MonkeyPatch(object):
self._setitem.append((dic, name, dic.get(name, notset)))
del dic[name]
def _warn_if_env_name_is_not_str(self, name):
"""On Python 2, warn if the given environment variable name is not a native str (#4056)"""
if six.PY2 and not isinstance(name, str):
warnings.warn(
pytest.PytestWarning(
"Environment variable name {!r} should be str".format(name)
)
)
def setenv(self, name, value, prepend=None):
""" Set environment variable ``name`` to ``value``. If ``prepend``
is a character, read the current environment variable value
and prepend the ``value`` adjoined with the ``prepend`` character."""
value = str(value)
if not isinstance(value, str):
warnings.warn(
pytest.PytestWarning(
"Environment variable value {!r} should be str, converted to str implicitly".format(
value
)
)
)
value = str(value)
if prepend and name in os.environ:
value = value + prepend + os.environ[name]
self._warn_if_env_name_is_not_str(name)
self.setitem(os.environ, name, value)
def delenv(self, name, raising=True):
""" Delete ``name`` from the environment. Raise KeyError it does not
exist.
""" Delete ``name`` from the environment. Raise KeyError if it does
not exist.
If ``raising`` is set to False, no exception will be raised if the
environment variable is missing.
"""
self._warn_if_env_name_is_not_str(name)
self.delitem(os.environ, name, raising=raising)
def syspath_prepend(self, path):

View File

@@ -1,5 +1,6 @@
from __future__ import absolute_import, division, print_function
import os
import warnings
import six
import py
@@ -7,6 +8,7 @@ import attr
import _pytest
import _pytest._code
from _pytest.compat import getfslineno
from _pytest.mark.structures import NodeKeywords, MarkInfo
@@ -134,19 +136,98 @@ class Node(object):
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None))
def warn(self, code, message):
""" generate a warning with the given code and message for this
item. """
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 = getattr(self, "location", None)
if fslocation is None:
fslocation = getattr(self, "fspath", None)
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):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed
: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.
"""
from _pytest.warning_types import PytestWarning
if not isinstance(warning, PytestWarning):
raise ValueError(
"warning must be an instance of PytestWarning or subclass, got {!r}".format(
warning
)
)
path, lineno = get_fslocation_from_item(self)
warnings.warn_explicit(
warning,
category=None,
filename=str(path),
lineno=lineno + 1 if lineno is not None else None,
)
# methods for ordering nodes
@property
def nodeid(self):
@@ -310,6 +391,24 @@ class Node(object):
repr_failure = _repr_failure_py
def get_fslocation_from_item(item):
"""Tries to extract the actual location from an item, depending on available attributes:
* "fslocation": a pair (path, lineno)
* "obj": a Python object that the item wraps.
* "fspath": just a path
:rtype: a tuple of (str|LocalPath, int) with filename and line number.
"""
result = getattr(item, "location", None)
if result is not None:
return result[:2]
obj = getattr(item, "obj", None)
if obj is not None:
return getfslineno(obj)
return getattr(item, "fspath", "unknown location"), -1
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.

View File

@@ -67,13 +67,19 @@ exit.Exception = Exit
def skip(msg="", **kwargs):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
Skip an executing test with the given message.
This function should be called only during testing (setup, call or teardown) or
during collection by using the ``allow_module_level`` flag.
:kwarg bool allow_module_level: allows this function to be called at
module level, skipping the rest of the module. Default to False.
.. note::
It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies.
"""
__tracebackhide__ = True
allow_module_level = kwargs.pop("allow_module_level", False)
@@ -87,10 +93,12 @@ skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitly fail a currently-executing test with the given Message.
"""
Explicitly fail an executing test with the given message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
:param str msg: the message to show the user as reason for the failure.
:param bool pytrace: if false the msg represents the full failure information and no
python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
@@ -104,7 +112,15 @@ class XFailed(fail.Exception):
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
"""
Imperatively xfail an executing test or setup functions with the given reason.
This function should be called only during testing (setup, call or teardown).
.. note::
It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be
xfailed under certain conditions like known bugs or missing features.
"""
__tracebackhide__ = True
raise XFailed(reason)

View File

@@ -1,5 +1,11 @@
from .compat import Path
from os.path import expanduser, expandvars, isabs
from os.path import expanduser, expandvars, isabs, sep
from posixpath import sep as posix_sep
import fnmatch
import sys
import six
from .compat import Path, PurePath
def resolve_from_str(input, root):
@@ -11,3 +17,36 @@ def resolve_from_str(input, root):
return Path(input)
else:
return root.joinpath(input)
def fnmatch_ex(pattern, path):
"""FNMatcher port from py.path.common which works with PurePath() instances.
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
for each part of the path, while this algorithm uses the whole path instead.
For example:
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
PurePath.match().
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
this logic.
References:
* https://bugs.python.org/issue29249
* https://bugs.python.org/issue34731
"""
path = PurePath(path)
iswin32 = sys.platform.startswith("win")
if iswin32 and sep not in pattern and posix_sep in pattern:
# Running on Windows, the pattern has no Windows path separators,
# and the pattern has one or more Posix path separators. Replace
# the Posix path separators with the Windows path separator.
pattern = pattern.replace(posix_sep, sep)
if sep not in pattern:
name = path.name
else:
name = six.text_type(path)
return fnmatch.fnmatch(name, pattern)

View File

@@ -126,7 +126,7 @@ class LsofFdLeakChecker(object):
error.append(error[0])
error.append("*** function %s:%s: %s " % item.location)
error.append("See issue #2366")
item.warn("", "\n".join(error))
item.warn(pytest.PytestWarning("\n".join(error)))
# XXX copied from execnet's conftest.py - needs to be merged
@@ -407,7 +407,9 @@ class RunResult(object):
return d
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
def assert_outcomes(
self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0
):
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
@@ -418,10 +420,18 @@ class RunResult(object):
"skipped": d.get("skipped", 0),
"failed": d.get("failed", 0),
"error": d.get("error", 0),
"xpassed": d.get("xpassed", 0),
"xfailed": d.get("xfailed", 0),
}
assert obtained == dict(
passed=passed, skipped=skipped, failed=failed, error=error
)
expected = {
"passed": passed,
"skipped": skipped,
"failed": failed,
"error": error,
"xpassed": xpassed,
"xfailed": xfailed,
}
assert obtained == expected
class CwdSnapshot(object):
@@ -515,7 +525,6 @@ class Testdir(object):
def make_hook_recorder(self, pluginmanager):
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
assert not hasattr(pluginmanager, "reprec")
pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
self.request.addfinalizer(reprec.finish_recording)
return reprec
@@ -633,10 +642,10 @@ class Testdir(object):
return p
def copy_example(self, name=None):
from . import experiments
import warnings
from _pytest.warning_types import PYTESTER_COPY_EXAMPLE
warnings.warn(experiments.PYTESTER_COPY_EXAMPLE, stacklevel=2)
warnings.warn(PYTESTER_COPY_EXAMPLE, stacklevel=2)
example_dir = self.request.config.getini("pytester_example_dir")
if example_dir is None:
raise ValueError("pytester_example_dir is unset, can't copy examples")

View File

@@ -37,6 +37,7 @@ from _pytest.compat import (
getlocation,
enum,
get_default_arg_names,
getimfunc,
)
from _pytest.outcomes import fail
from _pytest.mark.structures import (
@@ -44,7 +45,7 @@ from _pytest.mark.structures import (
get_unpacked_marks,
normalize_mark_list,
)
from _pytest.warning_types import RemovedInPytest4Warning, PytestWarning
# relative paths that we use to filter traceback entries from appearing to the user;
# see filter_traceback
@@ -173,13 +174,14 @@ def pytest_configure(config):
"or a list of tuples of values if argnames specifies multiple names. "
"Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
"decorated test function, one with arg1=1 and another with arg1=2."
"see http://pytest.org/latest/parametrize.html for more info and "
"examples.",
"see https://docs.pytest.org/en/latest/parametrize.html for more info "
"and examples.",
)
config.addinivalue_line(
"markers",
"usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures ",
"all of the specified fixtures. see "
"https://docs.pytest.org/en/latest/fixture.html#usefixtures ",
)
@@ -238,9 +240,14 @@ def pytest_pycollect_makeitem(collector, name, obj):
# or a funtools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only)
if not (isfunction(obj) or isfunction(get_real_func(obj))):
collector.warn(
code="C2",
message="cannot collect %r because it is not a function." % name,
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
message=PytestWarning(
"cannot collect %r because it is not a function." % name
),
category=None,
filename=str(filename),
lineno=lineno + 1,
)
elif getattr(obj, "__test__", True):
if is_generator(obj):
@@ -348,11 +355,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
if isinstance(obj, staticmethod):
# static methods need to be unwrapped
obj = safe_getattr(obj, "__func__", False)
if obj is False:
# Python 2.6 wraps in a different way that we won't try to handle
msg = "cannot collect static method %r because it is not a function"
self.warn(code="C2", message=msg % name)
return False
return (
safe_getattr(obj, "__call__", False)
and fixtures.getfixturemarker(obj) is None
@@ -661,16 +663,18 @@ class Class(PyCollector):
return []
if hasinit(self.obj):
self.warn(
"C1",
"cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__,
PytestWarning(
"cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__
)
)
return []
elif hasnew(self.obj):
self.warn(
"C1",
"cannot collect test class %r because it has a "
"__new__ constructor" % self.obj.__name__,
PytestWarning(
"cannot collect test class %r because it has a "
"__new__ constructor" % self.obj.__name__
)
)
return []
return [self._getcustomclass("Instance")(name="()", parent=self)]
@@ -678,14 +682,12 @@ class Class(PyCollector):
def setup(self):
setup_class = _get_xunit_func(self.obj, "setup_class")
if setup_class is not None:
setup_class = getattr(setup_class, "im_func", setup_class)
setup_class = getattr(setup_class, "__func__", setup_class)
setup_class = getimfunc(setup_class)
setup_class(self.obj)
fin_class = getattr(self.obj, "teardown_class", None)
if fin_class is not None:
fin_class = getattr(fin_class, "im_func", fin_class)
fin_class = getattr(fin_class, "__func__", fin_class)
fin_class = getimfunc(fin_class)
self.addfinalizer(lambda: fin_class(self.obj))
@@ -798,7 +800,7 @@ class Generator(FunctionMixin, PyCollector):
)
seen[name] = True
values.append(self.Function(name, self, args=args, callobj=call))
self.warn("C1", deprecated.YIELD_TESTS)
self.warn(deprecated.YIELD_TESTS)
return values
def getcallargs(self, obj):
@@ -965,7 +967,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
from _pytest.mark import ParameterSet
argnames, parameters = ParameterSet._for_parametrize(
argnames, argvalues, self.function, self.config
argnames,
argvalues,
self.function,
self.config,
function_definition=self.definition,
)
del argvalues
@@ -976,7 +982,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
ids = self._resolve_arg_ids(argnames, ids, parameters)
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
@@ -999,13 +1005,14 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
newcalls.append(newcallspec)
self._calls = newcalls
def _resolve_arg_ids(self, argnames, ids, parameters):
def _resolve_arg_ids(self, argnames, ids, parameters, item):
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.
:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param ids: the ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
:param Item item: the item that generated this parametrized call.
:rtype: List[str]
:return: the list of ids for each argname given
"""
@@ -1026,7 +1033,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
raise ValueError(
msg % (saferepr(id_value), type(id_value).__name__)
)
ids = idmaker(argnames, parameters, idfn, ids, self.config)
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
return ids
def _resolve_arg_value_types(self, argnames, indirect):
@@ -1099,10 +1106,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
:arg param: a parameter which will be exposed to a later fixture function
invocation through the ``request.param`` attribute.
"""
if self.config:
self.config.warn(
"C1", message=deprecated.METAFUNC_ADD_CALL, fslocation=None
)
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:
@@ -1136,13 +1141,18 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
"""
from _pytest.fixtures import scopes
indirect_as_list = isinstance(indirect, (list, tuple))
all_arguments_are_fixtures = (
indirect is True or indirect_as_list and len(indirect) == argnames
)
if isinstance(indirect, (list, tuple)):
all_arguments_are_fixtures = len(indirect) == len(argnames)
else:
all_arguments_are_fixtures = bool(indirect)
if all_arguments_are_fixtures:
fixturedefs = arg2fixturedefs or {}
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
used_scopes = [
fixturedef[0].scope
for name, fixturedef in fixturedefs.items()
if name in argnames
]
if used_scopes:
# Takes the most narrow scope from used fixtures
for scope in reversed(scopes):
@@ -1152,21 +1162,20 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
return "function"
def _idval(val, argname, idx, idfn, config=None):
def _idval(val, argname, idx, idfn, item, config):
if idfn:
s = None
try:
s = idfn(val)
except Exception:
except Exception as e:
# See issue https://github.com/pytest-dev/pytest/issues/2169
import warnings
msg = (
"Raised while trying to determine id of parameter %s at position %d."
% (argname, idx)
"While trying to determine id of parameter {} at position "
"{} the following exception was raised:\n".format(argname, idx)
)
msg += "\nUpdate your code as this will raise an error in pytest-4.0."
warnings.warn(msg, DeprecationWarning)
msg += " {}: {}\n".format(type(e).__name__, e)
msg += "This warning will be an error error in pytest-4.0."
item.warn(RemovedInPytest4Warning(msg))
if s:
return ascii_escaped(s)
@@ -1190,12 +1199,12 @@ def _idval(val, argname, idx, idfn, config=None):
return str(argname) + str(idx)
def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
def _idvalset(idx, parameterset, argnames, idfn, ids, item, config):
if parameterset.id is not None:
return parameterset.id
if ids is None or (idx >= len(ids) or ids[idx] is None):
this_id = [
_idval(val, argname, idx, idfn, config)
_idval(val, argname, idx, idfn, item=item, config=config)
for val, argname in zip(parameterset.values, argnames)
]
return "-".join(this_id)
@@ -1203,9 +1212,9 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
return ascii_escaped(ids[idx])
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None):
ids = [
_idvalset(valindex, parameterset, argnames, idfn, ids, config)
_idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item)
for valindex, parameterset in enumerate(parametersets)
]
if len(set(ids)) != len(ids):
@@ -1428,7 +1437,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
@property
def function(self):
"underlying python 'function' object"
return getattr(self.obj, "im_func", self.obj)
return getimfunc(self.obj)
def _getobj(self):
name = self.name

View File

@@ -212,6 +212,8 @@ class WarningsChecker(WarningsRecorder):
def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
__tracebackhide__ = True
# only check if we're not currently handling an exception
if all(a is None for a in exc_info):
if self.expected_warning is not None:

View File

@@ -110,7 +110,7 @@ class TestReport(BaseReport):
when,
sections=(),
duration=0,
user_properties=(),
user_properties=None,
**extra
):
#: normalized collection node id
@@ -136,7 +136,7 @@ class TestReport(BaseReport):
#: user properties is a list of tuples (name, value) that holds user
#: defined properties of the test
self.user_properties = user_properties
self.user_properties = list(user_properties or [])
#: list of pairs ``(str, str)`` of extra information which needs to
#: marshallable. Used by pytest to add captured text

View File

@@ -31,8 +31,10 @@ def pytest_configure(config):
config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warnings import _issue_config_warning
config.warn("C1", RESULT_LOG)
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
def pytest_unconfigure(config):

View File

@@ -51,7 +51,7 @@ def pytest_configure(config):
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html",
"https://docs.pytest.org/en/latest/skipping.html",
)
config.addinivalue_line(
"markers",
@@ -61,7 +61,7 @@ def pytest_configure(config):
"and run=False if you don't even want to execute the test function. "
"If only specific exception(s) are expected, you can list them in "
"raises, and if the test fails in other ways, it will be reported as "
"a true failure. See http://pytest.org/latest/skipping.html",
"a true failure. See https://docs.pytest.org/en/latest/skipping.html",
)

View File

@@ -9,6 +9,7 @@ import platform
import sys
import time
import attr
import pluggy
import py
import six
@@ -184,23 +185,23 @@ def pytest_report_teststatus(report):
return report.outcome, letter, report.outcome.upper()
@attr.s
class WarningReport(object):
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
Simple structure to hold warnings information captured by ``pytest_logwarning`` and ``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.
"""
def __init__(self, code, message, nodeid=None, fslocation=None):
"""
:param code: unused
:param str message: user friendly message about the warning
:param str|None nodeid: node id that generated the warning (see ``get_location``).
:param tuple|py.path.local fslocation:
file system location of the source of the warning (see ``get_location``).
"""
self.code = code
self.message = message
self.nodeid = nodeid
self.fslocation = fslocation
message = attr.ib()
nodeid = attr.ib(default=None)
fslocation = attr.ib(default=None)
legacy = attr.ib(default=False)
def get_location(self, config):
"""
@@ -213,6 +214,8 @@ class WarningReport(object):
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
filename, linenum = self.fslocation[:2]
relpath = py.path.local(filename).relto(config.invocation_dir)
if not relpath:
relpath = str(filename)
return "%s:%s" % (relpath, linenum)
else:
return str(self.fslocation)
@@ -254,7 +257,7 @@ class TerminalReporter(object):
# do not show progress if we are showing fixture setup/teardown
if self.config.getoption("setupshow"):
return False
return self.config.getini("console_output_style") == "progress"
return self.config.getini("console_output_style") in ("progress", "count")
def hasopt(self, char):
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
@@ -263,7 +266,7 @@ class TerminalReporter(object):
def write_fspath_result(self, nodeid, res):
fspath = self.config.rootdir.join(nodeid.split("::")[0])
if fspath != self.currentfspath:
if self.currentfspath is not None:
if self.currentfspath is not None and self._show_progress_info:
self._write_progress_information_filling_space()
self.currentfspath = fspath
fspath = self.startdir.bestrelpath(fspath)
@@ -327,13 +330,27 @@ class TerminalReporter(object):
self.write_line("INTERNALERROR> " + line)
return 1
def pytest_logwarning(self, code, fslocation, message, nodeid):
def pytest_logwarning(self, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
warning = WarningReport(
code=code, fslocation=fslocation, message=message, nodeid=nodeid
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
warnings = self.stats.setdefault("warnings", [])
fslocation = warning_message.filename, warning_message.lineno
message = warning_record_to_str(warning_message)
nodeid = item.nodeid if item is not None else ""
warning_report = WarningReport(
fslocation=fslocation, message=message, nodeid=nodeid
)
warnings.append(warning_report)
def pytest_plugin_registered(self, plugin):
if self.config.option.traceconfig:
msg = "PLUGIN registered: %s" % (plugin,)
@@ -358,12 +375,12 @@ class TerminalReporter(object):
def pytest_runtest_logreport(self, report):
rep = report
res = self.config.hook.pytest_report_teststatus(report=rep)
cat, letter, word = res
category, letter, word = res
if isinstance(word, tuple):
word, markup = word
else:
markup = None
self.stats.setdefault(cat, []).append(rep)
self.stats.setdefault(category, []).append(rep)
self._tests_ran = True
if not letter and not word:
# probably passed setup/teardown
@@ -404,6 +421,12 @@ class TerminalReporter(object):
self.currentfspath = -2
def pytest_runtest_logfinish(self, nodeid):
if self.config.getini("console_output_style") == "count":
num_tests = self._session.testscollected
progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests)))
else:
progress_length = len(" [100%]")
if self.verbosity <= 0 and self._show_progress_info:
self._progress_nodeids_reported.add(nodeid)
last_item = (
@@ -412,31 +435,43 @@ class TerminalReporter(object):
if last_item:
self._write_progress_information_filling_space()
else:
past_edge = (
self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1
>= self._screen_width
)
w = self._width_of_current_line
past_edge = w + progress_length + 1 >= self._screen_width
if past_edge:
msg = self._get_progress_information_message()
self._tw.write(msg + "\n", cyan=True)
_PROGRESS_LENGTH = len(" [100%]")
def _get_progress_information_message(self):
if self.config.getoption("capture") == "no":
return ""
collected = self._session.testscollected
if collected:
progress = len(self._progress_nodeids_reported) * 100 // collected
return " [{:3d}%]".format(progress)
return " [100%]"
if self.config.getini("console_output_style") == "count":
if collected:
progress = self._progress_nodeids_reported
counter_format = "{{:{}d}}".format(len(str(collected)))
format_string = " [{}/{{}}]".format(counter_format)
return format_string.format(len(progress), collected)
return " [ {} / {} ]".format(collected, collected)
else:
if collected:
progress = len(self._progress_nodeids_reported) * 100 // collected
return " [{:3d}%]".format(progress)
return " [100%]"
def _write_progress_information_filling_space(self):
msg = self._get_progress_information_message()
fill = " " * (
self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1
)
self.write(fill + msg, cyan=True)
w = self._width_of_current_line
fill = self._tw.fullwidth - w - 1
self.write(msg.rjust(fill), cyan=True)
@property
def _width_of_current_line(self):
"""Return the width of current line, using the superior implementation of py-1.6 when available"""
try:
return self._tw.width_of_current_line
except AttributeError:
# py < 1.6.0
return self._tw.chars_on_current_line
def pytest_collection(self):
if not self.isatty and self.config.option.verbose >= 1:
@@ -685,13 +720,22 @@ class TerminalReporter(object):
self.write_sep("=", "warnings summary", yellow=True, bold=False)
for location, warning_records in grouped:
self._tw.line(str(location) if location else "<undetermined location>")
# 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)
is_legacy = warning_records[0].legacy
if location and is_legacy:
self._tw.line(str(location))
for w in warning_records:
lines = w.message.splitlines()
indented = "\n".join(" " + x for x in lines)
self._tw.line(indented)
if is_legacy:
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()
self._tw.line("-- Docs: http://doc.pytest.org/en/latest/warnings.html")
self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html")
def summary_passes(self):
if self.config.option.tbstyle != "no":
@@ -701,9 +745,10 @@ class TerminalReporter(object):
return
self.write_sep("=", "PASSES")
for rep in reports:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
self._outrep_summary(rep)
if rep.sections:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
self._outrep_summary(rep)
def print_teardown_sections(self, rep):
showcapture = self.config.option.showcapture

View File

@@ -9,6 +9,7 @@ import _pytest._code
from _pytest.config import hookimpl
from _pytest.outcomes import fail, skip, xfail
from _pytest.python import transfer_markers, Class, Module, Function
from _pytest.compat import getimfunc
def pytest_pycollect_makeitem(collector, name, obj):
@@ -53,7 +54,7 @@ class UnitTestCase(Class):
x = getattr(self.obj, name)
if not getattr(x, "__test__", True):
continue
funcobj = getattr(x, "im_func", x)
funcobj = getimfunc(x)
transfer_markers(funcobj, cls, module)
yield TestCaseFunction(name, parent=self, callobj=funcobj)
foundsomething = True

View File

@@ -0,0 +1,42 @@
class PytestWarning(UserWarning):
"""
Bases: :class:`UserWarning`.
Base class for all warnings emitted by pytest.
"""
class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
"""
Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`.
Warning class for features that will be removed in a future version.
"""
class RemovedInPytest4Warning(PytestDeprecationWarning):
"""
Bases: :class:`pytest.PytestDeprecationWarning`.
Warning class for features scheduled to be removed in pytest 4.0.
"""
class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
"""
Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`.
Warning category used to denote experiments in pytest. Use sparingly as the API might change or even be
removed completely in future version
"""
@classmethod
def simple(cls, apiname):
return cls(
"{apiname} is an experimental api that may change over time".format(
apiname=apiname
)
)
PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")

View File

@@ -1,5 +1,6 @@
from __future__ import absolute_import, division, print_function
import sys
import warnings
from contextlib import contextmanager
@@ -53,67 +54,119 @@ def pytest_configure(config):
config.addinivalue_line(
"markers",
"filterwarnings(warning): add a warning filter to the given test. "
"see http://pytest.org/latest/warnings.html#pytest-mark-filterwarnings ",
"see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ",
)
@contextmanager
def catch_warnings_for_item(item):
def catch_warnings_for_item(config, ihook, when, item):
"""
catches the warnings generated during setup/call/teardown execution
of the given item and after it is done posts them as warnings to this
item.
Context manager that catches warnings generated in the contained execution block.
``item`` can be None if we are not in the context of an item execution.
Each warning captured triggers the ``pytest_warning_captured`` hook.
"""
args = item.config.getoption("pythonwarnings") or []
inifilters = item.config.getini("filterwarnings")
args = config.getoption("pythonwarnings") or []
inifilters = config.getini("filterwarnings")
with warnings.catch_warnings(record=True) as log:
filters_configured = args or inifilters or sys.warnoptions
for arg in args:
warnings._setoption(arg)
for arg in inifilters:
_setoption(warnings, arg)
for mark in item.iter_markers(name="filterwarnings"):
for arg in mark.args:
warnings._setoption(arg)
if item is not None:
for mark in item.iter_markers(name="filterwarnings"):
for arg in mark.args:
_setoption(warnings, arg)
filters_configured = True
if not filters_configured:
# if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
warnings.filterwarnings("always", category=DeprecationWarning)
warnings.filterwarnings("always", category=PendingDeprecationWarning)
yield
for warning in log:
warn_msg = warning.message
unicode_warning = False
if compat._PY2 and any(
isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args
):
new_args = []
for m in warn_msg.args:
new_args.append(
compat.ascii_escaped(m)
if isinstance(m, compat.UNICODE_TYPES)
else m
)
unicode_warning = list(warn_msg.args) != new_args
warn_msg.args = new_args
msg = warnings.formatwarning(
warn_msg,
warning.category,
warning.filename,
warning.lineno,
warning.line,
for warning_message in log:
ihook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=warning_message, when=when, item=item)
)
item.warn("unused", msg)
if unicode_warning:
warnings.warn(
"Warning is using unicode non convertible to ascii, "
"converting to a safe representation:\n %s" % msg,
UnicodeWarning,
)
def warning_record_to_str(warning_message):
"""Convert a warnings.WarningMessage to a string, taking in account a lot of unicode shenaningans in Python 2.
When Python 2 support is dropped this function can be greatly simplified.
"""
warn_msg = warning_message.message
unicode_warning = False
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
new_args = []
for m in warn_msg.args:
new_args.append(
compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m
)
unicode_warning = list(warn_msg.args) != new_args
warn_msg.args = new_args
msg = warnings.formatwarning(
warn_msg,
warning_message.category,
warning_message.filename,
warning_message.lineno,
warning_message.line,
)
if unicode_warning:
warnings.warn(
"Warning is using unicode non convertible to ascii, "
"converting to a safe representation:\n %s" % msg,
UnicodeWarning,
)
return msg
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_protocol(item):
with catch_warnings_for_item(
config=item.config, ihook=item.ihook, when="runtest", item=item
):
yield
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection(session):
config = session.config
with catch_warnings_for_item(
config=config, ihook=config.hook, when="collect", item=None
):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
with catch_warnings_for_item(item):
def pytest_terminal_summary(terminalreporter):
config = terminalreporter.config
with catch_warnings_for_item(
config=config, ihook=config.hook, when="config", item=None
):
yield
def _issue_config_warning(warning, config):
"""
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:
"""
with warnings.catch_warnings(record=True) as records:
warnings.simplefilter("always", type(warning))
warnings.warn(warning, stacklevel=2)
config.hook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=records[0], when="config", item=None)
)

View File

@@ -19,45 +19,54 @@ from _pytest.main import Session
from _pytest.nodes import Item, Collector, File
from _pytest.fixtures import fillfixtures as _fillfuncargs
from _pytest.python import Package, Module, Class, Instance, Function, Generator
from _pytest.python_api import approx, raises
from _pytest.warning_types import (
PytestWarning,
PytestDeprecationWarning,
RemovedInPytest4Warning,
PytestExperimentalApiWarning,
)
set_trace = __pytestPDB.set_trace
__all__ = [
"main",
"UsageError",
"cmdline",
"hookspec",
"hookimpl",
"__version__",
"register_assert_rewrite",
"freeze_includes",
"set_trace",
"warns",
"deprecated_call",
"fixture",
"yield_fixture",
"fail",
"skip",
"xfail",
"importorskip",
"exit",
"mark",
"param",
"approx",
"_fillfuncargs",
"Item",
"File",
"Collector",
"Package",
"Session",
"Module",
"approx",
"Class",
"Instance",
"cmdline",
"Collector",
"deprecated_call",
"exit",
"fail",
"File",
"fixture",
"freeze_includes",
"Function",
"Generator",
"hookimpl",
"hookspec",
"importorskip",
"Instance",
"Item",
"main",
"mark",
"Module",
"Package",
"param",
"PytestDeprecationWarning",
"PytestExperimentalApiWarning",
"PytestWarning",
"raises",
"register_assert_rewrite",
"RemovedInPytest4Warning",
"Session",
"set_trace",
"skip",
"UsageError",
"warns",
"xfail",
"yield_fixture",
]
if __name__ == "__main__":

View File

@@ -577,7 +577,7 @@ class TestInvocationVariants(object):
return what
empty_package = testdir.mkpydir("empty_package")
monkeypatch.setenv("PYTHONPATH", join_pythonpath(empty_package))
monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(empty_package)))
# the path which is not a package raises a warning on pypy;
# no idea why only pypy and not normal python warn about it here
with warnings.catch_warnings():
@@ -586,7 +586,7 @@ class TestInvocationVariants(object):
assert result.ret == 0
result.stdout.fnmatch_lines(["*2 passed*"])
monkeypatch.setenv("PYTHONPATH", join_pythonpath(testdir))
monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(testdir)))
result = testdir.runpytest("--pyargs", "tpkg.test_missing", syspathinsert=True)
assert result.ret != 0
result.stderr.fnmatch_lines(["*not*found*test_missing*"])
@@ -1061,3 +1061,8 @@ def test_fixture_mock_integration(testdir):
p = testdir.copy_example("acceptance/fixture_mock_integration.py")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines("*1 passed*")
def test_usage_error_code(testdir):
result = testdir.runpytest("-unknown-option-")
assert result.ret == EXIT_USAGEERROR

View File

@@ -1,13 +1,19 @@
# coding: utf-8
from __future__ import absolute_import, division, print_function
import sys
import _pytest._code
import pytest
import mock
from test_excinfo import TWMock
from six import text_type
from test_excinfo import TWMock
try:
import mock
except ImportError:
import unittest.mock as mock
def test_ne():
code1 = _pytest._code.Code(compile('foo = "bar"', "", "exec"))
@@ -32,10 +38,8 @@ def test_code_with_class():
pytest.raises(TypeError, "_pytest._code.Code(A)")
if True:
def x():
pass
def x():
raise NotImplementedError()
def test_code_fullsource():
@@ -48,7 +52,7 @@ def test_code_source():
code = _pytest._code.Code(x)
src = code.source()
expected = """def x():
pass"""
raise NotImplementedError()"""
assert str(src) == expected
@@ -85,9 +89,9 @@ def test_unicode_handling():
raise Exception(value)
excinfo = pytest.raises(Exception, f)
str(excinfo)
if sys.version_info[0] < 3:
text_type(excinfo)
text_type(excinfo)
if sys.version_info < (3,):
bytes(excinfo)
@pytest.mark.skipif(sys.version_info[0] >= 3, reason="python 2 only issue")
@@ -105,25 +109,25 @@ def test_unicode_handling_syntax_error():
def test_code_getargs():
def f1(x):
pass
raise NotImplementedError()
c1 = _pytest._code.Code(f1)
assert c1.getargs(var=True) == ("x",)
def f2(x, *y):
pass
raise NotImplementedError()
c2 = _pytest._code.Code(f2)
assert c2.getargs(var=True) == ("x", "y")
def f3(x, **z):
pass
raise NotImplementedError()
c3 = _pytest._code.Code(f3)
assert c3.getargs(var=True) == ("x", "z")
def f4(x, *y, **z):
pass
raise NotImplementedError()
c4 = _pytest._code.Code(f4)
assert c4.getargs(var=True) == ("x", "y", "z")
@@ -188,11 +192,14 @@ class TestReprFuncArgs(object):
tw = TWMock()
args = [("unicode_string", u"São Paulo"), ("utf8_string", "S\xc3\xa3o Paulo")]
args = [("unicode_string", u"São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")]
r = ReprFuncArgs(args)
r.toterminal(tw)
if sys.version_info[0] >= 3:
assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo"
assert (
tw.lines[0]
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
)
else:
assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo"

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# flake8: noqa
# disable flake check on this file because some constructs are strange
# or redundant on purpose and can't be disable on a line-by-line basis
@@ -41,15 +42,11 @@ def test_source_str_function():
def test_unicode():
try:
unicode
except NameError:
return
x = Source(unicode("4"))
x = Source(u"4")
assert str(x) == "4"
co = _pytest._code.compile(unicode('u"\xc3\xa5"', "utf8"), mode="eval")
co = _pytest._code.compile(u'u"å"', mode="eval")
val = eval(co)
assert isinstance(val, unicode)
assert isinstance(val, six.text_type)
def test_source_from_function():
@@ -132,7 +129,7 @@ def test_source_strip_multiline():
def test_syntaxerror_rerepresentation():
ex = pytest.raises(SyntaxError, _pytest._code.compile, "xyz xyz")
assert ex.value.lineno == 1
assert ex.value.offset in (4, 7) # XXX pypy/jython versus cpython?
assert ex.value.offset in (4, 5, 7) # XXX pypy/jython versus cpython?
assert ex.value.text.strip(), "x x"
@@ -632,7 +629,7 @@ def test_issue55():
assert str(s) == ' round_trip("""\n""")'
def XXXtest_multiline():
def test_multiline():
source = getstatement(
0,
"""\

View File

@@ -1,9 +1,11 @@
from __future__ import absolute_import, division, print_function
import os
import pytest
@pytest.mark.filterwarnings("default")
def test_yield_tests_deprecation(testdir):
testdir.makepyfile(
"""
@@ -17,16 +19,18 @@ def test_yield_tests_deprecation(testdir):
yield func1, 1, 1
"""
)
result = testdir.runpytest("-ra")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"*yield tests are deprecated, and scheduled to be removed in pytest 4.0*",
"*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
@pytest.mark.filterwarnings("default")
def test_funcarg_prefix_deprecation(testdir):
testdir.makepyfile(
"""
@@ -41,16 +45,15 @@ def test_funcarg_prefix_deprecation(testdir):
result.stdout.fnmatch_lines(
[
(
"*pytest_funcarg__value: "
'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."
"*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):
testdir.makefile(
".cfg",
@@ -65,6 +68,7 @@ def test_pytest_setup_cfg_deprecated(testdir):
)
@pytest.mark.filterwarnings("default")
def test_pytest_custom_cfg_deprecated(testdir):
testdir.makefile(
".cfg",
@@ -79,15 +83,15 @@ def test_pytest_custom_cfg_deprecated(testdir):
)
def test_str_args_deprecated(tmpdir, testdir):
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_logwarning(self, message):
warnings.append(message)
def pytest_warning_captured(self, warning_message):
warnings.append(str(warning_message.message))
ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
msg = (
@@ -102,6 +106,7 @@ def test_getfuncargvalue_is_deprecated(request):
pytest.deprecated_call(request.getfuncargvalue, "tmpdir")
@pytest.mark.filterwarnings("default")
def test_resultlog_is_deprecated(testdir):
result = testdir.runpytest("--help")
result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"])
@@ -116,7 +121,7 @@ def test_resultlog_is_deprecated(testdir):
result.stdout.fnmatch_lines(
[
"*--result-log is deprecated and scheduled for removal in pytest 4.0*",
"*See https://docs.pytest.org/*/usage.html#creating-resultlog-format-files for more information*",
"*See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information*",
]
)
@@ -173,21 +178,12 @@ def test_pytest_catchlog_deprecated(testdir, plugin):
def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
subdirectory = testdir.tmpdir.join("subdirectory")
subdirectory.mkdir()
# create the inner conftest with makeconftest and then move it to the subdirectory
testdir.makeconftest(
"""
testdir.makepyfile(
**{
"subdirectory/conftest.py": """
pytest_plugins=['capture']
"""
)
testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py"))
# make the top level conftest
testdir.makeconftest(
"""
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
"""
}
)
testdir.makepyfile(
"""
@@ -195,13 +191,44 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
pass
"""
)
res = testdir.runpytest_subprocess()
res = testdir.runpytest()
assert res.ret == 0
res.stderr.fnmatch_lines(
"*" + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
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
)
)
@pytest.mark.parametrize("use_pyargs", [True, False])
def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
testdir, use_pyargs
):
"""When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)"""
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
files = {
"src/pkg/__init__.py": "",
"src/pkg/conftest.py": "",
"src/pkg/test_root.py": "def test(): pass",
"src/pkg/sub/__init__.py": "",
"src/pkg/sub/conftest.py": "pytest_plugins=['capture']",
"src/pkg/sub/test_bar.py": "def test(): pass",
}
testdir.makepyfile(**files)
testdir.syspathinsert(testdir.tmpdir.join("src"))
args = ("--pyargs", "pkg") if use_pyargs else ()
res = testdir.runpytest(*args)
assert res.ret == 0
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
if use_pyargs:
assert msg not in res.stdout.str()
else:
res.stdout.fnmatch_lines("*{msg}*".format(msg=msg))
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_conftest(
testdir
):
@@ -227,8 +254,11 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte
res = testdir.runpytest_subprocess()
assert res.ret == 0
res.stderr.fnmatch_lines(
"*" + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
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
)
)
@@ -261,10 +291,8 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(
)
res = testdir.runpytest_subprocess()
assert res.ret == 0
assert (
str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
not in res.stderr.str()
)
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
assert msg not in res.stdout.str()
def test_call_fixture_function_deprecated():
@@ -276,3 +304,23 @@ def test_call_fixture_function_deprecated():
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

View File

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

View File

@@ -8,10 +8,6 @@ import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.nodes import Collector
ignore_parametrized_marks = pytest.mark.filterwarnings(
"ignore:Applying marks directly to parameters"
)
class TestModule(object):
def test_failing_import(self, testdir):
@@ -456,12 +452,20 @@ class TestGenerator(object):
class TestFunction(object):
@pytest.fixture
def ignore_parametrized_marks_args(self):
"""Provides arguments to pytester.runpytest() to ignore the warning about marks being applied directly
to parameters.
"""
return ("-W", "ignore:Applying marks directly to parameters")
def test_getmodulecollector(self, testdir):
item = testdir.getitem("def test_func(): pass")
modcol = item.getparent(pytest.Module)
assert isinstance(modcol, pytest.Module)
assert hasattr(modcol.obj, "test_func")
@pytest.mark.filterwarnings("default")
def test_function_as_object_instance_ignored(self, testdir):
testdir.makepyfile(
"""
@@ -472,8 +476,14 @@ class TestFunction(object):
test_a = A()
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome()
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"collected 0 items",
"*test_function_as_object_instance_ignored.py:2: "
"*cannot collect 'test_a' because it is not a function.",
]
)
def test_function_equality(self, testdir, tmpdir):
from _pytest.fixtures import FixtureManager
@@ -662,7 +672,7 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
@ignore_parametrized_marks
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
def test_parametrize_with_mark(self, testdir):
items = testdir.getitems(
"""
@@ -748,8 +758,7 @@ class TestFunction(object):
assert colitems[2].name == "test2[a-c]"
assert colitems[3].name == "test2[b-c]"
@ignore_parametrized_marks
def test_parametrize_skipif(self, testdir):
def test_parametrize_skipif(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -761,11 +770,10 @@ class TestFunction(object):
assert x < 2
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
@ignore_parametrized_marks
def test_parametrize_skip(self, testdir):
def test_parametrize_skip(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -777,11 +785,10 @@ class TestFunction(object):
assert x < 2
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
@ignore_parametrized_marks
def test_parametrize_skipif_no_skip(self, testdir):
def test_parametrize_skipif_no_skip(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -793,11 +800,10 @@ class TestFunction(object):
assert x < 2
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
@ignore_parametrized_marks
def test_parametrize_xfail(self, testdir):
def test_parametrize_xfail(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -809,11 +815,10 @@ class TestFunction(object):
assert x < 2
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
@ignore_parametrized_marks
def test_parametrize_passed(self, testdir):
def test_parametrize_passed(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -825,11 +830,10 @@ class TestFunction(object):
pass
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
@ignore_parametrized_marks
def test_parametrize_xfail_passed(self, testdir):
def test_parametrize_xfail_passed(self, testdir, ignore_parametrized_marks_args):
testdir.makepyfile(
"""
import pytest
@@ -841,7 +845,7 @@ class TestFunction(object):
pass
"""
)
result = testdir.runpytest()
result = testdir.runpytest(*ignore_parametrized_marks_args)
result.stdout.fnmatch_lines("* 3 passed in *")
def test_function_original_name(self, testdir):
@@ -1468,6 +1472,7 @@ def test_collect_functools_partial(testdir):
result.assertoutcome(passed=6, failed=2)
@pytest.mark.filterwarnings("default")
def test_dont_collect_non_function_callable(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/331
@@ -1490,7 +1495,7 @@ def test_dont_collect_non_function_callable(testdir):
result.stdout.fnmatch_lines(
[
"*collected 1 item*",
"*cannot collect 'test_a' because it is not a function*",
"*test_dont_collect_non_function_callable.py:2: *cannot collect 'test_a' because it is not a function*",
"*1 passed, 1 warnings in *",
]
)
@@ -1586,6 +1591,13 @@ def test_package_collection_infinite_recursion(testdir):
result.stdout.fnmatch_lines("*1 passed*")
def test_package_collection_init_given_as_argument(testdir):
"""Regression test for #3749"""
p = testdir.copy_example("collect/package_init_given_as_arg")
result = testdir.runpytest(p / "pkg" / "__init__.py")
result.stdout.fnmatch_lines("*1 passed*")
def test_package_with_modules(testdir):
"""
.

View File

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import sys
import textwrap
import pytest
@@ -488,6 +489,10 @@ class TestRequestBasic(object):
assert len(arg2fixturedefs) == 1
assert arg2fixturedefs["something"][0].argname == "something"
@pytest.mark.skipif(
hasattr(sys, "pypy_version_info"),
reason="this method of test doesn't work on pypy",
)
def test_request_garbage(self, testdir):
testdir.makepyfile(
"""
@@ -498,33 +503,32 @@ class TestRequestBasic(object):
@pytest.fixture(autouse=True)
def something(request):
# this method of test doesn't work on pypy
if hasattr(sys, "pypy_version_info"):
yield
else:
original = gc.get_debug()
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect()
original = gc.get_debug()
gc.set_debug(gc.DEBUG_SAVEALL)
gc.collect()
yield
yield
try:
gc.collect()
leaked_types = sum(1 for _ in gc.garbage
if isinstance(_, PseudoFixtureDef))
# debug leaked types if the test fails
print(leaked_types)
gc.garbage[:] = []
try:
assert leaked_types == 0
finally:
gc.set_debug(original)
assert leaked_types == 0
finally:
gc.set_debug(original)
def test_func():
pass
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 passed in *")
def test_getfixturevalue_recursive(self, testdir):
testdir.makeconftest(
@@ -1584,6 +1588,7 @@ class TestFixtureManagerParseFactories(object):
values = []
"""
)
testdir.syspathinsert(testdir.tmpdir.dirname)
package = testdir.mkdir("package")
package.join("__init__.py").write("")
package.join("conftest.py").write(

View File

@@ -132,6 +132,52 @@ class TestMetafunc(object):
except ValueError as ve:
assert "has an unsupported scope value 'doggy'" in str(ve)
def test_find_parametrized_scope(self):
"""unittest for _find_parametrized_scope (#3941)"""
from _pytest.python import _find_parametrized_scope
@attr.s
class DummyFixtureDef(object):
scope = attr.ib()
fixtures_defs = dict(
session_fix=[DummyFixtureDef("session")],
package_fix=[DummyFixtureDef("package")],
module_fix=[DummyFixtureDef("module")],
class_fix=[DummyFixtureDef("class")],
func_fix=[DummyFixtureDef("function")],
)
# use arguments to determine narrow scope; the cause of the bug is that it would look on all
# fixture defs given to the method
def find_scope(argnames, indirect):
return _find_parametrized_scope(argnames, fixtures_defs, indirect=indirect)
assert find_scope(["func_fix"], indirect=True) == "function"
assert find_scope(["class_fix"], indirect=True) == "class"
assert find_scope(["module_fix"], indirect=True) == "module"
assert find_scope(["package_fix"], indirect=True) == "package"
assert find_scope(["session_fix"], indirect=True) == "session"
assert find_scope(["class_fix", "func_fix"], indirect=True) == "function"
assert find_scope(["func_fix", "session_fix"], indirect=True) == "function"
assert find_scope(["session_fix", "class_fix"], indirect=True) == "class"
assert find_scope(["package_fix", "session_fix"], indirect=True) == "package"
assert find_scope(["module_fix", "session_fix"], indirect=True) == "module"
# when indirect is False or is not for all scopes, always use function
assert find_scope(["session_fix", "module_fix"], indirect=False) == "function"
assert (
find_scope(["session_fix", "module_fix"], indirect=["module_fix"])
== "function"
)
assert (
find_scope(
["session_fix", "module_fix"], indirect=["session_fix", "module_fix"]
)
== "module"
)
def test_parametrize_and_id(self):
def func(x, y):
pass
@@ -211,13 +257,10 @@ class TestMetafunc(object):
@hypothesis.settings(
deadline=400.0
) # very close to std deadline and CI boxes are not reliable in CPU power
@pytest.mark.xfail(
sys.platform.startswith("win32"), reason="flaky #3707", strict=False
)
def test_idval_hypothesis(self, value):
from _pytest.python import _idval
escaped = _idval(value, "a", 6, None)
escaped = _idval(value, "a", 6, None, item=None, config=None)
assert isinstance(escaped, str)
if PY3:
escaped.encode("ascii")
@@ -244,7 +287,7 @@ class TestMetafunc(object):
),
]
for val, expected in values:
assert _idval(val, "a", 6, None) == expected
assert _idval(val, "a", 6, None, item=None, config=None) == expected
def test_bytes_idval(self):
"""unittest for the expected behavior to obtain ids for parametrized
@@ -262,7 +305,7 @@ class TestMetafunc(object):
(u"αρά".encode("utf-8"), "\\xce\\xb1\\xcf\\x81\\xce\\xac"),
]
for val, expected in values:
assert _idval(val, "a", 6, None) == expected
assert _idval(val, "a", 6, idfn=None, item=None, config=None) == expected
def test_class_or_function_idval(self):
"""unittest for the expected behavior to obtain ids for parametrized
@@ -278,7 +321,7 @@ class TestMetafunc(object):
values = [(TestClass, "TestClass"), (test_function, "test_function")]
for val, expected in values:
assert _idval(val, "a", 6, None) == expected
assert _idval(val, "a", 6, None, item=None, config=None) == expected
@pytest.mark.issue250
def test_idmaker_autoname(self):
@@ -383,44 +426,7 @@ class TestMetafunc(object):
)
assert result == ["a-a0", "a-a1", "a-a2"]
@pytest.mark.issue351
def test_idmaker_idfn_exception(self):
from _pytest.python import idmaker
from _pytest.recwarn import WarningsRecorder
class BadIdsException(Exception):
pass
def ids(val):
raise BadIdsException("ids raised")
rec = WarningsRecorder()
with rec:
idmaker(
("a", "b"),
[
pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
],
idfn=ids,
)
assert [str(i.message) for i in rec.list] == [
"Raised while trying to determine id of parameter a at position 0."
"\nUpdate your code as this will raise an error in pytest-4.0.",
"Raised while trying to determine id of parameter b at position 0."
"\nUpdate your code as this will raise an error in pytest-4.0.",
"Raised while trying to determine id of parameter a at position 1."
"\nUpdate your code as this will raise an error in pytest-4.0.",
"Raised while trying to determine id of parameter b at position 1."
"\nUpdate your code as this will raise an error in pytest-4.0.",
"Raised while trying to determine id of parameter a at position 2."
"\nUpdate your code as this will raise an error in pytest-4.0.",
"Raised while trying to determine id of parameter b at position 2."
"\nUpdate your code as this will raise an error in pytest-4.0.",
]
@pytest.mark.filterwarnings("default")
def test_parametrize_ids_exception(self, testdir):
"""
:param testdir: the instance of Testdir class, a temporary
@@ -438,13 +444,14 @@ class TestMetafunc(object):
pass
"""
)
with pytest.warns(DeprecationWarning):
result = testdir.runpytest("--collect-only")
result = testdir.runpytest("--collect-only")
result.stdout.fnmatch_lines(
[
"<Module 'test_parametrize_ids_exception.py'>",
" <Function 'test_foo[a]'>",
" <Function 'test_foo[b]'>",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 0*",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 1*",
]
)
@@ -835,7 +842,7 @@ class TestMetafuncFunctional(object):
p = testdir.makepyfile(
"""
# assumes that generate/provide runs in the same process
import sys, pytest
import sys, pytest, six
def pytest_generate_tests(metafunc):
metafunc.addcall(param=metafunc)
@@ -854,11 +861,7 @@ class TestMetafuncFunctional(object):
def test_method(self, metafunc, pytestconfig):
assert metafunc.config == pytestconfig
assert metafunc.module.__name__ == __name__
if sys.version_info > (3, 0):
unbound = TestClass.test_method
else:
unbound = TestClass.test_method.im_func
# XXX actually have an unbound test function here?
unbound = six.get_unbound_function(TestClass.test_method)
assert metafunc.function == unbound
assert metafunc.cls == TestClass
"""
@@ -1426,6 +1429,39 @@ class TestMetafuncFunctionalAuto(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines(["* 3 passed *"])
def test_parametrize_some_arguments_auto_scope(self, testdir, monkeypatch):
"""Integration test for (#3941)"""
class_fix_setup = []
monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False)
func_fix_setup = []
monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False)
testdir.makepyfile(
"""
import pytest
import sys
@pytest.fixture(scope='class', autouse=True)
def class_fix(request):
sys.class_fix_setup.append(request.param)
@pytest.fixture(autouse=True)
def func_fix():
sys.func_fix_setup.append(True)
@pytest.mark.parametrize('class_fix', [10, 20], indirect=True)
class Test:
def test_foo(self):
pass
def test_bar(self):
pass
"""
)
result = testdir.runpytest_inprocess()
result.stdout.fnmatch_lines(["* 4 passed in *"])
assert func_fix_setup == [True] * 4
assert class_fix_setup == [10, 20]
def test_parametrize_issue634(self, testdir):
testdir.makepyfile(
"""

View File

@@ -1,22 +0,0 @@
import pytest
from _pytest.python import PyCollector
class PyCollectorMock(PyCollector):
"""evil hack"""
def __init__(self):
self.called = False
def _makeitem(self, *k):
"""hack to disable the actual behaviour"""
self.called = True
def test_pycollector_makeitem_is_deprecated():
collector = PyCollectorMock()
with pytest.deprecated_call():
collector.makeitem("foo", "bar")
assert collector.called

View File

@@ -1075,17 +1075,27 @@ def test_diff_newline_at_end(monkeypatch, testdir):
)
@pytest.mark.filterwarnings("default")
def test_assert_tuple_warning(testdir):
msg = "assertion is always true"
testdir.makepyfile(
"""
def test_tuple():
assert(False, 'you shall not pass')
"""
)
result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines(
["*test_assert_tuple_warning.py:2", "*assertion is always true*"]
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*test_assert_tuple_warning.py:2:*{}*".format(msg)])
# tuples with size != 2 should not trigger the warning
testdir.makepyfile(
"""
def test_tuple():
assert ()
"""
)
result = testdir.runpytest()
assert msg not in result.stdout.str()
def test_assert_indirect_tuple_no_warning(testdir):

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import glob
@@ -57,7 +58,7 @@ def getmsg(f, extra_ns=None, must_pass=False):
except AssertionError:
if must_pass:
pytest.fail("shouldn't have raised")
s = str(sys.exc_info()[1])
s = six.text_type(sys.exc_info()[1])
if not s.startswith("assert"):
return "AssertionError: " + s
return s
@@ -608,6 +609,21 @@ class TestAssertionRewrite(object):
assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
def test_custom_repr_non_ascii(self):
def f():
class A(object):
name = u"ä"
def __repr__(self):
return self.name.encode("UTF-8") # only legal in python2
a = A()
assert not a.name
msg = getmsg(f)
assert "UnicodeDecodeError" not in msg
assert "UnicodeEncodeError" not in msg
class TestRewriteOnImport(object):
def test_pycache_is_a_file(self, testdir):
@@ -759,16 +775,16 @@ def test_rewritten():
testdir.makepyfile("import a_package_without_init_py.module")
assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
def test_rewrite_warning(self, pytestconfig, monkeypatch):
hook = AssertionRewritingHook(pytestconfig)
warnings = []
def mywarn(code, msg):
warnings.append((code, msg))
monkeypatch.setattr(hook.config, "warn", mywarn)
hook.mark_rewrite("_pytest")
assert "_pytest" in warnings[0][1]
def test_rewrite_warning(self, testdir):
testdir.makeconftest(
"""
import pytest
pytest.register_assert_rewrite("_pytest")
"""
)
# needs to be a subprocess because pytester explicitly disables this warning
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines("*Module already imported*: _pytest")
def test_rewrite_module_imported_from_conftest(self, testdir):
testdir.makeconftest(
@@ -1034,6 +1050,48 @@ class TestAssertionRewriteHookDetails(object):
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines(["* 1 passed*"])
def test_reload_reloads(self, testdir):
"""Reloading a module after change picks up the change."""
testdir.tmpdir.join("file.py").write(
textwrap.dedent(
"""
def reloaded():
return False
def rewrite_self():
with open(__file__, 'w') as self:
self.write('def reloaded(): return True')
"""
)
)
testdir.tmpdir.join("pytest.ini").write(
textwrap.dedent(
"""
[pytest]
python_files = *.py
"""
)
)
testdir.makepyfile(
test_fun="""
import sys
try:
from imp import reload
except ImportError:
pass
def test_loader():
import file
assert not file.reloaded()
file.rewrite_self()
reload(file)
assert file.reloaded()
"""
)
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines(["* 1 passed*"])
def test_get_data_support(self, testdir):
"""Implement optional PEP302 api (#808).
"""
@@ -1106,21 +1164,153 @@ class TestIssue925(object):
class TestIssue2121:
def test_simple(self, testdir):
testdir.tmpdir.join("tests/file.py").ensure().write(
"""
def test_simple_failure():
assert 1 + 1 == 3
"""
)
testdir.tmpdir.join("pytest.ini").write(
textwrap.dedent(
def test_rewrite_python_files_contain_subdirs(self, testdir):
testdir.makepyfile(
**{
"tests/file.py": """
def test_simple_failure():
assert 1 + 1 == 3
"""
[pytest]
python_files = tests/**.py
"""
)
}
)
testdir.makeini(
"""
[pytest]
python_files = tests/**.py
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3")
def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch):
"""Fix infinite recursion when writing pyc files: if an import happens to be triggered when writing the pyc
file, this would cause another call to the hook, which would trigger another pyc writing, which could
trigger another import, and so on. (#3506)"""
from _pytest.assertion import rewrite
testdir.syspathinsert()
testdir.makepyfile(test_foo="def test_foo(): pass")
testdir.makepyfile(test_bar="def test_bar(): pass")
original_write_pyc = rewrite._write_pyc
write_pyc_called = []
def spy_write_pyc(*args, **kwargs):
# make a note that we have called _write_pyc
write_pyc_called.append(True)
# try to import a module at this point: we should not try to rewrite this module
assert hook.find_module("test_bar") is None
return original_write_pyc(*args, **kwargs)
monkeypatch.setattr(rewrite, "_write_pyc", spy_write_pyc)
monkeypatch.setattr(sys, "dont_write_bytecode", False)
hook = AssertionRewritingHook(pytestconfig)
assert hook.find_module("test_foo") is not None
assert len(write_pyc_called) == 1
class TestEarlyRewriteBailout(object):
@pytest.fixture
def hook(self, pytestconfig, monkeypatch, testdir):
"""Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track
if imp.find_module has been called.
"""
import imp
self.find_module_calls = []
self.initial_paths = set()
class StubSession(object):
_initialpaths = self.initial_paths
def isinitpath(self, p):
return p in self._initialpaths
def spy_imp_find_module(name, path):
self.find_module_calls.append(name)
return imp.find_module(name, path)
hook = AssertionRewritingHook(pytestconfig)
# use default patterns, otherwise we inherit pytest's testing config
hook.fnpats[:] = ["test_*.py", "*_test.py"]
monkeypatch.setattr(hook, "_imp_find_module", spy_imp_find_module)
hook.set_session(StubSession())
testdir.syspathinsert()
return hook
def test_basic(self, testdir, hook):
"""
Ensure we avoid calling imp.find_module when we know for sure a certain module will not be rewritten
to optimize assertion rewriting (#3918).
"""
testdir.makeconftest(
"""
import pytest
@pytest.fixture
def fix(): return 1
"""
)
testdir.makepyfile(test_foo="def test_foo(): pass")
testdir.makepyfile(bar="def bar(): pass")
foobar_path = testdir.makepyfile(foobar="def foobar(): pass")
self.initial_paths.add(foobar_path)
# conftest files should always be rewritten
assert hook.find_module("conftest") is not None
assert self.find_module_calls == ["conftest"]
# files matching "python_files" mask should always be rewritten
assert hook.find_module("test_foo") is not None
assert self.find_module_calls == ["conftest", "test_foo"]
# file does not match "python_files": early bailout
assert hook.find_module("bar") is None
assert self.find_module_calls == ["conftest", "test_foo"]
# file is an initial path (passed on the command-line): should be rewritten
assert hook.find_module("foobar") is not None
assert self.find_module_calls == ["conftest", "test_foo", "foobar"]
def test_pattern_contains_subdirectories(self, testdir, hook):
"""If one of the python_files patterns contain subdirectories ("tests/**.py") we can't bailout early
because we need to match with the full path, which can only be found by calling imp.find_module.
"""
p = testdir.makepyfile(
**{
"tests/file.py": """
def test_simple_failure():
assert 1 + 1 == 3
"""
}
)
testdir.syspathinsert(p.dirpath())
hook.fnpats[:] = ["tests/**.py"]
assert hook.find_module("file") is not None
assert self.find_module_calls == ["file"]
@pytest.mark.skipif(
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"
)
def test_cwd_changed(self, testdir):
testdir.makepyfile(
**{
"test_bar.py": """
import os
import shutil
import tempfile
d = tempfile.mkdtemp()
os.chdir(d)
shutil.rmtree(d)
""",
"test_foo.py": """
def test():
pass
""",
}
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 passed in *")

View File

@@ -31,6 +31,7 @@ class TestNewAPI(object):
val = config.cache.get("key/name", -2)
assert val == -2
@pytest.mark.filterwarnings("default")
def test_cache_writefail_cachfile_silent(self, testdir):
testdir.makeini("[pytest]")
testdir.tmpdir.join(".pytest_cache").write("gone wrong")
@@ -39,6 +40,9 @@ class TestNewAPI(object):
cache.set("test/broken", [])
@pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows")
@pytest.mark.filterwarnings(
"ignore:could not create cache path:pytest.PytestWarning"
)
def test_cache_writefail_permissions(self, testdir):
testdir.makeini("[pytest]")
testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0)
@@ -47,6 +51,7 @@ class TestNewAPI(object):
cache.set("test/broken", [])
@pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows")
@pytest.mark.filterwarnings("default")
def test_cache_failure_warns(self, testdir):
testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0)
testdir.makepyfile(
@@ -210,7 +215,7 @@ def test_cache_show(testdir):
class TestLastFailed(object):
def test_lastfailed_usecase(self, testdir, monkeypatch):
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
p = testdir.makepyfile(
"""
def test_1():
@@ -296,7 +301,7 @@ class TestLastFailed(object):
assert "test_a.py" not in result.stdout.str()
def test_lastfailed_difference_invocations(self, testdir, monkeypatch):
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
testdir.makepyfile(
test_a="""\
def test_a1():
@@ -330,7 +335,7 @@ class TestLastFailed(object):
result.stdout.fnmatch_lines(["*1 failed*1 desel*"])
def test_lastfailed_usecase_splice(self, testdir, monkeypatch):
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", 1)
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
testdir.makepyfile(
"""\
def test_1():
@@ -414,13 +419,7 @@ class TestLastFailed(object):
)
result = testdir.runpytest(test_a, "--lf")
result.stdout.fnmatch_lines(
[
"collected 2 items",
"run-last-failure: run all (no recorded failures)",
"*2 passed in*",
]
)
result.stdout.fnmatch_lines(["collected 2 items", "*2 passed in*"])
result = testdir.runpytest(test_b, "--lf")
result.stdout.fnmatch_lines(
@@ -475,8 +474,8 @@ class TestLastFailed(object):
)
def rlf(fail_import, fail_run):
monkeypatch.setenv("FAILIMPORT", fail_import)
monkeypatch.setenv("FAILTEST", fail_run)
monkeypatch.setenv("FAILIMPORT", str(fail_import))
monkeypatch.setenv("FAILTEST", str(fail_run))
testdir.runpytest("-q")
config = testdir.parseconfigure()
@@ -520,8 +519,8 @@ class TestLastFailed(object):
)
def rlf(fail_import, fail_run, args=()):
monkeypatch.setenv("FAILIMPORT", fail_import)
monkeypatch.setenv("FAILTEST", fail_run)
monkeypatch.setenv("FAILIMPORT", str(fail_import))
monkeypatch.setenv("FAILTEST", str(fail_run))
result = testdir.runpytest("-q", "--lf", *args)
config = testdir.parseconfigure()
@@ -617,6 +616,23 @@ class TestLastFailed(object):
assert self.get_cached_last_failed(testdir) == []
assert result.ret == 0
@pytest.mark.parametrize("quiet", [True, False])
@pytest.mark.parametrize("opt", ["--ff", "--lf"])
def test_lf_and_ff_prints_no_needless_message(self, quiet, opt, testdir):
# Issue 3853
testdir.makepyfile("def test(): assert 0")
args = [opt]
if quiet:
args.append("-q")
result = testdir.runpytest(*args)
assert "run all" not in result.stdout.str()
result = testdir.runpytest(*args)
if quiet:
assert "run all" not in result.stdout.str()
else:
assert "rerun previous" in result.stdout.str()
def get_cached_last_failed(self, testdir):
config = testdir.parseconfigure()
return sorted(config.cache.get("cache/lastfailed", {}))
@@ -868,3 +884,14 @@ class TestReadme(object):
)
testdir.runpytest()
assert self.check_readme(testdir) is True
def test_gitignore(testdir):
"""Ensure we automatically create .gitignore file in the pytest_cache directory (#3286)."""
from _pytest.cacheprovider import Cache
config = testdir.parseconfig()
cache = Cache.for_config(config)
cache.set("foo", "bar")
msg = "# created by pytest automatically, do not change\n*"
assert cache._cachedir.joinpath(".gitignore").read_text(encoding="UTF-8") == msg

View File

@@ -18,7 +18,9 @@ from _pytest.capture import CaptureManager
from _pytest.main import EXIT_NOTESTSCOLLECTED
needsosdup = pytest.mark.xfail("not hasattr(os, 'dup')")
needsosdup = pytest.mark.skipif(
not hasattr(os, "dup"), reason="test needs os.dup, not available on this platform"
)
def tobytes(obj):
@@ -61,9 +63,8 @@ class TestCaptureManager(object):
pytest_addoption(parser)
assert parser._groups[0].options[0].default == "sys"
@needsosdup
@pytest.mark.parametrize(
"method", ["no", "sys", pytest.mark.skipif('not hasattr(os, "dup")', "fd")]
"method", ["no", "sys", pytest.param("fd", marks=needsosdup)]
)
def test_capturing_basic_api(self, method):
capouter = StdCaptureFD()

View File

@@ -135,13 +135,13 @@ class TestConfigCmdlineParsing(object):
"""
)
testdir.makefile(
".cfg",
".ini",
custom="""
[pytest]
custom = 1
""",
)
config = testdir.parseconfig("-c", "custom.cfg")
config = testdir.parseconfig("-c", "custom.ini")
assert config.getini("custom") == "1"
testdir.makefile(
@@ -155,8 +155,8 @@ class TestConfigCmdlineParsing(object):
assert config.getini("custom") == "1"
def test_absolute_win32_path(self, testdir):
temp_cfg_file = testdir.makefile(
".cfg",
temp_ini_file = testdir.makefile(
".ini",
custom="""
[pytest]
addopts = --version
@@ -164,8 +164,8 @@ class TestConfigCmdlineParsing(object):
)
from os.path import normpath
temp_cfg_file = normpath(str(temp_cfg_file))
ret = pytest.main("-c " + temp_cfg_file)
temp_ini_file = normpath(str(temp_ini_file))
ret = pytest.main(["-c", temp_ini_file])
assert ret == _pytest.main.EXIT_OK
@@ -605,6 +605,26 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block
)
@pytest.mark.parametrize(
"parse_args,should_load", [(("-p", "mytestplugin"), True), ((), False)]
)
def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
pkg_resources = pytest.importorskip("pkg_resources")
def my_iter(name):
raise AssertionError("Should not be called")
class PseudoPlugin(object):
x = 42
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin())
config = testdir.parseconfig(*parse_args)
has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None
assert has_loaded == should_load
def test_cmdline_processargs_simple(testdir):
testdir.makeconftest(
"""
@@ -763,13 +783,14 @@ def test_collect_pytest_prefix_bug(pytestconfig):
assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None
class TestWarning(object):
class TestLegacyWarning(object):
@pytest.mark.filterwarnings("default")
def test_warn_config(self, testdir):
testdir.makeconftest(
"""
values = []
def pytest_configure(config):
config.warn("C1", "hello")
def pytest_runtest_setup(item):
item.config.warn("C1", "hello")
def pytest_logwarning(code, message):
if message == "hello" and code == "C1":
values.append(1)
@@ -782,24 +803,31 @@ class TestWarning(object):
assert conftest.values == [1]
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["*hello", "*config.warn has been deprecated*", "*1 passed*"]
)
def test_warn_on_test_item_from_request(self, testdir, request):
@pytest.mark.filterwarnings("default")
@pytest.mark.parametrize("use_kw", [True, False])
def test_warn_on_test_item_from_request(self, testdir, use_kw):
code_kw = "code=" if use_kw else ""
message_kw = "message=" if use_kw else ""
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fix(request):
request.node.warn("T1", "hello")
request.node.warn({code_kw}"T1", {message_kw}"hello")
def test_hello(fix):
pass
"""
""".format(
code_kw=code_kw, message_kw=message_kw
)
)
result = testdir.runpytest("--disable-pytest-warnings")
assert result.parseoutcomes()["warnings"] > 0
assert "hello" not in result.stdout.str()
result = testdir.runpytest()
@@ -808,6 +836,7 @@ class TestWarning(object):
===*warnings summary*===
*test_warn_on_test_item_from_request.py::test_hello*
*hello*
*test_warn_on_test_item_from_request.py:7:*Node.warn(code, message) form has been deprecated*
"""
)
@@ -827,7 +856,7 @@ class TestRootdir(object):
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
def test_with_ini(self, tmpdir, name):
inifile = tmpdir.join(name)
inifile.write("[pytest]\n")
inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n")
a = tmpdir.mkdir("a")
b = a.mkdir("b")
@@ -873,11 +902,14 @@ class TestRootdir(object):
class TestOverrideIniArgs(object):
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
def test_override_ini_names(self, testdir, name):
section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]"
testdir.tmpdir.join(name).write(
textwrap.dedent(
"""
[pytest]
custom = 1.0"""
{section}
custom = 1.0""".format(
section=section
)
)
)
testdir.makeconftest(

View File

@@ -30,6 +30,7 @@ def conftest_setinitial(conftest, args, confcutdir=None):
self.file_or_dir = args
self.confcutdir = str(confcutdir)
self.noconftest = False
self.pyargs = False
conftest._set_initial_conftests(Namespace())

View File

@@ -850,7 +850,7 @@ def test_logxml_path_expansion(tmpdir, monkeypatch):
assert xml_tilde.logfile == home_tilde
# this is here for when $HOME is not set correct
monkeypatch.setenv("HOME", tmpdir)
monkeypatch.setenv("HOME", str(tmpdir))
home_var = os.path.normpath(os.path.expandvars("$HOME/test.xml"))
xml_var = LogXML("$HOME%stest.xml" % tmpdir.sep, None)
@@ -1005,6 +1005,7 @@ def test_record_property_same_name(testdir):
pnodes[1].assert_attr(name="foo", value="baz")
@pytest.mark.filterwarnings("default")
def test_record_attribute(testdir):
testdir.makepyfile(
"""
@@ -1023,7 +1024,7 @@ def test_record_attribute(testdir):
tnode.assert_attr(bar="1")
tnode.assert_attr(foo="<1")
result.stdout.fnmatch_lines(
["test_record_attribute.py::test_record", "*record_xml_attribute*experimental*"]
["*test_record_attribute.py:6:*record_xml_attribute is an experimental feature"]
)

View File

@@ -16,7 +16,7 @@ from _pytest.mark import (
from _pytest.nodes import Node
ignore_markinfo = pytest.mark.filterwarnings(
"ignore:MarkInfo objects:_pytest.deprecated.RemovedInPytest4Warning"
"ignore:MarkInfo objects:pytest.RemovedInPytest4Warning"
)
@@ -799,6 +799,18 @@ class TestFunctional(object):
deselected_tests = dlist[0].items
assert len(deselected_tests) == 2
def test_invalid_m_option(self, testdir):
testdir.makepyfile(
"""
def test_a():
pass
"""
)
result = testdir.runpytest("-m bogus/")
result.stdout.fnmatch_lines(
["INTERNALERROR> Marker expression must be valid Python!"]
)
def test_keywords_at_node_level(self, testdir):
testdir.makepyfile(
"""
@@ -1039,10 +1051,19 @@ class TestKeywordSelection(object):
),
],
)
@pytest.mark.filterwarnings("ignore")
@pytest.mark.filterwarnings("default")
def test_parameterset_extractfrom(argval, expected):
extracted = ParameterSet.extract_from(argval)
from _pytest.deprecated import MARK_PARAMETERSET_UNPACKING
warn_called = []
class DummyItem:
def warn(self, warning):
warn_called.append(warning)
extracted = ParameterSet.extract_from(argval, belonging_definition=DummyItem())
assert extracted == expected
assert warn_called == [MARK_PARAMETERSET_UNPACKING]
def test_legacy_transfer():

View File

@@ -3,6 +3,8 @@ import os
import sys
import textwrap
import six
import pytest
from _pytest.monkeypatch import MonkeyPatch
@@ -163,7 +165,8 @@ def test_delitem():
def test_setenv():
monkeypatch = MonkeyPatch()
monkeypatch.setenv("XYZ123", 2)
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 2)
import os
assert os.environ["XYZ123"] == "2"
@@ -192,13 +195,49 @@ def test_delenv():
del os.environ[name]
class TestEnvironWarnings(object):
"""
os.environ keys and values should be native strings, otherwise it will cause problems with other modules (notably
subprocess). On Python 2 os.environ accepts anything without complaining, while Python 3 does the right thing
and raises an error.
"""
VAR_NAME = u"PYTEST_INTERNAL_MY_VAR"
@pytest.mark.skipif(six.PY3, reason="Python 2 only test")
def test_setenv_unicode_key(self, monkeypatch):
with pytest.warns(
pytest.PytestWarning,
match="Environment variable name {!r} should be str".format(self.VAR_NAME),
):
monkeypatch.setenv(self.VAR_NAME, "2")
@pytest.mark.skipif(six.PY3, reason="Python 2 only test")
def test_delenv_unicode_key(self, monkeypatch):
with pytest.warns(
pytest.PytestWarning,
match="Environment variable name {!r} should be str".format(self.VAR_NAME),
):
monkeypatch.delenv(self.VAR_NAME, raising=False)
def test_setenv_non_str_warning(self, monkeypatch):
value = 2
msg = (
"Environment variable value {!r} should be str, converted to str implicitly"
)
with pytest.warns(pytest.PytestWarning, match=msg.format(value)):
monkeypatch.setenv(str(self.VAR_NAME), value)
def test_setenv_prepend():
import os
monkeypatch = MonkeyPatch()
monkeypatch.setenv("XYZ123", 2, prepend="-")
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 2, prepend="-")
assert os.environ["XYZ123"] == "2"
monkeypatch.setenv("XYZ123", 3, prepend="-")
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 3, prepend="-")
assert os.environ["XYZ123"] == "3-2"
monkeypatch.undo()
assert "XYZ123" not in os.environ

View File

@@ -19,3 +19,14 @@ from _pytest import nodes
def test_ischildnode(baseid, nodeid, expected):
result = nodes.ischildnode(baseid, nodeid)
assert result is expected
def test_std_warn_not_pytestwarning(testdir):
items = testdir.getitems(
"""
def test():
pass
"""
)
with pytest.raises(ValueError, match=".*instance of PytestWarning.*"):
items[0].warn(UserWarning("some warning"))

69
testing/test_paths.py Normal file
View File

@@ -0,0 +1,69 @@
import sys
import py
import pytest
from _pytest.paths import fnmatch_ex
class TestPort:
"""Test that our port of py.common.FNMatcher (fnmatch_ex) produces the same results as the
original py.path.local.fnmatch method.
"""
@pytest.fixture(params=["pathlib", "py.path"])
def match(self, request):
if request.param == "py.path":
def match_(pattern, path):
return py.path.local(path).fnmatch(pattern)
else:
assert request.param == "pathlib"
def match_(pattern, path):
return fnmatch_ex(pattern, path)
return match_
if sys.platform == "win32":
drv1 = "c:"
drv2 = "d:"
else:
drv1 = "/c"
drv2 = "/d"
@pytest.mark.parametrize(
"pattern, path",
[
("*.py", "foo.py"),
("*.py", "bar/foo.py"),
("test_*.py", "foo/test_foo.py"),
("tests/*.py", "tests/foo.py"),
(drv1 + "/*.py", drv1 + "/foo.py"),
(drv1 + "/foo/*.py", drv1 + "/foo/foo.py"),
("tests/**/test*.py", "tests/foo/test_foo.py"),
("tests/**/doc/test*.py", "tests/foo/bar/doc/test_foo.py"),
("tests/**/doc/**/test*.py", "tests/foo/doc/bar/test_foo.py"),
],
)
def test_matching(self, match, pattern, path):
assert match(pattern, path)
@pytest.mark.parametrize(
"pattern, path",
[
("*.py", "foo.pyc"),
("*.py", "foo/foo.pyc"),
("tests/*.py", "foo/foo.py"),
(drv1 + "/*.py", drv2 + "/foo.py"),
(drv1 + "/foo/*.py", drv2 + "/foo/foo.py"),
("tests/**/test*.py", "tests/foo.py"),
("tests/**/test*.py", "foo/test_foo.py"),
("tests/**/doc/test*.py", "tests/foo/bar/doc/foo.py"),
("tests/**/doc/test*.py", "tests/foo/bar/test_foo.py"),
],
)
def test_not_matching(self, match, pattern, path):
assert not match(pattern, path)

View File

@@ -397,6 +397,24 @@ class TestPDB(object):
child.read()
self.flush(child)
def test_pdb_with_caplog_on_pdb_invocation(self, testdir):
p1 = testdir.makepyfile(
"""
def test_1(capsys, caplog):
import logging
logging.getLogger(__name__).warning("some_warning")
assert 0
"""
)
child = testdir.spawn_pytest("--pdb %s" % str(p1))
child.send("caplog.record_tuples\n")
child.expect_exact(
"[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]"
)
child.sendeof()
child.read()
self.flush(child)
def test_set_trace_capturing_afterwards(self, testdir):
p1 = testdir.makepyfile(
"""

View File

@@ -83,6 +83,57 @@ def test_testdir_runs_with_plugin(testdir):
result.assert_outcomes(passed=1)
def test_runresult_assertion_on_xfail(testdir):
testdir.makepyfile(
"""
import pytest
pytest_plugins = "pytester"
@pytest.mark.xfail
def test_potato():
assert False
"""
)
result = testdir.runpytest()
result.assert_outcomes(xfailed=1)
assert result.ret == 0
def test_runresult_assertion_on_xpassed(testdir):
testdir.makepyfile(
"""
import pytest
pytest_plugins = "pytester"
@pytest.mark.xfail
def test_potato():
assert True
"""
)
result = testdir.runpytest()
result.assert_outcomes(xpassed=1)
assert result.ret == 0
def test_xpassed_with_strict_is_considered_a_failure(testdir):
testdir.makepyfile(
"""
import pytest
pytest_plugins = "pytester"
@pytest.mark.xfail(strict=True)
def test_potato():
assert True
"""
)
result = testdir.runpytest()
result.assert_outcomes(failed=1)
assert result.ret != 0
def make_holder():
class apiclass(object):
def pytest_xyz(self, arg):

View File

@@ -7,7 +7,7 @@ from _pytest.recwarn import WarningsRecorder
def test_recwarn_functional(testdir):
reprec = testdir.inline_runsource(
testdir.makepyfile(
"""
import warnings
def test_method(recwarn):
@@ -16,8 +16,8 @@ def test_recwarn_functional(testdir):
assert isinstance(warn.message, UserWarning)
"""
)
res = reprec.countoutcomes()
assert tuple(res) == (1, 0, 0), res
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
class TestWarningsRecorderChecker(object):

View File

@@ -13,6 +13,9 @@ from _pytest.resultlog import (
)
pytestmark = pytest.mark.filterwarnings("ignore:--result-log is deprecated")
def test_generic_path(testdir):
from _pytest.main import Session

View File

@@ -3,6 +3,7 @@ terminal reporting of the full testing process.
"""
from __future__ import absolute_import, division, print_function
import collections
import os
import sys
import textwrap
@@ -472,7 +473,7 @@ class TestTerminalFunctional(object):
def test_show_deselected_items_using_markexpr_before_test_execution(self, testdir):
testdir.makepyfile(
"""
test_show_deselected="""
import pytest
@pytest.mark.foo
@@ -491,7 +492,7 @@ class TestTerminalFunctional(object):
result.stdout.fnmatch_lines(
[
"collected 3 items / 1 deselected",
"*test_show_des*.py ..*",
"*test_show_deselected.py ..*",
"*= 2 passed, 1 deselected in * =*",
]
)
@@ -680,14 +681,22 @@ def test_pass_reporting_on_fail(testdir):
def test_pass_output_reporting(testdir):
testdir.makepyfile(
"""
def test_pass_output():
def test_pass_has_output():
print("Four score and seven years ago...")
def test_pass_no_output():
pass
"""
)
result = testdir.runpytest()
assert "Four score and seven years ago..." not in result.stdout.str()
s = result.stdout.str()
assert "test_pass_has_output" not in s
assert "Four score and seven years ago..." not in s
assert "test_pass_no_output" not in s
result = testdir.runpytest("-rP")
result.stdout.fnmatch_lines(["Four score and seven years ago..."])
result.stdout.fnmatch_lines(
["*test_pass_has_output*", "Four score and seven years ago..."]
)
assert "test_pass_no_output" not in result.stdout.str()
def test_color_yes(testdir):
@@ -1046,20 +1055,21 @@ def test_terminal_summary(testdir):
)
@pytest.mark.filterwarnings("default")
def test_terminal_summary_warnings_are_displayed(testdir):
"""Test that warnings emitted during pytest_terminal_summary are displayed.
(#1305).
"""
testdir.makeconftest(
"""
import warnings
def pytest_terminal_summary(terminalreporter):
config = terminalreporter.config
config.warn('C1', 'internal warning')
warnings.warn(UserWarning('internal warning'))
"""
)
result = testdir.runpytest("-rw")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
["<undetermined location>", "*internal warning", "*== 1 warnings in *"]
["*conftest.py:3:*internal warning", "*== 1 warnings in *"]
)
assert "None" not in result.stdout.str()
@@ -1134,7 +1144,53 @@ def test_no_trailing_whitespace_after_inifile_word(testdir):
assert "inifile: tox.ini\n" in result.stdout.str()
class TestProgress(object):
class TestClassicOutputStyle(object):
"""Ensure classic output style works as expected (#3883)"""
@pytest.fixture
def test_files(self, testdir):
testdir.makepyfile(
**{
"test_one.py": "def test_one(): pass",
"test_two.py": "def test_two(): assert 0",
"sub/test_three.py": """
def test_three_1(): pass
def test_three_2(): assert 0
def test_three_3(): pass
""",
}
)
def test_normal_verbosity(self, testdir, test_files):
result = testdir.runpytest("-o", "console_output_style=classic")
result.stdout.fnmatch_lines(
[
"test_one.py .",
"test_two.py F",
"sub{}test_three.py .F.".format(os.sep),
"*2 failed, 3 passed in*",
]
)
def test_verbose(self, testdir, test_files):
result = testdir.runpytest("-o", "console_output_style=classic", "-v")
result.stdout.fnmatch_lines(
[
"test_one.py::test_one PASSED",
"test_two.py::test_two FAILED",
"sub{}test_three.py::test_three_1 PASSED".format(os.sep),
"sub{}test_three.py::test_three_2 FAILED".format(os.sep),
"sub{}test_three.py::test_three_3 PASSED".format(os.sep),
"*2 failed, 3 passed in*",
]
)
def test_quiet(self, testdir, test_files):
result = testdir.runpytest("-o", "console_output_style=classic", "-q")
result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"])
class TestProgressOutputStyle(object):
@pytest.fixture
def many_tests_files(self, testdir):
testdir.makepyfile(
@@ -1183,6 +1239,22 @@ class TestProgress(object):
]
)
def test_count(self, many_tests_files, testdir):
testdir.makeini(
"""
[pytest]
console_output_style = count
"""
)
output = testdir.runpytest()
output.stdout.re_match_lines(
[
r"test_bar.py \.{10} \s+ \[10/20\]",
r"test_foo.py \.{5} \s+ \[15/20\]",
r"test_foobar.py \.{5} \s+ \[20/20\]",
]
)
def test_verbose(self, many_tests_files, testdir):
output = testdir.runpytest("-v")
output.stdout.re_match_lines(
@@ -1193,11 +1265,38 @@ class TestProgress(object):
]
)
def test_verbose_count(self, many_tests_files, testdir):
testdir.makeini(
"""
[pytest]
console_output_style = count
"""
)
output = testdir.runpytest("-v")
output.stdout.re_match_lines(
[
r"test_bar.py::test_bar\[0\] PASSED \s+ \[ 1/20\]",
r"test_foo.py::test_foo\[4\] PASSED \s+ \[15/20\]",
r"test_foobar.py::test_foobar\[4\] PASSED \s+ \[20/20\]",
]
)
def test_xdist_normal(self, many_tests_files, testdir):
pytest.importorskip("xdist")
output = testdir.runpytest("-n2")
output.stdout.re_match_lines([r"\.{20} \s+ \[100%\]"])
def test_xdist_normal_count(self, many_tests_files, testdir):
pytest.importorskip("xdist")
testdir.makeini(
"""
[pytest]
console_output_style = count
"""
)
output = testdir.runpytest("-n2")
output.stdout.re_match_lines([r"\.{20} \s+ \[20/20\]"])
def test_xdist_verbose(self, many_tests_files, testdir):
pytest.importorskip("xdist")
output = testdir.runpytest("-n2", "-v")

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