Compare commits

...

883 Commits
3.2.3 ... 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
2a111ff700 Preparing release version 3.3.1 2017-12-05 20:41:57 +00:00
Bruno Oliveira
5c6758fde4 Merge pull request #3006 from The-Compiler/template-fix
Fix example in PR template
2017-12-05 18:35:42 -02:00
Bruno Oliveira
9bd8420a6b Merge pull request #3005 from The-Compiler/blacklist-catchlog
Also blacklist pytest-capturelog plugin
2017-12-05 18:35:09 -02:00
Florian Bruhin
cbdab02d05 Fix example in PR template 2017-12-05 20:05:35 +01:00
Florian Bruhin
ce30896cd2 Also blacklist pytest-capturelog plugin
This is the older plugin before pytest-catchlog was around. Apparently there are
people still using it.

Fixes #3004
2017-12-05 20:02:56 +01:00
Ronny Pfannschmidt
2e8b0a83fe Merge pull request #2989 from nicoddemus/bring-tr-writer-back-2984
Bring back TerminalReporter.writer as an alias to TerminalReporter._tw
2017-12-01 17:42:31 +01:00
Bruno Oliveira
369c711f14 Merge pull request #2971 from blueyed/fix-ZeroDivisionError
Fix ZeroDivisionError with 0 collected tests
2017-11-30 21:12:46 -02:00
Bruno Oliveira
cf0cac3b73 Bring back TerminalReporter.writer as an alias to TerminalReporter._tw
Fix #2984
2017-11-30 19:56:47 -02:00
Bruno Oliveira
a9dd37f429 Merge pull request #2980 from nicoddemus/immutable-fix-parameters
Add CHANGELOG entry about pytest.fixture "params" being now immutable
2017-11-30 19:56:03 -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
Ronny Pfannschmidt
4de433e280 Merge pull request #2983 from nicoddemus/improve-assert-2979
Improve getscopeitem assertion message
2017-11-30 16:58:29 +01: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
70f1e3b4b0 Improve getscopeitem assertion message
Fix #2979
2017-11-30 07:53:43 -02:00
Bruno Oliveira
fdd4abb88a Small rewording of the CHANGELOG 2017-11-29 21:11:34 -02:00
Bruno Oliveira
fdfc1946da Add CHANGELOG entry about pytest.fixture "params" being now immutable
Fix #2959
2017-11-29 18:56:57 -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
Florian Bruhin
88ed1ab648 Merge pull request #2964 from rpuntaie/master
fix issue #2920
2017-11-29 10:32:28 +01:00
Florian Bruhin
191e8c6d9b Merge pull request #2969 from nicoddemus/null-bytes-2957
Always escape null bytes when setting PYTEST_CURRENT_TEST
2017-11-29 06:57:48 +01:00
Bruno Oliveira
6bbd741039 Add test for #2971 2017-11-28 22:42:52 -02:00
Daniel Hahler
0f5fb7ed05 Fix ZeroDivisionError with 0 collected tests
This can easily happen with pytest-testmon.
2017-11-29 00:54:14 +01:00
Bruno Oliveira
5f1a7330b2 Small fixes in changelog items 2017-11-28 21:36:17 -02:00
Bruno Oliveira
2a75ae46c3 Improve test that blocks setuptools plugins from being loaded
Make it a parametrized test to ensure all the mocked mechanisms in
the test actually work as expected when we *do not* use "-p no:"
2017-11-28 21:29:58 -02:00
Bruno Oliveira
89cf943e04 Always escape null bytes when setting PYTEST_CURRENT_TEST
Fix #2957
2017-11-28 19:45:13 -02:00
Roland Puntaier
833f33fa0c removed comments 2017-11-28 17:38:49 +01:00
Roland Puntaier
3dbac17d75 Merge branch 'master' of https://github.com/rpuntaie/pytest 2017-11-28 17:30:54 +01:00
Roland Puntaier
6843d45c51 added test for #2920 fix 2017-11-28 17:29:52 +01:00
Bruno Oliveira
4a840a7c09 Fix formatting in CHANGELOG 2017-11-28 13:59:27 -02:00
Roland Puntaier
9b7e4ab0c6 prepare pull request for #2920 fix 2017-11-28 16:56:59 +01:00
Roland Puntaier
4ea7bbc197 fix issue #2920 2017-11-28 16:47:12 +01:00
Bruno Oliveira
4d2f05e4b9 Merge pull request #2962 from pytest-dev/fix-nbsp-in-changelog
replace non-breaking space with space
2017-11-28 09:51:26 -02:00
Florian Bruhin
454b60b6c5 Merge pull request #2963 from redtoad/patch-1
Fix broken link to project
2017-11-28 12:51:15 +01:00
Sebastian Rahlf
f6be23b68b Add changelog entry. 2017-11-28 12:31:34 +01:00
Sebastian Rahlf
644fdc5237 Fix broken link to project
The plugin has since moved to pytest-dev
2017-11-28 12:22:47 +01:00
Tom Viner
4b5f0d5ffa replace non-breaking space with space 2017-11-28 11:12:40 +00:00
Ronny Pfannschmidt
9f7ba00611 Merge pull request #2958 from nicoddemus/issue-2956
Only escape str-like arguments passed to warnings
2017-11-28 07:44:28 +01:00
Bruno Oliveira
796db80ca4 Only escape str-like arguments passed to warnings
Fix #2956
2017-11-27 23:17:15 -02:00
Bruno Oliveira
d95c8a2204 Fix link to #2636 in CHANGELOG 2017-11-27 19:51:41 -02:00
Bruno Oliveira
4678cbeb91 Merge remote-tracking branch 'upstream/features' 2017-11-27 19:37:58 -02:00
Bruno Oliveira
67ad0fa364 Merge pull request #2945 from nicoddemus/release-3.3.0
Release 3.3.0
2017-11-27 19:37:19 -02:00
Bruno Oliveira
6cdd851227 CHANGELOG and docs changes requested during review 2017-11-27 10:19:34 -02:00
Bruno Oliveira
c58715371c Merge pull request #2954 from blueyed/rewritten
Fix spelling: s/re-writ/rewrit/g
2017-11-26 18:12:30 -02:00
Daniel Hahler
d5f038e29a Fix spelling: s/re-writ/rewrit/g
This also fixes "can not" to "cannot" in the "Module already imported so
cannot be rewritten" assertion error.
2017-11-26 20:46:06 +01:00
Bruno Oliveira
6eeacaba3e Fix typos in CHANGELOG 2017-11-26 09:00:08 -02:00
Bruno Oliveira
6b90ad4d4b Merge pull request #2949 from eprikazc/master
Update github "bugs" link
2017-11-25 16:18:29 -02:00
Eugene Prikazchikov
e273f5399d Update github "bugs" link
Apparently, some time ago label was renamed from bug to "type:bug"
2017-11-25 17:44:58 +03:00
Bruno Oliveira
0de1a65644 Update pluggy pin to pluggy>=0.5,<0.7 after pluggy-0.6 release 2017-11-24 14:42:25 -02:00
Bruno Oliveira
95de11a44e Block pytest-catchlog and issue a warning
Trying to install pytest-3.3 and pytest-catchlog will result in an
option conflicts because both declare the same options.
2017-11-23 19:53:12 -02:00
Bruno Oliveira
05cfdcc8cb Revert making TerminalWriter public in TerminalReporter plugin
We eventually want to replace py.io.TerminalWriter (exposed as
"_tw" in TerminalReporter) by something else, so deprecating access
to "_tw" and making it public as "writer" is contrary to that.
2017-11-23 19:53:12 -02:00
Bruno Oliveira
0ddd3e2839 Fix linting in CHANGELOG 2017-11-23 19:02:13 -02:00
Bruno Oliveira
aa9a02ec44 Preparing release version 3.3.0 2017-11-23 20:25:36 +00:00
Bruno Oliveira
e97c774f8e Remove vendoring import from tasks/__init__ 2017-11-23 20:23:50 +00:00
Bruno Oliveira
f50ace7c0a Merge remote-tracking branch 'upstream/master' into release-3.3.0 2017-11-23 20:20:40 +00:00
Bruno Oliveira
49c0c599b0 Merge pull request #2944 from nicoddemus/regendoc-norm
Remove gendoc normalization for '=' and '_' headers
2017-11-23 16:31:09 -02:00
Bruno Oliveira
e0d236c031 Remove gendoc normalization for '=' and '_' headers
Using the default for non-atty terminals (80) so the new
progress indicator aligns correctly.
2017-11-23 15:33:41 +00:00
Ronny Pfannschmidt
b533c2600a Merge pull request #2858 from nicoddemus/console-progress-2657
Console progress output
2017-11-23 14:46:49 +01:00
Bruno Oliveira
dc574c60ef Use regex match-count syntax to improve test readability 2017-11-22 17:16:25 -02:00
Bruno Oliveira
1d26f3730f Fix docstrings in pytester 2017-11-22 17:15:59 -02:00
Bruno Oliveira
27935ebec9 Merge pull request #2943 from Perlence/fix-marks-without-description
Handle marks without description
2017-11-22 15:36:29 -02:00
Bruno Oliveira
378eb5d67b Minor formatting change in CHANGELOG 2017-11-22 11:24:48 -02:00
Sviatoslav Abakumov
5e71ffab87 Handle marks without description 2017-11-22 15:47:15 +03:00
Florian Bruhin
8df7ed12c1 Merge pull request #2940 from nicoddemus/rewrite-bug-2939
Fix assertion rewrite to match module names correctly
2017-11-22 06:57:55 +01:00
Bruno Oliveira
f05333ab75 Fix rewrite to not write past the edge of the screen 2017-11-22 00:00:02 -02:00
Bruno Oliveira
c8d52b633b Fix assertion rewrite to match module names correctly
Fix #2939
2017-11-21 23:06:47 -02:00
Bruno Oliveira
2455f8670e Add changelog 2017-11-21 21:01:51 -02:00
Bruno Oliveira
3a5dbabf60 Add tests for progress output and docs for `console_output_style`
Fix #2657
2017-11-21 21:01:51 -02:00
Bruno Oliveira
3441084bd2 Add progress output for verbose mode with xdist 2017-11-21 21:01:51 -02:00
Bruno Oliveira
8b92527d7d Add re_match_lines and re_match_lines_random to pytester fixture 2017-11-21 21:01:51 -02:00
Bruno Oliveira
dab889304e Implement progress percentage reporting while running tests
Fix #2657
2017-11-21 21:01:41 -02:00
Bruno Oliveira
7a7cb8c8c5 Require py>=1.5.0 2017-11-21 21:01:41 -02:00
Bruno Oliveira
77bd0aa02f Merge pull request #2931 from nicoddemus/deprecation-roadmap-docs
Add Deprecation Roadmap to backward compatibility document
2017-11-18 12:30:33 -02:00
Bruno Oliveira
b0f558da44 Add Future/Past Releases section 2017-11-18 11:57:00 -02:00
Ronny Pfannschmidt
ca1f4bc537 Merge pull request #2935 from asottile/capsysbinary
Add capsysbinary fixture
2017-11-17 21:30:14 +01:00
Anthony Sottile
219b758949 Add capsysbinary fixture
`capsysbinary` works like `capsys` but produces bytes for `readouterr()`.
2017-11-17 09:02:46 -08:00
Bruno Oliveira
6161bcff6e Merge pull request #2925 from asottile/capfdbinary
Add capfdbinary fixture
2017-11-17 08:00:10 -02:00
Ronny Pfannschmidt
99a4a93dbc Merge pull request #2932 from nicoddemus/deprecate-add-call
Deprecate metafunc.addcall
2017-11-16 07:15:25 +01:00
Bruno Oliveira
99ba3c9700 Add CHANGELOG entry for #2876 2017-11-15 22:02:47 -02:00
Bruno Oliveira
c19708b193 Merge pull request #2930 from nicoddemus/pin-pluggy-0.5
Update pluggy pin to 0.5.*
2017-11-15 21:59:53 -02:00
Bruno Oliveira
1f08d990d5 Deprecate metafunc.addcall
Fix #2876
2017-11-15 15:16:00 -02:00
Bruno Oliveira
e2c59d3282 Change pluggy._CallOutcome to pluggy._Result after update to pluggy-0.5 2017-11-15 14:08:11 -02:00
Bruno Oliveira
f9029f11af Add Deprecation Roadmap to backward compatibility document
We should replace the wiki document to a link to this document now
2017-11-15 13:53:43 -02:00
Bruno Oliveira
c7be83ac47 Update pluggy pin to 0.5.* 2017-11-15 13:00:29 -02:00
Bruno Oliveira
74aaf91653 Merge pull request #2927 from nicoddemus/release-3.2.5
Release 3.2.5
2017-11-15 09:22:48 -02:00
Florian Bruhin
1aeb58b531 Merge pull request #2928 from nicoddemus/python-requires
Prevent pip from installing pytest in unsupported Python versions
2017-11-15 09:46:04 +01:00
Ronny Pfannschmidt
7b3febd314 Merge pull request #2924 from nicoddemus/merge-master-into-features
Merge master into features
2017-11-15 09:36:31 +01:00
Bruno Oliveira
e87ff07370 Prevent pip from installing pytest in unsupported Python versions
Fix #2922
2017-11-14 22:27:15 -02:00
Bruno Oliveira
a220a40350 Preparing release version 3.2.5 2017-11-15 00:12:42 +00:00
Bruno Oliveira
dd6c534468 Remove py<1.5 restriction
Fix #2926
2017-11-14 22:08:03 -02:00
Bruno Oliveira
4a0aea2deb Add missing entry to CHANGELOG for 3.2.4 2017-11-14 22:05:48 -02:00
Anthony Sottile
8f90812481 Add capfdbinary fixture
`capfdbinary` works like `capfd` but produces bytes for `readouterr()`.
2017-11-14 14:50:00 -08:00
Bruno Oliveira
3b3bf9f53d Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2017-11-14 18:42:29 -02:00
Bruno Oliveira
54cea3d178 Small formatting changes in CHANGELOG 2017-11-14 18:33:46 -02:00
Bruno Oliveira
9628c71210 Merge pull request #2921 from nicoddemus/release-3.2.4
Preparing release version 3.2.4
2017-11-14 18:31:03 -02:00
Bruno Oliveira
a0ad9e31da Preparing release version 3.2.4 2017-11-13 20:01:17 +00:00
Bruno Oliveira
22cff038f8 Merge pull request #2916 from nicoddemus/bin-py-1.5
Pin py<1.5 as 1.5 drops py26 and py33 support
2017-11-13 17:56:52 -02:00
Bruno Oliveira
685387a43e Merge pull request #2127 from malinoff/fix-2124
Use session.config.hook instead of ihook. Fixes #2124
2017-11-12 21:57:41 -02:00
Bruno Oliveira
d26c1e3ad9 Pin py<1.5 as 1.5 drops py26 and py33 support 2017-11-12 21:50:11 -02:00
Bruno Oliveira
a6f2d2d2c9 Rename FixtureDef.finalizer to FixtureDef.finalizers 2017-11-12 11:35:46 -02:00
Bruno Oliveira
6d3fe0b826 Explicitly clear finalizers list in finalize to ensure cleanup 2017-11-12 11:28:57 -02:00
Bruno Oliveira
bdad345f99 Fix passing request to finish() in FixtureDef 2017-11-12 11:28:26 -02:00
Bruno Oliveira
063335a715 Add changelog entries for #2124 2017-11-12 11:19:25 -02:00
Bruno Oliveira
f074fd9ac6 Merge remote-tracking branch 'upstream/features' into malinoff/fix-2124 2017-11-12 11:16:08 -02:00
Bruno Oliveira
6550b9911b pytest_fixture_post_finalizer now receives a request argument 2017-11-12 11:14:55 -02:00
Bruno Oliveira
258031afe5 Merge remote-tracking branch 'upstream/master' into malinoff/fix-2124 2017-11-12 10:34:47 -02:00
Bruno Oliveira
259b86b6ab Merge pull request #2776 from cryporchild/fix-missing-nodeid-with-pyargs
Fix #2775 - running pytest with "--pyargs" will result in Items with …
2017-11-12 10:18:35 -02:00
Ronny Pfannschmidt
f0f2d2b861 Merge branch 'master' into fix-missing-nodeid-with-pyargs 2017-11-11 14:33:55 +01:00
Ronny Pfannschmidt
d1af369800 Merge pull request #2913 from nicoddemus/merge-master-into-features
Merge master into features
2017-11-11 10:09:49 +01:00
Ronny Pfannschmidt
b671c5a8bf Merge pull request #2914 from nicoddemus/addfinalizer-refactor
Make SubRequest.addfinalizer an explicit method
2017-11-11 09:26:24 +01:00
Bruno Oliveira
f320686fe0 Make SubRequest.addfinalizer an explicit method
This implicit definition really tripped me while debugging #2127,
unfortunately hidden as it was in the middle of all the variable
declarations.

I think the explicit definition is much easier for the eyes and IDEs
to find.
2017-11-11 03:07:34 -02:00
Bruno Oliveira
742f9cb825 Merge pull request #2911 from RonnyPfannschmidt/remove-nodeinfo
remove unused _pytest.runner.NodeInfo class
2017-11-10 18:57:14 -02:00
Bruno Oliveira
99496d9e5b Merge pull request #2910 from RonnyPfannschmidt/scopenode-sanitize-specials
switch a special case in scope node lookup to a general one
2017-11-10 18:49:35 -02:00
Bruno Oliveira
983a09a2d4 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2017-11-10 18:33:02 -02:00
Bruno Oliveira
66fbebfc26 Merge pull request #2894 from nicoddemus/fix-linting-errors
Fix linting errors
2017-11-10 18:15:53 -02:00
Bruno Oliveira
0108f262b1 Fix typo in CHANGELOG 2017-11-10 18:15:09 -02:00
Ronny Pfannschmidt
76f3be452a remove unused _pytest.runner.NodeInfo class 2017-11-10 17:48:52 +01:00
Ronny Pfannschmidt
c47dcaa713 switch a special case in scope node lookup to a general one 2017-11-10 17:35:42 +01:00
Ronny Pfannschmidt
b2b1eb262f Merge pull request #2906 from nicoddemus/larger-parametrize-section
Make the "examples" section more prominent in the docs for parametrize
2017-11-10 10:50:57 +01:00
Ronny Pfannschmidt
9fce430c89 Merge pull request #2907 from nicoddemus/lazy-fixture
Mention pytest-lazy-fixture plugin in the proposal for parametrize_with_fixtures
2017-11-10 10:48:48 +01:00
Bruno Oliveira
e114feb458 Mention pytest-lazy-fixture plugin in the proposal for parametrize_with_fixtures 2017-11-09 20:14:24 -02:00
Bruno Oliveira
c09f69df2a Make the "examples" section more prominent in the docs
I spent some time today figuring out why PR #2881 was not showing up
on doc/parametrize... then after some digging even on readthedocs
wondering if the last documentation build had failed, I realized the
docs I was looking for was in doc/example/parametrize instead.

The section that mentions this is very easy to miss, this makes it a
full fledged title and easier to find.
2017-11-09 19:25:30 -02:00
Bruno Oliveira
3900879a5c Mark test_py2_unicode as xfail in PyPy2 on Windows
#2905
2017-11-09 19:17:17 -02:00
Florian Bruhin
7b1cc55add Merge pull request #2903 from pagles/docs-3.6
List python 3.6 in the documented supported versions
2017-11-08 22:57:28 +01:00
Yorgos Pagles
d904981bf3 Rename 2903.trivial to 2903.doc 2017-11-08 18:20:50 +02:00
Yorgos Pagles
f13333afce Create changelog entry 2017-11-08 18:19:59 +02:00
Yorgos Pagles
fad1fbe381 List python 3.6 in the documented supported versions 2017-11-08 18:13:34 +02:00
Bruno Oliveira
c33074c8b9 Merge pull request #2641 from RonnyPfannschmidt/introduce-attrs
[RFC] Introduce attrs
2017-11-06 12:56:57 -02:00
Bruno Oliveira
b11640c1eb Fix linting E722: do not use bare except 2017-11-04 13:59:10 -02:00
Bruno Oliveira
03829fde8a Fix linting E741: ambiguous variable name 2017-11-04 13:59:10 -02:00
Bruno Oliveira
e351976ef4 Merge remote-tracking branch 'upstream/features' into RonnyPfannschmidt/introduce-attrs 2017-11-04 12:32:27 -02:00
Ronny Pfannschmidt
b18a9deb4c Merge pull request #2892 from nicoddemus/merge-master-into-features
Merge upstream/master into features
2017-11-04 08:15:28 +01:00
Ronny Pfannschmidt
2e2f72156a Merge pull request #2893 from fmichea/patch-1
Clarify language of proposal for fixtures parameters
2017-11-04 08:08:48 +01:00
Franck Michea
22e9b006da Add fragment per PR's guidelines. 2017-11-03 22:24:05 +01:00
Franck Michea
802585cb66 Clarify language of proposal for parametrized fixtures
This change slightly modifies the language of the proposal document around use of fixture as parameters of pytest.mark.parametrize. When looking for documentation around this, I very quickly scrolled through this document and landed on the last paragraph thinking it was documenting a real function. This change attempts to make it less likely for this to happen.
2017-11-03 22:13:47 +01:00
Bruno Oliveira
d7e8eeef56 Merge pull request #2878 from RonnyPfannschmidt/collector-makeitem-deprecate
deprecate the public internal PyCollector.makeitem method
2017-11-03 17:13:18 -02:00
Bruno Oliveira
e58e8faf47 Add CHANGELOG entry for attrs module dependency 2017-11-03 17:07:33 -02:00
Bruno Oliveira
7d43225c36 Merge pull request #2877 from RonnyPfannschmidt/extract-extract-parameterset
move responsibility for parameterset extraction into parameterset class
2017-11-03 16:52:28 -02:00
Bruno Oliveira
460cae02b0 Small formatting fix in CHANGELOG 2017-11-03 16:51:59 -02:00
Bruno Oliveira
f3a119c06a Merge upstream/master into features 2017-11-03 16:37:18 -02:00
Ronny Pfannschmidt
d1aa553f73 add mocked integrationtest for the deprecationwarning of makeitem 2017-10-30 19:49:14 +01:00
Ronny Pfannschmidt
cd747c48a4 Merge pull request #2874 from nicoddemus/fix-py27-xdist-envs-2843
Change directory for py27 xdist-related envs
2017-10-30 17:09:34 +01:00
Ronny Pfannschmidt
07b2b18a01 introduce attrs as dependency and use it
for FixtureFunctionMarker and marks
2017-10-30 17:04:42 +01:00
Bruno Oliveira
766de67392 Fix linting error in deprecated.py 2017-10-30 16:35:34 +01:00
Ronny Pfannschmidt
821f9a94d8 deprecate the public internal PyCollector.makeitem method 2017-10-30 16:35:23 +01:00
Ronny Pfannschmidt
26019b33f8 Merge pull request #2882 from thisch/doctest_lineno
Fix context output handling for doctests
2017-10-30 09:57:25 +01:00
Ronny Pfannschmidt
cb30848e5a Merge pull request #2880 from samueldg/capture-result-namedtuple
Capture result namedtuple
2017-10-30 09:47:06 +01:00
Ronny Pfannschmidt
d00e2da6e9 Merge pull request #2881 from dawran6/2658
Document pytest.param
2017-10-30 09:36:33 +01:00
Thomas Hisch
2f993af54a Fix context output handling for doctests
Show full context of doctest source in the pytest output, if the lineno of
failed example in the docstring is < 9.
2017-10-29 20:06:10 +00:00
Daw-Ran Liou
af5e9238c8 Document pytest.param
Append example for pytest.param in the example/parametrize document.
2017-10-28 14:25:09 -07:00
Samuel Dion-Girardeau
8e178e9f9b Add myself to AUTHORS list 2017-10-28 15:08:53 -04:00
Samuel Dion-Girardeau
8e28815d44 Add changelog entry for issue #2879 2017-10-28 15:08:07 -04:00
Samuel Dion-Girardeau
b27dde24d6 Use a nametuple for readouterr() results
This allows accessing `out` and `err` directly by attribute,
while preserving tuple unpacking.

Also added tests, one for the `capsys` fixture, and one for the
`MultiCapture` class itself.
2017-10-28 14:53:19 -04:00
Ronny Pfannschmidt
4a436f2255 move responsibility for parameterset extraction into parameterset class 2017-10-27 17:55:07 +02:00
Bruno Oliveira
27cea340f3 Remove trailing whitespace 2017-10-26 21:55:28 -02:00
Bruno Oliveira
c3ba9225ef Change directory for py27 xdist-related envs
The "filter_traceback" function was not filtering the frames
that belonged to the pytest internals.

"filter_traceback" was receiving *relative* paths when running with
xdist, and full paths in non-distributed runs; for this reason
the traceback function did not consider the received path to be
relative to the pytest internal modules.

Fix #2843
2017-10-26 21:34:41 -02:00
Bruno Oliveira
111d640bdb Merge pull request #2873 from stephenfin/doc/skipping-entire-files
doc: Include collector config in the skip doc
2017-10-26 20:21:31 -02:00
Bruno Oliveira
734c435d00 Merge pull request #2870 from Perlence/rewrite-python-37-docstring
Adapt the Python 3.7 AST changes
2017-10-26 20:15:22 -02:00
Bruno Oliveira
27bb2eceb4 Add comment about why we remove docstrings on test_assertrewrite
As explained in pytest-dev/pytest#2870
2017-10-26 20:15:05 -02:00
Stephen Finucane
383239cafc doc: Include collector config in the skip doc
None of the decorators are sufficient to skip an entire file, for
example if the file contain invalid code for a given Python version.
Simply link to details about customizing the collector.

Signed-off-by: Stephen Finucane <stephen@that.guru>
2017-10-26 17:14:29 +01:00
Sviatoslav Abakumov
fd7bfa30d0 Put imports on the last line unless there are other exprs 2017-10-25 11:05:07 +03:00
Sviatoslav Abakumov
3427d27d5a Try to get docstring from module node 2017-10-25 10:54:43 +03:00
Ronny Pfannschmidt
def471b975 Merge pull request #2869 from nicoddemus/merge-master-into-features
Merge master into features
2017-10-25 09:08:08 +02:00
Ronny Pfannschmidt
f743e95cfc Merge pull request #2791 from OfirOshir/features
bugfix for issue #2491
2017-10-25 09:04:01 +02:00
Bruno Oliveira
4e581b637f Use zip and map from six 2017-10-24 22:13:32 -02:00
Bruno Oliveira
6b86b0dbfe Fix additional linting issues 2017-10-24 21:01:00 -02:00
Bruno Oliveira
0b540f98b1 Merge pull request #2864 from bilderbuchi/fix-1505
Introduce a dedicated section about conftest.py
2017-10-24 20:12:46 -02:00
Bruno Oliveira
bdab29fa3d Merge pull request #2867 from Perlence/ini-markers-whitespace
Strip whitespace from markers in INI config
2017-10-24 20:11:20 -02:00
Bruno Oliveira
6821d36ca5 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2017-10-24 19:57:14 -02:00
Ronny Pfannschmidt
5631a86296 Merge pull request #2862 from tom-dalton-fanduel/issue-2836-fixture-collection-bug
Issue 2836 fixture collection bug
2017-10-24 16:32:50 +02:00
Sviatoslav Abakumov
52aadcd7c1 Strip whitespace from markers in INI config
Resolves #2856.
2017-10-24 14:18:39 +03:00
Tom Dalton
f5e72d2f5f Unused import / lint 2017-10-24 11:25:42 +01:00
Tom Dalton
a5ac19cc5e Merge branch 'issue-2836-fixture-collection-bug' of github.com:tom-dalton-fanduel/pytest into issue-2836-fixture-collection-bug 2017-10-24 10:45:06 +01:00
Tom Dalton
14e3a5fcb9 Move the generic separator to a constant 2017-10-24 10:42:16 +01:00
Florian Bruhin
7b608f976d Merge pull request #2865 from nicoddemus/contributing-docs
Make CONTRIBUTING and PR template more consistent regarding doc contributions
2017-10-24 06:46:32 +02:00
Bruno Oliveira
fe560b7192 Make CONTRIBUTING and PR template more consistent regarding doc contributions 2017-10-23 18:46:14 -02:00
Bruno Oliveira
b61cbc4fba Merge pull request #2859 from OwenTuz/issue-2692-document-setup-teardown-mismatch-in-unittest-integration
2692.docs document setup/teardown behaviour when using unittest-based su…
2017-10-23 18:35:25 -02:00
Bruno Oliveira
a3ec3df0c8 Add E722 and E741 flake errors to the ignore list
Also fixed 'E704 multiple statements on one line (def)' in python_api
2017-10-23 18:19:15 -02:00
Christoph Buchner
e23af009f9 Introduce a dedicated section about conftest.py
Also reorganize conftest-related content a bit.
Closes #1505, closes #1770.
2017-10-23 22:11:09 +02:00
Florian Bruhin
531e0dcaa3 Merge pull request #2863 from lancelote/patch-1
Remove typo @ in assignment
2017-10-23 20:51:07 +02:00
Pavel Karateev
dc5f33ba5c Remove typo @ in assignment 2017-10-23 21:39:13 +03:00
Tom Dalton
655ab0bf8b Address more review comments, fix massive bug I reintroduced in the node-splitting code :-/ 2017-10-23 17:49:49 +01:00
Tom Dalton
a7199fa8ab Docstring typo 2017-10-23 16:59:56 +01:00
Tom Dalton
d714c196a5 Shorter code, longer docstring 2017-10-23 16:55:35 +01:00
Tom Dalton
ee7e1c94d2 Remove redundant if, tidy if-body 2017-10-23 16:12:07 +01:00
Tom Dalton
de9d116a49 Added Tom Dalton to AUTHORS 2017-10-23 15:37:15 +01:00
Tom Dalton
f003914d4b Add changelog entry for #2836 2017-10-23 15:37:02 +01:00
Tom Dalton
1e6dc6f8e5 Working (I think) fix for #2836 2017-10-23 13:26:42 +01:00
Tom Dalton
c03612f729 Test now looks for real expected output 2017-10-23 12:40:32 +01:00
Tom Dalton
29fa9d5bff Add failing test 2017-10-23 12:28:54 +01:00
Owen Tuz
3cdbb1854f #2692: Document setup/teardown behaviour when using unittest-based suites 2017-10-21 23:12:49 +01:00
Ronny Pfannschmidt
083084fcbc Merge pull request #2842 from ceridwen/features
Use funcsigs and inspect.signature to do function argument analysis
2017-10-20 07:49:08 +02:00
Bruno Oliveira
f7387e45ea Fix linting 2017-10-19 21:50:15 -02:00
Ceridwen
3da28067f3 Replace introspection in compat.getfuncargnames() with inspect/funcsigs.signature 2017-10-19 16:01:26 -07:00
Bruno Oliveira
5c71151967 Add more text to the 2.6 and 3.3 announcement
The text was a bit short for this important announcement.
2017-10-19 20:48:23 -02:00
Ronny Pfannschmidt
3f9f4be070 Merge pull request #2845 from jespino/fix/2832
Adding Failed exception to manage maxfail behavior
2017-10-19 09:12:11 +02:00
Bruno Oliveira
4cb60dac3d Merge pull request #2850 from bilderbuchi/docs-911
Add documentation about python -m pytest invocation.
2017-10-18 19:44:52 -02:00
Bruno Oliveira
8c7974af01 Merge pull request #2848 from bilderbuchi/fix-538
Clarify the documentation of fixture scopes
2017-10-18 19:18:57 -02:00
Bruno Oliveira
e81b275eda Update formatting in CHANGELOG 2017-10-18 18:54:44 -02:00
Bruno Oliveira
537fc3c315 Merge pull request #2824 from dirk-thomas/pytest_addopts_before_initini
get PYTEST_ADDOPTS before calling _initini
2017-10-18 18:35:12 -02:00
Christoph Buchner
46cc9ab77c Add documentation about python -m pytest invocation. 2017-10-18 21:30:56 +02:00
Florian Bruhin
2d08005039 Merge pull request #2847 from meawoppl/patch-1
Spelling Fix
2017-10-18 07:50:43 +02:00
Christoph Buchner
baadd569e8 Clarify the documentation of fixture scopes. Closes #538. 2017-10-17 23:42:32 +02:00
Matty G
11b391ff49 Update mark.py 2017-10-17 14:11:07 -07:00
Jesús Espino
00d3abe6dc Adding Failed exception to manage maxfail behavior 2017-10-17 21:32:20 +02:00
Ronny Pfannschmidt
71c76d96d3 Merge pull request #2834 from aysonje/ignore-setup
ignore valid setup.py during --doctest-modules
2017-10-17 07:19:09 +02:00
Bruno Oliveira
3676da594c Merge pull request #2841 from pgiraud/patch-1
Fix auto-use fixture doc
2017-10-16 21:18:18 -02:00
Bruno Oliveira
843872b501 Improve formatting in 502.feature file 2017-10-16 21:07:57 -02:00
Pierre GIRAUD
a4fd5cdcb5 Fix auto-use fixture doc 2017-10-16 10:23:35 +02:00
Florian Bruhin
ae4e596b31 Merge pull request #2840 from MarSoft/patch-2
Fix typo in parametrization doc
2017-10-16 05:23:12 +02:00
Семён Марьясин
cfdebb3ba4 Fix typo in parametrization doc 2017-10-16 01:55:30 +03:00
je
eaf38c7239 call path.read(), add tests, add news fragment 2017-10-14 00:49:54 +08:00
je
b29a9711c4 ignore valid setup.py during --doctest-modules 2017-10-14 00:49:45 +08:00
Ronny Pfannschmidt
c750a5beec Merge pull request #2794 from thisch/catchlog
Merge pytest-catchlog plugin
2017-10-12 09:02:46 +02:00
Ronny Pfannschmidt
df37cdf51f Merge pull request #2808 from georgeyk/allow-module-level-skip
Allow module level skip
2017-10-12 06:50:53 +02:00
Thomas Hisch
af75ca435b Fix some coding-style issues in the logging plugin 2017-10-12 02:19:24 +02:00
Thomas Hisch
8aed5fecd9 Remove test_logging_initialized_in_test 2017-10-12 02:19:24 +02:00
Thomas Hisch
f3261d9418 Move logging docu into own rst file
Remove reference of pytest-catchlog plugin in plugins.rst
2017-10-12 02:19:24 +02:00
Thomas Hisch
775f4a6f2f Fix flake8 issue 2017-10-12 02:19:24 +02:00
Thomas Hisch
502652ff02 Add preliminary documentation for logging-plugin 2017-10-12 02:19:24 +02:00
Thomas Hisch
0e83511d6d Rename name of registered logging plugin 2017-10-12 02:19:24 +02:00
Thomas Hisch
815dd19fb4 Remove unicode literal compat code 2017-10-12 02:19:24 +02:00
Thomas Hisch
1f3ab118fa Remove usage of get_logger_obj 2017-10-12 02:19:24 +02:00
Thomas Hisch
0ec72d0745 Improve get_option_ini and get_actual_log_level 2017-10-12 02:19:24 +02:00
Thomas Hisch
69f3bd8336 Add changelog entry for catchlog plugin 2017-10-12 02:19:24 +02:00
Bruno Oliveira
10a3b9118b Use a relative cache_dir in test because of how arguments are parsed on Windows
We use shlex to parse command-line arguments and PYTEST_ADDOPTS, so passing
a full path with '\' arguments produces incorrect results on Windows

Anyway users are advised to use relative paths for portability
2017-10-11 19:37:17 -03:00
Dirk Thomas
ce8c829945 add test for #2824 2017-10-11 09:41:18 -07:00
Dirk Thomas
ed7aa074aa add changelog file for #2824 2017-10-11 09:41:14 -07:00
Dirk Thomas
66e9a79472 get PYTEST_ADDOPTS before calling _initini 2017-10-11 09:38:44 -07:00
Ronny Pfannschmidt
1480aed781 Merge pull request #2823 from hugovk/features-rm-2.6
Remove code for unsupported Python versions
2017-10-10 09:42:32 +02:00
Hugo
be0e2132b7 Update authors [CI skip] 2017-10-10 10:01:15 +03:00
hugovk
7113c76f0d Remove unused import 2017-10-10 09:03:26 +03:00
hugovk
ef732fc51d Remove code for unsupported Python versions 2017-10-10 08:54:56 +03:00
Ronny Pfannschmidt
dd45f8ba6c Merge pull request #2822 from RonnyPfannschmidt/nomore-py26
Nomore py26
2017-10-10 07:00:08 +02:00
Ronny Pfannschmidt
c486598440 remove some support code for old python versions 2017-10-09 17:25:13 +02:00
Bruno Oliveira
059455b45d Merge pull request #2773 from RonnyPfannschmidt/fix-markeval-2767
refactor mark evaluators
2017-10-09 12:17:15 -03:00
Ronny Pfannschmidt
73ff53c742 remove eol python from the ci config 2017-10-09 16:36:41 +02:00
Ronny Pfannschmidt
88366b393c start the removal of python 2.6/3.3 support 2017-10-09 16:35:33 +02:00
Ronny Pfannschmidt
9b0ce535c9 Merge pull request #2801 from nicoddemus/capture-fixture
Allow fixtures to use capsys and capfd
2017-10-09 15:26:50 +02:00
Ronny Pfannschmidt
8a6bdb282f fix changelog entry 2017-10-09 15:21:06 +02:00
Ronny Pfannschmidt
46e30435eb Merge pull request #2819 from leezu/fix_kwargs_fixtures
Fix pytest.parametrize when argnames are specified as kwarg
2017-10-09 06:49:40 +02:00
Leonard Lausen
e86ba41a32 Add testcase for #2819 2017-10-09 01:06:25 +09:00
Leonard Lausen
e89abe6a40 Defensive fallback in case of kwargs not being present 2017-10-09 00:37:27 +09:00
Leonard Lausen
48b5c13f73 Add changelog for #2819 2017-10-08 12:33:02 +09:00
Leonard Lausen
c24ffa3b4c Fix pytest.parametrize when argnames are specified as kwarg 2017-10-08 12:23:26 +09:00
Ronny Pfannschmidt
459cc40192 skipping: cleanup
remove dead comments
fix naming
remove dead code
2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
e3b73682b2 flake8 fix 2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
8480075f01 resuffle markevaluator internal structure 2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
9ad2b75038 skipping: replace _evalskip with a more consistent _skipped_by_mark 2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
a33650953a remove unused import 2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
667e70f555 switch out the placeholder MarkEvaluator in unittest plugin 2017-10-06 10:44:49 +02:00
Ronny Pfannschmidt
761d552814 Merge pull request #2815 from xuanluong/issue-1997-document-xpass
Add mention of xpass in skip/xfail documentation
2017-10-06 06:51:35 +02:00
Xuan Luong
4bc6ecb8a5 Add mention of xpass in skip/xfail documentation 2017-10-05 20:14:45 -04:00
George Y. Kussumoto
0668a6c6d3 Add myself to authors file 2017-10-04 22:14:29 -03:00
George Y. Kussumoto
03ce0adb79 Fix: handle CollectReport in folded_skips function 2017-10-04 19:18:55 -03:00
Bruno Oliveira
e7a4d3d8cf Merge remote-tracking branch 'upstream/master' into features 2017-10-04 17:28:34 -03:00
Bruno Oliveira
9ee0a1f5c3 Merge pull request #2813 from nicoddemus/release-3.2.3
Preparing release version 3.2.3
2017-10-04 17:27:36 -03:00
Ronny Pfannschmidt
61eb20df71 Merge pull request #2810 from nicoddemus/issue-2809
Fix warning about non-ascii warnings even when they are ascii
2017-10-03 20:56:23 +02:00
Bruno Oliveira
df6d5cd4e7 Use ascii_escaped to escape unicode warnings 2017-10-03 12:19:37 -03:00
Bruno Oliveira
fbb9e9328b Fix warning about non-ascii warnings even when they are ascii
Fix #2809
2017-10-03 07:39:53 -03:00
George Y. Kussumoto
9824499396 Add 2808.feature changelog entry 2017-10-02 21:26:29 -03:00
George Y. Kussumoto
59f66933cd Update documentation example of pytest.skip(allow_module_level=True) 2017-10-02 21:26:00 -03:00
George Y. Kussumoto
c1aa63c0bb Fix docstring alignment and typos 2017-10-02 21:24:52 -03:00
George Y. Kussumoto
e4a6e52b81 Update skipping documentation to include usage of allow_module_level kwarg 2017-10-02 21:07:21 -03:00
George Y. Kussumoto
06307be15d Add initial tests using skip with allow_module_level kwarg 2017-10-02 21:07:21 -03:00
George Y. Kussumoto
79d3353081 Add allow_module_level kwarg to skip helper 2017-10-02 21:07:21 -03:00
Bruno Oliveira
f9589f7b64 Resume output capturing after capsys/capfd.disabled() context manager
Fix #1993
2017-09-29 17:24:31 -03:00
Christian Boelsen
794d4585d3 Remove unnecessary complexity in _check_initialpaths_for_relpath(). 2017-09-28 20:53:50 +01:00
Ronny Pfannschmidt
d132c502e6 Merge pull request #2804 from nicoddemus/terminal-reporter-tw-2803
Deprecate TerminalReporter._tw
2017-09-28 10:01:24 +02:00
Bruno Oliveira
3b30c93f73 Deprecate TerminalReporter._tw
Fix #2803
2017-09-27 17:16:34 -03:00
Bruno Oliveira
c0c859ce99 Merge pull request #2799 from blueyed/cleanup-tox.ini
minor: cleanup tox.ini
2017-09-27 15:36:10 -03:00
Bruno Oliveira
22f338d74d Refactor some names for better understanding and consistency 2017-09-26 20:09:42 -03:00
Bruno Oliveira
9919269ed0 Allow to use capsys and capfd in other fixtures
Fix #2709
2017-09-26 20:09:36 -03:00
Daniel Hahler
87596714bf minor: cleanup tox.ini 2017-09-27 00:45:10 +02:00
Thomas Hisch
296ac5c476 Add thisch to AUTHORS 2017-09-26 23:24:27 +02:00
Thomas Hisch
ad21d5cac4 Remove pytest-capturelog backward compat code 2017-09-26 23:24:27 +02:00
Thomas Hisch
2559ec8bdb use 'formatter' kwarg of catching_logs 2017-09-22 22:27:50 +02:00
Thomas Hisch
207f153ec1 Remove logging_at_level ctx manager 2017-09-22 22:27:50 +02:00
Thomas Hisch
3a4011585f catching_logs: Remove usage of 'closing' ctx manager
The 'closing' context manager is only needed for the log_file_handler.
2017-09-22 22:27:50 +02:00
Thomas Hisch
57f66a455a catching_logs: Remove unused 'filter' kwarg 2017-09-22 22:27:50 +02:00
Thomas Hisch
e41fd52e8c Introduce live_logs context manager 2017-09-22 22:27:50 +02:00
Thomas Hisch
08f6b5f4ea Use pytest.hookimpl instead of pytest.mark.hookwrapper
pytest.mark.hookwrapper seems to be used nowhere in the _pytest package.
2017-09-22 22:27:50 +02:00
Thomas Hisch
d13e17cf51 Don't modify the 'config' object in __init__ 2017-09-22 22:27:50 +02:00
Thomas Hisch
f1f6109255 Remove _catchlog_ prefix 2017-09-22 22:27:50 +02:00
Thomas Hisch
87b8dc5afb Move 'config' handling from pytest_configure to __init__ 2017-09-22 22:27:50 +02:00
Thomas Hisch
fc965c1dc5 Remove outdated docstring 2017-09-22 22:27:50 +02:00
Thomas Hisch
a1bd54e4ea Clean-up LogCaptureHandler 2017-09-22 22:27:50 +02:00
Thomas Hisch
36cceeb10e Set type of log_print ini-variable to 'bool' 2017-09-22 22:27:50 +02:00
Thomas Hisch
3e71a50403 Remove unneeded sys import from unittest 2017-09-22 22:27:50 +02:00
Thomas Hisch
98209e92ee Remove superfluous whitespace in docstring 2017-09-22 22:27:50 +02:00
Thomas Hisch
1bea7e6985 Cleanup pytest_addoption 2017-09-22 22:27:50 +02:00
Thomas Hisch
1ba219e0da Adapt (logging) unittest 2017-09-22 22:27:50 +02:00
Thomas Hisch
a8e3effb6c Upgrade py 2017-09-22 22:27:49 +02:00
Thomas Hisch
ca46f4fe2a Remove conftest 2017-09-22 22:27:49 +02:00
Thomas Hisch
5130f5707f Fix name clash 2017-09-22 22:27:49 +02:00
Thomas Hisch
6607478b23 Add unittests for LoggingPlugin (excluding perf tests) 2017-09-22 22:27:49 +02:00
Thomas Hisch
8eafbd05ca Merge the pytest-catchlog plugin 2017-09-22 22:27:49 +02:00
Ronny Pfannschmidt
de0d19ca09 Merge pull request #2790 from nicoddemus/merge-master-into-features
Merge master into features
2017-09-20 10:25:56 +02:00
OfirOshir
d96869ff66 fixing cr 2017-09-20 09:45:40 +03:00
Ofir
b57a84d065 updating bugfix changelog 2017-09-19 15:36:12 +03:00
Ofir
c89827b9f2 updating import plugin error test in order to make sure it also checks that the original traceback has been shown to the users 2017-09-19 15:23:07 +03:00
Ofir
062a0e3e68 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 exceptions, the original exception while loading the plugin in addition to an exception that PyTest throws about loading a plugin.
2017-09-19 15:14:08 +03:00
Bruno Oliveira
a2da5a691a Update tox and appveyor environments to use py36 by default 2017-09-18 21:38:15 -03:00
Bruno Oliveira
afe7966683 Fix call to outcome.get_result now that outcome.result is deprecated 2017-09-18 21:36:54 -03:00
Bruno Oliveira
3ebfb881c9 Merge remote-tracking branch 'upstream/master' into features 2017-09-18 19:10:31 -03:00
Christian Boelsen
14b6380e5f Fix #2775 - running pytest with "--pyargs" will result in Items with empty "parent.nodeid" if run from a different root directory 2017-09-13 17:15:32 +01:00
Bruno Oliveira
9273e11f21 Merge branch 'master' into features 2017-09-07 13:59:10 -03:00
Ronny Pfannschmidt
6967f3070e Merge pull request #2711 from massich/mimic_raises_signature_in_warns
[MRG][feature] Change warns signature to mimic the raises call
2017-09-07 13:19:09 +02:00
Joan Massich
a0c6758202 Add changelog 2017-09-07 10:51:14 +02:00
Joan Massich
80d165475b Add documentation 2017-09-07 10:28:52 +02:00
Joan Massich
aa6a67044f Add match_regex functionality to warns 2017-09-07 09:58:03 +02:00
Bruno Oliveira
b55a4f805f Merge pull request #2744 from nicoddemus/pluggy-master
Add test environment using pluggy from master branch
2017-09-06 15:03:19 -03:00
Bruno Oliveira
d01f08e96f Merge branch 'features' into pluggy-master 2017-09-06 13:13:35 -03:00
Bruno Oliveira
e1f2254fc2 Merge pull request #2734 from RonnyPfannschmidt/simplify-string-safening
simplyfy ascii escaping by using backslashreplace error handling
2017-09-05 21:14:27 -03:00
Bruno Oliveira
f825b4979b Merge remote-tracking branch 'upstream/master' into features 2017-09-05 19:44:33 -03:00
Bruno Oliveira
3d70727021 Improve wording in changelog entry 2017-09-05 19:36:53 -03:00
Bruno Oliveira
7d59b2e350 Fix call to outcome.force_result
Even though the test is not running at the moment (xfail), at least
we avoid future confusion
2017-09-05 19:08:20 -03:00
Bruno Oliveira
d9992558fc Refactor tox.ini so pluggymaster envs share definitions 2017-09-05 19:04:58 -03:00
Bruno Oliveira
d1f71b0575 Merge pull request #2752 from tarcisiofischer/issue-2751-fix-flaky-testdir
Issue 2751 fix flaky testdir
2017-09-05 18:51:08 -03:00
Tarcisio Fischer
de6b41e318 Update changelog and AUTHORS files, following the CONTRIBUTING guidelines 2017-09-05 15:46:32 -03:00
Tarcisio Fischer
8d1903fed3 Avoid creating arbitrary filenames for tmpdir on Testdir's constructor
Fixes #2751
2017-09-05 15:22:04 -03:00
Ronny Pfannschmidt
13eac944ae restore ascii escaping for python 3.3/3.4 2017-09-04 21:25:46 +02:00
Joan Massich
d8ecca5ebd Add test to design warns signature in TDD mimicking raises signature 2017-09-04 15:26:00 +02:00
Bruno Oliveira
9bbf14d0f6 Create explicit 'pluggymaster' env definitions
For some reason, the previous approach brakes 'coveralls' because
pip still tries to install the 'pluggy' master requirement
(git+https://...)
2017-09-01 20:40:14 -03:00
Bruno Oliveira
c42d966a40 Change all pytest report options to the more concise '-ra' in tox.ini 2017-09-01 18:57:42 -03:00
Bruno Oliveira
3dc0da9339 Remove __multicall__ warning and usages in testing
pluggy>=0.5 already warns about those
2017-09-01 18:53:06 -03:00
Bruno Oliveira
11ec6aeafb Add test environment using pluggy from master branch
Fix #2737
2017-09-01 18:33:30 -03:00
Ronny Pfannschmidt
9d373d83ac Merge pull request #2741 from nicoddemus/pytester-makepyfile
Encode utf-8 byte strings in pytester's makefile
2017-09-01 08:31:35 +02:00
Bruno Oliveira
221797c609 Encode utf-8 byte strings in pytester's makefile
Fix #2738
2017-08-31 18:49:06 -03:00
Ronny Pfannschmidt
78a027e128 simplyfy ascii escaping by using backslashreplace error handling 2017-08-30 16:43:16 +02:00
Ronny Pfannschmidt
488bbd2aeb Merge pull request #2719 from tgoodlet/stop_vendoring_pluggy
Stop vendoring pluggy
2017-08-29 09:08:07 +02:00
Tyler Goodlet
312891daa6 Add a trivial changelog entry 2017-08-25 15:34:42 -04:00
Tyler Goodlet
fe415e3ff8 Use latest patch release 2017-08-25 11:49:02 -04:00
Tyler Goodlet
ff35c17ecf Drop wrapper module; import directly 2017-08-25 11:46:55 -04:00
Tyler Goodlet
9ab83083d1 Update docs 2017-08-24 16:53:37 -04:00
Tyler Goodlet
756db2131f Drop vendoring from packaging 2017-08-24 16:53:24 -04:00
Tyler Goodlet
cb700208e8 Drop vendoring task 2017-08-24 16:48:54 -04:00
Tyler Goodlet
333a9ad7fa Stop vendoring pluggy
Resolves #2716
2017-08-24 15:54:33 -04:00
Ronny Pfannschmidt
5c0feb2877 Merge pull request #2680 from prokaktus/skipping-same-module
Fold skipped tests with global pytestmark variable
2017-08-15 10:52:24 +02:00
Bruno Oliveira
5f17caa156 Merge pull request #2675 from RonnyPfannschmidt/mark-callspeck
ensure callspecs contain the list of marks
2017-08-14 20:33:16 -03:00
prokaktus
98bf5fc9be Fold skipped tests with global pytestmark variable 2017-08-15 02:18:46 +03:00
Ronny Pfannschmidt
eb462582af fix #2675 - store marks correctly in callspecs 2017-08-10 19:46:36 +02:00
Ronny Pfannschmidt
9e62a31b63 Merge pull request #2650 from srinivasreddy/2642
Fixed#2642: Convert py module references to six module
2017-08-04 09:21:51 +02:00
Bruno Oliveira
2e33d9b35e Add changelog entry for using six for portability 2017-08-04 00:03:43 -03:00
Srinivas Reddy Thatiparthy
dc563e4954 convert py module references to six module 2017-08-04 08:05:03 +05:30
Bruno Oliveira
40254b64e5 Merge branch 'master' into features 2017-08-01 19:35:56 -03:00
Ronny Pfannschmidt
6e7547244b Merge pull request #2636 from RonnyPfannschmidt/remove-preinit
remove preinit, its no longer needed
2017-08-01 05:22:41 +02:00
Ronny Pfannschmidt
333ec8ba5a Merge pull request #2638 from RonnyPfannschmidt/function-definition
small code cleanups
2017-08-01 05:21:21 +02:00
Ronny Pfannschmidt
dcaeef7c10 take review comments into account 2017-07-31 22:21:09 +02:00
Bruno Oliveira
8a2e6a8d51 Fix linting 2017-07-31 16:49:30 -03:00
Ronny Pfannschmidt
74d536314f pytester: make pytest fullpath a constant 2017-07-31 18:18:53 +02:00
Ronny Pfannschmidt
ceb016514b remove dead code - Node._memoizedcall 2017-07-31 18:18:26 +02:00
Ronny Pfannschmidt
e90f876b34 remove the last own implementation of pytest_namespace 2017-07-31 13:48:25 +02:00
Ronny Pfannschmidt
c68a89b4a7 remove preinit, its no longer needed 2017-07-31 13:44:05 +02: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
Dmitry Malinovsky
a63b34c685 Switch to item fspath 2016-12-20 10:44:09 +06:00
Dmitry Malinovsky
522d59e844 Use session.config.hook instead of ihook. Fixes #2124 2016-12-10 16:45:40 +06:00
Floris Bruynooghe
4c62cd451a Add section about how you become a committer 2016-11-17 21:25:22 +00:00
202 changed files with 10969 additions and 4930 deletions

View File

@@ -1,7 +1,4 @@
[run]
omit =
omit =
# standlonetemplate is read dynamically and tested by test_genscript
*standalonetemplate.py
# oldinterpret could be removed, as it is no longer used in py26+
*oldinterpret.py
vendored_packages

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.bug)
* 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`;
- [ ] 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
@@ -19,20 +17,18 @@ env:
- TOXENV=py27-xdist
- TOXENV=py27-trial
- TOXENV=py27-numpy
- TOXENV=py27-pluggymaster
- TOXENV=py36-pexpect
- TOXENV=py36-xdist
- TOXENV=py36-trial
- TOXENV=py36-numpy
- TOXENV=py36-pluggymaster
- TOXENV=py27-nobyte
- TOXENV=doctesting
- TOXENV=docs
matrix:
jobs:
include:
- env: TOXENV=py26
python: '2.6'
- env: TOXENV=py33
python: '3.3'
- env: TOXENV=pypy
python: 'pypy-5.4'
- env: TOXENV=py35
@@ -41,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

29
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,14 @@ 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)
Chris Lamb
@@ -37,6 +44,7 @@ Christian Boelsen
Christian Theunert
Christian Tismer
Christopher Gilling
Cyrus Maden
Daniel Grana
Daniel Hahler
Daniel Nuri
@@ -46,6 +54,7 @@ Dave Hunt
David Díaz-Barquero
David Mohr
David Vierra
Daw-Ran Liou
Denis Kirisov
Diego Russo
Dmitry Dygalo
@@ -64,6 +73,7 @@ Feng Ma
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
George Kussumoto
Georgy Dyuldin
Graham Horler
Greg Price
@@ -71,20 +81,26 @@ 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
Jon Sonesen
Jonas Obrist
Jordan Guymon
Jordan Moldow
Jordan Speicher
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@@ -92,6 +108,7 @@ Kale Kundert
Katarzyna Jachim
Kevin Cox
Kodi B. Arfer
Kostis Anagnostopoulos
Lawrence Mitchell
Lee Kamentsky
Lev Maximov
@@ -117,6 +134,7 @@ Matt Bachmann
Matt Duck
Matt Williams
Matthias Hafner
Maxim Filipenko
mbyt
Michael Aquilina
Michael Birtwell
@@ -130,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
@@ -151,6 +172,7 @@ Ronny Pfannschmidt
Ross Lawley
Russel Winder
Ryan Wooden
Samuel Dion-Girardeau
Samuele Pedroni
Segev Finer
Simon Gomizelj
@@ -161,18 +183,25 @@ Stefan Zimmermann
Stefano Taschini
Steffen Allner
Stephan Obermann
Tarcisio Fischer
Tareq Alayan
Ted Xiao
Thomas Grainger
Thomas Hisch
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,704 @@
.. 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)
=========================
Bug Fixes
---------
- Fix issue about ``-p no:<plugin>`` having no effect. (`#2920
<https://github.com/pytest-dev/pytest/issues/2920>`_)
- Fix regression with warnings that contained non-strings in their arguments in
Python 2. (`#2956 <https://github.com/pytest-dev/pytest/issues/2956>`_)
- Always escape null bytes when setting ``PYTEST_CURRENT_TEST``. (`#2957
<https://github.com/pytest-dev/pytest/issues/2957>`_)
- Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests
were actually collected. (`#2971
<https://github.com/pytest-dev/pytest/issues/2971>`_)
- Bring back ``TerminalReporter.writer`` as an alias to
``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
running pytest with it still installed. (`#3004
<https://github.com/pytest-dev/pytest/issues/3004>`_)
Improved Documentation
----------------------
- 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
<https://github.com/pytest-dev/pytest/issues/2949>`_)
Pytest 3.3.0 (2017-11-23)
=========================
Deprecations and Removals
-------------------------
- Pytest no longer supports Python **2.6** and **3.3**. Those Python versions
are EOL for some time now and incur maintenance and compatibility costs on
the pytest core team, and following up with the rest of the community we
decided that they will no longer be supported starting on this version. Users
which still require those versions should pin pytest to ``<3.3``. (`#2812
<https://github.com/pytest-dev/pytest/issues/2812>`_)
- Remove internal ``_preloadplugins()`` function. This removal is part of the
``pytest_namespace()`` hook deprecation. (`#2636
<https://github.com/pytest-dev/pytest/issues/2636>`_)
- Internally change ``CallSpec2`` to have a list of marks instead of a broken
mapping of keywords. This removes the keywords attribute of the internal
``CallSpec2`` class. (`#2672
<https://github.com/pytest-dev/pytest/issues/2672>`_)
- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack
of the underscore was a naming error. (`#2675
<https://github.com/pytest-dev/pytest/issues/2675>`_)
- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it
with the boolean ``Node._skipped_by_mark``. (`#2767
<https://github.com/pytest-dev/pytest/issues/2767>`_)
- The ``params`` list passed to ``pytest.fixture`` is now for
all effects considered immutable and frozen at the moment of the ``pytest.fixture``
call. Previously the list could be changed before the first invocation of the fixture
allowing for a form of dynamic parametrization (for example, updated from command-line options),
but this was an unwanted implementation detail which complicated the internals and prevented
some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_
for details and a recommended workaround.
Features
--------
- ``pytest_fixture_post_finalizer`` hook can now receive a ``request``
argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_)
- Replace the old introspection code in compat.py that determines the available
arguments of fixtures with inspect.signature on Python 3 and
funcsigs.signature on Python 2. This should respect ``__signature__``
declarations on functions. (`#2267
<https://github.com/pytest-dev/pytest/issues/2267>`_)
- Report tests with global ``pytestmark`` variable only once. (`#2549
<https://github.com/pytest-dev/pytest/issues/2549>`_)
- Now pytest displays the total progress percentage while running tests. The
previous output style can be set by configuring the ``console_output_style``
setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_)
- 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.
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
the captured log during specific tests (similar to ``capsys`` for example).
For more information, please see the `logging docs
<https://docs.pytest.org/en/latest/logging.html>`_. This feature was
introduced by merging the popular `pytest-catchlog
<https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch
<https://github.com/thisch>`_. Be advised that during the merging the
backward compatibility interface with the defunct ``pytest-capturelog`` has
been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_)
- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the
whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_)
- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824
<https://github.com/pytest-dev/pytest/issues/2824>`_)
- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and
``err`` can be accessed by attribute. (`#2879
<https://github.com/pytest-dev/pytest/issues/2879>`_)
- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from
``readouterr()``. (`#2923
<https://github.com/pytest-dev/pytest/issues/2923>`_)
- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from
``readouterr()``. (`#2934
<https://github.com/pytest-dev/pytest/issues/2934>`_)
- Implement feature to skip ``setup.py`` files when run with
``--doctest-modules``. (`#502
<https://github.com/pytest-dev/pytest/issues/502>`_)
Bug Fixes
---------
- Resume output capturing after ``capsys/capfd.disabled()`` context manager.
(`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_)
- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now
called for all ``conftest.py`` files. (`#2124
<https://github.com/pytest-dev/pytest/issues/2124>`_)
- If an exception happens while loading a plugin, pytest no longer hides the
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
<https://github.com/pytest-dev/pytest/issues/2491>`_)
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
<https://github.com/pytest-dev/pytest/issues/2709>`_)
- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to
``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_)
- ``testdir`` now uses use the same method used by ``tmpdir`` to create its
temporary directory. This changes the final structure of the ``testdir``
directory slightly, but should not affect usage in normal scenarios and
avoids a number of potential problems. (`#2751
<https://github.com/pytest-dev/pytest/issues/2751>`_)
- Pytest no longer complains about warnings with unicode messages being
non-ascii compatible even for ascii-compatible messages. As a result of this,
warnings with unicode messages are converted first to an ascii representation
for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_)
- Change return value of pytest command when ``--maxfail`` is reached from
``2`` (interrupted) to ``1`` (failed). (`#2845
<https://github.com/pytest-dev/pytest/issues/2845>`_)
- Fix issue in assertion rewriting which could lead it to rewrite modules which
should not be rewritten. (`#2939
<https://github.com/pytest-dev/pytest/issues/2939>`_)
- Handle marks without description in ``pytest.ini``. (`#2942
<https://github.com/pytest-dev/pytest/issues/2942>`_)
Trivial/Internal Changes
------------------------
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
structures to ease code maintainability. (`#2641
<https://github.com/pytest-dev/pytest/issues/2641>`_)
- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642
<https://github.com/pytest-dev/pytest/issues/2642>`_)
- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not
much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_)
- Internal refactor: simplify ascii string escaping by using the
backslashreplace error handler in newer Python 3 versions. (`#2734
<https://github.com/pytest-dev/pytest/issues/2734>`_)
- Remove unnecessary mark evaluator in unittest plugin (`#2767
<https://github.com/pytest-dev/pytest/issues/2767>`_)
- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function
is scheduled to be removed in ``pytest-4.0``. (`#2876
<https://github.com/pytest-dev/pytest/issues/2876>`_)
- Internal move of the parameterset extraction to a more maintainable place.
(`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_)
- Internal refactoring to simplify scope node lookup. (`#2910
<https://github.com/pytest-dev/pytest/issues/2910>`_)
- Configure ``pytest`` to prevent pip from installing pytest in unsupported
Python versions. (`#2922
<https://github.com/pytest-dev/pytest/issues/2922>`_)
Pytest 3.2.5 (2017-11-15)
=========================
Bug Fixes
---------
- Remove ``py<1.5`` restriction from ``pytest`` as this can cause version
conflicts in some installations. (`#2926
<https://github.com/pytest-dev/pytest/issues/2926>`_)
Pytest 3.2.4 (2017-11-13)
=========================
Bug Fixes
---------
- Fix the bug where running with ``--pyargs`` will result in items with
empty ``parent.nodeid`` if run from a different root directory. (`#2775
<https://github.com/pytest-dev/pytest/issues/2775>`_)
- Fix issue with ``@pytest.parametrize`` if argnames was specified as keyword arguments.
(`#2819 <https://github.com/pytest-dev/pytest/issues/2819>`_)
- Strip whitespace from marker names when reading them from INI config. (`#2856
<https://github.com/pytest-dev/pytest/issues/2856>`_)
- Show full context of doctest source in the pytest output, if the line number of
failed example in the docstring is < 9. (`#2882
<https://github.com/pytest-dev/pytest/issues/2882>`_)
- Match fixture paths against actual path segments in order to avoid matching folders which share a prefix.
(`#2836 <https://github.com/pytest-dev/pytest/issues/2836>`_)
Improved Documentation
----------------------
- Introduce a dedicated section about conftest.py. (`#1505
<https://github.com/pytest-dev/pytest/issues/1505>`_)
- Explicitly mention ``xpass`` in the documentation of ``xfail``. (`#1997
<https://github.com/pytest-dev/pytest/issues/1997>`_)
- Append example for pytest.param in the example/parametrize document. (`#2658
<https://github.com/pytest-dev/pytest/issues/2658>`_)
- Clarify language of proposal for fixtures parameters (`#2893
<https://github.com/pytest-dev/pytest/issues/2893>`_)
- List python 3.6 in the documented supported versions in the getting started
document. (`#2903 <https://github.com/pytest-dev/pytest/issues/2903>`_)
- Clarify the documentation of available fixture scopes. (`#538
<https://github.com/pytest-dev/pytest/issues/538>`_)
- Add documentation about the ``python -m pytest`` invocation adding the
current directory to sys.path. (`#911
<https://github.com/pytest-dev/pytest/issues/911>`_)
Pytest 3.2.3 (2017-10-03)
=========================
@@ -132,7 +830,7 @@ Deprecations and Removals
-------------------------
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
operators to avoid surprising/inconsistent behavior. See `the docs
operators to avoid surprising/inconsistent behavior. See `the approx docs
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
@@ -2019,7 +2717,7 @@ time or change existing behaviors in order to make them less surprising/more use
- fix issue655: work around different ways that cause python2/3
to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
- fix issue615: assertion re-writing did not correctly escape % signs
- fix issue615: assertion rewriting did not correctly escape % signs
when formatting boolean operations, which tripped over mixing
booleans with modulo operators. Thanks to Tom Viner for the report,
triaging and fix.

View File

@@ -49,7 +49,7 @@ Fix bugs
--------
Look through the GitHub issues for bugs. Here is a filter you can use:
https://github.com/pytest-dev/pytest/labels/bug
https://github.com/pytest-dev/pytest/labels/type%3A%20bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
@@ -177,7 +177,8 @@ Short version
#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number
and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or
``trivial`` for the issue type.
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
#. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please
add yourself to the ``AUTHORS`` file, in alphabetical order;
Long version
@@ -275,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.
@@ -76,7 +79,7 @@ Features
- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
`nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

View File

@@ -4,9 +4,6 @@ needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
to find the magic string, so _ARGCOMPLETE env. var is never set, and
this does not need special code.
argcomplete does not support python 2.5 (although the changes for that
are minor).
Function try_argcomplete(parser) should be called directly before
the call to ArgumentParser.parse_args().
@@ -63,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
@@ -8,8 +12,6 @@ from _pytest.compat import _PY2, _PY3, PY35, safe_str
import py
builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib')
if _PY3:
from traceback import format_exception_only
else:
@@ -235,7 +237,7 @@ class TracebackEntry(object):
except KeyError:
return False
if py.builtin.callable(tbh):
if callable(tbh):
return tbh(None if self._excinfo is None else self._excinfo())
else:
return tbh
@@ -250,7 +252,7 @@ class TracebackEntry(object):
line = str(self.statement).lstrip()
except KeyboardInterrupt:
raise
except:
except: # noqa
line = "???"
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
@@ -338,16 +340,16 @@ class Traceback(list):
# XXX needs a test
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
# print "checking for recursion at", key
l = cache.setdefault(key, [])
if l:
values = cache.setdefault(key, [])
if values:
f = entry.frame
loc = f.f_locals
for otherloc in l:
for otherloc in values:
if f.is_true(f.eval(co_equal,
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
return i
l.append(entry.frame.f_locals)
values.append(entry.frame.f_locals)
return None
@@ -424,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,
@@ -458,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
@@ -478,12 +480,12 @@ class FormattedExcinfo(object):
s = str(source.getstatement(len(source) - 1))
except KeyboardInterrupt:
raise
except:
except: # noqa
try:
s = str(source[-1])
except KeyboardInterrupt:
raise
except:
except: # noqa
return 0
return 4 + (len(s) - len(s.lstrip()))
@@ -558,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):
@@ -671,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)]
@@ -888,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,18 +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):
@@ -28,11 +26,11 @@ 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]
elif isinstance(part, py.builtin._basestring):
elif isinstance(part, six.string_types):
partlines = part.split('\n')
if rstrip:
while partlines:
@@ -100,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.
"""
@@ -133,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())
@@ -194,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
#
@@ -208,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
@@ -221,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
@@ -251,10 +242,10 @@ 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:
except: # noqa
return None, -1
source = Source()
source.lines = [line.rstrip() for line in sourcelines]
@@ -262,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:
@@ -289,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)
@@ -319,35 +308,30 @@ def get_statement_startend2(lineno, node):
import ast
# flatten all statements and except handlers into one lineno-list
# AST's line numbers start indexing at 1
l = []
values = []
for x in ast.walk(node):
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
l.append(x.lineno - 1)
for name in "finalbody", "orelse":
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
values.append(x.lineno - 1)
for name in ("finalbody", "orelse"):
val = getattr(x, name, None)
if val:
# treat the finally/orelse part as its own statement
l.append(val[0].lineno - 1 - 1)
l.sort()
insert_index = bisect_right(l, lineno)
start = l[insert_index - 1]
if insert_index >= len(l):
values.append(val[0].lineno - 1 - 1)
values.sort()
insert_index = bisect_right(values, lineno)
start = values[insert_index - 1]
if insert_index >= len(values):
end = None
else:
end = l[insert_index]
end = values[insert_index]
return start, end
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
if astnode is None:
content = str(source)
if sys.version_info < (2, 7):
content += "\n"
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
@@ -379,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

@@ -1,11 +0,0 @@
"""
imports symbols from vendored "pluggy" if available, otherwise
falls back to importing "pluggy" from the default namespace.
"""
from __future__ import absolute_import, division, print_function
try:
from _pytest.vendored_packages.pluggy import * # noqa
from _pytest.vendored_packages.pluggy import __version__ # noqa
except ImportError:
from pluggy import * # noqa
from pluggy import __version__ # noqa

View File

@@ -2,8 +2,8 @@
support for presenting detailed information in failing assertions.
"""
from __future__ import absolute_import, division, print_function
import py
import sys
import six
from _pytest.assertion import util
from _pytest.assertion import rewrite
@@ -56,7 +56,7 @@ class DummyRewriteHook(object):
pass
class AssertionState:
class AssertionState(object):
"""State for the assertion plugin."""
def __init__(self, config, mode):
@@ -67,10 +67,8 @@ class AssertionState:
def install_importhook(config):
"""Try to install the rewrite hook, raise SystemError if it fails."""
# Both Jython and CPython 2.6.0 have AST bugs that make the
# assertion rewriting hook malfunction.
if (sys.platform.startswith('java') or
sys.version_info[:3] == (2, 6, 0)):
# Jython has an AST bug that make the assertion rewriting hook malfunction.
if (sys.platform.startswith('java')):
raise SystemError('rewrite not supported')
config._assertstate = AssertionState(config, 'rewrite')
@@ -126,7 +124,7 @@ def pytest_runtest_setup(item):
if new_expl:
new_expl = truncate.truncate_if_required(new_expl, item)
new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = py.builtin._totext("\n~").join(new_expl)
res = six.text_type("\n~").join(new_expl)
if item.config.getvalue("assertmode") == "rewrite":
res = res.replace("%", "%%")
return res

View File

@@ -1,13 +1,13 @@
"""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
import marshal
import os
import re
import six
import struct
import sys
import types
@@ -33,7 +33,6 @@ else:
PYC_EXT = ".py" + (__debug__ and "c" or "o")
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
if sys.version_info >= (3, 5):
@@ -168,29 +167,31 @@ class AssertionRewritingHook(object):
return True
for marked in self._must_rewrite:
if name.startswith(marked):
if name == marked or name.startswith(marked + '.'):
state.trace("matched marked file %r (from %r)" % (name, marked))
return True
return False
def mark_rewrite(self, *names):
"""Mark import names as needing to be re-written.
"""Mark import names as needing to be rewritten.
The named module or package as well as any nested modules will
be re-written on import.
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):
self.config.warn(
'P1',
'Module already imported so can not be re-written: %s' % name)
'Module already imported so cannot be rewritten: %s' % name)
def load_module(self, name):
# If there is an existing module object named 'fullname' in
@@ -210,7 +211,7 @@ class AssertionRewritingHook(object):
mod.__cached__ = pyc
mod.__loader__ = self
py.builtin.exec_(co, mod.__dict__)
except:
except: # noqa
if name in sys.modules:
del sys.modules[name]
raise
@@ -320,10 +321,6 @@ def _rewrite_test(config, fn):
return None, None
finally:
del state._indecode
# On Python versions which are not 2.7 and less than or equal to 3.1, the
# parser expects *nix newlines.
if REWRITE_NEWLINES:
source = source.replace(RN, N) + N
try:
tree = ast.parse(source)
except SyntaxError:
@@ -405,10 +402,10 @@ def _saferepr(obj):
"""
repr = py.io.saferepr(obj)
if py.builtin._istext(repr):
t = py.builtin.text
if isinstance(repr, six.text_type):
t = six.text_type
else:
t = py.builtin.bytes
t = six.binary_type
return repr.replace(t("\n"), t("\\n"))
@@ -427,16 +424,16 @@ def _format_assertmsg(obj):
# contains a newline it gets escaped, however if an object has a
# .__repr__() which contains newlines it does not get escaped.
# However in either case we want to preserve the newline.
if py.builtin._istext(obj) or py.builtin._isbytes(obj):
if isinstance(obj, six.text_type) or isinstance(obj, six.binary_type):
s = obj
is_repr = False
else:
s = py.io.saferepr(obj)
is_repr = True
if py.builtin._istext(s):
t = py.builtin.text
if isinstance(s, six.text_type):
t = six.text_type
else:
t = py.builtin.bytes
t = six.binary_type
s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
if is_repr:
s = s.replace(t("\\n"), t("\n~"))
@@ -444,15 +441,15 @@ def _format_assertmsg(obj):
def _should_repr_global_name(obj):
return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
return not hasattr(obj, "__name__") and not callable(obj)
def _format_boolop(explanations, is_or):
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
if py.builtin._istext(explanation):
t = py.builtin.text
if isinstance(explanation, six.text_type):
t = six.text_type
else:
t = py.builtin.bytes
t = six.binary_type
return explanation.replace(t('%'), t('%%'))
@@ -533,7 +530,7 @@ class AssertionRewriter(ast.NodeVisitor):
"""Assertion rewriting implementation.
The main entrypoint is to call .run() with an ast.Module instance,
this will then find all the assert statements and re-write them to
this will then find all the assert statements and rewrite them to
provide intermediate values and a detailed assertion error. See
http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
for an overview of how this works.
@@ -542,7 +539,7 @@ class AssertionRewriter(ast.NodeVisitor):
statements in an ast.Module and for each ast.Assert statement it
finds call .visit() with it. Then .visit_Assert() takes over and
is responsible for creating new ast statements to replace the
original assert statement: it re-writes the test of an assertion
original assert statement: it rewrites the test of an assertion
to provide intermediate values and replace it with an if statement
which raises an assertion error with a detailed explanation in
case the expression is false.
@@ -595,23 +592,26 @@ class AssertionRewriter(ast.NodeVisitor):
# docstrings and __future__ imports.
aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
expect_docstring = True
doc = getattr(mod, "docstring", None)
expect_docstring = doc is None
if doc is not None and self.is_rewrite_disabled(doc):
return
pos = 0
lineno = 0
lineno = 1
for item in mod.body:
if (expect_docstring and isinstance(item, ast.Expr) and
isinstance(item.value, ast.Str)):
doc = item.value.s
if "PYTEST_DONT_REWRITE" in doc:
# The module has disabled assertion rewriting.
if self.is_rewrite_disabled(doc):
return
lineno += len(doc) - 1
expect_docstring = False
elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
item.module != "__future__"):
lineno = item.lineno
break
pos += 1
else:
lineno = item.lineno
imports = [ast.Import([alias], lineno=lineno, col_offset=0)
for alias in aliases]
mod.body[pos:pos] = imports
@@ -637,6 +637,10 @@ class AssertionRewriter(ast.NodeVisitor):
not isinstance(field, ast.expr)):
nodes.append(field)
@staticmethod
def is_rewrite_disabled(docstring):
return "PYTEST_DONT_REWRITE" in docstring
def variable(self):
"""Get a new variable."""
# Use a character invalid in python identifiers to avoid clashing.
@@ -720,7 +724,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Assert(self, assert_):
"""Return the AST statements to replace the ast.Assert instance.
This re-writes the test of an assertion to provide
This rewrites the test of an assertion to provide
intermediate values and replace it with an if statement which
raises an assertion error with a detailed explanation in case
the expression is false.
@@ -912,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]
@@ -923,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

@@ -7,7 +7,7 @@ Current default behaviour is to truncate assertion explanations at
from __future__ import absolute_import, division, print_function
import os
import py
import six
DEFAULT_MAX_LINES = 8
@@ -74,8 +74,8 @@ def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
msg += ' ({0} lines hidden)'.format(truncated_line_count)
msg += ", {0}" .format(USAGE_MSG)
truncated_explanation.extend([
py.builtin._totext(""),
py.builtin._totext(msg),
six.text_type(""),
six.text_type(msg),
])
return truncated_explanation

View File

@@ -4,13 +4,10 @@ import pprint
import _pytest._code
import py
try:
from collections import Sequence
except ImportError:
Sequence = list
import six
from collections import Sequence
u = py.builtin._totext
u = six.text_type
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
@@ -53,11 +50,11 @@ def _split_explanation(explanation):
"""
raw_lines = (explanation or u('')).split('\n')
lines = [raw_lines[0]]
for l in raw_lines[1:]:
if l and l[0] in ['{', '}', '~', '>']:
lines.append(l)
for values in raw_lines[1:]:
if values and values[0] in ['{', '}', '~', '>']:
lines.append(values)
else:
lines[-1] += '\\n' + l
lines[-1] += '\\n' + values
return lines
@@ -112,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)
@@ -174,9 +171,9 @@ def _diff_text(left, right, verbose=False):
"""
from difflib import ndiff
explanation = []
if isinstance(left, py.builtin.bytes):
if isinstance(left, six.binary_type):
left = u(repr(left)[1:-1]).replace(r'\n', '\n')
if isinstance(right, py.builtin.bytes):
if isinstance(right, six.binary_type):
right = u(repr(right)[1:-1]).replace(r'\n', '\n')
if not verbose:
i = 0 # just in case left or right has zero length

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

@@ -4,6 +4,7 @@ per-test stdout/stderr capturing mechanism.
"""
from __future__ import absolute_import, division, print_function
import collections
import contextlib
import sys
import os
@@ -11,11 +12,10 @@ import io
from io import UnsupportedOperation
from tempfile import TemporaryFile
import py
import six
import pytest
from _pytest.compat import CaptureIO
unicode = py.builtin.text
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -44,7 +44,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
early_config.add_cleanup(capman.reset_capturings)
early_config.add_cleanup(capman.stop_global_capturing)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
@@ -53,17 +53,30 @@ def pytest_load_initial_conftests(early_config, parser, args):
early_config.add_cleanup(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.init_capturings()
capman.start_global_capturing()
outcome = yield
out, err = capman.suspendcapture()
out, err = capman.suspend_global_capture()
if outcome.excinfo is not None:
sys.stdout.write(out)
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
attached to the collection/runtest report.
There are two levels of capture:
* global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
during collection and each test phase.
* fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
case special handling is needed to ensure the fixtures take precedence over the global capture.
"""
def __init__(self, method):
self._method = method
self._global_capturing = None
def _getcapture(self, method):
if method == "fd":
@@ -75,23 +88,24 @@ class CaptureManager:
else:
raise ValueError("unknown capturing method: %r" % method)
def init_capturings(self):
assert not hasattr(self, "_capturing")
self._capturing = self._getcapture(self._method)
self._capturing.start_capturing()
def start_global_capturing(self):
assert self._global_capturing is None
self._global_capturing = self._getcapture(self._method)
self._global_capturing.start_capturing()
def reset_capturings(self):
cap = self.__dict__.pop("_capturing", None)
if cap is not None:
cap.pop_outerr_to_orig()
cap.stop_capturing()
def stop_global_capturing(self):
if self._global_capturing is not None:
self._global_capturing.pop_outerr_to_orig()
self._global_capturing.stop_capturing()
self._global_capturing = None
def resumecapture(self):
self._capturing.resume_capturing()
def resume_global_capture(self):
self._global_capturing.resume_capturing()
def suspendcapture(self, in_=False):
self.deactivate_funcargs()
cap = getattr(self, "_capturing", None)
def suspend_global_capture(self, item=None, in_=False):
if item is not None:
self.deactivate_fixture(item)
cap = getattr(self, "_global_capturing", None)
if cap is not None:
try:
outerr = cap.readouterr()
@@ -99,23 +113,26 @@ class CaptureManager:
cap.suspend_capturing(in_=in_)
return outerr
def activate_funcargs(self, pyfuncitem):
capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
if capfuncarg is not None:
capfuncarg._start()
self._capfuncarg = capfuncarg
def activate_fixture(self, item):
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
the global capture.
"""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture._start()
def deactivate_funcargs(self):
capfuncarg = self.__dict__.pop("_capfuncarg", None)
if capfuncarg is not None:
capfuncarg.close()
def deactivate_fixture(self, item):
"""Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture.close()
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector):
if isinstance(collector, pytest.File):
self.resumecapture()
self.resume_global_capture()
outcome = yield
out, err = self.suspendcapture()
out, err = self.suspend_global_capture()
rep = outcome.get_result()
if out:
rep.sections.append(("Captured stdout", out))
@@ -126,68 +143,139 @@ class CaptureManager:
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item):
self.resumecapture()
self.resume_global_capture()
# no need to activate a capture fixture because they activate themselves during creation; this
# only makes sense when a fixture uses a capture fixture, otherwise the capture fixture will
# be activated during pytest_runtest_call
yield
self.suspendcapture_item(item, "setup")
self.suspend_capture_item(item, "setup")
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item):
self.resumecapture()
self.activate_funcargs(item)
self.resume_global_capture()
# it is important to activate this fixture during the call phase so it overwrites the "global"
# capture
self.activate_fixture(item)
yield
# self.deactivate_funcargs() called from suspendcapture()
self.suspendcapture_item(item, "call")
self.suspend_capture_item(item, "call")
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item):
self.resumecapture()
self.resume_global_capture()
self.activate_fixture(item)
yield
self.suspendcapture_item(item, "teardown")
self.suspend_capture_item(item, "teardown")
@pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo):
self.reset_capturings()
self.stop_global_capturing()
@pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo):
self.reset_capturings()
self.stop_global_capturing()
def suspendcapture_item(self, item, when, in_=False):
out, err = self.suspendcapture(in_=in_)
def suspend_capture_item(self, item, when, in_=False):
out, err = self.suspend_global_capture(item, in_=in_)
item.add_report_section(when, "stdout", out)
item.add_report_section(when, "stderr", err)
error_capsysfderror = "cannot use capsys and capfd at the same time"
capture_fixtures = {'capfd', 'capfdbinary', 'capsys', 'capsysbinary'}
def _ensure_only_one_capture_fixture(request, name):
fixtures = set(request.fixturenames) & capture_fixtures - set((name,))
if fixtures:
fixtures = sorted(fixtures)
fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
raise request.raiseerror(
"cannot use {0} and {1} at the same time".format(
fixtures, 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.
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
objects.
"""
if "capfd" in request.fixturenames:
raise request.raiseerror(error_capsysfderror)
request.node._capfuncarg = c = CaptureFixture(SysCapture, request)
return c
_ensure_only_one_capture_fixture(request, 'capsys')
with _install_capture_fixture_on_item(request, SysCapture) as fixture:
yield fixture
@pytest.fixture
def capsysbinary(request):
"""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.
"""
_ensure_only_one_capture_fixture(request, 'capsysbinary')
# Currently, the implementation uses the python3 specific `.buffer`
# property of CaptureIO.
if sys.version_info < (3,):
raise request.raiseerror('capsysbinary is only supported on python 3')
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
yield fixture
@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.
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
objects.
"""
if "capsys" in request.fixturenames:
request.raiseerror(error_capsysfderror)
_ensure_only_one_capture_fixture(request, 'capfd')
if not hasattr(os, 'dup'):
pytest.skip("capfd funcarg needs os.dup")
request.node._capfuncarg = c = CaptureFixture(FDCapture, request)
return c
pytest.skip("capfd fixture needs os.dup function which is not available in this system")
with _install_capture_fixture_on_item(request, FDCapture) as fixture:
yield fixture
class CaptureFixture:
@pytest.fixture
def capfdbinary(request):
"""Enable capturing of write to file descriptors 1 and 2 and make
captured output available via ``capfdbinary.readouterr`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
``bytes`` objects.
"""
_ensure_only_one_capture_fixture(request, 'capfdbinary')
if not hasattr(os, 'dup'):
pytest.skip("capfdbinary fixture needs os.dup function which is not available in this system")
with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
yield fixture
@contextlib.contextmanager
def _install_capture_fixture_on_item(request, capture_class):
"""
Context manager which creates a ``CaptureFixture`` instance and "installs" it on
the item/node of the given request. Used by ``capsys`` and ``capfd``.
The CaptureFixture is added as attribute of the item because it needs to accessed
by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
"""
request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
capmanager = request.config.pluginmanager.getplugin('capturemanager')
# need to active this fixture right away in case it is being used by another fixture (setup phase)
# if this fixture is being used only by a test function (call phase), then we wouldn't need this
# activation, but it doesn't hurt
capmanager.activate_fixture(request.node)
yield fixture
fixture.close()
del request.node._capture_fixture
class CaptureFixture(object):
"""
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
@@ -204,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:
@@ -211,12 +303,15 @@ 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.suspendcapture_item(self.request.node, "call", in_=True)
capmanager.suspend_global_capture(item=None, in_=False)
try:
yield
finally:
capmanager.resumecapture()
capmanager.resume_global_capture()
self._capture.resume_capturing()
def safe_text_dupfile(f, mode, default_encoding="UTF8"):
@@ -246,7 +341,7 @@ class EncodedFile(object):
self.encoding = encoding
def write(self, obj):
if isinstance(obj, unicode):
if isinstance(obj, six.text_type):
obj = obj.encode(self.encoding, "replace")
self.buffer.write(obj)
@@ -263,6 +358,9 @@ class EncodedFile(object):
return getattr(object.__getattribute__(self, "buffer"), name)
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
class MultiCapture(object):
out = err = in_ = None
@@ -323,16 +421,19 @@ class MultiCapture(object):
def readouterr(self):
""" return snapshot unicode value of stdout/stderr capturings. """
return (self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "")
return CaptureResult(self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "")
class NoCapture:
class NoCapture(object):
__init__ = start = done = suspend = resume = lambda *args: None
class FDCapture:
""" Capture IO to/from a given os-level filedescriptor. """
class FDCaptureBinary(object):
"""Capture IO to/from a given os-level filedescriptor.
snap() produces `bytes`
"""
def __init__(self, targetfd, tmpfile=None):
self.targetfd = targetfd
@@ -371,17 +472,11 @@ class FDCapture:
self.syscapture.start()
def snap(self):
f = self.tmpfile
f.seek(0)
res = f.read()
if res:
enc = getattr(f, "encoding", None)
if enc and isinstance(res, bytes):
res = py.builtin._totext(res, enc, "replace")
f.truncate(0)
f.seek(0)
return res
return ''
self.tmpfile.seek(0)
res = self.tmpfile.read()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
def done(self):
""" stop capturing, restore streams, return original capture file,
@@ -390,7 +485,7 @@ class FDCapture:
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()
@@ -402,12 +497,25 @@ class FDCapture:
def writeorg(self, data):
""" write to original file descriptor. """
if py.builtin._istext(data):
if isinstance(data, six.text_type):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
class SysCapture:
class FDCapture(FDCaptureBinary):
"""Capture IO to/from a given os-level filedescriptor.
snap() produces text
"""
def snap(self):
res = FDCaptureBinary.snap(self)
enc = getattr(self.tmpfile, "encoding", None)
if enc and isinstance(res, bytes):
res = six.text_type(res, enc, "replace")
return res
class SysCapture(object):
def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
self._old = getattr(sys, name)
@@ -423,16 +531,15 @@ class SysCapture:
setattr(sys, self.name, self.tmpfile)
def snap(self):
f = self.tmpfile
res = f.getvalue()
f.truncate(0)
f.seek(0)
res = self.tmpfile.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
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)
@@ -445,7 +552,15 @@ class SysCapture:
self._old.flush()
class DontReadFromInput:
class SysCaptureBinary(SysCapture):
def snap(self):
res = self.tmpfile.buffer.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
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,
@@ -459,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, "
@@ -575,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

@@ -2,18 +2,18 @@
python version compatibility code
"""
from __future__ import absolute_import, division, print_function
import sys
import inspect
import types
import re
import codecs
import functools
import inspect
import re
import sys
import py
import _pytest
from _pytest.outcomes import TEST_OUTCOME
try:
import enum
except ImportError: # pragma: no cover
@@ -25,6 +25,12 @@ _PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
if _PY3:
from inspect import signature, Parameter as Parameter
else:
from funcsigs import signature, Parameter as Parameter
NoneType = type(None)
NOTSET = object()
@@ -32,12 +38,10 @@ PY35 = sys.version_info[:2] >= (3, 5)
PY36 = sys.version_info[:2] >= (3, 6)
MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError'
if hasattr(inspect, 'signature'):
def _format_args(func):
return str(inspect.signature(func))
else:
def _format_args(func):
return inspect.formatargspec(*inspect.getargspec(func))
def _format_args(func):
return str(signature(func))
isfunction = inspect.isfunction
isclass = inspect.isclass
@@ -63,7 +67,6 @@ def iscoroutinefunction(func):
def getlocation(function, curdir):
import inspect
fn = py.path.local(inspect.getfile(function))
lineno = py.builtin._getcode(function).co_firstlineno
if fn.relto(curdir):
@@ -76,67 +79,72 @@ 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)
def getfuncargnames(function, startindex=None, cls=None):
"""
@RonnyPfannschmidt: This function should be refactored when we revisit fixtures. The
fixture mechanism should ask the node for the fixture names, and not try to obtain
directly from the function object well after collection has occurred.
"""
if startindex is None and cls is not None:
is_staticmethod = isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
startindex = 0 if is_staticmethod else 1
# XXX merge with main.py's varnames
# assert not isclass(function)
realfunction = function
while hasattr(realfunction, "__wrapped__"):
realfunction = realfunction.__wrapped__
if startindex is None:
startindex = inspect.ismethod(function) and 1 or 0
if realfunction != function:
startindex += num_mock_patch_args(function)
function = realfunction
if isinstance(function, functools.partial):
argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0]
partial = function
argnames = argnames[len(partial.args):]
if partial.keywords:
for kw in partial.keywords:
argnames.remove(kw)
else:
argnames = inspect.getargs(_pytest._code.getrawcode(function))[0]
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)
if numdefaults:
return tuple(argnames[startindex:-numdefaults])
return tuple(argnames[startindex:])
def getfuncargnames(function, is_method=False, cls=None):
"""Returns the names of a function's mandatory arguments.
This should return the names of all function arguments that:
* Aren't bound to an instance or type as in instance or class methods.
* Don't have default values.
* Aren't bound with functools.partial.
* Aren't replaced with mocks.
if sys.version_info[:2] == (2, 6):
def isclass(object):
""" Return true if the object is a class. Overrides inspect.isclass for
python 2.6 because it will return True for objects which always return
something on __getattr__ calls (see #1035).
Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc
"""
return isinstance(object, (type, types.ClassType))
The is_method and cls arguments indicate that the function should
be treated as a bound method even though it's not unless, only in
the case of cls, the function is a static method.
@RonnyPfannschmidt: This function should be refactored when we
revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the
function object well after collection has occurred.
"""
# The parameters attribute of a Signature object contains an
# ordered mapping of parameter names to Parameter instances. This
# creates a tuple of the names of the parameters that don't have
# defaults.
arg_names = tuple(p.name for p in signature(function).parameters.values()
if (p.kind is Parameter.POSITIONAL_OR_KEYWORD or
p.kind is Parameter.KEYWORD_ONLY) and
p.default is Parameter.empty)
# If this function should be treated as a bound method even though
# it's passed as an unbound method or function, remove the first
# parameter name.
if (is_method or
(cls and not isinstance(cls.__dict__.get(function.__name__, None),
staticmethod))):
arg_names = arg_names[1:]
# Remove any names that will be replaced with mocks.
if hasattr(function, "__wrapped__"):
arg_names = arg_names[num_mock_patch_args(function):]
return arg_names
if _PY3:
import codecs
imap = map
izip = zip
STRING_TYPES = bytes, str
UNICODE_TYPES = str,
def _ascii_escaped(val):
if PY35:
def _bytes_to_ascii(val):
return val.decode('ascii', 'backslashreplace')
else:
def _bytes_to_ascii(val):
if val:
# source: http://goo.gl/bGsnwC
encoded_bytes, _ = codecs.escape_encode(val)
return encoded_bytes.decode('ascii')
else:
# empty bytes crashes codecs.escape_encode (#1087)
return ''
def ascii_escaped(val):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
@@ -155,22 +163,14 @@ if _PY3:
"""
if isinstance(val, bytes):
if val:
# source: http://goo.gl/bGsnwC
encoded_bytes, _ = codecs.escape_encode(val)
return encoded_bytes.decode('ascii')
else:
# empty bytes crashes codecs.escape_encode (#1087)
return ''
return _bytes_to_ascii(val)
else:
return val.encode('unicode_escape').decode('ascii')
else:
STRING_TYPES = bytes, str, unicode
UNICODE_TYPES = unicode,
from itertools import imap, izip # NOQA
def _ascii_escaped(val):
def ascii_escaped(val):
"""In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string,
otherwise escape it into its binary form.
@@ -223,10 +223,7 @@ def getimfunc(func):
try:
return func.__func__
except AttributeError:
try:
return func.im_func
except AttributeError:
return func
return func
def safe_getattr(object, name, default):

View File

@@ -5,15 +5,18 @@ 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
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
from pluggy import PluginManager, HookimplMarker, HookspecMarker
from _pytest.compat import safe_str
hookimpl = HookimplMarker("pytest")
@@ -51,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:
@@ -59,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)
@@ -100,27 +104,18 @@ def directory_arg(path, optname):
return path
_preinit = []
default_plugins = (
"mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan warnings").split()
"setuponly setupplan warnings logging").split()
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
def _preloadplugins():
assert not _preinit
_preinit.append(get_config())
def get_config():
if _preinit:
return _preinit.pop(0)
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
config = Config(pluginmanager)
@@ -158,7 +153,7 @@ def _prepareconfig(args=None, plugins=None):
try:
if plugins:
for plugin in plugins:
if isinstance(plugin, py.builtin._basestring):
if isinstance(plugin, six.string_types):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
@@ -173,10 +168,10 @@ def _prepareconfig(args=None, plugins=None):
class PytestPluginManager(PluginManager):
"""
Overwrites :py:class:`pluggy.PluginManager <_pytest.vendored_packages.pluggy.PluginManager>` to add pytest-specific
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;
"""
@@ -206,12 +201,14 @@ 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):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>`
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
instead.
"""
warning = dict(code="I2",
@@ -249,18 +246,12 @@ class PytestPluginManager(PluginManager):
"historic": hasattr(method, "historic")}
return opts
def _verify_hook(self, hook, hookmethod):
super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
if "__multicall__" in hookmethod.argnames:
fslineno = _pytest._code.getfslineno(hookmethod.function)
warning = dict(code="I1",
fslocation=fslineno,
nodeid=None,
message="%r hook uses deprecated __multicall__ "
"argument" % (hook.name))
self._warn(warning)
def register(self, plugin, name=None):
if name in ['pytest_catchlog', 'pytest_capturelog']:
self._warn('{0} plugin has been merged into the core, '
'please remove it from your requirements.'.format(
name.replace('_', '-')))
return
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
@@ -287,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 {
@@ -377,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())
@@ -430,9 +425,9 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, (py.builtin.text, str)), "module name as text required, got %r" % modname
assert isinstance(modname, (six.text_type, str)), "module name as text required, got %r" % modname
modname = str(modname)
if self.get_plugin(modname) is not None:
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
@@ -442,16 +437,13 @@ class PytestPluginManager(PluginManager):
try:
__import__(importspec)
except ImportError as e:
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, safe_str(e.args[0])))
# copy over name and path attributes
for attr in ('name', 'path'):
if hasattr(e, attr):
setattr(new_exc, attr, getattr(e, attr))
raise new_exc
except Exception as e:
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
new_exc_type = ImportError
new_exc_message = 'Error importing plugin "%s": %s' % (modname, safe_str(e.args[0]))
new_exc = new_exc_type(new_exc_message)
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
except Skipped as e:
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
else:
mod = sys.modules[importspec]
@@ -476,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
@@ -611,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
@@ -643,7 +635,7 @@ class Argument:
pass
else:
# this might raise a keyerror as well, don't want to catch that
if isinstance(typ, py.builtin._basestring):
if isinstance(typ, six.string_types):
if typ == 'choice':
warnings.warn(
'type argument to addoption() is a string %r.'
@@ -741,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
@@ -859,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>"
@@ -899,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),
@@ -968,7 +947,7 @@ class Config(object):
)
res = self.hook.pytest_internalerror(excrepr=excrepr,
excinfo=excinfo)
if not py.builtin.any(res):
if not any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
@@ -1003,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
@@ -1014,10 +994,10 @@ class Config(object):
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args):
"""Install the PEP 302 import hook if using assertion re-writing.
"""Install the PEP 302 import hook if using assertion rewriting.
Needs to parse the --assert=<mode> option from the commandline
and find all the installed plugins to mark them for re-writing
and find all the installed plugins to mark them for rewriting
by the importhook.
"""
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
@@ -1029,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):
"""
@@ -1056,34 +1036,19 @@ 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):
self._initini(args)
if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
self._initini(args)
if addopts:
args[:] = self.getini("addopts") + args
self._checkversion()
self._consider_importhook(args)
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
@@ -1170,10 +1135,10 @@ class Config(object):
return []
if type == "pathlist":
dp = py.path.local(self.inicfg.config.path).dirpath()
l = []
values = []
for relpath in shlex.split(value):
l.append(dp.join(relpath, abs=True))
return l
values.append(dp.join(relpath, abs=True))
return values
elif type == "args":
return shlex.split(value)
elif type == "linelist":
@@ -1190,26 +1155,25 @@ class Config(object):
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
l = []
values = []
for relroot in relroots:
if not isinstance(relroot, py.path.local):
relroot = relroot.replace("/", py.path.local.sep)
relroot = modpath.join(relroot, abs=True)
l.append(relroot)
return l
values.append(relroot)
return values
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
@@ -1246,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()
@@ -1263,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:
@@ -1277,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']
@@ -1336,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)
@@ -1359,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
@@ -54,7 +55,7 @@ class pytestPDB:
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
capman.suspend_global_capture(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
@@ -62,11 +63,11 @@ 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:
out, err = capman.suspendcapture(in_=True)
out, err = capman.suspend_global_capture(in_=True)
sys.stdout.write(out)
sys.stdout.write(err)
_enter_pdb(node, call.excinfo, report)
@@ -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"
@@ -40,3 +40,25 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
" please use pytest.param(..., marks=...) instead.\n"
"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"
)
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,17 +50,28 @@ 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):
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules:
if config.option.doctestmodules and not _is_setup_py(config, path, parent):
return DoctestModule(path, parent)
elif _is_doctest(config, path, parent):
return DoctestTextfile(path, parent)
def _is_setup_py(config, path, parent):
if path.basename != "setup.py":
return False
contents = path.read()
return 'setuptools' in contents or 'distutils' in contents
def _is_doctest(config, path, parent):
if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
return True
@@ -68,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):
@@ -96,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[example.lineno - 9: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)
@@ -165,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
@@ -180,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()
@@ -216,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
@@ -357,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

@@ -1,13 +1,17 @@
from __future__ import absolute_import, division, print_function
import sys
import functools
import inspect
import sys
import warnings
from collections import OrderedDict, deque, defaultdict
import attr
import py
from py._code.code import FormattedExcinfo
import py
import warnings
import inspect
import _pytest
from _pytest import nodes
from _pytest._code.code import TerminalRepr
from _pytest.compat import (
NOTSET, exc_clear, _format_args,
@@ -15,22 +19,26 @@ from _pytest.compat import (
is_generator, isclass, getimfunc,
getlocation, getfuncargnames,
safe_getattr,
FuncargnamesCompatAttr,
)
from _pytest.outcomes import fail, TEST_OUTCOME
from _pytest.compat import FuncargnamesCompatAttr
if sys.version_info[:2] == (2, 6):
from ordereddict import OrderedDict
else:
from collections import OrderedDict
@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)
@@ -62,8 +70,6 @@ def scopeproperty(name=None, doc=None):
def get_scope_node(node, scope):
cls = scopename2class.get(scope)
if cls is None:
if scope == "session":
return node.session
raise ValueError("unknown scope")
return node.getparent(cls)
@@ -163,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):
@@ -247,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
@@ -268,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()
@@ -444,30 +446,35 @@ 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
def _get_fixturestack(self):
current = self
l = []
values = []
while 1:
fixturedef = getattr(current, "_fixturedef", None)
if fixturedef is None:
l.reverse()
return l
l.append(fixturedef)
values.reverse()
return values
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
@@ -516,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(fixturedef.finish,
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":
@@ -554,7 +560,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
if node is None and scope == "class":
# fallback to function item itself
node = self._pyfuncitem
assert node
assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(scope, self._pyfuncitem)
return node
def __repr__(self):
@@ -573,9 +579,7 @@ class SubRequest(FixtureRequest):
self.param_index = param_index
self.scope = scope
self._fixturedef = fixturedef
self.addfinalizer = fixturedef.addfinalizer
self._pyfuncitem = request._pyfuncitem
self._fixture_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index
@@ -584,6 +588,9 @@ class SubRequest(FixtureRequest):
def __repr__(self):
return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
def addfinalizer(self, finalizer):
self._fixturedef.addfinalizer(finalizer)
class ScopeMismatchError(Exception):
""" A fixture function tries to use a different fixture function which
@@ -714,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,
@@ -731,23 +738,22 @@ class FixtureDef:
where=baseid
)
self.params = params
startindex = unittest and 1 or None
self.argnames = getfuncargnames(func, startindex=startindex)
self.argnames = getfuncargnames(func, is_method=unittest)
self.unittest = unittest
self.ids = ids
self._finalizer = []
self._finalizers = []
def addfinalizer(self, finalizer):
self._finalizer.append(finalizer)
self._finalizers.append(finalizer)
def finish(self):
def finish(self, request):
exceptions = []
try:
while self._finalizer:
while self._finalizers:
try:
func = self._finalizer.pop()
func = self._finalizers.pop()
func()
except:
except: # noqa
exceptions.append(sys.exc_info())
if exceptions:
e = exceptions[0]
@@ -755,12 +761,15 @@ class FixtureDef:
py.builtin._reraise(*e)
finally:
ihook = self._fixturemanager.session.ihook
ihook.pytest_fixture_post_finalizer(fixturedef=self)
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# even if finalization fails, we invalidate
# the cached fixture value
# the cached fixture value and remove
# all finalizers because they may be bound methods which will
# keep instances alive
if hasattr(self, "cached_result"):
del self.cached_result
self._finalizers = []
def execute(self, request):
# get required arguments and register our own finish()
@@ -768,7 +777,7 @@ class FixtureDef:
for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname)
if argname != "request":
fixturedef.addfinalizer(self.finish)
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
my_cache_key = request.param_index
cached_result = getattr(self, "cached_result", None)
@@ -781,11 +790,11 @@ class FixtureDef:
return result
# we have a previous but differently parametrized fixture instance
# so we need to tear it down before creating a new one
self.finish()
self.finish(request)
assert not hasattr(self, "cached_result")
ihook = self._fixturemanager.session.ihook
return ihook.pytest_fixture_setup(fixturedef=self, request=request)
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)
def __repr__(self):
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
@@ -824,13 +833,21 @@ def pytest_fixture_setup(fixturedef, request):
return result
class FixtureFunctionMarker:
def __init__(self, scope, params, autouse=False, ids=None, name=None):
self.scope = scope
self.params = params
self.autouse = autouse
self.ids = ids
self.name = name
def _ensure_immutable_ids(ids):
if ids is None:
return
if callable(ids):
return ids
return tuple(ids)
@attr.s(frozen=True)
class FixtureFunctionMarker(object):
scope = attr.ib()
params = attr.ib(converter=attr.converters.optional(tuple))
autouse = attr.ib(default=False)
ids = attr.ib(default=None, converter=_ensure_immutable_ids)
name = attr.ib(default=None)
def __call__(self, function):
if isclass(function):
@@ -841,7 +858,7 @@ class FixtureFunctionMarker:
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
@@ -906,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.
@@ -981,8 +1006,8 @@ class FixtureManager:
# by their test id)
if p.basename.startswith("conftest.py"):
nodeid = p.dirpath().relto(self.config.rootdir)
if p.sep != "/":
nodeid = nodeid.replace(p.sep, "/")
if p.sep != nodes.SEP:
nodeid = nodeid.replace(p.sep, nodes.SEP)
self.parsefactories(plugin, nodeid)
def _getautousenames(self, nodeid):
@@ -996,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):
@@ -1029,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):
@@ -1037,9 +1069,14 @@ class FixtureManager:
if faclist:
fixturedef = faclist[-1]
if fixturedef.params is not None:
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
parametrize_func = getattr(metafunc.function, 'parametrize', None)
func_params = getattr(parametrize_func, 'args', [[None]])
func_kwargs = getattr(parametrize_func, 'kwargs', {})
# skip directly parametrized arguments
argnames = func_params[0]
if "argnames" in func_kwargs:
argnames = parametrize_func.kwargs["argnames"]
else:
argnames = func_params[0]
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if argname not in func_params and argname not in argnames:
@@ -1127,5 +1164,5 @@ class FixtureManager:
def _matchfactories(self, fixturedefs, nodeid):
for fixturedef in fixturedefs:
if nodeid.startswith(fixturedef.baseid):
if nodes.ischildnode(fixturedef.baseid, nodeid):
yield fixturedef

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

@@ -1,6 +1,6 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from _pytest._pluggy import HookspecMarker
from pluggy import HookspecMarker
hookspec = HookspecMarker("pytest")
@@ -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,10 +391,18 @@ 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):
def pytest_fixture_post_finalizer(fixturedef, request):
""" called after fixture teardown, but before the cache is cleared so
the fixture result cache ``fixturedef.cached_result`` can
still be accessed."""
@@ -307,15 +413,25 @@ def pytest_fixture_post_finalizer(fixturedef):
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

@@ -17,6 +17,7 @@ import re
import sys
import time
import pytest
from _pytest import nodes
from _pytest.config import filename_arg
# Python 2.X and 3.X compatibility
@@ -84,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.
"""
@@ -97,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)
@@ -110,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)
@@ -124,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):
@@ -190,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):
@@ -227,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)
@@ -252,7 +337,7 @@ def mangle_test_address(address):
except ValueError:
pass
# convert file path to dotted path
names[0] = names[0].replace("/", '.')
names[0] = names[0].replace(nodes.SEP, '.')
names[0] = _py_ext_re.sub("", names[0])
# put any params back
names[-1] += possible_open_bracket + params
@@ -260,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',
@@ -372,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)

522
_pytest/logging.py Normal file
View File

@@ -0,0 +1,522 @@
""" Access and control log capturing. """
from __future__ import absolute_import, division, print_function
import logging
from contextlib import closing, contextmanager
import re
import six
from _pytest.config import create_terminal_writer
import pytest
import py
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
if ret is None:
ret = config.getini(name)
if ret:
return ret
def pytest_addoption(parser):
"""Add options to control log capturing."""
group = parser.getgroup('logging')
def add_option_ini(option, dest, default=None, type=None, **kwargs):
parser.addini(dest, default=default, type=type,
help='default value for ' + option)
group.addoption(option, dest=dest, **kwargs)
add_option_ini(
'--no-print-logs',
dest='log_print', action='store_const', const=False, default=True,
type='bool',
help='disable printing caught logs on failed tests.')
add_option_ini(
'--log-level',
dest='log_level', default=None,
help='logging level used by the logging module')
add_option_ini(
'--log-format',
dest='log_format', default=DEFAULT_LOG_FORMAT,
help='log format as used by the logging module.')
add_option_ini(
'--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,
help='cli logging level.')
add_option_ini(
'--log-cli-format',
dest='log_cli_format', default=None,
help='log format as used by the logging module.')
add_option_ini(
'--log-cli-date-format',
dest='log_cli_date_format', default=None,
help='log date format as used by the logging module.')
add_option_ini(
'--log-file',
dest='log_file', default=None,
help='path to a file when logging will be written to.')
add_option_ini(
'--log-file-level',
dest='log_file_level', default=None,
help='log file logging level.')
add_option_ini(
'--log-file-format',
dest='log_file_format', default=DEFAULT_LOG_FORMAT,
help='log format as used by the logging module.')
add_option_ini(
'--log-file-date-format',
dest='log_file_date_format', default=DEFAULT_LOG_DATE_FORMAT,
help='log date format as used by the logging module.')
@contextmanager
def catching_logs(handler, formatter=None, level=None):
"""Context manager that prepares the whole logging machinery properly."""
root_logger = logging.getLogger()
if formatter is not None:
handler.setFormatter(formatter)
if level is not None:
handler.setLevel(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):
"""A logging handler that stores log records and the log text."""
def __init__(self):
"""Creates a new log handler."""
logging.StreamHandler.__init__(self, py.io.TextIO())
self.records = []
def emit(self, record):
"""Keep the log records in a list in addition to the log text."""
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."""
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."""
return self.handler.stream.getvalue()
@property
def records(self):
"""Returns the list of log records."""
return self.handler.records
@property
def record_tuples(self):
"""Returns a list of a striped down version of log records intended
for use in assertion comparison.
The format of the tuple is:
(logger_name, log_level, message)
"""
return [(r.name, r.levelno, r.getMessage()) for r in self.records]
def clear(self):
"""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. The level will be restored to its previous value at the end of
the test.
: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.
"""
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. After the end of the 'with' statement the
level is restored to its original value.
: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.
"""
logger = logging.getLogger(logger)
orig_level = logger.level
logger.setLevel(level)
try:
yield
finally:
logger.setLevel(orig_level)
@pytest.fixture
def caplog(request):
"""Access and control log capturing.
Captured logs are available through the following methods::
* 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
"""
result = LogCaptureFixture(request.node)
yield result
result._finalize()
def get_actual_log_level(config, *setting_names):
"""Return the actual logging level."""
for setting_name in setting_names:
log_level = config.getoption(setting_name)
if log_level is None:
log_level = config.getini(setting_name)
if log_level:
break
else:
return
if isinstance(log_level, six.string_types):
log_level = log_level.upper()
try:
return int(getattr(logging, log_level, log_level))
except ValueError:
# Python logging does not recognise this as a logging level
raise pytest.UsageError(
"'{0}' is not recognized as a logging level name for "
"'{1}'. Please consider passing the "
"logging level num instead.".format(
log_level,
setting_name))
def pytest_configure(config):
config.pluginmanager.register(LoggingPlugin(config), 'logging-plugin')
@contextmanager
def _dummy_context_manager():
yield
class LoggingPlugin(object):
"""Attaches to the logging module and captures log messages for each test.
"""
def __init__(self, config):
"""Creates a new plugin to capture log messages.
The formatter can be safely shared across all handlers so
create a single one for the entire test session here.
"""
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'))
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')
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, 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.
log = log_handler.stream.getvalue().strip()
item.add_report_section(when, 'log', log)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item):
with self._runtest_for(item, 'setup'):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item):
with self._runtest_for(item, 'call'):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item):
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."""
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,
level=self.log_file_level):
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,23 +1,22 @@
""" 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
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.runner import collect_one_node
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
@@ -54,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",
@@ -62,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,
@@ -83,15 +89,6 @@ def pytest_addoption(parser):
help="base temporary directory for this test run.")
def pytest_namespace():
"""keeping this one works around a deeper startup issue in pytest
i tried to find it for a while but the amount of time turned unsustainable,
so i put a hack in to revisit later
"""
return {}
def pytest_configure(config):
__import__('pytest').config = config # compatibiltiy
@@ -110,6 +107,8 @@ def wrap_session(config, doit):
session.exitstatus = doit(config, session) or 0
except UsageError:
raise
except Failed:
session.exitstatus = EXIT_TESTSFAILED
except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo()
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
@@ -117,7 +116,7 @@ def wrap_session(config, doit):
excinfo.typename, excinfo.value.msg))
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = EXIT_INTERRUPTED
except:
except: # noqa
excinfo = _pytest._code.ExceptionInfo()
config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
@@ -167,6 +166,8 @@ def pytest_runtestloop(session):
for i, item in enumerate(session.items):
nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldfail:
raise session.Failed(session.shouldfail)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
@@ -176,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')
@@ -209,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
@@ -221,366 +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 _memoizedcall(self, attrname, function):
exattrname = "_ex_" + attrname
failure = getattr(self, exattrname, None)
if failure is not None:
py.builtin._reraise(failure[0], failure[1], failure[2])
if hasattr(self, attrname):
return getattr(self, attrname)
try:
res = function()
except py.builtin._sysex:
raise
except:
failure = sys.exc_info()
setattr(self, exattrname, failure)
raise
setattr(self, attrname, res)
return res
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, py.builtin._basestring):
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, "/")
super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath
def _makeid(self):
relpath = self.fspath.relto(self.config.rootdir)
if os.sep != "/":
relpath = relpath.replace(os.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. """
@@ -590,25 +289,32 @@ class Interrupted(KeyboardInterrupt):
__module__ = 'builtins' # for py3
class Session(FSCollector):
class Failed(Exception):
""" signals an stop as failed test run. """
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
self.shouldfail = False
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):
if self.shouldfail:
raise self.Failed(self.shouldfail)
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
@@ -618,7 +324,7 @@ class Session(FSCollector):
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self.testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self.shouldfail = "stopping after %d failures" % (
self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
@@ -733,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:
@@ -743,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
@@ -787,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
@@ -813,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,12 +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
from collections import namedtuple
from operator import attrgetter
from .compat import imap
from .deprecated import MARK_PARAMETERSET_UNPACKING
import inspect
import attr
from ..deprecated import MARK_PARAMETERSET_UNPACKING
from ..compat import NOTSET, getfslineno
from six.moves import map
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
def alias(name, warning=None):
@@ -19,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):
@@ -31,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):
@@ -67,224 +89,43 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
return cls(argval, marks=newmarks, id=None)
@property
def deprecated_arg_dict(self):
return dict((mark.name, mark) for mark in self.marks)
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"):
name, rest = line.split(":", 1)
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)
@classmethod
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
else:
if selectuntil:
keywordexpr = None
if matchexpr:
if not matchmark(colitem, matchexpr):
deselected.append(colitem)
continue
remaining.append(colitem)
force_tuple = False
parameters = [
ParameterSet.extract_from(x, legacy_force_tuple=force_tuple)
for x in argvalues]
del argvalues
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
if not parameters:
mark = get_empty_parameterset_mark(config, argnames, func)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
id=None,
))
return argnames, parameters
class MarkMapping:
"""Provides a local mapping for markers where item access
resolves to True if the marker is present. """
@attr.s(frozen=True)
class Mark(object):
name = attr.ib()
args = attr.ib()
kwargs = attr.ib()
def __init__(self, keywords):
mymarks = set()
for key, value in keywords.items():
if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
mymarks.add(key)
self._mymarks = mymarks
def __getitem__(self, name):
return name in self._mymarks
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
class KeywordMapping:
"""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(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 = l = set()
for line in self._config.getini("markers"):
beginning = line.split(":", 1)
x = beginning[0].split("(", 1)[0]
l.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>"
class MarkDecorator:
@attr.s
class MarkDecorator(object):
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
@@ -318,9 +159,7 @@ class MarkDecorator:
"""
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.mark = mark
mark = attr.ib(validator=attr.validators.instance_of(Mark))
name = alias('mark.name')
args = alias('mark.args')
@@ -383,7 +222,7 @@ def store_mark(obj, mark):
"""
assert isinstance(mark, Mark), mark
# always reassign name to avoid updating pytestmark
# in a referene that was only borrowed
# in a reference that was only borrowed
obj.pytestmark = get_unpacked_marks(obj) + [mark]
@@ -400,13 +239,31 @@ def store_legacy_markinfo(func, mark):
holder.add_mark(mark)
class Mark(namedtuple('Mark', 'name, args, kwargs')):
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
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):
@@ -431,34 +288,80 @@ class MarkInfo(object):
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
return imap(MarkInfo, self._marks)
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

@@ -4,8 +4,7 @@ from __future__ import absolute_import, division, print_function
import os
import sys
import re
from py.builtin import _basestring
import six
from _pytest.fixtures import fixture
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@@ -79,7 +78,7 @@ def annotated_getattr(obj, name, ann):
def derive_importpath(import_path, raising):
if not isinstance(import_path, _basestring) or "." not in import_path:
if not isinstance(import_path, six.string_types) or "." not in import_path:
raise TypeError("must be absolute import path string, not %r" %
(import_path,))
module, attr = import_path.rsplit('.', 1)
@@ -89,7 +88,7 @@ def derive_importpath(import_path, raising):
return attr, target
class Notset:
class Notset(object):
def __repr__(self):
return "<notset>"
@@ -97,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.
"""
@@ -114,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
@@ -125,7 +124,7 @@ class MonkeyPatch:
import inspect
if value is notset:
if not isinstance(target, _basestring):
if not isinstance(target, six.string_types):
raise TypeError("use setattr(target, name, value) or "
"setattr(target, value) with target being a dotted "
"import string")
@@ -155,7 +154,7 @@ class MonkeyPatch:
"""
__tracebackhide__ = True
if name is notset:
if not isinstance(target, _basestring):
if not isinstance(target, six.string_types):
raise TypeError("use delattr(target, name) or "
"delattr(target) with target being a dotted "
"import string")

370
_pytest/nodes.py Normal file
View File

@@ -0,0 +1,370 @@
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'.
Node IDs are strings, and can be things like:
''
'testing/code'
'testing/code/test_excinfo.py'
'testing/code/test_excinfo.py::TestFormattedExcinfo::()'
Return values are lists e.g.
[]
['testing', 'code']
['testing', 'code', 'test_excinfo.py']
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
"""
if nodeid == '':
# If there is no root node at all, return an empty list so the caller's logic can remain sane
return []
parts = nodeid.split(SEP)
# Replace single last element 'test_foo.py::Bar::()' with multiple elements 'test_foo.py', 'Bar', '()'
parts[-1:] = parts[-1].split("::")
return parts
def ischildnode(baseid, nodeid):
"""Return True if the nodeid is a child node of the baseid.
E.g. 'foo/bar::Baz::()' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
"""
base_parts = _splitnode(baseid)
node_parts = _splitnode(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

@@ -3,7 +3,6 @@ from __future__ import absolute_import, division, print_function
import sys
import py
from _pytest import unittest, runner, python
from _pytest.config import hookimpl
@@ -66,7 +65,7 @@ def is_potential_nosetest(item):
def call_optional(obj, name):
method = getattr(obj, name, None)
isfixture = hasattr(method, "_pytestfixturefunction")
if method is not None and not isfixture and py.builtin.callable(method):
if method is not None and not isfixture and callable(method):
# If there's any problems allow the exception to raise rather than
# silently ignoring them
method()

View File

@@ -62,14 +62,21 @@ def exit(msg):
exit.Exception = Exit
def skip(msg=""):
def skip(msg="", **kwargs):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
:kwarg bool allow_module_level: allows this function to be called at
module level, skipping the rest of the module. Default to False.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)
allow_module_level = kwargs.pop('allow_module_level', False)
if kwargs:
keys = [k for k in kwargs.keys()]
raise TypeError('unexpected keyword arguments: {0}'.format(keys))
raise Skipped(msg=msg, allow_module_level=allow_module_level)
skip.Exception = Skipped

View File

@@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function
import pytest
import six
import sys
import tempfile
@@ -16,7 +17,6 @@ def pytest_addoption(parser):
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
import py
if config.option.pastebin == "all":
tr = config.pluginmanager.getplugin('terminalreporter')
# if no terminal reporter plugin is present, nothing we can do here;
@@ -29,7 +29,7 @@ def pytest_configure(config):
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
if py.builtin._istext(s):
if isinstance(s, six.text_type):
s = s.encode('utf-8')
config._pastebinfile.write(s)

File diff suppressed because it is too large Load Diff

View File

@@ -6,29 +6,41 @@ import inspect
import sys
import os
import collections
import warnings
from textwrap import dedent
from itertools import count
import py
import six
from _pytest.mark import MarkerError
from _pytest.config import hookimpl
import _pytest
import _pytest._pluggy as pluggy
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,
isclass, isfunction, is_generator, ascii_escaped,
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
get_real_func, getfslineno, safe_getattr,
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):
@@ -43,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):
@@ -257,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)
@@ -321,22 +333,26 @@ class PyCollector(PyobjMixin, main.Collector):
for basecls in inspect.getmro(self.obj.__class__):
dicts.append(basecls.__dict__)
seen = {}
l = []
values = []
for dic in dicts:
for name, obj in list(dic.items()):
if name in seen:
continue
seen[name] = True
res = self.makeitem(name, obj)
res = self._makeitem(name, obj)
if res is None:
continue
if not isinstance(res, list):
res = [res]
l.extend(res)
l.sort(key=lambda item: item.reportinfo()[:2])
return l
values.extend(res)
values.sort(key=lambda item: item.reportinfo()[:2])
return values
def makeitem(self, name, obj):
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
self._makeitem(name, obj)
def _makeitem(self, name, obj):
# assert self.ihook.fspath == self.fspath, self
return self.ihook.pytest_pycollect_makeitem(
collector=self, name=name, obj=obj)
@@ -378,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):
@@ -555,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
@@ -592,7 +607,7 @@ class Generator(FunctionMixin, PyCollector):
self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
l = []
values = []
seen = {}
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
@@ -605,15 +620,15 @@ class Generator(FunctionMixin, PyCollector):
if name in seen:
raise ValueError("%r generated tests with non-unique name %r" % (self, name))
seen[name] = True
l.append(self.Function(name, self, args=args, callobj=call))
values.append(self.Function(name, self, args=args, callobj=call))
self.warn('C1', deprecated.YIELD_TESTS)
return l
return values
def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
# explicit naming
if isinstance(obj[0], py.builtin._basestring):
if isinstance(obj[0], six.string_types):
name = obj[0]
obj = obj[1:]
else:
@@ -644,14 +659,14 @@ class CallSpec2(object):
self._globalid_args = set()
self._globalparam = NOTSET
self._arg2scopenum = {} # used for sorting parametrized resources
self.keywords = {}
self.marks = []
self.indices = {}
def copy(self, metafunc):
cs = CallSpec2(self.metafunc)
cs.funcargs.update(self.funcargs)
cs.params.update(self.params)
cs.keywords.update(self.keywords)
cs.marks.extend(self.marks)
cs.indices.update(self.indices)
cs._arg2scopenum.update(self._arg2scopenum)
cs._idlist = list(self._idlist)
@@ -676,8 +691,8 @@ class CallSpec2(object):
def id(self):
return "-".join(map(str, filter(None, self._idlist)))
def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
param_index):
def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum,
param_index):
for arg, val in zip(argnames, valset):
self._checkargnotcontained(arg)
valtype_for_arg = valtypes[arg]
@@ -685,7 +700,7 @@ class CallSpec2(object):
self.indices[arg] = param_index
self._arg2scopenum[arg] = scopenum
self._idlist.append(id)
self.keywords.update(keywords)
self.marks.extend(marks)
def setall(self, funcargs, id, param):
for x in funcargs:
@@ -702,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.
@@ -725,7 +740,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
self.cls = cls
self._calls = []
self._ids = py.builtin.set()
self._ids = set()
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
@@ -768,30 +783,13 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
to set a dynamic scope using test context or configuration.
"""
from _pytest.fixtures import scope2index
from _pytest.mark import MARK_GEN, ParameterSet
from _pytest.mark import ParameterSet
from py.io import saferepr
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
force_tuple = len(argnames) == 1
else:
force_tuple = False
parameters = [
ParameterSet.extract_from(x, legacy_force_tuple=force_tuple)
for x in argvalues]
argnames, parameters = ParameterSet._for_parametrize(
argnames, argvalues, self.function, self.config)
del argvalues
if not parameters:
fs, lineno = getfslineno(self.function)
reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, self.function.__name__, fs, lineno)
mark = MARK_GEN.skip(reason=reason)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
id=None,
))
if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
@@ -827,7 +825,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
raise ValueError('%d tests specified with %d ids' % (
len(parameters), len(ids)))
for id_value in ids:
if id_value is not None and not isinstance(id_value, py.builtin._basestring):
if id_value is not None and not isinstance(id_value, six.string_types):
msg = 'ids must be list of strings, found: %s (type: %s)'
raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
ids = idmaker(argnames, parameters, idfn, ids, self.config)
@@ -841,15 +839,19 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
'equal to the number of names ({1})'.format(
param.values, argnames))
newcallspec = callspec.copy(self)
newcallspec.setmulti(valtypes, argnames, param.values, a_id,
param.deprecated_arg_dict, scopenum, param_index)
newcallspec.setmulti2(valtypes, argnames, param.values, a_id,
param.marks, scopenum, param_index)
newcalls.append(newcallspec)
self._calls = newcalls
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
""" (deprecated, use parametrize) Add a new call to the underlying
test function during the collection phase of a test run. Note that
request.addcall() is called during the test collection phase prior and
""" Add a new call to the underlying test function during the collection phase of a test run.
.. deprecated:: 3.3
Use :meth:`parametrize` instead.
Note that request.addcall() is called during the test collection phase prior and
independently to actual test execution. You should only use addcall()
if you need to specify multiple arguments of a test function.
@@ -862,6 +864,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
:arg param: a parameter which will be exposed to a later fixture function
invocation through the ``request.param`` attribute.
"""
if self.config:
self.config.warn('C1', message=deprecated.METAFUNC_ADD_CALL, fslocation=None)
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
@@ -921,7 +925,7 @@ def _idval(val, argname, idx, idfn, config=None):
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
warnings.warn(msg, DeprecationWarning)
if s:
return _ascii_escaped(s)
return ascii_escaped(s)
if config:
hook_id = config.hook.pytest_make_parametrize_id(
@@ -930,14 +934,14 @@ def _idval(val, argname, idx, idfn, config=None):
return hook_id
if isinstance(val, STRING_TYPES):
return _ascii_escaped(val)
return ascii_escaped(val)
elif isinstance(val, (float, int, bool, NoneType)):
return str(val)
elif isinstance(val, REGEX_TYPE):
return _ascii_escaped(val.pattern)
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)
@@ -950,7 +954,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
for val, argname in zip(parameterset.values, argnames)]
return "-".join(this_id)
else:
return _ascii_escaped(ids[idx])
return ascii_escaped(ids[idx])
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
@@ -1094,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.
"""
@@ -1112,7 +1116,13 @@ class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
self.keywords.update(self.obj.__dict__)
if callspec:
self.callspec = callspec
self.keywords.update(callspec.keywords)
# this is total hostile and a mess
# keywords are broken by design by now
# this will be redeemed later
for mark in callspec.marks:
# feel free to cry, this was broken for years before
# and keywords cant fix it per design
self.keywords[mark.name] = mark
if keywords:
self.keywords.update(keywords)

View File

@@ -2,8 +2,10 @@ import math
import sys
import py
from six.moves import zip, filterfalse
from more_itertools.more import always_iterable
from _pytest.compat import isclass, izip
from _pytest.compat import isclass
from _pytest.outcomes import fail
import _pytest._code
@@ -29,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
@@ -67,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
@@ -82,12 +87,15 @@ class ApproxNumpy(ApproxBase):
def __eq__(self, actual):
import numpy as np
try:
actual = np.asarray(actual)
except:
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)
@@ -95,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):
@@ -129,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):
@@ -145,13 +155,15 @@ class ApproxSequence(ApproxBase):
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
return izip(actual, self.expected)
return zip(actual, self.expected)
class ApproxScalar(ApproxBase):
"""
Perform approximate comparisons for single numbers only.
"""
DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
DEFAULT_RELATIVE_TOLERANCE = 1e-6
def __repr__(self):
"""
@@ -185,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:
@@ -217,11 +231,12 @@ class ApproxScalar(ApproxBase):
absolute tolerance or a relative tolerance, depending on what the user
specified or which would be larger.
"""
def set_default(x, default): return x if x is not None else default
def set_default(x, default):
return x if x is not None else default
# 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))
@@ -239,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))
@@ -250,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
@@ -296,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
@@ -399,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).
@@ -420,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
@@ -451,10 +482,13 @@ 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).
If using Python 2.5 or above, you may use this function as a
context manager::
You may use this function as a context manager::
>>> with raises(ZeroDivisionError):
... 1/0
@@ -530,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
@@ -549,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
@@ -566,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
@@ -609,17 +639,10 @@ class RaisesContext(object):
__tracebackhide__ = True
if tp[0] is None:
fail(self.message)
if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the
# exception value.
# http://bugs.python.org/issue7853
if not isinstance(tp[1], BaseException):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
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

@@ -8,16 +8,15 @@ import py
import sys
import warnings
import re
from _pytest.fixtures import yield_fixture
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.
@@ -86,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
@@ -98,10 +97,28 @@ def warns(expected_warning, *args, **kwargs):
>>> with warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
In the context manager form you may use the keyword argument ``match`` to assert
that the exception matches a text or regex::
>>> with warns(UserWarning, match='must be 0 or None'):
... warnings.warn("value must be 0 or None", UserWarning)
>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("value must be 42", UserWarning)
>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("this is not here", UserWarning)
Traceback (most recent call last):
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
"""
wcheck = WarningsChecker(expected_warning)
match_expr = None
if not args:
return wcheck
if "match" in kwargs:
match_expr = kwargs.pop("match")
return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
@@ -109,12 +126,12 @@ def warns(expected_warning, *args, **kwargs):
loc = frame.f_locals.copy()
loc.update(kwargs)
with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
return func(*args[1:], **kwargs)
@@ -174,7 +191,7 @@ class WarningsRecorder(warnings.catch_warnings):
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None):
def __init__(self, expected_warning=None, match_expr=None):
super(WarningsChecker, self).__init__()
msg = ("exceptions must be old-style classes or "
@@ -189,6 +206,7 @@ class WarningsChecker(WarningsRecorder):
raise TypeError(msg % type(expected_warning))
self.expected_warning = expected_warning
self.match_expr = match_expr
def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
@@ -203,3 +221,13 @@ class WarningsChecker(WarningsRecorder):
"The list of emitted warnings is: {1}.".format(
self.expected_warning,
[each.message for each in self]))
elif self.match_expr is not None:
for r in self:
if issubclass(r.category, self.expected_warning):
if re.compile(self.match_expr).search(str(r.message)):
break
else:
fail("DID NOT WARN. No warnings of type {0} matching"
" ('{1}') was emitted. The list of emitted warnings"
" is: {2}.".format(self.expected_warning, self.match_expr,
[each.message for each in self]))

View File

@@ -7,7 +7,6 @@ import sys
from time import time
import py
from _pytest.compat import _PY2
from _pytest._code.code import TerminalRepr, ExceptionInfo
from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
@@ -56,16 +55,14 @@ def pytest_sessionfinish(session):
session._setupstate.teardown_all()
class NodeInfo:
def __init__(self, location):
self.location = location
def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(
nodeid=item.nodeid, location=item.location,
)
runtestprotocol(item, nextitem=nextitem)
item.ihook.pytest_runtest_logfinish(
nodeid=item.nodeid, location=item.location,
)
return True
@@ -136,9 +133,8 @@ def _update_current_test_var(item, when):
var_name = 'PYTEST_CURRENT_TEST'
if when:
value = '{0} ({1})'.format(item.nodeid, when)
if _PY2:
# python 2 doesn't like null bytes on environment variables (see #2644)
value = value.replace('\x00', '(null)')
# don't allow null bytes on environment variables (see #2644, #2957)
value = value.replace('\x00', '(null)')
os.environ[var_name] = value
else:
os.environ.pop(var_name)
@@ -182,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
@@ -197,7 +193,7 @@ class CallInfo:
except KeyboardInterrupt:
self.stop = time()
raise
except:
except: # noqa
self.excinfo = ExceptionInfo()
self.stop = time()
@@ -260,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
@@ -313,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):
@@ -322,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
@@ -344,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
@@ -436,7 +444,7 @@ class SetupState(object):
is called at the end of teardown_all().
"""
assert colitem and not isinstance(colitem, tuple)
assert py.builtin.callable(finalizer)
assert callable(finalizer)
# assert colitem in self.stack # some unit tests don't setup stack :/
self._finalizers.setdefault(colitem, []).append(finalizer)

View File

@@ -44,7 +44,7 @@ def _show_fixture_action(fixturedef, msg):
config = fixturedef._fixturemanager.config
capman = config.pluginmanager.getplugin('capturemanager')
if capman:
out, err = capman.suspendcapture()
out, err = capman.suspend_global_capture()
tw = config.get_terminal_writer()
tw.line()
@@ -63,7 +63,7 @@ def _show_fixture_action(fixturedef, msg):
tw.write('[{0}]'.format(fixturedef.cached_param))
if capman:
capman.resumecapture()
capman.resume_global_capture()
sys.stdout.write(out)
sys.stderr.write(err)

View File

@@ -1,14 +1,10 @@
""" support for skip/xfail functions and markers. """
from __future__ import absolute_import, division, print_function
import os
import sys
import traceback
import py
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,20 @@ def pytest_configure(config):
)
class MarkEvaluator:
def __init__(self, item, name):
self.item = item
self.name = name
@property
def holder(self):
return self.item.keywords.get(self.name)
def __bool__(self):
return bool(self.holder)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
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.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
if self.holder:
if self.holder.args or 'condition' in self.holder.kwargs:
self.result = False
# "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an
# _arglist attribute
marks = getattr(self.holder, '_marks', None) \
or [self.holder.mark]
for _, args, kwargs in marks:
if 'condition' in kwargs:
args = (kwargs['condition'],)
for expr in args:
self.expr = expr
if isinstance(expr, py.builtin._basestring):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in 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 = kwargs.get('reason', None)
self.expr = expr
return self.result
else:
self.result = True
return getattr(self, 'result', False)
def get(self, attr, default=None):
return self.holder.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
item._skipped_by_mark = False
skipif_info = item.keywords.get('skipif')
if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
eval_skipif = MarkEvaluator(item, 'skipif')
if eval_skipif.istrue():
item._evalskip = eval_skipif
item._skipped_by_mark = True
skip(eval_skipif.getexplanation())
skip_info = item.keywords.get('skip')
if isinstance(skip_info, (MarkInfo, MarkDecorator)):
item._evalskip = True
item._skipped_by_mark = True
if 'reason' in skip_info.kwargs:
skip(skip_info.kwargs['reason'])
elif skip_info.args:
@@ -212,7 +116,6 @@ def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
evalxfail = getattr(item, '_evalxfail', None)
evalskip = getattr(item, '_evalskip', None)
# unitttest special case, see setting of _unexpectedsuccess
if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
from _pytest.compat import _is_unittest_unexpected_success_a_failure
@@ -226,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"
@@ -248,7 +151,7 @@ def pytest_runtest_makereport(item, call):
else:
rep.outcome = "passed"
rep.wasxfail = explanation
elif evalskip is not None 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
@@ -256,6 +159,7 @@ def pytest_runtest_makereport(item, call):
filename, line = item.location[:2]
rep.longrepr = filename, line, reason
# called by terminalreporter progress reporting
@@ -266,6 +170,7 @@ def pytest_report_teststatus(report):
elif report.passed:
return "xpassed", "X", ("XPASS", {'yellow': True})
# called by the terminalreporter instance/plugin
@@ -281,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")
@@ -328,28 +223,23 @@ 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:
key = event.longrepr
assert len(key) == 3, (event, key)
keywords = getattr(event, 'keywords', {})
# folding reports with global pytestmark variable
# this is workaround, because for now we cannot identify the scope of a skip marker
# TODO: revisit after marks scope would be fixed
when = getattr(event, 'when', None)
if when == 'setup' and 'skip' in keywords and 'pytestmark' not in keywords:
key = (key[0], None, key[2])
d.setdefault(key, []).append(event)
l = []
values = []
for key, events in d.items():
l.append((len(events),) + key)
return l
values.append((len(events),) + key)
return values
def show_skipped(terminalreporter, lines):
@@ -367,6 +257,31 @@ def show_skipped(terminalreporter, lines):
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno + 1, reason))
if lineno is not None:
lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno + 1, reason))
else:
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

@@ -5,23 +5,59 @@ This is a good source for looking at the various reporting hooks.
from __future__ import absolute_import, division, print_function
import itertools
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import py
import platform
import sys
import time
import platform
import _pytest._pluggy as pluggy
import pluggy
import py
import six
import pytest
from _pytest import nodes
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, "
@@ -39,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).")
@@ -47,9 +88,12 @@ def pytest_addoption(parser):
choices=['yes', 'no', 'auto'],
help="color terminal output (yes/no/auto).")
parser.addini("console_output_style",
help="console output: classic or with additional progress information (classic|progress).",
default='progress')
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:
@@ -87,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``.
"""
@@ -122,7 +166,7 @@ class WarningReport:
return None
class TerminalReporter:
class TerminalReporter(object):
def __init__(self, config, file=None):
import _pytest.config
self.config = config
@@ -131,17 +175,32 @@ class TerminalReporter:
self.showfspath = self.verbosity >= 0
self.showlongtestinfo = self.verbosity > 0
self._numcollected = 0
self._session = None
self.stats = {}
self.startdir = py.path.local()
if file is None:
file = sys.stdout
self._tw = self.writer = _pytest.config.create_terminal_writer(config,
file)
self._tw = _pytest.config.create_terminal_writer(config, file)
# self.writer will be deprecated in pytest-3.4
self.writer = self._tw
self._screen_width = self._tw.fullwidth
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
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)
@@ -150,6 +209,8 @@ class TerminalReporter:
def write_fspath_result(self, nodeid, res):
fspath = self.config.rootdir.join(nodeid.split("::")[0])
if fspath != self.currentfspath:
if self.currentfspath is not None:
self._write_progress_information_filling_space()
self.currentfspath = fspath
fspath = self.startdir.bestrelpath(fspath)
self._tw.line()
@@ -174,8 +235,8 @@ class TerminalReporter:
self._tw.write(content, **markup)
def write_line(self, line, **markup):
if not py.builtin._istext(line):
line = py.builtin.text(line, errors="replace")
if not isinstance(line, six.text_type):
line = six.text_type(line, errors="replace")
self.ensure_newline()
self._tw.line(line, **markup)
@@ -190,7 +251,7 @@ class TerminalReporter:
"""
erase = markup.pop('erase', False)
if erase:
fill_count = self._tw.fullwidth - len(line)
fill_count = self._tw.fullwidth - len(line) - 1
fill = ' ' * fill_count
else:
fill = ''
@@ -208,7 +269,7 @@ class TerminalReporter:
self._tw.line(msg, **kw)
def pytest_internalerror(self, excrepr):
for line in py.builtin.text(excrepr).split("\n"):
for line in six.text_type(excrepr).split("\n"):
self.write_line("INTERNALERROR> " + line)
return 1
@@ -243,38 +304,76 @@ class TerminalReporter:
rep = report
res = self.config.hook.pytest_report_teststatus(report=rep)
cat, letter, word = res
if isinstance(word, tuple):
word, markup = word
else:
markup = None
self.stats.setdefault(cat, []).append(rep)
self._tests_ran = True
if not letter and not word:
# probably passed setup/teardown
return
running_xdist = hasattr(rep, 'node')
if self.verbosity <= 0:
if not hasattr(rep, 'node') and self.showfspath:
if not running_xdist and self.showfspath:
self.write_fspath_result(rep.nodeid, letter)
else:
self._tw.write(letter)
else:
if isinstance(word, tuple):
word, markup = word
else:
self._progress_nodeids_reported.add(rep.nodeid)
if markup is None:
if rep.passed:
markup = {'green': True}
elif rep.failed:
markup = {'red': True}
elif rep.skipped:
markup = {'yellow': True}
else:
markup = {}
line = self._locationline(rep.nodeid, *rep.location)
if not hasattr(rep, 'node'):
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
# self._tw.write(word, **markup)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
self.ensure_newline()
if hasattr(rep, 'node'):
self._tw.write("[%s] " % rep.node.gateway.id)
self._tw.write("[%s]" % rep.node.gateway.id)
if self._show_progress_info:
self._tw.write(self._get_progress_information_message() + " ", cyan=True)
else:
self._tw.write(' ')
self._tw.write(word, **markup)
self._tw.write(" " + line)
self.currentfspath = -2
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 = len(self._progress_nodeids_reported) * 100 // collected
return ' [{:3d}%]'.format(progress)
return ' [100%]'
def _write_progress_information_filling_space(self):
msg = self._get_progress_information_message()
fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
self.write(fill + msg, cyan=True)
def pytest_collection(self):
if not self.isatty and self.config.option.verbose >= 1:
self.write("collecting ... ", bold=True)
@@ -296,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:
@@ -303,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:
@@ -312,11 +414,13 @@ class TerminalReporter:
else:
self.write_line(line)
@pytest.hookimpl(trylast=True)
def pytest_collection_modifyitems(self):
self.report_collect(True)
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session):
self._session = session
self._sessionstarttime = time.time()
if not self.showheader:
return
@@ -411,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)
@@ -444,15 +551,15 @@ class TerminalReporter:
line = self.config.cwd_relative_nodeid(nodeid)
if domain and line.endswith(domain):
line = line[:-len(domain)]
l = domain.split("[")
l[0] = l[0].replace('.', '::') # don't replace '.' in params
line += "[".join(l)
values = domain.split("[")
values[0] = values[0].replace('.', '::') # don't replace '.' in params
line += "[".join(values)
return line
# collect_fspath comes from testid which has a "/"-normalized path
if fspath:
res = mkrel(nodeid).replace("::()", "") # parens-normalization
if nodeid.split("::")[0] != fspath.replace("\\", "/"):
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
res += " <- " + self.startdir.bestrelpath(fspath)
else:
res = "[location]"
@@ -478,11 +585,11 @@ class TerminalReporter:
# summaries for sessionfinish
#
def getreports(self, name):
l = []
values = []
for x in self.stats.get(name, []):
if not hasattr(x, '_pdbshown'):
l.append(x)
return l
values.append(x)
return values
def summary_warnings(self):
if self.hasopt("w"):
@@ -493,9 +600,9 @@ class TerminalReporter:
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
self.write_sep("=", "warnings summary", yellow=True, bold=False)
for location, warnings in grouped:
for location, warning_records in grouped:
self._tw.line(str(location) or '<undetermined location>')
for w in warnings:
for w in warning_records:
lines = w.message.splitlines()
indented = '\n'.join(' ' + x for x in lines)
self._tw.line(indented)
@@ -561,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]
@@ -578,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:
@@ -593,8 +700,8 @@ def repr_pythonversion(v=None):
return str(v)
def flatten(l):
for x in l:
def flatten(values):
for x in values:
if isinstance(x, (list, tuple)):
for y in flatten(x):
yield y
@@ -635,7 +742,7 @@ def build_summary_stats_line(stats):
def _plugin_nameversions(plugininfo):
l = []
values = []
for plugin, dist in plugininfo:
# gets us name and version!
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
@@ -644,6 +751,6 @@ def _plugin_nameversions(plugininfo):
name = name[7:]
# we decided to print python package names
# they can have more than one plugin
if name not in l:
l.append(name)
return l
if name not in values:
values.append(name)
return values

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)

View File

@@ -9,7 +9,6 @@ import _pytest._code
from _pytest.config import hookimpl
from _pytest.outcomes import fail, skip, xfail
from _pytest.python import transfer_markers, Class, Module, Function
from _pytest.skipping import MarkEvaluator
def pytest_pycollect_makeitem(collector, name, obj):
@@ -109,13 +108,13 @@ class TestCaseFunction(Function):
except TypeError:
try:
try:
l = traceback.format_exception(*rawexcinfo)
l.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n")
fail("".join(l), pytrace=False)
values = traceback.format_exception(*rawexcinfo)
values.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n")
fail("".join(values), pytrace=False)
except (fail.Exception, KeyboardInterrupt):
raise
except:
except: # noqa
fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" % (rawexcinfo,), pytrace=False)
except KeyboardInterrupt:
@@ -134,8 +133,7 @@ class TestCaseFunction(Function):
try:
skip(reason)
except skip.Exception:
self._evalskip = MarkEvaluator(self, 'SkipTest')
self._evalskip.result = True
self._skipped_by_mark = True
self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason=""):

View File

@@ -1,13 +0,0 @@
This directory vendors the `pluggy` module.
For a more detailed discussion for the reasons to vendoring this
package, please see [this issue](https://github.com/pytest-dev/pytest/issues/944).
To update the current version, execute:
```
$ pip install -U pluggy==<version> --no-compile --target=_pytest/vendored_packages
```
And commit the modified files. The `pluggy-<version>.dist-info` directory
created by `pip` should be added as well.

View File

@@ -1,11 +0,0 @@
Plugin registration and hook calling for Python
===============================================
This is the plugin manager as used by pytest but stripped
of pytest specific details.
During the 0.x series this plugin does not have much documentation
except extensive docstrings in the pluggy.py module.

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,40 +0,0 @@
Metadata-Version: 2.0
Name: pluggy
Version: 0.4.0
Summary: plugin and hook calling mechanisms for python
Home-page: https://github.com/pytest-dev/pluggy
Author: Holger Krekel
Author-email: holger at merlinux.eu
License: MIT license
Platform: unix
Platform: linux
Platform: osx
Platform: win32
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Plugin registration and hook calling for Python
===============================================
This is the plugin manager as used by pytest but stripped
of pytest specific details.
During the 0.x series this plugin does not have much documentation
except extensive docstrings in the pluggy.py module.

View File

@@ -1,9 +0,0 @@
pluggy.py,sha256=u0oG9cv-oLOkNvEBlwnnu8pp1AyxpoERgUO00S3rvpQ,31543
pluggy-0.4.0.dist-info/DESCRIPTION.rst,sha256=ltvjkFd40LW_xShthp6RRVM6OB_uACYDFR3kTpKw7o4,307
pluggy-0.4.0.dist-info/LICENSE.txt,sha256=ruwhUOyV1HgE9F35JVL9BCZ9vMSALx369I4xq9rhpkM,1134
pluggy-0.4.0.dist-info/METADATA,sha256=pe2hbsqKFaLHC6wAQPpFPn0KlpcPfLBe_BnS4O70bfk,1364
pluggy-0.4.0.dist-info/RECORD,,
pluggy-0.4.0.dist-info/WHEEL,sha256=9Z5Xm-eel1bTS7e6ogYiKz0zmPEqDwIypurdHN1hR40,116
pluggy-0.4.0.dist-info/metadata.json,sha256=T3go5L2qOa_-H-HpCZi3EoVKb8sZ3R-fOssbkWo2nvM,1119
pluggy-0.4.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
pluggy-0.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4

View File

@@ -1,6 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@@ -1 +0,0 @@
{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "holger at merlinux.eu", "name": "Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://github.com/pytest-dev/pluggy"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT license", "metadata_version": "2.0", "name": "pluggy", "platform": "unix", "summary": "plugin and hook calling mechanisms for python", "version": "0.4.0"}

View File

@@ -1,802 +0,0 @@
"""
PluginManager, basic initialization and tracing.
pluggy is the cristallized core of plugin management as used
by some 150 plugins for pytest.
Pluggy uses semantic versioning. Breaking changes are only foreseen for
Major releases (incremented X in "X.Y.Z"). If you want to use pluggy in
your project you should thus use a dependency restriction like
"pluggy>=0.1.0,<1.0" to avoid surprises.
pluggy is concerned with hook specification, hook implementations and hook
calling. For any given hook specification a hook call invokes up to N implementations.
A hook implementation can influence its position and type of execution:
if attributed "tryfirst" or "trylast" it will be tried to execute
first or last. However, if attributed "hookwrapper" an implementation
can wrap all calls to non-hookwrapper implementations. A hookwrapper
can thus execute some code ahead and after the execution of other hooks.
Hook specification is done by way of a regular python function where
both the function name and the names of all its arguments are significant.
Each hook implementation function is verified against the original specification
function, including the names of all its arguments. To allow for hook specifications
to evolve over the livetime of a project, hook implementations can
accept less arguments. One can thus add new arguments and semantics to
a hook specification by adding another argument typically without breaking
existing hook implementations.
The chosen approach is meant to let a hook designer think carefuly about
which objects are needed by an extension writer. By contrast, subclass-based
extension mechanisms often expose a lot more state and behaviour than needed,
thus restricting future developments.
Pluggy currently consists of functionality for:
- a way to register new hook specifications. Without a hook
specification no hook calling can be performed.
- a registry of plugins which contain hook implementation functions. It
is possible to register plugins for which a hook specification is not yet
known and validate all hooks when the system is in a more referentially
consistent state. Setting an "optionalhook" attribution to a hook
implementation will avoid PluginValidationError's if a specification
is missing. This allows to have optional integration between plugins.
- a "hook" relay object from which you can launch 1:N calls to
registered hook implementation functions
- a mechanism for ordering hook implementation functions
- mechanisms for two different type of 1:N calls: "firstresult" for when
the call should stop when the first implementation returns a non-None result.
And the other (default) way of guaranteeing that all hook implementations
will be called and their non-None result collected.
- mechanisms for "historic" extension points such that all newly
registered functions will receive all hook calls that happened
before their registration.
- a mechanism for discovering plugin objects which are based on
setuptools based entry points.
- a simple tracing mechanism, including tracing of plugin calls and
their arguments.
"""
import sys
import inspect
__version__ = '0.4.0'
__all__ = ["PluginManager", "PluginValidationError", "HookCallError",
"HookspecMarker", "HookimplMarker"]
_py3 = sys.version_info > (3, 0)
class HookspecMarker:
""" Decorator helper class for marking functions as hook specifications.
You can instantiate it with a project_name to get a decorator.
Calling PluginManager.add_hookspecs later will discover all marked functions
if the PluginManager uses the same project_name.
"""
def __init__(self, project_name):
self.project_name = project_name
def __call__(self, function=None, firstresult=False, historic=False):
""" if passed a function, directly sets attributes on the function
which will make it discoverable to add_hookspecs(). If passed no
function, returns a decorator which can be applied to a function
later using the attributes supplied.
If firstresult is True the 1:N hook call (N being the number of registered
hook implementation functions) will stop at I<=N when the I'th function
returns a non-None result.
If historic is True calls to a hook will be memorized and replayed
on later registered plugins.
"""
def setattr_hookspec_opts(func):
if historic and firstresult:
raise ValueError("cannot have a historic firstresult hook")
setattr(func, self.project_name + "_spec",
dict(firstresult=firstresult, historic=historic))
return func
if function is not None:
return setattr_hookspec_opts(function)
else:
return setattr_hookspec_opts
class HookimplMarker:
""" Decorator helper class for marking functions as hook implementations.
You can instantiate with a project_name to get a decorator.
Calling PluginManager.register later will discover all marked functions
if the PluginManager uses the same project_name.
"""
def __init__(self, project_name):
self.project_name = project_name
def __call__(self, function=None, hookwrapper=False, optionalhook=False,
tryfirst=False, trylast=False):
""" if passed a function, directly sets attributes on the function
which will make it discoverable to register(). If passed no function,
returns a decorator which can be applied to a function later using
the attributes supplied.
If optionalhook is True a missing matching hook specification will not result
in an error (by default it is an error if no matching spec is found).
If tryfirst is True this hook implementation will run as early as possible
in the chain of N hook implementations for a specfication.
If trylast is True this hook implementation will run as late as possible
in the chain of N hook implementations.
If hookwrapper is True the hook implementations needs to execute exactly
one "yield". The code before the yield is run early before any non-hookwrapper
function is run. The code after the yield is run after all non-hookwrapper
function have run. The yield receives an ``_CallOutcome`` object representing
the exception or result outcome of the inner calls (including other hookwrapper
calls).
"""
def setattr_hookimpl_opts(func):
setattr(func, self.project_name + "_impl",
dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
tryfirst=tryfirst, trylast=trylast))
return func
if function is None:
return setattr_hookimpl_opts
else:
return setattr_hookimpl_opts(function)
def normalize_hookimpl_opts(opts):
opts.setdefault("tryfirst", False)
opts.setdefault("trylast", False)
opts.setdefault("hookwrapper", False)
opts.setdefault("optionalhook", False)
class _TagTracer:
def __init__(self):
self._tag2proc = {}
self.writer = None
self.indent = 0
def get(self, name):
return _TagTracerSub(self, (name,))
def format_message(self, tags, args):
if isinstance(args[-1], dict):
extra = args[-1]
args = args[:-1]
else:
extra = {}
content = " ".join(map(str, args))
indent = " " * self.indent
lines = [
"%s%s [%s]\n" % (indent, content, ":".join(tags))
]
for name, value in extra.items():
lines.append("%s %s: %s\n" % (indent, name, value))
return lines
def processmessage(self, tags, args):
if self.writer is not None and args:
lines = self.format_message(tags, args)
self.writer(''.join(lines))
try:
self._tag2proc[tags](tags, args)
except KeyError:
pass
def setwriter(self, writer):
self.writer = writer
def setprocessor(self, tags, processor):
if isinstance(tags, str):
tags = tuple(tags.split(":"))
else:
assert isinstance(tags, tuple)
self._tag2proc[tags] = processor
class _TagTracerSub:
def __init__(self, root, tags):
self.root = root
self.tags = tags
def __call__(self, *args):
self.root.processmessage(self.tags, args)
def setmyprocessor(self, processor):
self.root.setprocessor(self.tags, processor)
def get(self, name):
return self.__class__(self.root, self.tags + (name,))
def _raise_wrapfail(wrap_controller, msg):
co = wrap_controller.gi_code
raise RuntimeError("wrap_controller at %r %s:%d %s" %
(co.co_name, co.co_filename, co.co_firstlineno, msg))
def _wrapped_call(wrap_controller, func):
""" Wrap calling to a function with a generator which needs to yield
exactly once. The yield point will trigger calling the wrapped function
and return its _CallOutcome to the yield point. The generator then needs
to finish (raise StopIteration) in order for the wrapped call to complete.
"""
try:
next(wrap_controller) # first yield
except StopIteration:
_raise_wrapfail(wrap_controller, "did not yield")
call_outcome = _CallOutcome(func)
try:
wrap_controller.send(call_outcome)
_raise_wrapfail(wrap_controller, "has second yield")
except StopIteration:
pass
return call_outcome.get_result()
class _CallOutcome:
""" Outcome of a function call, either an exception or a proper result.
Calling the ``get_result`` method will return the result or reraise
the exception raised when the function was called. """
excinfo = None
def __init__(self, func):
try:
self.result = func()
except BaseException:
self.excinfo = sys.exc_info()
def force_result(self, result):
self.result = result
self.excinfo = None
def get_result(self):
if self.excinfo is None:
return self.result
else:
ex = self.excinfo
if _py3:
raise ex[1].with_traceback(ex[2])
_reraise(*ex) # noqa
if not _py3:
exec("""
def _reraise(cls, val, tb):
raise cls, val, tb
""")
class _TracedHookExecution:
def __init__(self, pluginmanager, before, after):
self.pluginmanager = pluginmanager
self.before = before
self.after = after
self.oldcall = pluginmanager._inner_hookexec
assert not isinstance(self.oldcall, _TracedHookExecution)
self.pluginmanager._inner_hookexec = self
def __call__(self, hook, hook_impls, kwargs):
self.before(hook.name, hook_impls, kwargs)
outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
self.after(outcome, hook.name, hook_impls, kwargs)
return outcome.get_result()
def undo(self):
self.pluginmanager._inner_hookexec = self.oldcall
class PluginManager(object):
""" Core Pluginmanager class which manages registration
of plugin objects and 1:N hook calling.
You can register new hooks by calling ``add_hookspec(module_or_class)``.
You can register plugin objects (which contain hooks) by calling
``register(plugin)``. The Pluginmanager is initialized with a
prefix that is searched for in the names of the dict of registered
plugin objects. An optional excludefunc allows to blacklist names which
are not considered as hooks despite a matching prefix.
For debugging purposes you can call ``enable_tracing()``
which will subsequently send debug information to the trace helper.
"""
def __init__(self, project_name, implprefix=None):
""" if implprefix is given implementation functions
will be recognized if their name matches the implprefix. """
self.project_name = project_name
self._name2plugin = {}
self._plugin2hookcallers = {}
self._plugin_distinfo = []
self.trace = _TagTracer().get("pluginmanage")
self.hook = _HookRelay(self.trace.root.get("hook"))
self._implprefix = implprefix
self._inner_hookexec = lambda hook, methods, kwargs: \
_MultiCall(methods, kwargs, hook.spec_opts).execute()
def _hookexec(self, hook, methods, kwargs):
# called from all hookcaller instances.
# enable_tracing will set its own wrapping function at self._inner_hookexec
return self._inner_hookexec(hook, methods, kwargs)
def register(self, plugin, name=None):
""" Register a plugin and return its canonical name or None if the name
is blocked from registering. Raise a ValueError if the plugin is already
registered. """
plugin_name = name or self.get_canonical_name(plugin)
if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
if self._name2plugin.get(plugin_name, -1) is None:
return # blocked plugin, return None to indicate no registration
raise ValueError("Plugin already registered: %s=%s\n%s" %
(plugin_name, plugin, self._name2plugin))
# XXX if an error happens we should make sure no state has been
# changed at point of return
self._name2plugin[plugin_name] = plugin
# register matching hook implementations of the plugin
self._plugin2hookcallers[plugin] = hookcallers = []
for name in dir(plugin):
hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
if hookimpl_opts is not None:
normalize_hookimpl_opts(hookimpl_opts)
method = getattr(plugin, name)
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
hook = getattr(self.hook, name, None)
if hook is None:
hook = _HookCaller(name, self._hookexec)
setattr(self.hook, name, hook)
elif hook.has_spec():
self._verify_hook(hook, hookimpl)
hook._maybe_apply_history(hookimpl)
hook._add_hookimpl(hookimpl)
hookcallers.append(hook)
return plugin_name
def parse_hookimpl_opts(self, plugin, name):
method = getattr(plugin, name)
try:
res = getattr(method, self.project_name + "_impl", None)
except Exception:
res = {}
if res is not None and not isinstance(res, dict):
# false positive
res = None
elif res is None and self._implprefix and name.startswith(self._implprefix):
res = {}
return res
def unregister(self, plugin=None, name=None):
""" unregister a plugin object and all its contained hook implementations
from internal data structures. """
if name is None:
assert plugin is not None, "one of name or plugin needs to be specified"
name = self.get_name(plugin)
if plugin is None:
plugin = self.get_plugin(name)
# if self._name2plugin[name] == None registration was blocked: ignore
if self._name2plugin.get(name):
del self._name2plugin[name]
for hookcaller in self._plugin2hookcallers.pop(plugin, []):
hookcaller._remove_plugin(plugin)
return plugin
def set_blocked(self, name):
""" block registrations of the given name, unregister if already registered. """
self.unregister(name=name)
self._name2plugin[name] = None
def is_blocked(self, name):
""" return True if the name blogs registering plugins of that name. """
return name in self._name2plugin and self._name2plugin[name] is None
def add_hookspecs(self, module_or_class):
""" add new hook specifications defined in the given module_or_class.
Functions are recognized if they have been decorated accordingly. """
names = []
for name in dir(module_or_class):
spec_opts = self.parse_hookspec_opts(module_or_class, name)
if spec_opts is not None:
hc = getattr(self.hook, name, None)
if hc is None:
hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
setattr(self.hook, name, hc)
else:
# plugins registered this hook without knowing the spec
hc.set_specification(module_or_class, spec_opts)
for hookfunction in (hc._wrappers + hc._nonwrappers):
self._verify_hook(hc, hookfunction)
names.append(name)
if not names:
raise ValueError("did not find any %r hooks in %r" %
(self.project_name, module_or_class))
def parse_hookspec_opts(self, module_or_class, name):
method = getattr(module_or_class, name)
return getattr(method, self.project_name + "_spec", None)
def get_plugins(self):
""" return the set of registered plugins. """
return set(self._plugin2hookcallers)
def is_registered(self, plugin):
""" Return True if the plugin is already registered. """
return plugin in self._plugin2hookcallers
def get_canonical_name(self, plugin):
""" Return canonical name for a plugin object. Note that a plugin
may be registered under a different name which was specified
by the caller of register(plugin, name). To obtain the name
of an registered plugin use ``get_name(plugin)`` instead."""
return getattr(plugin, "__name__", None) or str(id(plugin))
def get_plugin(self, name):
""" Return a plugin or None for the given name. """
return self._name2plugin.get(name)
def has_plugin(self, name):
""" Return True if a plugin with the given name is registered. """
return self.get_plugin(name) is not None
def get_name(self, plugin):
""" Return name for registered plugin or None if not registered. """
for name, val in self._name2plugin.items():
if plugin == val:
return name
def _verify_hook(self, hook, hookimpl):
if hook.is_historic() and hookimpl.hookwrapper:
raise PluginValidationError(
"Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
(hookimpl.plugin_name, hook.name))
for arg in hookimpl.argnames:
if arg not in hook.argnames:
raise PluginValidationError(
"Plugin %r\nhook %r\nargument %r not available\n"
"plugin definition: %s\n"
"available hookargs: %s" %
(hookimpl.plugin_name, hook.name, arg,
_formatdef(hookimpl.function), ", ".join(hook.argnames)))
def check_pending(self):
""" Verify that all hooks which have not been verified against
a hook specification are optional, otherwise raise PluginValidationError"""
for name in self.hook.__dict__:
if name[0] != "_":
hook = getattr(self.hook, name)
if not hook.has_spec():
for hookimpl in (hook._wrappers + hook._nonwrappers):
if not hookimpl.optionalhook:
raise PluginValidationError(
"unknown hook %r in plugin %r" %
(name, hookimpl.plugin))
def load_setuptools_entrypoints(self, entrypoint_name):
""" Load modules from querying the specified setuptools entrypoint name.
Return the number of loaded plugins. """
from pkg_resources import (iter_entry_points, DistributionNotFound,
VersionConflict)
for ep in iter_entry_points(entrypoint_name):
# is the plugin registered or blocked?
if self.get_plugin(ep.name) or self.is_blocked(ep.name):
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
except VersionConflict as e:
raise PluginValidationError(
"Plugin %r could not be loaded: %s!" % (ep.name, e))
self.register(plugin, name=ep.name)
self._plugin_distinfo.append((plugin, ep.dist))
return len(self._plugin_distinfo)
def list_plugin_distinfo(self):
""" return list of distinfo/plugin tuples for all setuptools registered
plugins. """
return list(self._plugin_distinfo)
def list_name_plugin(self):
""" return list of name/plugin pairs. """
return list(self._name2plugin.items())
def get_hookcallers(self, plugin):
""" get all hook callers for the specified plugin. """
return self._plugin2hookcallers.get(plugin)
def add_hookcall_monitoring(self, before, after):
""" add before/after tracing functions for all hooks
and return an undo function which, when called,
will remove the added tracers.
``before(hook_name, hook_impls, kwargs)`` will be called ahead
of all hook calls and receive a hookcaller instance, a list
of HookImpl instances and the keyword arguments for the hook call.
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
same arguments as ``before`` but also a :py:class:`_CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` object
which represents the result of the overall hook call.
"""
return _TracedHookExecution(self, before, after).undo
def enable_tracing(self):
""" enable tracing of hook calls and return an undo function. """
hooktrace = self.hook._trace
def before(hook_name, methods, kwargs):
hooktrace.root.indent += 1
hooktrace(hook_name, kwargs)
def after(outcome, hook_name, methods, kwargs):
if outcome.excinfo is None:
hooktrace("finish", hook_name, "-->", outcome.result)
hooktrace.root.indent -= 1
return self.add_hookcall_monitoring(before, after)
def subset_hook_caller(self, name, remove_plugins):
""" Return a new _HookCaller instance for the named method
which manages calls to all registered plugins except the
ones from remove_plugins. """
orig = getattr(self.hook, name)
plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
if plugins_to_remove:
hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
orig.spec_opts)
for hookimpl in (orig._wrappers + orig._nonwrappers):
plugin = hookimpl.plugin
if plugin not in plugins_to_remove:
hc._add_hookimpl(hookimpl)
# we also keep track of this hook caller so it
# gets properly removed on plugin unregistration
self._plugin2hookcallers.setdefault(plugin, []).append(hc)
return hc
return orig
class _MultiCall:
""" execute a call into multiple python functions/methods. """
# XXX note that the __multicall__ argument is supported only
# for pytest compatibility reasons. It was never officially
# supported there and is explicitely deprecated since 2.8
# so we can remove it soon, allowing to avoid the below recursion
# in execute() and simplify/speed up the execute loop.
def __init__(self, hook_impls, kwargs, specopts={}):
self.hook_impls = hook_impls
self.kwargs = kwargs
self.kwargs["__multicall__"] = self
self.specopts = specopts
def execute(self):
all_kwargs = self.kwargs
self.results = results = []
firstresult = self.specopts.get("firstresult")
while self.hook_impls:
hook_impl = self.hook_impls.pop()
try:
args = [all_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in all_kwargs:
raise HookCallError(
"hook call must provide argument %r" % (argname,))
if hook_impl.hookwrapper:
return _wrapped_call(hook_impl.function(*args), self.execute)
res = hook_impl.function(*args)
if res is not None:
if firstresult:
return res
results.append(res)
if not firstresult:
return results
def __repr__(self):
status = "%d meths" % (len(self.hook_impls),)
if hasattr(self, "results"):
status = ("%d results, " % len(self.results)) + status
return "<_MultiCall %s, kwargs=%r>" % (status, self.kwargs)
def varnames(func, startindex=None):
""" return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered.
For methods the "self" parameter is not included unless you are passing
an unbound method with Python3 (which has no supports for unbound methods)
"""
cache = getattr(func, "__dict__", {})
try:
return cache["_varnames"]
except KeyError:
pass
if inspect.isclass(func):
try:
func = func.__init__
except AttributeError:
return ()
startindex = 1
else:
if not inspect.isfunction(func) and not inspect.ismethod(func):
try:
func = getattr(func, '__call__', func)
except Exception:
return ()
if startindex is None:
startindex = int(inspect.ismethod(func))
try:
rawcode = func.__code__
except AttributeError:
return ()
try:
x = rawcode.co_varnames[startindex:rawcode.co_argcount]
except AttributeError:
x = ()
else:
defaults = func.__defaults__
if defaults:
x = x[:-len(defaults)]
try:
cache["_varnames"] = x
except TypeError:
pass
return x
class _HookRelay:
""" hook holder object for performing 1:N hook calls where N is the number
of registered plugins.
"""
def __init__(self, trace):
self._trace = trace
class _HookCaller(object):
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
if specmodule_or_class is not None:
assert spec_opts is not None
self.set_specification(specmodule_or_class, spec_opts)
def has_spec(self):
return hasattr(self, "_specmodule_or_class")
def set_specification(self, specmodule_or_class, spec_opts):
assert not self.has_spec()
self._specmodule_or_class = specmodule_or_class
specfunc = getattr(specmodule_or_class, self.name)
argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class))
assert "self" not in argnames # sanity check
self.argnames = ["__multicall__"] + list(argnames)
self.spec_opts = spec_opts
if spec_opts.get("historic"):
self._call_history = []
def is_historic(self):
return hasattr(self, "_call_history")
def _remove_plugin(self, plugin):
def remove(wrappers):
for i, method in enumerate(wrappers):
if method.plugin == plugin:
del wrappers[i]
return True
if remove(self._wrappers) is None:
if remove(self._nonwrappers) is None:
raise ValueError("plugin %r not found" % (plugin,))
def _add_hookimpl(self, hookimpl):
if hookimpl.hookwrapper:
methods = self._wrappers
else:
methods = self._nonwrappers
if hookimpl.trylast:
methods.insert(0, hookimpl)
elif hookimpl.tryfirst:
methods.append(hookimpl)
else:
# find last non-tryfirst method
i = len(methods) - 1
while i >= 0 and methods[i].tryfirst:
i -= 1
methods.insert(i + 1, hookimpl)
def __repr__(self):
return "<_HookCaller %r>" % (self.name,)
def __call__(self, **kwargs):
assert not self.is_historic()
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
def call_historic(self, proc=None, kwargs=None):
self._call_history.append((kwargs or {}, proc))
# historizing hooks don't return results
self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
def call_extra(self, methods, kwargs):
""" Call the hook with some additional temporarily participating
methods using the specified kwargs as call parameters. """
old = list(self._nonwrappers), list(self._wrappers)
for method in methods:
opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
hookimpl = HookImpl(None, "<temp>", method, opts)
self._add_hookimpl(hookimpl)
try:
return self(**kwargs)
finally:
self._nonwrappers, self._wrappers = old
def _maybe_apply_history(self, method):
if self.is_historic():
for kwargs, proc in self._call_history:
res = self._hookexec(self, [method], kwargs)
if res and proc is not None:
proc(res[0])
class HookImpl:
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
self.function = function
self.argnames = varnames(self.function)
self.plugin = plugin
self.opts = hook_impl_opts
self.plugin_name = plugin_name
self.__dict__.update(hook_impl_opts)
class PluginValidationError(Exception):
""" plugin failed validation. """
class HookCallError(Exception):
""" Hook was called wrongly. """
if hasattr(inspect, 'signature'):
def _formatdef(func):
return "%s%s" % (
func.__name__,
str(inspect.signature(func))
)
else:
def _formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@@ -72,8 +72,10 @@ def catch_warnings_for_item(item):
unicode_warning = False
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
new_args = [compat.safe_str(m) for m in warn_msg.args]
unicode_warning = warn_msg.args != new_args
new_args = []
for m in warn_msg.args:
new_args.append(compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m)
unicode_warning = list(warn_msg.args) != new_args
warn_msg.args = new_args
msg = warnings.formatwarning(

View File

@@ -10,9 +10,7 @@ environment:
- TOXENV: "coveralls"
# note: please use "tox --listenvs" to populate the build matrix below
- TOXENV: "linting"
- TOXENV: "py26"
- TOXENV: "py27"
- TOXENV: "py33"
- TOXENV: "py34"
- TOXENV: "py35"
- TOXENV: "py36"
@@ -21,10 +19,12 @@ environment:
- TOXENV: "py27-xdist"
- TOXENV: "py27-trial"
- TOXENV: "py27-numpy"
- TOXENV: "py27-pluggymaster"
- TOXENV: "py36-pexpect"
- TOXENV: "py36-xdist"
- TOXENV: "py36-trial"
- TOXENV: "py36-numpy"
- TOXENV: "py36-pluggymaster"
- TOXENV: "py27-nobyte"
- TOXENV: "doctesting"
- TOXENV: "py35-freeze"

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

@@ -13,8 +13,6 @@ PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
REGENDOC_ARGS := \
--normalize "/={8,} (.*) ={8,}/======= \1 ========/" \
--normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \
--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \

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>

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