Compare commits

...

534 Commits
3.3.1 ... 3.5.0

Author SHA1 Message Date
Bruno Oliveira
beacecf29b Preparing release version 3.5.0 2018-03-21 20:46:07 +00:00
Bruno Oliveira
b148770066 Fix example in usage.rst 2018-03-21 20:45:28 +00:00
Bruno Oliveira
f3c87a77a7 Merge remote-tracking branch 'upstream/master' into release-3.5.0 2018-03-21 20:39:44 +00:00
Bruno Oliveira
6f95189cf7 Merge pull request #3306 from nicoddemus/2405-scope-fixture-order
Instantiate fixtures by scope order in test function requests
2018-03-21 17:37:05 -03:00
Ronny Pfannschmidt
add5ce0fb8 Merge pull request #3230 from brianmaissy/features
deprecate pytest_plugins in non-top-level conftest
2018-03-21 07:44:34 +01:00
Bruno Oliveira
59e7fd478e Sort fixtures by scope when determining fixture closure
Fix #2405
2018-03-20 20:10:53 -03:00
Bruno Oliveira
9e24b09a9f Use re_match_lines in test_class_ordering
"[1-a]" works fine using fnmatch_lines, but "[a-1]" breaks horribly
inside `re`.
2018-03-20 20:10:52 -03:00
Bruno Oliveira
d03e38941b Merge pull request #3295 from brianmaissy/feature/last-failed-no-failures-behavior
implemented --last-failed-no-failures
2018-03-20 19:17:25 -03:00
Bruno Oliveira
672239b149 Merge pull request #3321 from jeffreyrack/'test_from_base_fix'
3245: Fix tests to not fail when running from base directory
2018-03-20 18:45:56 -03:00
Jeffrey Rackauckas
9b449eee15 Add self to authors 2018-03-18 21:28:20 -07:00
Jeffrey Rackauckas
c1d73bb535 Modifying changelog to reflect new changes for 3245 2018-03-18 21:27:48 -07:00
Jeffrey Rackauckas
a4cf380343 Fix tests to not fail when running from root. 2018-03-18 21:23:39 -07:00
Ronny Pfannschmidt
f61d0525a5 Merge pull request #3318 from nicoddemus/merge-master-into-features
Merge master into features
2018-03-17 09:40:32 +01:00
Kale Kundert
86d6804e60 Merge pull request #3313 from tadeu/approx-array-scalar
Add support for `pytest.approx` comparisons between array and scalar
2018-03-16 23:29:46 -07:00
Bruno Oliveira
1fff81e21d Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2018-03-16 18:15:28 -03:00
Bruno Oliveira
93847bfeb4 Merge pull request #3315 from pytest-dev/issue/3314
Allow DontReadFromInput to produce iterator without error.
2018-03-16 10:05:54 -03:00
Tadeu Manoel
a754f00ae7 Improve numpy.approx array-scalar comparisons
So that `self.expected` in ApproxNumpy is always a numpy array.
2018-03-16 09:01:18 -03:00
Bruno Oliveira
278d8ac74e Fix caplog docstring: indentation caused errors during docs build 2018-03-16 00:06:15 -03:00
Bruno Oliveira
17468fc99c Fix caplog docstring: indentation caused errors during docs build 2018-03-16 00:04:28 -03:00
Jason R. Coombs
b66019202e Fix test failure on Python 2. Ref #3314. 2018-03-15 21:33:58 -04:00
Jason R. Coombs
965a030564 Add changelog. Ref #3314. 2018-03-15 21:31:12 -04:00
Jason R. Coombs
2f47624b19 Allow DontReadFromInput to produce iterator without error. Fixes #3314. 2018-03-15 21:26:40 -04:00
Jason R. Coombs
3c3fc3bb9d Add test capturing new expectation. Ref #3314. 2018-03-15 21:25:50 -04:00
Tadeu Manoel
42c84f4f30 Add fixes to numpy.approx array-scalar comparisons (from PR suggestions) 2018-03-15 13:41:58 -03:00
Bruno Oliveira
fbcf1a90c9 Merge pull request #3193 from pytest-dev/ref-docs
Reference docs
2018-03-15 12:04:34 -03:00
Tadeu Manoel
97f9a8bfdf Add fixes to make numpy.approx array-scalar comparisons work with older numpy versions 2018-03-14 17:10:35 -03:00
Tadeu Manoel
161d4e5fe4 Add support for pytest.approx comparisons between scalar and array (inverted order) 2018-03-14 16:29:04 -03:00
Tadeu Manoel
c34dde7a3f Add support for pytest.approx comparisons between array and scalar 2018-03-14 15:47:21 -03:00
Ronny Pfannschmidt
1b535387bf Merge pull request #3309 from tareqalayan/issue-3308
issue #3308: fix example in documentation
2018-03-14 15:52:55 +01:00
Tareq Alayan
7e53f9432c issue #3308: fix example in documentation
https://github.com/pytest-dev/pytest/issues/3308
2018-03-14 15:44:43 +02:00
Ronny Pfannschmidt
b2b629f462 Merge pull request #3305 from nicoddemus/2370-tmpfile-close-py27
Suppress ``IOError`` when closing the temporary file used for capturing streams in Python 2.7
2018-03-14 06:42:06 +01:00
Bruno Oliveira
cbb2c55dea Merge pull request #3304 from RonnyPfannschmidt/cmdoptions-removal
remove CmdOptions since we can use argparse.Namespace()
2018-03-13 17:48:21 -03:00
Bruno Oliveira
9517c3a2aa Suppress `IOError` when closing the temporary file used for capturing streams in Python 2.7.
Fix #2370
2018-03-13 17:41:42 -03:00
Bruno Oliveira
4175ed8b43 Merge pull request #3296 from RonnyPfannschmidt/verbosity-actions
unify cli verbosity handling
2018-03-13 17:38:41 -03:00
Ronny Pfannschmidt
87f2003245 remove CmdOptions since we can use argparse.Namespace() 2018-03-13 18:13:56 +01:00
Ronny Pfannschmidt
2612d967f8 Merge pull request #3301 from ankostis/caplog_clear_text
Fix #3297 where caplog.clear() did not clear text, just records
2018-03-13 16:39:53 +01:00
Ronny Pfannschmidt
37a52607c2 unify cli verbosity handling
based on https://github.com/pytest-dev/pytest/issues/3294#issuecomment-372190084

we really shouldnt have N options we post mortem hack together to determine verbosity
this change starts by unifying the data, we still need to handle deprecation/removal of config.quiet
2018-03-13 07:05:22 +01:00
Bruno Oliveira
51c0256cd4 Remove :py:mod: ref from changelog: fails our linting check 2018-03-12 20:57:51 -03:00
Bruno Oliveira
f8791c9246 Merge pull request #3292 from RonnyPfannschmidt/exception-attrs
internal refactor port exc FOrmattedExcinfo to attrs, remove old code
2018-03-12 20:54:52 -03:00
Kostis Anagnostopoulos
02bec7a3bb Add changelog & myself (ankostis) to authors 2018-03-13 01:46:44 +02:00
Bruno Oliveira
4459aa3d8b Remove redundant "# content of pytest.ini" headers from examples 2018-03-12 20:09:31 -03:00
Bruno Oliveira
2e347643a3 Add ini options for logging 2018-03-12 20:09:05 -03:00
Bruno Oliveira
8629ef6a78 Add missing ini options: markers, usefixtures and xfail_strict 2018-03-12 19:43:31 -03:00
Thomas Hisch
5e92644f94 Properly reset LogCaptureHandler in caplog
Closes #3297
2018-03-13 00:36:51 +02:00
Bruno Oliveira
3198596f8a Sort ini options 2018-03-12 19:33:34 -03:00
Kostis Anagnostopoulos
68375513f3 Add TC to demonstrate #3297 that caplog.clear() does not clean text 2018-03-13 00:28:47 +02:00
Bruno Oliveira
61b8ea8656 Add doctest_encoding and junit_suite_name ini options 2018-03-12 19:27:17 -03:00
Bruno Oliveira
0557ab431f Merge pull request #3291 from RonnyPfannschmidt/small-moves
move some code and make nodeid computed early
2018-03-12 18:49:13 -03:00
Bruno Oliveira
8035f6cced Fix typo in docs 2018-03-12 18:32:51 -03:00
Bruno Oliveira
0302622310 Improve CHANGELOG entry a bit 2018-03-12 18:22:12 -03:00
Bruno Oliveira
3909225bf9 Merge pull request #3300 from JulienPalard/typo
Typo in example, os.getcwd does not take any parameter.
2018-03-12 12:16:18 -03:00
Julien Palard
e71d907d34 Typo in example, os.getcwd does not take any parameter. 2018-03-12 15:19:26 +01:00
Brian Maissy
d2e533b8a3 implemented --last-failed-no-failures 2018-03-10 22:45:45 +02:00
Brian Maissy
54b15f5826 deprecated pytest_plugins in non-top-level conftest 2018-03-10 21:20:12 +02:00
Bruno Oliveira
d2dbbd4caa Add CHANGELOG entry 2018-03-09 17:46:44 -03:00
Bruno Oliveira
5f9bc557ea Fix linting 2018-03-09 17:44:39 -03:00
Ronny Pfannschmidt
543bac925a fix if-chain in _code.source 2018-03-09 16:50:46 +01:00
Ronny Pfannschmidt
2fe56b97c9 remove unused assertion parameter in source and minor cleanups 2018-03-09 15:03:57 +01:00
Ronny Pfannschmidt
3284d575e8 readline generator no longer needs to yield empty strings 2018-03-09 14:58:11 +01:00
Ronny Pfannschmidt
a406ca14d6 remove getstatementrange_old - its documented for python <= 2.4 2018-03-09 14:48:28 +01:00
Ronny Pfannschmidt
2e5337f5e3 makr strutures: lint fixings, removal of shadowing 2018-03-09 14:39:54 +01:00
Ronny Pfannschmidt
74884b1901 turn FormattedExcinfo into a attrs class 2018-03-09 14:21:56 +01:00
Ronny Pfannschmidt
c67f45b716 simplify a few imports in _pytest._code.source 2018-03-09 12:06:50 +01:00
Ronny Pfannschmidt
50e682d2db add changelog 2018-03-09 11:06:26 +01:00
Ronny Pfannschmidt
5e5935759e make nodeids precalculated, there is no sane reason to commpute lazyly 2018-03-09 10:52:59 +01:00
Ronny Pfannschmidt
45b6b7df92 move nodekeywords to the mark structures 2018-03-09 10:23:18 +01:00
Ronny Pfannschmidt
c9b9d796e6 remove dead code 2018-03-09 10:22:43 +01:00
Ronny Pfannschmidt
09ce84e64e remove unneeded commas 2018-03-09 10:21:42 +01:00
Bruno Oliveira
8243900960 Move definition of ini options to reference 2018-03-07 20:45:41 -03:00
Bruno Oliveira
c0fe4d483d Add environment variables 2018-03-06 20:41:21 -03:00
Bruno Oliveira
18a47bfd22 Add docs for CaptureFixture 2018-03-06 20:41:21 -03:00
Bruno Oliveira
3979f9ba3a Add pytest_plugins and pytestmark special variables 2018-03-06 20:41:21 -03:00
Bruno Oliveira
443888248a Improve fixture reference a bit 2018-03-06 20:41:21 -03:00
Bruno Oliveira
69d49f18e9 Add pytest_addhooks 2018-03-06 20:41:21 -03:00
Bruno Oliveira
8b7e6df73d Add register_assert_rewrite and improve signature of other functions 2018-03-06 20:41:21 -03:00
Bruno Oliveira
9c2203d381 Reorder top level: functions, marks, fixtures, hooks and objects 2018-03-06 20:41:21 -03:00
Bruno Oliveira
df3a4111a9 Sort objects by name 2018-03-06 20:41:20 -03:00
Bruno Oliveira
988ace9eb2 Add documentation for marks 2018-03-06 20:41:20 -03:00
Bruno Oliveira
ed8b1efc16 Add a CHANGELOG entry for the reference docs
Fix #1713
2018-03-06 20:41:20 -03:00
Bruno Oliveira
f7178654e5 Add recwarn and tmpdir fixtures to the reference docs 2018-03-06 20:41:20 -03:00
Bruno Oliveira
f1df6c5a60 Add testdir fixture to reference 2018-03-06 20:41:20 -03:00
Bruno Oliveira
66009b0f91 Add tutorial links to all references 2018-03-06 20:41:20 -03:00
Bruno Oliveira
215c6b281e Add Monkeypatch and tutorial links for logging 2018-03-06 20:41:20 -03:00
Bruno Oliveira
adcb28f61b Add caplog to reference 2018-03-06 20:41:20 -03:00
Bruno Oliveira
111c6d6a22 Improve pytestconfig and add record_xml_property 2018-03-06 20:41:20 -03:00
Bruno Oliveira
7ba8a4ee75 Use `pytest.` prefix for outcomes 2018-03-06 20:41:20 -03:00
Bruno Oliveira
400558cc9c Add pytestconfig fixture 2018-03-06 20:41:20 -03:00
Bruno Oliveira
918dffba96 Add :with: modifier to raises() and deprecated_call() 2018-03-06 20:41:20 -03:00
Bruno Oliveira
44e2715529 Add sections to pytest's builtin API 2018-03-06 20:41:20 -03:00
Bruno Oliveira
b53c4246ef Move most of 'builtin' to 'reference' 2018-03-06 20:41:19 -03:00
Bruno Oliveira
e73d4f4e1f Add local table of contents in reference.rst 2018-03-06 20:41:19 -03:00
Bruno Oliveira
e7bce90d29 Remove basepython from testenv:docs so it can be built with Python 3 2018-03-06 20:41:19 -03:00
Bruno Oliveira
623bab4447 Use :no-auto-options: in capsys fixtures
Otherwise they are recognized as iterators
2018-03-06 20:41:19 -03:00
Bruno Oliveira
70f93263e9 Add reference docs to doctest_namespace 2018-03-06 20:41:19 -03:00
Bruno Oliveira
749288dcb6 Add reference docs to cache and capture fixtures 2018-03-06 20:41:19 -03:00
Bruno Oliveira
6fa9768545 Use sphinxcontrib-trio extension
This adds some more capabilities to sphinx, like marking
functions as decorators or with contexts
2018-03-06 20:41:19 -03:00
Bruno Oliveira
6b4565f8d1 Reorder the global toc and include 'contributing' section 2018-03-06 20:41:19 -03:00
Bruno Oliveira
6d4e72e1eb Cleanup the links section to only external links
Internal links should be displayed in the side-bar
2018-03-06 20:41:19 -03:00
Bruno Oliveira
162557c2b2 Remove internal plugin list
This gets out of sync and shows internal plugins which don't make
much sense
2018-03-06 20:41:19 -03:00
Bruno Oliveira
d4c3850231 Create reference and moved Objects and Hooks reference from writing_plugins 2018-03-06 20:40:55 -03:00
Bruno Oliveira
28df322500 Merge pull request #3283 from jeffreyrack/3245-doc-test-failures
3245: Add doc to norecursedirs in tox.ini
2018-03-06 19:22:55 -03:00
Bruno Oliveira
d6ddeb395b Merge pull request #3285 from RonnyPfannschmidt/minor-refactor
Minor refactors
2018-03-06 19:22:48 -03:00
Ronny Pfannschmidt
c4430e4354 extract _warn_about_missing_assertion into freestanding function 2018-03-06 10:32:41 +01:00
Ronny Pfannschmidt
fbf01bd31a enhance skip except clause by directly using the Skipped exception 2018-03-06 10:29:26 +01:00
Jeffrey Rackauckas
e42fe5f0f9 Add doc to norecursedirs in tox.ini 2018-03-05 19:35:41 -08:00
Bruno Oliveira
ade7ad25c7 Merge remote-tracking branch 'upstream/master' into features 2018-03-05 19:48:25 -03:00
Bruno Oliveira
27c4de242f Merge pull request #3281 from nicoddemus/release-3.4.2
Preparing release version 3.4.2
2018-03-05 19:42:59 -03:00
Bruno Oliveira
e8368e6c2e Fix duplicated link in CHANGELOG 2018-03-05 07:26:38 -03:00
Bruno Oliveira
fac8208e8f Preparing release version 3.4.2 2018-03-04 19:37:49 +00:00
Bruno Oliveira
9879ac5911 Merge pull request #3279 from nicoddemus/introduce-more_itertools
Re-Introduce more_itertools
2018-03-04 16:33:58 -03:00
Bruno Oliveira
51abdb80db Merge pull request #3278 from nicoddemus/revert-more_itertools-master
Revert introduction of more_itertools on master
2018-03-04 16:33:31 -03:00
Bruno Oliveira
25399da904 Reintroduce more_itertools dependency
Reintroduce more_itertools that was added in #3265, now in the features branch
2018-03-04 10:59:46 -03:00
Bruno Oliveira
54884b8c87 Merge branch 'revert-more_itertools-master' into introduce-more_itertools 2018-03-04 10:58:52 -03:00
Bruno Oliveira
65534682aa Revert introduction of more_itertools
This was merged on master but really should be on features: we should not
add new dependencies in bug-fix releases

This reverts commits:

* cfaf3600c1
* 14a9b1ec83
2018-03-04 10:56:24 -03:00
Bruno Oliveira
e980fbbe39 Merge pull request #3277 from maiksensi/bug/3241-check-if-dir-exists
Use `isdir` instead of `exists`
2018-03-04 10:49:25 -03:00
Bruno Oliveira
07e768ab68 Merge pull request #3275 from maiksensi/docs/3209-add-logging-plugin
Add logging plugin to plugins list
2018-03-03 23:17:05 -03:00
Bruno Oliveira
9a2e0c061d Update 3241.bugfix.rst 2018-03-03 23:14:57 -03:00
Maik Figura
056d9e8dc2 Use isdir instead of exists 2018-03-04 00:55:04 +01:00
Maik Figura
46f5d7a1bb Add filetype to changelog fragment 2018-03-04 00:52:27 +01:00
Maik Figura
30453057e8 Add logging plugin to plugins list 2018-03-03 23:50:13 +01:00
Bruno Oliveira
08831396a5 Merge pull request #3218 from feuillemorte/3034-new-tests-first
#3034 Added new option "--new-first"
2018-03-02 18:33:25 -03:00
Bruno Oliveira
1549d61379 Merge pull request #3274 from feuillemorte/3268-add-deprecation
Added warning when "[pytest]" section is used in a ".cfg" file passed with "-c"
2018-03-02 16:34:31 -03:00
feuillemorte
f501d0021c #3268 Fix warning variable 2018-03-02 11:28:30 +03:00
feuillemorte
a506052d12 #3268 Added changelog file 2018-03-02 10:54:31 +03:00
feuillemorte
99aab2c3f5 #3268 Added deprecation to custom configs 2018-03-02 10:52:38 +03:00
Ronny Pfannschmidt
3b757b1b8c Merge pull request #3265 from pytest-dev/feature/always-iterable-refactor
Consolidate behavior by using filterfalse and always_iterable
2018-03-01 08:51:44 +01:00
Bruno Oliveira
14a9b1ec83 Add CHANGELOG for #3265 2018-02-28 17:31:11 -03:00
Bruno Oliveira
ea854086e1 Rename 3236.trivial.rst to 3236.feature.rst 2018-02-27 22:46:12 -03:00
Bruno Oliveira
0a5a6c19be Merge pull request #3269 from nicoddemus/merge-master-into-features
Merge master into features
2018-02-27 22:42:08 -03:00
Bruno Oliveira
90638b661d Merge pull request #3270 from pytest-dev/blueyed-patch-1
Fix typo with test_summary_list_after_errors
2018-02-27 18:27:31 -03:00
Daniel Hahler
8239103aa9 Fix typo with test_summary_list_after_errors 2018-02-27 21:07:00 +01:00
Bruno Oliveira
a2a64546eb Merge remote-tracking branch 'upstream/master' into features 2018-02-27 16:43:45 -03:00
Bruno Oliveira
9fcbf57163 Merge pull request #3267 from feuillemorte/3260-fix-pytest-section
#3260 fix pytest section
2018-02-27 16:41:45 -03:00
Bruno Oliveira
dab96cbf27 Merge pull request #3255 from nicoddemus/post-summary
Show "short test summary info" after tracebacks and warnings
2018-02-27 16:36:13 -03:00
Bruno Oliveira
1fb2457018 Adjust CHANGELOG 2018-02-27 14:58:27 -03:00
feuillemorte
92219e576b #3260 Remove deprecation 2018-02-27 20:00:46 +03:00
feuillemorte
143ac5af99 #3260 Fix config.py for py27 2018-02-27 19:26:35 +03:00
feuillemorte
409b919fc0 #3260 Added test 2018-02-27 19:16:45 +03:00
feuillemorte
eadd15fe45 #3260 Added changelog file 2018-02-27 19:05:53 +03:00
feuillemorte
3f7223af44 #3260 Fix pytest section in setup ini file 2018-02-27 19:02:49 +03:00
Bruno Oliveira
a968c0fa05 Merge pull request #3264 from jeffreyrack/3236-skipif-using-platform
#3236 Use platform module in pytest.mark
2018-02-27 09:44:52 -03:00
Bruno Oliveira
5cb72b6188 Merge pull request #3257 from will133/continue-on-failure
Continue on doctest failure
2018-02-27 07:45:41 -03:00
Bruno Oliveira
20085542e2 Merge pull request #3263 from feuillemorte/3203-remove-statistic-indicator
#3203 Remove progress when no-capture
2018-02-27 07:33:45 -03:00
Bruno Oliveira
94050a8aaf Remove config paramter from pytest_terminal_summary as discussed during review 2018-02-27 07:26:25 -03:00
Bruno Oliveira
9479bda392 Adjust the CHANGELOG 2018-02-27 07:03:20 -03:00
Jason R. Coombs
cfaf3600c1 Consolidate behavior by using filterfalse and always_iterable 2018-02-27 03:38:56 -05:00
Jeffrey Rackauckas
f6ad25928e Fixing grammar. 2018-02-26 19:15:10 -08:00
Jeffrey Rackauckas
a6762f7328 Update test_skipping to test that platform can be used in xfail 2018-02-26 19:11:13 -08:00
Bruno Oliveira
4e405dd9f9 Show "short test summary info" after tracebacks and warnings 2018-02-26 21:12:33 -03:00
Bruno Oliveira
d196ab45d3 Change 1642 entry from "trivial" to "feature" 2018-02-26 19:56:53 -03:00
Bruno Oliveira
188df8100c Small adjustment to the CHANGELOG 2018-02-26 17:14:28 -03:00
Bruno Oliveira
e8f9a91056 Small adjustment to CHANGELOG entry 2018-02-26 17:10:59 -03:00
Bruno Oliveira
44fa5a77d4 Merge pull request #3262 from nicoddemus/fix-tmpdir-docs
Fix broken links in getting-started
2018-02-26 16:49:15 -03:00
feuillemorte
31476c69ab #3203 Fix tests 2018-02-26 17:39:32 +03:00
feuillemorte
6200920dc3 #3203 Added changelog file 2018-02-26 17:24:16 +03:00
feuillemorte
46c5d5355e #3203 Remove progress when no-capture 2018-02-26 17:19:58 +03:00
Bruno Oliveira
39024a7536 Fix broken links in getting-started
Fix #3256
2018-02-26 10:56:27 -03:00
Jeffrey Rackauckas
307cd6630f Add the ability to use platform in pytest.mark.skipif 2018-02-25 22:38:25 -08:00
Florian Bruhin
ae62ced080 Merge pull request #3259 from joshm91/fix_typo
Fix minor typo in fixture.rst
2018-02-24 21:37:40 +01:00
joshm91
6166151ee4 Fix minor typo in fixture.rst 2018-02-24 19:12:40 +00:00
Ronny Pfannschmidt
da3f4045e7 Merge pull request #3250 from RonnyPfannschmidt/mark-package
Mark package
2018-02-24 18:19:27 +01:00
William Lee
c21eb72924 Added documentation for the continue-on-failure flag 2018-02-23 22:42:22 -06:00
William Lee
f4cc45bb41 Turn on the continue on failure only when the flag is given 2018-02-23 22:31:11 -06:00
William Lee
7f2dd74ae9 Fixed test for the continue run 2018-02-23 21:20:14 -06:00
feuillemorte
c032d4c5d5 Merge branch 'features' of https://github.com/feuillemorte/pytest into 3034-new-tests-first 2018-02-23 23:55:28 +03:00
feuillemorte
e865f2a235 #3034 Fix comments 2018-02-23 22:49:17 +03:00
William Lee
8d90591b33 Merge remote-tracking branch 'upstream/features' into continue-on-failure 2018-02-22 21:01:31 -06:00
William Lee
14cd1e9d94 Added the feature in change log for #3149 2018-02-22 21:01:19 -06:00
William Lee
fbc45be83f Fixed #3149 where doctest does not continue to run when there is a failure 2018-02-22 21:00:54 -06:00
Bruno Oliveira
0a3c80e959 Merge pull request #3253 from thisch/nolooggingtest
Add pdb test with disabled logging plugin
2018-02-22 22:19:47 -03:00
Bruno Oliveira
bedceaacc4 Merge pull request #3249 from a-feld/request-fixture-reference-cycle
Fix PseudoFixtureDef reference cycle.
2018-02-22 19:23:56 -03:00
Bruno Oliveira
1127d519db Merge pull request #3254 from altendky/patch-1
Correct docs to config.pluginmanager.get_plugin()
2018-02-22 19:14:47 -03:00
Bruno Oliveira
9959164c9a Add CHANGELOG entry for #3250 2018-02-22 18:55:25 -03:00
Bruno Oliveira
60358b6db8 Fix linting 2018-02-22 18:49:20 -03:00
Kyle Altendorf
b5ac61657a Correct docs to config.pluginmanager.get_plugin()
`getplugin()` is deprecated in favor of `get_plugin()`.

dd97c94035/_pytest/config.py (L261)
2018-02-22 13:48:59 -05:00
Thomas Hisch
0f58fc881b Add pdb test with disabled logging plugin
Implement the test from #3210, which was not merged yet, because the PR was
abandoned in favor or #3234.
2018-02-22 19:26:46 +01:00
Ronny Pfannschmidt
2cd69cf632 sort out import misstake 2018-02-22 15:18:17 +01:00
Ronny Pfannschmidt
935dd3aaa5 simplify complexyity in mark plugin modifyitems 2018-02-22 15:11:55 +01:00
Ronny Pfannschmidt
c8d24739ed move current mark datastructures to own module 2018-02-22 14:43:01 +01:00
Ronny Pfannschmidt
be2e3a973e remove complexity from match_keywords 2018-02-22 14:30:32 +01:00
Ronny Pfannschmidt
cef0423b27 move the keyword/mark matching to the "legacy" module 2018-02-22 14:26:22 +01:00
Ronny Pfannschmidt
de2de00de9 update setup.py for the mark package 2018-02-22 14:19:31 +01:00
Ronny Pfannschmidt
25a3e9296a reduce the complexity of skipping terminal summary 2018-02-22 14:18:49 +01:00
Ronny Pfannschmidt
cf40c0743c move mark evaluator into mark package 2018-02-22 14:05:10 +01:00
Ronny Pfannschmidt
c31e1a3797 turn mark into a package 2018-02-22 13:36:33 +01:00
Allan Feldman
48548767fc Use a frozen attr class for PseudoFixtureDef. 2018-02-21 23:15:39 -08:00
Allan Feldman
7536e949b1 Add changelog entry. 2018-02-21 22:42:44 -08:00
Allan Feldman
287c003cfd Add myself to AUTHORS. 2018-02-21 22:42:44 -08:00
Allan Feldman
aa53e37fa2 Add a test to expose leaked PseudoFixtureDef types. 2018-02-21 22:42:44 -08:00
Ronny Pfannschmidt
54e63b7dd5 Merge pull request #2770 from HPENetworking/master
Make record_xml_property generic and compatible with xdist and markers.
2018-02-22 07:38:55 +01:00
Ronny Pfannschmidt
dd97c94035 Merge pull request #3240 from codetriage-readme-bot/codetriage-badge
Add CodeTriage badge to pytest-dev/pytest
2018-02-22 07:38:31 +01:00
Ronny Pfannschmidt
264e455410 Merge pull request #3248 from durocher/master
Fix approx default tolerances for Decimal
2018-02-22 07:35:56 +01:00
Allan Feldman
75f11f0b65 Fix reference cycle caused by PseudoFixtureDef.
Python types have reference cycles to themselves when they are created. This is
partially caused by descriptors which get / set values from the __dict__
attribute for getattr / setattr on classes.

This is not normally an issue since types tend to remain referenced for the
lifetime of the Python process (and thus never become garbage).

However, in the case of PseudoFixtureDef, the class is generated in
_get_active_fixturedef and later discarded when pytest_fixture_setup returns.
As a result, the generated PseudoFixtureDef type becomes garbage.

This is not really a performance issue but it can lead to some problems when
making tests and assertions about garbage when using pytest.

This garbage creation problem can be rectified by returning a namedtuple
instance which is functionally the same. In the modified code, the namedtuple
is allocated / deallocated using reference counting rather than having to use
the garbage collector.
2018-02-21 21:40:54 -08:00
Bruno Oliveira
45d0a21294 Fix README because of code triage badge 2018-02-21 20:42:09 -03:00
Bruno Oliveira
147b43f832 Small changelog tweak 2018-02-21 20:31:33 -03:00
Bruno Oliveira
7336dbb979 Merge pull request #3246 from RonnyPfannschmidt/remove-addcall-in-tests
remove addcall in the terminal tests
2018-02-21 19:42:53 -03:00
mike
6e14585ca2 Fix approx default tolerances for Decimal 2018-02-21 23:08:23 +01:00
Bruno Oliveira
567b1ea7a1 Move user_properties to the end of parameter list for backward compatibility 2018-02-21 17:56:49 -03:00
Bruno Oliveira
d838193d2d Add note about deprecating record_xml_property
Also make record_xml_property return record_property directly
2018-02-21 17:45:52 -03:00
Bruno Oliveira
d844ad18c2 Fix formatting of CHANGELOG entry 2018-02-21 15:40:25 -03:00
Ronny Pfannschmidt
3d4d0a2614 remove addcall in the terminal tests 2018-02-21 18:56:20 +01:00
Bruno Oliveira
7a62619a75 Merge remote-tracking branch 'upstream/master' into features 2018-02-21 10:39:55 -03:00
Bruno Oliveira
dae74b674e Merge pull request #3242 from nicoddemus/delete-changelog-3.4.1
Delete changelog entries for 3.4.1 release
2018-02-21 10:38:53 -03:00
Bruno Oliveira
2a99e5dd2a Delete changelog entries for 3.4.1 release
Unfortunately due to hawkowl/towncrier#99 those news fragments were
not deleted at the time of CHANGELOG generation.
2018-02-20 23:45:21 -03:00
Carlos Jenkins
8b49ddfa58 Renamed the fixture record_xml_property to record_property and adapted logic so that the properties are passed to the TestReport object and thus allow compatibility with pytest-xdist. 2018-02-20 15:46:26 -06:00
Bruno Oliveira
9361d48b61 Merge pull request #3239 from nicoddemus/release-3.4.1
Release 3.4.1
2018-02-20 18:08:47 -03:00
codetriage-readme-bot
ebddac6a5c Add CodeTriage badge to pytest-dev/pytest
Adds a badge showing the number of people helping this repo on CodeTriage.

[![Open Source Helpers](https://www.codetriage.com/pytest-dev/pytest/badges/users.svg)](https://www.codetriage.com/pytest-dev/pytest)

## What is CodeTriage?

CodeTriage is an Open Source app that is designed to make contributing to Open Source projects easier. It works by sending subscribers a few open issues in their inbox. If subscribers get busy, there is an algorithm that backs off issue load so they do not get overwhelmed

[Read more about the CodeTriage project](https://www.codetriage.com/what).

## Why am I getting this PR?

Your project was picked by the human, @schneems. They selected it from the projects submitted to https://www.codetriage.com and hand edited the PR. How did your project get added to [CodeTriage](https://www.codetriage.com/what)? Roughly 11 months ago, [cacoze](https://github.com/cacoze) added this project to CodeTriage in order to start contributing. Since then, 32 people have subscribed to help this repo.

## What does adding a badge accomplish?

Adding a badge invites people to help contribute to your project. It also lets developers know that others are invested in the longterm success and maintainability of the project.

You can see an example of a CodeTriage badge on these popular OSS READMEs:

- [![](https://www.codetriage.com/rails/rails/badges/users.svg)](https://www.codetriage.com/rails/rails) https://github.com/rails/rails
- [![](https://www.codetriage.com/crystal-lang/crystal/badges/users.svg)](https://www.codetriage.com/crystal-lang/crystal) https://github.com/crystal-lang/crystal

## Have a question or comment?

While I am a bot, this PR was manually reviewed and monitored by a human - @schneems. My job is writing commit messages and handling PR logistics.

If you have any questions, you can reply back to this PR and they will be answered by @schneems. If you do not want a badge right now, no worries, close the PR, you will not hear from me again.

Thanks for making your project Open Source! Any feedback is greatly appreciated.
2018-02-20 10:51:51 -06:00
Bruno Oliveira
b319375592 Preparing release version 3.4.1 2018-02-20 01:43:59 +00:00
Bruno Oliveira
f8fdf0ae91 Add --yes to towncrier to automatically remove files 2018-02-20 01:43:20 +00:00
Bruno Oliveira
053fc118b7 Merge pull request #3199 from nicoddemus/publish-tag-3060
Implement deploying to PyPI by pushing a tag
2018-02-19 22:34:35 -03:00
Bruno Oliveira
97bb6abcfa Merge pull request #3234 from thisch/showcapture_log_support
Add captured-log support to --show-capture
2018-02-19 20:46:50 -03:00
Thomas Hisch
acda6c46fb Partially revert "Remove --no-print-logs option"
We'll deprecate --no-print-logs beginning with pytest-4.0.

This reverts commit ac7eb63a6b.
2018-02-19 20:34:11 +01:00
Thomas Hisch
ac7eb63a6b Remove --no-print-logs option
This option is superseded by the --show-capture option. With --no-print-logs
it was possible to only disable the reporting of captured logs, which is no
longer possible with --show-capture. If --show-capture=no is used, no
captured content (stdout, stderr and logs) is reported for failed tests.
2018-02-18 20:48:07 +01:00
Thomas Hisch
51ece00923 Add captured-log support to --show-capture
Fixes: #3233
2018-02-18 20:18:37 +01:00
Bruno Oliveira
b486e1294b Merge pull request #3222 from The-Compiler/match-msg
Remove "matching '...'" part from the pytest.raises message
2018-02-17 21:18:05 -02:00
Bruno Oliveira
3bc8b50a0d Merge pull request #3228 from thisch/update_attr
Update minimal attrs library to v17.4.0
2018-02-17 20:19:07 -02:00
Bruno Oliveira
81fa547fa8 Add CHANGELOG entry about changed attrs req 2018-02-17 20:18:32 -02:00
Bruno Oliveira
9d879bee36 Merge pull request #3215 from pytest-dev/bugfix/985/disable-output-capturing-in-doctest
Disable output capturing in doctest
2018-02-17 18:39:32 -02:00
Brian Maissy
069f32a8c4 print captured logs before entering pdb 2018-02-17 20:32:49 +02:00
Jason R. Coombs
4131d3f300 Probably it's best to write the err stream to stderr. 2018-02-17 12:13:33 -05:00
Jason R. Coombs
254e357076 Correct the broken indentation. 2018-02-17 12:10:29 -05:00
Bruno Oliveira
f263932883 Merge pull request #3232 from nicoddemus/merge-upstream
Merge master into features
2018-02-17 12:59:55 -02:00
Bruno Oliveira
0f6879bf5e Small update to changelog/README.rst 2018-02-17 10:20:41 -02:00
Bruno Oliveira
bfe2cbe875 Implement publishing to PyPI by pushing a tag
Fix #3060
2018-02-17 10:13:25 -02:00
Bruno Oliveira
69d608aec3 Merge remote-tracking branch 'upstream/master' into merge-upstream 2018-02-17 09:38:06 -02:00
Bruno Oliveira
dfbaa20240 Bring test_live_logs_unknown_sections directly due to merge conflicts 2018-02-17 09:36:46 -02:00
Bruno Oliveira
fa8354e872 Merge pull request #3213 from thisch/collection_deselection
Show deselection count before tests are executed
2018-02-17 09:03:26 -02:00
Bruno Oliveira
00d3001138 Merge pull request #3188 from s0undt3ch/issues/3184
Don't traceback on unkown sections.
2018-02-17 09:02:44 -02:00
Bruno Oliveira
05faa69c37 Merge pull request #3194 from s0undt3ch/feature/logstart-logfinish
Fix issue where a new line was always written for the live log finish section
2018-02-16 21:19:38 -02:00
Bruno Oliveira
b1abe5db23 Merge pull request #3201 from uSpike/deselect_cli
Add "--deselect" command line option
2018-02-16 20:50:22 -02:00
Jordan Speicher
774c539f1a Add --deselect command line option
Fixes #3198
2018-02-16 08:17:37 -06:00
Bruno Oliveira
df2f019997 Slight rewording in the CHANGELOG 2018-02-15 19:45:05 -02:00
Bruno Oliveira
6bc45d158d Merge pull request #3186 from brianmaissy/bugfix/print_captured_stdout_before_entering_pdb
Added printing of captured stdout before entering pdb
2018-02-15 19:38:23 -02:00
Bruno Oliveira
371eb8c6af Merge pull request #3206 from mbachry/fix-unittest-mock
Fix mock patchings detection when both mock and unittest.mock are present
2018-02-15 19:35:44 -02:00
Thomas Hisch
82cdc487ce Fix raised warning when attrs 17.4.0 is used
Related: #3223
2018-02-15 21:09:44 +01:00
Jason R. Coombs
435b8ddc7c Rename method for additional clarity. 2018-02-15 09:17:33 -05:00
Florian Bruhin
3cbf0c8ec0 Raise unexpected exceptions with pytest.raises() using match= 2018-02-15 12:11:56 +01:00
Florian Bruhin
9849022eb2 Remove "matching '...'" part from the pytest.raises message
When a test with pytest.raises(ValueError, match='foo') doesn't raise, the
following error is printed:

    Failed: DID NOT RAISE <class 'ValueError'> matching 'foo'

This error message is confusing as it implies a ValueError was raised, but the
message wasn't matching 'foo'.

I first considered rewording it somehow to preserve the match pattern in it, but
I don't think that's worthwhile as the pattern should usually be apparent from
the stacktrace anyways (hard-coded, as parametrization, or with --showlocals for
more sophisticated cases).
2018-02-14 19:20:00 +01:00
Jason R. Coombs
18c84a1904 Restrict fix to macOS only. Ref #3215. 2018-02-13 17:51:20 -05:00
Thomas Hisch
6496131b79 Show deselection count before tests are exectued
Fixes #1527
2018-02-13 22:27:02 +01:00
feuillemorte
dff0500114 #3034 Added new option "--new-first" 2018-02-13 22:49:28 +03:00
Jason R. Coombs
247cdb835a Remove xfail; tests now pass on macOS. Ref #985. 2018-02-13 12:41:19 -05:00
Jason R. Coombs
ce1872e7e8 Merge branch 'master' into bugfix/985/disable-output-capturing-in-doctest 2018-02-13 12:33:07 -05:00
Jason R. Coombs
d845af7b24 Add changelog. Ref #985. 2018-02-13 12:32:00 -05:00
Jason R. Coombs
391553887b Disable output capturing in doctest to avoid losing reference to stdout. Fixes #985. 2018-02-13 12:08:39 -05:00
Brian Maissy
7656fc8320 Added printing of captured stdout and stderr before entering pdb 2018-02-12 23:17:51 +02:00
Marcin Bachry
b6166dccb4 Fix mock patchings detection when both mock and unittest.mock are present 2018-02-12 20:29:37 +01:00
Bruno Oliveira
e7bcc854d9 Merge pull request #3202 from rouge8/pytest-raises-sphinx-parameters
Add Sphinx parameter docs for `match` and `message` args to `pytest.raises()`
2018-02-10 02:32:56 -02:00
Bruno Oliveira
ffee213c85 Update and rename 3202.trivial.rst to 3202.doc.rst 2018-02-09 22:51:15 -02:00
Andy Freeland
e5b527d0e3 Add Sphinx parameter docs for match and message args to pytest.raises() 2018-02-09 16:28:17 -08:00
Bruno Oliveira
063e2da967 Merge pull request #3176 from feuillemorte/1478_no_stdout_option
#1478 Added --no-stdout option
2018-02-09 18:36:36 -02:00
feuillemorte
da5882c2d5 #1478 Add doc and remove print 2018-02-09 21:36:48 +03:00
Bruno Oliveira
c04e248de5 Rename 3161.bugfix to 3161.bugfix.rst 2018-02-09 14:06:24 -02:00
Pedro Algarvio
d776e5610e Fix issue where a new line was always written for the live log finish section 2018-02-09 11:17:01 +00:00
Bruno Oliveira
bba258aa5e Merge pull request #3190 from s0undt3ch/feature/logs-stream
Expose `log_cli` as a CLI parser option.
2018-02-08 17:07:48 -02:00
Florian Bruhin
3685c1bc01 Merge pull request #3192 from nicoddemus/pr-template
Add changelog/README.rst and streamline our PR template text
2018-02-08 19:20:54 +01:00
Bruno Oliveira
a4cbd03535 Fix linting 2018-02-08 13:22:32 -02:00
feuillemorte
71367881ed #1478 Added --show-capture=both option (fix comments) 2018-02-08 16:21:22 +03:00
Bruno Oliveira
ad7d63df97 Rename _stream_logs_enabled to _log_cli_enabled and remove _stream_logs 2018-02-08 09:48:51 -02:00
Bruno Oliveira
eea169e515 Code review suggestions 2018-02-08 08:03:14 -02:00
Pedro Algarvio
0b71255dda Expose log_cli as a CLI parser option. 2018-02-08 09:42:53 +00:00
Bruno Oliveira
ce0a9aadec Merge pull request #3127 from feuillemorte/1642-add-rootdir-option
#1642 add rootdir option
2018-02-07 18:18:59 -02:00
Bruno Oliveira
16c52f05f1 Merge pull request #3177 from nicoddemus/mention-force-result
Mention outcome.force_result() and add link to pluggy's docs
2018-02-07 18:16:16 -02:00
Bruno Oliveira
f72182977d Merge pull request #3183 from cheezman34/master
Fix ordering of tests to minimize fixture creating
2018-02-07 18:15:41 -02:00
Bruno Oliveira
ed12cf3fb3 Merge pull request #3189 from s0undt3ch/feature/logstart-logfinish
Additionally handle logstart and logfinish hooks
2018-02-07 18:09:45 -02:00
Bruno Oliveira
40d0ade2d9 Add changelog/README.rst and streamline our PR template text
This streamlines the PR template text and adds a more in-depth explanation
about how the changelog entries work because this topic is a common source of
confusion:

- How to name the files.
- Which formatting to use (people in general assume it is Markdown).
- Recommend adding `.rst` extension to changelog files to help with the
  above (`towncrier` doesn't care).

This was heavily inspired by the excellent python-trio/trio docs.
2018-02-06 22:20:39 -02:00
Bruno Oliveira
29a074eae3 Merge pull request #3156 from thisch/junit_xml_log_fix
Add captured log msgs to junit xml file
2018-02-06 21:42:11 -02:00
feuillemorte
1a650a9eb9 #1478 Added --show-capture option 2018-02-06 23:38:51 +03:00
Bruno Oliveira
ea06c1345f Update changelog wording slightly 2018-02-06 15:45:37 +00:00
Pedro Algarvio
00d8787bb8 Add name to AUTHORS 2018-02-06 15:45:37 +00:00
Pedro Algarvio
67558e0e22 Additionally handle logstart and logfinish hooks 2018-02-06 15:45:36 +00:00
Bruno Oliveira
7152707280 Update changelog entry to "trivial" as it is a temporary workaround
I think this is a more appropriate category given that the underlying problem still exists
2018-02-06 08:54:44 -02:00
Bruno Oliveira
2d0c1e941e Fix versionadded tag in caplog function 2018-02-05 20:22:21 -02:00
Bruno Oliveira
42c1f85257 Update CHANGELOG 2018-02-05 20:07:42 -02:00
Pedro Algarvio
0d15a46863 Don't traceback on unkown sections. 2018-02-05 19:09:38 +00:00
Ronny Pfannschmidt
570edb466b Merge pull request #3185 from nicoddemus/remove-ignore-py37
Support py37 officially
2018-02-05 10:04:34 +01:00
Ronny Pfannschmidt
7c80c81433 Merge pull request #3152 from brianmaissy/silently_fail_to_import_during_postmortem
If we fail to import doctest.UnexpectedException during postmortem, fail quietly
2018-02-05 10:04:00 +01:00
Bruno Oliveira
9202ba91cf Merge pull request #3187 from brianmaissy/bugfix/skip_failing_pdb_doctest_test_on_mac
skip failing pdb/doctest test on mac
2018-02-03 23:35:39 -02:00
Bruno Oliveira
ea8997a108 Update wording in CHANGELOG 2018-02-03 20:21:06 -02:00
Brian Maissy
867344d0d7 move import to top 2018-02-04 00:03:17 +02:00
Brian Maissy
e64feaba7a xfail is better than skip 2018-02-03 23:28:37 +02:00
Brian Maissy
74633815aa skip failing pdb/doctest test on mac 2018-02-03 23:24:11 +02:00
Thomas Hisch
c0ef4a4d35 Add captured log msgs to junit xml file
For each test this adds the captured log msgs to a system-* tag in the junit
xml output file. The destination of the system-* tag is specified by
junit_logging ini option.
2018-02-03 09:39:29 +01:00
Bruno Oliveira
e289c60c3a Support py37 officially
Python 3.7.0b1 has been released:

	https://www.python.org/downloads/release/python-370b1/

Fix #3168
2018-02-02 17:15:16 -02:00
Bruno Oliveira
8c81722a0c Merge pull request #3167 from nicoddemus/fix-py37
Fix test for py37
2018-02-02 17:13:00 -02:00
Bruno Oliveira
3425edd2a5 Reword changelog a bit 2018-02-01 19:54:51 -02:00
Bruno Oliveira
37d836d754 Reword docs slightly 2018-02-01 19:34:15 -02:00
Aaron
4458e65fe7 Fix ordering of tests to minimize fixture creating 2018-02-01 13:07:45 -08:00
feuillemorte
3eb6cad222 #1642 Fix comments 2018-02-01 11:20:37 +03:00
feuillemorte
936651702b #1642 Fix tests 2018-02-01 00:27:48 +03:00
feuillemorte
9f1772e679 #1642 Resolve conflicts 2018-02-01 00:18:28 +03:00
feuillemorte
741b571f3b #1642 fix tests and config.py 2018-02-01 00:03:24 +03:00
Bruno Oliveira
653abad27b Mention outcome.force_result() and add link to pluggy's docs
Related to #3169
2018-01-31 18:18:15 -02:00
feuillemorte
949a620d3a #1478 Added --no-stdout option 2018-01-31 22:36:28 +03:00
Bruno Oliveira
89a55d85a9 Merge pull request #3166 from alanbato/typo_for_parametrize
Switch `for_parameterize` to `for_parametrize`
2018-01-30 21:41:14 -02:00
Bruno Oliveira
ef7df8f167 Small update to CHANGELOG 2018-01-30 21:40:14 -02:00
Bruno Oliveira
90a8faabba Fix test for py37
In previous Python versions, the list of warnigns appears like:

	... list of emitted warnings is: [UserWarning('user',)].

In Python 3.7 apparently the string representation has been improved to:

	... list of emitted warnings is: [UserWarning('user')].

Fix #3011
2018-01-30 20:33:07 -02:00
Alan Velasco
e12a588c39 Merge branch 'master' into typo_for_parametrize 2018-01-30 16:22:54 -06:00
Alan Velasco
547070e2d8 Switch for_parameterize to for_parametrize 2018-01-30 16:20:43 -06:00
Bruno Oliveira
f2fb841b29 Merge remote-tracking branch 'upstream/master' into features 2018-01-30 20:12:14 -02:00
Bruno Oliveira
3256fa9ee9 Add devpi-client to tasks requirements 2018-01-30 20:10:40 -02:00
Bruno Oliveira
dc9a9ec4c2 Merge pull request #3162 from nicoddemus/release-3.4.0
Release 3.4.0
2018-01-30 20:08:36 -02:00
Bruno Oliveira
527845ef29 Changelog adjustments suggested during review 2018-01-30 19:49:08 +00:00
Bruno Oliveira
489e638b4e Preparing release version 3.4.0 2018-01-30 19:47:56 +00:00
Bruno Oliveira
13ee1cffed Suggest to update all dependencies when preparing releases 2018-01-30 19:44:06 +00:00
Bruno Oliveira
4c148bd0ef Fix imports in failure_demo.py 2018-01-30 19:44:06 +00:00
Bruno Oliveira
71a7b3c062 Merge pull request #3142 from thisch/only_colorize_levelname
Colorize live-log levelnames
2018-01-30 17:41:18 -02:00
Thomas Hisch
ebab1b6c69 live-logging: Colorize levelname 2018-01-30 18:21:12 +01:00
Bruno Oliveira
49773b573f Merge pull request #3132 from raphaelcastaneda/feature/add-record-xml-attribute
implement #3130 - add record_xml_attribute fixture
2018-01-29 15:42:55 -02:00
Ronny Pfannschmidt
32979def7d Merge pull request #3158 from nicoddemus/cache-getvalue
Replace deprecated option.getvalue by option.getoption in cache plugin
2018-01-29 13:57:45 +01:00
Bruno Oliveira
ab00c3e911 Add .pytest_cache directory to gitignore 2018-01-29 08:44:11 -02:00
Bruno Oliveira
6e4efccc38 Rename test_cache to test_cacheprovider for consistency with cacheprovider 2018-01-29 08:40:29 -02:00
Bruno Oliveira
269eeec702 Replace deprecated option.getvalue by option.getoption in cacheprovider 2018-01-29 08:40:29 -02:00
Bruno Oliveira
0e1be01b7a Merge pull request #3108 from cheezman34/features
Optimize reorder_items in fixtures.py
2018-01-27 23:21:09 -02:00
Bruno Oliveira
aff463a3c4 Merge remote-tracking branch 'upstream/master' into features 2018-01-27 12:28:43 -02:00
Bruno Oliveira
b3247c1d03 Merge pull request #3044 from RonnyPfannschmidt/parameterset-empty-enable-xfail
empty parameterset - enable opt to xfail
2018-01-27 12:23:49 -02:00
Bruno Oliveira
169635e889 Move example of empty_parameter_set_mark closer to the options 2018-01-27 11:02:32 -02:00
Bruno Oliveira
cd0b2ace67 Merge pull request #3150 from alanbato/pytest_cache
Change cache directory structure to include `pytest`
2018-01-27 10:08:56 -02:00
Ronny Pfannschmidt
17a1ed5edf use a constant to sort out repeated use of the EMPTY_PARAMETERSET_OPTION 2018-01-26 12:12:26 +01:00
Ronny Pfannschmidt
a54cd4c2fd correct testing and usage of the empty_parameter_set_mark config option 2018-01-26 12:05:52 +01:00
Ronny Pfannschmidt
77de45cce3 enhance docs for empty_parameter_set_mark according to review comments 2018-01-26 12:01:27 +01:00
Ronny Pfannschmidt
d550c33cd0 s/empty_parameterset/empty_parameter_set_mark 2018-01-26 11:56:24 +01:00
Bruno Oliveira
a58099022a Merge pull request #3124 from nicoddemus/logging-3013
Changes in the logging plugin for 3.4
2018-01-26 08:52:43 -02:00
Ronny Pfannschmidt
3bc7ced97a Merge pull request #3153 from brianmaissy/document_not_to_call_main_multiple_times
Added note that calling pytest.main multiple times from the same process is not recommended because of import caching
2018-01-26 11:47:16 +01:00
Ronny Pfannschmidt
8979b2a9d7 document empty_parameterset in customize.rst 2018-01-26 11:26:48 +01:00
Ronny Pfannschmidt
d4c11e58aa exted empty parameterset check with reason test 2018-01-26 11:18:50 +01:00
Ronny Pfannschmidt
7f83605c81 fix empty parameterset tests by mocking a config object 2018-01-26 11:10:00 +01:00
Ronny Pfannschmidt
37b41de779 fix #2527 - introduce a option to pic the empty parameterset action 2018-01-26 11:10:00 +01:00
Ronny Pfannschmidt
bf2c10c810 parameterset: refactor marking empty parametersets 2018-01-26 11:10:00 +01:00
Brian Maissy
4285325cb8 Added note that calling pytest.main multiple times from the same process is not recommended because of import caching 2018-01-25 23:07:34 +02:00
Brian Maissy
2a1b1107c5 If we fail to import doctest.UnexpectedException during postmortem, fail quietly and continue 2018-01-25 22:28:27 +02:00
Bruno Oliveira
cbbd606b6c Reword changelog 2018-01-25 17:21:54 -02:00
feuillemorte
ace772c743 Merge branch '1642-add-rootdir-option' of github.com:feuillemorte/pytest into 1642-add-rootdir-option 2018-01-25 19:46:43 +03:00
feuillemorte
3a004a4507 added rootdir description to customize.rst 2018-01-25 19:46:22 +03:00
Alan Velasco
a24ca9872f Change cache directory name to include pytest 2018-01-25 07:50:08 -06:00
feuillemorte
503e00f7ff #1642 Remove adding rootdir to sys path 2018-01-25 15:57:29 +03:00
feuillemorte
0cfa975930 #1642 Fix tests 2018-01-25 15:57:04 +03:00
Bruno Oliveira
b8be339632 Merge pull request #3148 from nicoddemus/deprecate-old-style-classes-2147
All classes now subclass object for better py3 compatibility
2018-01-25 10:41:28 -02:00
Bruno Oliveira
2aad8c0fce Merge pull request #3147 from nicoddemus/issue-3103
'-o' option no longer swallows all other non-options after it
2018-01-25 10:41:18 -02:00
Bruno Oliveira
15cbd61159 Change caplog.get_handler(when) to caplog.get_records(when)
While updating the docs I noticed that caplog.get_handler() exposes
the underlying Handler object, which I think it is a bit too much
detail at this stage. Update to return the records directly instead.
2018-01-24 19:08:49 -02:00
Bruno Oliveira
2f955e0c99 Update documentation: rewording and move things for better reading flow 2018-01-24 18:42:59 -02:00
Bruno Oliveira
af37778b0d All classes now subclass object for better py3 compatibility
Fix #2147
2018-01-24 18:23:42 -02:00
Bruno Oliveira
3f5e9ea71e Fix -o behavior to no longer swallow all remaining options
The current behavior was too error-prone because a "-o" option would
swallow all the following non-option parameters:

  pytest -o foo=bar path/to/test.py

path/to/test.py would be captured by the -o option, and would fail
because "path/to/test.py" is not in the format "key=value".
2018-01-23 21:19:16 -02:00
Bruno Oliveira
443275f025 Reword changelog a bit 2018-01-23 21:19:04 -02:00
Bruno Oliveira
8426c57a9e Ensure changes in the message in the future do not make the test pass by accident 2018-01-23 21:18:59 -02:00
Aron Coyle
30ca9f9d38 Add endline 2018-01-23 21:18:51 -02:00
Aron Coyle
46d87deb5d Add changelog update authors 2018-01-23 21:18:45 -02:00
Aron Coyle
203508d9f3 cleanup test cases 2018-01-23 21:18:37 -02:00
Andrew Toolan
2c7f94fdb9 Added basic fix and test 2018-01-23 21:18:10 -02:00
Bruno Oliveira
ff90c9e237 Merge pull request #3116 from brianmaissy/pexpect_test_hangs_on_mac
use flush in order to avoid hanging on mac
2018-01-23 19:32:19 -02:00
Bruno Oliveira
b4e8861aa5 Fix typos 2018-01-23 19:02:32 -02:00
Bruno Oliveira
baa189f5a3 Merge pull request #3131 from soyrice/master
Improve readability of Getting Started guide
2018-01-23 17:48:11 -02:00
Bruno Oliveira
a7066ba837 Update formatting in the CHANGELOG 2018-01-23 17:31:07 -02:00
Bruno Oliveira
113bfb6be8 Report 'call' phase as 'live log call'
As commented in review, this makes it consistent with the headers shown
by stdout/stderr capturing ("Captured log call")
2018-01-22 21:43:35 -02:00
Bruno Oliveira
9f4688e549 Remove unnecessary -s from test_log_cli_enabled_disabled 2018-01-22 21:26:14 -02:00
Bruno Oliveira
3a9d0b26d5 Use pytest_runtest_logstart to signal the start of a new test
This also simplifies the code a bit because we don't need to keep
a set of ids anymore
2018-01-22 21:20:48 -02:00
Raphael Castaneda
a5e60b6a2d implement #3130 - adding record_xml_attribute fixture
update incorrect expected attribute value in test_record_attribute

attr names must be strings

Update CHANGELOG formatting

update usage documentation

Fix versionadded for record_xml_attribute

Indent the xml schema properly inside the warning box in the docs
2018-01-22 15:14:53 -08:00
Bruno Oliveira
0df42b4426 Show a header for each testing phase during live logging
As suggested during review
2018-01-22 21:00:52 -02:00
Bruno Oliveira
0d96a5bf90 Merge pull request #3135 from brianmaissy/doc/clarify_pytest_fixture_setup_with_fixture_that_returns_None
Clarify a possible confusion when using pytest_fixture_setup with fixture functions that return None
2018-01-22 18:02:33 -02:00
Bruno Oliveira
060f68bd90 Merge pull request #3137 from brianmaissy/doc/note_that_certain_hooks_cant_be_used_with_hookwrapper
Document hooks (defined with historic=True) which cannot be used with hookwrapper=True
2018-01-22 18:00:35 -02:00
Bruno Oliveira
e5739a3115 Merge pull request #3136 from brianmaissy/doc/document_warning_filtering_behavior
Clarify that warning capturing doesn't change the warning filter by d…
2018-01-22 17:45:36 -02:00
Bruno Oliveira
8a8797df80 Small changelog formatting 2018-01-21 23:21:21 -02:00
Brian Maissy
8994603d46 Document hooks (defined with historic=True) which cannot be used with hookwrapper=True 2018-01-21 23:17:16 +02:00
Brian Maissy
5c0b340a4b Clarify that warning capturing doesn't change the warning filter by default 2018-01-21 22:43:00 +02:00
Brian Maissy
196dcc37a8 Clarify a possible confusion when using pytest_fixture_setup with fixture functions that return None 2018-01-21 21:50:26 +02:00
feuillemorte
83034bbd48 #1642 Fix rootdir option 2018-01-20 22:30:01 +03:00
Cyrus Maden
0ab57c4139 Typo fix: "handeling" --> "handling" 2018-01-20 11:12:59 -08:00
Bruno Oliveira
29a7b5e064 Initialize log_cli_handler to None during LoggingPlugin init
Some of testdir's functionality bypasses pytest_runtestloop so this
attribute needs to be set early
2018-01-20 14:19:45 -02:00
Bruno Oliveira
27ae270159 Mention in docs that log messages of level WARNING or above are captured 2018-01-20 12:08:51 -02:00
Bruno Oliveira
2e40a8b3ca Fix test_caplog_captures_for_all_stages by setting log level 2018-01-20 12:04:28 -02:00
Bruno Oliveira
18e053546c Use six.StringIO and __name__ in test_live_logging_suspends_capture 2018-01-20 12:02:55 -02:00
Bruno Oliveira
9dbcac9af3 Suspend stdout/stderr capturing when emitting live logging messages 2018-01-20 12:02:55 -02:00
Bruno Oliveira
4a436572a8 Simplify test assertions a bit 2018-01-20 12:02:55 -02:00
Bruno Oliveira
97a4967b03 Improve code formatting 2018-01-20 12:02:55 -02:00
Bruno Oliveira
8f6a5928f7 Add newline before log messages and enable -v output when log_cli is enabled 2018-01-20 12:02:55 -02:00
Bruno Oliveira
5d89a93977 Small improvements to tests suggested during review 2018-01-20 12:02:54 -02:00
Bruno Oliveira
c53b72fd7b Add CHANGELOG for 3013 2018-01-20 12:02:54 -02:00
Bruno Oliveira
6bb739516f Update logging docs with the new changes in 3.4
Ref: #3013
2018-01-20 12:02:54 -02:00
Bruno Oliveira
8d735f3e1d Live log option now writes to the terminal reporter
Ref: #3013
2018-01-20 12:01:42 -02:00
Bruno Oliveira
aca1b06747 Undo log level set by caplog.set_level at the end of the test
Otherwise this leaks the log level information to other tests

Ref: #3013
2018-01-20 12:01:42 -02:00
Bruno Oliveira
8dcd2718aa No longer change the level of any logger unless requested explicitly
Ref: #3013
2018-01-20 12:01:42 -02:00
Bruno Oliveira
5ad1313b8a log_cli must now be enabled explicitly
Ref: #3013
2018-01-20 12:00:46 -02:00
Bruno Oliveira
3b3d237f07 Merge pull request #3117 from boxed/access_logs_in_teardown
Access captures logs in teardown
2018-01-20 11:21:17 -02:00
Bruno Oliveira
c4c968fe69 Reword CHANGELOG after introduction of caplog.get_handler() 2018-01-20 11:14:09 -02:00
Cyrus Maden
0b6df94b12 Delete .DS_Store 2018-01-19 18:45:02 -08:00
Cyrus Maden
c3d420bf75 Delete .DS_Store 2018-01-19 18:44:46 -08:00
Cyrus Maden
ebb4c47155 Delete .DS_Store 2018-01-19 18:44:33 -08:00
Anders Hovmöller
7ea5a22657 Access captures logs in teardown 2018-01-19 12:42:35 +01:00
Cyrus Maden
cd76366d87 Rename pr.doc to 3131.doc 2018-01-18 15:59:37 -08:00
Cyrus Maden
931e8830ba Update changelog
Not issue ID. Will update with pr ID after submitting pr
2018-01-18 15:54:31 -08:00
Bruno Oliveira
621374679b Merge pull request #3129 from kimberlythegeek/typo_in_logging_doc
Typo in logging doc
2018-01-18 16:40:09 -02:00
Bruno Oliveira
8be1136d03 Small changelog formatting 2018-01-18 16:40:00 -02:00
Kimberly
e0b63e34fa fixed typo in logging doc and added fix to changelog 2018-01-18 10:40:18 -07:00
feuillemorte
a7c39c894b #1642 fix flake8 2018-01-17 23:48:04 +03:00
feuillemorte
4a18d76160 #1642 remove print 2018-01-17 23:14:40 +03:00
feuillemorte
86f01967e1 #1642 Added changelog entry 2018-01-17 23:05:22 +03:00
feuillemorte
d784155fd2 #1642 Add rootdir option 2018-01-17 23:02:31 +03:00
Florian Bruhin
1fd67c9000 Merge pull request #3121 from feuillemorte/2953-keyword-expressions-error
#2953 show a simple and easy error when keyword expressions trigger a syntax error
2018-01-17 19:00:22 +01:00
Bruno Oliveira
e3406e0818 Show usage errors in red 2018-01-16 19:35:32 -02:00
feuillemorte
dc79116de3 Merge branch '2953-keyword-expressions-error' of github.com:feuillemorte/pytest into 2953-keyword-expressions-error 2018-01-16 23:36:40 +03:00
feuillemorte
8433e2ba04 #2953 fix comments: fix exception type 2018-01-16 23:35:57 +03:00
Bruno Oliveira
86e1b44230 Improve changelog formatting 2018-01-16 18:10:15 -02:00
feuillemorte
5d3f7d7142 Merge branch '2953-keyword-expressions-error' of github.com:feuillemorte/pytest into 2953-keyword-expressions-error 2018-01-16 22:56:23 +03:00
feuillemorte
648d5d0c6b #2953 fix comments: use keyword module 2018-01-16 22:55:24 +03:00
feuillemorte
dff597dcd0 Add changelog entry 2018-01-16 21:34:13 +03:00
feuillemorte
076fb56f85 show a simple and easy error when keyword expressions trigger a syntax error 2018-01-16 21:30:44 +03:00
Bruno Oliveira
150537d5e0 Merge pull request #3119 from PoppyBagel/fix-formatting
[doc] Fix wrong formatting in parametrize.rst
2018-01-16 08:33:48 -02:00
Kate
d8c23fd39b Fix wrong formatting 2018-01-16 12:36:28 +03:00
Cyrus Maden
b748576358 Add name 2018-01-15 13:36:44 -08:00
Cyrus Maden
f555a3a76c Update getting started guide
Proofread; added intro paragraph under first header to orient new users; fixed grammar errors (switched to active voice, actionable directions, etc) to improve readability
2018-01-15 13:27:10 -08:00
Cyrus Maden
1f4831a23f Update getting-started.rst 2018-01-15 12:28:21 -08:00
Brian Maissy
0a0d97aeb5 added changelog news fragment 2018-01-14 23:14:59 +02:00
Brian Maissy
4a3863c2e2 use flush in order to avoid hanging on mac 2018-01-14 23:00:23 +02:00
Aaron
d314691fd3 more descriptive changelog message 2018-01-12 10:29:18 -08:00
Ronny Pfannschmidt
01e37fe892 Merge pull request #3110 from nicoddemus/progress-teardown-3088
Fix progress report when tests fail during teardown
2018-01-12 11:09:01 +01:00
Bruno Oliveira
abbdb60051 Move logic determining if progress should be displayed to a function 2018-01-12 07:04:43 -02:00
Bruno Oliveira
5939b336cd Fix progress report when tests fail during teardown
Fix #3088
2018-01-11 20:42:05 -02:00
Aaron
3d289b803d update AUTHORS and changelog 2018-01-11 12:25:41 -08:00
Aaron
4a704bbb55 fix reorder_items_atscope ordering 2018-01-11 11:30:52 -08:00
Aaron
ee6c9f50a2 optimize fixtures.reorder_items 2018-01-11 11:30:52 -08:00
Bruno Oliveira
3181718fe0 Merge pull request #3087 from nicoddemus/deprecation-roadmap
Update deprecation/removal docs to point to labels/milestones instead
2018-01-11 11:14:47 -02:00
Bruno Oliveira
2674f352e8 Merge pull request #3091 from nicoddemus/conftest-hooks
Document bootstrap and initialization hooks
2018-01-11 11:14:20 -02:00
Bruno Oliveira
6fb46a0e79 Merge pull request #3104 from pbrod/patch-1
Update Freezing pytest description in simple.rst
2018-01-10 14:19:25 -02:00
Per A. Brodtkorb
820ea6d68f Update Freezing pytest description in simple.rst
I have trouble using third party plugins in my frozen program and discovered 
that other people have experienced it as well:

    https://mail.python.org/pipermail//pytest-dev/2015-June/003015.html

The problem is that the mechanism for plugin discovery used by pytest
(setupttools entry points) doesn't work with frozen executables so pytest
can't find any plugins. The solution seems to be to import the third party 
plugins explicitly as shown here:

    https://mail.python.org/pipermail//pytest-dev/2015-June/003018.html

This is not mentioned anywhere in the documentaion.
2018-01-10 16:44:26 +01:00
Ronny Pfannschmidt
b0032ba2b3 Merge pull request #3102 from nicoddemus/logfinish-hook-3101
Add new pytest_runtest_logfinish hook
2018-01-10 07:57:35 +01:00
Ronny Pfannschmidt
cf9b31bd5a Merge pull request #3068 from asottile/pystd
Replace py.std with stdlib imports
2018-01-10 07:52:55 +01:00
Bruno Oliveira
b68b80aec9 Add new pytest_runtest_logfinish hook
Fix #3101
2018-01-09 22:17:39 -02:00
Anthony Sottile
bd1d17e8de Replace py.std with stdlib imports 2018-01-09 12:44:10 -08:00
Bruno Oliveira
93306f6a5e Merge remote-tracking branch 'upstream/master' into features 2018-01-09 18:41:00 -02:00
Bruno Oliveira
962aede290 Merge pull request #3096 from nicoddemus/import-warnings
Ignore ImportWarnings regarding package resolution
2018-01-09 18:40:33 -02:00
Ronny Pfannschmidt
a8d3d329ec Merge pull request #3098 from uranusjr/pep426-setuptools-36.2.1
Add support for new environment marker usages
2018-01-09 18:21:06 +01:00
Tzu-ping Chung
b256cd2a6a Add support for new environment marker usages 2018-01-09 21:34:11 +08:00
Bruno Oliveira
b6b36bc167 Handle pluggy package or module for traceback filtering
Since 0.6.1 pluggy has been turned into a package
2018-01-08 21:29:15 -02:00
Bruno Oliveira
3dd24f8d21 Ignore ImportWarnings regarding package resolution
The problem is described/discussed in #3061

Ideally this should be a temporary solution until we find a proper one
which gets rid of the warning
2018-01-08 21:11:08 -02:00
Bruno Oliveira
794fb193ba Merge pull request #3093 from cryvate/fix-issue-2985-blurb
Fix issue 2985 blurb in changelog
2018-01-07 01:18:18 -02:00
Henk-Jaap Wagenaar
d7e1f037d9 Fix issue 2985 blurb in changelog 2018-01-06 22:15:06 +00:00
Bruno Oliveira
29ff9301d8 Merge pull request #3092 from jdufresne/readthedocs
Prefer https://*.readthedocs.io over http://*.rtfd.org
2018-01-06 15:53:10 -02:00
Bruno Oliveira
f3c666db3c Small formatting update in CHANGELOG 2018-01-06 14:08:15 -02:00
Jon Dufresne
b93aa5e35f Prefer https://*.readthedocs.io over http://*.rtfd.org
- Requests URL using https instead of http
- Avoids unnecessary redirect of *.rtfd.org -> *.readthedocs.io

*.rtfd.org exists as a means for pasting short URLs, which doesn't much
 apply for links in documentation.
2018-01-06 08:05:42 -08:00
Bruno Oliveira
bc66f7e43f Merge pull request #3075 from elliterate/bugs/fix-skipping-plugin-reporting
Assume not skipped by mark if attribute missing
2018-01-06 13:56:07 -02:00
Bruno Oliveira
cb6b851780 Update deprecation/removal docs to point to labels/milestones instead
Using milestones and proper issues are a much saner way to handle these topics
than keeping them in sync in a separate document
2018-01-06 13:48:39 -02:00
Bruno Oliveira
afb8a4e35d Document bootstrap and initialization hooks
Fix #2616
2018-01-06 13:31:38 -02:00
Bruno Oliveira
06a182386b Merge pull request #3073 from nicoddemus/pytest_runtestloop-docs
Add pytest_runtestloop to the docs
2018-01-05 20:35:51 -02:00
Bruno Oliveira
fac07c1b3f Merge pull request #3076 from pafonta/patch-1
[doc] Reword strange sentence on doctest flags
2018-01-05 09:49:54 -02:00
Bruno Oliveira
e8c0ca4f08 Merge pull request #3064 from nicoddemus/release-3.3.2
Preparing release version 3.3.2
2018-01-04 20:42:51 -02:00
Pierre-Alexandre Fonta
ac6f257efc Create news fragment for the pull request 2018-01-04 14:58:30 +01:00
Pierre-Alexandre Fonta
554cb8d09c Reword strange sentence on doctest flags 2018-01-04 14:47:05 +01:00
Ian Lesperance
25b504b4f0 Add self to authors list 2018-01-03 18:54:49 -05:00
Ian Lesperance
0a6e086f9d Add changelog entry 2018-01-03 18:53:32 -05:00
Ian Lesperance
f24c470403 Assume not skipped by mark if attribute missing
Fixes #3074.
2018-01-03 18:47:18 -05:00
Bruno Oliveira
52a7ccef57 Add pytest_runtestloop to the docs 2018-01-03 16:58:57 -02:00
Bruno Oliveira
bd2d0d2c3c Preparing release version 3.3.2 2017-12-25 20:38:27 +00:00
Bruno Oliveira
08997279f4 Merge pull request #3059 from asottile/yesqa
Remove unnecessary `# noqa` comments.
2017-12-22 17:03:02 -02:00
Anthony Sottile
205e29d843 Remove unnecessary # noqa comments.
Commit automated with https://github.com/asottile/yesqa
2017-12-22 08:29:48 -08:00
Ronny Pfannschmidt
672c901c70 Merge pull request #2784 from RonnyPfannschmidt/fix-fdleaks
pytester: ignore files used to obtain current user metadata
2017-12-19 17:08:53 +01:00
Bruno Oliveira
c70efaa0fb Merge pull request #3047 from RonnyPfannschmidt/mark-newapi
move node base classes from main to nodes
2017-12-19 07:59:25 -02:00
Ronny Pfannschmidt
d370e7788d Merge pull request #2068 from flub/commitaccess
[proposal] Add section about how you become a committer
2017-12-18 18:20:49 +01:00
Ronny Pfannschmidt
0d83dd1b31 Merge pull request #3016 from jurko-gospodnetic/clean-up-state-after-in-process-pytest-runs
Clean up state after in process pytest runs
2017-12-18 17:56:24 +01:00
Ronny Pfannschmidt
ed293ec3e9 pytester: no ignore files used to obtain current user metadata in the fd leak detector 2017-12-18 17:52:55 +01:00
Ronny Pfannschmidt
2f8427bb4e Merge pull request #3043 from awbdallas/parameterize_documentation_fix
Parameterize documentation fix
2017-12-18 17:48:41 +01:00
Ronny Pfannschmidt
94608c6110 port _Compatproperty to attrs 2017-12-18 11:08:20 +01:00
Ronny Pfannschmidt
afc607cfd8 move node base classes from main to nodes 2017-12-18 11:08:20 +01:00
Florian Bruhin
30729b7c3c Merge pull request #3045 from thisch/revert_accidental_push
Revert accidental push
2017-12-17 22:06:38 +01:00
Thomas Hisch
dfc5399cd7 Revert "WIP #3013"
This reverts commit 28a93b9eeb.
2017-12-17 21:50:17 +01:00
Thomas Hisch
76489d30f7 Revert "WIP"
This reverts commit d6f75d2836.
2017-12-17 21:49:57 +01:00
Thomas Hisch
d6f75d2836 WIP 2017-12-17 20:11:24 +01:00
Jurko Gospodnetić
d85a3ca19a add changelog entry 2017-12-17 12:47:50 +01:00
Jurko Gospodnetić
f3c9c6e8a8 fix restoring Python state after in-process pytest runs
Now each in-process pytest run saves a snapshot of important global Python
state and restores it after the test completes, including the list of loaded
modules & the Python path settings.

Previously only the loaded package data was getting restored, but that was
also reverting any loaded package changes done in the test triggering the
pytest runs, and not only those done by the pytest runs themselves.

Updated acceptance tests broken by this change, which were only passing before
by accident as they were making multiple pytest runs with later ones depending
on sys.path changes left behind by the initial one.
2017-12-17 12:47:50 +01:00
Jurko Gospodnetić
67bd60d5c6 clean up Testdir taking snapshots & restoring global Python state
Now extracted to new CwdSnapshot, SysModulesSnapshot & SysPathsSnapshot
classes, each saving the state they are interested in on instantiation
and restoring it in its `restore()` method.
2017-12-17 12:47:50 +01:00
Thomas Hisch
28a93b9eeb WIP #3013 2017-12-17 06:54:57 +01:00
Austin
70461d1ead Update parametrize.rst 2017-12-16 09:31:48 -06:00
Austin
73eccb4c36 Adding news fragment 2017-12-16 09:21:27 -06:00
Austin
d7a76a4d07 Updated for more clarity
- Changed original wording for parameterize. Should help be a little more clear
2017-12-16 09:04:18 -06:00
Bruno Oliveira
924b5e2e3d Merge pull request #3031 from nicoddemus/symlink-privileges
Check if symlink does have privileges on Windows for test_cmdline_python_package_symlink
2017-12-16 12:36:11 -02:00
Bruno Oliveira
d87279115d Merge pull request #3041 from segevfiner/capture-no-disable-progress
Use classic console output when -s is used
2017-12-16 12:34:34 -02:00
Bruno Oliveira
0a2735a275 Reword changelog entry 2017-12-16 12:33:34 -02:00
Austin
c90e76c371 Clarifiyng documentation for parameterize
- Added snippet dealing with parameterize run order of tests
2017-12-16 08:25:02 -06:00
Segev Finer
370daf0441 Use classic console output when -s is used
Fixes #3038
2017-12-16 15:00:23 +02:00
Bruno Oliveira
586ecea6f2 Check if symlink does not privileges on Windows for test_cmdline_python_package_symlink
os.symlink might fail on Windows because users require a special policy
to create symlinks (argh).

This is not a problem on AppVeyor because it is logged in as an admin,
but would be surprising for Windows users running pytest's test
suite on their computer.
2017-12-14 19:23:15 -02:00
Bruno Oliveira
db4df5833a Merge pull request #3030 from nicoddemus/leak
Fix memory leak caused by fixture values never been garbage collected
2017-12-14 19:20:34 -02:00
Bruno Oliveira
b17c6e5f89 Merge pull request #3028 from thisch/removelogger
Remove logger parameter from catching_logs
2017-12-13 20:56:48 -02:00
Bruno Oliveira
c3f63ac143 Fix memory leak caused by fixture values never been garbage collected
The leak was caused by the (unused) `FixtureRequest._fixture_values`
cache.

This was introduced because the `partial` object (created to call
FixtureDef.finish() bound with the Request) is kept alive
through the entire session when a function-scoped fixture depends
on a session-scoped (or higher) fixture because of the nested
`addfinalizer` calls.

FixtureDef.finish() started receiving a request object in order to
obtain the proper hook proxy object (#2127), but this does not seem
useful at all in practice because `pytest_fixture_post_finalizer`
will be called with the `request` object of the moment the fixture value
was *created*, not the request object active when the fixture value
is being destroyed. We should probably deprecate/remove the request
parameter from `pytest_fixture_post_finalizer`.

Fix #2981
2017-12-13 19:49:06 -02:00
Thomas Hisch
3862b0b28d Remove logger parameter from catching_logs
The logger parameter of catching_logs is not used anywhere. The main
motivation for removing the logger parameter is that it removes the

logger = logger or logging.getLogger(logger)

line. IMO there are too many occurences of the string 'logg' ;)
2017-12-13 21:13:59 +01:00
Ronny Pfannschmidt
476d4df1b7 Merge pull request #3010 from cryvate/fix-issue-2985
Improve handling of pyargs
2017-12-13 13:56:42 +01:00
Ronny Pfannschmidt
52449903c3 Merge pull request #3023 from nicoddemus/preparse-deprecated
Add param annotations and types to hookspec
2017-12-13 13:51:20 +01:00
Ronny Pfannschmidt
506c9c91c0 Merge pull request #3025 from anntzer/dont-rewrite-plugin
Respect PYTEST_DONT_REWRITE for plugins too.

closes #2995
2017-12-13 12:34:51 +01:00
Bruno Oliveira
38f34e2fa1 Merge pull request #3012 from thisch/merge_contextmanagers
Integrate logging_using_handler into catching_logs
2017-12-13 06:59:48 -02:00
Bruno Oliveira
ba41015ef6 Merge pull request #3021 from jurko-gospodnetic/cleanup
Code cleanup
2017-12-13 06:58:18 -02:00
Bruno Oliveira
ebfc1c49d1 Fix changelog formatting 2017-12-13 06:58:07 -02:00
Antony Lee
45e7734b1a Change set ops to use methods instead of operators. 2017-12-13 00:54:57 -08:00
Bruno Oliveira
8ce6e39b1c Small formatting to CHANGELOG 2017-12-13 06:52:37 -02:00
Antony Lee
c8e7d1ae34 Respect PYTEST_DONT_REWRITE for plugins too. 2017-12-12 17:43:17 -08:00
Bruno Oliveira
7b5d4d01ed Add param annotations and types to hookspec
Also mention which hook to use instead of the deprecated
pytest_cmdline_preparse

Fix #3022
2017-12-12 18:01:31 -02:00
Jurko Gospodnetić
a4f4579f19 add changelog entry 2017-12-12 13:41:31 +01:00
Jurko Gospodnetić
e4da9bacdf fix actial --> actual typo 2017-12-12 12:55:32 +01:00
Henk-Jaap Wagenaar
1e295535c3 Move _patched_find_module to module namespace. 2017-12-12 09:53:06 +00:00
Henk-Jaap Wagenaar
3ca1e4b7f0 Make patch for issue in pkgutil.ImpImporter local by using contextmanager. 2017-12-12 08:43:01 +00:00
Henk-Jaap Wagenaar
dc19624248 Improve testing comments and code of issue 2985 (symlink in pyargs). 2017-12-12 08:26:40 +00:00
Bruno Oliveira
f8f1a52ea0 Merge remote-tracking branch 'upstream/master' into features 2017-12-11 22:18:50 -02:00
Bruno Oliveira
5c6d7739bc Merge pull request #3015 from jurko-gospodnetic/code-cleanup
Code cleanup
2017-12-11 22:15:50 -02:00
Bruno Oliveira
771b5c8852 Merge pull request #3019 from srinivasreddy/rm_ast
remove '_ast' module; and redirect '_ast' references to 'ast'
2017-12-11 22:12:40 -02:00
Bruno Oliveira
fc5ec5807e Add changelog for 3018 2017-12-11 17:40:35 -02:00
Srinivas Reddy Thatiparthy
fc544dc660 remove redundant checking because
* isinstance([], Sequence) is True

* isinstance((), Sequence) is True
2017-12-12 00:10:59 +05:30
Srinivas Reddy Thatiparthy
7792587b3f remove unnecessary import 2017-12-12 00:03:38 +05:30
Srinivas Reddy Thatiparthy
c2cd239d35 remove _ast reference from test cases as well 2017-12-11 23:24:47 +05:30
Srinivas Reddy Thatiparthy
cb0ba18f53 remove '_ast' module; and redirect '_ast' references to 'ast' 2017-12-11 23:14:17 +05:30
Jurko Gospodnetić
3b85e0c3a9 simplify test_conftest_found_with_double_dash() test code 2017-12-11 15:10:04 +01:00
Jurko Gospodnetić
73bc6bacfa add changelog entry 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
8e8a953ac6 fix test name typo 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
852b96714e use spaces consistently instead of some of them being &nbsp; 2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
dd64f1a4a9 fix tox.ini comment typos 2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
41a6ec6f31 touch up test_cmdline_python_namespace_package() test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
7feab7391d simplify test_pdb_collection_failure_is_shown test data setup code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
f0bfe9de3d shorten some test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
596937e610 remove extra whitespace 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
57fcd3f57e remove corpse code comments 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
65f5383106 fix comment & docstring typos, line wrapping & wording 2017-12-09 13:34:54 +01:00
Ronny Pfannschmidt
964c29cb93 Merge pull request #3014 from nicoddemus/cap-named-tuple
Change capture docs to use namedtuple
2017-12-09 09:38:39 +01:00
Bruno Oliveira
38fb6aae78 Change capture docs to use namedtuple 2017-12-08 18:34:29 -02:00
Bruno Oliveira
1c5b887dfd Fix spelling of pytest in CHANGELOG 2017-12-08 09:04:42 -02:00
Thomas Hisch
ba209b5230 Integrate logging_using_handler into catching_logs
logging_using_handler is only used in catching_logs. Therefore it makes
sense to remove one of the many context managers from the logging
plugin.
2017-12-07 16:34:53 +01:00
Henk-Jaap Wagenaar
b62fd79c0c Fix issue 2985. 2017-12-07 15:12:44 +00:00
Bruno Oliveira
655146e522 Merge remote-tracking branch 'upstream/master' into features 2017-12-05 22:30:35 -02:00
Bruno Oliveira
88f2cc9b64 Small formatting fixes in CHANGELOG 2017-12-05 22:28:56 -02:00
Bruno Oliveira
ed2bb9d723 Merge pull request #2997 from nicoddemus/release-3.3.1
Preparing release version 3.3.1
2017-12-05 22:21:41 -02:00
Bruno Oliveira
294729962d Merge pull request #2976 from st--/master
Extend _pytest.python._idval to return __name__ of functions as well
2017-11-30 18:01:21 -02:00
ST John
652936f47f make linter happier 2017-11-30 10:29:05 +00:00
ST John
1fe2e2cb03 Merge branch 'master' of https://github.com/st--/pytest 2017-11-30 10:19:38 +00:00
ST John
e66473853c add test 2017-11-30 10:19:29 +00:00
Bruno Oliveira
fdd4abb88a Small rewording of the CHANGELOG 2017-11-29 21:11:34 -02:00
ST John
5085aa2bce add changelog file 2017-11-29 16:30:34 +00:00
ST John
912330a7e2 Extend _pytest.python._idval to return __name__ of functions as well, not just for classes 2017-11-29 16:17:49 +00:00
Floris Bruynooghe
1f0d06641a Update text to only give access when wanted
Also clarify merging perms given the PR approval now available.
2017-02-19 15:16:34 -03:00
Floris Bruynooghe
4c62cd451a Add section about how you become a committer 2016-11-17 21:25:22 +00:00
158 changed files with 7172 additions and 2794 deletions

View File

@@ -1,15 +1,14 @@
Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs:
Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is
just a guideline):
- [ ] Add a new news fragment into the changelog folder
* name it `$issue_id.$type` for example (588.bugfix)
* if you don't have an issue_id change it to the pr id after creating the pr
* ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
* Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`;
- [ ] Make sure to include reasonable tests for your change if necessary
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](/changelog/README.rst) for details.
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
- [ ] Target the `features` branch for new features and removals/deprecations.
- [ ] Include documentation when adding new features.
- [ ] Include new tests or update existing tests when applicable.
Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please:
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
- [ ] Add yourself to `AUTHORS`, in alphabetical order;
- [ ] Add yourself to `AUTHORS` in alphabetical order;

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ env/
3rdparty/
.tox
.cache
.pytest_cache
.coverage
.ropeproject
.idea

View File

@@ -2,10 +2,8 @@ sudo: false
language: python
python:
- '3.6'
# command to install dependencies
install:
- pip install --upgrade --pre tox
# # command to run tests
env:
matrix:
# coveralls is not listed in tox's envlist, but should run in travis
@@ -29,7 +27,7 @@ env:
- TOXENV=doctesting
- TOXENV=docs
matrix:
jobs:
include:
- env: TOXENV=pypy
python: 'pypy-5.4'
@@ -39,9 +37,22 @@ matrix:
python: '3.5'
- env: TOXENV=py37
python: 'nightly'
allow_failures:
- env: TOXENV=py37
python: 'nightly'
- stage: deploy
python: '3.6'
env:
install: pip install -U setuptools setuptools_scm
script: skip
deploy:
provider: pypi
user: nicoddemus
distributions: sdist bdist_wheel
skip_upload_docs: true
password:
secure: xanTgTUu6XDQVqB/0bwJQXoDMnU5tkwZc5koz6mBkkqZhKdNOi2CLoC1XhiSZ+ah24l4V1E0GAqY5kBBcy9d7NVe4WNg4tD095LsHw+CRU6/HCVIFfyk2IZ+FPAlguesCcUiJSXOrlBF+Wj68wEvLoK7EoRFbJeiZ/f91Ww1sbtDlqXABWGHrmhPJL5Wva7o7+wG7JwJowqdZg1pbQExsCc7b53w4v2RBu3D6TJaTAzHiVsW+nUSI67vKI/uf+cR/OixsTfy37wlHgSwihYmrYLFls3V0bSpahCim3bCgMaFZx8S8xrdgJ++PzBCof2HeflFKvW+VCkoYzGEG4NrTWJoNz6ni4red9GdvfjGH3YCjAKS56h9x58zp2E5rpsb/kVq5/45xzV+dq6JRuhQ1nJWjBC6fSKAc/bfwnuFK3EBxNLkvBssLHvsNjj5XG++cB8DdS9wVGUqjpoK4puaXUWFqy4q3S9F86HEsKNgExtieA9qNx+pCIZVs6JCXZNjr0I5eVNzqJIyggNgJG6RyravsU35t9Zd9doL5g4Y7UKmAGTn1Sz24HQ4sMQgXdm2SyD8gEK5je4tlhUvfGtDvMSlstq71kIn9nRpFnqB6MFlbYSEAZmo8dGbCquoUc++6Rum208wcVbrzzVtGlXB/Ow9AbFMYeAGA0+N/K1e59c=
on:
tags: true
repo: pytest-dev/pytest
script: tox --recreate

18
AUTHORS
View File

@@ -3,12 +3,15 @@ merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Aaron Coleman
Abdeali JK
Abhijeet Kasurde
Ahn Ki-Wook
Alan Velasco
Alexander Johnson
Alexei Kozlenok
Anatoly Bubenkoff
Anders Hovmöller
Andras Tim
Andreas Zeidler
Andrzej Ostrowski
@@ -17,6 +20,7 @@ Anthon van der Neut
Anthony Sottile
Antony Lee
Armin Rigo
Aron Coyle
Aron Curzon
Aviv Palivoda
Barney Gale
@@ -25,11 +29,13 @@ Benjamin Peterson
Bernard Pratz
Bob Ippolito
Brian Dorsey
Brian Maissy
Brian Okken
Brianna Laugher
Bruno Oliveira
Cal Leeming
Carl Friedrich Bolz
Carlos Jenkins
Ceridwen
Charles Cloud
Charnjit SiNGH (CCSJ)
@@ -38,6 +44,7 @@ Christian Boelsen
Christian Theunert
Christian Tismer
Christopher Gilling
Cyrus Maden
Daniel Grana
Daniel Hahler
Daniel Nuri
@@ -74,15 +81,18 @@ Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
Harald Armin Massa
Henk-Jaap Wagenaar
Hugo van Kemenade
Hui Wang (coldnight)
Ian Bicking
Ian Lesperance
Jaap Broekhuizen
Jan Balster
Janne Vanhala
Jason R. Coombs
Javier Domingo Cansino
Javier Romero
Jeff Rackauckas
Jeff Widman
John Eddie Ayson
John Towler
@@ -90,6 +100,7 @@ Jon Sonesen
Jonas Obrist
Jordan Guymon
Jordan Moldow
Jordan Speicher
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@@ -97,6 +108,7 @@ Kale Kundert
Katarzyna Jachim
Kevin Cox
Kodi B. Arfer
Kostis Anagnostopoulos
Lawrence Mitchell
Lee Kamentsky
Lev Maximov
@@ -136,17 +148,20 @@ Ned Batchelder
Neven Mundar
Nicolas Delaby
Oleg Pidsadnyi
Oleg Sushchenko
Oliver Bestwalter
Omar Kohl
Omer Hadari
Patrick Hayes
Paweł Adamczak
Pedro Algarvio
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Quentin Pradet
Ralf Schmitt
Ran Benita
Raphael Castaneda
Raphael Pierzina
Raquel Alegre
Ravi Chandra
@@ -177,13 +192,16 @@ Tom Dalton
Tom Viner
Trevor Bekolay
Tyler Goodlet
Tzu-ping Chung
Vasily Kuznetsov
Victor Uriarte
Vidar T. Fauske
Vitaly Lashmanov
Vlad Dragos
William Lee
Wouter van Ackooy
Xuan Luong
Xuecong Liao
Zoltán Máté
Roland Puntaier
Allan Feldman

View File

@@ -1,4 +1,4 @@
..
..
You should *NOT* be adding new change log entries to this file, this
file is managed by towncrier. You *may* edit previous change logs to
fix problems like typo corrections or such.
@@ -8,6 +8,425 @@
.. towncrier release notes start
Pytest 3.5.0 (2018-03-21)
=========================
Deprecations and Removals
-------------------------
- ``record_xml_property`` fixture is now deprecated in favor of the more
generic ``record_property``. (`#2770
<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
<https://github.com/pytest-dev/pytest/issues/3084>`_)
Features
--------
- New ``--show-capture`` command-line option that allows to specify how to
display captured output when tests fail: ``no``, ``stdout``, ``stderr``,
``log`` or ``all`` (the default). (`#1478
<https://github.com/pytest-dev/pytest/issues/1478>`_)
- New ``--rootdir`` command-line option to override the rules for discovering
the root directory. See `customize
<https://docs.pytest.org/en/latest/customize.html>`_ in the documentation for
details. (`#1642 <https://github.com/pytest-dev/pytest/issues/1642>`_)
- Fixtures are now instantiated based on their scopes, with higher-scoped
fixtures (such as ``session``) being instantiated first than lower-scoped
fixtures (such as ``function``). The relative order of fixtures of the same
scope is kept unchanged, based in their declaration order and their
dependencies. (`#2405 <https://github.com/pytest-dev/pytest/issues/2405>`_)
- ``record_xml_property`` renamed to ``record_property`` and is now compatible
with xdist, markers and any reporter. ``record_xml_property`` name is now
deprecated. (`#2770 <https://github.com/pytest-dev/pytest/issues/2770>`_)
- New ``--nf``, ``--new-first`` options: run new tests first followed by the
rest of the tests, in both cases tests are also sorted by the file modified
time, with more recent files coming first. (`#3034
<https://github.com/pytest-dev/pytest/issues/3034>`_)
- New ``--last-failed-no-failures`` command-line option that allows to specify
the behavior of the cache plugin's ```--last-failed`` feature when no tests
failed in the last run (or no cache was found): ``none`` or ``all`` (the
default). (`#3139 <https://github.com/pytest-dev/pytest/issues/3139>`_)
- New ``--doctest-continue-on-failure`` command-line option to enable doctests
to show multiple failures for each snippet, instead of stopping at the first
failure. (`#3149 <https://github.com/pytest-dev/pytest/issues/3149>`_)
- Captured log messages are added to the ``<system-out>`` tag in the generated
junit xml file if the ``junit_logging`` ini option is set to ``system-out``.
If the value of this ini option is ``system-err`, the logs are written to
``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning
captured logs are not written to the output file. (`#3156
<https://github.com/pytest-dev/pytest/issues/3156>`_)
- Allow the logging plugin to handle ``pytest_runtest_logstart`` and
``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189
<https://github.com/pytest-dev/pytest/issues/3189>`_)
- Passing `--log-cli-level` in the command-line now automatically activates
live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_)
- Add command line option ``--deselect`` to allow deselection of individual
tests at collection time. (`#3198
<https://github.com/pytest-dev/pytest/issues/3198>`_)
- Captured logs are printed before entering pdb. (`#3204
<https://github.com/pytest-dev/pytest/issues/3204>`_)
- Deselected item count is now shown before tests are run, e.g. ``collected X
items / Y deselected``. (`#3213
<https://github.com/pytest-dev/pytest/issues/3213>`_)
- The builtin module ``platform`` is now available for use in expressions in
``pytest.mark``. (`#3236
<https://github.com/pytest-dev/pytest/issues/3236>`_)
- The *short test summary info* section now is displayed after tracebacks and
warnings in the terminal. (`#3255
<https://github.com/pytest-dev/pytest/issues/3255>`_)
- New ``--verbosity`` flag to set verbosity level explicitly. (`#3296
<https://github.com/pytest-dev/pytest/issues/3296>`_)
- ``pytest.approx`` now accepts comparing a numpy array with a scalar. (`#3312
<https://github.com/pytest-dev/pytest/issues/3312>`_)
Bug Fixes
---------
- Suppress ``IOError`` when closing the temporary file used for capturing
streams in Python 2.7. (`#2370
<https://github.com/pytest-dev/pytest/issues/2370>`_)
- Fixed ``clear()`` method on ``caplog`` fixture which cleared ``records``, but
not the ``text`` property. (`#3297
<https://github.com/pytest-dev/pytest/issues/3297>`_)
- During test collection, when stdin is not allowed to be read, the
``DontReadFromStdin`` object still allow itself to be iterable and resolved
to an iterator without crashing. (`#3314
<https://github.com/pytest-dev/pytest/issues/3314>`_)
Improved Documentation
----------------------
- Added a `reference <https://docs.pytest.org/en/latest/reference.html>`_ page
to the docs. (`#1713 <https://github.com/pytest-dev/pytest/issues/1713>`_)
Trivial/Internal Changes
------------------------
- Change minimum requirement of ``attrs`` to ``17.4.0``. (`#3228
<https://github.com/pytest-dev/pytest/issues/3228>`_)
- Renamed example directories so all tests pass when ran from the base
directory. (`#3245 <https://github.com/pytest-dev/pytest/issues/3245>`_)
- Internal ``mark.py`` module has been turned into a package. (`#3250
<https://github.com/pytest-dev/pytest/issues/3250>`_)
- ``pytest`` now depends on the `more_itertools
<https://github.com/erikrose/more-itertools>`_ package. (`#3265
<https://github.com/pytest-dev/pytest/issues/3265>`_)
- Added warning when ``[pytest]`` section is used in a ``.cfg`` file passed
with ``-c`` (`#3268 <https://github.com/pytest-dev/pytest/issues/3268>`_)
- ``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node``
constructors. (`#3291 <https://github.com/pytest-dev/pytest/issues/3291>`_)
- Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and
remove old support code for legacy Python versions. (`#3292
<https://github.com/pytest-dev/pytest/issues/3292>`_)
- Refactoring to unify how verbosity is handled internally. (`#3296
<https://github.com/pytest-dev/pytest/issues/3296>`_)
- Internal refactoring to better integrate with argparse. (`#3304
<https://github.com/pytest-dev/pytest/issues/3304>`_)
- Fix a python example when calling a fixture in doc/en/usage.rst (`#3308
<https://github.com/pytest-dev/pytest/issues/3308>`_)
Pytest 3.4.2 (2018-03-04)
=========================
Bug Fixes
---------
- Removed progress information when capture option is ``no``. (`#3203
<https://github.com/pytest-dev/pytest/issues/3203>`_)
- Refactor check of bindir from ``exists`` to ``isdir``. (`#3241
<https://github.com/pytest-dev/pytest/issues/3241>`_)
- Fix ``TypeError`` issue when using ``approx`` with a ``Decimal`` value.
(`#3247 <https://github.com/pytest-dev/pytest/issues/3247>`_)
- Fix reference cycle generated when using the ``request`` fixture. (`#3249
<https://github.com/pytest-dev/pytest/issues/3249>`_)
- ``[tool:pytest]`` sections in ``*.cfg`` files passed by the ``-c`` option are
now properly recognized. (`#3260
<https://github.com/pytest-dev/pytest/issues/3260>`_)
Improved Documentation
----------------------
- Add logging plugin to plugins list. (`#3209
<https://github.com/pytest-dev/pytest/issues/3209>`_)
Trivial/Internal Changes
------------------------
- Fix minor typo in fixture.rst (`#3259
<https://github.com/pytest-dev/pytest/issues/3259>`_)
Pytest 3.4.1 (2018-02-20)
=========================
Bug Fixes
---------
- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible
errors when using ``--pdb``. (`#1810
<https://github.com/pytest-dev/pytest/issues/1810>`_)
- Added printing of captured stdout/stderr before entering pdb, and improved a
test which was giving false negatives about output capturing. (`#3052
<https://github.com/pytest-dev/pytest/issues/3052>`_)
- Fix ordering of tests using parametrized fixtures which can lead to fixtures
being created more than necessary. (`#3161
<https://github.com/pytest-dev/pytest/issues/3161>`_)
- Fix bug where logging happening at hooks outside of "test run" hooks would
cause an internal error. (`#3184
<https://github.com/pytest-dev/pytest/issues/3184>`_)
- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when
pypi ``mock.patch`` is installed and imported. (`#3206
<https://github.com/pytest-dev/pytest/issues/3206>`_)
- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner
on what happened: When no exception was raised, the "matching '...'" part got
removed as it falsely implies that an exception was raised but it didn't
match. When a wrong exception was raised, it's now thrown (like
``pytest.raised()`` without ``match=`` would) instead of complaining about
the unmatched text. (`#3222
<https://github.com/pytest-dev/pytest/issues/3222>`_)
- Fixed output capture handling in doctests on macOS. (`#985
<https://github.com/pytest-dev/pytest/issues/985>`_)
Improved Documentation
----------------------
- Add Sphinx parameter docs for ``match`` and ``message`` args to
``pytest.raises``. (`#3202
<https://github.com/pytest-dev/pytest/issues/3202>`_)
Trivial/Internal Changes
------------------------
- pytest has changed the publication procedure and is now being published to
PyPI directly from Travis. (`#3060
<https://github.com/pytest-dev/pytest/issues/3060>`_)
- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in
order to comply with the naming convention. (`#3166
<https://github.com/pytest-dev/pytest/issues/3166>`_)
- Skip failing pdb/doctest test on mac. (`#985
<https://github.com/pytest-dev/pytest/issues/985>`_)
Pytest 3.4.0 (2018-01-30)
=========================
Deprecations and Removals
-------------------------
- All pytest classes now subclass ``object`` for better Python 2/3 compatibility.
This should not affect user code except in very rare edge cases. (`#2147
<https://github.com/pytest-dev/pytest/issues/2147>`_)
Features
--------
- Introduce ``empty_parameter_set_mark`` ini option to select which mark to
apply when ``@pytest.mark.parametrize`` is given an empty set of parameters.
Valid options are ``skip`` (default) and ``xfail``. Note that it is planned
to change the default to ``xfail`` in future releases as this is considered
less error prone. (`#2527
<https://github.com/pytest-dev/pytest/issues/2527>`_)
- **Incompatible change**: after community feedback the `logging
<https://docs.pytest.org/en/latest/logging.html>`_ functionality has
undergone some changes. Please consult the `logging documentation
<https://docs.pytest.org/en/latest/logging.html#incompatible-changes-in-pytest-3-4>`_
for details. (`#3013 <https://github.com/pytest-dev/pytest/issues/3013>`_)
- Console output falls back to "classic" mode when capturing is disabled (``-s``),
otherwise the output gets garbled to the point of being useless. (`#3038
<https://github.com/pytest-dev/pytest/issues/3038>`_)
- New `pytest_runtest_logfinish
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_logfinish>`_
hook which is called when a test item has finished executing, analogous to
`pytest_runtest_logstart
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_start>`_.
(`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_)
- Improve performance when collecting tests using many fixtures. (`#3107
<https://github.com/pytest-dev/pytest/issues/3107>`_)
- New ``caplog.get_records(when)`` method which provides access to the captured
records for the ``"setup"``, ``"call"`` and ``"teardown"``
testing stages. (`#3117 <https://github.com/pytest-dev/pytest/issues/3117>`_)
- New fixture ``record_xml_attribute`` that allows modifying and inserting
attributes on the ``<testcase>`` xml node in JUnit reports. (`#3130
<https://github.com/pytest-dev/pytest/issues/3130>`_)
- The default cache directory has been renamed from ``.cache`` to
``.pytest_cache`` after community feedback that the name ``.cache`` did not
make it clear that it was used by pytest. (`#3138
<https://github.com/pytest-dev/pytest/issues/3138>`_)
- Colorize the levelname column in the live-log output. (`#3142
<https://github.com/pytest-dev/pytest/issues/3142>`_)
Bug Fixes
---------
- Fix hanging pexpect test on MacOS by using flush() instead of wait().
(`#2022 <https://github.com/pytest-dev/pytest/issues/2022>`_)
- Fix restoring Python state after in-process pytest runs with the
``pytester`` plugin; this may break tests using multiple inprocess
pytest runs if later ones depend on earlier ones leaking global interpreter
changes. (`#3016 <https://github.com/pytest-dev/pytest/issues/3016>`_)
- Fix skipping plugin reporting hook when test aborted before plugin setup
hook. (`#3074 <https://github.com/pytest-dev/pytest/issues/3074>`_)
- Fix progress percentage reported when tests fail during teardown. (`#3088
<https://github.com/pytest-dev/pytest/issues/3088>`_)
- **Incompatible change**: ``-o/--override`` option no longer eats all the
remaining options, which can lead to surprising behavior: for example,
``pytest -o foo=1 /path/to/test.py`` would fail because ``/path/to/test.py``
would be considered as part of the ``-o`` command-line argument. One
consequence of this is that now multiple configuration overrides need
multiple ``-o`` flags: ``pytest -o foo=1 -o bar=2``. (`#3103
<https://github.com/pytest-dev/pytest/issues/3103>`_)
Improved Documentation
----------------------
- Document hooks (defined with ``historic=True``) which cannot be used with
``hookwrapper=True``. (`#2423
<https://github.com/pytest-dev/pytest/issues/2423>`_)
- Clarify that warning capturing doesn't change the warning filter by default.
(`#2457 <https://github.com/pytest-dev/pytest/issues/2457>`_)
- Clarify a possible confusion when using pytest_fixture_setup with fixture
functions that return None. (`#2698
<https://github.com/pytest-dev/pytest/issues/2698>`_)
- Fix the wording of a sentence on doctest flags used in pytest. (`#3076
<https://github.com/pytest-dev/pytest/issues/3076>`_)
- Prefer ``https://*.readthedocs.io`` over ``http://*.rtfd.org`` for links in
the documentation. (`#3092
<https://github.com/pytest-dev/pytest/issues/3092>`_)
- Improve readability (wording, grammar) of Getting Started guide (`#3131
<https://github.com/pytest-dev/pytest/issues/3131>`_)
- Added note that calling pytest.main multiple times from the same process is
not recommended because of import caching. (`#3143
<https://github.com/pytest-dev/pytest/issues/3143>`_)
Trivial/Internal Changes
------------------------
- Show a simple and easy error when keyword expressions trigger a syntax error
(for example, ``"-k foo and import"`` will show an error that you can not use
the ``import`` keyword in expressions). (`#2953
<https://github.com/pytest-dev/pytest/issues/2953>`_)
- Change parametrized automatic test id generation to use the ``__name__``
attribute of functions instead of the fallback argument name plus counter.
(`#2976 <https://github.com/pytest-dev/pytest/issues/2976>`_)
- Replace py.std with stdlib imports. (`#3067
<https://github.com/pytest-dev/pytest/issues/3067>`_)
- Corrected 'you' to 'your' in logging docs. (`#3129
<https://github.com/pytest-dev/pytest/issues/3129>`_)
Pytest 3.3.2 (2017-12-25)
=========================
Bug Fixes
---------
- pytester: ignore files used to obtain current user metadata in the fd leak
detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_)
- Fix **memory leak** where objects returned by fixtures were never destructed
by the garbage collector. (`#2981
<https://github.com/pytest-dev/pytest/issues/2981>`_)
- Fix conversion of pyargs to filename to not convert symlinks on Python 2. (`#2985
<https://github.com/pytest-dev/pytest/issues/2985>`_)
- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for
test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_)
Improved Documentation
----------------------
- Add clarifying note about behavior of multiple parametrized arguments (`#3001
<https://github.com/pytest-dev/pytest/issues/3001>`_)
Trivial/Internal Changes
------------------------
- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_,
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018
<https://github.com/pytest-dev/pytest/issues/3018>`_)
Pytest 3.3.1 (2017-12-05)
=========================
@@ -31,7 +450,7 @@ Bug Fixes
``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0``
release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_)
- The pytest-capturelog plugin is now also blacklisted, avoiding errors when
- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when
running pytest with it still installed. (`#3004
<https://github.com/pytest-dev/pytest/issues/3004>`_)
@@ -39,14 +458,14 @@ Bug Fixes
Improved Documentation
----------------------
- Fix broken link to plugin pytest-localserver. (`#2963
- Fix broken link to plugin ``pytest-localserver``. (`#2963
<https://github.com/pytest-dev/pytest/issues/2963>`_)
Trivial/Internal Changes
------------------------
- Update github "bugs" link in CONTRIBUTING.rst (`#2949
- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949
<https://github.com/pytest-dev/pytest/issues/2949>`_)
@@ -110,7 +529,7 @@ Features
- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708
<https://github.com/pytest-dev/pytest/issues/2708>`_)
- Pytest now captures and displays output from the standard `logging` module.
- Pytest now captures and displays output from the standard ``logging`` module.
The user can control the logging level to be captured by specifying options
in ``pytest.ini``, the command line and also during individual tests using
markers. Also, a ``caplog`` fixture is available that enables users to test
@@ -157,10 +576,10 @@ Bug Fixes
<https://github.com/pytest-dev/pytest/issues/2124>`_)
- If an exception happens while loading a plugin, pytest no longer hides the
original traceback. In python2 it will show the original traceback with a new
message that explains in which plugin. In python3 it will show 2 canonized
original traceback. In Python 2 it will show the original traceback with a new
message that explains in which plugin. In Python 3 it will show 2 canonized
exceptions, the original exception while loading the plugin in addition to an
exception that PyTest throws about loading a plugin. (`#2491
exception that pytest throws about loading a plugin. (`#2491
<https://github.com/pytest-dev/pytest/issues/2491>`_)
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709

View File

@@ -276,3 +276,15 @@ Here is a simple overview, with pytest-specific bits:
base: features # if it's a feature
Joining the Development Team
----------------------------
Anyone who has successfully seen through a pull request which did not
require any extra work from the development team to merge will
themselves gain commit access if they so wish (if we forget to ask please send a friendly
reminder). This does not mean your workflow to contribute changes,
everyone goes through the same pull-request-and-review process and
no-one merges their own pull requests unless already approved. It does however mean you can
participate in the development process more fully since you can merge
pull requests from other contributors yourself after having reviewed
them.

View File

@@ -12,7 +12,7 @@ taking a lot of time to make a new one.
#. Install development dependencies in a virtual environment with::
pip3 install -r tasks/requirements.txt
pip3 install -U -r tasks/requirements.txt
#. Create a branch ``release-X.Y.Z`` with the version for the release.
@@ -22,44 +22,28 @@ taking a lot of time to make a new one.
Ensure your are in a clean work tree.
#. Generate docs, changelog, announcements and upload a package to
your ``devpi`` staging server::
#. Generate docs, changelog, announcements and a **local** tag::
invoke generate.pre-release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
If ``--password`` is not given, it is assumed the user is already logged in ``devpi``.
If you don't have an account, please ask for one.
invoke generate.pre-release <VERSION>
#. Open a PR for this branch targeting ``master``.
#. Test the package
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
* **Manual method**
git push git@github.com:pytest-dev/pytest.git <VERSION>
Run from multiple machines::
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION
#. Send an email announcement with the contents from::
Check that tests pass for relevant combinations with::
doc/en/announce/release-<VERSION>.rst
devpi list pytest
To the following mailing lists:
* **CI servers**
* pytest-dev@python.org (all releases)
* python-announce-list@python.org (all releases)
* testing-in-python@lists.idyll.org (only major/minor releases)
Configure a repository as per-instructions on
devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
All test environments should pass.
#. Publish to PyPI::
invoke generate.publish-release <VERSION> <DEVPI USER> <PYPI_NAME>
where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.
#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
.. _AppVeyor: https://www.appveyor.com/
.. _Travis: https://travis-ci.org

View File

@@ -23,6 +23,9 @@
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
:target: https://www.codetriage.com/pytest-dev/pytest
The ``pytest`` framework makes it easy to write small tests, yet
scales to support complex functional testing for applications and libraries.

View File

@@ -60,7 +60,7 @@ import os
from glob import glob
class FastFilesCompleter:
class FastFilesCompleter(object):
'Fast file completer class'
def __init__(self, directories=True):

View File

@@ -1,6 +1,10 @@
from __future__ import absolute_import, division, print_function
import inspect
import sys
import traceback
from inspect import CO_VARARGS, CO_VARKEYWORDS
import attr
import re
from weakref import ref
from _pytest.compat import _PY2, _PY3, PY35, safe_str
@@ -422,7 +426,7 @@ class ExceptionInfo(object):
"""
if style == 'native':
return ReprExceptionInfo(ReprTracebackNative(
py.std.traceback.format_exception(
traceback.format_exception(
self.type,
self.value,
self.traceback[0]._rawentry,
@@ -456,19 +460,19 @@ class ExceptionInfo(object):
return True
@attr.s
class FormattedExcinfo(object):
""" presenting information about failing Functions and Generators. """
# for traceback entries
flow_marker = ">"
fail_marker = "E"
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
self.showlocals = showlocals
self.style = style
self.tbfilter = tbfilter
self.funcargs = funcargs
self.abspath = abspath
self.astcache = {}
showlocals = attr.ib(default=False)
style = attr.ib(default="long")
abspath = attr.ib(default=True)
tbfilter = attr.ib(default=True)
funcargs = attr.ib(default=False)
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
def _getindent(self, source):
# figure out indent for given source
@@ -556,7 +560,7 @@ class FormattedExcinfo(object):
# else:
# self._line("%-10s =\\" % (name,))
# # XXX
# py.std.pprint.pprint(value, stream=self.excinfowriter)
# pprint.pprint(value, stream=self.excinfowriter)
return ReprLocals(lines)
def repr_traceback_entry(self, entry, excinfo=None):
@@ -669,7 +673,7 @@ class FormattedExcinfo(object):
else:
# fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work
reprtraceback = ReprTracebackNative(py.std.traceback.format_exception(type(e), e, None))
reprtraceback = ReprTracebackNative(traceback.format_exception(type(e), e, None))
reprcrash = None
repr_chain += [(reprtraceback, reprcrash, descr)]
@@ -886,7 +890,7 @@ def getrawcode(obj, trycall=True):
obj = getattr(obj, 'f_code', obj)
obj = getattr(obj, '__code__', obj)
if trycall and not hasattr(obj, 'co_firstlineno'):
if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
if hasattr(obj, '__call__') and not inspect.isclass(obj):
x = getrawcode(obj.__call__, trycall=False)
if hasattr(x, 'co_firstlineno'):
return x

View File

@@ -1,19 +1,16 @@
from __future__ import absolute_import, division, generators, print_function
import ast
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
import linecache
import sys
import six
import inspect
import tokenize
import py
cpy_compile = compile
try:
import _ast
from _ast import PyCF_ONLY_AST as _AST_FLAG
except ImportError:
_AST_FLAG = 0
_ast = None
cpy_compile = compile
class Source(object):
@@ -29,7 +26,7 @@ class Source(object):
for part in parts:
if not part:
partlines = []
if isinstance(part, Source):
elif isinstance(part, Source):
partlines = part.lines
elif isinstance(part, (tuple, list)):
partlines = [x.rstrip("\n") for x in part]
@@ -101,14 +98,14 @@ class Source(object):
newsource.lines = [(indent + line) for line in self.lines]
return newsource
def getstatement(self, lineno, assertion=False):
def getstatement(self, lineno):
""" return Source statement which contains the
given linenumber (counted from 0).
"""
start, end = self.getstatementrange(lineno, assertion)
start, end = self.getstatementrange(lineno)
return self[start:end]
def getstatementrange(self, lineno, assertion=False):
def getstatementrange(self, lineno):
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
"""
@@ -134,13 +131,7 @@ class Source(object):
""" return True if source is parseable, heuristically
deindenting it by default.
"""
try:
import parser
except ImportError:
def syntax_checker(x):
return compile(x, 'asd', 'exec')
else:
syntax_checker = parser.suite
from parser import suite as syntax_checker
if deindent:
source = str(self.deindent())
@@ -195,7 +186,7 @@ class Source(object):
if flag & _AST_FLAG:
return co
lines = [(x + "\n") for x in self.lines]
py.std.linecache.cache[filename] = (1, None, lines, filename)
linecache.cache[filename] = (1, None, lines, filename)
return co
#
@@ -209,7 +200,7 @@ def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag,
retrieval of the source code for the code object
and any recursively created code objects.
"""
if _ast is not None and isinstance(source, _ast.AST):
if isinstance(source, ast.AST):
# XXX should Source support having AST?
return cpy_compile(source, filename, mode, flags, dont_inherit)
_genframe = sys._getframe(1) # the caller
@@ -222,13 +213,12 @@ def getfslineno(obj):
""" Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1)
"""
import _pytest._code
from .code import Code
try:
code = _pytest._code.Code(obj)
code = Code(obj)
except TypeError:
try:
fn = (py.std.inspect.getsourcefile(obj) or
py.std.inspect.getfile(obj))
fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
except TypeError:
return "", -1
@@ -252,7 +242,7 @@ def getfslineno(obj):
def findsource(obj):
try:
sourcelines, lineno = py.std.inspect.findsource(obj)
sourcelines, lineno = inspect.findsource(obj)
except py.builtin._sysex:
raise
except: # noqa
@@ -263,8 +253,8 @@ def findsource(obj):
def getsource(obj, **kwargs):
import _pytest._code
obj = _pytest._code.getrawcode(obj)
from .code import getrawcode
obj = getrawcode(obj)
try:
strsrc = inspect.getsource(obj)
except IndentationError:
@@ -290,8 +280,6 @@ def deindent(lines, offset=None):
def readline_generator(lines):
for line in lines:
yield line + '\n'
while True:
yield ''
it = readline_generator(lines)
@@ -322,9 +310,9 @@ def get_statement_startend2(lineno, node):
# AST's line numbers start indexing at 1
values = []
for x in ast.walk(node):
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
values.append(x.lineno - 1)
for name in "finalbody", "orelse":
for name in ("finalbody", "orelse"):
val = getattr(x, name, None)
if val:
# treat the finally/orelse part as its own statement
@@ -342,11 +330,8 @@ def get_statement_startend2(lineno, node):
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
if astnode is None:
content = str(source)
try:
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
except ValueError:
start, end = getstatementrange_old(lineno, source, assertion)
return None, start, end
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
start, end = get_statement_startend2(lineno, astnode)
# we need to correct the end:
# - ast-parsing strips comments
@@ -378,38 +363,3 @@ def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
else:
break
return astnode, start, end
def getstatementrange_old(lineno, source, assertion=False):
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
raise an IndexError if no such statementrange can be found.
"""
# XXX this logic is only used on python2.4 and below
# 1. find the start of the statement
from codeop import compile_command
for start in range(lineno, -1, -1):
if assertion:
line = source.lines[start]
# the following lines are not fully tested, change with care
if 'super' in line and 'self' in line and '__init__' in line:
raise IndexError("likely a subclass")
if "assert" not in line and "raise" not in line:
continue
trylines = source.lines[start:lineno + 1]
# quick hack to prepare parsing an indented line with
# compile_command() (which errors on "return" outside defs)
trylines.insert(0, 'def xxx():')
trysource = '\n '.join(trylines)
# ^ space here
try:
compile_command(trysource)
except (SyntaxError, OverflowError, ValueError):
continue
# 2. find the end of the statement
for end in range(lineno + 1, len(source) + 1):
trysource = source[start:end]
if trysource.isparseable():
return start, end
raise SyntaxError("no valid source range around line %d " % (lineno,))

View File

@@ -56,7 +56,7 @@ class DummyRewriteHook(object):
pass
class AssertionState:
class AssertionState(object):
"""State for the assertion plugin."""
def __init__(self, config, mode):

View File

@@ -1,7 +1,6 @@
"""Rewrite assertion AST to produce nice error messages"""
from __future__ import absolute_import, division, print_function
import ast
import _ast
import errno
import itertools
import imp
@@ -180,11 +179,13 @@ class AssertionRewritingHook(object):
The named module or package as well as any nested modules will
be rewritten on import.
"""
already_imported = set(names).intersection(set(sys.modules))
if already_imported:
for name in already_imported:
if name not in self._rewritten_names:
self._warn_already_imported(name)
already_imported = (set(names)
.intersection(sys.modules)
.difference(self._rewritten_names))
for name in already_imported:
if not AssertionRewriter.is_rewrite_disabled(
sys.modules[name].__doc__ or ""):
self._warn_already_imported(name)
self._must_rewrite.update(names)
def _warn_already_imported(self, name):
@@ -636,7 +637,8 @@ class AssertionRewriter(ast.NodeVisitor):
not isinstance(field, ast.expr)):
nodes.append(field)
def is_rewrite_disabled(self, docstring):
@staticmethod
def is_rewrite_disabled(docstring):
return "PYTEST_DONT_REWRITE" in docstring
def variable(self):
@@ -914,7 +916,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Compare(self, comp):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
left_expl = "({0})".format(left_expl)
res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
@@ -925,7 +927,7 @@ class AssertionRewriter(ast.NodeVisitor):
results = [left_res]
for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
next_expl = "({0})".format(next_expl)
results.append(next_res)
sym = binop_map[op.__class__]

View File

@@ -5,11 +5,7 @@ import pprint
import _pytest._code
import py
import six
try:
from collections import Sequence
except ImportError:
Sequence = list
from collections import Sequence
u = six.text_type
@@ -113,7 +109,7 @@ def assertrepr_compare(config, op, left, right):
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
def issequence(x):
return (isinstance(x, (list, tuple, Sequence)) and not isinstance(x, basestring))
return isinstance(x, Sequence) and not isinstance(x, basestring)
def istext(x):
return isinstance(x, basestring)

View File

@@ -5,7 +5,12 @@ the name cache was not chosen to ensure pluggy automatically
ignores the external pytest-cache
"""
from __future__ import absolute_import, division, print_function
from collections import OrderedDict
import py
import six
import pytest
import json
import os
@@ -17,7 +22,7 @@ class Cache(object):
self.config = config
self._cachedir = Cache.cache_dir_from_config(config)
self.trace = config.trace.root.get("cache")
if config.getvalue("cacheclear"):
if config.getoption("cacheclear"):
self.trace("clearing cachedir")
if self._cachedir.check():
self._cachedir.remove()
@@ -98,23 +103,25 @@ class Cache(object):
json.dump(value, f, indent=2, sort_keys=True)
class LFPlugin:
class LFPlugin(object):
""" Plugin which implements the --lf (run last-failing) option """
def __init__(self, config):
self.config = config
active_keys = 'lf', 'failedfirst'
self.active = any(config.getvalue(key) for key in active_keys)
self.active = any(config.getoption(key) for key in active_keys)
self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = None
self._no_failures_behavior = self.config.getoption('last_failed_no_failures')
def pytest_report_collectionfinish(self):
if self.active:
if not self._previously_failed_count:
mode = "run all (no recorded failures)"
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.getvalue("failedfirst") else ""
suffix = " first" if self.config.getoption(
"failedfirst") else ""
mode = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
)
@@ -138,28 +145,32 @@ class LFPlugin:
self.lastfailed[report.nodeid] = True
def pytest_collection_modifyitems(self, session, config, items):
if self.active and self.lastfailed:
previously_failed = []
previously_passed = []
for item in items:
if item.nodeid in self.lastfailed:
previously_failed.append(item)
if self.active:
if self.lastfailed:
previously_failed = []
previously_passed = []
for item in items:
if item.nodeid in self.lastfailed:
previously_failed.append(item)
else:
previously_passed.append(item)
self._previously_failed_count = len(previously_failed)
if not previously_failed:
# running a subset of all tests with recorded failures outside
# of the set of tests currently executing
return
if self.config.getoption("lf"):
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
else:
previously_passed.append(item)
self._previously_failed_count = len(previously_failed)
if not previously_failed:
# running a subset of all tests with recorded failures outside
# of the set of tests currently executing
return
if self.config.getvalue("lf"):
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
else:
items[:] = previously_failed + previously_passed
items[:] = previously_failed + previously_passed
elif self._no_failures_behavior == 'none':
config.hook.pytest_deselected(items=items)
items[:] = []
def pytest_sessionfinish(self, session):
config = self.config
if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
return
saved_lastfailed = config.cache.get("cache/lastfailed", {})
@@ -167,6 +178,39 @@ class LFPlugin:
config.cache.set("cache/lastfailed", self.lastfailed)
class NFPlugin(object):
""" Plugin which implements the --nf (run new-first) option """
def __init__(self, config):
self.config = config
self.active = config.option.newfirst
self.cached_nodeids = config.cache.get("cache/nodeids", [])
def pytest_collection_modifyitems(self, session, config, items):
if self.active:
new_items = OrderedDict()
other_items = OrderedDict()
for item in items:
if item.nodeid not in self.cached_nodeids:
new_items[item.nodeid] = item
else:
other_items[item.nodeid] = item
items[:] = self._get_increasing_order(six.itervalues(new_items)) + \
self._get_increasing_order(six.itervalues(other_items))
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
def _get_increasing_order(self, items):
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)
def pytest_sessionfinish(self, session):
config = self.config
if config.getoption("cacheshow") or hasattr(config, "slaveinput"):
return
config.cache.set("cache/nodeids", self.cached_nodeids)
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption(
@@ -178,6 +222,10 @@ def pytest_addoption(parser):
help="run all tests but run the last failures first. "
"This may re-order tests and thus lead to "
"repeated fixture setup/teardown")
group.addoption(
'--nf', '--new-first', action='store_true', dest="newfirst",
help="run tests from new files first, then the rest of the tests "
"sorted by file mtime")
group.addoption(
'--cache-show', action='store_true', dest="cacheshow",
help="show cache contents, don't perform collection or tests")
@@ -185,8 +233,14 @@ def pytest_addoption(parser):
'--cache-clear', action='store_true', dest="cacheclear",
help="remove all cache contents at start of test run.")
parser.addini(
"cache_dir", default='.cache',
"cache_dir", default='.pytest_cache',
help="cache directory path.")
group.addoption(
'--lfnf', '--last-failed-no-failures', action='store',
dest='last_failed_no_failures', choices=('all', 'none'), default='all',
help='change the behavior when no test failed in the last run or no '
'information about the last failures was found in the cache'
)
def pytest_cmdline_main(config):
@@ -199,6 +253,7 @@ def pytest_cmdline_main(config):
def pytest_configure(config):
config.cache = Cache(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")
@pytest.fixture

View File

@@ -61,7 +61,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
sys.stderr.write(err)
class CaptureManager:
class CaptureManager(object):
"""
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
test phase (setup, call, teardown). After each of those points, the captured output is obtained and
@@ -197,9 +197,9 @@ def _ensure_only_one_capture_fixture(request, name):
@pytest.fixture
def capsys(request):
"""Enable capturing of writes to sys.stdout/sys.stderr and make
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
objects.
"""
_ensure_only_one_capture_fixture(request, 'capsys')
@@ -209,7 +209,7 @@ def capsys(request):
@pytest.fixture
def capsysbinary(request):
"""Enable capturing of writes to sys.stdout/sys.stderr and make
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
objects.
@@ -225,7 +225,7 @@ def capsysbinary(request):
@pytest.fixture
def capfd(request):
"""Enable capturing of writes to file descriptors 1 and 2 and make
"""Enable capturing of writes to file descriptors ``1`` and ``2`` and make
captured output available via ``capfd.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
objects.
@@ -271,7 +271,11 @@ def _install_capture_fixture_on_item(request, capture_class):
del request.node._capture_fixture
class CaptureFixture:
class CaptureFixture(object):
"""
Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
fixtures.
"""
def __init__(self, captureclass, request):
self.captureclass = captureclass
self.request = request
@@ -288,6 +292,10 @@ class CaptureFixture:
cap.stop_capturing()
def readouterr(self):
"""Read and return the captured output so far, resetting the internal buffer.
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
"""
try:
return self._capture.readouterr()
except AttributeError:
@@ -295,6 +303,7 @@ class CaptureFixture:
@contextlib.contextmanager
def disabled(self):
"""Temporarily disables capture while inside the 'with' block."""
self._capture.suspend_capturing()
capmanager = self.request.config.pluginmanager.getplugin('capturemanager')
capmanager.suspend_global_capture(item=None, in_=False)
@@ -416,11 +425,11 @@ class MultiCapture(object):
self.err.snap() if self.err is not None else "")
class NoCapture:
class NoCapture(object):
__init__ = start = done = suspend = resume = lambda *args: None
class FDCaptureBinary:
class FDCaptureBinary(object):
"""Capture IO to/from a given os-level filedescriptor.
snap() produces `bytes`
@@ -476,7 +485,7 @@ class FDCaptureBinary:
os.dup2(targetfd_save, self.targetfd)
os.close(targetfd_save)
self.syscapture.done()
self.tmpfile.close()
_attempt_to_close_capture_file(self.tmpfile)
def suspend(self):
self.syscapture.suspend()
@@ -506,7 +515,7 @@ class FDCapture(FDCaptureBinary):
return res
class SysCapture:
class SysCapture(object):
def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
self._old = getattr(sys, name)
@@ -530,7 +539,7 @@ class SysCapture:
def done(self):
setattr(sys, self.name, self._old)
del self._old
self.tmpfile.close()
_attempt_to_close_capture_file(self.tmpfile)
def suspend(self):
setattr(sys, self.name, self._old)
@@ -551,7 +560,7 @@ class SysCaptureBinary(SysCapture):
return res
class DontReadFromInput:
class DontReadFromInput(six.Iterator):
"""Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured
so far sent to the screen. This should be configurable, though,
@@ -565,7 +574,10 @@ class DontReadFromInput:
raise IOError("reading from stdin while output is captured")
readline = read
readlines = read
__iter__ = read
__next__ = read
def __iter__(self):
return self
def fileno(self):
raise UnsupportedOperation("redirected stdin is pseudofile, "
@@ -681,3 +693,14 @@ def _py36_windowsconsoleio_workaround(stream):
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, 'rb')
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, 'wb')
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, 'wb')
def _attempt_to_close_capture_file(f):
"""Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
if six.PY2:
try:
f.close()
except IOError:
pass
else:
f.close()

View File

@@ -79,10 +79,11 @@ def num_mock_patch_args(function):
patchings = getattr(function, "patchings", None)
if not patchings:
return 0
mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
if mock is not None:
mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
if any(mock_modules):
sentinels = [m.DEFAULT for m in mock_modules if m is not None]
return len([p for p in patchings
if not p.attribute_name and p.new is mock.DEFAULT])
if not p.attribute_name and p.new in sentinels])
return len(patchings)

View File

@@ -5,12 +5,14 @@ import shlex
import traceback
import types
import warnings
import copy
import six
import py
# DON't import pytest here because it causes import cycle troubles
import sys
import os
from _pytest.outcomes import Skipped
import _pytest._code
import _pytest.hookspec # the extension point definitions
import _pytest.assertion
@@ -52,7 +54,7 @@ def main(args=None, plugins=None):
tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True)
tw.line("ERROR: could not load %s\n" % (e.path,), red=True)
return 4
else:
try:
@@ -60,12 +62,13 @@ def main(args=None, plugins=None):
finally:
config._ensure_unconfigure()
except UsageError as e:
tw = py.io.TerminalWriter(sys.stderr)
for msg in e.args:
sys.stderr.write("ERROR: %s\n" % (msg,))
tw.line("ERROR: {}\n".format(msg), red=True)
return 4
class cmdline: # compatibility namespace
class cmdline(object): # NOQA compatibility namespace
main = staticmethod(main)
@@ -168,7 +171,7 @@ class PytestPluginManager(PluginManager):
Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
functionality:
* loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
* loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
``pytest_plugins`` global variables found in plugins being loaded;
* ``conftest.py`` loading during start-up;
"""
@@ -198,6 +201,8 @@ class PytestPluginManager(PluginManager):
# Config._consider_importhook will set a real object if required.
self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
# Used to know when we are importing conftests after the pytest_configure stage
self._configured = False
def addhooks(self, module_or_class):
"""
@@ -273,6 +278,7 @@ class PytestPluginManager(PluginManager):
config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
self._configured = True
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
@@ -363,6 +369,9 @@ class PytestPluginManager(PluginManager):
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
if hasattr(mod, 'pytest_plugins') and self._configured:
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
warnings.warn(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST)
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
@@ -434,10 +443,7 @@ class PytestPluginManager(PluginManager):
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
except Exception as e:
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
except Skipped as e:
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
else:
mod = sys.modules[importspec]
@@ -462,7 +468,7 @@ def _get_plugin_specs_as_list(specs):
return []
class Parser:
class Parser(object):
""" Parser for command line arguments and ini-file values.
:ivar extra_info: dict of generic param -> value to display in case
@@ -597,7 +603,7 @@ class ArgumentError(Exception):
return self.msg
class Argument:
class Argument(object):
"""class that mimics the necessary behaviour of optparse.Option
its currently a least effort implementation
@@ -727,7 +733,7 @@ class Argument:
return 'Argument({0})'.format(', '.join(args))
class OptionGroup:
class OptionGroup(object):
def __init__(self, name, description="", parser=None):
self.name = name
self.description = description
@@ -845,20 +851,7 @@ def _ensure_removed_sysmodule(modname):
pass
class CmdOptions(object):
""" holds cmdline options as attributes."""
def __init__(self, values=()):
self.__dict__.update(values)
def __repr__(self):
return "<CmdOptions %r>" % (self.__dict__,)
def copy(self):
return CmdOptions(self.__dict__)
class Notset:
class Notset(object):
def __repr__(self):
return "<NOTSET>"
@@ -885,7 +878,7 @@ class Config(object):
def __init__(self, pluginmanager):
#: access to command line option as attributes.
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
self.option = CmdOptions()
self.option = argparse.Namespace()
_a = FILE_OR_DIR
self._parser = Parser(
usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
@@ -989,8 +982,9 @@ class Config(object):
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args):
ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn)
ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=copy.copy(self.option))
r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn,
rootdir_cmd_arg=ns.rootdir or None)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info['rootdir'] = self.rootdir
self._parser.extra_info['inifile'] = self.inifile
@@ -1015,7 +1009,7 @@ class Config(object):
mode = 'plain'
else:
self._mark_plugins_for_rewrite(hook)
self._warn_about_missing_assertion(mode)
_warn_about_missing_assertion(mode)
def _mark_plugins_for_rewrite(self, hook):
"""
@@ -1042,23 +1036,6 @@ class Config(object):
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _warn_about_missing_assertion(self, mode):
try:
assert False
except AssertionError:
pass
else:
if mode == 'plain':
sys.stderr.write("WARNING: ASSERTIONS ARE NOT EXECUTED"
" and FAILING TESTS WILL PASS. Are you"
" using python -O?")
else:
sys.stderr.write("WARNING: assertions not in test modules or"
" plugins will be ignored"
" because assert statements are not executed "
"by the underlying Python interpreter "
"(are you using python -O?)\n")
def _preparse(self, args, addopts=True):
if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
@@ -1070,7 +1047,8 @@ class Config(object):
self.pluginmanager.consider_preparse(args)
self.pluginmanager.load_setuptools_entrypoints('pytest11')
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
self.known_args_namespace = ns = self._parser.parse_known_args(
args, namespace=copy.copy(self.option))
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir
@@ -1187,16 +1165,15 @@ class Config(object):
def _get_override_ini_value(self, name):
value = None
# override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and
# and -o foo1=bar1 -o foo2=bar2 options
# always use the last item if multiple value set for same ini-name,
# override_ini is a list of "ini=value" options
# always use the last item if multiple values are set for same ini-name,
# e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
for ini_config_list in self._override_ini:
for ini_config in ini_config_list:
try:
(key, user_ini_value) = ini_config.split("=", 1)
except ValueError:
raise UsageError("-o/--override-ini expects option=value style.")
for ini_config in self._override_ini:
try:
key, user_ini_value = ini_config.split("=", 1)
except ValueError:
raise UsageError("-o/--override-ini expects option=value style.")
else:
if key == name:
value = user_ini_value
return value
@@ -1233,6 +1210,29 @@ class Config(object):
return self.getoption(name, skip=True)
def _assertion_supported():
try:
assert False
except AssertionError:
return True
else:
return False
def _warn_about_missing_assertion(mode):
if not _assertion_supported():
if mode == 'plain':
sys.stderr.write("WARNING: ASSERTIONS ARE NOT EXECUTED"
" and FAILING TESTS WILL PASS. Are you"
" using python -O?")
else:
sys.stderr.write("WARNING: assertions not in test modules or"
" plugins will be ignored"
" because assert statements are not executed "
"by the underlying Python interpreter "
"(are you using python -O?)\n")
def exists(path, ignore=EnvironmentError):
try:
return path.check()
@@ -1250,7 +1250,7 @@ def getcfg(args, warnfunc=None):
This parameter should be removed when pytest
adopts standard deprecation warnings (#1804).
"""
from _pytest.deprecated import SETUP_CFG_PYTEST
from _pytest.deprecated import CFG_PYTEST_SECTION
inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"]
args = [x for x in args if not str(x).startswith("-")]
if not args:
@@ -1264,7 +1264,7 @@ def getcfg(args, warnfunc=None):
iniconfig = py.iniconfig.IniConfig(p)
if 'pytest' in iniconfig.sections:
if inibasename == 'setup.cfg' and warnfunc:
warnfunc('C1', SETUP_CFG_PYTEST)
warnfunc('C1', CFG_PYTEST_SECTION.format(filename=inibasename))
return base, p, iniconfig['pytest']
if inibasename == 'setup.cfg' and 'tool:pytest' in iniconfig.sections:
return base, p, iniconfig['tool:pytest']
@@ -1323,14 +1323,22 @@ def get_dirs_from_args(args):
]
def determine_setup(inifile, args, warnfunc=None):
def determine_setup(inifile, args, warnfunc=None, rootdir_cmd_arg=None):
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
try:
inicfg = iniconfig["pytest"]
except KeyError:
inicfg = None
is_cfg_file = str(inifile).endswith('.cfg')
# TODO: [pytest] section in *.cfg files is depricated. Need refactoring.
sections = ['tool:pytest', 'pytest'] if is_cfg_file else ['pytest']
for section in sections:
try:
inicfg = iniconfig[section]
if is_cfg_file and section == 'pytest' and warnfunc:
from _pytest.deprecated import CFG_PYTEST_SECTION
warnfunc('C1', CFG_PYTEST_SECTION.format(filename=str(inifile)))
break
except KeyError:
inicfg = None
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
@@ -1346,6 +1354,11 @@ def determine_setup(inifile, args, warnfunc=None):
is_fs_root = os.path.splitdrive(str(rootdir))[1] == '/'
if is_fs_root:
rootdir = ancestor
if rootdir_cmd_arg:
rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg))
if not os.path.isdir(str(rootdir_abs_path)):
raise UsageError("Directory '{}' not found. Check your '--rootdir' option.".format(rootdir_abs_path))
rootdir = rootdir_abs_path
return rootdir, inifile, inicfg or {}

View File

@@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function
import pdb
import sys
from doctest import UnexpectedException
def pytest_addoption(parser):
@@ -40,7 +41,7 @@ def pytest_configure(config):
config._cleanup.append(fin)
class pytestPDB:
class pytestPDB(object):
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None
_config = None
@@ -62,7 +63,7 @@ class pytestPDB:
cls._pdb_cls().set_trace(frame)
class PdbInvoke:
class PdbInvoke(object):
def pytest_exception_interact(self, node, call, report):
capman = node.config.pluginmanager.getplugin("capturemanager")
if capman:
@@ -85,6 +86,18 @@ def _enter_pdb(node, excinfo, rep):
# for not completely clear reasons.
tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
tw.line()
showcapture = node.config.option.showcapture
for sectionname, content in (('stdout', rep.capstdout),
('stderr', rep.capstderr),
('log', rep.caplog)):
if showcapture in (sectionname, 'all') and content:
tw.sep(">", "captured " + sectionname)
if content[-1:] == "\n":
content = content[:-1]
tw.line(content)
tw.sep(">", "traceback")
rep.toterminal(tw)
tw.sep(">", "entering PDB")
@@ -95,10 +108,9 @@ def _enter_pdb(node, excinfo, rep):
def _postmortem_traceback(excinfo):
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:
from doctest import UnexpectedException
if isinstance(excinfo.value, UnexpectedException):
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:
return excinfo.value.exc_info[2]
else:
return excinfo._excinfo[2]

View File

@@ -22,7 +22,7 @@ FUNCARG_PREFIX = (
'and scheduled to be removed in pytest 4.0. '
'Please remove the prefix and use the @pytest.fixture decorator instead.')
SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.'
CFG_PYTEST_SECTION = '[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.'
GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
@@ -41,6 +41,12 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)
RECORD_XML_PROPERTY = (
'Fixture renamed from "record_xml_property" to "record_property" as user '
'properties are now available to all reporters.\n'
'"record_xml_property" is now deprecated.'
)
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
"pycollector makeitem was removed "
"as it is an accidentially leaked internal api"
@@ -50,3 +56,9 @@ METAFUNC_ADD_CALL = (
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
"Please use Metafunc.parametrize instead."
)
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
"Defining pytest_plugins in a non-top-level conftest is deprecated, "
"because it affects the entire directory tree in a non-explicit way.\n"
"Please move it to the top level conftest file instead."
)

View File

@@ -2,6 +2,8 @@
from __future__ import absolute_import, division, print_function
import traceback
import sys
import platform
import pytest
from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
@@ -22,6 +24,9 @@ DOCTEST_REPORT_CHOICES = (
DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
)
# Lazy definiton of runner class
RUNNER_CLASS = None
def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests',
@@ -45,6 +50,10 @@ def pytest_addoption(parser):
action="store_true", default=False,
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
group.addoption("--doctest-continue-on-failure",
action="store_true", default=False,
help="for a given doctest, continue to run after the first failure",
dest="doctest_continue_on_failure")
def pytest_collect_file(path, parent):
@@ -75,14 +84,63 @@ def _is_doctest(config, path, parent):
class ReprFailDoctest(TerminalRepr):
def __init__(self, reprlocation, lines):
self.reprlocation = reprlocation
self.lines = lines
def __init__(self, reprlocation_lines):
# List of (reprlocation, lines) tuples
self.reprlocation_lines = reprlocation_lines
def toterminal(self, tw):
for line in self.lines:
tw.line(line)
self.reprlocation.toterminal(tw)
for reprlocation, lines in self.reprlocation_lines:
for line in lines:
tw.line(line)
reprlocation.toterminal(tw)
class MultipleDoctestFailures(Exception):
def __init__(self, failures):
super(MultipleDoctestFailures, self).__init__()
self.failures = failures
def _init_runner_class():
import doctest
class PytestDoctestRunner(doctest.DebugRunner):
"""
Runner to collect failures. Note that the out variable in this case is
a list instead of a stdout-like object
"""
def __init__(self, checker=None, verbose=None, optionflags=0,
continue_on_failure=True):
doctest.DebugRunner.__init__(
self, checker=checker, verbose=verbose, optionflags=optionflags)
self.continue_on_failure = continue_on_failure
def report_failure(self, out, test, example, got):
failure = doctest.DocTestFailure(test, example, got)
if self.continue_on_failure:
out.append(failure)
else:
raise failure
def report_unexpected_exception(self, out, test, example, exc_info):
failure = doctest.UnexpectedException(test, example, exc_info)
if self.continue_on_failure:
out.append(failure)
else:
raise failure
return PytestDoctestRunner
def _get_runner(checker=None, verbose=None, optionflags=0,
continue_on_failure=True):
# We need this in order to do a lazy import on doctest
global RUNNER_CLASS
if RUNNER_CLASS is None:
RUNNER_CLASS = _init_runner_class()
return RUNNER_CLASS(
checker=checker, verbose=verbose, optionflags=optionflags,
continue_on_failure=continue_on_failure)
class DoctestItem(pytest.Item):
@@ -103,46 +161,71 @@ class DoctestItem(pytest.Item):
def runtest(self):
_check_all_skipped(self.dtest)
self.runner.run(self.dtest)
self._disable_output_capturing_for_darwin()
failures = []
self.runner.run(self.dtest, out=failures)
if failures:
raise MultipleDoctestFailures(failures)
def _disable_output_capturing_for_darwin(self):
"""
Disable output capturing. Otherwise, stdout is lost to doctest (#985)
"""
if platform.system() != 'Darwin':
return
capman = self.config.pluginmanager.getplugin("capturemanager")
if capman:
out, err = capman.suspend_global_capture(in_=True)
sys.stdout.write(out)
sys.stderr.write(err)
def repr_failure(self, excinfo):
import doctest
failures = None
if excinfo.errisinstance((doctest.DocTestFailure,
doctest.UnexpectedException)):
doctestfailure = excinfo.value
example = doctestfailure.example
test = doctestfailure.test
filename = test.filename
if test.lineno is None:
lineno = None
else:
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = _get_checker()
report_choice = _get_report_choice(self.config.getoption("doctestreport"))
if lineno is not None:
lines = doctestfailure.test.docstring.splitlines(False)
# add line numbers to the left of the error message
lines = ["%03d %s" % (i + test.lineno + 1, x)
for (i, x) in enumerate(lines)]
# trim docstring error lines to 10
lines = lines[max(example.lineno - 9, 0):example.lineno + 1]
else:
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
indent = '>>>'
for line in example.source.splitlines():
lines.append('??? %s %s' % (indent, line))
indent = '...'
if excinfo.errisinstance(doctest.DocTestFailure):
lines += checker.output_difference(example,
doctestfailure.got, report_choice).split("\n")
else:
inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
lines += traceback.format_exception(*excinfo.value.exc_info)
return ReprFailDoctest(reprlocation, lines)
failures = [excinfo.value]
elif excinfo.errisinstance(MultipleDoctestFailures):
failures = excinfo.value.failures
if failures is not None:
reprlocation_lines = []
for failure in failures:
example = failure.example
test = failure.test
filename = test.filename
if test.lineno is None:
lineno = None
else:
lineno = test.lineno + example.lineno + 1
message = type(failure).__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = _get_checker()
report_choice = _get_report_choice(self.config.getoption("doctestreport"))
if lineno is not None:
lines = failure.test.docstring.splitlines(False)
# add line numbers to the left of the error message
lines = ["%03d %s" % (i + test.lineno + 1, x)
for (i, x) in enumerate(lines)]
# trim docstring error lines to 10
lines = lines[max(example.lineno - 9, 0):example.lineno + 1]
else:
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
indent = '>>>'
for line in example.source.splitlines():
lines.append('??? %s %s' % (indent, line))
indent = '...'
if isinstance(failure, doctest.DocTestFailure):
lines += checker.output_difference(example,
failure.got,
report_choice).split("\n")
else:
inner_excinfo = ExceptionInfo(failure.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
lines += traceback.format_exception(*failure.exc_info)
reprlocation_lines.append((reprlocation, lines))
return ReprFailDoctest(reprlocation_lines)
else:
return super(DoctestItem, self).repr_failure(excinfo)
@@ -172,6 +255,16 @@ def get_optionflags(parent):
return flag_acc
def _get_continue_on_failure(config):
continue_on_failure = config.getvalue('doctest_continue_on_failure')
if continue_on_failure:
# We need to turn off this if we use pdb since we should stop at
# the first failure
if config.getvalue("usepdb"):
continue_on_failure = False
return continue_on_failure
class DoctestTextfile(pytest.Module):
obj = None
@@ -187,8 +280,11 @@ class DoctestTextfile(pytest.Module):
globs = {'__name__': '__main__'}
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
runner = _get_runner(
verbose=0, optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config))
_fix_spoof_python2(runner, encoding)
parser = doctest.DocTestParser()
@@ -223,8 +319,10 @@ class DoctestModule(pytest.Module):
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
runner = _get_runner(
verbose=0, optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config))
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
@@ -364,6 +462,6 @@ def _fix_spoof_python2(runner, encoding):
@pytest.fixture(scope='session')
def doctest_namespace():
"""
Inject names into the doctest namespace.
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
"""
return dict()

View File

@@ -4,7 +4,7 @@ import functools
import inspect
import sys
import warnings
from collections import OrderedDict
from collections import OrderedDict, deque, defaultdict
import attr
import py
@@ -24,13 +24,20 @@ from _pytest.compat import (
from _pytest.outcomes import fail, TEST_OUTCOME
@attr.s(frozen=True)
class PseudoFixtureDef(object):
cached_result = attr.ib()
scope = attr.ib()
def pytest_sessionstart(session):
import _pytest.python
import _pytest.nodes
scopename2class.update({
'class': _pytest.python.Class,
'module': _pytest.python.Module,
'function': _pytest.main.Item,
'function': _pytest.nodes.Item,
'session': _pytest.main.Session,
})
session._fixturemanager = FixtureManager(session)
@@ -162,62 +169,59 @@ def get_parametrized_fixture_keys(item, scopenum):
def reorder_items(items):
argkeys_cache = {}
items_by_argkey = {}
for scopenum in range(0, scopenum_function):
argkeys_cache[scopenum] = d = {}
items_by_argkey[scopenum] = item_d = defaultdict(deque)
for item in items:
keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
if keys:
d[item] = keys
return reorder_items_atscope(items, set(), argkeys_cache, 0)
for key in keys:
item_d[key].append(item)
items = OrderedDict.fromkeys(items)
return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0))
def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
def fix_cache_order(item, argkeys_cache, items_by_argkey):
for scopenum in range(0, scopenum_function):
for key in argkeys_cache[scopenum].get(item, []):
items_by_argkey[scopenum][key].appendleft(item)
def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum):
if scopenum >= scopenum_function or len(items) < 3:
return items
items_done = []
while 1:
items_before, items_same, items_other, newignore = \
slice_items(items, ignore, argkeys_cache[scopenum])
items_before = reorder_items_atscope(
items_before, ignore, argkeys_cache, scopenum + 1)
if items_same is None:
# nothing to reorder in this scope
assert items_other is None
return items_done + items_before
items_done.extend(items_before)
items = items_same + items_other
ignore = newignore
def slice_items(items, ignore, scoped_argkeys_cache):
# we pick the first item which uses a fixture instance in the
# requested scope and which we haven't seen yet. We slice the input
# items list into a list of items_nomatch, items_same and
# items_other
if scoped_argkeys_cache: # do we need to do work at all?
it = iter(items)
# first find a slicing key
for i, item in enumerate(it):
argkeys = scoped_argkeys_cache.get(item)
if argkeys is not None:
newargkeys = OrderedDict.fromkeys(k for k in argkeys if k not in ignore)
if newargkeys: # found a slicing key
slicing_argkey, _ = newargkeys.popitem()
items_before = items[:i]
items_same = [item]
items_other = []
# now slice the remainder of the list
for item in it:
argkeys = scoped_argkeys_cache.get(item)
if argkeys and slicing_argkey in argkeys and \
slicing_argkey not in ignore:
items_same.append(item)
else:
items_other.append(item)
newignore = ignore.copy()
newignore.add(slicing_argkey)
return (items_before, items_same, items_other, newignore)
return items, None, None, None
ignore = set()
items_deque = deque(items)
items_done = OrderedDict()
scoped_items_by_argkey = items_by_argkey[scopenum]
scoped_argkeys_cache = argkeys_cache[scopenum]
while items_deque:
no_argkey_group = OrderedDict()
slicing_argkey = None
while items_deque:
item = items_deque.popleft()
if item in items_done or item in no_argkey_group:
continue
argkeys = OrderedDict.fromkeys(k for k in scoped_argkeys_cache.get(item, []) if k not in ignore)
if not argkeys:
no_argkey_group[item] = None
else:
slicing_argkey, _ = argkeys.popitem()
# we don't have to remove relevant items from later in the deque because they'll just be ignored
matching_items = [i for i in scoped_items_by_argkey[slicing_argkey] if i in items]
for i in reversed(matching_items):
fix_cache_order(i, argkeys_cache, items_by_argkey)
items_deque.appendleft(i)
break
if no_argkey_group:
no_argkey_group = reorder_items_atscope(
no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1)
for item in no_argkey_group:
items_done[item] = None
ignore.add(slicing_argkey)
return items_done
def fillfixtures(function):
@@ -246,7 +250,7 @@ def get_direct_param_fixture_func(request):
return request.param
class FuncFixtureInfo:
class FuncFixtureInfo(object):
def __init__(self, argnames, names_closure, name2fixturedefs):
self.argnames = argnames
self.names_closure = names_closure
@@ -267,7 +271,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
self.fixturename = None
#: Scope string, one of "function", "class", "module", "session"
self.scope = "function"
self._fixture_values = {} # argname -> fixture value
self._fixture_defs = {} # argname -> FixtureDef
fixtureinfo = pyfuncitem._fixtureinfo
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
@@ -443,15 +446,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
fixturedef = self._getnextfixturedef(argname)
except FixtureLookupError:
if argname == "request":
class PseudoFixtureDef:
cached_result = (self, [0], None)
scope = "function"
return PseudoFixtureDef
cached_result = (self, [0], None)
scope = "function"
return PseudoFixtureDef(cached_result, scope)
raise
# remove indent to prevent the python3 exception
# from leaking into the call
result = self._getfixturevalue(fixturedef)
self._fixture_values[argname] = result
self._compute_fixture_value(fixturedef)
self._fixture_defs[argname] = fixturedef
return fixturedef
@@ -466,7 +467,14 @@ class FixtureRequest(FuncargnamesCompatAttr):
values.append(fixturedef)
current = current._parent_request
def _getfixturevalue(self, fixturedef):
def _compute_fixture_value(self, fixturedef):
"""
Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will
force the FixtureDef object to throw away any previous results and compute a new fixture value, which
will be stored into the FixtureDef object itself.
:param FixtureDef fixturedef:
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
argname = fixturedef.argname
@@ -515,12 +523,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
exc_clear()
try:
# call the fixture function
val = fixturedef.execute(request=subrequest)
fixturedef.execute(request=subrequest)
finally:
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
subrequest.node)
return val
def _check_scope(self, argname, invoking_scope, requested_scope):
if argname == "request":
@@ -573,7 +580,6 @@ class SubRequest(FixtureRequest):
self.scope = scope
self._fixturedef = fixturedef
self._pyfuncitem = request._pyfuncitem
self._fixture_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index
@@ -715,7 +721,7 @@ def call_fixture_func(fixturefunc, request, kwargs):
return res
class FixtureDef:
class FixtureDef(object):
""" A container for a factory definition. """
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
@@ -838,9 +844,9 @@ def _ensure_immutable_ids(ids):
@attr.s(frozen=True)
class FixtureFunctionMarker(object):
scope = attr.ib()
params = attr.ib(convert=attr.converters.optional(tuple))
params = attr.ib(converter=attr.converters.optional(tuple))
autouse = attr.ib(default=False)
ids = attr.ib(default=None, convert=_ensure_immutable_ids)
ids = attr.ib(default=None, converter=_ensure_immutable_ids)
name = attr.ib(default=None)
def __call__(self, function):
@@ -852,7 +858,7 @@ class FixtureFunctionMarker(object):
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
""" (return a) decorator to mark a fixture factory function.
"""Decorator to mark a fixture factory function.
This decorator can be used (with or without parameters) to define a
fixture function. The name of the fixture function can later be
@@ -917,11 +923,19 @@ defaultfuncargprefixmarker = fixture()
@fixture(scope="session")
def pytestconfig(request):
""" the pytest config object with access to command line opts."""
"""Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose"):
...
"""
return request.config
class FixtureManager:
class FixtureManager(object):
"""
pytest fixtures definitions and information is stored and managed
from this class.
@@ -1007,9 +1021,6 @@ class FixtureManager:
if nextchar and nextchar not in ":/":
continue
autousenames.extend(basenames)
# make sure autousenames are sorted by scope, scopenum 0 is session
autousenames.sort(
key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
return autousenames
def getfixtureclosure(self, fixturenames, parentnode):
@@ -1040,6 +1051,16 @@ class FixtureManager:
if fixturedefs:
arg2fixturedefs[argname] = fixturedefs
merge(fixturedefs[-1].argnames)
def sort_by_scope(arg_name):
try:
fixturedefs = arg2fixturedefs[arg_name]
except KeyError:
return scopes.index('function')
else:
return fixturedefs[-1].scopenum
fixturenames_closure.sort(key=sort_by_scope)
return fixturenames_closure, arg2fixturedefs
def pytest_generate_tests(self, metafunc):

View File

@@ -57,9 +57,9 @@ def pytest_addoption(parser):
action="store_true", dest="debug", default=False,
help="store internal tracing debug information in 'pytestdebug.log'.")
group._addoption(
'-o', '--override-ini', nargs='*', dest="override_ini",
'-o', '--override-ini', dest="override_ini",
action="append",
help="override config option with option=value style, e.g. `-o xfail_strict=True`.")
help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.')
@pytest.hookimpl(hookwrapper=True)

View File

@@ -12,22 +12,40 @@ hookspec = HookspecMarker("pytest")
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to
pluginmanager.add_hookspecs(module_or_class, prefix)."""
``pluginmanager.add_hookspecs(module_or_class, prefix)``.
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
.. note::
This hook is incompatible with ``hookwrapper=True``.
"""
@hookspec(historic=True)
def pytest_namespace():
"""
DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged
(**Deprecated**) this hook causes direct monkeypatching on pytest, its use is strongly discouraged
return dict of name->object to be made globally available in
the pytest namespace. This hook is called at plugin registration
time.
the pytest namespace.
This hook is called at plugin registration time.
.. note::
This hook is incompatible with ``hookwrapper=True``.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
""" a new pytest plugin got registered.
:param plugin: the plugin module or instance
:param _pytest.config.PytestPluginManager manager: pytest plugin manager
.. note::
This hook is incompatible with ``hookwrapper=True``.
"""
@hookspec(historic=True)
@@ -41,7 +59,7 @@ def pytest_addoption(parser):
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
:arg parser: To add command line options, call
:arg _pytest.config.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.Parser.addini>`.
@@ -56,8 +74,10 @@ def pytest_addoption(parser):
a value read from an ini-style file.
The config object is passed around on many internal objects via the ``.config``
attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
via (deprecated) ``pytest.config``.
attribute or can be retrieved as the ``pytestconfig`` fixture.
.. note::
This hook is incompatible with ``hookwrapper=True``.
"""
@@ -72,14 +92,15 @@ def pytest_configure(config):
After that, the hook is called for other conftest files as they are
imported.
:arg config: pytest config object
:type config: _pytest.config.Config
.. note::
This hook is incompatible with ``hookwrapper=True``.
:arg _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
# Bootstrapping hooks called for plugins registered early enough:
# internal and 3rd party plugins as well as directly
# discoverable conftest.py local plugins.
# internal and 3rd party plugins.
# -------------------------------------------------------------------------
@@ -87,11 +108,28 @@ def pytest_configure(config):
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
:param list[str] args: list of arguments passed on the command line
"""
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
"""(**Deprecated**) modify command line arguments before option parsing.
This hook is considered deprecated and will be removed in a future pytest version. Consider
using :func:`pytest_load_initial_conftests` instead.
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
:param _pytest.config.Config config: pytest config object
:param list[str] args: list of arguments passed on the command line
"""
@hookspec(firstresult=True)
@@ -99,12 +137,26 @@ def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult` """
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
"""
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
of command line option parsing.
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
:param _pytest.config.Config early_config: pytest config object
:param list[str] args: list of arguments passed on the command line
:param _pytest.config.Parser parser: to add command line options
"""
# -------------------------------------------------------------------------
@@ -113,18 +165,29 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session.
"""Perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
the items in-place.
:param _pytest.main.Session session: the pytest session object
:param _pytest.config.Config config: pytest config object
:param List[_pytest.nodes.Item] items: list of item objects
"""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
""" called after collection has been performed and modified.
:param _pytest.main.Session session: the pytest session object
"""
@hookspec(firstresult=True)
@@ -134,6 +197,9 @@ def pytest_ignore_collect(path, config):
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
:param _pytest.config.Config config: pytest config object
"""
@@ -141,12 +207,18 @@ def pytest_ignore_collect(path, config):
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
"""
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
needs to have the specified ``parent`` as a parent.
:param str path: the path to collect
"""
# logging hooks for collection
@@ -212,7 +284,12 @@ def pytest_make_parametrize_id(config, val, argname):
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
The parameter name is available as ``argname``, if required.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
:param val: the parametrized value
:param str argname: the automatic parameter name produced by pytest
"""
# -------------------------------------------------------------------------
# generic runtest related hooks
@@ -224,11 +301,14 @@ def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished).
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.main.Session session: the pytest session object
"""
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
"""(**Deprecated**) use pytest_runtest_logstart. """
@hookspec(firstresult=True)
@@ -250,7 +330,25 @@ def pytest_runtest_protocol(item, nextitem):
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
""" signal the start of running a single test item.
This hook will be called **before** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
:func:`pytest_runtest_teardown` hooks.
:param str nodeid: full id of the item
:param location: a triple of ``(filename, linenum, testname)``
"""
def pytest_runtest_logfinish(nodeid, location):
""" signal the complete finish of running a single test item.
This hook will be called **after** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and
:func:`pytest_runtest_teardown` hooks.
:param str nodeid: full id of the item
:param location: a triple of ``(filename, linenum, testname)``
"""
def pytest_runtest_setup(item):
@@ -293,7 +391,15 @@ def pytest_runtest_logreport(report):
def pytest_fixture_setup(fixturedef, request):
""" performs fixture setup execution.
Stops at first non-None result, see :ref:`firstresult` """
:return: The return value of the call to the fixture function
Stops at first non-None result, see :ref:`firstresult`
.. note::
If the fixture function returns None, other implementations of
this hook function will continue to be called, according to the
behavior of the :ref:`firstresult` option.
"""
def pytest_fixture_post_finalizer(fixturedef, request):
@@ -307,15 +413,25 @@ def pytest_fixture_post_finalizer(fixturedef, request):
def pytest_sessionstart(session):
""" before session.main() is called. """
""" before session.main() is called.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
""" whole test run finishes.
:param _pytest.main.Session session: the pytest session object
:param int exitstatus: the status which pytest will return to the system
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
""" called before test process is exited.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -329,6 +445,8 @@ def pytest_assertrepr_compare(config, op, left, right):
of strings. The strings will be joined by newlines but any newlines
*in* a string will be escaped. Note that all but the first line will
be indented slightly, the intention is for the first line to be a summary.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -339,7 +457,7 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir):
""" return a string or list of strings to be displayed as header info for terminal reporting.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
.. note::
@@ -358,7 +476,7 @@ def pytest_report_collectionfinish(config, startdir, items):
This strings will be displayed after the standard "collected X items" message.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
:param items: list of pytest items that are going to be executed; this list should not be modified.
"""
@@ -372,14 +490,25 @@ def pytest_report_teststatus(report):
def pytest_terminal_summary(terminalreporter, exitstatus):
""" add additional section in terminal summary reporting. """
"""Add a section to terminal summary reporting.
:param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object
:param int exitstatus: the exit status that will be reported back to the OS
.. versionadded:: 3.5
The ``config`` parameter.
"""
@hookspec(historic=True)
def pytest_logwarning(message, code, nodeid, fslocation):
""" 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 partilar node/location)."""
if the warning is not tied to a particular node/location).
.. note::
This hook is incompatible with ``hookwrapper=True``.
"""
# -------------------------------------------------------------------------
# doctest hooks
@@ -418,6 +547,5 @@ def pytest_enter_pdb(config):
""" called upon pdb.set_trace(), can be used by plugins to take special
action just before the python debugger enters in interactive mode.
:arg config: pytest config object
:type config: _pytest.config.Config
:param _pytest.config.Config config: pytest config object
"""

View File

@@ -85,6 +85,9 @@ class _NodeReporter(object):
def add_property(self, name, value):
self.properties.append((str(name), bin_xml_escape(value)))
def add_attribute(self, name, value):
self.attrs[str(name)] = bin_xml_escape(value)
def make_properties_node(self):
"""Return a Junit node containing custom properties, if any.
"""
@@ -98,6 +101,7 @@ class _NodeReporter(object):
def record_testreport(self, testreport):
assert not self.testcase
names = mangle_test_address(testreport.nodeid)
existing_attrs = self.attrs
classnames = names[:-1]
if self.xml.prefix:
classnames.insert(0, self.xml.prefix)
@@ -111,6 +115,7 @@ class _NodeReporter(object):
if hasattr(testreport, "url"):
attrs["url"] = testreport.url
self.attrs = attrs
self.attrs.update(existing_attrs) # restore any user-defined attributes
def to_xml(self):
testcase = Junit.testcase(time=self.duration, **self.attrs)
@@ -125,10 +130,47 @@ class _NodeReporter(object):
self.append(node)
def write_captured_output(self, report):
for capname in ('out', 'err'):
content = getattr(report, 'capstd' + capname)
content_out = report.capstdout
content_log = report.caplog
content_err = report.capstderr
if content_log or content_out:
if content_log and self.xml.logging == 'system-out':
if content_out:
# syncing stdout and the log-output is not done yet. It's
# probably not worth the effort. Therefore, first the captured
# stdout is shown and then the captured logs.
content = '\n'.join([
' Captured Stdout '.center(80, '-'),
content_out,
'',
' Captured Log '.center(80, '-'),
content_log])
else:
content = content_log
else:
content = content_out
if content:
tag = getattr(Junit, 'system-' + capname)
tag = getattr(Junit, 'system-out')
self.append(tag(bin_xml_escape(content)))
if content_log or content_err:
if content_log and self.xml.logging == 'system-err':
if content_err:
content = '\n'.join([
' Captured Stderr '.center(80, '-'),
content_err,
'',
' Captured Log '.center(80, '-'),
content_log])
else:
content = content_log
else:
content = content_err
if content:
tag = getattr(Junit, 'system-err')
self.append(tag(bin_xml_escape(content)))
def append_pass(self, report):
@@ -191,24 +233,61 @@ class _NodeReporter(object):
@pytest.fixture
def record_xml_property(request):
"""Add extra xml properties to the tag for the calling test.
def record_property(request):
"""Add an extra properties the calling test.
User properties become part of the test report and are available to the
configured reporters, like JUnit XML.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded.
Example::
def test_function(record_property):
record_property("example_key", 1)
"""
request.node.warn(
code='C3',
message='record_xml_property is an experimental feature',
message='record_property is an experimental feature',
)
def append_property(name, value):
request.node.user_properties.append((name, value))
return append_property
@pytest.fixture
def record_xml_property(record_property):
"""(Deprecated) use record_property."""
import warnings
from _pytest import deprecated
warnings.warn(
deprecated.RECORD_XML_PROPERTY,
DeprecationWarning,
stacklevel=2
)
return record_property
@pytest.fixture
def record_xml_attribute(request):
"""Add extra xml attributes to the tag for the calling test.
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',
)
xml = getattr(request.config, "_xml", None)
if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)
return node_reporter.add_property
return node_reporter.add_attribute
else:
def add_property_noop(name, value):
def add_attr_noop(name, value):
pass
return add_property_noop
return add_attr_noop
def pytest_addoption(parser):
@@ -228,13 +307,18 @@ def pytest_addoption(parser):
default=None,
help="prepend prefix to classnames in junit-xml output")
parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest")
parser.addini("junit_logging", "Write captured log messages to JUnit report: "
"one of no|system-out|system-err",
default="no") # choices=['no', 'stdout', 'stderr'])
def pytest_configure(config):
xmlpath = config.option.xmlpath
# prevent opening xmllog on slave nodes (xdist)
if xmlpath and not hasattr(config, 'slaveinput'):
config._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name"))
config._xml = LogXML(xmlpath, config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"))
config.pluginmanager.register(config._xml)
@@ -261,11 +345,12 @@ def mangle_test_address(address):
class LogXML(object):
def __init__(self, logfile, prefix, suite_name="pytest"):
def __init__(self, logfile, prefix, suite_name="pytest", logging="no"):
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix
self.suite_name = suite_name
self.logging = logging
self.stats = dict.fromkeys([
'error',
'passed',
@@ -373,6 +458,10 @@ class LogXML(object):
if report.when == "teardown":
reporter = self._opentestcase(report)
reporter.write_captured_output(report)
for propname, propvalue in report.user_properties:
reporter.add_property(propname, propvalue)
self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)

View File

@@ -1,10 +1,12 @@
""" Access and control log capturing. """
from __future__ import absolute_import, division, print_function
import logging
from contextlib import closing, contextmanager
import sys
import re
import six
from _pytest.config import create_terminal_writer
import pytest
import py
@@ -13,6 +15,58 @@ DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
class ColoredLevelFormatter(logging.Formatter):
"""
Colorize the %(levelname)..s part of the log format passed to __init__.
"""
LOGLEVEL_COLOROPTS = {
logging.CRITICAL: {'red'},
logging.ERROR: {'red', 'bold'},
logging.WARNING: {'yellow'},
logging.WARN: {'yellow'},
logging.INFO: {'green'},
logging.DEBUG: {'purple'},
logging.NOTSET: set(),
}
LEVELNAME_FMT_REGEX = re.compile(r'%\(levelname\)([+-]?\d*s)')
def __init__(self, terminalwriter, *args, **kwargs):
super(ColoredLevelFormatter, self).__init__(
*args, **kwargs)
if six.PY2:
self._original_fmt = self._fmt
else:
self._original_fmt = self._style._fmt
self._level_to_fmt_mapping = {}
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
if not levelname_fmt_match:
return
levelname_fmt = levelname_fmt_match.group()
for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
formatted_levelname = levelname_fmt % {
'levelname': logging.getLevelName(level)}
# add ANSI escape sequences around the formatted levelname
color_kwargs = {name: True for name in color_opts}
colorized_formatted_levelname = terminalwriter.markup(
formatted_levelname, **color_kwargs)
self._level_to_fmt_mapping[level] = self.LEVELNAME_FMT_REGEX.sub(
colorized_formatted_levelname,
self._fmt)
def format(self, record):
fmt = self._level_to_fmt_mapping.get(
record.levelno, self._original_fmt)
if six.PY2:
self._fmt = fmt
else:
self._style._fmt = fmt
return super(ColoredLevelFormatter, self).format(record)
def get_option_ini(config, *names):
for name in names:
ret = config.getoption(name) # 'default' arg won't work as expected
@@ -48,6 +102,9 @@ def pytest_addoption(parser):
'--log-date-format',
dest='log_date_format', default=DEFAULT_LOG_DATE_FORMAT,
help='log date format as used by the logging module.')
parser.addini(
'log_cli', default=False, type='bool',
help='enable log display during test run (also known as "live logging").')
add_option_ini(
'--log-cli-level',
dest='log_cli_level', default=None,
@@ -79,39 +136,31 @@ def pytest_addoption(parser):
@contextmanager
def logging_using_handler(handler, logger=None):
"""Context manager that safely registers a given handler."""
logger = logger or logging.getLogger(logger)
if handler in logger.handlers: # reentrancy
# Adding the same handler twice would confuse logging system.
# Just don't do that.
yield
else:
logger.addHandler(handler)
try:
yield
finally:
logger.removeHandler(handler)
@contextmanager
def catching_logs(handler, formatter=None,
level=logging.NOTSET, logger=None):
def catching_logs(handler, formatter=None, level=None):
"""Context manager that prepares the whole logging machinery properly."""
logger = logger or logging.getLogger(logger)
root_logger = logging.getLogger()
if formatter is not None:
handler.setFormatter(formatter)
handler.setLevel(level)
if level is not None:
handler.setLevel(level)
with logging_using_handler(handler, logger):
orig_level = logger.level
logger.setLevel(min(orig_level, level))
try:
yield handler
finally:
logger.setLevel(orig_level)
# Adding the same handler twice would confuse logging system.
# Just don't do that.
add_new_handler = handler not in root_logger.handlers
if add_new_handler:
root_logger.addHandler(handler)
if level is not None:
orig_level = root_logger.level
root_logger.setLevel(level)
try:
yield handler
finally:
if level is not None:
root_logger.setLevel(orig_level)
if add_new_handler:
root_logger.removeHandler(handler)
class LogCaptureHandler(logging.StreamHandler):
@@ -127,6 +176,10 @@ class LogCaptureHandler(logging.StreamHandler):
self.records.append(record)
logging.StreamHandler.emit(self, record)
def reset(self):
self.records = []
self.stream = py.io.TextIO()
class LogCaptureFixture(object):
"""Provides access and control of log capturing."""
@@ -134,11 +187,43 @@ class LogCaptureFixture(object):
def __init__(self, item):
"""Creates a new funcarg."""
self._item = item
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level
def _finalize(self):
"""Finalizes the fixture.
This restores the log levels changed by :meth:`set_level`.
"""
# restore log levels
for logger_name, level in self._initial_log_levels.items():
logger = logging.getLogger(logger_name)
logger.setLevel(level)
@property
def handler(self):
"""
:rtype: LogCaptureHandler
"""
return self._item.catch_log_handler
def get_records(self, when):
"""
Get the logging records for one of the possible test phases.
:param str when:
Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".
:rtype: List[logging.LogRecord]
:return: the list of captured records at the given stage
.. versionadded:: 3.4
"""
handler = self._item.catch_log_handlers.get(when)
if handler:
return handler.records
else:
return []
@property
def text(self):
"""Returns the log text."""
@@ -161,35 +246,35 @@ class LogCaptureFixture(object):
return [(r.name, r.levelno, r.getMessage()) for r in self.records]
def clear(self):
"""Reset the list of log records."""
self.handler.records = []
"""Reset the list of log records and the captured log text."""
self.handler.reset()
def set_level(self, level, logger=None):
"""Sets the level for capturing of logs.
"""Sets the level for capturing of logs. The level will be restored to its previous value at the end of
the test.
By default, the level is set on the handler used to capture
logs. Specify a logger name to instead set the level of any
logger.
:param int level: the logger to level.
:param str logger: the logger to update the level. If not given, the root logger level is updated.
.. versionchanged:: 3.4
The levels of the loggers changed by this function will be restored to their initial values at the
end of the test.
"""
if logger is None:
logger = self.handler
else:
logger = logging.getLogger(logger)
logger_name = logger
logger = logging.getLogger(logger_name)
# save the original log-level to restore it during teardown
self._initial_log_levels.setdefault(logger_name, logger.level)
logger.setLevel(level)
@contextmanager
def at_level(self, level, logger=None):
"""Context manager that sets the level for capturing of logs.
"""Context manager that sets the level for capturing of logs. After the end of the 'with' statement the
level is restored to its original value.
By default, the level is set on the handler used to capture
logs. Specify a logger name to instead set the level of any
logger.
:param int level: the logger to level.
:param str logger: the logger to update the level. If not given, the root logger level is updated.
"""
if logger is None:
logger = self.handler
else:
logger = logging.getLogger(logger)
logger = logging.getLogger(logger)
orig_level = logger.level
logger.setLevel(level)
try:
@@ -207,8 +292,11 @@ def caplog(request):
* caplog.text() -> string containing formatted log output
* caplog.records() -> list of logging.LogRecord instances
* caplog.record_tuples() -> list of (logger_name, level, message) tuples
* caplog.clear() -> clear captured records and formatted log output string
"""
return LogCaptureFixture(request.node)
result = LogCaptureFixture(request.node)
yield result
result._finalize()
def get_actual_log_level(config, *setting_names):
@@ -238,8 +326,12 @@ def get_actual_log_level(config, *setting_names):
def pytest_configure(config):
config.pluginmanager.register(LoggingPlugin(config),
'logging-plugin')
config.pluginmanager.register(LoggingPlugin(config), 'logging-plugin')
@contextmanager
def _dummy_context_manager():
yield
class LoggingPlugin(object):
@@ -252,57 +344,64 @@ class LoggingPlugin(object):
The formatter can be safely shared across all handlers so
create a single one for the entire test session here.
"""
self.log_cli_level = get_actual_log_level(
config, 'log_cli_level', 'log_level') or logging.WARNING
self._config = config
# enable verbose output automatically if live logging is enabled
if self._log_cli_enabled() and not config.getoption('verbose'):
# sanity check: terminal reporter should not have been loaded at this point
assert self._config.pluginmanager.get_plugin('terminalreporter') is None
config.option.verbose = 1
self.print_logs = get_option_ini(config, 'log_print')
self.formatter = logging.Formatter(
get_option_ini(config, 'log_format'),
get_option_ini(config, 'log_date_format'))
log_cli_handler = logging.StreamHandler(sys.stderr)
log_cli_format = get_option_ini(
config, 'log_cli_format', 'log_format')
log_cli_date_format = get_option_ini(
config, 'log_cli_date_format', 'log_date_format')
log_cli_formatter = logging.Formatter(
log_cli_format,
datefmt=log_cli_date_format)
self.log_cli_handler = log_cli_handler # needed for a single unittest
self.live_logs = catching_logs(log_cli_handler,
formatter=log_cli_formatter,
level=self.log_cli_level)
self.formatter = logging.Formatter(get_option_ini(config, 'log_format'),
get_option_ini(config, 'log_date_format'))
self.log_level = get_actual_log_level(config, 'log_level')
log_file = get_option_ini(config, 'log_file')
if log_file:
self.log_file_level = get_actual_log_level(
config, 'log_file_level') or logging.WARNING
self.log_file_level = get_actual_log_level(config, 'log_file_level')
log_file_format = get_option_ini(
config, 'log_file_format', 'log_format')
log_file_date_format = get_option_ini(
config, 'log_file_date_format', 'log_date_format')
self.log_file_handler = logging.FileHandler(
log_file,
# Each pytest runtests session will write to a clean logfile
mode='w')
log_file_formatter = logging.Formatter(
log_file_format,
datefmt=log_file_date_format)
log_file_format = get_option_ini(config, 'log_file_format', 'log_format')
log_file_date_format = get_option_ini(config, 'log_file_date_format', 'log_date_format')
# Each pytest runtests session will write to a clean logfile
self.log_file_handler = logging.FileHandler(log_file, mode='w')
log_file_formatter = logging.Formatter(log_file_format, datefmt=log_file_date_format)
self.log_file_handler.setFormatter(log_file_formatter)
else:
self.log_file_handler = None
# initialized during pytest_runtestloop
self.log_cli_handler = None
def _log_cli_enabled(self):
"""Return True if log_cli should be considered enabled, either explicitly
or because --log-cli-level was given in the command-line.
"""
return self._config.getoption('--log-cli-level') is not None or \
self._config.getini('log_cli')
@contextmanager
def _runtest_for(self, item, when):
"""Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(LogCaptureHandler(),
formatter=self.formatter) as log_handler:
formatter=self.formatter, level=self.log_level) as log_handler:
if self.log_cli_handler:
self.log_cli_handler.set_when(when)
if item is None:
yield # run the test
return
if not hasattr(item, 'catch_log_handlers'):
item.catch_log_handlers = {}
item.catch_log_handlers[when] = log_handler
item.catch_log_handler = log_handler
try:
yield # run test
finally:
del item.catch_log_handler
if when == 'teardown':
del item.catch_log_handlers
if self.print_logs:
# Add a captured log section to the report.
@@ -324,10 +423,23 @@ class LoggingPlugin(object):
with self._runtest_for(item, 'teardown'):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logstart(self):
if self.log_cli_handler:
self.log_cli_handler.reset()
with self._runtest_for(None, 'start'):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logfinish(self):
with self._runtest_for(None, 'finish'):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(self, session):
"""Runs all collected test items."""
with self.live_logs:
self._setup_cli_logging()
with self.live_logs_context:
if self.log_file_handler is not None:
with closing(self.log_file_handler):
with catching_logs(self.log_file_handler,
@@ -335,3 +447,76 @@ class LoggingPlugin(object):
yield # run all the tests
else:
yield # run all the tests
def _setup_cli_logging(self):
"""Sets up the handler and logger for the Live Logs feature, if enabled.
This must be done right before starting the loop so we can access the terminal reporter plugin.
"""
terminal_reporter = self._config.pluginmanager.get_plugin('terminalreporter')
if self._log_cli_enabled() and terminal_reporter is not None:
capture_manager = self._config.pluginmanager.get_plugin('capturemanager')
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
log_cli_format = get_option_ini(self._config, 'log_cli_format', 'log_format')
log_cli_date_format = get_option_ini(self._config, 'log_cli_date_format', 'log_date_format')
if self._config.option.color != 'no' and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format):
log_cli_formatter = ColoredLevelFormatter(create_terminal_writer(self._config),
log_cli_format, datefmt=log_cli_date_format)
else:
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
log_cli_level = get_actual_log_level(self._config, 'log_cli_level', 'log_level')
self.log_cli_handler = log_cli_handler
self.live_logs_context = catching_logs(log_cli_handler, formatter=log_cli_formatter, level=log_cli_level)
else:
self.live_logs_context = _dummy_context_manager()
class _LiveLoggingStreamHandler(logging.StreamHandler):
"""
Custom StreamHandler used by the live logging feature: it will write a newline before the first log message
in each test.
During live logging we must also explicitly disable stdout/stderr capturing otherwise it will get captured
and won't appear in the terminal.
"""
def __init__(self, terminal_reporter, capture_manager):
"""
:param _pytest.terminal.TerminalReporter terminal_reporter:
:param _pytest.capture.CaptureManager capture_manager:
"""
logging.StreamHandler.__init__(self, stream=terminal_reporter)
self.capture_manager = capture_manager
self.reset()
self.set_when(None)
self._test_outcome_written = False
def reset(self):
"""Reset the handler; should be called before the start of each test"""
self._first_record_emitted = False
def set_when(self, when):
"""Prepares for the given test phase (setup/call/teardown)"""
self._when = when
self._section_name_shown = False
if when == 'start':
self._test_outcome_written = False
def emit(self, record):
if self.capture_manager is not None:
self.capture_manager.suspend_global_capture()
try:
if not self._first_record_emitted:
self.stream.write('\n')
self._first_record_emitted = True
elif self._when in ('teardown', 'finish'):
if not self._test_outcome_written:
self._test_outcome_written = True
self.stream.write('\n')
if not self._section_name_shown and self._when:
self.stream.section('live log ' + self._when, sep='-', bold=True)
self._section_name_shown = True
logging.StreamHandler.emit(self, record)
finally:
if self.capture_manager is not None:
self.capture_manager.resume_global_capture()

View File

@@ -1,8 +1,10 @@
""" core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function
import contextlib
import functools
import os
import pkgutil
import six
import sys
@@ -10,16 +12,11 @@ import _pytest
from _pytest import nodes
import _pytest._code
import py
try:
from collections import MutableMapping as MappingMixin
except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
# exitcodes for the command line
EXIT_OK = 0
@@ -56,6 +53,11 @@ def pytest_addoption(parser):
group._addoption("--continue-on-collection-errors", action="store_true",
default=False, dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur.")
group._addoption("--rootdir", action="store",
dest="rootdir",
help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
"'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
"'$HOME/root_dir'.")
group = parser.getgroup("collect", "collection")
group.addoption('--collectonly', '--collect-only', action="store_true",
@@ -64,6 +66,8 @@ def pytest_addoption(parser):
help="try to interpret all arguments as python packages.")
group.addoption("--ignore", action="append", metavar="path",
help="ignore path during collection (multi-allowed).")
group.addoption("--deselect", action="append", metavar="nodeid_prefix",
help="deselect item during collection (multi-allowed).")
# when changing this to --conf-cut-dir, config.py Conftest.setinitial
# needs upgrading as well
group.addoption('--confcutdir', dest="confcutdir", default=None,
@@ -173,7 +177,7 @@ def _in_venv(path):
"""Attempts to detect if ``path`` is the root of a Virtual Environment by
checking for the existence of the appropriate activate script"""
bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin')
if not bindir.exists():
if not bindir.isdir():
return False
activates = ('activate', 'activate.csh', 'activate.fish',
'Activate', 'Activate.bat', 'Activate.ps1')
@@ -206,7 +210,65 @@ def pytest_ignore_collect(path, config):
return False
class FSHookProxy:
def pytest_collection_modifyitems(items, config):
deselect_prefixes = tuple(config.getoption("deselect") or [])
if not deselect_prefixes:
return
remaining = []
deselected = []
for colitem in items:
if colitem.nodeid.startswith(deselect_prefixes):
deselected.append(colitem)
else:
remaining.append(colitem)
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
@contextlib.contextmanager
def _patched_find_module():
"""Patch bug in pkgutil.ImpImporter.find_module
When using pkgutil.find_loader on python<3.4 it removes symlinks
from the path due to a call to os.path.realpath. This is not consistent
with actually doing the import (in these versions, pkgutil and __import__
did not share the same underlying code). This can break conftest
discovery for pytest where symlinks are involved.
The only supported python<3.4 by pytest is python 2.7.
"""
if six.PY2: # python 3.4+ uses importlib instead
def find_module_patched(self, fullname, path=None):
# Note: we ignore 'path' argument since it is only used via meta_path
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
# original: path = [os.path.realpath(self.path)]
path = [self.path]
try:
file, filename, etc = pkgutil.imp.find_module(subname,
path)
except ImportError:
return None
return pkgutil.ImpLoader(fullname, file, filename, etc)
old_find_module = pkgutil.ImpImporter.find_module
pkgutil.ImpImporter.find_module = find_module_patched
try:
yield
finally:
pkgutil.ImpImporter.find_module = old_find_module
else:
yield
class FSHookProxy(object):
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
self.pm = pm
@@ -218,356 +280,6 @@ class FSHookProxy:
return x
class _CompatProperty(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, owner):
if obj is None:
return self
# TODO: reenable in the features branch
# warnings.warn(
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
# name=self.name, owner=type(owner).__name__),
# PendingDeprecationWarning, stacklevel=2)
return getattr(__import__('pytest'), self.name)
class NodeKeywords(MappingMixin):
def __init__(self, node):
self.node = node
self.parent = node.parent
self._markers = {node.name: True}
def __getitem__(self, key):
try:
return self._markers[key]
except KeyError:
if self.parent is None:
raise
return self.parent.keywords[key]
def __setitem__(self, key, value):
self._markers[key] = value
def __delitem__(self, key):
raise ValueError("cannot delete key in keywords dict")
def __iter__(self):
seen = set(self._markers)
if self.parent is not None:
seen.update(self.parent.keywords)
return iter(seen)
def __len__(self):
return len(self.__iter__())
def keys(self):
return list(self)
def __repr__(self):
return "<NodeKeywords for node %s>" % (self.node, )
class Node(object):
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
def __init__(self, name, parent=None, config=None, session=None):
#: a unique name within the scope of the parent node
self.name = name
#: the parent collector node.
self.parent = parent
#: the pytest config object
self.config = config or parent.config
#: the session this node is part of
self.session = session or parent.session
#: filesystem path where this node was collected from (can be None)
self.fspath = getattr(parent, 'fspath', None)
#: keywords/markers collected from all scopes
self.keywords = NodeKeywords(self)
#: allow adding of extra keywords to use for matching
self.extra_keyword_matches = set()
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")
def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty):
return getattr(__import__('pytest'), name)
else:
cls = getattr(self, name)
# TODO: reenable in the features branch
# warnings.warn("use of node.%s is deprecated, "
# "use pytest_pycollect_makeitem(...) to create custom "
# "collection nodes" % name, category=DeprecationWarning)
return cls
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. """
assert isinstance(code, str)
fslocation = getattr(self, "location", None)
if fslocation is None:
fslocation = getattr(self, "fspath", None)
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation))
# methods for ordering nodes
@property
def nodeid(self):
""" a ::-separated string denoting its collection tree address. """
try:
return self._nodeid
except AttributeError:
self._nodeid = x = self._makeid()
return x
def _makeid(self):
return self.parent.nodeid + "::" + self.name
def __hash__(self):
return hash(self.nodeid)
def setup(self):
pass
def teardown(self):
pass
def listchain(self):
""" return list of all parent collectors up to self,
starting from root of collection tree. """
chain = []
item = self
while item is not None:
chain.append(item)
item = item.parent
chain.reverse()
return chain
def add_marker(self, marker):
""" dynamically add a marker object to the node.
``marker`` can be a string or pytest.mark.* instance.
"""
from _pytest.mark import MarkDecorator, MARK_GEN
if isinstance(marker, six.string_types):
marker = getattr(MARK_GEN, marker)
elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker
def get_marker(self, name):
""" get a marker object from this node or None if
the node doesn't have a marker with that name. """
val = self.keywords.get(name, None)
if val is not None:
from _pytest.mark import MarkInfo, MarkDecorator
if isinstance(val, (MarkDecorator, MarkInfo)):
return val
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()
item = self
for item in self.listchain():
extra_keywords.update(item.extra_keyword_matches)
return extra_keywords
def listnames(self):
return [x.name for x in self.listchain()]
def addfinalizer(self, fin):
""" register a function to be called when this node is finalized.
This method can only be called when this node is active
in a setup chain, for example during self.setup().
"""
self.session._setupstate.addfinalizer(fin, self)
def getparent(self, cls):
""" get the next parent node (including ourself)
which is an instance of the given class"""
current = self
while current and not isinstance(current, cls):
current = current.parent
return current
def _prunetraceback(self, excinfo):
pass
def _repr_failure_py(self, excinfo, style=None):
fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.option.fulltrace:
style = "long"
else:
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
self._prunetraceback(excinfo)
if len(excinfo.traceback) == 0:
excinfo.traceback = tb
tbfilter = False # prunetraceback already does it
if style == "auto":
style = "long"
# XXX should excinfo.getrepr record all data and toterminal() process it?
if style is None:
if self.config.option.tbstyle == "short":
style = "short"
else:
style = "long"
try:
os.getcwd()
abspath = False
except OSError:
abspath = True
return excinfo.getrepr(funcargs=True, abspath=abspath,
showlocals=self.config.option.showlocals,
style=style, tbfilter=tbfilter)
repr_failure = _repr_failure_py
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.
"""
class CollectError(Exception):
""" an error during collection, contains a custom message. """
def collect(self):
""" returns a list of children (items and collectors)
for this collection node.
"""
raise NotImplementedError("abstract")
def repr_failure(self, excinfo):
""" represent a collection failure. """
if excinfo.errisinstance(self.CollectError):
exc = excinfo.value
return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short")
def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'):
traceback = excinfo.traceback
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
excinfo.traceback = ntraceback.filter()
class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, session=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
if rel:
name = rel
name = name.replace(os.sep, nodes.SEP)
super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath
def _check_initialpaths_for_relpath(self):
for initialpath in self.session._initialpaths:
if self.fspath.common(initialpath) == initialpath:
return self.fspath.relto(initialpath.dirname)
def _makeid(self):
relpath = self.fspath.relto(self.config.rootdir)
if not relpath:
relpath = self._check_initialpaths_for_relpath()
if os.sep != nodes.SEP:
relpath = relpath.replace(os.sep, nodes.SEP)
return relpath
class File(FSCollector):
""" base class for collecting tests from a file. """
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
"""
nextitem = None
def __init__(self, name, parent=None, config=None, session=None):
super(Item, self).__init__(name, parent, config, session)
self._report_sections = []
def add_report_section(self, when, key, content):
"""
Adds a new report section, similar to what's done internally to add stdout and
stderr captured output::
item.add_report_section("call", "stdout", "report section contents")
:param str when:
One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
:param str key:
Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
``"stderr"`` internally.
:param str content:
The full contents as a string.
"""
if content:
self._report_sections.append((when, key, content))
def reportinfo(self):
return self.fspath, None, ""
@property
def location(self):
try:
return self._location
except AttributeError:
location = self.reportinfo()
# bestrelpath is a quite slow function
cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
try:
fspath = cache[location[0]]
except KeyError:
fspath = self.session.fspath.bestrelpath(location[0])
cache[location[0]] = fspath
location = (fspath, location[1], str(location[2]))
self._location = location
return location
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
@@ -581,13 +293,14 @@ class Failed(Exception):
""" signals an stop as failed test run. """
class Session(FSCollector):
class Session(nodes.FSCollector):
Interrupted = Interrupted
Failed = Failed
def __init__(self, config):
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
nodes.FSCollector.__init__(
self, config.rootdir, parent=None,
config=config, session=self, nodeid="")
self.testsfailed = 0
self.testscollected = 0
self.shouldstop = False
@@ -595,10 +308,8 @@ class Session(FSCollector):
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
self.config.pluginmanager.register(self, name="session")
def _makeid(self):
return ""
self.config.pluginmanager.register(self, name="session")
@hookimpl(tryfirst=True)
def pytest_collectstart(self):
@@ -728,9 +439,10 @@ class Session(FSCollector):
"""Convert a dotted module name to path.
"""
import pkgutil
try:
loader = pkgutil.find_loader(x)
with _patched_find_module():
loader = pkgutil.find_loader(x)
except ImportError:
return x
if loader is None:
@@ -738,7 +450,8 @@ class Session(FSCollector):
# This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place:
try:
path = loader.get_filename(x)
with _patched_find_module():
path = loader.get_filename(x)
except AttributeError:
# Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename
@@ -782,11 +495,11 @@ class Session(FSCollector):
nextnames = names[1:]
resultnodes = []
for node in matching:
if isinstance(node, Item):
if isinstance(node, nodes.Item):
if not names:
resultnodes.append(node)
continue
assert isinstance(node, Collector)
assert isinstance(node, nodes.Collector)
rep = collect_one_node(node)
if rep.passed:
has_matched = False
@@ -808,11 +521,11 @@ class Session(FSCollector):
def genitems(self, node):
self.trace("genitems", node)
if isinstance(node, Item):
if isinstance(node, nodes.Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, Collector)
assert isinstance(node, nodes.Collector)
rep = collect_one_node(node)
if rep.passed:
for subnode in rep.result:

156
_pytest/mark/__init__.py Normal file
View File

@@ -0,0 +1,156 @@
""" generic mechanism for marking and selecting python functions. """
from __future__ import absolute_import, division, print_function
from _pytest.config import UsageError
from .structures import (
ParameterSet, EMPTY_PARAMETERSET_OPTION, MARK_GEN,
Mark, MarkInfo, MarkDecorator, MarkGenerator,
transfer_markers, get_empty_parameterset_mark
)
from .legacy import matchkeyword, matchmark
__all__ = [
'Mark', 'MarkInfo', 'MarkDecorator', 'MarkGenerator',
'transfer_markers', 'get_empty_parameterset_mark'
]
class MarkerError(Exception):
"""Error in use of a pytest marker/attribute."""
def param(*values, **kw):
"""Specify a parameter in a `pytest.mark.parametrize`_ call.
.. code-block:: python
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
pytest.param("6*9", 42, marks=pytest.mark.xfail),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
:param values: variable args of the values of the parameter set, in order.
:keyword marks: a single mark or a list of marks to be applied to this parameter set.
:keyword str id: the id to attribute to this parameter set.
"""
return ParameterSet.param(*values, **kw)
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
'-k',
action="store", dest="keyword", default='', metavar="EXPRESSION",
help="only run tests which match the given substring expression. "
"An expression is a python evaluatable expression "
"where all names are substring-matched against test names "
"and their parent classes. Example: -k 'test_method or test_"
"other' matches all test functions and classes whose name "
"contains 'test_method' or 'test_other', while -k 'not test_method' "
"matches those that don't contain 'test_method' in their names. "
"Additionally keywords are matched to classes and functions "
"containing extra names in their 'extra_keyword_matches' set, "
"as well as functions which have names assigned directly to them."
)
group._addoption(
"-m",
action="store", dest="markexpr", default="", metavar="MARKEXPR",
help="only run tests matching given mark expression. "
"example: -m 'mark1 and not mark2'."
)
group.addoption(
"--markers", action="store_true",
help="show markers (builtin, plugin and per-project ones)."
)
parser.addini("markers", "markers for test functions", 'linelist')
parser.addini(
EMPTY_PARAMETERSET_OPTION,
"default marker for empty parametersets")
def pytest_cmdline_main(config):
import _pytest.config
if config.option.markers:
config._do_configure()
tw = _pytest.config.create_terminal_writer(config)
for line in config.getini("markers"):
parts = line.split(":", 1)
name = parts[0]
rest = parts[1] if len(parts) == 2 else ''
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
config._ensure_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
def deselect_by_keyword(items, config):
keywordexpr = config.option.keyword.lstrip()
if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
if keywordexpr[-1:] == ":":
selectuntil = True
keywordexpr = keywordexpr[:-1]
remaining = []
deselected = []
for colitem in items:
if keywordexpr and not matchkeyword(colitem, keywordexpr):
deselected.append(colitem)
else:
if selectuntil:
keywordexpr = None
remaining.append(colitem)
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
def deselect_by_mark(items, config):
matchexpr = config.option.markexpr
if not matchexpr:
return
remaining = []
deselected = []
for item in items:
if matchmark(item, matchexpr):
remaining.append(item)
else:
deselected.append(item)
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
def pytest_collection_modifyitems(items, config):
deselect_by_keyword(items, config)
deselect_by_mark(items, config)
def pytest_configure(config):
config._old_mark_config = MARK_GEN._config
if config.option.strict:
MARK_GEN._config = config
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
if empty_parameterset not in ('skip', 'xfail', None, ''):
raise UsageError(
"{!s} must be one of skip and xfail,"
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset))
def pytest_unconfigure(config):
MARK_GEN._config = getattr(config, '_old_mark_config', None)

126
_pytest/mark/evaluate.py Normal file
View File

@@ -0,0 +1,126 @@
import os
import six
import sys
import platform
import traceback
from . import MarkDecorator, MarkInfo
from ..outcomes import fail, TEST_OUTCOME
def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'):
config._evalcache = {}
try:
return config._evalcache[expr]
except KeyError:
import _pytest._code
exprcode = _pytest._code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x
class MarkEvaluator(object):
def __init__(self, item, name):
self.item = item
self._marks = None
self._mark = None
self._mark_name = name
def __bool__(self):
self._marks = self._get_marks()
return bool(self._marks)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
def _get_marks(self):
keyword = self.item.keywords.get(self._mark_name)
if isinstance(keyword, MarkDecorator):
return [keyword.mark]
elif isinstance(keyword, MarkInfo):
return [x.combined for x in keyword]
else:
return []
def invalidraise(self, exc):
raises = self.get('raises')
if not raises:
return
return not isinstance(exc, raises)
def istrue(self):
try:
return self._istrue()
except TEST_OUTCOME:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^", ]
msg.append("SyntaxError: invalid syntax")
else:
msg = traceback.format_exception_only(*self.exc[:2])
fail("Error evaluating %r expression\n"
" %s\n"
"%s"
% (self._mark_name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
d = {'os': os, 'sys': sys, 'platform': platform, 'config': self.item.config}
if hasattr(self.item, 'obj'):
d.update(self.item.obj.__globals__)
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
self._marks = self._get_marks()
if self._marks:
self.result = False
for mark in self._marks:
self._mark = mark
if 'condition' in mark.kwargs:
args = (mark.kwargs['condition'],)
else:
args = mark.args
for expr in args:
self.expr = expr
if isinstance(expr, six.string_types):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in mark.kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = mark.kwargs.get('reason', None)
self.expr = expr
return self.result
if not args:
self.result = True
self.reason = mark.kwargs.get('reason', None)
return self.result
return False
def get(self, attr, default=None):
if self._mark is None:
return default
return self._mark.kwargs.get(attr, default)
def getexplanation(self):
expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + str(self.expr)
return expl

97
_pytest/mark/legacy.py Normal file
View File

@@ -0,0 +1,97 @@
"""
this is a place where we put datastructures used by legacy apis
we hope ot remove
"""
import attr
import keyword
from . import MarkInfo, MarkDecorator
from _pytest.config import UsageError
@attr.s
class MarkMapping(object):
"""Provides a local mapping for markers where item access
resolves to True if the marker is present. """
own_mark_names = attr.ib()
@classmethod
def from_keywords(cls, keywords):
mark_names = set()
for key, value in keywords.items():
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
mark_names.add(key)
return cls(mark_names)
def __getitem__(self, name):
return name in self.own_mark_names
class KeywordMapping(object):
"""Provides a local mapping for keywords.
Given a list of names, map any substring of one of these names to True.
"""
def __init__(self, names):
self._names = names
@classmethod
def from_item(cls, item):
mapped_names = set()
# Add the names of the current item and any parent items
import pytest
for item in item.listchain():
if not isinstance(item, pytest.Instance):
mapped_names.add(item.name)
# Add the names added as extra keywords to current or parent items
for name in item.listextrakeywords():
mapped_names.add(name)
# Add the names attached to the current function through direct assignment
if hasattr(item, 'function'):
for name in item.function.__dict__:
mapped_names.add(name)
return cls(mapped_names)
def __getitem__(self, subname):
for name in self._names:
if subname in name:
return True
return False
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_keywords(colitem.keywords))
def matchkeyword(colitem, keywordexpr):
"""Tries to match given keyword expression to given collector item.
Will match on the name of colitem, including the names of its parents.
Only matches names of items which are either a :class:`Class` or a
:class:`Function`.
Additionally, matches on names in the 'extra_keyword_matches' set of
any item, as well as names directly assigned to test functions.
"""
mapping = KeywordMapping.from_item(colitem)
if " " not in keywordexpr:
# special case to allow for simple "-k pass" and "-k 1.3"
return mapping[keywordexpr]
elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
return not mapping[keywordexpr[4:]]
for kwd in keywordexpr.split():
if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list:
raise UsageError("Python keyword '{}' not accepted in expressions passed to '-k'".format(kwd))
try:
return eval(keywordexpr, {}, mapping)
except SyntaxError:
raise UsageError("Wrong expression passed to '-k': {}".format(keywordexpr))

View File

@@ -1,14 +1,15 @@
""" generic mechanism for marking and selecting python functions. """
from __future__ import absolute_import, division, print_function
import inspect
from collections import namedtuple, MutableMapping as MappingMixin
import warnings
import attr
from collections import namedtuple
from operator import attrgetter
import inspect
import attr
from ..deprecated import MARK_PARAMETERSET_UNPACKING
from ..compat import NOTSET, getfslineno
from six.moves import map
from .deprecated import MARK_PARAMETERSET_UNPACKING
from .compat import NOTSET, getfslineno
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
def alias(name, warning=None):
@@ -21,6 +22,25 @@ def alias(name, warning=None):
return property(getter if warning is None else warned, doc='alias for ' + name)
def istestfunc(func):
return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>"
def get_empty_parameterset_mark(config, argnames, func):
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
if requested_mark in ('', None, 'skip'):
mark = MARK_GEN.skip
elif requested_mark == 'xfail':
mark = MARK_GEN.xfail(run=False)
else:
raise LookupError(requested_mark)
fs, lineno = getfslineno(func)
reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, func.__name__, fs, lineno)
return mark(reason=reason)
class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
@classmethod
def param(cls, *values, **kw):
@@ -33,8 +53,8 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
def param_extract_id(id=None):
return id
id = param_extract_id(**kw)
return cls(values, marks, id)
id_ = param_extract_id(**kw)
return cls(values, marks, id_)
@classmethod
def extract_from(cls, parameterset, legacy_force_tuple=False):
@@ -70,7 +90,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
return cls(argval, marks=newmarks, id=None)
@classmethod
def _for_parameterize(cls, argnames, argvalues, function):
def _for_parametrize(cls, argnames, argvalues, func, config):
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
force_tuple = len(argnames) == 1
@@ -82,10 +102,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
del argvalues
if not parameters:
fs, lineno = getfslineno(function)
reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, function.__name__, fs, lineno)
mark = MARK_GEN.skip(reason=reason)
mark = get_empty_parameterset_mark(config, argnames, func)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
@@ -94,225 +111,6 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
return argnames, parameters
class MarkerError(Exception):
"""Error in use of a pytest marker/attribute."""
def param(*values, **kw):
return ParameterSet.param(*values, **kw)
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
'-k',
action="store", dest="keyword", default='', metavar="EXPRESSION",
help="only run tests which match the given substring expression. "
"An expression is a python evaluatable expression "
"where all names are substring-matched against test names "
"and their parent classes. Example: -k 'test_method or test_"
"other' matches all test functions and classes whose name "
"contains 'test_method' or 'test_other', while -k 'not test_method' "
"matches those that don't contain 'test_method' in their names. "
"Additionally keywords are matched to classes and functions "
"containing extra names in their 'extra_keyword_matches' set, "
"as well as functions which have names assigned directly to them."
)
group._addoption(
"-m",
action="store", dest="markexpr", default="", metavar="MARKEXPR",
help="only run tests matching given mark expression. "
"example: -m 'mark1 and not mark2'."
)
group.addoption(
"--markers", action="store_true",
help="show markers (builtin, plugin and per-project ones)."
)
parser.addini("markers", "markers for test functions", 'linelist')
def pytest_cmdline_main(config):
import _pytest.config
if config.option.markers:
config._do_configure()
tw = _pytest.config.create_terminal_writer(config)
for line in config.getini("markers"):
parts = line.split(":", 1)
name = parts[0]
rest = parts[1] if len(parts) == 2 else ''
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
config._ensure_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword.lstrip()
matchexpr = config.option.markexpr
if not keywordexpr and not matchexpr:
return
# pytest used to allow "-" for negating
# but today we just allow "-" at the beginning, use "not" instead
# we probably remove "-" altogether soon
if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
if keywordexpr[-1:] == ":":
selectuntil = True
keywordexpr = keywordexpr[:-1]
remaining = []
deselected = []
for colitem in items:
if keywordexpr and not matchkeyword(colitem, keywordexpr):
deselected.append(colitem)
else:
if selectuntil:
keywordexpr = None
if matchexpr:
if not matchmark(colitem, matchexpr):
deselected.append(colitem)
continue
remaining.append(colitem)
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
@attr.s
class MarkMapping(object):
"""Provides a local mapping for markers where item access
resolves to True if the marker is present. """
own_mark_names = attr.ib()
@classmethod
def from_keywords(cls, keywords):
mark_names = set()
for key, value in keywords.items():
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
mark_names.add(key)
return cls(mark_names)
def __getitem__(self, name):
return name in self.own_mark_names
class KeywordMapping(object):
"""Provides a local mapping for keywords.
Given a list of names, map any substring of one of these names to True.
"""
def __init__(self, names):
self._names = names
def __getitem__(self, subname):
for name in self._names:
if subname in name:
return True
return False
def matchmark(colitem, markexpr):
"""Tries to match on any marker names, attached to the given colitem."""
return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
def matchkeyword(colitem, keywordexpr):
"""Tries to match given keyword expression to given collector item.
Will match on the name of colitem, including the names of its parents.
Only matches names of items which are either a :class:`Class` or a
:class:`Function`.
Additionally, matches on names in the 'extra_keyword_matches' set of
any item, as well as names directly assigned to test functions.
"""
mapped_names = set()
# Add the names of the current item and any parent items
import pytest
for item in colitem.listchain():
if not isinstance(item, pytest.Instance):
mapped_names.add(item.name)
# Add the names added as extra keywords to current or parent items
for name in colitem.listextrakeywords():
mapped_names.add(name)
# Add the names attached to the current function through direct assignment
if hasattr(colitem, 'function'):
for name in colitem.function.__dict__:
mapped_names.add(name)
mapping = KeywordMapping(mapped_names)
if " " not in keywordexpr:
# special case to allow for simple "-k pass" and "-k 1.3"
return mapping[keywordexpr]
elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
return not mapping[keywordexpr[4:]]
return eval(keywordexpr, {}, mapping)
def pytest_configure(config):
config._old_mark_config = MARK_GEN._config
if config.option.strict:
MARK_GEN._config = config
def pytest_unconfigure(config):
MARK_GEN._config = getattr(config, '_old_mark_config', None)
class MarkGenerator:
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance. Example::
import pytest
@pytest.mark.slowtest
def test_function():
pass
will set a 'slowtest' :class:`MarkInfo` object
on the ``test_function`` object. """
_config = None
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
if self._config is not None:
self._check(name)
return MarkDecorator(Mark(name, (), {}))
def _check(self, name):
try:
if name in self._markers:
return
except AttributeError:
pass
self._markers = values = set()
for line in self._config.getini("markers"):
marker = line.split(":", 1)[0]
marker = marker.rstrip()
x = marker.split("(", 1)[0]
values.add(x)
if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,))
def istestfunc(func):
return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>"
@attr.s(frozen=True)
class Mark(object):
name = attr.ib()
@@ -441,6 +239,33 @@ def store_legacy_markinfo(func, mark):
holder.add_mark(mark)
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
@@ -466,31 +291,77 @@ class MarkInfo(object):
return map(MarkInfo, self._marks)
class MarkGenerator(object):
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance. Example::
import pytest
@pytest.mark.slowtest
def test_function():
pass
will set a 'slowtest' :class:`MarkInfo` object
on the ``test_function`` object. """
_config = None
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
if self._config is not None:
self._check(name)
return MarkDecorator(Mark(name, (), {}))
def _check(self, name):
try:
if name in self._markers:
return
except AttributeError:
pass
self._markers = values = set()
for line in self._config.getini("markers"):
marker = line.split(":", 1)[0]
marker = marker.rstrip()
x = marker.split("(", 1)[0]
values.add(x)
if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,))
MARK_GEN = MarkGenerator()
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
class NodeKeywords(MappingMixin):
def __init__(self, node):
self.node = node
self.parent = node.parent
self._markers = {node.name: True}
def __getitem__(self, key):
try:
return self._markers[key]
except KeyError:
if self.parent is None:
raise
return self.parent.keywords[key]
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
def __setitem__(self, key, value):
self._markers[key] = value
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
def __delitem__(self, key):
raise ValueError("cannot delete key in keywords dict")
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)
def __iter__(self):
seen = self._seen()
return iter(seen)
def _seen(self):
seen = set(self._markers)
if self.parent is not None:
seen.update(self.parent.keywords)
return seen
def __len__(self):
return len(self._seen())
def __repr__(self):
return "<NodeKeywords for node %s>" % (self.node, )

View File

@@ -88,7 +88,7 @@ def derive_importpath(import_path, raising):
return attr, target
class Notset:
class Notset(object):
def __repr__(self):
return "<notset>"
@@ -96,7 +96,7 @@ class Notset:
notset = Notset()
class MonkeyPatch:
class MonkeyPatch(object):
""" Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
"""
@@ -113,7 +113,7 @@ class MonkeyPatch:
For convenience you can specify a string as ``target`` which
will be interpreted as a dotted import path, with the last part
being the attribute name. Example:
``monkeypatch.setattr("os.getcwd", lambda x: "/")``
``monkeypatch.setattr("os.getcwd", lambda: "/")``
would set the ``getcwd`` function of the ``os`` module.
The ``raising`` value determines if the setattr should fail

View File

@@ -1,5 +1,19 @@
from __future__ import absolute_import, division, print_function
import os
import six
import py
import attr
import _pytest
import _pytest._code
from _pytest.mark.structures import NodeKeywords
SEP = "/"
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
def _splitnode(nodeid):
"""Split a nodeid into constituent 'parts'.
@@ -35,3 +49,322 @@ def ischildnode(baseid, nodeid):
if len(node_parts) < len(base_parts):
return False
return node_parts[:len(base_parts)] == base_parts
@attr.s
class _CompatProperty(object):
name = attr.ib()
def __get__(self, obj, owner):
if obj is None:
return self
# TODO: reenable in the features branch
# warnings.warn(
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
# name=self.name, owner=type(owner).__name__),
# PendingDeprecationWarning, stacklevel=2)
return getattr(__import__('pytest'), self.name)
class Node(object):
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
def __init__(self, name, parent=None, config=None, session=None, fspath=None, nodeid=None):
#: a unique name within the scope of the parent node
self.name = name
#: the parent collector node.
self.parent = parent
#: the pytest config object
self.config = config or parent.config
#: the session this node is part of
self.session = session or parent.session
#: filesystem path where this node was collected from (can be None)
self.fspath = fspath or getattr(parent, 'fspath', None)
#: keywords/markers collected from all scopes
self.keywords = NodeKeywords(self)
#: allow adding of extra keywords to use for matching
self.extra_keyword_matches = set()
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
if nodeid is not None:
self._nodeid = nodeid
else:
assert parent is not None
self._nodeid = self.parent.nodeid + "::" + self.name
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")
def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty):
return getattr(__import__('pytest'), name)
else:
cls = getattr(self, name)
# TODO: reenable in the features branch
# warnings.warn("use of node.%s is deprecated, "
# "use pytest_pycollect_makeitem(...) to create custom "
# "collection nodes" % name, category=DeprecationWarning)
return cls
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. """
assert isinstance(code, str)
fslocation = getattr(self, "location", None)
if fslocation is None:
fslocation = getattr(self, "fspath", None)
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation))
# methods for ordering nodes
@property
def nodeid(self):
""" a ::-separated string denoting its collection tree address. """
return self._nodeid
def __hash__(self):
return hash(self.nodeid)
def setup(self):
pass
def teardown(self):
pass
def listchain(self):
""" return list of all parent collectors up to self,
starting from root of collection tree. """
chain = []
item = self
while item is not None:
chain.append(item)
item = item.parent
chain.reverse()
return chain
def add_marker(self, marker):
""" dynamically add a marker object to the node.
``marker`` can be a string or pytest.mark.* instance.
"""
from _pytest.mark import MarkDecorator, MARK_GEN
if isinstance(marker, six.string_types):
marker = getattr(MARK_GEN, marker)
elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker
def get_marker(self, name):
""" get a marker object from this node or None if
the node doesn't have a marker with that name. """
val = self.keywords.get(name, None)
if val is not None:
from _pytest.mark import MarkInfo, MarkDecorator
if isinstance(val, (MarkDecorator, MarkInfo)):
return val
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()
for item in self.listchain():
extra_keywords.update(item.extra_keyword_matches)
return extra_keywords
def listnames(self):
return [x.name for x in self.listchain()]
def addfinalizer(self, fin):
""" register a function to be called when this node is finalized.
This method can only be called when this node is active
in a setup chain, for example during self.setup().
"""
self.session._setupstate.addfinalizer(fin, self)
def getparent(self, cls):
""" get the next parent node (including ourself)
which is an instance of the given class"""
current = self
while current and not isinstance(current, cls):
current = current.parent
return current
def _prunetraceback(self, excinfo):
pass
def _repr_failure_py(self, excinfo, style=None):
fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.option.fulltrace:
style = "long"
else:
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
self._prunetraceback(excinfo)
if len(excinfo.traceback) == 0:
excinfo.traceback = tb
tbfilter = False # prunetraceback already does it
if style == "auto":
style = "long"
# XXX should excinfo.getrepr record all data and toterminal() process it?
if style is None:
if self.config.option.tbstyle == "short":
style = "short"
else:
style = "long"
try:
os.getcwd()
abspath = False
except OSError:
abspath = True
return excinfo.getrepr(funcargs=True, abspath=abspath,
showlocals=self.config.option.showlocals,
style=style, tbfilter=tbfilter)
repr_failure = _repr_failure_py
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.
"""
class CollectError(Exception):
""" an error during collection, contains a custom message. """
def collect(self):
""" returns a list of children (items and collectors)
for this collection node.
"""
raise NotImplementedError("abstract")
def repr_failure(self, excinfo):
""" represent a collection failure. """
if excinfo.errisinstance(self.CollectError):
exc = excinfo.value
return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short")
def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'):
traceback = excinfo.traceback
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
excinfo.traceback = ntraceback.filter()
def _check_initialpaths_for_relpath(session, fspath):
for initial_path in session._initialpaths:
if fspath.common(initial_path) == initial_path:
return fspath.relto(initial_path.dirname)
class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
if rel:
name = rel
name = name.replace(os.sep, SEP)
self.fspath = fspath
session = session or parent.session
if nodeid is None:
nodeid = self.fspath.relto(session.config.rootdir)
if not nodeid:
nodeid = _check_initialpaths_for_relpath(session, fspath)
if os.sep != SEP:
nodeid = nodeid.replace(os.sep, SEP)
super(FSCollector, self).__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
class File(FSCollector):
""" base class for collecting tests from a file. """
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
"""
nextitem = None
def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
super(Item, self).__init__(name, parent, config, session, nodeid=nodeid)
self._report_sections = []
#: user properties is a list of tuples (name, value) that holds user
#: defined properties for this test.
self.user_properties = []
def add_report_section(self, when, key, content):
"""
Adds a new report section, similar to what's done internally to add stdout and
stderr captured output::
item.add_report_section("call", "stdout", "report section contents")
:param str when:
One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
:param str key:
Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
``"stderr"`` internally.
:param str content:
The full contents as a string.
"""
if content:
self._report_sections.append((when, key, content))
def reportinfo(self):
return self.fspath, None, ""
@property
def location(self):
try:
return self._location
except AttributeError:
location = self.reportinfo()
# bestrelpath is a quite slow function
cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
try:
fspath = cache[location[0]]
except KeyError:
fspath = self.session.fspath.bestrelpath(location[0])
cache[location[0]] = fspath
location = (fspath, location[1], str(location[2]))
self._location = location
return location

View File

@@ -1,4 +1,4 @@
""" (disabled by default) support for testing pytest and pytest plugins. """
"""(disabled by default) support for testing pytest and pytest plugins."""
from __future__ import absolute_import, division, print_function
import codecs
@@ -26,14 +26,18 @@ from _pytest.assertion.rewrite import AssertionRewritingHook
PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace("$py.class", ".py")
IGNORE_PAM = [ # filenames added when obtaining details about the current user
u'/var/lib/sss/mc/passwd'
]
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
choices=("inprocess", "subprocess"),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
@@ -67,6 +71,8 @@ class LsofFdLeakChecker(object):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename in IGNORE_PAM:
continue
if filename.startswith('/'):
open_files.append((fd, filename))
@@ -76,8 +82,8 @@ class LsofFdLeakChecker(object):
try:
py.process.cmdexec("lsof -v")
except (py.process.cmdexec.Error, UnicodeDecodeError):
# cmdexec may raise UnicodeDecodeError on Windows systems
# with locale other than english:
# cmdexec may raise UnicodeDecodeError on Windows systems with
# locale other than English:
# https://bitbucket.org/pytest-dev/py/issues/66
return False
else:
@@ -132,7 +138,7 @@ def getexecutable(name, cache={}):
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# Handle pyenv's 127.
# handle pyenv's 127
executable = None
cache[name] = executable
return executable
@@ -157,14 +163,15 @@ def anypython(request):
@pytest.fixture
def _pytest(request):
""" Return a helper which offers a gethookrecorder(hook)
method which returns a HookRecorder instance which helps
to make assertions about called hooks.
"""Return a helper which offers a gethookrecorder(hook) method which
returns a HookRecorder instance which helps to make assertions about called
hooks.
"""
return PytestArg(request)
class PytestArg:
class PytestArg(object):
def __init__(self, request):
self.request = request
@@ -179,7 +186,7 @@ def get_public_names(values):
return [x for x in values if x[0] != "_"]
class ParsedCall:
class ParsedCall(object):
def __init__(self, name, kwargs):
self.__dict__.update(kwargs)
self._name = name
@@ -190,11 +197,11 @@ class ParsedCall:
return "<ParsedCall %r(**%r)>" % (self._name, d)
class HookRecorder:
class HookRecorder(object):
"""Record all hooks called in a plugin manager.
This wraps all the hook calls in the plugin manager, recording
each call before propagating the normal calls.
This wraps all the hook calls in the plugin manager, recording each call
before propagating the normal calls.
"""
@@ -262,7 +269,7 @@ class HookRecorder:
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
"""return a testreport whose dotted import path matches"""
values = []
for rep in self.getreports(names=names):
try:
@@ -336,19 +343,19 @@ def testdir(request, tmpdir_factory):
rex_outcome = re.compile(r"(\d+) ([\w-]+)")
class RunResult:
class RunResult(object):
"""The result of running a command.
Attributes:
:ret: The return value.
:outlines: List of lines captured from stdout.
:errlines: List of lines captures from stderr.
:ret: the return value
:outlines: list of lines captured from stdout
:errlines: list of lines captures from stderr
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
reconstruct stdout or the commonly used
``stdout.fnmatch_lines()`` method.
:stderrr: :py:class:`LineMatcher` of stderr.
:duration: Duration in seconds.
reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
method
:stderr: :py:class:`LineMatcher` of stderr
:duration: duration in seconds
"""
@@ -361,8 +368,10 @@ class RunResult:
self.duration = duration
def parseoutcomes(self):
""" Return a dictionary of outcomestring->num from parsing
the terminal output that the test process produced."""
"""Return a dictionary of outcomestring->num from parsing the terminal
output that the test process produced.
"""
for line in reversed(self.outlines):
if 'seconds' in line:
outcomes = rex_outcome.findall(line)
@@ -374,8 +383,10 @@ class RunResult:
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=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."""
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
"""
d = self.parseoutcomes()
obtained = {
'passed': d.get('passed', 0),
@@ -386,24 +397,50 @@ class RunResult:
assert obtained == dict(passed=passed, skipped=skipped, failed=failed, error=error)
class Testdir:
class CwdSnapshot(object):
def __init__(self):
self.__saved = os.getcwd()
def restore(self):
os.chdir(self.__saved)
class SysModulesSnapshot(object):
def __init__(self, preserve=None):
self.__preserve = preserve
self.__saved = dict(sys.modules)
def restore(self):
if self.__preserve:
self.__saved.update(
(k, m) for k, m in sys.modules.items() if self.__preserve(k))
sys.modules.clear()
sys.modules.update(self.__saved)
class SysPathsSnapshot(object):
def __init__(self):
self.__saved = list(sys.path), list(sys.meta_path)
def restore(self):
sys.path[:], sys.meta_path[:] = self.__saved
class Testdir(object):
"""Temporary test directory with tools to test/run pytest itself.
This is based on the ``tmpdir`` fixture but provides a number of
methods which aid with testing pytest itself. Unless
:py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
current working directory.
This is based on the ``tmpdir`` fixture but provides a number of methods
which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
methods will use :py:attr:`tmpdir` as their current working directory.
Attributes:
:tmpdir: The :py:class:`py.path.local` instance of the temporary
directory.
:tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
:py:meth:`runpytest`. Initially this is an empty list but
plugins can be added to the list. The type of items to add to
the list depend on the method which uses them so refer to them
for details.
:py:meth:`runpytest`. Initially this is an empty list but plugins can
be added to the list. The type of items to add to the list depends on
the method using them so refer to them for details.
"""
@@ -413,9 +450,10 @@ class Testdir:
name = request.function.__name__
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
self.plugins = []
self._savesyspath = (list(sys.path), list(sys.meta_path))
self._savemodulekeys = set(sys.modules)
self.chdir() # always chdir
self._cwd_snapshot = CwdSnapshot()
self._sys_path_snapshot = SysPathsSnapshot()
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
self.chdir()
self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
@@ -429,29 +467,22 @@ class Testdir:
def finalize(self):
"""Clean up global state artifacts.
Some methods modify the global interpreter state and this
tries to clean this up. It does not remove the temporary
directory however so it can be looked at after the test run
has finished.
Some methods modify the global interpreter state and this tries to
clean this up. It does not remove the temporary directory however so
it can be looked at after the test run has finished.
"""
sys.path[:], sys.meta_path[:] = self._savesyspath
if hasattr(self, '_olddir'):
self._olddir.chdir()
self.delete_loaded_modules()
self._sys_modules_snapshot.restore()
self._sys_path_snapshot.restore()
self._cwd_snapshot.restore()
def delete_loaded_modules(self):
"""Delete modules that have been loaded during a test.
This allows the interpreter to catch module changes in case
the module is re-imported.
"""
for name in set(sys.modules).difference(self._savemodulekeys):
# some zope modules used by twisted-related tests keeps internal
# state and can't be deleted; we had some trouble in the past
# with zope.interface for example
if not name.startswith("zope"):
del sys.modules[name]
def __take_sys_modules_snapshot(self):
# some zope modules used by twisted-related tests keep internal state
# and can't be deleted; we had some trouble in the past with
# `zope.interface` for example
def preserve_module(name):
return name.startswith("zope")
return SysModulesSnapshot(preserve=preserve_module)
def make_hook_recorder(self, pluginmanager):
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
@@ -466,9 +497,7 @@ class Testdir:
This is done automatically upon instantiation.
"""
old = self.tmpdir.chdir()
if not hasattr(self, '_olddir'):
self._olddir = old
self.tmpdir.chdir()
def _makefile(self, ext, args, kwargs, encoding='utf-8'):
items = list(kwargs.items())
@@ -495,17 +524,15 @@ class Testdir:
def makefile(self, ext, *args, **kwargs):
"""Create a new file in the testdir.
ext: The extension the file should use, including the dot.
E.g. ".py".
ext: The extension the file should use, including the dot, e.g. `.py`.
args: All args will be treated as strings and joined using
newlines. The result will be written as contents to the
file. The name of the file will be based on the test
function requesting this fixture.
args: All args will be treated as strings and joined using newlines.
The result will be written as contents to the file. The name of the
file will be based on the test function requesting this fixture.
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
kwargs: Each keyword is the name of a file, while the value of
it will be written as contents of the file.
kwargs: Each keyword is the name of a file, while the value of it will
be written as contents of the file.
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
"""
@@ -535,14 +562,16 @@ class Testdir:
def syspathinsert(self, path=None):
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
This is undone automatically after the test.
This is undone automatically when this object dies at the end of each
test.
"""
if path is None:
path = self.tmpdir
sys.path.insert(0, str(path))
# a call to syspathinsert() usually means that the caller
# wants to import some dynamically created files.
# with python3 we thus invalidate import caches.
# a call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches
self._possibly_invalidate_import_caches()
def _possibly_invalidate_import_caches(self):
@@ -562,8 +591,8 @@ class Testdir:
def mkpydir(self, name):
"""Create a new python package.
This creates a (sub)directory with an empty ``__init__.py``
file so that is recognised as a python package.
This creates a (sub)directory with an empty ``__init__.py`` file so it
gets recognised as a python package.
"""
p = self.mkdir(name)
@@ -576,10 +605,10 @@ class Testdir:
"""Return the collection node of a file.
:param config: :py:class:`_pytest.config.Config` instance, see
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to
create the configuration.
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the
configuration
:param arg: A :py:class:`py.path.local` instance of the file.
:param arg: a :py:class:`py.path.local` instance of the file
"""
session = Session(config)
@@ -593,11 +622,10 @@ class Testdir:
def getpathnode(self, path):
"""Return the collection node of a file.
This is like :py:meth:`getnode` but uses
:py:meth:`parseconfigure` to create the (configured) pytest
Config instance.
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
create the (configured) pytest Config instance.
:param path: A :py:class:`py.path.local` instance of the file.
:param path: a :py:class:`py.path.local` instance of the file
"""
config = self.parseconfigure(path)
@@ -611,8 +639,8 @@ class Testdir:
def genitems(self, colitems):
"""Generate all test items from a collection node.
This recurses into the collection node and returns a list of
all the test items contained within.
This recurses into the collection node and returns a list of all the
test items contained within.
"""
session = colitems[0].session
@@ -624,10 +652,10 @@ class Testdir:
def runitem(self, source):
"""Run the "test_func" Item.
The calling test instance (the class which contains the test
method) must provide a ``.getrunner()`` method which should
return a runner which can run the test protocol for a single
item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
The calling test instance (class containing the test method) must
provide a ``.getrunner()`` method which should return a runner which
can run the test protocol for a single item, e.g.
:py:func:`_pytest.runner.runtestprotocol`.
"""
# used from runner functional tests
@@ -641,14 +669,14 @@ class Testdir:
"""Run a test module in process using ``pytest.main()``.
This run writes "source" into a temporary file and runs
``pytest.main()`` on it, returning a :py:class:`HookRecorder`
instance for the result.
``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
for the result.
:param source: The source code of the test module.
:param source: the source code of the test module
:param cmdlineargs: Any extra command line arguments to use.
:param cmdlineargs: any extra command line arguments to use
:return: :py:class:`HookRecorder` instance of the result.
:return: :py:class:`HookRecorder` instance of the result
"""
p = self.makepyfile(source)
@@ -658,13 +686,9 @@ class Testdir:
def inline_genitems(self, *args):
"""Run ``pytest.main(['--collectonly'])`` in-process.
Returns a tuple of the collected items and a
:py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself like
:py:meth:`inline_run`. However the return value is a tuple of
the collection items and a :py:class:`HookRecorder` instance.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself like :py:meth:`inline_run`, but returns a
tuple of the collected items and a :py:class:`HookRecorder` instance.
"""
rec = self.inline_run("--collect-only", *args)
@@ -674,60 +698,78 @@ class Testdir:
def inline_run(self, *args, **kwargs):
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself. This means it can
return a :py:class:`HookRecorder` instance which gives more
detailed results from then run then can be done by matching
stdout/stderr from :py:meth:`runpytest`.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself. This means it can return a
:py:class:`HookRecorder` instance which gives more detailed results
from that run than can be done by matching stdout/stderr from
:py:meth:`runpytest`.
:param args: Any command line arguments to pass to
:py:func:`pytest.main`.
:param args: command line arguments to pass to :py:func:`pytest.main`
:param plugin: (keyword-only) Extra plugin instances the
``pytest.main()`` instance should use.
:param plugin: (keyword-only) extra plugin instances the
``pytest.main()`` instance should use
:return: a :py:class:`HookRecorder` instance
:return: A :py:class:`HookRecorder` instance.
"""
# When running py.test inline any plugins active in the main
# test process are already imported. So this disables the
# warning which will trigger to say they can no longer be
# rewritten, which is fine as they are already rewritten.
orig_warn = AssertionRewritingHook._warn_already_imported
finalizers = []
try:
# When running py.test inline any plugins active in the main test
# process are already imported. So this disables the warning which
# will trigger to say they can no longer be rewritten, which is
# fine as they have already been rewritten.
orig_warn = AssertionRewritingHook._warn_already_imported
def revert():
AssertionRewritingHook._warn_already_imported = orig_warn
def revert_warn_already_imported():
AssertionRewritingHook._warn_already_imported = orig_warn
finalizers.append(revert_warn_already_imported)
AssertionRewritingHook._warn_already_imported = lambda *a: None
self.request.addfinalizer(revert)
AssertionRewritingHook._warn_already_imported = lambda *a: None
# Any sys.module or sys.path changes done while running py.test
# inline should be reverted after the test run completes to avoid
# clashing with later inline tests run within the same pytest test,
# e.g. just because they use matching test module names.
finalizers.append(self.__take_sys_modules_snapshot().restore)
finalizers.append(SysPathsSnapshot().restore)
rec = []
# Important note:
# - our tests should not leave any other references/registrations
# laying around other than possibly loaded test modules
# referenced from sys.modules, as nothing will clean those up
# automatically
class Collect:
def pytest_configure(x, config):
rec.append(self.make_hook_recorder(config.pluginmanager))
rec = []
plugins = kwargs.get("plugins") or []
plugins.append(Collect())
ret = pytest.main(list(args), plugins=plugins)
self.delete_loaded_modules()
if len(rec) == 1:
reprec = rec.pop()
else:
class reprec:
pass
reprec.ret = ret
class Collect(object):
def pytest_configure(x, config):
rec.append(self.make_hook_recorder(config.pluginmanager))
# typically we reraise keyboard interrupts from the child run
# because it's our user requesting interruption of the testing
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
calls = reprec.getcalls("pytest_keyboard_interrupt")
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
raise KeyboardInterrupt()
return reprec
plugins = kwargs.get("plugins") or []
plugins.append(Collect())
ret = pytest.main(list(args), plugins=plugins)
if len(rec) == 1:
reprec = rec.pop()
else:
class reprec(object):
pass
reprec.ret = ret
# typically we reraise keyboard interrupts from the child run
# because it's our user requesting interruption of the testing
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
calls = reprec.getcalls("pytest_keyboard_interrupt")
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
raise KeyboardInterrupt()
return reprec
finally:
for finalizer in finalizers:
finalizer()
def runpytest_inprocess(self, *args, **kwargs):
""" Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides. """
"""Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides.
"""
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
@@ -738,13 +780,13 @@ class Testdir:
reprec = self.inline_run(*args, **kwargs)
except SystemExit as e:
class reprec:
class reprec(object):
ret = e.args[0]
except Exception:
traceback.print_exc()
class reprec:
class reprec(object):
ret = 3
finally:
out, err = capture.readouterr()
@@ -759,7 +801,7 @@ class Testdir:
return res
def runpytest(self, *args, **kwargs):
""" Run pytest inline or in a subprocess, depending on the command line
"""Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
@@ -780,13 +822,13 @@ class Testdir:
def parseconfig(self, *args):
"""Return a new pytest Config instance from given commandline args.
This invokes the pytest bootstrapping code in _pytest.config
to create a new :py:class:`_pytest.core.PluginManager` and
call the pytest_cmdline_parse hook to create new
This invokes the pytest bootstrapping code in _pytest.config to create
a new :py:class:`_pytest.core.PluginManager` and call the
pytest_cmdline_parse hook to create a new
:py:class:`_pytest.config.Config` instance.
If :py:attr:`plugins` has been populated they should be plugin
modules which will be registered with the PluginManager.
If :py:attr:`plugins` has been populated they should be plugin modules
to be registered with the PluginManager.
"""
args = self._ensure_basetemp(args)
@@ -802,9 +844,8 @@ class Testdir:
def parseconfigure(self, *args):
"""Return a new pytest configured Config instance.
This returns a new :py:class:`_pytest.config.Config` instance
like :py:meth:`parseconfig`, but also calls the
pytest_configure hook.
This returns a new :py:class:`_pytest.config.Config` instance like
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
"""
config = self.parseconfig(*args)
@@ -815,14 +856,14 @@ class Testdir:
def getitem(self, source, funcname="test_func"):
"""Return the test item for a test function.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning the test item
for the requested function name.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning the test item for the requested
function name.
:param source: The module source.
:param source: the module source
:param funcname: The name of the test function for which the
Item must be returned.
:param funcname: the name of the test function for which to return a
test item
"""
items = self.getitems(source)
@@ -835,9 +876,8 @@ class Testdir:
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning all test items
contained within.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning all test items contained within.
"""
modcol = self.getmodulecol(source)
@@ -846,17 +886,17 @@ class Testdir:
def getmodulecol(self, source, configargs=(), withinit=False):
"""Return the module collection node for ``source``.
This writes ``source`` to a file using :py:meth:`makepyfile`
and then runs the pytest collection on it, returning the
collection node for the test module.
This writes ``source`` to a file using :py:meth:`makepyfile` and then
runs the pytest collection on it, returning the collection node for the
test module.
:param source: The source code of the module to collect.
:param source: the source code of the module to collect
:param configargs: Any extra arguments to pass to
:py:meth:`parseconfigure`.
:param configargs: any extra arguments to pass to
:py:meth:`parseconfigure`
:param withinit: Whether to also write a ``__init__.py`` file
to the temporary directory to ensure it is a package.
:param withinit: whether to also write an ``__init__.py`` file to the
same directory to ensure it is a package
"""
kw = {self.request.function.__name__: Source(source).strip()}
@@ -871,13 +911,12 @@ class Testdir:
def collect_by_name(self, modcol, name):
"""Return the collection node for name from the module collection.
This will search a module collection node for a collection
node matching the given name.
This will search a module collection node for a collection node
matching the given name.
:param modcol: A module collection node, see
:py:meth:`getmodulecol`.
:param modcol: a module collection node; see :py:meth:`getmodulecol`
:param name: The name of the node to return.
:param name: the name of the node to return
"""
if modcol not in self._mod_collections:
@@ -889,8 +928,8 @@ class Testdir:
def popen(self, cmdargs, stdout, stderr, **kw):
"""Invoke subprocess.Popen.
This calls subprocess.Popen making sure the current working
directory is the PYTHONPATH.
This calls subprocess.Popen making sure the current working directory
is in the PYTHONPATH.
You probably want to use :py:meth:`run` instead.
@@ -908,8 +947,7 @@ class Testdir:
def run(self, *cmdargs):
"""Run a command with arguments.
Run a process using subprocess.Popen saving the stdout and
stderr.
Run a process using subprocess.Popen saving the stdout and stderr.
Returns a :py:class:`RunResult`.
@@ -952,14 +990,15 @@ class Testdir:
print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a pytest.exe
# we cannot use `(sys.executable, script)` because on Windows the
# script is e.g. `pytest.exe`
return (sys.executable, PYTEST_FULLPATH) # noqa
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
Returns a :py:class:`RunResult`.
"""
return self.run(sys.executable, script)
@@ -970,25 +1009,18 @@ class Testdir:
def runpytest_subprocess(self, *args, **kwargs):
"""Run pytest as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will added
using the ``-p`` command line option. Addtionally
``--basetemp`` is used put any temporary files and directories
in a numbered directory prefixed with "runpytest-" so they do
not conflict with the normal numberd pytest location for
temporary files and directories.
Any plugins added to the :py:attr:`plugins` list will added using the
``-p`` command line option. Additionally ``--basetemp`` is used put
any temporary files and directories in a numbered directory prefixed
with "runpytest-" so they do not conflict with the normal numbered
pytest location for temporary files and directories.
Returns a :py:class:`RunResult`.
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
# for x in args:
# if '--confcutdir' in str(x):
# break
# else:
# pass
# args = ('--confcutdir=.',) + args
args = ('--basetemp=%s' % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
args = ('-p', plugins[0]) + args
@@ -998,8 +1030,8 @@ class Testdir:
def spawn_pytest(self, string, expect_timeout=10.0):
"""Run pytest using pexpect.
This makes sure to use the right pytest and sets up the
temporary directory locations.
This makes sure to use the right pytest and sets up the temporary
directory locations.
The pexpect child is returned.
@@ -1013,6 +1045,7 @@ class Testdir:
"""Run a command using pexpect.
The pexpect child is returned.
"""
pexpect = pytest.importorskip("pexpect", "3.0")
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
@@ -1034,13 +1067,15 @@ def getdecoded(out):
py.io.saferepr(out),)
class LineComp:
class LineComp(object):
def __init__(self):
self.stringio = py.io.TextIO()
def assert_contains_lines(self, lines2):
""" assert that lines2 are contained (linearly) in lines1.
return a list of extralines found.
"""Assert that lines2 are contained (linearly) in lines1.
Return a list of extralines found.
"""
__tracebackhide__ = True
val = self.stringio.getvalue()
@@ -1050,14 +1085,14 @@ class LineComp:
return LineMatcher(lines1).fnmatch_lines(lines2)
class LineMatcher:
class LineMatcher(object):
"""Flexible matching of text.
This is a convenience class to test large texts like the output of
commands.
The constructor takes a list of lines without their trailing
newlines, i.e. ``text.splitlines()``.
The constructor takes a list of lines without their trailing newlines, i.e.
``text.splitlines()``.
"""
@@ -1077,18 +1112,19 @@ class LineMatcher:
return lines2
def fnmatch_lines_random(self, lines2):
"""Check lines exist in the output using ``fnmatch.fnmatch``, in any order.
"""Check lines exist in the output using in any order.
Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
lines which have to occur in the output, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
"""
self._match_lines_random(lines2, fnmatch)
def re_match_lines_random(self, lines2):
"""Check lines exist in the output using ``re.match``, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
The argument is a list of lines which have to occur in the output, in
any order.
"""
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
@@ -1096,8 +1132,8 @@ class LineMatcher:
def _match_lines_random(self, lines2, match_func):
"""Check lines exist in the output.
The argument is a list of lines which have to occur in the
output, in any order. Each line can contain glob whildcards.
The argument is a list of lines which have to occur in the output, in
any order. Each line can contain glob whildcards.
"""
lines2 = self._getlines(lines2)
@@ -1114,6 +1150,7 @@ class LineMatcher:
"""Return all lines following the given line in the text.
The given line can contain glob wildcards.
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
@@ -1130,10 +1167,9 @@ class LineMatcher:
def fnmatch_lines(self, lines2):
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
The argument is a list of lines which have to match and can
use glob wildcards. If they do not match a pytest.fail() is
called. The matches and non-matches are also printed on
stdout.
The argument is a list of lines which have to match and can use glob
wildcards. If they do not match a pytest.fail() is called. The
matches and non-matches are also printed on stdout.
"""
self._match_lines(lines2, fnmatch, 'fnmatch')
@@ -1144,21 +1180,22 @@ class LineMatcher:
The argument is a list of lines which have to match using ``re.match``.
If they do not match a pytest.fail() is called.
The matches and non-matches are also printed on
stdout.
The matches and non-matches are also printed on stdout.
"""
self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
def _match_lines(self, lines2, match_func, match_nickname):
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
:param list[str] lines2: list of string patterns to match. The actual format depends on
``match_func``.
:param match_func: a callable ``match_func(line, pattern)`` where line is the captured
line from stdout/stderr and pattern is the matching pattern.
:param list[str] lines2: list of string patterns to match. The actual
format depends on ``match_func``
:param match_func: a callable ``match_func(line, pattern)`` where line
is the captured line from stdout/stderr and pattern is the matching
pattern
:param str match_nickname: the nickname for the match function that
will be logged to stdout when a match occurs
:param str match_nickname: the nickname for the match function that will be logged
to stdout when a match occurs.
"""
lines2 = self._getlines(lines2)
lines1 = self.lines[:]

View File

@@ -19,7 +19,7 @@ from _pytest.config import hookimpl
import _pytest
import pluggy
from _pytest import fixtures
from _pytest import main
from _pytest import nodes
from _pytest import deprecated
from _pytest.compat import (
isclass, isfunction, is_generator, ascii_escaped,
@@ -28,11 +28,19 @@ from _pytest.compat import (
safe_str, getlocation, enum,
)
from _pytest.outcomes import fail
from _pytest.mark import transfer_markers
from _pytest.mark.structures import transfer_markers
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
cutdir2 = py.path.local(_pytest.__file__).dirpath()
cutdir3 = py.path.local(py.__file__).dirpath()
# relative paths that we use to filter traceback entries from appearing to the user;
# see filter_traceback
# note: if we need to add more paths than what we have now we should probably use a list
# for better maintenance
_pluggy_dir = py.path.local(pluggy.__file__.rstrip("oc"))
# pluggy is either a package or a single module depending on the version
if _pluggy_dir.basename == '__init__.py':
_pluggy_dir = _pluggy_dir.dirpath()
_pytest_dir = py.path.local(_pytest.__file__).dirpath()
_py_dir = py.path.local(py.__file__).dirpath()
def filter_traceback(entry):
@@ -47,10 +55,10 @@ def filter_traceback(entry):
is_generated = '<' in raw_filename and '>' in raw_filename
if is_generated:
return False
# entry.path might point to an inexisting file, in which case it will
# alsso return a str object. see #1133
# entry.path might point to an non-existing file, in which case it will
# also return a str object. see #1133
p = py.path.local(entry.path)
return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
return not p.relto(_pluggy_dir) and not p.relto(_pytest_dir) and not p.relto(_py_dir)
def pyobj_property(name):
@@ -261,7 +269,7 @@ class PyobjMixin(PyobjContext):
return fspath, lineno, modpath
class PyCollector(PyobjMixin, main.Collector):
class PyCollector(PyobjMixin, nodes.Collector):
def funcnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_functions', name)
@@ -386,7 +394,7 @@ class PyCollector(PyobjMixin, main.Collector):
)
class Module(main.File, PyCollector):
class Module(nodes.File, PyCollector):
""" Collector for test classes and functions. """
def _getobj(self):
@@ -563,7 +571,6 @@ class FunctionMixin(PyobjMixin):
if ntraceback == traceback:
ntraceback = ntraceback.cut(path=path)
if ntraceback == traceback:
# ntraceback = ntraceback.cut(excludepath=cutdir2)
ntraceback = ntraceback.filter(filter_traceback)
if not ntraceback:
ntraceback = traceback
@@ -710,7 +717,7 @@ class CallSpec2(object):
class Metafunc(fixtures.FuncargnamesCompatAttr):
"""
Metafunc objects are passed to the ``pytest_generate_tests`` hook.
Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
They help to inspect a test function and to generate tests according to
test configuration or values specified in the class or module where a
test function is defined.
@@ -778,8 +785,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
from _pytest.fixtures import scope2index
from _pytest.mark import ParameterSet
from py.io import saferepr
argnames, parameters = ParameterSet._for_parameterize(
argnames, argvalues, self.function)
argnames, parameters = ParameterSet._for_parametrize(
argnames, argvalues, self.function, self.config)
del argvalues
if scope is None:
@@ -933,7 +941,7 @@ def _idval(val, argname, idx, idfn, config=None):
return ascii_escaped(val.pattern)
elif enum is not None and isinstance(val, enum.Enum):
return str(val)
elif isclass(val) and hasattr(val, '__name__'):
elif (isclass(val) or isfunction(val)) and hasattr(val, '__name__'):
return val.__name__
return str(argname) + str(idx)
@@ -1090,7 +1098,7 @@ def write_docstring(tw, doc):
tw.write(INDENT + line + "\n")
class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
""" a Function Item is responsible for setting up and executing a
Python test function.
"""

View File

@@ -2,7 +2,8 @@ import math
import sys
import py
from six.moves import zip
from six.moves import zip, filterfalse
from more_itertools.more import always_iterable
from _pytest.compat import isclass
from _pytest.outcomes import fail
@@ -30,6 +31,10 @@ class ApproxBase(object):
or sequences of numbers.
"""
# Tell numpy to use our `__eq__` operator instead of its
__array_ufunc__ = None
__array_priority__ = 100
def __init__(self, expected, rel=None, abs=None, nan_ok=False):
self.expected = expected
self.abs = abs
@@ -68,14 +73,13 @@ class ApproxNumpy(ApproxBase):
Perform approximate comparisons for numpy arrays.
"""
# Tell numpy to use our `__eq__` operator instead of its.
__array_priority__ = 100
def __repr__(self):
# It might be nice to rewrite this function to account for the
# shape of the array...
import numpy as np
return "approx({0!r})".format(list(
self._approx_scalar(x) for x in self.expected))
self._approx_scalar(x) for x in np.asarray(self.expected)))
if sys.version_info[0] == 2:
__cmp__ = _cmp_raises_type_error
@@ -83,12 +87,15 @@ class ApproxNumpy(ApproxBase):
def __eq__(self, actual):
import numpy as np
try:
actual = np.asarray(actual)
except: # noqa
raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
# self.expected is supposed to always be an array here
if actual.shape != self.expected.shape:
if not np.isscalar(actual):
try:
actual = np.asarray(actual)
except: # noqa
raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
if not np.isscalar(actual) and actual.shape != self.expected.shape:
return False
return ApproxBase.__eq__(self, actual)
@@ -96,11 +103,16 @@ class ApproxNumpy(ApproxBase):
def _yield_comparisons(self, actual):
import numpy as np
# We can be sure that `actual` is a numpy array, because it's
# casted in `__eq__` before being passed to `ApproxBase.__eq__`,
# which is the only method that calls this one.
for i in np.ndindex(self.expected.shape):
yield actual[i], self.expected[i]
# `actual` can either be a numpy array or a scalar, it is treated in
# `__eq__` before being passed to `ApproxBase.__eq__`, which is the
# only method that calls this one.
if np.isscalar(actual):
for i in np.ndindex(self.expected.shape):
yield actual, np.asscalar(self.expected[i])
else:
for i in np.ndindex(self.expected.shape):
yield np.asscalar(actual[i]), np.asscalar(self.expected[i])
class ApproxMapping(ApproxBase):
@@ -130,9 +142,6 @@ class ApproxSequence(ApproxBase):
Perform approximate comparisons for sequences of numbers.
"""
# Tell numpy to use our `__eq__` operator instead of its.
__array_priority__ = 100
def __repr__(self):
seq_type = type(self.expected)
if seq_type not in (tuple, list, set):
@@ -153,6 +162,8 @@ class ApproxScalar(ApproxBase):
"""
Perform approximate comparisons for single numbers only.
"""
DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
DEFAULT_RELATIVE_TOLERANCE = 1e-6
def __repr__(self):
"""
@@ -186,6 +197,8 @@ class ApproxScalar(ApproxBase):
Return true if the given value is equal to the expected value within
the pre-specified tolerance.
"""
if _is_numpy_array(actual):
return ApproxNumpy(actual, self.abs, self.rel, self.nan_ok) == self.expected
# Short-circuit exact equality.
if actual == self.expected:
@@ -223,7 +236,7 @@ class ApproxScalar(ApproxBase):
# Figure out what the absolute tolerance should be. ``self.abs`` is
# either None or a value specified by the user.
absolute_tolerance = set_default(self.abs, 1e-12)
absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE)
if absolute_tolerance < 0:
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
@@ -241,7 +254,7 @@ class ApproxScalar(ApproxBase):
# we've made sure the user didn't ask for an absolute tolerance only,
# because we don't want to raise errors about the relative tolerance if
# we aren't even going to use it.
relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
relative_tolerance = set_default(self.rel, self.DEFAULT_RELATIVE_TOLERANCE) * abs(self.expected)
if relative_tolerance < 0:
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
@@ -252,6 +265,13 @@ class ApproxScalar(ApproxBase):
return max(relative_tolerance, absolute_tolerance)
class ApproxDecimal(ApproxScalar):
from decimal import Decimal
DEFAULT_ABSOLUTE_TOLERANCE = Decimal('1e-12')
DEFAULT_RELATIVE_TOLERANCE = Decimal('1e-6')
def approx(expected, rel=None, abs=None, nan_ok=False):
"""
Assert that two numbers (or two sets of numbers) are equal to each other
@@ -298,12 +318,18 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
And ``numpy`` arrays::
``numpy`` arrays::
>>> import numpy as np # doctest: +SKIP
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
True
And for a ``numpy`` array against a scalar::
>>> import numpy as np # doctest: +SKIP
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
True
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
@@ -401,6 +427,7 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
from collections import Mapping, Sequence
from _pytest.compat import STRING_TYPES as String
from decimal import Decimal
# Delegate the comparison to a class that knows how to deal with the type
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
@@ -422,6 +449,8 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, String):
cls = ApproxSequence
elif isinstance(expected, Decimal):
cls = ApproxDecimal
else:
cls = ApproxScalar
@@ -453,6 +482,10 @@ def raises(expected_exception, *args, **kwargs):
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
:arg message: if specified, provides a custom failure message if the
exception is not raised
:arg match: if specified, asserts that the exception matches a text or regex
This helper produces a ``ExceptionInfo()`` object (see below).
You may use this function as a context manager::
@@ -531,8 +564,9 @@ def raises(expected_exception, *args, **kwargs):
The string will be evaluated using the same ``locals()`` and ``globals()``
at the moment of the ``raises`` call.
.. autoclass:: _pytest._code.ExceptionInfo
:members:
.. currentmodule:: _pytest._code
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
.. note::
Similar to caught exception objects in Python, explicitly clearing
@@ -550,14 +584,10 @@ def raises(expected_exception, *args, **kwargs):
"""
__tracebackhide__ = True
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
if isinstance(expected_exception, tuple):
for exc in expected_exception:
if not isclass(exc):
raise TypeError(msg % type(exc))
elif not isclass(expected_exception):
raise TypeError(msg % type(expected_exception))
for exc in filterfalse(isclass, always_iterable(expected_exception)):
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
raise TypeError(msg % type(exc))
message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
@@ -567,7 +597,6 @@ def raises(expected_exception, *args, **kwargs):
message = kwargs.pop("message")
if "match" in kwargs:
match_expr = kwargs.pop("match")
message += " matching '{0}'".format(match_expr)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
code, = args
@@ -614,6 +643,6 @@ class RaisesContext(object):
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear()
if self.match_expr:
if self.match_expr and suppress_exception:
self.excinfo.match(self.match_expr)
return suppress_exception

View File

@@ -16,10 +16,7 @@ from _pytest.outcomes import fail
@yield_fixture
def recwarn():
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
"""Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
See http://docs.python.org/library/warnings.html for information
on warning categories.
@@ -88,11 +85,11 @@ class _DeprecatedCallContext(object):
def warns(expected_warning, *args, **kwargs):
"""Assert that code raises a particular class of warning.
Specifically, the input @expected_warning can be a warning class or
tuple of warning classes, and the code must return that warning
(if a single class) or one of those warnings (if a tuple).
Specifically, the parameter ``expected_warning`` can be a warning class or
sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or
classes.
This helper produces a list of ``warnings.WarningMessage`` objects,
This helper produces a list of :class:`warnings.WarningMessage` objects,
one for each warning raised.
This function can be used as a context manager, or any of the other ways

View File

@@ -60,6 +60,9 @@ def pytest_runtest_protocol(item, nextitem):
nodeid=item.nodeid, location=item.location,
)
runtestprotocol(item, nextitem=nextitem)
item.ihook.pytest_runtest_logfinish(
nodeid=item.nodeid, location=item.location,
)
return True
@@ -175,7 +178,7 @@ def call_runtest_hook(item, when, **kwds):
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
class CallInfo:
class CallInfo(object):
""" Result/Exception info a function invocation. """
#: None or ExceptionInfo object.
excinfo = None
@@ -253,6 +256,14 @@ class BaseReport(object):
exc = tw.stringio.getvalue()
return exc.strip()
@property
def caplog(self):
"""Return captured log lines, if log capturing is enabled
.. versionadded:: 3.5
"""
return '\n'.join(content for (prefix, content) in self.get_sections('Captured log'))
@property
def capstdout(self):
"""Return captured text from stdout, if capturing is enabled
@@ -306,7 +317,7 @@ def pytest_runtest_makereport(item, call):
sections.append(("Captured %s %s" % (key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
sections, duration)
sections, duration, user_properties=item.user_properties)
class TestReport(BaseReport):
@@ -315,7 +326,7 @@ class TestReport(BaseReport):
"""
def __init__(self, nodeid, location, keywords, outcome,
longrepr, when, sections=(), duration=0, **extra):
longrepr, when, sections=(), duration=0, user_properties=(), **extra):
#: normalized collection node id
self.nodeid = nodeid
@@ -337,6 +348,10 @@ class TestReport(BaseReport):
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
self.when = when
#: user properties is a list of tuples (name, value) that holds user
#: defined properties of the test
self.user_properties = user_properties
#: list of pairs ``(str, str)`` of extra information which needs to
#: marshallable. Used by pytest to add captured text
#: from ``stdout`` and ``stderr``, but may be used by other plugins

View File

@@ -1,14 +1,10 @@
""" support for skip/xfail functions and markers. """
from __future__ import absolute_import, division, print_function
import os
import six
import sys
import traceback
from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.outcomes import fail, skip, xfail, TEST_OUTCOME
from _pytest.mark.evaluate import MarkEvaluator
from _pytest.outcomes import fail, skip, xfail
def pytest_addoption(parser):
@@ -17,11 +13,11 @@ def pytest_addoption(parser):
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
parser.addini("xfail_strict", "default for the strict parameter of xfail "
"markers when not given explicitly (default: "
"False)",
default=False,
type="bool")
parser.addini("xfail_strict",
"default for the strict parameter of xfail "
"markers when not given explicitly (default: False)",
default=False,
type="bool")
def pytest_configure(config):
@@ -60,112 +56,6 @@ def pytest_configure(config):
)
class MarkEvaluator(object):
def __init__(self, item, name):
self.item = item
self._marks = None
self._mark = None
self._mark_name = name
def __bool__(self):
self._marks = self._get_marks()
return bool(self._marks)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
def _get_marks(self):
keyword = self.item.keywords.get(self._mark_name)
if isinstance(keyword, MarkDecorator):
return [keyword.mark]
elif isinstance(keyword, MarkInfo):
return [x.combined for x in keyword]
else:
return []
def invalidraise(self, exc):
raises = self.get('raises')
if not raises:
return
return not isinstance(exc, raises)
def istrue(self):
try:
return self._istrue()
except TEST_OUTCOME:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^", ]
msg.append("SyntaxError: invalid syntax")
else:
msg = traceback.format_exception_only(*self.exc[:2])
fail("Error evaluating %r expression\n"
" %s\n"
"%s"
% (self._mark_name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config}
if hasattr(self.item, 'obj'):
d.update(self.item.obj.__globals__)
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
self._marks = self._get_marks()
if self._marks:
self.result = False
for mark in self._marks:
self._mark = mark
if 'condition' in mark.kwargs:
args = (mark.kwargs['condition'],)
else:
args = mark.args
for expr in args:
self.expr = expr
if isinstance(expr, six.string_types):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in mark.kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = mark.kwargs.get('reason', None)
self.expr = expr
return self.result
if not args:
self.result = True
self.reason = mark.kwargs.get('reason', None)
return self.result
return False
def get(self, attr, default=None):
if self._mark is None:
return default
return self._mark.kwargs.get(attr, default)
def getexplanation(self):
expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + str(self.expr)
return expl
@hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
# Check if skip or skipif are specified as pytest marks
@@ -239,7 +129,7 @@ def pytest_runtest_makereport(item, call):
rep.outcome = "passed"
rep.wasxfail = rep.longrepr
elif item.config.option.runxfail:
pass # don't interefere
pass # don't interefere
elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
@@ -261,7 +151,7 @@ def pytest_runtest_makereport(item, call):
else:
rep.outcome = "passed"
rep.wasxfail = explanation
elif item._skipped_by_mark and rep.skipped and type(rep.longrepr) is tuple:
elif getattr(item, '_skipped_by_mark', False) and rep.skipped and type(rep.longrepr) is tuple:
# skipped by mark.skipif; change the location of the failure
# to point to the item definition, otherwise it will display
# the location of where the skip exception was raised within pytest
@@ -269,6 +159,7 @@ def pytest_runtest_makereport(item, call):
filename, line = item.location[:2]
rep.longrepr = filename, line, reason
# called by terminalreporter progress reporting
@@ -279,6 +170,7 @@ def pytest_report_teststatus(report):
elif report.passed:
return "xpassed", "X", ("XPASS", {'yellow': True})
# called by the terminalreporter instance/plugin
@@ -294,18 +186,8 @@ def pytest_terminal_summary(terminalreporter):
lines = []
for char in tr.reportchars:
if char == "x":
show_xfailed(terminalreporter, lines)
elif char == "X":
show_xpassed(terminalreporter, lines)
elif char in "fF":
show_simple(terminalreporter, lines, 'failed', "FAIL %s")
elif char in "sS":
show_skipped(terminalreporter, lines)
elif char == "E":
show_simple(terminalreporter, lines, 'error', "ERROR %s")
elif char == 'p':
show_simple(terminalreporter, lines, 'passed', "PASSED %s")
action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None)
action(terminalreporter, lines)
if lines:
tr._tw.sep("=", "short test summary info")
@@ -341,18 +223,6 @@ def show_xpassed(terminalreporter, lines):
lines.append("XPASS %s %s" % (pos, reason))
def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'):
config._evalcache = {}
try:
return config._evalcache[expr]
except KeyError:
import _pytest._code
exprcode = _pytest._code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x
def folded_skips(skipped):
d = {}
for event in skipped:
@@ -364,7 +234,7 @@ def folded_skips(skipped):
# TODO: revisit after marks scope would be fixed
when = getattr(event, 'when', None)
if when == 'setup' and 'skip' in keywords and 'pytestmark' not in keywords:
key = (key[0], None, key[2], )
key = (key[0], None, key[2])
d.setdefault(key, []).append(event)
values = []
for key, events in d.items():
@@ -395,3 +265,23 @@ def show_skipped(terminalreporter, lines):
lines.append(
"SKIP [%d] %s: %s" %
(num, fspath, reason))
def shower(stat, format):
def show_(terminalreporter, lines):
return show_simple(terminalreporter, lines, stat, format)
return show_
REPORTCHAR_ACTIONS = {
'x': show_xfailed,
'X': show_xpassed,
'f': shower('failed', "FAIL %s"),
'F': shower('failed', "FAIL %s"),
's': show_skipped,
'S': show_skipped,
'p': shower('passed', "PASSED %s"),
'E': shower('error', "ERROR %s")
}

View File

@@ -19,12 +19,45 @@ from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import argparse
class MoreQuietAction(argparse.Action):
"""
a modified copy of the argparse count action which counts down and updates
the legacy quiet attribute at the same time
used to unify verbosity handling
"""
def __init__(self,
option_strings,
dest,
default=None,
required=False,
help=None):
super(MoreQuietAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
default=default,
required=required,
help=help)
def __call__(self, parser, namespace, values, option_string=None):
new_count = getattr(namespace, self.dest, 0) - 1
setattr(namespace, self.dest, new_count)
# todo Deprecate config.quiet
namespace.quiet = getattr(namespace, 'quiet', 0) + 1
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group._addoption('-v', '--verbose', action="count",
dest="verbose", default=0, help="increase verbosity."),
group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decrease verbosity."),
group._addoption('-v', '--verbose', action="count", default=0,
dest="verbose", help="increase verbosity."),
group._addoption('-q', '--quiet', action=MoreQuietAction, default=0,
dest="verbose", help="decrease verbosity."),
group._addoption("--verbosity", dest='verbose', type=int, default=0,
help="set verbosity")
group._addoption('-r',
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
@@ -42,6 +75,11 @@ def pytest_addoption(parser):
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (auto/long/short/line/native/no).")
group._addoption('--show-capture',
action="store", dest="showcapture",
choices=['no', 'stdout', 'stderr', 'log', 'all'], default='all',
help="Controls how captured stdout/stderr/log is shown on failed tests. "
"Default is 'all'.")
group._addoption('--fulltrace', '--full-trace',
action="store_true", default=False,
help="don't cut any tracebacks (default is to cut).")
@@ -56,7 +94,6 @@ def pytest_addoption(parser):
def pytest_configure(config):
config.option.verbose -= config.option.quiet
reporter = TerminalReporter(config, sys.stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
@@ -94,7 +131,7 @@ def pytest_report_teststatus(report):
return report.outcome, letter, report.outcome.upper()
class WarningReport:
class WarningReport(object):
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
"""
@@ -129,7 +166,7 @@ class WarningReport:
return None
class TerminalReporter:
class TerminalReporter(object):
def __init__(self, config, file=None):
import _pytest.config
self.config = config
@@ -152,8 +189,18 @@ class TerminalReporter:
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
self._progress_items_reported = 0
self._show_progress_info = self.config.getini('console_output_style') == 'progress'
self._progress_nodeids_reported = set()
self._show_progress_info = self._determine_show_progress_info()
def _determine_show_progress_info(self):
"""Return True if we should display progress information based on the current config"""
# do not show progress if we are not capturing output (#3038)
if self.config.getoption('capture') == 'no':
return False
# 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'
def hasopt(self, char):
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
@@ -178,7 +225,6 @@ class TerminalReporter:
if extra:
self._tw.write(extra, **kwargs)
self.currentfspath = -2
self._write_progress_information_filling_space()
def ensure_newline(self):
if self.currentfspath:
@@ -268,14 +314,13 @@ class TerminalReporter:
# probably passed setup/teardown
return
running_xdist = hasattr(rep, 'node')
self._progress_items_reported += 1
if self.verbosity <= 0:
if not running_xdist and self.showfspath:
self.write_fspath_result(rep.nodeid, letter)
else:
self._tw.write(letter)
self._write_progress_if_past_edge()
else:
self._progress_nodeids_reported.add(rep.nodeid)
if markup is None:
if rep.passed:
markup = {'green': True}
@@ -288,6 +333,8 @@ class TerminalReporter:
line = self._locationline(rep.nodeid, *rep.location)
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
self.ensure_newline()
self._tw.write("[%s]" % rep.node.gateway.id)
@@ -299,31 +346,30 @@ class TerminalReporter:
self._tw.write(" " + line)
self.currentfspath = -2
def _write_progress_if_past_edge(self):
if not self._show_progress_info:
return
last_item = self._progress_items_reported == self._session.testscollected
if last_item:
self._write_progress_information_filling_space()
return
past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
if past_edge:
msg = self._get_progress_information_message()
self._tw.write(msg + '\n', cyan=True)
def pytest_runtest_logfinish(self, nodeid):
if self.verbosity <= 0 and self._show_progress_info:
self._progress_nodeids_reported.add(nodeid)
last_item = len(self._progress_nodeids_reported) == self._session.testscollected
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
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 = self._progress_items_reported * 100 // collected
progress = len(self._progress_nodeids_reported) * 100 // collected
return ' [{:3d}%]'.format(progress)
return ' [100%]'
def _write_progress_information_filling_space(self):
if not self._show_progress_info:
return
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)
@@ -349,6 +395,7 @@ class TerminalReporter:
errors = len(self.stats.get('error', []))
skipped = len(self.stats.get('skipped', []))
deselected = len(self.stats.get('deselected', []))
if final:
line = "collected "
else:
@@ -356,6 +403,8 @@ class TerminalReporter:
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
if errors:
line += " / %d errors" % errors
if deselected:
line += " / %d deselected" % deselected
if skipped:
line += " / %d skipped" % skipped
if self.isatty:
@@ -365,6 +414,7 @@ class TerminalReporter:
else:
self.write_line(line)
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(self):
self.report_collect(True)
@@ -465,16 +515,19 @@ class TerminalReporter:
if exitstatus in summary_exit_codes:
self.config.hook.pytest_terminal_summary(terminalreporter=self,
exitstatus=exitstatus)
self.summary_errors()
self.summary_failures()
self.summary_warnings()
self.summary_passes()
if exitstatus == EXIT_INTERRUPTED:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
self.summary_stats()
@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(self):
self.summary_errors()
self.summary_failures()
yield
self.summary_warnings()
self.summary_passes()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
@@ -615,7 +668,12 @@ class TerminalReporter:
def _outrep_summary(self, rep):
rep.toterminal(self._tw)
showcapture = self.config.option.showcapture
if showcapture == 'no':
return
for secname, content in rep.sections:
if showcapture != 'all' and showcapture not in secname:
continue
self._tw.sep("-", secname)
if content[-1:] == "\n":
content = content[:-1]
@@ -632,11 +690,6 @@ class TerminalReporter:
if self.verbosity == -1:
self.write_line(msg, **markup)
def summary_deselected(self):
if 'deselected' in self.stats:
self.write_sep("=", "%d tests deselected" % (
len(self.stats['deselected'])), bold=True)
def repr_pythonversion(v=None):
if v is None:

View File

@@ -8,7 +8,7 @@ import py
from _pytest.monkeypatch import MonkeyPatch
class TempdirFactory:
class TempdirFactory(object):
"""Factory for temporary directories under the common base temp directory.
The base directory can be configured using the ``--basetemp`` option.
@@ -116,6 +116,8 @@ def tmpdir(request, tmpdir_factory):
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
"""
name = request.node.name
name = re.sub(r"[\W]", "_", name)

1
changelog/1478.feature Normal file
View File

@@ -0,0 +1 @@
New ``--show-capture`` command-line option that allows to specify how to display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default).

View File

@@ -0,0 +1 @@
New ``--rootdir`` command-line option to override the rules for discovering the root directory. See `customize <https://docs.pytest.org/en/latest/customize.html>`_ in the documentation for details.

1
changelog/1713.doc.rst Normal file
View File

@@ -0,0 +1 @@
Added a `reference <https://docs.pytest.org/en/latest/reference.html>`_ page to the docs.

View File

@@ -0,0 +1 @@
Suppress ``IOError`` when closing the temporary file used for capturing streams in Python 2.7.

View File

@@ -0,0 +1 @@
Fixtures are now instantiated based on their scopes, with higher-scoped fixtures (such as ``session``) being instantiated first than lower-scoped fixtures (such as ``function``). The relative order of fixtures of the same scope is kept unchanged, based in their declaration order and their dependencies.

2
changelog/2770.feature Normal file
View File

@@ -0,0 +1,2 @@
``record_xml_property`` renamed to ``record_property`` and is now compatible with xdist, markers and any reporter.
``record_xml_property`` name is now deprecated.

View File

@@ -0,0 +1 @@
``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``.

1
changelog/3034.feature Normal file
View File

@@ -0,0 +1 @@
New ``--nf``, ``--new-first`` options: run new tests first followed by the rest of the tests, in both cases tests are also sorted by the file modified time, with more recent files coming first.

1
changelog/3084.removal Normal file
View File

@@ -0,0 +1 @@
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py files, because they "leak" to the entire directory tree.

1
changelog/3139.feature Normal file
View File

@@ -0,0 +1 @@
New ``--last-failed-no-failures`` command-line option that allows to specify the behavior of the cache plugin's ```--last-failed`` feature when no tests failed in the last run (or no cache was found): ``none`` or ``all`` (the default).

1
changelog/3149.feature Normal file
View File

@@ -0,0 +1 @@
New ``--doctest-continue-on-failure`` command-line option to enable doctests to show multiple failures for each snippet, instead of stopping at the first failure.

1
changelog/3156.feature Normal file
View File

@@ -0,0 +1 @@
Captured log messages are added to the ``<system-out>`` tag in the generated junit xml file if the ``junit_logging`` ini option is set to ``system-out``. If the value of this ini option is ``system-err`, the logs are written to ``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning captured logs are not written to the output file.

1
changelog/3189.feature Normal file
View File

@@ -0,0 +1 @@
Allow the logging plugin to handle ``pytest_runtest_logstart`` and ``pytest_runtest_logfinish`` hooks when live logs are enabled.

1
changelog/3190.feature Normal file
View File

@@ -0,0 +1 @@
Passing `--log-cli-level` in the command-line now automatically activates live logging.

View File

@@ -0,0 +1 @@
Add command line option ``--deselect`` to allow deselection of individual tests at collection time.

1
changelog/3204.feature Normal file
View File

@@ -0,0 +1 @@
Captured logs are printed before entering pdb.

1
changelog/3213.feature Normal file
View File

@@ -0,0 +1 @@
Deselected item count is now shown before tests are run, e.g. ``collected X items / Y deselected``.

View File

@@ -0,0 +1 @@
Change minimum requirement of ``attrs`` to ``17.4.0``.

View File

@@ -0,0 +1 @@
The builtin module ``platform`` is now available for use in expressions in ``pytest.mark``.

View File

@@ -0,0 +1 @@
Renamed example directories so all tests pass when ran from the base directory.

View File

@@ -0,0 +1 @@
Remove usage of deprecated ``metafunc.addcall`` in our own tests.

View File

@@ -0,0 +1 @@
Internal ``mark.py`` module has been turned into a package.

View File

@@ -0,0 +1 @@
The *short test summary info* section now is displayed after tracebacks and warnings in the terminal.

View File

@@ -0,0 +1 @@
``pytest`` now depends on the `more_itertools <https://github.com/erikrose/more-itertools>`_ package.

1
changelog/3268.trivial Normal file
View File

@@ -0,0 +1 @@
Added warning when ``[pytest]`` section is used in a ``.cfg`` file passed with ``-c``

View File

@@ -0,0 +1 @@
``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` constructors.

View File

@@ -0,0 +1 @@
Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and remove old support code for legacy Python versions.

1
changelog/3296.feature Normal file
View File

@@ -0,0 +1 @@
New ``--verbosity`` flag to set verbosity level explicitly.

1
changelog/3296.trivial Normal file
View File

@@ -0,0 +1 @@
Refactoring to unify how verbosity is handled internally.

View File

@@ -0,0 +1,2 @@
Fixed ``clear()`` method on ``caplog`` fixture which cleared ``records``,
but not the ``text`` property.

1
changelog/3304.trivial Normal file
View File

@@ -0,0 +1 @@
Internal refactoring to better integrate with argparse.

View File

@@ -0,0 +1 @@
Fix a python example when calling a fixture in doc/en/usage.rst

1
changelog/3312.feature Normal file
View File

@@ -0,0 +1 @@
``pytest.approx`` now accepts comparing a numpy array with a scalar.

View File

@@ -0,0 +1,3 @@
During test collection, when stdin is not allowed to be read, the
``DontReadFromStdin`` object still allow itself to be iterable and
resolved to an iterator without crashing.

32
changelog/README.rst Normal file
View File

@@ -0,0 +1,32 @@
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
text that will be added to the next ``CHANGELOG``.
The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users
instead of describing internal changes which are only relevant to the developers.
Make sure to use full sentences with correct case and punctuation, for example::
Fix issue with non-ascii messages from the ``warnings`` module.
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
* ``feature``: new user facing features, like new command-line options and new behavior.
* ``bugfix``: fixes a reported bug.
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
* ``removal``: feature deprecation or removal.
* ``vendor``: changes in packages vendored in pytest.
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
So for example: ``123.feature.rst``, ``456.bugfix.rst``.
If your PR fixes an issue, use that number here. If there is no issue,
then after you submit the PR and get the PR number you can add a
changelog using that instead.
If you are not sure what issue type to use, don't hesitate to ask in your PR.
Note that the ``towncrier`` tool will automatically
reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK
and encouraged. You can install ``towncrier`` and then run ``towncrier --draft``
if you want to get a preview of how your change will look in the final release notes.

View File

@@ -2,15 +2,16 @@
<ul>
<li><a href="{{ pathto('index') }}">Home</a></li>
<li><a href="{{ pathto('contents') }}">Contents</a></li>
<li><a href="{{ pathto('getting-started') }}">Install</a></li>
<li><a href="{{ pathto('contents') }}">Contents</a></li>
<li><a href="{{ pathto('reference') }}">Reference</a></li>
<li><a href="{{ pathto('example/index') }}">Examples</a></li>
<li><a href="{{ pathto('customize') }}">Customize</a></li>
<li><a href="{{ pathto('contact') }}">Contact</a></li>
<li><a href="{{ pathto('talks') }}">Talks/Posts</a></li>
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
<li><a href="{{ pathto('contributing') }}">Contributing</a></li>
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
<li><a href="{{ pathto('license') }}">License</a></li>
<li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
</ul>
{%- if display_toc %}

View File

@@ -1,7 +1,5 @@
<h3>Useful Links</h3>
<ul>
<li><a href="{{ pathto('index') }}">The pytest Website</a></li>
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
<li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
<li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>

View File

@@ -6,6 +6,11 @@ Release announcements
:maxdepth: 2
release-3.5.0
release-3.4.2
release-3.4.1
release-3.4.0
release-3.3.2
release-3.3.1
release-3.3.0
release-3.2.5

View File

@@ -0,0 +1,28 @@
pytest-3.3.2
=======================================
pytest 3.3.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 http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Antony Lee
* Austin
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Henk-Jaap Wagenaar
* Jurko Gospodnetić
* Ronny Pfannschmidt
* Srinivas Reddy Thatiparthy
* Thomas Hisch
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,52 @@
pytest-3.4.0
=======================================
The pytest team is proud to announce the 3.4.0 release!
pytest is a mature Python testing tool with more than a 1600 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:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Aaron
* Alan Velasco
* Anders Hovmöller
* Andrew Toolan
* Anthony Sottile
* Aron Coyle
* Brian Maissy
* Bruno Oliveira
* Cyrus Maden
* Florian Bruhin
* Henk-Jaap Wagenaar
* Ian Lesperance
* Jon Dufresne
* Jurko Gospodnetić
* Kate
* Kimberly
* Per A. Brodtkorb
* Pierre-Alexandre Fonta
* Raphael Castaneda
* Ronny Pfannschmidt
* ST John
* Segev Finer
* Thomas Hisch
* Tzu-ping Chung
* feuillemorte
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,27 @@
pytest-3.4.1
=======================================
pytest 3.4.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Aaron
* Alan Velasco
* Andy Freeland
* Brian Maissy
* Bruno Oliveira
* Florian Bruhin
* Jason R. Coombs
* Marcin Bachry
* Pedro Algarvio
* Ronny Pfannschmidt
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-3.4.2
=======================================
pytest 3.4.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Allan Feldman
* Bruno Oliveira
* Florian Bruhin
* Jason R. Coombs
* Kyle Altendorf
* Maik Figura
* Ronny Pfannschmidt
* codetriage-readme-bot
* feuillemorte
* joshm91
* mike
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,51 @@
pytest-3.5.0
=======================================
The pytest team is proud to announce the 3.5.0 release!
pytest is a mature Python testing tool with more than a 1600 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:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Allan Feldman
* Brian Maissy
* Bruno Oliveira
* Carlos Jenkins
* Daniel Hahler
* Florian Bruhin
* Jason R. Coombs
* Jeffrey Rackauckas
* Jordan Speicher
* Julien Palard
* Kale Kundert
* Kostis Anagnostopoulos
* Kyle Altendorf
* Maik Figura
* Pedro Algarvio
* Ronny Pfannschmidt
* Tadeu Manoel
* Tareq Alayan
* Thomas Hisch
* William Lee
* codetriage-readme-bot
* feuillemorte
* joshm91
* mike
Happy testing,
The Pytest Development Team

View File

@@ -15,91 +15,6 @@ We will only remove deprecated functionality in major releases (e.g. if we depre
Deprecation Roadmap
-------------------
This page lists deprecated features and when we plan to remove them. It is important to list the feature, the version where it got deprecated and the version we plan to remove it.
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.
Following our deprecation policy, we should aim to keep features for *at least* two minor versions after it was considered deprecated.
Future Releases
~~~~~~~~~~~~~~~
3.4
^^^
**Old style classes**
Issue: `#2147 <https://github.com/pytest-dev/pytest/issues/2147>`_.
Deprecated in ``3.2``.
4.0
^^^
**Yield tests**
Deprecated in ``3.0``.
**pytest-namespace hook**
deprecated in ``3.2``.
**Marks in parameter sets**
Deprecated in ``3.2``.
**--result-log**
Deprecated in ``3.0``.
See `#830 <https://github.com/pytest-dev/pytest/issues/830>`_ for more information. Suggested alternative: `pytest-tap <https://pypi.python.org/pypi/pytest-tap>`_.
**metafunc.addcall**
Issue: `#2876 <https://github.com/pytest-dev/pytest/issues/2876>`_.
Deprecated in ``3.3``.
**pytest_plugins in non-toplevel conftests**
There is a deep conceptual confusion as ``conftest.py`` files themselves are activated/deactivated based on path, but the plugins they depend on aren't.
Issue: `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_.
Not yet officially deprecated.
**passing a single string to pytest.main()**
Pass a list of strings to ``pytest.main()`` instead.
Deprecated in ``3.1``.
**[pytest] section in setup.cfg**
Use ``[tool:pytest]`` instead for compatibility with other tools.
Deprecated in ``3.0``.
Past Releases
~~~~~~~~~~~~~
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;
* 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.
3.3
^^^
* Dropped support for EOL Python 2.6 and 3.3.
Following our deprecation policy, after starting issuing deprecation warnings we keep features for *at least* two minor versions before considering removal.

View File

@@ -1,81 +1,18 @@
:orphan:
.. _`pytest helpers`:
Pytest API and builtin fixtures
================================================
This is a list of ``pytest.*`` API functions and fixtures.
Most of the information of this page has been moved over to :ref:`reference`.
For information on plugin hooks and objects, see :ref:`plugins`.
For information on the ``pytest.mark`` mechanism, see :ref:`mark`.
For the below objects, you can also interactively ask for help, e.g. by
typing on the Python interactive prompt something like::
import pytest
help(pytest)
.. currentmodule:: pytest
Invoking pytest interactively
---------------------------------------------------
.. autofunction:: main
More examples at :ref:`pytest.main-usage`
Helpers for assertions about Exceptions/Warnings
--------------------------------------------------------
.. autofunction:: raises
Examples at :ref:`assertraises`.
.. autofunction:: deprecated_call
Comparing floating point numbers
--------------------------------
.. autofunction:: approx
Raising a specific test outcome
--------------------------------------
You can use the following functions in your test, fixture or setup
functions to force a certain test outcome. Note that most often
you can rather use declarative marks, see :ref:`skipping`.
.. autofunction:: _pytest.outcomes.fail
.. autofunction:: _pytest.outcomes.skip
.. autofunction:: _pytest.outcomes.importorskip
.. autofunction:: _pytest.outcomes.xfail
.. autofunction:: _pytest.outcomes.exit
Fixtures and requests
-----------------------------------------------------
To mark a fixture function:
.. autofunction:: _pytest.fixtures.fixture
Tutorial at :ref:`fixtures`.
The ``request`` object that can be used from fixture functions.
.. autoclass:: _pytest.fixtures.FixtureRequest()
:members:
.. _builtinfixtures:
.. _builtinfuncargs:
Builtin fixtures/function arguments
-----------------------------------------
You can ask for available builtin or project-custom
:ref:`fixtures <fixtures>` by typing::
For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures, type::
$ pytest -q --fixtures
cache
@@ -89,17 +26,17 @@ You can ask for available builtin or project-custom
Values can be any object handled by the json stdlib module.
capsys
Enable capturing of writes to sys.stdout/sys.stderr and make
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
objects.
capsysbinary
Enable capturing of writes to sys.stdout/sys.stderr and make
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
objects.
capfd
Enable capturing of writes to file descriptors 1 and 2 and make
Enable capturing of writes to file descriptors ``1`` and ``2`` and make
captured output available via ``capfd.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
objects.
@@ -109,13 +46,32 @@ You can ask for available builtin or project-custom
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
``bytes`` objects.
doctest_namespace
Inject names into the doctest namespace.
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
pytestconfig
the pytest config object with access to command line opts.
record_xml_property
Add extra xml properties to the tag for the calling test.
Session-scoped fixture that returns the :class:`_pytest.config.Config` object.
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose"):
...
record_property
Add an extra properties the calling test.
User properties become part of the test report and are available to the
configured reporters, like JUnit XML.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded.
Example::
def test_function(record_property):
record_property("example_key", 1)
record_xml_property
(Deprecated) use record_property.
record_xml_attribute
Add extra xml attributes to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being
automatically xml-encoded
caplog
Access and control log capturing.
@@ -124,6 +80,7 @@ You can ask for available builtin or project-custom
* caplog.text() -> string containing formatted log output
* caplog.records() -> list of logging.LogRecord instances
* caplog.record_tuples() -> list of (logger_name, level, message) tuples
* caplog.clear() -> clear captured records and formatted log output string
monkeypatch
The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
@@ -142,10 +99,7 @@ You can ask for available builtin or project-custom
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
recwarn
Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
See http://docs.python.org/library/warnings.html for information
on warning categories.
@@ -157,5 +111,13 @@ You can ask for available builtin or project-custom
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
no tests ran in 0.12 seconds
You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like::
import pytest
help(pytest)

View File

@@ -78,7 +78,7 @@ If you then run it with ``--lf``::
=========================== 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 50 items
collected 50 items / 48 deselected
run-last-failure: rerun previous 2 failures
test_50.py FF [100%]
@@ -106,7 +106,6 @@ If you then run it with ``--lf``::
E Failed: bad luck
test_50.py:6: Failed
=========================== 48 tests deselected ============================
================= 2 failed, 48 deselected in 0.12 seconds ==================
You have run only the two failing test from the last run, while 48 tests have
@@ -152,6 +151,20 @@ of ``FF`` and dots)::
.. _`config.cache`:
New ``--nf``, ``--new-first`` options: run new tests first followed by the rest
of the tests, in both cases tests are also sorted by the file modified time,
with more recent files coming first.
Behavior when no tests failed in the last run
---------------------------------------------
When no tests failed in the last run, or when no cached ``lastfailed`` data was
found, ``pytest`` can be configured either to run all of the tests or no tests,
using the ``--last-failed-no-failures`` option, which takes one of the following values::
pytest --last-failed-no-failures all # run all tests (default behavior)
pytest --last-failed-no-failures none # run no tests and exit
The new config.cache object
--------------------------------
@@ -212,7 +225,7 @@ the cache and this will be quick::
test_caching.py:14: AssertionError
1 failed in 0.12 seconds
See the `cache-api`_ for more details.
See the :ref:`cache-api` for more details.
Inspecting Cache content
@@ -225,10 +238,12 @@ You can always peek at the content of the cache using the
=========================== 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:
cachedir: $REGENDOC_TMPDIR/.cache
cachedir: $REGENDOC_TMPDIR/.pytest_cache
------------------------------- cache values -------------------------------
cache/lastfailed contains:
{'test_caching.py::test_function': True}
cache/nodeids contains:
['test_caching.py::test_function']
example/value contains:
42
@@ -247,22 +262,3 @@ servers where isolation and correctness is more important
than speed.
.. _`cache-api`:
config.cache API
------------------
The ``config.cache`` object allows other plugins,
including ``conftest.py`` files,
to safely and flexibly store and retrieve values across
test runs because the ``config`` object is available
in many places.
Under the hood, the cache plugin uses the simple
dumps/loads API of the json stdlib module
.. currentmodule:: _pytest.cacheprovider
.. automethod:: Cache.get
.. automethod:: Cache.set
.. automethod:: Cache.makedir

View File

@@ -9,7 +9,8 @@ Default stdout/stderr/stdin capturing behaviour
During test execution any output sent to ``stdout`` and ``stderr`` is
captured. If a test or a setup method fails its according captured
output will usually be shown along with the failure traceback.
output will usually be shown along with the failure traceback. (this
behavior can be configured by the ``--show-capture`` command-line option).
In addition, ``stdin`` is set to a "null" object which will
fail on attempts to read from it because it is rarely desired
@@ -92,14 +93,14 @@ an example test function that performs some output related checks:
.. code-block:: python
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")
print("hello")
sys.stderr.write("world\n")
out, err = capsys.readouterr()
assert out == "hello\n"
assert err == "world\n"
print ("next")
out, err = capsys.readouterr()
assert out == "next\n"
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert captured.err == "world\n"
print("next")
captured = capsys.readouterr()
assert captured.out == "next\n"
The ``readouterr()`` call snapshots the output so far -
and capturing will be continued. After the test
@@ -117,6 +118,10 @@ system level output streams (FD1 and FD2).
.. versionadded:: 3.3
The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
.. versionadded:: 3.3
If the code under test writes non-textual data, you can capture this using
the ``capsysbinary`` fixture which instead returns ``bytes`` from
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only

View File

@@ -18,8 +18,11 @@
# The full version, including alpha/beta/rc tags.
# The short X.Y version.
import os, sys
import os
import sys
from _pytest import __version__ as version
release = ".".join(version.split(".")[:2])
# If extensions (or modules to document with autodoc) are in another directory,
@@ -38,7 +41,7 @@ todo_include_todos = 1
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinxcontrib_trio']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -310,9 +313,7 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
# 'lib': ("http://docs.python.org/2.7library/", None),
}
intersphinx_mapping = {'python': ('http://docs.python.org/3', None)}
def setup(app):

View File

@@ -14,7 +14,6 @@ Full pytest documentation
usage
existingtestsuite
assert
builtin
fixture
monkeypatch
tmpdir
@@ -31,6 +30,7 @@ Full pytest documentation
plugins
writing_plugins
logging
reference
goodpractices
pythonpath

View File

@@ -38,6 +38,10 @@ Here's a summary what ``pytest`` uses ``rootdir`` for:
Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or
influence how modules are imported. See :ref:`pythonpath` for more details.
``--rootdir=path`` command-line option can be used to force a specific directory.
The directory passed may contain environment variables when it is used in conjunction
with ``addopts`` in a ``pytest.ini`` file.
Finding the ``rootdir``
~~~~~~~~~~~~~~~~~~~~~~~
@@ -152,182 +156,4 @@ above will show verbose output because ``-v`` overwrites ``-q``.
Builtin configuration file options
----------------------------------------------
.. confval:: minversion
Specifies a minimal pytest version required for running tests.
minversion = 2.1 # will fail if we run with pytest-2.0
.. confval:: addopts
Add the specified ``OPTS`` to the set of command line arguments as if they
had been specified by the user. Example: if you have this ini file content:
.. code-block:: ini
[pytest]
addopts = --maxfail=2 -rf # exit after 2 failures, report fail info
issuing ``pytest test_hello.py`` actually means::
pytest --maxfail=2 -rf test_hello.py
Default is to add no options.
.. confval:: norecursedirs
Set the directory basename patterns to avoid when recursing
for test discovery. The individual (fnmatch-style) patterns are
applied to the basename of a directory to decide if to recurse into it.
Pattern matching characters::
* matches everything
? matches any single character
[seq] matches any character in seq
[!seq] matches any char not in seq
Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'``.
Setting a ``norecursedirs`` replaces the default. Here is an example of
how to avoid certain directories:
.. code-block:: ini
# content of pytest.ini
[pytest]
norecursedirs = .svn _build tmp*
This would tell ``pytest`` to not look into typical subversion or
sphinx-build directories or into any ``tmp`` prefixed directory.
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
virtualenv by the presence of an activation script. Any directory deemed to
be the root of a virtual environment will not be considered during test
collection unless ``collectinvirtualenv`` is given. Note also that
``norecursedirs`` takes precedence over ``collectinvirtualenv``; e.g. if
you intend to run tests in a virtualenv with a base directory that matches
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
``collectinvirtualenv`` flag.
.. confval:: testpaths
.. versionadded:: 2.8
Sets list of directories that should be searched for tests when
no specific directories, files or test ids are given in the command line when
executing pytest from the :ref:`rootdir <rootdir>` directory.
Useful when all project tests are in a known location to speed up
test collection and to avoid picking up undesired tests by accident.
.. code-block:: ini
# content of pytest.ini
[pytest]
testpaths = testing doc
This tells pytest to only look for tests in ``testing`` and ``doc``
directories when executing from the root directory.
.. confval:: python_files
One or more Glob-style file patterns determining which python files
are considered as test modules. By default, pytest will consider
any file matching with ``test_*.py`` and ``*_test.py`` globs as a test
module.
.. confval:: python_classes
One or more name prefixes or glob-style patterns determining which classes
are considered for test collection. By default, pytest will consider any
class prefixed with ``Test`` as a test collection. Here is an example of how
to collect tests from classes that end in ``Suite``:
.. code-block:: ini
# content of pytest.ini
[pytest]
python_classes = *Suite
Note that ``unittest.TestCase`` derived classes are always collected
regardless of this option, as ``unittest``'s own collection framework is used
to collect those tests.
.. confval:: python_functions
One or more name prefixes or glob-patterns determining which test functions
and methods are considered tests. By default, pytest will consider any
function prefixed with ``test`` as a test. Here is an example of how
to collect test functions and methods that end in ``_test``:
.. code-block:: ini
# content of pytest.ini
[pytest]
python_functions = *_test
Note that this has no effect on methods that live on a ``unittest
.TestCase`` derived class, as ``unittest``'s own collection framework is used
to collect those tests.
See :ref:`change naming conventions` for more detailed examples.
.. confval:: doctest_optionflags
One or more doctest flag names from the standard ``doctest`` module.
:doc:`See how pytest handles doctests <doctest>`.
.. confval:: confcutdir
Sets a directory where search upwards for ``conftest.py`` files stops.
By default, pytest will stop searching for ``conftest.py`` files upwards
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
or up to the file-system root.
.. confval:: filterwarnings
.. versionadded:: 3.1
Sets a list of filters and actions that should be taken for matched
warnings. By default all warnings emitted during the test session
will be displayed in a summary at the end of the test session.
.. code-block:: ini
# content of pytest.ini
[pytest]
filterwarnings =
error
ignore::DeprecationWarning
This tells pytest to ignore deprecation warnings and turn all other warnings
into errors. For more information please refer to :ref:`warnings`.
.. confval:: cache_dir
.. versionadded:: 3.2
Sets a directory where stores content of cache plugin. Default directory is
``.cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
relative or absolute path. If setting relative path, then directory is created
relative to :ref:`rootdir <rootdir>`. Additionally path may contain environment
variables, that will be expanded. For more information about cache plugin
please refer to :ref:`cache_provider`.
.. confval:: console_output_style
.. versionadded:: 3.3
Sets the console output style while running tests:
* ``classic``: classic pytest output.
* ``progress``: like classic pytest output, but with a progress indicator.
The default is ``progress``, but you can fallback to ``classic`` if you prefer or
the new mode is causing unexpected problems:
.. code-block:: ini
# content of pytest.ini
[pytest]
console_output_style = classic
For the full list of options consult the :ref:`reference documentation <ini options ref>`.

View File

@@ -81,9 +81,9 @@ Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported
when executing text doctest files.
The standard ``doctest`` module provides some setting flags to configure the
strictness of doctest tests. In pytest You can enable those flags those flags
using the configuration file. To make pytest ignore trailing whitespaces and
ignore lengthy exception stack traces you can just write:
strictness of doctest tests. In pytest, you can enable those flags using the
configuration file. To make pytest ignore trailing whitespaces and ignore
lengthy exception stack traces you can just write:
.. code-block:: ini
@@ -115,6 +115,13 @@ itself::
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
'Hello'
By default, pytest would report only the first failure for a given doctest. If
you want to continue the test even when you have failures, do::
pytest --doctest-modules --doctest-continue-on-failure
.. _`doctest_namespace`:
The 'doctest_namespace' fixture
-------------------------------

View File

@@ -157,12 +157,14 @@ class TestRaises(object):
# thanks to Matthew Scott for this test
def test_dynamic_compile_shows_nicely():
import imp
import sys
src = 'def foo():\n assert 1 == 0\n'
name = 'abc-123'
module = py.std.imp.new_module(name)
module = imp.new_module(name)
code = _pytest._code.compile(src, name, 'exec')
py.builtin.exec_(code, module.__dict__)
py.std.sys.modules[name] = module
sys.modules[name] = module
module.foo()

View File

@@ -32,13 +32,12 @@ 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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
collecting ... collected 4 items / 3 deselected
test_server.py::test_send_http PASSED [100%]
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
Or the inverse, running all tests except the webtest ones::
@@ -46,15 +45,14 @@ 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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
collecting ... collected 4 items / 1 deselected
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
test_server.py::TestClass::test_method PASSED [100%]
============================ 1 tests deselected ============================
================== 3 passed, 1 deselected in 0.12 seconds ==================
Selecting tests based on their node ID
@@ -67,7 +65,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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -80,7 +78,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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
@@ -93,7 +91,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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
@@ -131,13 +129,12 @@ 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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
collecting ... collected 4 items / 3 deselected
test_server.py::test_send_http PASSED [100%]
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
And you can also run all tests except the ones that match the keyword::
@@ -145,15 +142,14 @@ 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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
collecting ... collected 4 items / 1 deselected
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
test_server.py::TestClass::test_method PASSED [100%]
============================ 1 tests deselected ============================
================== 3 passed, 1 deselected in 0.12 seconds ==================
Or to select "http" and "quick" tests::
@@ -161,14 +157,13 @@ 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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
collecting ... collected 4 items / 2 deselected
test_server.py::test_send_http PASSED [ 50%]
test_server.py::test_something_quick PASSED [100%]
============================ 2 tests deselected ============================
================== 2 passed, 2 deselected in 0.12 seconds ==================
.. note::
@@ -432,7 +427,7 @@ The output is as follows::
$ pytest -q -s
Marker info name=my_marker args=(<function hello_world at 0xdeadbeef>,) kwars={}
. [100%]
.
1 passed in 0.12 seconds
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
@@ -477,7 +472,7 @@ Let's run this without capturing output and see what we get::
glob args=('function',) kwargs={'x': 3}
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}
. [100%]
.
1 passed in 0.12 seconds
marking platform specific tests with pytest
@@ -547,11 +542,10 @@ Note that if you specify a platform via the marker-command line option like this
=========================== 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 4 items
collected 4 items / 3 deselected
test_plat.py . [100%]
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.
@@ -599,7 +593,7 @@ We can now use the ``-m option`` to select one set::
=========================== 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 4 items
collected 4 items / 2 deselected
test_module.py FF [100%]
@@ -612,7 +606,6 @@ We can now use the ``-m option`` to select one set::
test_module.py:6: in test_interface_complex
assert 0
E assert 0
============================ 2 tests deselected ============================
================== 2 failed, 2 deselected in 0.12 seconds ==================
or to select both "event" and "interface" tests::
@@ -621,7 +614,7 @@ or to select both "event" and "interface" tests::
=========================== 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 4 items
collected 4 items / 1 deselected
test_module.py FFF [100%]
@@ -638,5 +631,4 @@ or to select both "event" and "interface" tests::
test_module.py:9: in test_event_simple
assert 0
E assert 0
============================ 1 tests deselected ============================
================== 3 failed, 1 deselected in 0.12 seconds ==================

View File

@@ -60,7 +60,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.5
cachedir: .cache
cachedir: .pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items

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