Compare commits

...

1335 Commits
3.0.2 ... 3.3.0

Author SHA1 Message Date
Bruno Oliveira
6cdd851227 CHANGELOG and docs changes requested during review 2017-11-27 10:19:34 -02:00
Bruno Oliveira
6eeacaba3e Fix typos in CHANGELOG 2017-11-26 09:00:08 -02: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
Bruno Oliveira
6b91bc88de Preparing release version 3.2.3 2017-10-03 21:42:34 +00: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
6690b8a444 Merge pull request #2807 from xuanluong/issue-1442-mention-not--k-help
[doc] Add example of -k 'not test' in help text
2017-10-01 14:32:34 -03:00
Xuan Luong
7093d8f65e Add example of -k 'not test' in help text 2017-09-30 18:43:20 -04: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
Bruno Oliveira
966391c77e Merge pull request #2789 from Avira/master
Don't crash if an item has no _fixtureinfo attribute
2017-09-19 17:34:43 -03:00
Bruno Oliveira
9c8847a0cb Merge pull request #2792 from Avira/fix-tox-docs-link
fix tox documentation link
2017-09-19 12:31:21 -03:00
Oliver Bestwalter
58aaabbb10 fix tox documentation link 2017-09-19 16:57:16 +02: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
Oliver Bestwalter
2802135741 fix 'DoctestItem' object has no attribute '_fixtureinfo'
* doxtests don't seem to have this attribute, so nothing will be written in that case.
* tried to be a good boy scout and tidied up surrounding code a bit (comments, shadowed/unused names, removed random new lines, naming things)
2017-09-19 12:41:12 +02: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
Ronny Pfannschmidt
bf77daa2ee Merge pull request #2785 from nicoddemus/py36
Change to py36 as main environment for Python 3 environments in tox
2017-09-17 09:19:06 +02:00
Bruno Oliveira
9933635cf7 Change to py36 as main environment for Python 3 environments in tox
This also has the benefit of working around Travis recent failures
in Python 3.5 environments.
2017-09-16 19:18:07 -03:00
Bruno Oliveira
ac5c5cc1ef Merge pull request #2750 from evanunderscore/fix-filescompleter
Fix crash in FastFilesCompleter with no prefix
2017-09-14 20:11:43 -03:00
Bruno Oliveira
810320f591 Small fixes to development_guide: title and label names
* Fix title to use a proper "title" section marker
* Fix labels by adding a " " after the ":"
* Fix OS labels after obvious mishap
* Sort labels
2017-09-14 19:58:56 -03:00
Bruno Oliveira
25d2acbdb2 Merge pull request #2760 from nicoddemus/labels
Add development guide to docs
2017-09-14 19:20:45 -03:00
Bruno Oliveira
52c134aed3 Add development guide to docs 2017-09-13 19:32:40 -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
Ronny Pfannschmidt
70cdfaf661 Merge pull request #2754 from nicoddemus/xfail_strict
Set xfail_strict=True in pytest's own test suite
2017-09-13 09:01:32 +02:00
Bruno Oliveira
abfd9774ef Remove xfail mark from passing test in py26 2017-09-12 17:59:09 -03:00
Bruno Oliveira
e57cc55719 Merge pull request #2766 from xuanluong/issue-1548-docs-multiple-calls-metafunc-parametrize
Update documentation on multiple calls of metafunc.parametrize
2017-09-10 02:07:06 -03:00
Xuan Luong
696c702da7 Update documentation on multiple calls of metafunc.parametrize 2017-09-10 00:23:23 -04:00
Bruno Oliveira
bee2c864d8 Merge pull request #2765 from xuanluong/pass-callable-to-marker-typo
Fix typo in example of passing a callable to markers
2017-09-09 21:03:13 -03:00
Xuan Luong
e27a0d69aa Rename changelog file to PR id number 2017-09-09 18:26:15 -04:00
Xuan Luong
15222ceca2 Fix typo in example of passing a callable to markers 2017-09-09 18:22:23 -04:00
Bruno Oliveira
3c1ca03b9c Merge pull request #2763 from jhamman/docs/skipif_class_warning
add warning to skipping docs re marker inheritance
2017-09-09 15:15:47 -03:00
Ronny Pfannschmidt
25ed4edbc7 Merge pull request #2764 from xuanluong/issue-2758-fix-mark-decorator-equality
[bugfix] Checking MarkDecorator equality returns False for non-MarkDecorator object
2017-09-09 10:03:49 +02:00
Xuan Luong
1e93089165 [bugfix] Checking MarkDecorator equality returns False for non-MarkDecorator object 2017-09-09 01:31:08 -04:00
Joe Hamman
b2a8e06e4f add warning to skipping docs re marker inheritance 2017-09-08 12:01:33 -07:00
Bruno Oliveira
9273e11f21 Merge branch 'master' into features 2017-09-07 13:59:10 -03:00
Bruno Oliveira
09349c344e Merge pull request #2757 from nicoddemus/release-3.2.2
Preparing release version 3.2.2
2017-09-07 13:54:31 -03:00
Evan
6cf515b164 Fix crash in FastFilesCompleter with no prefix 2017-09-08 00:14:56 +10: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
c52f87ede3 Preparing release version 3.2.2 2017-09-06 21:37:57 +00:00
Bruno Oliveira
549f5c1a47 Merge pull request #2736 from xuanluong/issue-2604-documents-mark.with_args
[DOC] Add examples for mark.with_args
2017-09-06 16:33:01 -03:00
Xuan Luong
3f8ff7f090 [DOC] Add examples for mark.with_args 2017-09-06 14:26:28 -04: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
Ronny Pfannschmidt
ad36407747 Merge pull request #2700 from nicoddemus/staticmethods-fixtures
Allow tests declared as @staticmethod to use fixtures
2017-09-06 08:24:00 +02: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
10d43bd3bf Set xfail_strict=True in pytest's own test suite
Fix #2722
2017-09-05 19:57:26 -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
1fc185b640 Add comment about possible future refactoring in the fixture mechanism 2017-09-05 19:28:39 -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
Ronny Pfannschmidt
181bd60bf9 Merge pull request #2742 from nicoddemus/resultlog-deprecation
Improve user guidance regarding ``--resultlog`` deprecation
2017-09-01 08:29:41 +02:00
Bruno Oliveira
3288c9a110 Improve user guidance regarding `--resultlog` deprecation
Fix #2739
2017-08-31 19:11:41 -03: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
5e00549ecc Merge pull request #2735 from fgmacedo/fgm-fix-reprfuncargs-toterminal
2731.bug Fix ReprFuncArgs with mixed unicode and utf-8 args.
2017-08-31 09:36:56 +02:00
Ronny Pfannschmidt
b770a32dc8 Merge pull request #2707 from cybergrind/fix_baseexception
Catch BaseException in safe_getattr
2017-08-31 09:35:07 +02:00
Bruno Oliveira
f9157b1b6b Improve CHANGELOG entry to be more user-friendly 2017-08-30 21:21:44 -03:00
Bruno Oliveira
f4e811afc0 Merge remote-tracking branch 'upstream/master' into cybergrind/fix_baseexception 2017-08-30 21:11:22 -03:00
Fernando Macedo
59cdef92be fixes #2731 ReprFuncArgs with mixed unicode and utf-8 args 2017-08-30 16:06:12 -03:00
Ronny Pfannschmidt
78a027e128 simplyfy ascii escaping by using backslashreplace error handling 2017-08-30 16:43:16 +02:00
Bruno Oliveira
709b8b65a4 Merge pull request #2721 from josepht/patch-1
Fix typo in goodpractices.rst
2017-08-29 22:08:20 -03:00
Bruno Oliveira
0824076e11 Merge pull request #2710 from massich/raises_match_doc
[DOC] update raises documentation regarding regex match
2017-08-29 21:01:27 -03: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
Joe Talbott
67161ee9f8 Add changelog item for PR #2721. 2017-08-25 10:06:45 -04:00
Joe Talbott
1c891d7d97 Fix typo in goodpractices.rst 2017-08-25 10:01:24 -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
Kirill Pinchuk
12b1bff6c5 compat.safe_getattr now catches OutcomeExceptions too 2017-08-23 17:17:03 +03:00
Joan Massich
657976e98a update raises documentation regarding regex match 2017-08-22 12:12:48 +02:00
Bruno Oliveira
a993add783 Allow tests declared as @staticmethod to use fixtures
Fix #2699
2017-08-17 20:44:19 -03:00
Ronny Pfannschmidt
539523cfee Merge pull request #2697 from nicoddemus/match-kw-version
Update docs: ``match`` keyword was introduced in 3.1
2017-08-17 14:41:55 +02:00
Bruno Oliveira
f18780ed8a Update docs: `match` keyword was introduced in 3.1
Also update the text to recommend using the context-manager
over the callable/string forms.
2017-08-16 14:28:34 -03:00
Ronny Pfannschmidt
806d47b4d4 Merge pull request #2691 from anhiga/trivial_error
Fixed error in 'Good Practices' code snippet
2017-08-16 09:50:32 +02:00
Bruno Oliveira
bfc9f61482 Update the number of plugins in index.rst 2017-08-15 23:05:23 -03:00
Antonio Hidalgo
2a99d82c3b Fixed error in 'Good Practices' code snippet 2017-08-16 00:26:14 +02: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
Ronny Pfannschmidt
9b2753b302 Merge pull request #2687 from nicoddemus/use-py36-ci
Use 3.6 as preferred Python 3 interpreter for CI
2017-08-15 10:49:39 +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
Bruno Oliveira
e9bfccdf2d Merge pull request #2678 from jespino/fix/2676
Add default values documentation for python_files, python_classes and python_functions
2017-08-14 20:29:39 -03:00
Bruno Oliveira
7b5d26c1a8 Use py36 as preferred Python 3 interpreter for CI testing 2017-08-14 20:27:28 -03:00
prokaktus
98bf5fc9be Fold skipped tests with global pytestmark variable 2017-08-15 02:18:46 +03:00
Bruno Oliveira
362b1b3c4f Use tox release candidates in CI
Fix #2683
2017-08-14 19:19:20 -03:00
Ronny Pfannschmidt
5c0c1977e3 Merge pull request #2682 from pelme/getfuncargvalue-fix
Use the correct stacklevel for getfuncargvalue() deprecation warning.
2017-08-13 18:24:39 +02:00
Andreas Pelme
39331856ed Use the correct stacklevel for getfuncargvalue() deprecation warning.
Fixed #2681.
2017-08-13 14:59:33 +02:00
Jesús Espino
dc9154e8ff Add default values documentation for python_files, python_classes and python_functions 2017-08-12 12:29:13 +02:00
Bruno Oliveira
021fba4e84 Update number of plugins on README and poiint to plugincompat link 2017-08-10 21:15:22 -03:00
Ronny Pfannschmidt
eb462582af fix #2675 - store marks correctly in callspecs 2017-08-10 19:46:36 +02:00
Bruno Oliveira
fd84c886ee Merge pull request #2671 from nicoddemus/release-3.2.1
Release 3.2.1
2017-08-09 17:15:21 -03:00
Bruno Oliveira
e6020781f6 Merge pull request #2653 from felipedau/slow-sharing-note
Add note on sharing the `slow` marker in the basic examples
2017-08-09 08:18:26 -03:00
Felipe Dau
acd3c4fbc4 Update changelog for #2653 2017-08-09 02:57:36 +00:00
Felipe Dau
c847b83d56 Use pytest_collection_modifyitems() in the run/skip option example 2017-08-09 02:51:07 +00:00
Bruno Oliveira
45d2962e97 Preparing release version 3.2.1 2017-08-08 21:11:11 +00:00
Bruno Oliveira
8b322afcdb Make generated doc in simple.rst more reliable
Sometimes `test_funcfast` would show up in the `setup` step in
the slowest test durations summary.
2017-08-08 18:04:21 -03:00
Ronny Pfannschmidt
523bfa6151 Merge pull request #2667 from nicoddemus/py36-windows-workaround-error
Fix windows console workaround error with non-standard io-streams
2017-08-08 07:10:49 +02:00
Bruno Oliveira
cc0f2473eb Fix windows console workaround error with non-standard io-streams
Fix #2666
2017-08-07 20:57:13 -03:00
Bruno Oliveira
76c55b31c6 Merge pull request #2630 from srinivasreddy/2591
Fixed#2591: Replaced os.sep with '/' as it behaves differently on linux and windows.
2017-08-06 11:19:10 -03:00
Srinivas Reddy Thatiparthy
a0101f024e remove os.sep as it behaves differently linux and windows.
* on linux it is '/'

* on windows it is '\'
2017-08-05 23:21:43 +05:30
Ronny Pfannschmidt
d5f4496bdf Merge pull request #2656 from nicoddemus/unittest-features
Document which pytest features work with `unittest`
2017-08-05 09:38:02 +02:00
Bruno Oliveira
37353a854e Implement suggestions by Raphael 2017-08-04 17:56:13 -03:00
Ronny Pfannschmidt
12e60956de Merge pull request #2655 from nicoddemus/terminal-collecting-glitch
Fix small terminal glitch when collecting a single test item
2017-08-04 14:10:45 +02:00
Bruno Oliveira
15cdf137d5 Document which pytest features work with unittest
Fix #2626
2017-08-04 07:44:04 -03: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
ad52f714a9 Fix small terminal glitch when collecting a single test item
Fix #2579
2017-08-03 20:57:46 -03:00
Florian Bruhin
8969bd43c9 Merge pull request #2646 from nicoddemus/issue-2644
Properly escape test names when setting PYTEST_CURRENT_TEST environment variable
2017-08-02 16:27:05 +02:00
Bruno Oliveira
7703dc921c Only skip null bytes before setting the environment variable
As discussed, node ids have already been "ascii" sanitized by the
parametrization process
2017-08-02 10:27:45 -03:00
Bruno Oliveira
1deac2e210 Properly escape test names when setting PYTEST_CURRENT_TEST environment variable
Fix #2644
2017-08-01 20:52:37 -03:00
Bruno Oliveira
02da156351 Merge pull request #2645 from alex/patch-1
Tiny rst syntax fix
2017-08-01 20:14:39 -03:00
Alex Gaynor
84061233ef Tiny rst syntax fix 2017-08-01 19:10:50 -04:00
Bruno Oliveira
40254b64e5 Merge branch 'master' into features 2017-08-01 19:35:56 -03:00
Bruno Oliveira
0a15edd573 Merge branch 'release-3.2.0' 2017-08-01 18:58:43 -03:00
Bruno Oliveira
51ebad76f2 Fix merge instruction after a minor/major release 2017-08-01 18:24:17 -03:00
Bruno Oliveira
d2bca93109 Update grammar in changelog as requested 2017-08-01 18:01:22 -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
Bruno Oliveira
07dd1ca7b8 Preparing release version 3.2.0 2017-07-30 21:37:18 +00:00
Bruno Oliveira
f1467f8f03 Merge remote-tracking branch 'upstream/master' into features 2017-07-30 18:08:24 -03:00
Bruno Oliveira
763c580a2a Merge pull request #2576 from maiksensi/feat/raise-not-implemented-for-lt-gt-in-approx
#2003 Change behavior of `approx.py` to only support `__eq__` comparison
2017-07-30 17:48:33 -03:00
Bruno Oliveira
e1aed8cb17 Merge pull request #2490 from RonnyPfannschmidt/fix-580
Test Outcomes as BaseException - fix #580
2017-07-30 17:38:42 -03:00
Bruno Oliveira
713f7636e1 Merge pull request #2632 from jmoldow/pep_0415_suppress_exception_context
Support PEP-415's Exception.__suppress_context__
2017-07-30 17:22:40 -03:00
Bruno Oliveira
4cd8727379 Merge pull request #2617 from wence-/fix/nondeterministic-fixtures
Fix nondeterminism in fixture collection order
2017-07-30 17:17:40 -03:00
Bruno Oliveira
5e0e038fec Merge pull request #2625 from nicoddemus/historical-notes
Historical notes in the docs
2017-07-29 12:22:04 -03:00
Jordan Moldow
2e61f702c0 Support PEP-415's Exception.__suppress_context__
PEP-415 states that `exception.__context__` should be suppressed
in traceback outputs, if `exception.__suppress_context__` is
`True`.

Now if a ``raise exception from None`` is caught by pytest,
pytest will no longer chain the context in the test report.

The algorithm in `FormattedExcinfo` now better matches the one
in `traceback.TracebackException`.

`Exception.__suppress_context__` is available in all of the
versions of Python 3 that are supported by pytest.

Fixes #2631.
2017-07-29 02:39:17 -07:00
Bruno Oliveira
8c2319168a Rephrase the bit about unittest migration to pytest 2017-07-28 23:15:59 -03:00
Bruno Oliveira
768edde899 Merge pull request #2624 from nicoddemus/lf-report-after-collection
Fix --last-failed reported items in terminal
2017-07-28 14:48:23 -03:00
Ronny Pfannschmidt
be401bc2f8 fix linting issues 2017-07-28 18:27:59 +02:00
Ronny Pfannschmidt
06a49338b2 make Test Outcomes inherit from BaseException instead of exception
fixes #580
2017-07-28 15:28:51 +02:00
Bruno Oliveira
7a12acb6a1 Fix linting 2017-07-28 08:54:55 -03:00
Bruno Oliveira
5acb64be90 Add versionadded tag to pytest_report_collectionfinish hook 2017-07-28 08:54:55 -03:00
Bruno Oliveira
75e6f7717c Use new hook to report accurate tests skipped in --lf and --ff 2017-07-28 08:54:55 -03:00
Ronny Pfannschmidt
17121960b4 Merge pull request #2621 from nicoddemus/cumulative-cache
Make cache plugin cumulative
2017-07-28 13:46:37 +02:00
Bruno Oliveira
7082320f3f Apply modifications requested in review 2017-07-28 07:55:34 -03:00
Bruno Oliveira
6fe7069cbb Move historical notes to their own doc
Fix #2512
2017-07-27 20:47:12 -03:00
Bruno Oliveira
d46006f791 Fixes in "contact" doc 2017-07-27 19:28:55 -03:00
Bruno Oliveira
f770f16294 Replace deprecated "config.option.<name>" usages from docs 2017-07-27 19:18:44 -03:00
Bruno Oliveira
eb1bd3449e xfail and skipped tests are removed from the "last-failed" cache
This accommodates the case where a failing test is marked as
skipped/failed later
2017-07-27 18:43:04 -03:00
Bruno Oliveira
22212c4d61 Add xfail specific tests 2017-07-27 14:40:21 -03:00
Bruno Oliveira
62810f61b2 Make cache plugin always remember failed tests 2017-07-27 14:40:21 -03:00
Ronny Pfannschmidt
e97fd5ec55 Merge pull request #2623 from nicoddemus/post-collection-report-hook
Introduce new pytest_report_collectionfinish hook
2017-07-27 17:17:21 +02:00
Bruno Oliveira
17c544e793 Introduce new pytest_report_collectionfinish hook
Fix #2622
2017-07-27 10:44:29 -03:00
Ronny Pfannschmidt
ddf1751e6d Merge pull request #2613 from nicoddemus/features
Merge master into features
2017-07-27 07:59:49 +02:00
Bruno Oliveira
3d89905114 Merge remote-tracking branch 'upstream/master' into features 2017-07-26 19:01:28 -03:00
Bruno Oliveira
1a9bc141a5 Merge pull request #2620 from mihaic/multiple-issues-in-changelog
Show multiple issue links in CHANGELOG entries
2017-07-26 16:00:00 -03:00
Mihai Capotă
10ded399d8 Show multiple issue links in CHANGELOG entries
Restores the functionality removed in PR #2488.
2017-07-26 10:58:06 -07:00
Lawrence Mitchell
f047e078e2 Mention new (py26) ordereddict dependency in changelog and docs 2017-07-26 14:41:10 +01:00
Lawrence Mitchell
f8bd693f83 Add ordereddict to install_requires for py26 2017-07-26 14:41:10 +01:00
Lawrence Mitchell
a546a612bd Fix nondeterminism in fixture collection order
fixtures.reorder_items is non-deterministic because it reorders based
on iteration over an (unordered) set.  Change the code to use an
OrderedDict instead so that we get deterministic behaviour, fixes #920.
2017-07-26 14:41:10 +01:00
Bruno Oliveira
dd294aafb3 Merge pull request #2557 from blueyed/EncodedFile-name
capture: ensure name of EncodedFile being a string
2017-07-26 10:39:10 -03:00
Lawrence Mitchell
b39f957b88 Add test of issue #920 2017-07-26 14:38:38 +01:00
Bruno Oliveira
2c2cf81d0a Merge pull request #2580 from andras-tim/fix-runpytest-subprocess
Avoid interactive pdb when pytest tests itself - fix #2023
2017-07-26 10:37:48 -03:00
Bruno Oliveira
80f4699572 approx raises TypeError in Python 2 for comparison operators other than != and == 2017-07-25 20:07:10 -03:00
Maik Figura
57a232fc5a Remove out of scope change 2017-07-25 19:19:14 -03:00
Maik Figura
1851f36beb Add PR requirements changelog and authors 2017-07-25 19:19:14 -03:00
Maik Figura
f0936d42fb Fix linter errors 2017-07-25 19:17:58 -03:00
Maik Figura
d3ab1b9df4 Add user documentation
The new doc section explains why we raise a `NotImplementedError`.
2017-07-25 19:17:58 -03:00
Daniel Hahler
0603d1d500 capture: ensure name of EncodedFile being a string
Fixes https://github.com/pytest-dev/pytest/issues/2555.
2017-07-25 20:37:37 +02:00
Bruno Oliveira
79097e84e2 Merge pull request #2615 from blueyed/revisit_contributing.rst
Revisit CONTRIBUTING.rst
2017-07-25 13:40:45 -03:00
Bruno Oliveira
1a42e26586 Improve changelog wording to be more user-oriented 2017-07-25 13:37:06 -03:00
Bruno Oliveira
595ecd23fd Merge pull request #2548 from blueyed/skip-fix-lineno
Fix lineno offset in show_skipped
2017-07-25 13:35:14 -03:00
Daniel Hahler
949a1406f0 Revisit CONTRIBUTING.rst 2017-07-25 18:27:26 +02:00
Bruno Oliveira
71947cb4f0 Merge pull request #2546 from blueyed/better-skip-not-allowed-error
[RFC] Improve error message for CollectError with skip/skipif
2017-07-25 11:19:25 -03:00
Daniel Hahler
869eed9898 Fix lineno offset in show_skipped
The line number is 0-based here, so add 1.
2017-07-25 15:19:47 +02:00
Daniel Hahler
72531f30c0 Improve error message for CollectError with skip/skipif 2017-07-25 15:14:28 +02:00
Bruno Oliveira
73c6122f35 Merge remote-tracking branch 'upstream/master' into features 2017-07-24 21:12:51 -03:00
Bruno Oliveira
70d9f8638f Merge pull request #2610 from AgriConnect/doctest-lineno
Report lineno from doctest
2017-07-24 16:29:02 -03:00
Nguyễn Hồng Quân
d40d77432c Add test case for DoctestItem.reportinfo() 2017-07-24 23:07:45 +07:00
Bruno Oliveira
e44284c125 Merge pull request #2611 from segevfiner/patch-1
Early import colorama so that it get's the correct terminal
2017-07-24 12:37:22 -03:00
Nguyễn Hồng Quân
dea671f8ba Add changelog for #2610 2017-07-24 22:01:03 +07:00
Nguyễn Hồng Quân
cdaa720bc4 Merge remote-tracking branch 'upstream/master' into doctest-lineno 2017-07-24 21:49:35 +07:00
Segev Finer
d0ecfdf00f Delete trailing whitespace 2017-07-24 16:55:50 +03:00
Florian Bruhin
81ad185f0d Merge pull request #2595 from nicoddemus/docs-rootdir-pythonpath
Clarify PYTHONPATH changes and ``rootdir`` roles
2017-07-24 15:06:38 +02:00
Bruno Oliveira
d90bef44cc Update changelog file for #2510 2017-07-24 09:31:16 -03:00
Segev Finer
df12500661 Create 2611.bugfix 2017-07-24 15:28:20 +03:00
Segev Finer
43544a431c Early import colorama so that it get's the correct terminal 2017-07-24 15:17:39 +03:00
Bruno Oliveira
0aa2480e6a Fix travis build after change from "precise" to "trusty"
Travis recently has changed its dist from "precise" to "trusty", so
some Python versions are no longer installed by default
2017-07-24 07:44:04 -03:00
Florian Bruhin
6473a81b8b Merge pull request #2608 from nicoddemus/contributing-update
Update PR guide and add a "short" version
2017-07-24 12:14:56 +02:00
Nguyễn Hồng Quân
af2c153324 Report lineno from doctest
This is to fix pytest-sugar#122 issue.
2017-07-24 11:52:24 +07:00
Florian Bruhin
309152d9fd Merge pull request #2599 from nicoddemus/turn-warnings-into-errors
Turn warnings into errors WIP, waiting #2598
2017-07-23 11:27:14 +02:00
Bruno Oliveira
d5bb2004f9 Fix travis build after change from "precise" to "trusty"
Travis recently has changed its dist from "precise" to "trusty", so
some Python versions are no longer installed by default
2017-07-23 00:50:28 -03:00
Bruno Oliveira
bda07d8b27 Ignore socket warnings on windows for trial tests 2017-07-22 21:44:18 -03:00
Bruno Oliveira
0726d9a09f Turn warnings into errors in pytest's own test suite
Fix #2588
2017-07-22 21:44:18 -03:00
Bruno Oliveira
61219da0e2 Update PR guide and add a "short" version 2017-07-22 16:08:18 -03:00
Bruno Oliveira
1b732fe361 Merge pull request #2606 from kalekundert/simplify-numpy
Make approx more compatible with numpy
2017-07-22 14:21:08 -03:00
Bruno Oliveira
b35554ca2b Merge pull request #2607 from kalekundert/remove-code-dup
Remove stale copies of raises and approx
2017-07-22 14:20:53 -03:00
Kale Kundert
7e0553267d Remove unused import. 2017-07-22 09:19:13 -07:00
Kale Kundert
ebc7346be4 Raise TypeError for types that can't be compared to arrays. 2017-07-22 09:05:12 -07:00
Kale Kundert
a3b35e1c4b Remove raises and approx from python.py.
These two classes were recently moved to `python_api.py`, but it seems
that they found their way back into the original file somehow.  This
commit removes them again to avoid out-of-date code duplication.
2017-07-22 08:36:15 -07:00
Kale Kundert
4c45bc9971 Add the numpy tests back into tox.ini
I'm not sure why they were removed...
2017-07-22 08:24:45 -07:00
Kale Kundert
495f731760 Simplify how comparisons with numpy arrays work.
Previously I was subverting the natural order of operations by
subclassing from `ndarray`, but it turns out that you can tell just
numpy to call your operator instead of its by setting the
`__array_priority__` attribute on your class.  This is much simpler, and
it turns out the be a little more robust, too.
2017-07-22 07:52:03 -07:00
Andras Tim
50764d9ebb Avoid interactive pdb when pytest tests itself - fix #2023
The debugging.py calls post_mortem() on error and pdb will drops an
interactive debugger when the stdin is a readable fd.
2017-07-21 21:29:03 +02:00
Bruno Oliveira
6461dc9fc6 Merge pull request #2600 from RonnyPfannschmidt/mark_explicit_params
fix #2540, introduce mark.with_args
2017-07-21 10:42:25 -03:00
Florian Bruhin
1cf826624e Merge pull request #2602 from blueyed/doc-fix-filterwarnings
Fix help for filterwarnings ini option
2017-07-21 15:23:22 +02:00
Daniel Hahler
97e5a3c889 Fix help for filterwarnings ini option 2017-07-21 14:21:31 +02:00
Ronny Pfannschmidt
65b2de13a3 fix #2540, introduce mark.with_args 2017-07-21 13:37:09 +02:00
Bruno Oliveira
3d24485cae Clarify PYTHONPATH changes and `rootdir` roles
- Also minor adjustments in the docs (wording, formatting, links, etc).

Fix #2589
2017-07-21 07:28:11 -03:00
Ronny Pfannschmidt
ccc4b3a501 Merge pull request #2596 from nicoddemus/autopep-tox
Add "fix-lint" tox environment
2017-07-21 06:25:59 +02:00
Ronny Pfannschmidt
cbceef2008 Merge pull request #2598 from nicoddemus/filterwarnings-mark
Introduce filter-warnings mark
2017-07-21 06:23:18 +02:00
Bruno Oliveira
7341da1bc1 Introduce pytest.mark.filterwarnings 2017-07-20 22:31:49 -03:00
Bruno Oliveira
3c28a8ec1a Improve formatting/grammar of changelog entries 2017-07-20 22:12:15 -03:00
Bruno Oliveira
22f54784c2 Add "fix-lint" tox environment to fix linting errors 2017-07-20 22:12:15 -03:00
Bruno Oliveira
abb5d20841 Merge branch 'master' into features 2017-07-20 22:10:58 -03:00
Bruno Oliveira
da12c52347 Fix: do not load hypothesis during test_logging_initialized_in_test
A recent release seem to have added a "logging" import to the top-level,
which breaks test_logging_initialized_in_test
2017-07-20 21:43:24 -03:00
Floris Bruynooghe
9e3e58af60 Merge pull request #2594 from nicoddemus/fix-flake8-errors
Merge master into features and fix flake8 errors
2017-07-20 23:20:55 +02:00
Floris Bruynooghe
56e6b4b501 Merge pull request #2578 from Llandy3d/2375
Provides encoding attribute on CaptureIO
2017-07-20 23:14:14 +02:00
Bruno Oliveira
d44565f385 Merge remote-tracking branch 'upstream/master' into fix-flake8-errors 2017-07-19 17:57:30 -03:00
Bruno Oliveira
24da938321 Fix additional flake8 errors 2017-07-19 17:42:21 -03:00
Bruno Oliveira
26ee2355d9 Merge remote-tracking branch 'upstream/features' into fix-flake8-errors 2017-07-19 17:09:05 -03:00
Bruno Oliveira
c92760dca8 Merge branch 'fix-flake8-issues' 2017-07-19 17:03:36 -03:00
Bruno Oliveira
61d4345ea4 Merge pull request #2593 from hackebrot/extend-pytester-docs-for-testing-plugin-code
Extend pytester docs for testing plugin code
2017-07-19 16:40:00 -03:00
Raphael Pierzina
1ac02b8a3b Add plugin code 2017-07-19 20:15:27 +02:00
Raphael Pierzina
d06d97a7ac Remove unnecessary comma from docs 2017-07-19 19:42:33 +02:00
Raphael Pierzina
e73a2f7ad9 Add changelog entry changelog/971.doc 2017-07-19 19:38:21 +02:00
Raphael Pierzina
91b4b229aa Update documentation for testing plugin code 2017-07-19 19:38:21 +02:00
Raphael Pierzina
2840634c2c Fix typo and improve comment about cookiecutter-template 2017-07-19 19:38:21 +02:00
Florian Bruhin
eb79fa7825 Merge pull request #2590 from nicoddemus/current-test-var
Introduce new PYTEST_CURRENT_TEST environment variable
2017-07-19 15:56:32 +02:00
Bruno Oliveira
d7f182ac4f Remove SETUPTOOLS_SCM_PRETEND_VERSION during linting
It was needed because of check-manifest, but we no longer have a
MANIFEST file so it is not necessary
2017-07-19 10:02:13 -03:00
Bruno Oliveira
2d4f1f022e Introduce PYTEST_CURRENT_TEST environment variable
Fix #2583
2017-07-19 10:01:50 -03:00
Bruno Oliveira
2c03000b96 Merge pull request #2585 from RonnyPfannschmidt/fix-2573
remove the MARK_INFO_ATTRIBUTE warning until we can fix internal usage
2017-07-19 07:46:38 -03:00
Ronny Pfannschmidt
62556bada6 remove the MARK_INFO_ATTRIBUTE warning until we can fix internal usage
fixes #2573
2017-07-19 08:44:52 +02:00
Bruno Oliveira
637e566d05 Separate all options for running/selecting tests into sections 2017-07-18 22:50:32 -03:00
Bruno Oliveira
3a1c9c0e45 Clarify in the docs how PYTEST_ADDOPTS and addopts ini option work together 2017-07-18 15:37:01 -03:00
Ronny Pfannschmidt
0e559c978f Merge pull request #2587 from nicoddemus/remove-impl-file
Remove _pytest/impl file
2017-07-18 16:44:18 +02:00
Bruno Oliveira
bd96b0aabc Remove _pytest/impl file
The file apparently contains an early design document to what
has become @pytest.fixture and can be deleted
2017-07-18 09:47:06 -03:00
Bruno Oliveira
7b1870a94e Fix flake8 in features branch 2017-07-17 21:16:14 -03:00
Bruno Oliveira
4fd92ef9ba Merge branch 'fix-flake8-issues' into features 2017-07-17 21:05:39 -03:00
Bruno Oliveira
ac3f2207bb Merge pull request #2575 from MartinAltmayer/master
#2574 Options --fixtures and --fixtures-per-test keep indentation of docstrings
2017-07-17 18:06:56 -03:00
Andras Tim
b2a5ec3b94 updated meta 2017-07-17 02:16:51 +02:00
Andras Tim
b49e8baab3 Fixed E731 flake8 errors
do not assign a lambda expression, use a def
2017-07-17 01:44:23 +02:00
Andras Tim
15610289ac Fixed E712 flake8 errors
comparison to True should be ‘if cond is True:’ or ‘if cond:’
2017-07-17 01:44:23 +02:00
Andras Tim
5ae59279f4 Fixed E704 flake8 errors
multiple statements on one line (def)
2017-07-17 01:44:23 +02:00
Andras Tim
bf259d3c93 Fixed E702 flake8 errors
multiple statements on one line (semicolon)
2017-07-17 01:44:23 +02:00
Andras Tim
85141a419f Fixed E701 flake8 errors
multiple statements on one line (colon)
2017-07-17 01:44:23 +02:00
Andras Tim
7d2ceb7872 Fixed E501 flake8 errors
line too long (> 120 characters)
2017-07-17 01:44:23 +02:00
Andras Tim
b9e318866e Fixed E402 flake8 errors
module level import not at top of file
2017-07-17 01:44:23 +02:00
Andras Tim
45ac863069 Fixed E401 flake8 errors
multiple imports on one line
2017-07-17 01:44:23 +02:00
Andras Tim
7248b759e8 Fixed E303 flake8 errors
too many blank lines (3)
2017-07-17 01:44:23 +02:00
Andras Tim
b840622819 Fixed E302 flake8 errors
expected 2 blank lines, found 0
2017-07-17 01:44:23 +02:00
Andras Tim
17a21d540b Fixed E301 flake8 errors
expected 1 blank line, found 0
2017-07-17 01:44:23 +02:00
Andras Tim
9bad9b53d8 Fixed E293 flake8 errors 2017-07-17 01:44:23 +02:00
Andras Tim
4730c6d99d Fixed E272 flake8 errors
multiple spaces before keyword
2017-07-17 01:44:23 +02:00
Andras Tim
c9a081d1a3 Fixed E271 flake8 errors
multiple spaces after keyword
2017-07-17 01:44:22 +02:00
Andras Tim
195a816522 Fixed E265 flake8 errors
block comment should start with ‘# ‘
2017-07-17 01:44:22 +02:00
Andras Tim
eae8b41b07 Fixed E262 flake8 errors
inline comment should start with ‘# ‘
2017-07-17 01:44:22 +02:00
Andras Tim
8f3eb6dfc7 Fixed E261 flake8 errors
at least two spaces before inline comment
2017-07-17 01:44:22 +02:00
Andras Tim
b226454582 Fixed E251 flake8 errors
unexpected spaces around keyword / parameter equals
2017-07-17 01:44:22 +02:00
Andras Tim
4c24947785 Fixed E241 flake8 errors
multiple spaces after ‘,’
2017-07-17 01:44:22 +02:00
Andras Tim
617e510b6e Fixed E231 flake8 errors
missing whitespace after ‘,’, ‘;’, or ‘:’
2017-07-17 01:44:22 +02:00
Andras Tim
4b22f270a3 Fixed E226 flake8 errors
missing whitespace around arithmetic operator
2017-07-17 01:44:22 +02:00
Andras Tim
2e8caefcab Fixed E225 flake8 errors
missing whitespace around operator
2017-07-17 01:44:22 +02:00
Andras Tim
3fabc4d219 Fixed E222 flake8 errors
multiple spaces after operator
2017-07-17 01:44:22 +02:00
Andras Tim
f640e0cb04 Fixed E221 flake8 errors
multiple spaces before operator
2017-07-17 01:44:22 +02:00
Andras Tim
ebb6d0650b Fixed E203 flake8 errors
whitespace before ‘:’
2017-07-17 01:44:22 +02:00
Andras Tim
ba0a4d0b2e Fixed E202 flake8 errors
whitespace before ‘)’
2017-07-17 01:28:17 +02:00
Andras Tim
1ff54ba205 Fixed E201 flake8 errors
whitespace after ‘(‘
2017-07-17 01:28:17 +02:00
Andras Tim
df54bf0db5 Fixed E131 flake8 errors
continuation line unaligned for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
1c935db571 Fixed E129 flake8 errors
visually indented line with same indent as next logical line
2017-07-17 01:28:16 +02:00
Andras Tim
cf97159009 Fixed E128 flake8 errors
continuation line under-indented for visual indent
2017-07-17 01:28:16 +02:00
Andras Tim
57438f3efe Fixed E127 flake8 errors
continuation line over-indented for visual indent
2017-07-17 01:28:16 +02:00
Andras Tim
e855a79dd4 Fixed E126 flake8 errors
continuation line over-indented for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
92e2cd9c68 Fixed E125 flake8 errors
continuation line with same indent as next logical line
2017-07-17 01:28:16 +02:00
Andras Tim
051d76a63f Fixed E124 flake8 errors
closing bracket does not match visual indentation
2017-07-17 01:28:16 +02:00
Andras Tim
4b20b9d8d9 Fixed E123 flake8 errors
closing bracket does not match indentation of opening bracket’s line
2017-07-17 01:28:16 +02:00
Andras Tim
425665cf25 Fixed E122 flake8 errors
continuation line missing indentation or outdented
2017-07-17 01:28:16 +02:00
Andras Tim
0be97624b7 Fixed E121 flake8 errors
continuation line under-indented for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
64a4b9058c Fixed E113 flake8 errors
unexpected indentation
2017-07-17 01:28:16 +02:00
Andras Tim
8de49e8742 Fixed E111 flake8 errors
indentation is not a multiple of four
2017-07-17 01:28:16 +02:00
Andras Tim
6146ac97d9 Fixed E101 flake8 errors
indentation contains mixed spaces and tabs
2017-07-17 01:28:16 +02:00
Andras Tim
6af2abdb53 Fixed flake8 warnings
W191 indentation contains tabs
W292 no newline at end of file
W293 blank line contains whitespace
W391 blank line at end of file
2017-07-17 01:28:16 +02:00
Andras Tim
796ffa5123 reformatted tox.ini 2017-07-17 01:27:37 +02:00
Llandy Riveron Del Risco
ba9a76fdb3 Provides encoding attribute on CaptureIO
Fix #2375
2017-07-16 14:29:00 +02:00
Martin Altmayer
cc39f41c53 Add myself to AUTHORS as required by the PR help text. 2017-07-15 13:54:18 +02:00
Martin Altmayer
2a979797ef Add a changelog entry. 2017-07-15 13:43:59 +02:00
Martin Altmayer
e5169a026a #2574: --fixtures, --fixtures-per-test keep indentation of docstring 2017-07-15 13:33:11 +02:00
Ronny Pfannschmidt
3578f4e405 Merge pull request #2571 from ahartoto/master
Ensure final collected line doesn't include artifacts of previous write
2017-07-13 22:37:33 +02:00
Alex Hartoto
97fdc9a7fe Ensure final collected line doesn't include artifacts
We sometimes would see the following line:
collected 1 item s

just because previous write to the terminal includes number of
characters greater than 'collected 1 item'.
2017-07-13 12:11:20 -07:00
Ronny Pfannschmidt
771cedd3da Merge pull request #2567 from nicoddemus/add-report-section-docs
Add docs for Item.add_report_section in the docs
2017-07-13 10:55:03 +02:00
Ronny Pfannschmidt
81cec9f5e3 Merge pull request #2563 from pv/yield-warn-spam
Make YIELD_TEST warning less spammy
2017-07-13 10:45:46 +02:00
Ronny Pfannschmidt
1485a3a902 Merge pull request #2566 from jmsdvl/iss2518
Detect and warn/ignore local python installations
2017-07-13 10:44:04 +02:00
Ronny Pfannschmidt
f16c3b9568 Merge pull request #2569 from nicoddemus/backwards-sidebar
Add a link to our backwards compatibility policy to our side-bar
2017-07-13 10:43:39 +02:00
Bruno Oliveira
e6b9a81ccf Add a link to our backwards compatibility policy to our side-bar
It is important enough that it should be easier to find
2017-07-12 16:22:16 -03:00
John Still
67fca04050 update docs and note; add virtualenv collection tests 2017-07-11 23:14:38 -05:00
Bruno Oliveira
73b07e1439 Add docs for Item.add_report_section in the docs
Fix #2381
2017-07-11 21:45:27 -03:00
John Still
b32cfc88da use presence of activate script rather than sys.prefix to determine if a dir is a virtualenv 2017-07-11 14:32:09 -05:00
John Still
676c4f970d trim trailing ws 2017-07-11 13:31:11 -05:00
John Still
c2d49e39a2 add news item 2017-07-11 13:01:56 -05:00
John Still
89c73582ca ignore the active python installation unless told otherwise 2017-07-11 11:52:16 -05:00
Bruno Oliveira
d9aaab7ab2 Merge remote-tracking branch 'upstream/master' into features 2017-07-10 17:51:59 -03:00
Bruno Oliveira
9e0b19cce2 Merge pull request #2561 from jmsdvl/iss2533
Rename _escape_strings to _ascii_escaped
2017-07-10 17:49:44 -03:00
Bruno Oliveira
a87f6f84cc Merge pull request #2559 from RockBomber/features
cache_dir ini option for setting cache directory
2017-07-10 17:49:05 -03:00
Pauli Virtanen
8a7d98fed9 Make YIELD_TEST warning less spammy
Emit it only once per each generator, rather than for each generated
function. Also add information about which test caused it to be emitted.
2017-07-10 21:41:09 +02:00
John Still
cdd788085d add news fragment to changelog folder 2017-07-10 12:37:12 -05:00
John Still
80595115b0 replace all _escape_strings to _ascii_escaped 2017-07-10 12:32:27 -05:00
V.Kuznetsov
bd52eebab4 changelog for ini option cache_dir 2017-07-07 13:20:39 +03:00
V.Kuznetsov
91418eda3b docs for ini option cache_dir 2017-07-07 13:08:12 +03:00
V.Kuznetsov
7a9fc69435 tests for ini option cache_dir 2017-07-07 13:07:33 +03:00
V.Kuznetsov
f471eef661 ini option cache_dir 2017-07-07 13:07:06 +03:00
Ronny Pfannschmidt
ef62b86335 Merge pull request #2492 from kalekundert/features
Add support for numpy arrays (and dicts) to approx.
2017-07-06 11:46:51 +02:00
Ronny Pfannschmidt
7cd03d7611 Merge pull request #2554 from nicoddemus/pytest-configure-order
Clarify pytest_configure hook call order
2017-07-05 14:22:55 +02:00
Bruno Oliveira
3667086acc Clarify pytest_configure hook call order
Fix #2539
2017-07-05 07:50:59 -03:00
Ronny Pfannschmidt
db24a3b0fb Merge pull request #2552 from nicoddemus/strict-help-message
Change --strict help message to clarify it deals with unregistered markers
2017-07-05 06:14:25 +02:00
Bruno Oliveira
221f42c5ce Change --strict help message to clarify it deals with unregistered markers
Fix #2444
2017-07-04 20:14:57 -03:00
Kale Kundert
7a1a439049 Use cls instead of ApproxNumpyBase.
Slightly more general, probably doesn't make a difference.
2017-07-04 09:20:52 -07:00
Bruno Oliveira
b62aef3372 Merge branch 'master' into features 2017-07-04 12:44:58 -03:00
Kale Kundert
c111e9dac3 Avoid making multiple ApproxNumpy types. 2017-07-03 22:45:24 -07:00
Kale Kundert
8524a57075 Add "approx" to all the repr-strings. 2017-07-03 22:44:37 -07:00
Bruno Oliveira
b63f6770a1 Preparing release version 3.1.3 2017-07-03 23:29:13 +00:00
Bruno Oliveira
8a8687122d Add wheel to tasks/requirements.txt: required for package creation 2017-07-03 23:28:29 +00:00
Bruno Oliveira
7277fbdb20 Fix SMTP port in fixture docs
Also add timeout to avoid regen getting stuck due
to connection problems

Fix #2509
2017-07-03 20:21:41 -03:00
Bruno Oliveira
6908d93ba1 Merge pull request #2475 from ant31/master
[wip] Fix ignore_path condition
2017-07-03 18:57:17 -03:00
Bruno Oliveira
c578418791 Add changelog for triple leading '/' problem. 2017-07-03 12:39:17 -03:00
Ronny Pfannschmidt
0303d95a53 Merge pull request #2531 from waisbrot/staticmethods
Allow staticmethods to be detected as test functions
2017-06-29 16:18:55 +02:00
Nathaniel Waisbrot
9b9fede5be allow staticmethods to be detected as test functions
Allow a class method decorated `@staticmethod` to be collected as a test
function (if it meets the usual criteria).

This feature will not work in Python 2.6 -- static methods will still be
ignored there.
2017-06-29 07:44:36 -04:00
Ronny Pfannschmidt
9b51fc646c Merge pull request #2526 from nicoddemus/merge-master-into-features
Merge master into features
2017-06-24 11:25:35 +02:00
Ronny Pfannschmidt
6eeab45a8f Merge pull request #2525 from nicoddemus/deprecate-old-style
Add changelog entry explicitly deprecating old-style classes from pytest API
2017-06-24 11:24:53 +02:00
Antoine Legrand
16df4da1f7 Fix exclude_path check 2017-06-24 10:58:47 +02:00
Bruno Oliveira
3de93657bd Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2017-06-24 00:47:48 -03:00
Bruno Oliveira
1906f8c565 Merge pull request #2524 from RonnyPfannschmidt/fix-2427
introduce deprecation warnings for legacy parametersets, fixes #2427
2017-06-24 00:41:44 -03:00
Bruno Oliveira
655d44b413 Add changelog entry explicitly deprecating old-style classes from pytest API
Related to #2147
2017-06-24 00:37:40 -03:00
Ronny Pfannschmidt
0d0b01bded introduce deprecation warnings for legacy parametersets, fixes #2427 2017-06-23 22:06:49 +02:00
Bruno Oliveira
6e2b5a3f1b Merge pull request #2523 from RonnyPfannschmidt/vendoring-tasks
Vendoring tasks
2017-06-23 16:55:32 -03:00
Ronny Pfannschmidt
b3bf7fc496 add tasks for updating vendored libs 2017-06-23 21:26:45 +02:00
Ronny Pfannschmidt
bb659fcffe Merge pull request #2521 from nicoddemus/show-trivial-changelog
Show "trivial" category in CHANGELOG
2017-06-23 17:38:36 +02:00
Bruno Oliveira
6de19ab7ba Show "trivial" category in CHANGELOG
I think it might sense to display in the CHANGELOG internal or
trivial changes because they might trip users between releases.

For example, a note about an internal refactoring (like
moving a class between modules) is useful for a user
that has been using the internal API. Of course
we are not breaking anything because it was an internal API, but no
reason not to save time for users who did use it.
2017-06-23 12:33:50 -03:00
Bruno Oliveira
bab18e10eb Merge pull request #2517 from RonnyPfannschmidt/mark-expose-nontransfered
Mark expose nontransfered marks via pytestmark property
2017-06-23 10:15:39 -03:00
Ronny Pfannschmidt
8d5f2872d3 minor code style fix 2017-06-23 11:59:03 +02:00
Ronny Pfannschmidt
b0b6c355f7 fixup changelog, thanks Bruno 2017-06-23 11:09:16 +02:00
Ronny Pfannschmidt
23d016f114 address review comments
* enhance api for fetching marks off an object
* rename functions for storing marks
* enhance deprecation message for MarkInfo
2017-06-23 11:05:38 +02:00
Bruno Oliveira
22b7701431 Merge pull request #2480 from nicoddemus/issue-2469-deprecated-call-ctx
deprecated_call context manager captures warnings already raised
2017-06-22 10:53:28 -03:00
Ronny Pfannschmidt
1d926011a4 add deprecation warnings for using markinfo attributes 2017-06-22 15:12:50 +02:00
Bruno Oliveira
ff8dbd0ad8 Add tracebackhide to function call form of deprecated_call 2017-06-22 08:54:39 -03:00
Ronny Pfannschmidt
5e832017d5 Merge pull request #2487 from nicoddemus/recursion-error-2486
Fix internal error when trying to detect the start of a recursive traceback
2017-06-22 13:40:32 +02:00
Ronny Pfannschmidt
c791895c93 changelog addition 2017-06-22 10:50:46 +02:00
Ronny Pfannschmidt
19b12b22e7 store pristine marks on function.pytestmark
fixes #2516
2017-06-22 10:48:45 +02:00
Ronny Pfannschmidt
64ae6ae25d extract application of marks and legacy markinfos 2017-06-22 10:41:28 +02:00
Ronny Pfannschmidt
bdec2c8f9e move marker transfer to _pytest.mark 2017-06-22 08:45:10 +02:00
Ronny Pfannschmidt
4a62102b57 Merge pull request #2511 from nicoddemus/addfinalizer-docs
fixture docs: highlight difference between yield and addfinalizer methods
2017-06-22 07:59:44 +02:00
Bruno Oliveira
f2ba8d70b9 Fix typo and add suggestion from review 2017-06-21 09:06:52 -03:00
Bruno Oliveira
afe847ecdc fixture docs: highlight difference between yield and addfinalizer methods
Fix #2508
2017-06-20 23:43:34 -03:00
Kale Kundert
9597e674d9 Use sets to compare dictionary keys. 2017-06-16 08:25:13 -07:00
Kale Kundert
d6000e5ab1 Remove py36 from .travis.yml
I thought the file was just out of date, but adding py36 made Travis
complain "InterpreterNotFound: python3.6", so I guess it was correct as
it was.
2017-06-15 20:34:36 -07:00
Kale Kundert
4d02863b16 Remove a dict-comprehension.
Not compatible with python26.
2017-06-15 18:56:09 -07:00
Kale Kundert
5d2496862a Only test numpy with py27 and py35.
Travis was not successfully installing numpy with python<=2.6,
python<=3.3, or PyPy.  I decided that it didn't make sense to use numpy
for all the tests, so instead I made new testing environments
specifically for numpy.
2017-06-15 18:46:58 -07:00
Kale Kundert
50769557e8 Skip the numpy doctests.
They seem like more trouble that they're worth.
2017-06-15 14:53:27 -07:00
Kale Kundert
b41852c93b Use autofunction to document approx.
It used to be a class, but it's a function now.
2017-06-15 14:52:39 -07:00
Kale Kundert
8badb47db6 Implement suggestions from code review.
- Avoid importing numpy unless necessary.

- Mention numpy arrays and dictionaries in the docs.

- Add numpy to the list of tox dependencies.

- Don't unnecessarily copy arrays or allocate empty space for them.

- Use code from compat.py rather than writing py2/3 versions of things
  myself.

- Avoid reimplementing __repr__ for built-in types.

- Add an option to consider NaN == NaN, because sometimes people use NaN
  to mean "missing data".
2017-06-15 09:19:10 -07:00
Ronny Pfannschmidt
8c3c4307db Merge pull request #2503 from nicoddemus/remove-manifest-check
Remove MANIFEST.in and related lint check
2017-06-15 14:23:16 +02:00
Bruno Oliveira
731c35fcab Remove MANIFEST.in and related lint check
Because setuptools_scm already includes all version-controlled files
in an sdist, we don't need to maintain a MANIFEST.in file and anymore

See pytest-dev/pytest-xdist#161
2017-06-14 07:43:21 -04:00
Ronny Pfannschmidt
31b971d79d Merge pull request #2488 from nicoddemus/issue-links-changelog
Add issue links in the CHANGELOG entries
2017-06-14 10:58:59 +02:00
Ronny Pfannschmidt
4e57a39067 Merge pull request #2468 from nicoddemus/collection-report-2464
Fix incorrect "collected items" report when specifying tests on the command-line
2017-06-14 10:57:57 +02:00
Ronny Pfannschmidt
af0344e940 Merge pull request #2500 from nicoddemus/issue-2434-doctest-modules
Fix decode error in Python 2 for doctests in docstrings
2017-06-14 05:52:44 +02:00
Bruno Oliveira
97367cf773 Remove obsolete comment from rewrite.py
This was made obsolete by 021e843427
2017-06-13 23:12:33 -03:00
Bruno Oliveira
336cf3e1f5 Merge pull request #2496 from rmfitzpatrick/pytest2440_handle_subrequest_finalizer_exceptions
Handle exceptions in subrequest finalizers
2017-06-13 23:03:03 -03:00
Bruno Oliveira
4e4ebbef5a Improve test to ensure the expected function is re-raised 2017-06-13 20:16:48 -03:00
Bruno Oliveira
b09d60c60a Fix decode error in Python 2 for doctests in docstrings
Fix #2434
2017-06-13 19:41:34 -03:00
Raphael Pierzina
0908f40e43 Merge pull request #2499 from hackebrot/update-license-dates
Update copyright dates in LICENSE, README.rst and in the documentation
2017-06-13 17:18:08 +02:00
Raphael Pierzina
0e73724e58 Add changelog/2499.trivial 2017-06-13 15:15:52 +02:00
Raphael Pierzina
9970dea8c1 Update copyright date in doc pages 2017-06-13 15:00:52 +02:00
Raphael Pierzina
218af42325 Update copyright date in LICENSE and README.rst 2017-06-13 14:58:07 +02:00
Ronny Pfannschmidt
6fa7b16482 Merge pull request #2497 from pkch/firstresult
Docs: clarify when hooks stop after the first non-None result
2017-06-13 07:17:05 +02:00
Max Moroz
4a992bafdb Changelog 2017-06-12 19:55:30 -07:00
Max Moroz
21137cf8c5 Add firstresult=True to the hook docs 2017-06-12 19:45:35 -07:00
Ryan Fitzpatrick
5a856b6e29 handle and reraise subrequest finalizer exceptions 2017-06-12 21:26:42 -04:00
Kale Kundert
89292f08dc Add a changelog entry. 2017-06-11 19:51:21 -07:00
Kale Kundert
8c22aee256 Resolve merge conflict due to approx being moved. 2017-06-11 19:46:59 -07:00
Kale Kundert
9f3122fec6 Add support for numpy arrays (and dicts) to approx.
This fixes #1994.  It turned out to require a lot of refactoring because
subclassing numpy.ndarray was necessary to coerce python into calling
the right `__eq__` operator.
2017-06-11 19:27:41 -07:00
Bruno Oliveira
9bd8907716 Merge pull request #2489 from RonnyPfannschmidt/move-python-api
Move python api code to own file
2017-06-11 11:25:35 -03:00
Ronny Pfannschmidt
f8b2277413 changelog fragment 2017-06-11 14:17:40 +02:00
Ronny Pfannschmidt
6be57a3711 move python api helpers out of the python module
this separates exposed normal api from collection elements
2017-06-11 12:27:16 +02:00
Ronny Pfannschmidt
36251e0db4 move approx to own file 2017-06-11 12:15:30 +02:00
Bruno Oliveira
f0541b685b Improve CHANGELOG formatting a bit 2017-06-10 12:31:20 -03:00
Bruno Oliveira
536f1723ac Add issue links in the CHANGELOG entries
This unfortunately no longer supports multiple entries with the same text,
but this is worth the improved readability and navigation IMO
2017-06-10 12:25:40 -03:00
Bruno Oliveira
8bb589fc5d Fix internal error when trying to detect the start of a recursive traceback.
Fix #2486
2017-06-09 19:26:26 -03:00
Bruno Oliveira
467c526307 Merge remote-tracking branch 'upstream/master' into features 2017-06-09 18:33:46 -03:00
Bruno Oliveira
b2d7c26d80 Merge pull request #2483 from nicoddemus/release-3.1.2
Preparing release version 3.1.2
2017-06-09 08:18:43 -03:00
Bruno Oliveira
7cbf265bb5 Preparing release version 3.1.2 2017-06-08 17:37:42 -04:00
Ronny Pfannschmidt
917b9a8352 Merge pull request #2476 from nicoddemus/fix-2459-numpy-comparison
Fix internal error when a recursion error occurs and frames contain objects that can't be compared
2017-06-07 20:34:36 +02:00
Bruno Oliveira
2127a2378a Fix internal error with recursive tracebacks with that frames contain objects that can't be compared
Fix #2459
2017-06-07 14:40:13 -03:00
Ronny Pfannschmidt
d2db6626cf Merge pull request #2466 from nicoddemus/remove-unicode-warning
Remove UnicodeWarning from pytest warnings
2017-06-07 08:00:36 +02:00
Bruno Oliveira
620ba5971f deprecated_call context manager captures warnings already raised
Fix #2469
2017-06-06 22:40:04 -03:00
Bruno Oliveira
c67bf9d82a Merge remote-tracking branch 'upstream/master' into features 2017-06-06 20:45:23 -03:00
Bruno Oliveira
57e2ced969 Merge pull request #2473 from ApaDoctor/docs-fixes
docs: Create links for objects to show the api
2017-06-06 09:52:54 -03:00
Bruno Oliveira
80944e32ad Add CHANGELOG entry 2017-06-06 09:09:54 -03:00
ApaDoctor
54a90e9555 docs: Create links for objects to show the api 2017-06-06 01:10:32 +02:00
Bruno Oliveira
9d41eaedbf Issue UnicodeWarning only for non-ascii unicode
Fix #2463
2017-06-05 10:43:15 -03:00
Bruno Oliveira
46d157fe07 Fix collection report when collecting a single test item 2017-06-03 20:39:53 -03:00
Bruno Oliveira
87e4a28351 Fix incorrect collected items report when specifying tests on the command-line
Fix #2464
2017-06-03 20:39:53 -03:00
Bruno Oliveira
5ee9793c99 Fix CHANGELOG issue id
Fix #2467
2017-06-03 17:34:05 -03:00
Bruno Oliveira
1863b7c7b2 Merge pull request #2462 from segevfiner/py36-windowsconsoleio-workaround
[WIP] A workaround for Python 3.6 WindowsConsoleIO breaking with FDCapture
2017-06-03 15:11:36 -03:00
Segev Finer
01ed6dfc3b Added a changelog entry for the WindowsConsoleIO workaround 2017-06-02 12:38:31 +03:00
Segev Finer
59b3693988 Fixed wrong if in the WindowsConsoleIO workaround 2017-06-02 12:34:26 +03:00
Segev Finer
05796be21a A workaround for Python 3.6 WindowsConsoleIO breaking with FDCapture
Python 3.6 implemented unicode console handling for Windows. This works
by reading/writing to the raw console handle using
``{Read,Write}ConsoleW``.

The problem is that we are going to ``dup2`` over the stdio file
descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
handles used by Python to write to the console. Though there is still some
weirdness and the console handle seems to only be closed randomly and not
on the first call to ``CloseHandle``, or maybe it gets reopened with the
same handle value when we suspend capturing.

The workaround in this case will reopen stdio with a different fd which
also means a different handle by replicating the logic in
"Py_lifecycle.c:initstdio/create_stdio".

See https://github.com/pytest-dev/py/issues/103
2017-06-02 11:19:03 +03:00
Ronny Pfannschmidt
f826b23f58 Merge pull request #2458 from segevfiner/fix-required-options-help
Fix --help with required options
2017-06-02 08:39:19 +02:00
Segev Finer
9abff7f72f Add a docstring to HelpAction 2017-06-01 22:25:09 +03:00
Segev Finer
f74f14f038 Fix --help with required options
This works by adding an argparse Action that will raise an exception in
order to skip the rest of the argument parsing. This prevents argparse
from quitting due to missing required arguments, similar to the way that
the builtin argparse --help option is implemented by raising SystemExit.

Fixes: #1999
2017-06-01 21:29:50 +03:00
Bruno Oliveira
bcbad5b1af Merge pull request #2140 from pelme/issue2121
Handle python_files correctly in assertion rewrite
2017-06-01 07:55:42 -03:00
Ronny Pfannschmidt
5d785e415e Merge pull request #2454 from nicoddemus/xfail-docs
Make it clear that pytest.xfail stops the test
2017-06-01 08:46:49 +02:00
Bruno Oliveira
409d2f1d54 Make it clear that pytest.xfail stops the test
Also did a general review of the document to improve the flow

Fix #810
2017-05-31 19:54:01 -03:00
Bruno Oliveira
9adf513c4b Merge remote-tracking branch 'upstream/master' into features 2017-05-31 13:51:41 -03:00
Bruno Oliveira
cca4de20cf Merge pull request #2450 from nicoddemus/release-3.1.1
Release 3.1.1
2017-05-31 08:55:40 -03:00
Andreas Pelme
c98ad2a0a0 Install py 1.4.33 that contains the fnmatch py.std import fix. 2017-05-31 08:32:51 +02:00
Andreas Pelme
5de203195c Add changelog for #2121 2017-05-31 08:29:19 +02:00
Andreas Pelme
021e843427 Fixed #2121 Use py.paths fnmatch. This fixes an issue where
python_files handled properly when rewriting assertions.
2017-05-31 08:25:04 +02:00
Andreas Pelme
ac9c8fcdab Failing test for issue #2121 2017-05-31 08:25:04 +02:00
Florian Bruhin
3871810d1c Merge pull request #2451 from nicoddemus/update-release-howto
Update HOWTORELEASE
2017-05-31 07:45:33 +02:00
Bruno Oliveira
281fcd5a58 Update HOWTORELEASE
* Remove the CHANGELOG step now that it is automated;
* Overall clean-up and formatting, trying to make the steps more
  explicit;
2017-05-30 22:16:54 -03:00
Bruno Oliveira
2fd7626046 Preparing release version 3.1.1 2017-05-30 17:19:34 -04:00
Bruno Oliveira
0540d72c87 Add extra space between changelog items 2017-05-30 17:15:31 -04:00
Bruno Oliveira
1dee443c2b Merge pull request #2445 from nicoddemus/warnings-remove-filter
No longer override existing warning filters during warnings capture
2017-05-30 18:14:01 -03:00
Bruno Oliveira
32e2642233 No longer override existing warning filters during warnings capture
Fix #2430
2017-05-30 17:17:36 -03:00
Ronny Pfannschmidt
454426cba5 Merge pull request #2446 from nicoddemus/issue-2441
pytest.deprecated_call now captures PendingDeprecationWarning in context manager form
2017-05-30 20:09:37 +02:00
Bruno Oliveira
f96a1d89c5 pytest.deprecated_call now captures PendingDeprecationWarning in context manager form
Fix #2441
2017-05-30 12:52:18 -03:00
Bruno Oliveira
ee0844dbd8 Merge pull request #2431 from RonnyPfannschmidt/towncrier
initial addition of towncrier
2017-05-30 12:41:37 -03:00
Ronny Pfannschmidt
b74c626026 switch changelog management to towncrier 2017-05-30 15:54:15 +02:00
Ronny Pfannschmidt
4e6e29dbee Merge pull request #2442 from nicoddemus/merge-master-into-features
Merge master into features
2017-05-29 17:55:08 +02:00
Floris Bruynooghe
6117930642 Merge pull request #2438 from nicoddemus/issue-2434
Fix unicode issue while running doctests in Python 2
2017-05-29 13:36:07 +02:00
Floris Bruynooghe
7bb06b6dad Merge pull request #2439 from nicoddemus/warnings-docs
Warn that warning-capture can break existing suites in the docs and CHANGELOG
2017-05-29 13:34:39 +02:00
Bruno Oliveira
7950c26a8e Add Hui Wang to AUTHORS list 2017-05-26 08:09:29 -03:00
Bruno Oliveira
836dc451f4 Fix unicode issue while running doctests in Python 2
Fix #2434
2017-05-26 07:35:14 -03:00
Bruno Oliveira
8df3e55a31 Merge pull request #2437 from coldnight/master
Correct warnings that contains Unicode message
2017-05-26 07:24:27 -03:00
wanghui
53add4435f Add ChangeLog 2017-05-26 13:14:42 +08:00
wanghui
d7a5c5716f Add UnicodeWarning for unicode warnings in Python2 2017-05-26 13:12:02 +08:00
Bruno Oliveira
313a884459 Warn that warning-capture can break existing suites in the docs and CHANGELOG
Related to discussion in #2430
2017-05-25 21:19:08 -03:00
wanghui
c39689da41 Correct warnings with unicode message. 2017-05-25 17:59:42 +08:00
Bruno Oliveira
17f64704c2 Merge remote-tracking branch 'upstream/features' 2017-05-23 14:20:12 -03:00
Ronny Pfannschmidt
f9953fbe7c Merge pull request #2425 from nicoddemus/publish-task
Create task for publishing a release
2017-05-23 10:46:48 +02:00
Ronny Pfannschmidt
0ea80eb63c Merge pull request #2428 from The-Compiler/param-id-docs
Add docs for id= with pytest.param
2017-05-23 10:30:54 +02:00
Ronny Pfannschmidt
38ebf8dd10 Merge pull request #2429 from The-Compiler/regenschauer
Make --cache-show output deterministic
2017-05-23 10:30:39 +02:00
Ronny Pfannschmidt
04b1583d10 Merge pull request #2426 from The-Compiler/fix-changelog
Fix up 3.1 changelog
2017-05-23 08:18:24 +02:00
Florian Bruhin
d9b93674c3 Make --cache-show output deterministic
This makes sure things don't jump around in the regenerated docs.
2017-05-23 08:01:39 +02:00
Florian Bruhin
7d6bde2496 Add docs for id= with pytest.param 2017-05-23 07:57:34 +02:00
Florian Bruhin
bd065a12bb Fix up 3.1 changelog 2017-05-23 07:40:39 +02:00
Bruno Oliveira
f9df750025 Update 3.1.0 release date 2017-05-22 19:39:17 -03:00
Bruno Oliveira
d343f9497c Merge branch 'release-3.1' 2017-05-22 19:10:06 -03:00
Bruno Oliveira
5192191c38 Create task for publishing a release 2017-05-22 19:06:53 -03:00
Bruno Oliveira
69343310c6 Merge pull request #2422 from pytest-dev/refactor-config
Refactor config
2017-05-20 10:00:12 -03:00
Jason R. Coombs
c9c2c34b44 Remove unused parameter 2017-05-20 04:39:45 -04:00
Jason R. Coombs
9beeef970e Parse the filename in the generator expression 2017-05-20 04:38:30 -04:00
Jason R. Coombs
43aa037ebd Reindent 2017-05-20 04:38:30 -04:00
Jason R. Coombs
2abf2070f2 Collapse nested for loops into a generator expression 2017-05-20 04:38:30 -04:00
Jason R. Coombs
ce0ff0040f Reindent and add docstring 2017-05-20 04:38:27 -04:00
Jason R. Coombs
6d2e11b7d1 Extract method for _mark_plugins_for_rewrite 2017-05-20 04:18:41 -04:00
Bruno Oliveira
9b48613baa Preparing release version 3.1.0 2017-05-19 18:12:59 -04:00
Bruno Oliveira
0ff7f5d0c6 Prepare CHANGELOG for version 3.1.0 2017-05-19 19:10:09 -03:00
Bruno Oliveira
36cf89a2de Merge remote-tracking branch 'upstream/master' into features 2017-05-19 18:01:56 -04:00
Bruno Oliveira
3a4d37248d Merge pull request #2414 from nicoddemus/revert-new-style-classes
Revert refactor of old-style to new-style classes
2017-05-19 18:58:46 -03:00
Bruno Oliveira
637550b249 Merge pull request #2418 from axil/master
small fix in an example from the docs
2017-05-18 12:24:52 -03:00
Lev Maximov
598aefc686 small fix in an example from the docs 2017-05-18 21:18:09 +07:00
Bruno Oliveira
7af0e6bda1 Merge pull request #2415 from flub/training
Remove past training
2017-05-17 20:57:50 -03:00
Floris Bruynooghe
f7247dc99d Remove past training
Leaving the sidebar as an example for in the future.
2017-05-17 22:47:51 +01:00
Bruno Oliveira
d86c89e193 Revert refactor of old-style to new-style classes
As discussed in the mailing list, unfortunately this might break APIs
due to the subtle differences between new and old-style classes (see #2398).

This reverts commit d4afa1554b from PR #2179.
2017-05-17 18:20:51 -03:00
Florian Bruhin
e484f4760f Merge pull request #2412 from pytest-dev/pytest-book-ref
Add a reference to Python testing book to talks docs
2017-05-17 22:52:13 +02:00
Bruno Oliveira
70bcd1fb7b Add a reference to Python testing book to talks docs 2017-05-17 17:17:19 -03:00
Ronny Pfannschmidt
6f407ef308 Merge pull request #2411 from nicoddemus/automate-pre-release
Automate pre release steps
2017-05-17 14:36:33 +02:00
Bruno Oliveira
feab3ba70f Implement tasks to improve release automation 2017-05-15 23:44:03 -03:00
Bruno Oliveira
00e7ee532e Fix minor regendoc issues 2017-05-15 21:57:04 -03:00
Bruno Oliveira
fe49c78f32 Merge remote-tracking branch 'upstream/master' into release-3.1 2017-05-15 21:56:44 -03:00
Bruno Oliveira
3c41349fe1 Merge pull request #2406 from RonnyPfannschmidt/regendoc-reduce-version-noise
regendoc: reduce version noise by replacing minor/patch with placeholders
2017-05-15 19:40:45 -03:00
Bruno Oliveira
bd708068ab Merge branch 'features' into regendoc-reduce-version-noise 2017-05-14 14:34:07 -03:00
Ronny Pfannschmidt
783670b84e Merge pull request #2274 from dmand/feat/junitxml/suite-name-option
Add `junit_suite_name` ini option
2017-05-13 19:10:29 +02:00
Ronny Pfannschmidt
03753ca201 intermediate state after attempt with the plain env, DONT MERGE 2017-05-13 13:25:52 +02:00
Bruno Oliveira
456925b604 Merge pull request #2395 from RonnyPfannschmidt/consider-all-modules
Consider all modules
2017-05-12 18:25:12 -03:00
Bruno Oliveira
f39f416c5d Improve tests a bit
Use a normal function instead of a lambda
Parametrize test about suite name option
2017-05-12 17:52:50 -03:00
Ronny Pfannschmidt
d1e44d16e7 regenerate docs from the pytest env 2017-05-12 22:51:20 +02:00
Dmitri Pribysh
2ab8d12fe3 Update changelog and add usage info 2017-05-12 17:48:50 -03:00
Dmitri Pribysh
c9282f9e94 Transition to using ini option for suite name 2017-05-12 17:48:50 -03:00
Dmitri Pribysh
bcfa6264f1 Update AUTHORS list 2017-05-12 17:48:50 -03:00
Dmitri Pribysh
204db4d1e2 Update Changelog 2017-05-12 17:48:50 -03:00
Dmitri Pribysh
fe7d89f033 Add '--junit-suite-name' CLI option 2017-05-12 17:48:50 -03:00
Ronny Pfannschmidt
c765fa6d04 add regendoc normaliz for pytest --version 2017-05-12 22:38:50 +02:00
Ronny Pfannschmidt
f1c4e2c032 regendoc: reduce version noise by replacing minor/patch with placeholders 2017-05-12 22:17:40 +02:00
Ronny Pfannschmidt
a92e397011 add changelog for fixing #2391 2017-05-12 18:39:45 +02:00
Ronny Pfannschmidt
b6125d9a13 Merge pull request #2397 from nicoddemus/announce-task
Introduce a task to generate the announcement file for releases
2017-05-11 15:12:12 +02:00
Bruno Oliveira
66ba3c3aa4 Introduce a task to generate the announcement file for releases 2017-05-09 23:50:08 -03:00
Ronny Pfannschmidt
7ee2db23df Merge pull request #2396 from johndgiese/patch-1
Clarify opening paragraph of parameterization docs
2017-05-09 21:36:50 +02:00
David Giese
52c67af63c Clarify opening paragraph of parameterization docs
- Fix a few small grammar mistakes
- Rewrite a few sentences to make them shorter and easier to read
2017-05-09 10:02:08 -04:00
Ronny Pfannschmidt
8bcf88ec12 try to consider all modules after registration as plugin 2017-05-05 11:16:05 +02:00
Florian Bruhin
daca618012 Merge pull request #2389 from nicoddemus/merge-master-into-features
Merge master into features
2017-05-04 10:33:00 +02:00
Bruno Oliveira
f3b359f5b8 Merge remote-tracking branch 'upstream/master' into merge-master-into-features
# Conflicts:
#	_pytest/capture.py
#	_pytest/compat.py
#	_pytest/python.py
#	testing/python/collect.py
#	testing/test_mark.py
2017-05-03 19:04:53 -03:00
Ronny Pfannschmidt
3fc917a261 Merge pull request #2385 from nicoddemus/anaconda-badge
Add badge for anaconda package version
2017-05-03 07:07:52 +02:00
Ronny Pfannschmidt
814ea9d62c Merge pull request #2387 from nicoddemus/trial-errors
Fix py35-trial environment
2017-05-03 06:52:12 +02:00
Bruno Oliveira
630cca2fba Fix py35-trial environment
After updating to twisted 17.1.0, again the trial tests started to fail; didn't investigate too deep, decided to just
no longer delete "zope" modules when cleaning up after pytester because it seems more zope modules keep
global state that shouldn't be discarded
2017-05-02 21:05:42 -03:00
Florian Bruhin
bfd2563b3a Merge pull request #2386 from robin0371/show-correct-msg
Issue #2383 - AssertionError with wrong number of parametrize arguments
2017-04-29 14:57:01 +02:00
Vitaly Lashmanov
60b8339166 Issue #2383 - Show the correct error message when collect "parametrize" func with wrong args and add test for this case. 2017-04-29 14:32:09 +03:00
Bruno Oliveira
34f488757f Add badge for anaconda package version 2017-04-28 12:51:40 -03:00
Bruno Oliveira
cccb2cc92b Merge pull request #1834 from RonnyPfannschmidt/setuptools-scm-take-2
second take at setuptools_scm
2017-04-27 09:10:23 -03:00
Bruno Oliveira
d7d2249d99 Merge pull request #2378 from szuliq/patch-1
Update docs
2017-04-26 20:55:52 -03:00
Ronny Pfannschmidt
a280e43949 fix import error 2017-04-26 15:57:55 +02:00
Krzysztof Szularz
f0533194ed Update fixture.rst
Remove yet another not needed `request` argument in fixture definition.
2017-04-26 10:31:53 +02:00
Krzysztof Szularz
a9b44c4529 Update docs
Remove not needed `request` arg in order to simplify the example.
2017-04-25 16:35:19 +02:00
Ronny Pfannschmidt
e02cb6d7ce restore setuptools_scm write_to usage 2017-04-23 16:59:08 +02:00
Florian Bruhin
314d4afa57 Merge pull request #2367 from nicoddemus/py36-official-travis
Test against py36 official release for consistency with other python versions
2017-04-21 23:19:20 +02:00
Bruno Oliveira
25371ddbfd Merge pull request #2315 from RonnyPfannschmidt/namespace-hook
remove pytest internal usage of the namespace hook
2017-04-21 16:24:20 -03:00
Bruno Oliveira
80225ce72c Merge pull request #2374 from Kodiologist/getmodpath-file-ext
Try not to assume a module's file extension is .py
2017-04-21 16:23:25 -03:00
Ronny Pfannschmidt
4242bf6262 use unknown to specify unknown versions 2017-04-20 21:46:58 +02:00
Kodi Arfer
dcefb287fc Try not to assume a module's file extension is .py 2017-04-19 12:26:56 -07:00
Ronny Pfannschmidt
2cf422733c restore linting, drop _pytest._version for check-manifest 2017-04-19 20:25:53 +02:00
Ronny Pfannschmidt
c0a51f5662 restore check-manifst functionality 2017-04-19 20:12:38 +02:00
Ronny Pfannschmidt
31e6fe8f52 HOWTORELEASE.tst: use restructuredtext autonumbering 2017-04-19 19:40:42 +02:00
Ronny Pfannschmidt
c3aee4b1e6 second take at setuptools_scm
since setuptools 18.6 fixes the issues with develop installs

https://github.com/pypa/setuptools/blob/master/CHANGES.rst#186

https://github.com/pypa/setuptools/issues/439
2017-04-19 19:40:42 +02:00
Florian Bruhin
581b463b60 Merge pull request #2372 from nicoddemus/pytest-dont-rewrite-docs
Document PYTEST_DONT_REWRITE
2017-04-19 13:49:12 +02:00
Bruno Oliveira
90be44c812 Document PYTEST_DONT_REWRITE
Fixes #2203
2017-04-19 08:19:19 -03:00
Ronny Pfannschmidt
80cabca21a Merge pull request #2292 from nicoddemus/defer-hook-checking
Verify hooks after collection completes
2017-04-19 13:04:03 +02:00
Bruno Oliveira
cac82e71d8 Improve item.warn handling of fslocation parameter
Just pass fslocation forward and let the hook implementer decide what to do with the parameter
2017-04-13 19:01:14 -03:00
Bruno Oliveira
6e2bbe88b1 Test against py36 official release for consistency with other python versions 2017-04-13 17:54:56 -03:00
Bruno Oliveira
d9a2e70155 Change LsofFdLeakChecker to emit a warning instead of failing when detecting leaked FDs
Related to #2366
2017-04-13 17:34:48 -03:00
Bruno Oliveira
7dfdfa5813 Merge pull request #2359 from pytest-dev/fix-2343
Fix #2343: Replace version checks by constants.
2017-04-12 07:58:15 -03:00
Michael Howitz
7d4ac14a31 Fix #2343: Replace version checks by constants.
This way they do not have to be recomputed at runtime.
2017-04-12 08:18:09 +02:00
Bruno Oliveira
731776702d Fix hook name in LsofFdLeakChecker 2017-04-11 21:55:12 -03:00
Bruno Oliveira
0baf5e1499 Fix test that expected "unknown hook" error on stderr 2017-04-11 21:55:12 -03:00
Bruno Oliveira
83c508eea3 Verify hooks after collection completes
Fix #1821
2017-04-11 21:55:12 -03:00
Bruno Oliveira
78ac1bf5d1 Merge pull request #2350 from nicoddemus/future-imports-rewrite
Ensure rewritten modules don't inherit __future__ flags from pytest
2017-04-11 20:59:05 -03:00
Bruno Oliveira
02da278894 Merge pull request #2357 from ojii/lastfailed-failedfirst
Changed behavior if --lf and --ff are both used.
2017-04-11 19:05:01 -03:00
Bruno Oliveira
1125786e78 Improve --lf/--ff test as commented during review 2017-04-11 17:55:55 -03:00
Jonas Obrist
08d83a5c6a updated changelog and authors files 2017-04-10 17:50:18 +09:00
Jonas Obrist
0ab85e7a9c Changed behavior if --lf and --ff are both used.
When using both --last-failed/--lf and --failed-first/--ff pytest would
run all tests with failed tests first (as if --lf was not provied). This
patch changes it so that when using both flags, only the last failed
tests are run. This makes it easier to set --ff as the default behavior
via the config file and then selectively use --lf to only run the last
failed tests.
2017-04-10 17:46:13 +09:00
Bruno Oliveira
47a2a77cb4 Merge pull request #2354 from shobute/patch-1
Corrected documentation typo in fixtures.py
2017-04-07 14:31:36 -03:00
Ben Lloyd
21f1c2b03f Update fixtures.py
Corrected "or or" typo in docstring (and made indentation consistent).
2017-04-07 16:48:38 +01:00
Bruno Oliveira
8c69d5c939 Merge pull request #1940 from skylarjhdownes/master
adding documentation about pointing pytest at source files
2017-04-06 23:40:15 -03:00
Bruno Oliveira
f2300fbab2 Fix links in docs 2017-04-06 23:29:41 -03:00
Bruno Oliveira
45852386e5 Fix small typo in docs 2017-04-06 23:02:47 -03:00
Bruno Oliveira
5462697924 Small formatting fixes to nose.rst 2017-04-06 23:01:26 -03:00
Bruno Oliveira
639c592f31 Add missing link in CHANGELOG 2017-04-06 22:59:24 -03:00
Skylar Downes
f7caa56a6b moved documentation of conftest.py hack to nose.rst 2017-04-06 18:01:03 -07:00
Skylar Downes
3aa4fb62d6 Merge branch 'master' into master 2017-04-06 16:35:38 -07:00
Bruno Oliveira
c734a2d8d5 Merge pull request #2352 from pytest-dev/fix-search-docs
Pin sphinx to 1.4 when generating docs to workaround search issues on RTD
2017-04-06 18:41:30 -03:00
Bruno Oliveira
44a3db3dc6 Pin sphinx to 1.4 when generating docs to workaround search issues on RTD
Fix #2302
2017-04-06 18:09:01 -03:00
Bruno Oliveira
1b5f898dc5 Ensure rewritten modules don't inherit __future__ flags from pytest
In a recent refactoring we enabled all __future__ features in pytest
modules, but that has the unwanted side effect of propagating those
features to compile()'d modules inside assertion rewriting, unless
we pass dont_inherit=False to compile().
2017-04-06 15:00:36 -03:00
Ronny Pfannschmidt
83b241b449 Merge pull request #2347 from reutsharabani/features
added option to unicode plugin name
2017-04-06 18:16:20 +02:00
Bruno Oliveira
24ac923938 Add CHANGELOG entry 2017-04-06 12:42:17 -03:00
reut
333ce9849d added acceptance test for unicode plugin names 2017-04-06 10:54:47 +00:00
reut
417b54abed added option to unicode plugin name 2017-04-05 13:03:11 +00:00
Ronny Pfannschmidt
144d90932e Merge pull request #2337 from nicoddemus/2336-unicode-tb
Fix exception formatting while importing test modules
2017-03-30 09:12:14 +02:00
Bruno Oliveira
a542ed48a2 Convert using utf-8 instead of ascii in safe_str()
This way we don't lose information and the returned string is
ascii-compatible anyway
2017-03-29 15:18:41 -03:00
Bruno Oliveira
58ac4faf0c Fix exception formatting while importing test modules
Fix #2336
2017-03-29 14:43:13 -03:00
Ronny Pfannschmidt
afb1778294 put in a singular namespace hook to work around the strange issue 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
ebeba79be3 remove the namespace hook from mark after the param feature merge 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
6165939b0d fix rebase mistakes 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
efe03400d8 fixup nose/pytest plugins 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
c9ab421398 fix python2 only import loop failure 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
147bb8aea5 correct setting pytest.config 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
7cdefce656 fix up oversights 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
4d31ea8316 add a comment explaining the modimport tests 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
bb750a7945 add missed file 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
92f6ab1881 fix all singular internal module imports and add a test for them 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
809c36e1f6 add a changelog note for pytest_namespace 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
23bc9815c4 remove pytest_namespace from _pytest.fixtures 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
ae234786ea remove pytest_namespace from _pytest.python 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
99c8f2d403 remove pytest_namespace from _pytest.main 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
61f418a267 hollow out pytest_namespace in _pytest.fixtures 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
9b58d6eaca prepare a own pytest.collect fake module in oder to remove the nested builtin namespaces 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
839c936153 _pytest.mark: fix unconfigure after bad configure, still potential bug 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
7d797b7dbf add a note about the deprecation of the pytest_namespace hook 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
9b755f6ec6 remove pytest_namespace from _pytest.skipping 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
90788defb2 remove pytest_namespace from _pytest.mark and fix latent pytest nesting bug 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
6a02cdbb35 remove pytest_namespace from _pytest/runner.py 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
c74103f395 remove pytest_namespace from recwarn and fixture decorators 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
794fd5658c remove pytest_namespace from _pytest/debugging.py 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
fab9b993f8 remove pytest_namespace from _pytest.freeze_support 2017-03-28 11:45:06 +02:00
Ronny Pfannschmidt
5818e65cf3 remove pytest_namespace from _pytest/assertion 2017-03-28 11:35:29 +02:00
Ronny Pfannschmidt
2a130daae6 Merge pull request #2072 from nicoddemus/integrate-pytest-warnings
Integrate pytest warnings
2017-03-22 17:10:04 +01:00
Bruno Oliveira
0c1c2580d0 Add CHANGELOG entry 2017-03-22 12:40:31 -03:00
Bruno Oliveira
74b54ac0ec Fix errors related to warnings raised on pypy test environment
For some reason pypy raises this warning in the line that the catch_warnings block was added:

______________________________ ERROR collecting  ______________________________
C:\ProgramData\chocolatey\lib\python.pypy\tools\pypy2-v5.4.1-win32\lib-python\2.7\pkgutil.py:476: in find_loader
    loader = importer.find_module(fullname)
c:\pytest\.tox\pypy\site-packages\_pytest\assertion\rewrite.py:75: in find_module
    fd, fn, desc = imp.find_module(lastname, path)
<builtin>/?:3: in anonymous
    ???
E   ImportWarning: Not importing directory 'c:\users\bruno\appdata\local\temp\pytest-of-Bruno\pytest-3192\testdir\test_cmdline_python_package0' missing __init__.py
2017-03-21 22:32:41 -03:00
Bruno Oliveira
2c730743f1 Fix errors related to warnings raised by xdist
- pytester was creating a 'pexpect' directory to serve as temporary dir, but due to the fact that
   xdist adds the current directory to sys.path, that directory was being considered as candidate
   for import as a package. The directory is empty and a warning was being raised about
   it missing __init__ file, which is now turned into an error by our filterwarnings config
   in pytest.ini.

- Decided to play it safe and ignore any warnings during `pytest.importorskip`.

- pytest-xdist and execnet raise two warnings which should be fixed upstream:
   pytest-dev/pytest-xdist/issues/133
2017-03-21 22:17:07 -03:00
Bruno Oliveira
916d272c44 Fix test on linux 2017-03-20 23:44:50 -03:00
Bruno Oliveira
eabe3eed6b Add docs for the warnings functionality 2017-03-20 23:35:01 -03:00
Bruno Oliveira
fa56114115 Clean up warnings generated by pytest's own suite 2017-03-20 22:13:17 -03:00
Bruno Oliveira
d027f760c0 Avoid displaying the same warning multiple times for an item 2017-03-20 20:40:53 -03:00
Bruno Oliveira
3373e02eae Add __future__ imports to warnings module 2017-03-20 20:06:01 -03:00
Bruno Oliveira
9f85584656 Merge remote-tracking branch 'upstream/features' into integrate-pytest-warnings 2017-03-20 19:59:05 -03:00
Florian Bruhin
de8607deb2 Merge pull request #1921 from RonnyPfannschmidt/marked-value
introduce pytest.Marked as holder for marked parameter values
2017-03-20 10:54:12 +01:00
Ronny Pfannschmidt
e8a1b36c82 add changelog 2017-03-20 10:21:47 +01:00
Ronny Pfannschmidt
6cfe087261 Merge pull request #2320 from pawelad/2239/exit-code-docs
Added 'Possible exit codes' section to docs (#2239)
2017-03-20 06:22:23 +01:00
Paweł Adamczak
8b57aaf944 Added 'Paweł Adamczak' to AUTHORS 2017-03-19 18:38:41 +00:00
Paweł Adamczak
d58bc14645 Added 'Possible exit codes' section to docs (#2239) 2017-03-19 18:34:43 +00:00
Ronny Pfannschmidt
e368fb4b29 implement pytest.param
this allows a clear addition of parameterization parameters that carry along marks
instead of nesting multiple mark objects and destroying the possibility of creating
function valued parameters,
it just folders everything together into one object carrfying parameters, and the marks.
2017-03-17 16:53:43 +01:00
Ronny Pfannschmidt
a122ae85e9 Merge pull request #2316 from nicoddemus/add-future-imports
Add __future__ imports to all pytest modules
2017-03-17 13:41:46 +01:00
Bruno Oliveira
4d947077bb Fix test in py26 that expected a floor division error message 2017-03-16 23:07:03 -03:00
Bruno Oliveira
e5021dc9dc Replace py.builtin.print_() calls by builtin print() function 2017-03-16 22:46:51 -03:00
Bruno Oliveira
42a5d6bdfa Add __future__ imports to all pytest modules
This prevents silly errors from creeping in Python 2 when testing in Python 3
2017-03-16 22:45:40 -03:00
Bruno Oliveira
7684b3af7b Recommend using py36 for testing on CONTRIBUTING 2017-03-16 22:20:38 -03:00
Bruno Oliveira
78194093af Improve warning representation in terminal plugin and fix tests 2017-03-16 21:57:32 -03:00
Bruno Oliveira
be5db6fa22 Capture warnings around the entire runtestprotocol
This is necessary for the warnings plugin to play nice with the
recwarn fixture
2017-03-16 21:54:41 -03:00
Bruno Oliveira
0baed781fe Merge remote-tracking branch 'upstream/features' into integrate-pytest-warnings 2017-03-16 20:02:06 -03:00
Bruno Oliveira
337f891d78 Fixed tests 2017-03-16 20:01:47 -03:00
Bruno Oliveira
5482dfe0f3 Merge pull request #2303 from nicoddemus/recwarn-refactor
Refactor recwarn to use warnings.catch_warnings instead of custom code
2017-03-15 12:27:31 -03:00
Bruno Oliveira
75ec893d75 Merge pull request #2297 from nicoddemus/init-files-docs
Attempt to clarify the confusion regarding __init__ files and unique test names
2017-03-15 10:53:09 -03:00
Bruno Oliveira
76df77418d Merge pull request #2313 from nicoddemus/DontReadFromInput-exceptions
Dont read from input exceptions
2017-03-15 10:51:13 -03:00
Bruno Oliveira
55b891ddd0 Merge pull request #2312 from nicoddemus/merge-master-into-features-post-3.0.7
Merge master into features post 3.0.7
2017-03-14 23:26:43 -03:00
Bruno Oliveira
aad4946fb6 Move CHANGELOG entry for #2276 to 3.0.8 2017-03-14 18:58:28 -03:00
Xander Johnson
9062fbb9cc Add AUTHORS & CHANGELOG 2017-03-14 18:55:58 -03:00
Xander Johnson
dc6890709e Change ValueError to io.UnsupportedOperation in capture.py. Resolves issue #2276 2017-03-14 18:55:58 -03:00
Bruno Oliveira
272aba98e2 Mention the src layout as recommended practice 2017-03-14 18:39:02 -03:00
Bruno Oliveira
6c9011c12f Merge branch 'master' into merge-master-into-features-post-3.0.7 2017-03-14 18:10:23 -03:00
Bruno Oliveira
fa15ae7545 Post 3.0.7 release handling 2017-03-14 18:07:44 -03:00
Bruno Oliveira
5056d8cbe8 Merge pull request #2304 from nicoddemus/release-3.0.7
Release 3.0.7
2017-03-14 18:03:44 -03:00
Bruno Oliveira
4a9348324d Add more information to test-layout docs as discussed during PR 2017-03-14 07:59:01 -03:00
Ronny Pfannschmidt
5e52a4dda4 Merge pull request #2307 from nicoddemus/clarify-record-xml-property
Clarify that record_xml_property is experimental, not junitxml
2017-03-14 09:39:12 +01:00
Bruno Oliveira
92b49d246e Clarify that record_xml_property is experimental, not junitxml
Related to #2306
2017-03-13 23:04:44 -03:00
Bruno Oliveira
90c934e25e Include release 3.0.7 announce in index.rst 2017-03-13 18:59:15 -04:00
Bruno Oliveira
3c07072bfd Fix test_recwarn in Python 3.6
No longer test for implementation details of recwarn since warnings.catch_warnings has changed
significantly in 3.6.
2017-03-13 19:52:35 -03:00
Bruno Oliveira
d58780f9a6 Update regendoc 2017-03-13 18:41:20 -04:00
Bruno Oliveira
b1ab2ca963 Bump to version 3.0.7 and update CHANGELOG 2017-03-13 18:37:49 -04:00
Bruno Oliveira
22864b75ee Refactor recwarn to use warnings.catch_warnings instead of custom code
Since we dropped 2.5, we can now use warnings.catch_warnings to do the
"catch warnings" magic for us, simplifying the code a bit.
2017-03-13 19:28:36 -03:00
Bruno Oliveira
d1ea7c8cc8 Merge pull request #2301 from nicoddemus/merge-master-into-features
Merge master into features
2017-03-12 12:32:44 -03:00
Bruno Oliveira
1e0cf5ce4d Merge remote-tracking branch 'upstream/master' into merge-master-into-features
# Conflicts:
#	AUTHORS
#	CHANGELOG.rst
#	_pytest/pytester.py
2017-03-10 15:54:05 -03:00
Bruno Oliveira
581857aab6 Fix typo 2017-03-09 20:46:22 -03:00
Bruno Oliveira
841f731707 Attempt to clarify the confusion regarding __init__ files and unique test names
Fix #529
2017-03-09 20:41:33 -03:00
Ronny Pfannschmidt
906b40fbb2 Merge pull request #2289 from fbjorn/fix-trailing-whitespace-in-terminal
Fix trailing whitespace in terminal output
2017-03-05 22:45:46 +01:00
fbjorn
cee578e327 Fix trailing whitespace in terminal output 2017-03-05 23:20:55 +03:00
Florian Bruhin
29383d477d Merge pull request #2288 from nodakai/patch-1
assert.rst: typographical correction
2017-03-05 18:37:07 +01:00
NODA, Kai
e05ff0338a assert.rst: typographical correction 2017-03-06 01:01:55 +08:00
Bruno Oliveira
272afa9422 Display node ids and the warnings generated by it
The rationale of using node ids is that users can copy/paste it to run a chosen test
2017-03-04 20:53:42 -03:00
Bruno Oliveira
bddb922f7b Rename internal option to disable_warnings 2017-03-04 16:32:10 -03:00
Bruno Oliveira
de09023e45 Also capture warnings during setup/teardown 2017-03-04 16:15:03 -03:00
Bruno Oliveira
e24081bf76 Change warning output 2017-03-04 15:59:20 -03:00
Floris Bruynooghe
b28749eb92 Merge pull request #2284 from omerhadari/bugfix-unprintable-assertion-errors
Bugfix unprintable assertion errors
2017-03-04 15:28:11 +00:00
Ronny Pfannschmidt
07623e78ce Merge pull request #2286 from pytest-dev/disable-py37-travis
Allow py37-nightly to fail on Travis
2017-03-04 11:55:07 +01:00
Omer Hadari
dd25ae7f33 added in the correct alphabitcal order 2017-03-04 12:50:02 +02:00
Omer Hadari
02dc545311 added in the correct alphabitcal order 2017-03-04 12:49:00 +02:00
Bruno Oliveira
b61dcded37 Allow py37-nightly to fail on Travis
Related to #2285
2017-03-04 07:17:39 -03:00
Omer Hadari
f71467f5b1 added link to changelog 2017-03-04 10:55:59 +02:00
Omer Hadari
6aaf7ae18b added to authors and changelog 2017-03-04 10:32:14 +02:00
Omer Hadari
6a52fe1650 fixed internal error on unprintable raised AssertionErrors 2017-03-04 10:26:46 +02:00
Ronny Pfannschmidt
0c94f517a1 Merge pull request #2236 from KKoukiou/junitxml-change-schema
Change junitxml.py to produce results that comply with Junitxml schema
2017-03-02 17:26:11 +01:00
Katerina Koukiou
26e50f1162 junitxml: adjust junitxml output file to comply with JUnit xsd
Change XML file structure in the manner that failures in call and errors
in teardown in one test will appear under separate testcase elements in
the XML report.
2017-03-02 15:10:25 +01:00
Bruno Oliveira
5721d8aed1 Merge pull request #2249 from pfhayes/anydbmfix
Fix importing anydbm within pytest
2017-03-01 14:41:31 -03:00
Bruno Oliveira
3aac3d0a00 Merge branch 'master' into anydbmfix 2017-03-01 14:41:18 -03:00
Floris Bruynooghe
3e3f20380e Merge pull request #2277 from nicoddemus/yield-fixture-docs-2262
Improve docs for yield-fixture and with statement a bit
2017-03-01 11:32:18 -03:00
Bruno Oliveira
bb5f200ed7 Improve docs for yield-fixture and with statement a bit
Fix #2262
2017-02-25 12:06:51 -03:00
Bruno Oliveira
0f3d7acdc4 Merge pull request #2266 from asottile/capture_v2
Make capsys more like stdio streams in python3. Resolves #1407.
2017-02-24 20:18:55 -03:00
Anthony Sottile
8b598f00e9 Make pytester use pytest's capture implementation 2017-02-23 17:46:28 -08:00
Anthony Sottile
6ba3475448 Make capsys more like stdio streams in python3. Resolves #1407. 2017-02-23 17:46:27 -08:00
Ronny Pfannschmidt
0a89db2739 Merge pull request #2271 from KKoukiou/double-tag
junitxml: Fix double system-out tags per testcase
2017-02-22 18:20:17 +01:00
Katerina Koukiou
d3a6be4130 junitxml: Fix double system-out tags per testcase
In the xml report we now have two occurences for the system-out tag if
the testcase writes to stdout both on call and teardown and fails in
teardown.
This behaviour is against the xsd.
This patch makes sure that the system-out section exists only
once per testcase.
2017-02-22 16:39:20 +01:00
Bruno Oliveira
6680cb9100 Merge pull request #2264 from asottile/simplify_travis
Simplify travis.yml with tox environment variables
2017-02-19 18:07:13 -03:00
Floris Bruynooghe
44ad369c17 Merge pull request #2263 from nicoddemus/revert-pluggy
Revert subclassing explicitly from object introduced by accident in #2260
2017-02-19 16:18:38 -03:00
Anthony Sottile
5fd010c4c3 Simplify travis.yml with tox environment variables 2017-02-19 09:02:35 -08:00
Bruno Oliveira
82785fcd40 Use warnings.catch_warnings instead of WarningsRecorder
This has the benefical side-effect of not calling the original
warnings.showwarnings function, which in its original form
only writes the formatted warning to sys.stdout.

Calling the original warnings.showwarnings has the effect that nested WarningsRecorder all catch the warnings:

with WarningsRecorder() as rec1:
    with WarningsRecorder() as rec2:
        warnings.warn(UserWarning, 'some warning')

(both rec1 and rec2 sees the warning)

When running tests with `testdir`, the main pytest session would then see the warnings created by
the internal code being tested (if any), and the main pytest session would end up with warnings as well.
2017-02-18 13:08:14 -02:00
Bruno Oliveira
a7643a5fbe Merge branch 'features' into integrate-pytest-warnings 2017-02-18 11:03:15 -02:00
Bruno Oliveira
f1900bbea6 Revert subclassing explicitly from object introduced by accident in #2260 2017-02-18 10:34:41 -02:00
Bruno Oliveira
21a09f0895 Merge pull request #2261 from vmuriart/doc-report_header
Document pytest_report_header and conftest behavior
2017-02-17 12:22:28 -02:00
Victor Uriarte
a88017cf26 Add note documenting #2257 2017-02-16 23:00:55 -07:00
Victor Uriarte
58d7f4e048 Correct typo 2017-02-16 22:52:06 -07:00
Bruno Oliveira
abd6ad3751 Merge pull request #2260 from MichalTHEDUDE/feature/NewStyleClasses-2147
New-style classes implemented for python 2.7 - #2147
2017-02-16 19:05:09 -02:00
Michal Wajszczuk
fb0b90646e New-style classes implemented for python 2.7 - #2147 2017-02-16 20:28:17 +01:00
Bruno Oliveira
9c809f5ad0 Merge pull request #2255 from scop/spelling
Spelling fixes
2017-02-15 18:41:49 -02:00
Bruno Oliveira
27f12ed0c3 Merge pull request #2254 from scop/py36-escseq
Python 3.6 invalid escape sequence deprecation fixes
2017-02-15 18:41:06 -02:00
Ronny Pfannschmidt
0a26132232 Merge pull request #2241 from nicoddemus/override-python-files
--override-ini now correctly overrides some fundamental options like "python_files"
2017-02-15 21:40:50 +01:00
Bruno Oliveira
da828aac05 Merge pull request #2253 from The-Compiler/norecursedirs
Add venv to the default norecursedirs
2017-02-15 18:40:38 -02:00
Bruno Oliveira
8f98ac5ae8 Fix typo in docs "textures" -> "fixtures" 2017-02-15 13:15:53 -02:00
Ville Skyttä
ede4e9171f Spelling fixes 2017-02-15 17:00:58 +02:00
Ville Skyttä
eeb6603d71 Python 3.6 invalid escape sequence deprecation fixes 2017-02-15 16:54:53 +02:00
Ronny Pfannschmidt
231e2f9a90 Merge pull request #2252 from nicoddemus/fixture-visibility-docs
Improve pytest_plugins docs
2017-02-15 15:53:30 +01:00
Bruno Oliveira
c4d974460c Improve pytest_plugins docs
As discussed in #2246
2017-02-15 11:57:03 -02:00
Florian Bruhin
91c6bef77a Add venv to the default norecursedirs
venv (without a dot) is commonly used as a name for a virtualenv directory, and
we don't want to collect that.
2017-02-15 14:55:12 +01:00
Patrick Hayes
6b5566db66 Update changelog 2017-02-14 17:47:42 -08:00
Patrick Hayes
49289fed52 Fix docs 2017-02-14 17:21:20 -08:00
Patrick Hayes
00ec30353b Update docs as requested 2017-02-14 17:08:42 -08:00
Patrick Hayes
58ce3a9e8c Safer sys.modules delete 2017-02-14 16:54:32 -08:00
Floris Bruynooghe
427bf42a52 Merge pull request #2247 from flub/flub/training
Mention next training event.
2017-02-14 12:06:13 +00:00
Floris Bruynooghe
b536fb7ace Mention next training event. 2017-02-14 11:45:39 +00:00
Bruno Oliveira
9eb1d73951 --override-ini now correctly overrides some fundamental options like "python_files"
#2238
2017-02-08 23:03:33 -02:00
Bruno Oliveira
3d9c5cf19f Merge pull request #2225 from mbyt/allow_skipping_unittests_with_pdb_active
Allow to skip unittests if --pdb active
2017-02-08 21:23:51 -02:00
Bruno Oliveira
6a097aa0f1 Merge branch 'master' into allow_skipping_unittests_with_pdb_active 2017-02-08 20:30:14 -02:00
Bruno Oliveira
a4fb971c1f Merge pull request #2235 from bluetech/dont-execute-properties
ignore property errors when parsing fixure factories
2017-02-07 23:41:36 -02:00
Ran Benita
3a0a0c2df9 Ignore errors raised from descriptors when collecting fixtures
Descriptors (e.g. properties) such as in the added test case are
triggered during collection, executing arbitrary code which can raise.
Previously, such exceptions were propagated and failed the collection.
Now these exceptions are caught and the corresponding attributes are
silently ignored.

A better solution would be to completely skip access to all custom
descriptors, such that the offending code doesn't even trigger. However
I think this requires manually going through the instance and all of its
MRO for each and every attribute checking if it might be a proper
fixture before accessing it. So I took the easy route here.

In other words, putting something like this in your test class is still
a bad idea...:

    @property
    def innocent(self):
        os.system('rm -rf /')

Fixes #2234.
2017-02-07 14:27:34 +02:00
Ran Benita
87fb689ab1 Remove an unneeded except KeyboardInterrupt
KeyboardInterrupt is a subclass of BaseException, but not of Exception.
Hence if we remove this except, KeyboardInterrupts will still be raised
so the behavior stays the same.
2017-02-07 14:12:09 +02:00
Bruno Oliveira
ccf9877447 Merge pull request #2232 from vidartf/patch-1
Do not asssume `Item.obj` in 'skipping' plugin
2017-02-03 21:39:42 -02:00
Bruno Oliveira
a4d2a5785b Merge pull request #2142 from barneygale/xfail_without_condition_getglobals
'xfail' markers without a condition no longer rely on the underlying `Item` objects deriving from `PyobjMixin`
2017-02-03 16:09:47 -02:00
Vidar Tonaas Fauske
832c89dd5f Test for pytest.mark.xfail with non-Python Item 2017-02-03 17:13:05 +01:00
Vidar Tonaas Fauske
1a88a91c7a Update authors/history 2017-02-03 16:29:43 +01:00
Vidar Tonaas Fauske
bad261279c Do not asssume Item.obj in 'skipping' plugin
See #2231 for discussion.
2017-02-03 16:04:34 +01:00
Bruno Oliveira
208fae5bf0 Merge pull request #2227 from Kriechi/raises-info
add matching the error message to pytest.raises
2017-02-03 00:56:35 -02:00
Bruno Oliveira
abbff681ba Fix '{0}' format for py26 2017-02-02 18:01:44 -02:00
Thomas Kriechbaumer
43662ce789 allow error message matching in pytest.raises 2017-02-02 19:52:33 +01:00
mbyt
ad56cd8027 extract a _handle_skip method, secure PY2 branch 2017-02-02 05:01:51 +01:00
Bruno Oliveira
176c680e19 Merge branch 'master' into allow_skipping_unittests_with_pdb_active 2017-02-01 15:53:14 -02:00
Ronny Pfannschmidt
da5a3dba87 Merge pull request #2226 from nicoddemus/raise-stop-iteration
Replace 'raise StopIteration' usages in the code by 'return's in accordance to PEP-479
2017-02-01 14:50:54 +01:00
Bruno Oliveira
e1c5314d80 Replace 'raise StopIteration' usages in the code by 'return's in accordance to PEP-479
Fix #2160
2017-02-01 02:37:55 -02:00
mbyt
36b6f17727 fixing code-style, keep flake8 happy 2017-01-31 21:03:49 +01:00
mbyt
d1c725078a Allow to skip unittests if --pdb active
closes #2137
2017-01-31 04:47:31 +01:00
Ronny Pfannschmidt
3b47cb45e6 Merge pull request #2222 from RonnyPfannschmidt/features
merge master into features
2017-01-26 13:48:10 +01:00
Ronny Pfannschmidt
3f30c22894 fix changelog merge mistake 2017-01-26 13:03:29 +01:00
Ronny Pfannschmidt
713bdc1f9f merge master into features 2017-01-26 12:00:52 +01:00
Bruno Oliveira
0931fe2c89 Merge pull request #2221 from pytest-dev/ionelmc-patch-1
Discourage users from using unittest support somewhat
2017-01-25 21:38:44 -02:00
Bruno Oliveira
34e98bce0a Merge pull request #2198 from unsignedint/make-parametrize-enhancement
Enhancement to `make_parametrize_id`hook function
2017-01-25 21:37:18 -02:00
Ionel Cristian Mărieș
c8032a9bbb Fix reference. 2017-01-25 14:44:07 +02:00
Ionel Cristian Mărieș
d98d122e81 Discourage users from using this all the time. 2017-01-25 14:20:38 +02:00
Bruno Oliveira
beb77c1a38 Fix release date for 3.0.6 2017-01-23 13:51:12 -02:00
Ronny Pfannschmidt
d076e4158f Merge pull request #2216 from vmuriart/patch-1
Add py36 classifier & Add py37 to travis
2017-01-23 10:19:13 +01:00
Victor Uriarte
902fd2ff6a Add py37-nightly to travis 2017-01-22 17:20:15 -07:00
Victor Uriarte
839aa963a1 Add py36 identifier
and order AUTHORS
2017-01-22 17:13:17 -07:00
Ronny Pfannschmidt
400b0779f9 Merge pull request #2213 from RonnyPfannschmidt/release-3.0.6
Release 3.0.6
2017-01-22 22:21:53 +01:00
Ronny Pfannschmidt
c9f327dc87 bump version to next dev 2017-01-22 22:21:08 +01:00
Ronny Pfannschmidt
0e64cd045c take off author_email after pypi rejects 2017-01-22 22:14:54 +01:00
Ronny Pfannschmidt
22da561ae5 fix copy+paste error, its supposed to be 3.0.6 2017-01-22 18:44:30 +01:00
Ronny Pfannschmidt
449b88c640 rerun regendoc with correct install 2017-01-22 18:44:30 +01:00
Ronny Pfannschmidt
34b898b47e generate the release announcement 2017-01-22 18:44:30 +01:00
Ronny Pfannschmidt
01eaf9db51 fix the xfail docstring typo at the actual docstring + regendoc 2017-01-22 18:44:30 +01:00
Ronny Pfannschmidt
4d0c635252 regendoc 2017-01-22 18:44:30 +01:00
Ronny Pfannschmidt
55f21bd2b9 bump version 2017-01-22 18:44:30 +01:00
Bruno Oliveira
c39d846c1b Merge pull request #2215 from RonnyPfannschmidt/fix-doctesting/devpi-bug
fix devpi test for doctesting env
2017-01-22 13:02:03 -02:00
Ronny Pfannschmidt
403122281a fix devpi test for doctesting env
due to a devpi bug, we always get a sdist install which in turn triggers
the pytest issue #2042 / #726

going for pyargs and a changed folder, it should no longer happen
(and will be tested firther after rebasing the release branch)
2017-01-22 11:48:05 +01:00
Ravi Chandra
0e58c3fa80 updates for PR review #2198 2017-01-21 16:47:49 +13:00
Ravi Chandra
c848d0a771 Pass parameter name to make_parametrize_id hook function 2017-01-21 16:46:45 +13:00
Bruno Oliveira
15a3b57ec7 Merge pull request #2120 from RonnyPfannschmidt/fix-2118
fix #2118 - rework Node._getcustomclass and Node compat properties
2017-01-20 14:38:55 -02:00
Ronny Pfannschmidt
6a96b464ab update changelog as suggested 2017-01-20 15:26:59 +01:00
Ronny Pfannschmidt
7b4afd8946 remove unused import 2017-01-20 12:09:49 +01:00
Ronny Pfannschmidt
1a2d6388ac Merge pull request #2211 from nicoddemus/trial-envs
Fix pytester internal plugin to work correctly with latest versions of zope.interface
2017-01-20 12:08:12 +01:00
Bruno Oliveira
3766060893 Merge branch 'master' into trial-envs 2017-01-20 08:37:34 -02:00
Ronny Pfannschmidt
4082f4024a comment out compatproperty deprecations
todo: reenable in the features branch
2017-01-20 11:25:48 +01:00
Ronny Pfannschmidt
e0c48b4fe7 Merge pull request #2212 from nicoddemus/pytester-rewrite
Assert statements of the pytester plugin again benefit from assertion rewriting

fixes #1920
2017-01-20 10:04:12 +01:00
Bruno Oliveira
7b4368f3f4 Merge pull request #2184 from eli-b/parseoutcomes-explicit-failure
Fail assert_outcomes() on missing terminal report
2017-01-19 21:38:54 -02:00
Bruno Oliveira
88f7befabb Merge pull request #2209 from RonnyPfannschmidt/bugfix-2208/get_real_func_loop_limit
fixes #2208 by introducing a iteration limit
2017-01-19 21:35:47 -02:00
Bruno Oliveira
c477f09177 Assert statements of the pytester plugin again benefit from assertion rewriting
Fix #1920
2017-01-19 21:33:51 -02:00
Bruno Oliveira
2574da8d32 Fix pytester internal plugin to work correctly with latest versions of zope.interface
Fix #1989
2017-01-19 20:53:35 -02:00
Ronny Pfannschmidt
250597d468 get_real_func: use saferepr when formatting the error message 2017-01-19 13:05:58 +01:00
Ronny Pfannschmidt
123289a88e fixes #2208 by introducing a iteration limit 2017-01-19 11:38:15 +01:00
Ronny Pfannschmidt
d15724f74f Merge pull request #2204 from nicoddemus/linux-marker-doc
Fix marker example on "linux" platform
2017-01-18 09:34:34 +01:00
Bruno Oliveira
61fa91f3d0 Fix marker example on "linux" platform
I cheated a little and updated the example output by hand. 😁

Fix #2200
2017-01-17 21:09:04 -02:00
Ronny Pfannschmidt
125e89b7f8 Merge pull request #2194 from rjprins/remove-reinterp-from-docs
Remove mention of --assert=reinterp in documentation
2017-01-14 14:58:50 +01:00
Rutger Prins
46a9861d29 Remove mention of --assert=reinterp in documentation
Related to #1940
2017-01-13 22:35:37 +01:00
Bruno Oliveira
3dfdbaf490 Merge pull request #2186 from nicoddemus/pytest-plugins-env-rewrite
Consider plugins loaded by PYTEST_PLUGINS for assertion rewrite
2017-01-12 23:29:58 -02:00
Bruno Oliveira
7cd7c283dd Refactor plugin specs handling into an isolated function 2017-01-12 14:31:35 -02:00
Bruno Oliveira
043aadeaf2 Consider plugins loaded by PYTEST_PLUGINS for assertion rewrite
Fix #2185
2017-01-11 17:15:16 -02:00
Eli Boyarski
e18b2a427a Fail assert_outcomes() on missing terminal report
Currently if the terminal report of testdir.runpytest() is missing,
assert_outcomes() on its output fails because parseoutcomes()
returns an unexpected value (None).
It's better to fail parseoutcomes() directly.
2017-01-11 17:09:37 +02:00
Ronny Pfannschmidt
ff309b3584 Merge pull request #2182 from pombredanne/patch-1
Ensure the LICENSE is included in built wheels
2017-01-10 21:44:43 +01:00
Philippe Ombredanne
aa82db9fe2 Ensure the LICENSE is included in built wheels
Otherwise it is not included by default as wheels do not honor the MANIFEST.in

Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
2017-01-10 19:25:57 +01:00
Ronny Pfannschmidt
6c011f43e9 Merge pull request #2179 from mandeep/new-style-classes
Refactored old style classes to new style classes
2017-01-10 16:08:24 +01:00
mandeep
e412ea1d5a Added name to AUTHORS and change to CHANGELOG 2017-01-10 07:58:22 -06:00
mandeep
d4afa1554b Refactored old style classes to new style classes 2017-01-08 22:52:42 -06:00
Bruno Oliveira
64cb67b703 Merge pull request #2174 from nicoddemus/appveyor-py36
Test py36 on AppVeyor
2017-01-05 22:35:20 -02:00
Bruno Oliveira
7559400183 Add py36 to test on AppVeyor
Fix #2134
2017-01-05 19:18:03 -02:00
Bruno Oliveira
9477f598d8 Merge pull request #2173 from jeffwidman/patch-1
Switch monkeypatch fixture to yield syntax
2017-01-04 23:17:30 -02:00
Jeff Widman
6d81c684cc Switch monkeypatch fixture to yield syntax 2017-01-04 15:06:52 -08:00
Loïc Estève
3494dd06fe Remove duplicate target in rst 2017-01-03 10:57:19 -02:00
Loïc Estève
9e9547a9e4 Simplify condition 2017-01-03 10:57:19 -02:00
Loïc Estève
7930a8373d Newline for flake8 2017-01-03 10:57:19 -02:00
Loïc Estève
0bd8159b60 Add a test when multiple classes are specified in warns 2017-01-03 10:57:19 -02:00
Bruno Oliveira
56d1858ea2 Remove duplicate '@lesteve' link from CHANGELOG 2017-01-03 10:57:19 -02:00
Loïc Estève
6fd0394c63 pytest.warns checks for subclass relationship
rather than class equality. This makes it more similar to
pytest.raises.
2017-01-03 10:57:19 -02:00
Ronny Pfannschmidt
8f1450114f Merge pull request #2167 from fogo/parametrize-ids-silent-failure
No longer silently ignore errors in parametrize callable ids
2017-01-03 09:17:54 +01:00
Bruno Oliveira
b769e41d8f Merge pull request #2170 from gogoengie/broken-links
Fix broken links
2017-01-02 21:57:17 -02:00
Peter Heatwole
ef903460b1 Fix broken links 2017-01-02 14:19:49 -08:00
Barney Gale
df409a0c0e Fix CHANGELOG.rst 2017-01-02 22:01:40 +00:00
Barney Gale
8db9915374 Update AUTHORS, CHANGELOG 2017-01-02 22:01:04 +00:00
Barney Gale
3d18c9c1c6 'xfail' markers without a condition no longer rely on the underlying Item
deriving from `PyobjMixin`
2017-01-02 22:01:04 +00:00
Rafael Bertoldi
a9193a1531 No longer silently ignore errors in parametrize callable ids 2017-01-02 17:26:17 -02:00
Bruno Oliveira
78f03888f4 Merge pull request #2168 from jwilk/spelling
Fix typos
2017-01-01 20:08:08 -02:00
Jakub Wilk
03a7a2cd3e Fix typos 2016-12-31 19:01:15 +01:00
Bruno Oliveira
964ccb93bb Merge pull request #2163 from nicoddemus/merge-master-into-features
Merge master into features
2016-12-28 22:21:06 -02:00
Bruno Oliveira
402fbe503a Merge branch 'master' into merge-master-into-features 2016-12-27 23:31:26 -02:00
Bruno Oliveira
7592c5b491 Sort issues and user references in CHANGELOG 2016-12-27 23:20:34 -02:00
Bruno Oliveira
091148f843 Merge pull request #2136 from hroncok/i2132
Tests: Check for ModuleNotFoundError on Python 3.6+
2016-12-27 23:10:58 -02:00
Bruno Oliveira
718f0b0255 Merge pull request #2130 from malinoff/fix-2129
Use inspect to properly detect generators. Fixes #2129
2016-12-27 22:11:46 -02:00
Bruno Oliveira
b4295aa19e Merge pull request #2144 from sscherfke/patch-1
Explicitly add setuptools to install_requires
2016-12-27 22:08:39 -02:00
Bruno Oliveira
7d259401cd Merge pull request #2161 from nicoddemus/silence-trial-on-ci
Allow trial environments to fail on CI for now
2016-12-27 22:04:50 -02:00
Bruno Oliveira
515fb09995 Move module error compatibility code to _pytest.compat 2016-12-27 22:01:22 -02:00
Bruno Oliveira
088b742d40 Merge pull request #2149 from pelme/issue2148
Fix handling of ini configuration in subdirs when specifying ::
2016-12-27 21:54:14 -02:00
Miro Hrončok
6b24ce2a9d Test Python 3.6 on Travis CI
Partial fix for https://github.com/pytest-dev/pytest/issues/2134
2016-12-27 23:16:25 +01:00
Miro Hrončok
1680eeb3a3 Tests: Check for ModuleNotFoundError on Python 3.6+
Those tests originally checked for ImportError. Since Python 3.6
ModuleNotFoundError is raised in this context instead, the tests didn't work
as they are text based (so exception inheritance does not save the day).

Fixes https://github.com/pytest-dev/pytest/issues/2132
2016-12-27 23:15:12 +01:00
Andreas Pelme
0bb8a4a36d Fixed #2148 - parse directory names properly when args contains ::.
This commit also improves readbility in get_dirs_from_args by using self
documenting local functions.

get_dirs_from_args also now only returns directories that actually exists,
and not files to avoid confusion.

This commit also removes redundant checks in get_common_ancestor that
was already performed in get_dirs_from_args..
2016-12-27 15:28:56 +01:00
Bruno Oliveira
f7a1d369c3 Allow trial environments to fail on CI for now
While I agree this is *far* from ideal, IMHO It is better to ignore them for now otherwise we hamper contributors with unrelated errors.

We should fix this before the next release.

#1989
2016-12-26 21:47:50 -02:00
Bruno Oliveira
316406291d Merge pull request #2150 from lesteve/add-caught-warnings-info-when-warns-fail
Improve error message when pytest.warns fail
2016-12-26 21:42:15 -02:00
Bruno Oliveira
a27c824fd0 Merge pull request #2146 from lwm/minor-docs-fixup
Fix fixture/parametrize override example.
2016-12-26 20:13:00 -02:00
Bruno Oliveira
fc74eb332b Merge pull request #2128 from nicoddemus/pytest-m
Mention that Python also adds CWD to sys.path using python -m
2016-12-26 20:11:59 -02:00
Loïc Estève
bfada968d3 Update AUTHORS and CHANGELOG.rst
following contribution guidelines
2016-12-20 14:36:10 +01:00
Loïc Estève
c5f0b751f4 Improve error message when pytest.warns fail
The error message contains the expected type of warnings and the
warnings that were captured. Add tests.
2016-12-20 13:45:39 +01:00
Dmitry Malinovsky
a63b34c685 Switch to item fspath 2016-12-20 10:44:09 +06:00
Luke Murphy
f94189b48b Fix wrong fixture name. Closes #2143. 2016-12-20 01:48:32 +01:00
Stefan Scherfke
3f5edc705a Explicitly add setuptools to install_requires
Setuptools is used in `_pytest/config.py` but was not explicitly listed as requirement. This led to an error when creating a Conda package for pytest since setuptools is not necessarily installed by default in Conda envs.
2016-12-19 13:22:25 +01:00
Bruno Oliveira
caee5ce489 Avoid importing asyncio directly because that in turn initializes logging (#8) 2016-12-13 21:54:20 -02:00
Bruno Oliveira
1312b83866 Add CHANGELOG entry for #2129 2016-12-13 21:33:01 -02:00
Bruno Oliveira
45eb9b566c Move compat tests to a single file using testdir
This avoids having to resort to skipping modules in conftest.py file and avoids flake8 errors
2016-12-13 21:28:07 -02:00
Dmitry Malinovsky
3a59acf69f Use inspect to properly detect generators. Fixes #2129 2016-12-11 21:59:11 +06:00
Bruno Oliveira
81c9bdcd0b Mention that Python also adds CWD to sys.path using python -m 2016-12-10 16:55:04 -02:00
Dmitry Malinovsky
522d59e844 Use session.config.hook instead of ihook. Fixes #2124 2016-12-10 16:45:40 +06:00
Bruno Oliveira
da40bcf97f Merge pull request #2119 from nicoddemus/changelog-dates
Add release dates to CHANGELOG entries
2016-12-08 19:49:22 -02:00
Bruno Oliveira
a4a30ae4a2 Merge pull request #2123 from oscarh/documentation-issue-687
Also update yield teardown for Issue #687
2016-12-07 12:46:43 -02:00
Oscar Hellström
f42a954cb3 Also update yield teardown for Issue #687 2016-12-07 15:08:38 +01:00
Ronny Pfannschmidt
9c285dfc1d fix #2118 - rework Node._getcustomclass and Node compat properties 2016-12-06 09:13:25 +01:00
Bruno Oliveira
8afca5d0fa Add release dates to CHANGELOG entry
Fix #721
2016-12-05 17:21:01 -02:00
Bruno Oliveira
3a0a1d2de3 Bump version to 3.0.6.dev0 2016-12-05 12:51:00 -02:00
Bruno Oliveira
6a52afc8c9 Merge pull request #2116 from nicoddemus/release-3.0.5
Release 3.0.5
2016-12-05 12:42:32 -02:00
Bruno Oliveira
f592c7746a Regendocs for 3.0.5 2016-12-05 07:22:06 -05:00
Bruno Oliveira
31f114e51f Add release announcement for 3.0.5 2016-12-05 10:19:27 -02:00
Bruno Oliveira
833acb9d3c Finalize CHANGELOG for 3.0.5 2016-12-05 10:07:37 -02:00
Bruno Oliveira
0febd855e1 Bump version to 3.0.5 2016-12-05 10:06:58 -02:00
Ronny Pfannschmidt
3c81f83602 Merge pull request #2113 from nicoddemus/approx-repr-unicode
Use a simple ``+-`` ASCII string in the string representation of pytest.approx In Python 2
2016-12-05 13:02:07 +01:00
Bruno Oliveira
57c4489916 Use a simple `+-` ASCII string in the string representation of pytest.approx In Python 2
Fix #2111
2016-12-02 20:01:53 -02:00
Bruno Oliveira
5365f7c9ca Merge pull request #2112 from ismail-s/patch-1
Fix minor typo
2016-12-02 14:12:06 -02:00
Ismail
1f0401ab62 Fix minor typo 2016-12-02 15:09:38 +00:00
Bruno Oliveira
7480342710 Fix typo in docstring of register_assert_rewrite 2016-12-02 09:22:47 -02:00
Bruno Oliveira
db62f160e1 Merge pull request #2073 from nicoddemus/fix-hookproxy-cache
Remove hook proxy cache
2016-12-02 09:07:20 -02:00
Bruno Oliveira
81528ea81f Remove hook proxy cache
Fix #2016
2016-12-02 07:32:11 -02:00
Ronny Pfannschmidt
64193add91 Merge pull request #2110 from nicoddemus/rewrite-warning-pytest-plugins
Fix false-positive assert rewrite warnings when using 'pytest_plugins'
2016-12-01 20:33:38 +01:00
Bruno Oliveira
bc0f7e6243 Fix false-positive assert rewrite warnings when using 'pytest_plugins'
pytest would emit false positive warnings about assertion-rewrite when a
module appears multiple times in plugins which depend
on other plugins using the 'pytest_plugins' mechanism
2016-12-01 15:50:08 -02:00
Ronny Pfannschmidt
9ed3d76b51 Merge pull request #2108 from lwm/exp-2105
Add warning for incorrect passing args to `-o`.
2016-12-01 13:49:24 +01:00
Luke Murphy
c856537e71 Add warning for incorrect passing args to -o. 2016-12-01 13:20:42 +01:00
Ronny Pfannschmidt
e612619aea Merge pull request #2106 from nicoddemus/sys-modules-none
Remove support code for earlier Python 3 version in Source.compile
2016-12-01 10:22:32 +01:00
Bruno Oliveira
30f0152ae6 Remove unused import 2016-11-30 22:34:02 -02:00
Bruno Oliveira
f8d195253e Remove support code for earlier Python 3 version in Source.compile
This code leaves None in sys.modules as a side effect but is no longer needed in the Python 3 versions we support.

Fix #2103
2016-11-30 22:23:02 -02:00
Bruno Oliveira
669332b7e0 Merge pull request #2101 from wheerd/doctest-encoding
Added doctest encoding command line option
2016-11-30 17:43:42 -02:00
Florian Bruhin
9c224c94f0 Merge pull request #2099 from lwm/fixup-2007
Add missing `__test__` check for discovery
2016-11-30 20:23:40 +01:00
Wheerd
2edfc805af Added "versionadded" for doctest_encoding ini option to docs. 2016-11-30 18:01:00 +01:00
Luke Murphy
f5afd8cb54 Add missing __test__ check for test discovery. 2016-11-30 17:05:42 +01:00
Manuel Krebber
f8fef07b4c Fixed the tests for python 2.6 2016-11-30 14:19:07 +01:00
Manuel Krebber
b7fb9fac91 Fixed the documentation for doctest_encoding. 2016-11-30 14:17:54 +01:00
Manuel Krebber
1f62e5b5a0 Added CHANGELOG entry and myself to AUTHORS. 2016-11-30 11:50:11 +01:00
Manuel Krebber
c043bbb854 Changed the doctest_encoding option to an ini option.
Parametrized the tests for it.
2016-11-30 11:43:33 +01:00
Manuel Krebber
929912de29 Changed the tests to pass on python 2 as well. 2016-11-29 14:51:17 +01:00
Manuel Krebber
d254c6b0ae Added some tests for --docstring-encoding option. Added option to specify encoding for internal testdir._makefile() for the tests. 2016-11-29 12:29:16 +01:00
Manuel Krebber
ed977513ec Added a console option to specify the encoding to use for doctest files. Defaults to UTF-8. 2016-11-29 12:29:14 +01:00
Bruno Oliveira
8208a77a3e Merge pull request #2098 from DuncanBetts/master
Improved description of functionality for Issue #687
2016-11-28 23:05:27 -02:00
Bruno Oliveira
8b4da9d955 Merge pull request #2100 from blueyed/fix-help-grammar
minor: fix grammar with help for --setup-{only,show}
2016-11-28 20:11:56 -02:00
Bruno Oliveira
454d288138 Merge pull request #2097 from lwm/junitxml-warn
Add `type` validation.
2016-11-28 20:10:51 -02:00
Daniel Hahler
40cffacadc minor: fix grammar with help for --setup-{only,show} 2016-11-28 21:33:15 +01:00
Duncan Betts
6473c3d87e Improved description of functionality for Issue #687 2016-11-28 14:30:25 +00:00
Luke Murphy
4e1609b12e Add type validation.
Argparse driven argument type validation is added for the
`--junit-xml` and `--confcutdir` arguments.

The commit partially reverts #2080. Closes #2089.
2016-11-28 02:16:01 +01:00
Ronny Pfannschmidt
36eb5b36d1 Merge pull request #2096 from nicoddemus/merge-master-into-features
Merge master into features
2016-11-27 20:50:08 +01:00
Bruno Oliveira
b30a6d22c5 Merge branch 'master' into merge-master-into-features 2016-11-27 17:30:40 -02:00
Bruno Oliveira
0735d4549d Merge pull request #2088 from nmundar/issue-2034
Issue 2034
2016-11-27 16:12:31 -02:00
Ronny Pfannschmidt
f25ba4dd0b Merge pull request #2095 from nicoddemus/readme-example
Use "inc" instead of "func" in the snipped on README
2016-11-27 19:11:35 +01:00
Bruno Oliveira
788e394c93 Use "inc" instead of "func" in the snipped on README and doc index
"inc" reads better, also fixed the line separators so
they have the same size
2016-11-27 15:49:39 -02:00
Bruno Oliveira
2d7197926a Improve CHANGELOG entry for #2034 2016-11-27 14:26:30 -02:00
nmundar
0a30f072e6 Show name argment in compatproperty deprecation message 2016-11-27 14:24:55 -02:00
nmundar
0e6ad8e59f update CHANGELOG and AUTHORS 2016-11-27 14:24:55 -02:00
nmundar
b38fad4b82 Add compatproperty deprecation warning. 2016-11-27 14:24:55 -02:00
Bruno Oliveira
483754216f Merge pull request #2094 from nicoddemus/remove-setuptools-pin
Remove setuptools pin now that upstream has been fixed
2016-11-27 14:15:47 -02:00
Bruno Oliveira
1e97ea60f7 Merge pull request #2091 from lwm/move-595-along
Add test case for #595.
2016-11-27 14:13:15 -02:00
Ronny Pfannschmidt
58f28bf049 Merge pull request #2093 from lwm/add-docs-notes
Add documentation building note.
2016-11-27 10:13:28 +01:00
Bruno Oliveira
5566b3ccb6 Remove setuptools pin now that upstream has been fixed
Related to pypa/setuptools#861
2016-11-27 03:30:23 -02:00
Bruno Oliveira
8138d88da2 Merge pull request #2087 from lwm/ref-recwarns
Add `:ref:` targets to `recwarn.rst`.
2016-11-26 18:06:17 -02:00
Luke Murphy
0aa891543d Add documentation building note. 2016-11-26 18:57:51 +01:00
Luke Murphy
6c5475660a Add test case for #595.
This new test proves that reports do not capture stdout
by default when skipped.
2016-11-26 18:49:24 +01:00
Luke Murphy
1aa5bfea11 Add :ref: targets to recwarn.rst. 2016-11-26 18:41:38 +01:00
Bruno Oliveira
a6084ed797 Merge pull request #2090 from avojnovicDk/issue-1808
Clarify test discovery docs.
2016-11-26 15:28:06 -02:00
Bruno Oliveira
d05d19da78 Merge pull request #2085 from DuncanBetts/master
Add hint of Issue #478 to error text
2016-11-26 15:25:54 -02:00
Bruno Oliveira
fa4d5da4ca Merge pull request #2092 from nicoddemus/pin-setuptools
Pin setuptools to < 29 because of AppVeyor failures
2016-11-26 15:25:43 -02:00
Bruno Oliveira
6120570198 Pin setuptools to < 29 because of AppVeyor failures
Related to pypa/setuptools#861

Remove the pin when we have a new setuptools release
2016-11-26 14:49:31 -02:00
Ana Vojnovic
2e6a58ab69 Clarify test discovery docs. 2016-11-26 15:21:39 +01:00
Duncan Betts
c1b83cdeea Add hint of Issue #478 to error text 2016-11-26 10:47:15 +00:00
Bruno Oliveira
33796c8a13 Merge pull request #2084 from nicoddemus/fix-coveralls-appveyor
Only execute "coveralls" toxenv once on AppVeyor
2016-11-24 19:43:52 -02:00
Bruno Oliveira
8763590eef Only execute "coveralls" toxenv on master once
Just noticed that the "coveralls" env was being execute after each env.

This was introduced by mistake in #2056
2016-11-24 19:27:27 -02:00
Ronny Pfannschmidt
b3efd9aa59 Merge pull request #2083 from nicoddemus/approx-check-float
Fix error in approx's repr with complex numbers
2016-11-24 19:40:29 +01:00
Bruno Oliveira
33c0b06fdf Fix error in approx's repr with complex numbers
Fix #2082
2016-11-24 15:33:12 -02:00
Bruno Oliveira
38f7562c7c Merge pull request #2080 from nicoddemus/confcutdir-check
Show an error if --confcutdir is not a valid directory
2016-11-23 10:23:01 -02:00
Bruno Oliveira
629d8e9fd6 Show an error if --confcutdir is not a valid directory
Fixes #2078
2016-11-23 09:49:11 -02:00
Ronny Pfannschmidt
a5b5090c72 Merge pull request #2070 from nedbat/bug2038
Don't fail if imp can't find the source for a .pyc file. #2038
2016-11-22 17:45:29 +01:00
Bruno Oliveira
bd343ef757 Merge remote-tracking branch 'upstream/features' into integrate-pytest-warnings 2016-11-22 14:35:39 -02:00
Bruno Oliveira
5ce551e469 Merge pull request #2075 from pytest-dev/master
Merge master into features after fixing flake8 errors
2016-11-22 14:10:31 -02:00
Ronny Pfannschmidt
a3319ffe80 Merge pull request #2071 from nicoddemus/fix-flake8
Fix flake8 E305 and E306 errors
2016-11-22 13:22:11 +01:00
Bruno Oliveira
1e2b2af296 Merge pull request #2074 from nedbat/fix-double-spaces
Remove an accidental double space
2016-11-21 14:19:56 -02:00
Ned Batchelder
632c4d5daf Remove an accidental double space 2016-11-21 10:17:23 -05:00
Bruno Oliveira
26ca5a702e Add tests and integrated the original code into the core 2016-11-21 08:26:43 -02:00
Bruno Oliveira
1da1906483 Rename code to _pytest.warnings and delete old files from the repository 2016-11-21 07:38:12 -02:00
Bruno Oliveira
9db32aea48 Merge c:\pytest-warnings\ into integrate-pytest-warnings 2016-11-21 07:34:24 -02:00
Bruno Oliveira
e31421a5d2 Moving all stuff to a subdirectory to try to retain history 2016-11-21 07:27:26 -02:00
Bruno Oliveira
984d4ce5ec Fix test_excinfo_getstatement that broke because of whitespace changes 2016-11-20 19:12:42 -02:00
Bruno Oliveira
1eb5a690d4 Fix flake8 E305 and E306 errors
These errors started to appear with flake8-3.1.1, while they don't appear with
version 3.1.0 (weird).
2016-11-20 18:59:15 -02:00
Ned Batchelder
06bb61bbe3 Don't fail if imp can't find the source for a .pyc file. #2038 2016-11-20 13:09:32 -05:00
Bruno Oliveira
cbf261c74e Merge pull request #2065 from sebastinas/spelling2
Fix spelling mistakes
2016-11-15 22:51:49 -02:00
Sebastian Ramacher
0ba930a11d Fix spelling mistakes
Signed-off-by: Sebastian Ramacher <sramacher@debian.org>
2016-11-15 23:05:58 +01:00
Floris Bruynooghe
75740337d1 Merge pull request #2060 from pytest-dev/master
Merge master into features due to recent CI updates
2016-11-13 18:18:38 -08:00
Bruno Oliveira
6876ba9ba6 Merge pull request #1995 from mattduck/feat/restructure-assert-truncation
Restructure truncation of assertion messages
2016-11-13 19:07:35 -02:00
Bruno Oliveira
73d481552d Merge pull request #2058 from idlesign/patch-1
Docs: Added pytest promotional talk in Russian
2016-11-13 11:06:35 -02:00
Igor Starikov
50328f47db Docs: Added pytest promotional talk in Russian 2016-11-13 18:04:39 +07:00
Bruno Oliveira
f0e0250cd5 Merge pull request #2056 from nicoddemus/appveyor-list
Use one job for each tox env on AppVeyor
2016-11-12 22:15:41 -02:00
Bruno Oliveira
ec69514eb2 Only install pypy on AppVeyor for "pypy" tox-env 2016-11-12 20:20:59 -02:00
Bruno Oliveira
c169c883d3 Use one job for each tox env on AppVeyor
Some time ago when we first added support for testing pytest on AppVeyor,
jobs in a build would not start immediately one after the other, as if AppVeyor
would schedule jobs from other builds (projects) in its VMs. So it made sense
at the time to reduce the number of jobs.

I have noticed in other projects that this behavior has changed, and jobs
in a build now start one after the other. Having a separate list then improves
visibility when the build fails, because we can see at a glance the failing(s)
tox environment(s).
2016-11-12 12:49:12 -02:00
Bruno Oliveira
5185f2a6ae Merge pull request #2053 from nicoddemus/check-manifest-script
Use a wrapper script to bypass check-manifest if not under git
2016-11-12 12:12:54 -02:00
Bruno Oliveira
351395b7ea Use a wrapper script to bypass check-manifest if not under git
Related to comment in #2051
2016-11-12 11:39:41 -02:00
Ronny Pfannschmidt
0fab78ee8f Merge pull request #2054 from nicoddemus/merge-master-features
Merge master into features after 3.0.4 release
2016-11-11 22:39:13 +01:00
Ronny Pfannschmidt
71b68334e2 Merge pull request #2055 from nicoddemus/error-matrix-for-failing-jobs
Allow problematic jobs to fail instead of commenting them out
2016-11-11 22:38:33 +01:00
Bruno Oliveira
98caeedd9e Allow failure of pypy on AppVeyor
Related to #1963
2016-11-11 19:26:58 -02:00
Bruno Oliveira
1519b38af0 Allow failure of py35-trial on Travis
Related to #1989
2016-11-11 19:26:54 -02:00
Bruno Oliveira
efc54b2e56 Merge branch 'master' into merge-master-features 2016-11-11 18:56:53 -02:00
Bruno Oliveira
3e01e83390 Bump version to 3.0.5.dev 2016-11-11 18:20:34 -02:00
Bruno Oliveira
ad4ef4f583 Merge pull request #2050 from nicoddemus/release-3.0.4
Release 3.0.4
2016-11-11 18:11:49 -02:00
Ronny Pfannschmidt
5717c71179 Merge pull request #2052 from nicoddemus/lint-readme
Check README.rst with rst-lint
2016-11-11 18:04:50 +01:00
Ronny Pfannschmidt
6c8c1da428 add pygments dependency because of rst-lint 2016-11-11 14:25:53 -02:00
Bruno Oliveira
b8c6f13b37 Check README.rst with rst-lint 2016-11-11 12:56:07 -02:00
Bruno Oliveira
8e0f7d3793 Merge pull request #2051 from nicoddemus/check-manifest
Check manifest
2016-11-10 22:58:48 -02:00
Bruno Oliveira
aaa547e763 Add some recursive-exclude related to hypothesis and freeze 2016-11-10 08:48:56 -02:00
Bruno Oliveira
26b1519534 Add keywords to setup.py as suggested by pyroma
Related to #1
2016-11-09 20:47:14 -02:00
Bruno Oliveira
84d7068723 Add "check-manifest" to linting and remove unused scripts from root
Fix #1
2016-11-09 20:42:28 -02:00
Bruno Oliveira
ab274299fe Regen doc for 3.0.4 2016-11-09 16:44:58 -05:00
Bruno Oliveira
ff72db2f1a Version bump to 3.0.4, CHANGELOG, announcement 2016-11-09 19:38:11 -02:00
Bruno Oliveira
fc304b8b44 Merge pull request #2006 from MSeifert04/fix-1965
Fix memory leak with pytest.raises by using weakref
2016-11-09 19:19:40 -02:00
Bruno Oliveira
1130b9f742 Fix the stubborn test about cyclic references left by pytest.raises
In Python 2, a context manager's __exit__() leaves sys.exc_info with the exception values even when it was supposed
to suppress the exception, so we explicitly call sys.exc_clear() which removes the traceback and allow the object
to be released.

Also updated the test to not depend on the immediate destruction of the object but instead to ensure it is not being
tracked as a cyclic reference.

Fix #1965
2016-11-08 22:20:27 -02:00
Michael Seifert
552c7d4286 added test (thanks @nicoddemus) and added links in Changelog 2016-11-08 22:13:02 -02:00
Michael Seifert
1e5b21cd61 Fix memory leak with pytest.raises by using weakref 2016-11-08 22:12:23 -02:00
Bruno Oliveira
0b94c43bac Merge pull request #2046 from d-b-w/clean-up-unittest-issue1649
Clean up unittest TestCase objects after tests are complete (#1649).
2016-11-08 15:23:46 -02:00
Dan Wandschneider
e46e653794 Clean up unittest TestCase objects after tests are complete (#1649).
Fix #1649

Users of unittest style TestCases will create expensive objects
in setUp. We should clean up TestCase instances that are lying
around so that they don't fill up memory.
2016-11-07 18:32:56 -08:00
Ronny Pfannschmidt
07af307e4a Merge pull request #2045 from manueljacob/normalized-version
Change version to be in normal form according to PEP 440.
2016-11-06 12:30:22 +01:00
Manuel Jacob
a190ad27f2 Change version to be in normal form according to PEP 440.
The version is changed from 3.0.4.dev to 3.0.4.dev0.  Note that
according to PEP 440 these are considered equivalent, but 3.0.4.dev0 is
the normal form.

This standard was followed when the version was set to 3.0.3.dev0 in
commit ee284ec5, but in commit a87b1c79 the version was set to
3.0.4.dev, leaving the development number implicit again.
2016-11-06 09:00:04 +01:00
Bruno Oliveira
f331e8f576 Merge pull request #2028 from nicoddemus/empty-tracebacks
Properly handle exceptions in multiprocessing tasks
2016-11-03 12:18:38 -02:00
Bruno Oliveira
006a901b86 Properly handle exceptions in multiprocessing tasks
Fix #1984
2016-11-03 10:48:43 -02:00
Ronny Pfannschmidt
45b21fa9b0 Merge pull request #2041 from gdyuldin/fix_xuint_teardown
Fix teardown error message in generated xUnit XML
2016-11-02 15:24:53 +01:00
Georgy Dyuldin
e2bb4f893b Fix teardown error message in generated xUnit XML
It was "test setup failure" even error happens on test teardown.
2016-11-02 15:50:32 +03:00
Bruno Oliveira
d49e9e5562 Merge pull request #2014 from RonnyPfannschmidt/warning-record-namedtuple
turn RecordedWarning into a namedtuple
2016-11-01 19:24:44 -02:00
Bruno Oliveira
e3544553b7 Merge pull request #2030 from matclab/fix/442
Report teardown output on test failure
2016-10-31 13:50:50 -02:00
Ronny Pfannschmidt
1e6ed2a25a Merge pull request #2033 from The-Compiler/workshop
Update "Next Open Trainings"
2016-10-31 07:52:42 +01:00
Florian Bruhin
382fa231a1 Update "Next Open Trainings" 2016-10-31 06:49:06 +01:00
Mathieu Clabaut
6f93ffb5d4 Report teardown output on test failure
Until now, teardown stdout/stderr output was not reported upon test failure.
However such output is sometime necessary to understand the failure.

fix #442
2016-10-30 09:52:46 +01:00
Ronny Pfannschmidt
b3c337db00 add changelog entry and documentation note about RecordedWarning 2016-10-24 15:28:35 +02:00
Ronny Pfannschmidt
e9668d75b8 turn RecordedWarning into a namedtuple
fixes #2013
2016-10-24 15:23:53 +02:00
Ronny Pfannschmidt
377e649e61 local merge of #1967 - Change exception raised by capture.DontReadFromInput.fileno() 2016-10-24 12:47:55 +02:00
Ronny Pfannschmidt
35d154f580 Merge pull request #2011 from nicoddemus/false-rewrite-warnings
Fix false-positive warnings from assertion rewrite hook
2016-10-24 12:19:23 +02:00
Ronny Pfannschmidt
4e9c633185 Merge pull request #2021 from nicoddemus/doctest-modules-ci
Re-enable docstring testing of _pytest modules on CI
2016-10-24 12:19:05 +02:00
Florian Schulze
6ec0c3f369 Bump. 2016-10-24 12:10:49 +02:00
Florian Schulze
ce138060ac Prepare pytest-warnings 0.2.0. 2016-10-24 12:09:49 +02:00
Florian Schulze
f229b573fa Bump version, add changelog entry and move stuff around for added coverage reporting. 2016-10-24 12:08:21 +02:00
Florian Schulze
28621b0510 Merge pull request #2 from Carreau/filter-regex
Add a warning option which does not escape its arguments.
2016-10-24 11:51:15 +02:00
Matthias Bussonnier
bc94a51a96 Add a warning option which does not escape its arguments.
This is useful when to use regular expressions, like for example ignore
a bunch of dynamic messages
    --filterwarnigns 'ignore:Please use assert.* instead.:'

Or ignore all the warnings in a sub-backage
    --filterwarnigns 'ignore:::package.submodule.*'

This is also available in the ini file as the filterwarnigns options
2016-10-22 09:58:13 -07:00
Bruno Oliveira
7f95ea31d5 Merge pull request #2024 from jaraco/issue-2022
Restore pexpect tests on macOS. Fixes #2022.
2016-10-21 17:17:14 -02:00
Jason R. Coombs
f2c01c5407 Restore pexpect tests and bypass isalive/wait on macOS. Ref #2022. 2016-10-21 12:36:42 -04:00
Jason R. Coombs
60a347aeb5 Also use flush where wait was called unconditionally 2016-10-21 12:11:35 -04:00
Jason R. Coombs
11ec96a927 Extract child flush as a staticmethod 2016-10-21 12:10:35 -04:00
Bruno Oliveira
37dcdfbc58 Re-enable docstring testing of _pytest modules on CI
* Fix doctests
* List one env per line in tox.ini
* "doctesting" tox env now also tests docstrings using doctest
2016-10-21 08:55:53 -02:00
Bruno Oliveira
9d00615bbf Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2016-10-20 21:51:42 -02:00
Bruno Oliveira
2a2b8cee09 Fix false-positive warnings from assertion rewrite hook
Fix #2005
2016-10-20 21:40:57 -02:00
Ronny Pfannschmidt
82fb63ca2d Merge pull request #2019 from nicoddemus/fix-metavar-pair-help
Fix cmdline help message for custom options with two or more metavars
2016-10-21 01:36:24 +02:00
Bruno Oliveira
620b384b69 Fix cmdline help message for custom options with two or more metavars
Fix #2004
2016-10-20 20:34:39 -02:00
Ronny Pfannschmidt
5cbfefbba0 Merge pull request #2018 from nicoddemus/disable-py35-trial
Disable py35-trial while #1989 is not fixed
2016-10-20 23:18:48 +02:00
Bruno Oliveira
95007ddeca Disable py35-trial while #1989 is not fixed 2016-10-20 18:56:54 -02:00
Ronny Pfannschmidt
995e60efbf Merge pull request #2017 from nicoddemus/pytest-main-args-docs
Remove example of obsolete pytest.main usage with string
2016-10-20 18:29:54 +02:00
Bruno Oliveira
918af99a2a Remove example of obsolete pytest.main usage with string 2016-10-20 12:30:58 -02:00
Ronny Pfannschmidt
c0719a5b4c Merge pull request #2009 from pytest-dev/RonnyPfannschmidt-patch-docs-remove-main-string
docs: remove mention of string args to main

closes #2008
2016-10-18 17:52:15 +02:00
Ronny Pfannschmidt
afc1e2b0e1 docs: remove mention of string args to main
fixes #2008

string args got deprecated due to the insane amount of edge-cases wrt splitting on windows vs posix
2016-10-18 17:21:40 +02:00
Ronny Pfannschmidt
de1614923f Merge pull request #2000 from nicoddemus/issue-1998
Handle import errors with non-ascii messages when importing plugins
2016-10-17 21:13:56 +02:00
Matthew Duck
acee88a118 Fix truncation tests: pyruntest() match
Fix issue with truncation tests for -vv and CI, where the test for
non-truncated output would return a positive match even on truncated output.
2016-10-16 20:54:51 +01:00
Bruno Oliveira
bc1f8666aa Fix link in CHANGELOG for #1853 2016-10-12 18:19:34 -03:00
Bruno Oliveira
78eec0d7f8 Handle import errors with non-ascii messages when importing plugins
Fix #1998
2016-10-12 18:19:32 -03:00
Matthew Duck
0061d9bd3d Fix flake8 (unused import, trailng whitespace) 2016-10-11 00:17:15 +01:00
Matthew Duck
b629da424e Restructure truncation of assertion messages
This addresses ref https://github.com/pytest-dev/pytest/issues/1954.

The current truncation for assertion explanations does not deal with long lines
properly:

- Previously if lines were too long it would display a "-n more lines"
  message.

- 999e7c6541 introduced a bug where long lines can
  cause index errors if there are < 10 lines.

Extract the truncation logic into its own file and ensure it can deal with
long lines properly.
2016-10-10 23:38:27 +01:00
Bruno Oliveira
3301a1c173 Merge pull request #1987 from Budulianin/master
Update fixture.rst
2016-10-05 17:01:11 -03:00
Grigorii Eremeev
65ebc75ee8 Update fixture.rst
Removed redundant word
2016-10-05 22:26:13 +03:00
Bruno Oliveira
cf13355d3f Merge pull request #1979 from nicoddemus/show-traceback-during-collection
Show traceback during collection
2016-10-05 16:18:43 -03:00
Florian Bruhin
1289cbb9a5 Merge pull request #1988 from nicoddemus/pr-template-small-fixes
Mention small doc fixes don't need tests/changelog entries in PR template
2016-10-05 21:10:36 +02:00
Bruno Oliveira
10433db225 Mention small doc fixes don't need tests/changelog entries in PR template 2016-10-05 15:36:38 -03:00
Bruno Oliveira
50b960c1f0 Add note about not monkey-patching builtins (#1986)
* Add note about not monkey-patching builtins

Related to #1985

* Mention -s as well
2016-10-05 17:57:40 +02:00
Bruno Oliveira
d47ae799a7 Merge pull request #1983 from pytest-dev/fix-1981-improve-ini-options-help-text
Fix #1981, improve ini-options help text
2016-10-04 12:43:42 -03:00
Tom V
c93a9e3361 Fix #1981, improve ini-options help text 2016-10-04 14:41:09 +01:00
Bruno Oliveira
a1d446b8e8 Display traceback from Import errors using pytest's short representation
Also omit pytest's own modules and internal libraries (py and pluggy) in low verbosity

Fix #1976
2016-10-03 21:46:44 -03:00
Bruno Oliveira
7d66e4eae1 Display full traceback from Import errors when collecting test modules
Fix #1976
2016-10-03 20:47:44 -03:00
Bruno Oliveira
fc02003220 Merge pull request #1975 from nicoddemus/pytest-skip-message
Pytest skip message
2016-10-01 14:44:49 -03:00
Bruno Oliveira
336d7900c5 Fix test about pytest.skip message being used at global level
Fix #1959
2016-10-01 13:38:52 -03:00
Nikolaus Rath
57bb3c6922 Improve error message when using pytest.skip at module level
As discussed in issue #1959.
2016-10-01 13:38:52 -03:00
Bruno Oliveira
4667b4decc Merge branch 'master' into features 2016-09-29 19:03:26 -03:00
Bruno Oliveira
a87b1c79c1 post 3.0.3 release changes 2016-09-29 18:58:17 -03:00
Bruno Oliveira
30f3d95aeb Merge pull request #1973 from nicoddemus/release-3.0.3
Release 3.0.3
2016-09-29 18:54:50 -03:00
Bruno Oliveira
ba6ecc14c8 Include release-3.0.3 into the announce toctree 2016-09-28 17:16:57 -04:00
Bruno Oliveira
41d3b3f4f9 Regendoc for version 3.0.3 2016-09-28 16:36:15 -04:00
Bruno Oliveira
dda17994ec Prepare for 3.0.3 release 2016-09-28 16:33:38 -04:00
Skylar Downes
b0c78c867d Update CHANGELOG.rst 2016-09-27 15:50:45 -07:00
Skylar Downes
3d211da9bd add existing test suite page to table of contents 2016-09-27 14:01:54 -07:00
Skylar Downes
1ab1962eb1 make issue #1934 reference a link 2016-09-27 13:53:31 -07:00
Bruno Oliveira
427cee109a Merge pull request #1964 from nicoddemus/pluggy-0.4.0
Vendor pluggy 0.4.0
2016-09-27 17:35:51 -03:00
Skylar Downes
12ac3c7338 remove existing tests stuff, add link to new page
Moved the "Contributing tests to an existing project" section to it's own page.
2016-09-27 13:08:15 -07:00
Skylar Downes
7e2f66adc3 Create existingtestsuite.rst 2016-09-27 12:51:46 -07:00
Bruno Oliveira
654af0ba25 Merge remote-tracking branch 'upstream/master' into features 2016-09-26 19:32:53 -03:00
Vlad Dragos
acac78adc0 Add link to profile. 2016-09-26 16:09:25 +03:00
Florian Bruhin
30d459e2e3 Merge pull request #1968 from nicoddemus/use-latest-hypothesis
Use hypothesis >= 3.5.2
2016-09-26 15:01:09 +02:00
Bruno Oliveira
f31447b82b Use hypothesis >= 3.5.2
Related to #1962
2016-09-26 09:08:30 -03:00
Vlad Dragos
3444796f3e Fix formating error. 2016-09-26 13:59:28 +03:00
Vlad Dragos
ff492ca73f Thanked my self in the change log :) 2016-09-26 13:27:41 +03:00
Vlad Dragos
8985c0be3e Change exception raised by DontReadFromInput.fileno() from ValueError to io.UnsupportedOperation 2016-09-26 13:15:35 +03:00
Florian Bruhin
b071fdc633 Merge pull request #1960 from nicoddemus/skip-module-level-doc
Add a note about pytest.skip not being allowed at module level
2016-09-26 08:37:14 +02:00
Bruno Oliveira
19766ef0bc Add a summary on how to skip all tests in a module in different situations 2016-09-25 23:36:25 -03:00
Bruno Oliveira
94155ee62a Add a note about pytest.skip not being allowed at module level 2016-09-25 23:36:25 -03:00
Bruno Oliveira
835328d862 Vendor pluggy 0.4.0
Fix #1637
2016-09-25 23:36:02 -03:00
Bruno Oliveira
8dc497b54b Merge pull request #1966 from nicoddemus/disable-pypy-windows
Disable pypy on AppVeyor until #1963 gets fixed
2016-09-25 23:34:59 -03:00
Bruno Oliveira
da201c7d29 Disable pypy on AppVeyor until #1963 gets fixed 2016-09-25 23:21:37 -03:00
Bruno Oliveira
01068e5571 Merge pull request #1962 from nicoddemus/fix-ci
Pin hypothesis to 3.5.0 because 3.5.1 breaks the test suite
2016-09-25 21:17:17 -03:00
Bruno Oliveira
3fce78498f Mention #1952 on the CHANGELOG
Also created "New Features" and "Changes" sections.
2016-09-25 20:31:03 -03:00
Bruno Oliveira
5e96edd435 Merge pull request #1952 from davidszotten/pdbcls_without_pdb_on_fail
Pdbcls without pdb on fail
2016-09-25 20:24:45 -03:00
Bruno Oliveira
73cab77249 Pin hypothesis to 3.5.0 because 3.5.1 breaks the test suite
Related to HypothesisWorks/hypothesis-python#368
2016-09-25 18:52:27 -03:00
Bruno Oliveira
09bcf7f170 Merge pull request #1958 from rowillia/master
Remove implementation of `__getslice__`
2016-09-24 08:39:58 -03:00
Roy Williams
f1c4cfea2c Remove implementation of __getslice__
`__getslice__` has been Deprecated since Python 2.0 and is removed in Python 3.  See
https://docs.python.org/2/reference/datamodel.html#object.__getslice__

Unfortunately, Python 2 will still dispatch to `__getslice__` over `__getitem__`, See
http://bugs.python.org/issue2041, which causes Warnings when running with `-3` in 2.7.
2016-09-23 09:52:30 -07:00
Bruno Oliveira
e5deb8a927 Merge pull request #1955 from rowillia/fix_python3_deprecation_warnings
Fix `DeprecationWarnings` found when running py.test in Python 2.7 with the -3 flag.
2016-09-22 17:37:52 -03:00
Roy Williams
24db3c123d Explicitly set to None to have consistent behavior in Python 2 and Python 3 2016-09-22 09:22:12 -07:00
Roy Williams
940ed7e943 Fix DeprecationWarnings found when running py.test in Python 2.7 with the -3 flag.
Running through some of my tests with the `-3` flag in python2.7 I encountered some errors within py.test itself.  This fixes those errors so we can use py.test in order to identify problems with Python 3.
2016-09-21 17:44:25 -07:00
Bruno Oliveira
f279bd2bd5 Merge pull request #1918 from tgoodlet/explain_bad_scope
Explain a bad scope value to the user
2016-09-21 18:58:03 -03:00
Tyler Goodlet
6db2f315fa Explain a bad scope value to the user 2016-09-21 16:40:58 -04:00
David Szotten
d75748ef6f test for calling set_trace with custom pdb cls 2016-09-21 09:45:26 +00:00
David Szotten
c7b4b8cf6f test 2016-09-21 09:45:23 +00:00
David Szotten
0ac85218d1 allow pdbcls without implying usepdb 2016-09-21 09:45:20 +00:00
Florian Bruhin
7660a19d0a Merge pull request #1945 from axil/master
AttributeError chaining bug #1944 fix
2016-09-20 08:36:04 +02:00
Lev Maximov
8cba033bbb added a test 2016-09-20 02:47:59 +07:00
Lev Maximov
6b56c36ae7 added to changelog and authors 2016-09-20 02:47:59 +07:00
Lev Maximov
65be1231b1 AttributeError chaining bug #1944 fix 2016-09-20 02:20:13 +07:00
Floris Bruynooghe
887c097f8e Merge pull request #1951 from mattduck/feat/1512-dict-compare-output
Feat/1512 dict compare output
2016-09-19 17:34:09 +01:00
Floris Bruynooghe
01db0f1cd1 Merge pull request #1923 from RonnyPfannschmidt/mark-internal-value
use consistent inner repressentation for marks
2016-09-19 15:30:22 +01:00
Matthew Duck
4df74a5cfb Add AUTHORS and CHANGELOG for #1512 2016-09-19 15:29:37 +01:00
Matthew Duck
999e7c6541 Tidy formatting of assertion truncation
Part two of https://github.com/pytest-dev/pytest/issues/1512. Update the format
of the truncation message to help make it clear that pytest truncates the
entire assertion output when verbosity < 2.
2016-09-19 15:27:38 +01:00
Matthew Duck
dd64d823b9 Don't display dict common items if verbosity=1
Part one of https://github.com/pytest-dev/pytest/issues/1512.

If verbosity=1, assertion explanations are truncated at 10 lines. In this
situation, it's more important to tell the user which dictionary items are
different than which are the same.
2016-09-19 15:27:28 +01:00
Floris Bruynooghe
dc16fe2bb9 Merge junit-xml url attribute branch
Merge branch 'url_attr' of https://github.com/fushi/pytest into junitxml-url
2016-09-19 12:57:05 +01:00
Floris Bruynooghe
5b260d80f9 Merge pull request #1883 from RonnyPfannschmidt/kill-memoized-call
Kill memoized call
2016-09-19 12:47:52 +01:00
Floris Bruynooghe
8639bf7554 fixup! Merge pkg_resources workaround 2016-09-19 10:20:36 +01:00
Floris Bruynooghe
f484e7c9ca Merge pkg_resources workaround
Merge branch 'pkg_resources_bugfix' of github.com:raquel-ucl/pytest into pkg-resources

This is an odd case and doesn't really make sense, it is working around a bug
in maven/jython and the old version or weird packaging of setuptools they use.
But we used to do this in the past so might as well keep doing this.
2016-09-19 10:14:41 +01:00
Ronny Pfannschmidt
2bafa74765 Merge pull request #1942 from mbyt/better_doc_for_1890
better doc for #1890 based on #1932
2016-09-17 20:14:44 +02:00
mbyt
77d842ceb2 better doc for #1890 based on #1932 2016-09-17 19:58:27 +02:00
Skylar Downes
34117be98b Update goodpractices.rst 2016-09-15 16:45:35 -07:00
Skylar Downes
330a2f6784 Update getting-started.rst 2016-09-15 16:28:37 -07:00
Skylar Downes
f1faaea3fd Update CHANGELOG.rst 2016-09-15 15:22:53 -07:00
Skylar Downes
d781b76627 Update AUTHORS 2016-09-15 15:20:47 -07:00
Skylar Downes
81a733f2dc add how-to for getting started on existing project
ref #1937
2016-09-15 15:09:47 -07:00
Skylar Downes
07ad71e851 clarified purpose of pip install -e command
ref #1937
2016-09-15 14:55:54 -07:00
Skylar Downes
b4fd74c6ff add mention of setup.py develop 2016-09-15 14:10:57 -07:00
Skylar Downes
69f72c6f4b fix typo 2016-09-15 10:49:59 -07:00
Skylar Downes
383fc02ba6 fix spacing 2016-09-15 10:47:16 -07:00
Skylar Downes
d217984129 documenting how to point pytest at local code
Related to #1937
2016-09-15 10:46:15 -07:00
Bruno Oliveira
863b7d0c50 Merge pull request #1933 from pytest-dev/metafunc-members-docs
Remove duplicated for Metafunc members on docs
2016-09-15 12:43:54 -03:00
Ronny Pfannschmidt
40ec35767f Merge pull request #1935 from nicoddemus/assert-rewrite-dev-plugins
Fixed assertion rewriting for plugins in development mode
2016-09-15 08:45:23 +02:00
Bruno Oliveira
04cf5e1df4 Fixed assertion rewriting for plugins in development mode
Fix #1934
2016-09-14 21:59:33 -03:00
Bruno Oliveira
138e255631 Remove duplicated for Metafunc members on docs
Fix #1924
2016-09-14 17:11:58 -03:00
Ronny Pfannschmidt
45524241a5 mark: fix introduced linting error 2016-09-08 10:16:45 +02:00
Ronny Pfannschmidt
1812387bf0 Mark: fix python 3 compatibility 2016-09-08 10:03:45 +02:00
Ronny Pfannschmidt
10094a3f09 use consistent inner repressentation for marks 2016-09-08 09:52:22 +02:00
Bruno Oliveira
51378aba01 Merge pull request #1919 from The-Compiler/pdf
Fix link to PDF docs
2016-09-07 16:23:26 -03:00
Florian Bruhin
8e67dd13e7 Fix link to PDF docs
Fixes #1917
2016-09-06 21:17:54 +02:00
Bruno Oliveira
99efc281ee Merge pull request #1914 from RonnyPfannschmidt/docs-remove-getdoctarget
docs: remove the need for special scripts
2016-09-05 16:38:41 -03:00
Ronny Pfannschmidt
a2b8981b50 docs: remove unused helper script 2016-09-05 21:03:22 +02:00
Ronny Pfannschmidt
1c9bd9278e Merge pull request #1913 from flub/builtin-assertion
Remove BuiltinAssertionError
2016-09-05 16:53:13 +02:00
Ronny Pfannschmidt
e21ae3991d docs: remove the need for special scripts
* import version from pytest since it is importable
* remove makefile bits from legacy hosting
* remove the script to determine the version for legacy hosting
2016-09-05 16:42:33 +02:00
Floris Bruynooghe
28b1896e9a Remove BuiltinAssertionError
We used to have this when we where patching the real Python
AssertionError for use with reinterpret, but reinterpret is now
gone so we no longer need this as it is not used by rewrite.
2016-09-05 15:29:09 +02:00
Florian Bruhin
8a41b26f56 Merge pull request #1908 from nicoddemus/parametrize-unicode-id
The "ids" argument to "parametrize" again accepts unicode strings in Python 2
2016-09-05 15:09:12 +02:00
Ronny Pfannschmidt
e1674f60e7 remove memocollect anmd memoized_call 2016-09-05 14:41:28 +02:00
Ronny Pfannschmidt
e572c16d3f remove memoizedcall in Module 2016-09-05 14:41:28 +02:00
Ronny Pfannschmidt
b6f766ae87 Merge pull request #1912 from flub/reg-assert-rewrite-changelog
Mention register_assert_rewrite explicitly
2016-09-05 14:36:05 +02:00
Floris Bruynooghe
ceeb5149f3 Mention register_assert_rewrite explicitly
It helps mentioning this explicitly in the changelog.
Fixes #1871.
2016-09-05 13:09:36 +01:00
Florian Bruhin
b38cf77562 Merge pull request #1911 from irachex/patch-1
Fix keyword docs
2016-09-05 11:27:49 +02:00
Huayi Zhang
c60854474a Fix keyword docs 2016-09-05 16:48:04 +08:00
Ronny Pfannschmidt
c69fbd79a9 Merge pull request #1910 from nicoddemus/monkeypatch-members-docs
Fix reference docs for monkeypatch
2016-09-03 21:13:54 +02:00
Bruno Oliveira
183f3737d4 Fix reference docs for monkeypatch
Fix #1909
2016-09-03 15:47:54 -03:00
Bruno Oliveira
1e10de574d The "ids" argument to "parametrize" again accepts unicode strings in Python 2
Fixes #1905
2016-09-02 18:38:15 -03:00
Florian Bruhin
722f9eadcd Merge pull request #1906 from pytest-dev/adjustment-how-to-release
Send only minor/major release announces to TIP mailing list
2016-09-02 18:10:29 +02:00
Bruno Oliveira
7a4fe7582c Send only minor/major release announces to TIP mailing list
As discussed recently on the testing-in-python mailing list, people have asked 
to restrict the amount of announcements there only to major/minor 
pytest releases
2016-09-02 12:27:06 -03:00
Florian Bruhin
98ac1dd34b Merge pull request #1902 from nicoddemus/features
Merge master into features after 3.0.2
2016-09-02 06:35:44 +02:00
Bruno Oliveira
f5d900d972 Merge remote-tracking branch 'upstream/master' into features 2016-09-01 23:07:49 -03:00
Bruno Oliveira
96a1d2941b Merge pull request #1900 from nicoddemus/finalize-3.0.2-release
Finalize 3.0.2 release
2016-09-01 23:05:39 -03:00
Bruno Oliveira
ee284ec587 Set version to 3.0.3.dev0
Also, using "dev0" as development suffix otherwise distutils gives a warning during "setup.py develop":

UserWarning: Normalizing '3.0.3.dev' to '3.0.3.dev0'
  normalized_version,
2016-09-01 21:34:54 -03:00
Bruno Oliveira
927f411ee2 Fix release 3.0.2 release announcement list of authors 2016-09-01 21:28:30 -03:00
Bruno Oliveira
919f50a3bd Merge pull request #1896 from nicoddemus/release-3.0.2
Release 3.0.2
2016-09-01 21:19:50 -03:00
Bruno Oliveira
9d2149d9c0 Merge pull request #1884 from pytest-dev/master
merge master into features
2016-08-30 18:47:57 -03:00
John Towler
1b259f70f3 Testcase reports with a url attribute will now properly write this to junitxml 2016-08-25 13:08:51 -07:00
Raquel Alegre
8f516d27fa Moved import pkg_resources to else clause. 2016-08-24 10:25:01 +01:00
Bruno Oliveira
c5675d3efc Remove merge-conflict marker merged by accident
Unfortunately rst-lint didn't catch that
2016-08-23 23:43:05 -03:00
Bruno Oliveira
6359894fe4 Merge pull request #1859 from nicoddemus/merge-master-into-features
Merge master into features
2016-08-23 23:39:01 -03:00
Bruno Oliveira
7704f73db9 Merge branch 'master' into merge-master-into-features 2016-08-23 21:36:42 -03:00
Raquel Alegre
d3f4b3d14a mend 2016-08-23 16:50:00 +01:00
Raquel Alegre
a1597aca89 Added #1853 to changelog as requested on PR. 2016-08-23 16:48:24 +01:00
Raquel Alegre
7d498fdc82 Added myself as AUTHOR as requested on PR. 2016-08-23 16:47:15 +01:00
Raquel Alegre
2f11a85698 Import pkg_resources only when necessary. 2016-08-23 16:41:11 +01:00
Bruno Oliveira
db922403cc Bump version to 3.1.0.dev 2016-08-19 17:44:07 -03:00
Florian Schulze
bc5a8c761e Fix version in readme. 2016-06-27 11:33:36 +02:00
Florian Schulze
3feee0c483 Prepare 0.1 release. 2016-06-27 11:32:54 +02:00
Florian Schulze
b9c4ecf5a8 Add MANIFEST.in. 2016-06-27 11:32:38 +02:00
Florian Schulze
6b135c83be Initial commit. 2016-06-22 12:21:51 +02:00
230 changed files with 15606 additions and 7086 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

@@ -2,10 +2,14 @@ Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs:
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`;
- [ ] Make sure to include one or more tests for your change;
- [ ] Add yourself to `AUTHORS`;
- [ ] Add a new entry to `CHANGELOG.rst`
* Choose any open position to avoid merge conflicts with other PRs.
* Add a link to the issue you are fixing (if any) using RST syntax.
* The pytest team likes to have people to acknowledged in the `CHANGELOG`, so please add a thank note to yourself ("Thanks @user for the PR") and a link to your GitHub profile. It may sound weird thanking yourself, but otherwise a maintainer would have to do it manually before or after merging instead of just using GitHub's merge button. This makes it easier on the maintainers to merge PRs.
- [ ] 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
Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please:
- [ ] Add yourself to `AUTHORS`, in alphabetical order;

3
.gitignore vendored
View File

@@ -18,6 +18,9 @@ include/
*~
.hypothesis/
# autogenerated
_pytest/_version.py
# setuptools
.eggs/
doc/*/_build

View File

@@ -1,34 +1,49 @@
sudo: false
language: python
python:
- '3.5'
- '3.6'
# command to install dependencies
install: "pip install -U tox"
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
- TESTENV=coveralls
- TOXENV=coveralls
# note: please use "tox --listenvs" to populate the build matrix below
- TESTENV=linting
- TESTENV=py26
- TESTENV=py27
- TESTENV=py33
- TESTENV=py34
- TESTENV=py35
- TESTENV=pypy
- TESTENV=py27-pexpect
- TESTENV=py27-xdist
- TESTENV=py27-trial
- TESTENV=py35-pexpect
- TESTENV=py35-xdist
- TESTENV=py35-trial
- TESTENV=py27-nobyte
- TESTENV=doctesting
- TESTENV=freeze
- TESTENV=docs
- TOXENV=linting
- TOXENV=py27
- TOXENV=py34
- TOXENV=py36
- TOXENV=py27-pexpect
- 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
script: tox --recreate -e $TESTENV
matrix:
include:
- env: TOXENV=pypy
python: 'pypy-5.4'
- env: TOXENV=py35
python: '3.5'
- env: TOXENV=py35-freeze
python: '3.5'
- env: TOXENV=py37
python: 'nightly'
allow_failures:
- env: TOXENV=py37
python: 'nightly'
script: tox --recreate
notifications:
irc:

58
AUTHORS
View File

@@ -6,16 +6,20 @@ Contributors include::
Abdeali JK
Abhijeet Kasurde
Ahn Ki-Wook
Alexander Johnson
Alexei Kozlenok
Anatoly Bubenkoff
Andras Tim
Andreas Zeidler
Andrzej Ostrowski
Andy Freeland
Anthon van der Neut
Anthony Sottile
Antony Lee
Armin Rigo
Aron Curzon
Aviv Palivoda
Barney Gale
Ben Webb
Benjamin Peterson
Bernard Pratz
@@ -26,6 +30,7 @@ Brianna Laugher
Bruno Oliveira
Cal Leeming
Carl Friedrich Bolz
Ceridwen
Charles Cloud
Charnjit SiNGH (CCSJ)
Chris Lamb
@@ -36,16 +41,22 @@ Christopher Gilling
Daniel Grana
Daniel Hahler
Daniel Nuri
Daniel Wandschneider
Danielle Jenkins
Dave Hunt
David Díaz-Barquero
David Mohr
David Vierra
Daw-Ran Liou
Denis Kirisov
Diego Russo
Dmitry Dygalo
Dmitry Pribysh
Duncan Betts
Edison Gustavo Muenz
Edoardo Batini
Eduardo Schettino
Eli Boyarski
Elizaveta Shashkova
Endre Galaczi
Eric Hunsberger
@@ -55,12 +66,16 @@ Feng Ma
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
George Kussumoto
Georgy Dyuldin
Graham Horler
Greg Price
Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
Harald Armin Massa
Hugo van Kemenade
Hui Wang (coldnight)
Ian Bicking
Jaap Broekhuizen
Jan Balster
@@ -68,44 +83,73 @@ Janne Vanhala
Jason R. Coombs
Javier Domingo Cansino
Javier Romero
Jeff Widman
John Eddie Ayson
John Towler
Jon Sonesen
Jonas Obrist
Jordan Guymon
Jordan Moldow
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
Kale Kundert
Katarzyna Jachim
Kevin Cox
Kodi B. Arfer
Lawrence Mitchell
Lee Kamentsky
Lev Maximov
Llandy Riveron Del Risco
Loic Esteve
Lukas Bednar
Luke Murphy
Maciek Fijalkowski
Maho
Maik Figura
Mandeep Bhutani
Manuel Krebber
Marc Schlaich
Marcin Bachry
Mark Abramowitz
Markus Unterwaditzer
Martijn Faassen
Martin Altmayer
Martin K. Scherer
Martin Prusse
Mathieu Clabaut
Matt Bachmann
Matt Duck
Matt Williams
Matthias Hafner
Maxim Filipenko
mbyt
Michael Aquilina
Michael Birtwell
Michael Droettboom
Michael Seifert
Michal Wajszczuk
Mihai Capotă
Mike Lundy
Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
Nicolas Delaby
Oleg Pidsadnyi
Oliver Bestwalter
Omar Kohl
Omer Hadari
Patrick Hayes
Paweł Adamczak
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Quentin Pradet
Ralf Schmitt
Ran Benita
Raphael Pierzina
Raquel Alegre
Ravi Chandra
Roberto Polli
Romain Dorgueil
Roman Bolshakov
@@ -113,18 +157,32 @@ Ronny Pfannschmidt
Ross Lawley
Russel Winder
Ryan Wooden
Samuel Dion-Girardeau
Samuele Pedroni
Segev Finer
Simon Gomizelj
Skylar Downes
Srinivas Reddy Thatiparthy
Stefan Farmbauer
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
Vasily Kuznetsov
Victor Uriarte
Vidar T. Fauske
Vitaly Lashmanov
Vlad Dragos
Wouter van Ackooy
Xuan Luong
Xuecong Liao
Zoltán Máté

File diff suppressed because it is too large Load Diff

View File

@@ -34,13 +34,13 @@ If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting,
specifically Python interpreter version,
installed libraries and pytest version.
specifically the Python interpreter version, installed libraries, and pytest
version.
* Detailed steps to reproduce the bug.
If you can write a demonstration test that currently fails but should pass (xfail),
that is a very useful commit to make as well, even if you can't find how
to fix the bug yet.
If you can write a demonstration test that currently fails but should pass
(xfail), that is a very useful commit to make as well, even if you cannot
fix the bug itself.
.. _fixbugs:
@@ -79,6 +79,16 @@ Pytest could always use more documentation. What exactly is needed?
You can also edit documentation files directly in the GitHub web interface,
without using a local copy. This can be convenient for small fixes.
.. note::
Build the documentation locally with the following command:
.. code:: bash
$ tox -e docs
The built documentation should be available in the ``doc/en/_build/``.
Where 'en' refers to the documentation language.
.. _submitplugin:
@@ -110,7 +120,7 @@ the following:
- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
prefixed name, version number, authors, short and long description.
- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
- a ``tox.ini`` for running tests using `tox <https://tox.readthedocs.io>`_.
- a ``README.txt`` describing how to use the plugin and on which
platforms it runs.
@@ -148,19 +158,41 @@ As stated, the objective is to share maintenance and avoid "plugin-abandon".
.. _`pull requests`:
.. _pull-requests:
Preparing Pull Requests on GitHub
---------------------------------
Preparing Pull Requests
-----------------------
.. note::
What is a "pull request"? It informs project's core developers about the
changes you want to review and merge. Pull requests are stored on
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on.
Short version
~~~~~~~~~~~~~
There's an excellent tutorial on how Pull Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_,
but here is a simple overview:
#. Fork the repository;
#. Target ``master`` for bugfixes and doc changes;
#. Target ``features`` for new features or functionality changes.
#. Follow **PEP-8**. There's a ``tox`` command to help fixing it: ``tox -e fix-lint``.
#. Tests are run using ``tox``::
tox -e linting,py27,py36
The test environments above are usually enough to cover most cases locally.
#. 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.
#. 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
~~~~~~~~~~~~
What is a "pull request"? It informs the project's core developers about the
changes you want to review and merge. Pull requests are stored on
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on. There's an excellent tutorial on how Pull
Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
Here is a simple overview, with pytest-specific bits:
#. Fork the
`pytest GitHub repository <https://github.com/pytest-dev/pytest>`__. It's
@@ -196,38 +228,43 @@ but here is a simple overview:
#. Run all the tests
You need to have Python 2.7 and 3.5 available in your system. Now
You need to have Python 2.7 and 3.6 available in your system. Now
running tests is as simple as issuing this command::
$ python3 runtox.py -e linting,py27,py35
$ tox -e linting,py27,py36
This command will run tests via the "tox" tool against Python 2.7 and 3.5
and also perform "lint" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to PyPI) versions of dependencies
(especially ``py``) might be present.
This command will run tests via the "tox" tool against Python 2.7 and 3.6
and also perform "lint" coding-style checks.
#. You can now edit your local working copy.
#. You can now edit your local working copy. Please follow PEP-8.
You can now make the changes you want and run the tests again as necessary.
To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on
failure) to pytest you can do::
If you have too much linting errors, try running::
$ python3 runtox.py -e py27 -- --pdb
$ tox -e fix-lint
Or to only run tests in a particular test module on Python 3.5::
To fix pep8 related errors.
$ python3 runtox.py -e py35 -- testing/test_config.py
You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest
(e.g. enter pdb on failure) to pytest you can do::
$ tox -e py27 -- --pdb
Or to only run tests in a particular test module on Python 3.6::
$ tox -e py36 -- testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s)::
$ git commit -a -m "<commit message>"
$ git push -u
Make sure you add a message to ``CHANGELOG.rst`` and add yourself to
``AUTHORS``. If you are unsure about either of these steps, submit your
pull request and we'll help you fix it up.
#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>``,
where *issueid* is the number of the issue related to the change and *type* is one of
``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial``.
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
#. Finally, submit a pull request through the GitHub website using this data::

View File

@@ -1,85 +1,65 @@
How to release pytest
--------------------------------------------
Release Procedure
-----------------
Note: this assumes you have already registered on pypi.
Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea
is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
taking a lot of time to make a new one.
1. Bump version numbers in ``_pytest/__init__.py`` (``setup.py`` reads it).
.. important::
2. Check and finalize ``CHANGELOG.rst``.
pytest releases must be prepared on **Linux** because the docs and examples expect
to be executed in that platform.
3. Write ``doc/en/announce/release-VERSION.txt`` and include
it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved::
#. Install development dependencies in a virtual environment with::
git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u
pip3 install -r tasks/requirements.txt
4. Regenerate the docs examples using tox::
#. Create a branch ``release-X.Y.Z`` with the version for the release.
tox -e regen
* **patch releases**: from the latest ``master``;
5. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions.
* **minor releases**: from the latest ``features``; then merge with the latest ``master``;
6. Use devpi for uploading a release tarball to a staging area::
Ensure your are in a clean work tree.
devpi use https://devpi.net/USER/dev
devpi upload --formats sdist,bdist_wheel
#. Generate docs, changelog, announcements and upload a package to
your ``devpi`` staging server::
7. Run from multiple machines::
invoke generate.pre-release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION
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.
Alternatively, you can use `devpi-cloud-tester <https://github.com/nicoddemus/devpi-cloud-tester>`_ to test
the package on AppVeyor and Travis (follow instructions on the ``README``).
#. Open a PR for this branch targeting ``master``.
8. Check that tests pass for relevant combinations with::
#. Test the package
* **Manual method**
Run from multiple machines::
devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION
Check that tests pass for relevant combinations with::
devpi list pytest
or look at failures with "devpi list -f pytest".
* **CI servers**
9. Feeling confident? Publish to pypi::
Configure a repository as per-instructions on
devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
All test environments should pass.
devpi push pytest==VERSION pypi:NAME
#. Publish to PyPI::
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
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>`_.
10. Tag the release::
git tag VERSION <hash>
git push origin VERSION
Make sure ``<hash>`` is **exactly** the git hash at the time the package was created.
11. Send release announcement to mailing lists:
- pytest-dev@python.org
- testing-in-python@lists.idyll.org
- python-announce-list@python.org
And announce the release on Twitter, making sure to add the hashtag ``#pytest``.
12. **After the release**
a. **patch release (2.8.3)**:
1. Checkout ``master``.
2. Update version number in ``_pytest/__init__.py`` to ``"2.8.4.dev"``.
3. Create a new section in ``CHANGELOG.rst`` titled ``2.8.4.dev`` and add a few bullet points as placeholders for new entries.
4. Commit and push.
b. **minor release (2.9.0)**:
1. Merge ``features`` into ``master``.
2. Checkout ``master``.
3. Follow the same steps for a **patch release** above, using the next patch release: ``2.9.1.dev``.
4. Commit ``master``.
5. Checkout ``features`` and merge with ``master`` (should be a fast-forward at this point).
6. Update version number in ``_pytest/__init__.py`` to the next minor release: ``"2.10.0.dev"``.
7. Create a new section in ``CHANGELOG.rst`` titled ``2.10.0.dev``, above ``2.9.1.dev``, and add a few bullet points as placeholders for new entries.
8. Commit ``features``.
9. Push ``master`` and ``features``.
c. **major release (3.0.0)**: same steps as that of a **minor release**
#. 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

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

View File

@@ -1,32 +0,0 @@
include CHANGELOG.rst
include LICENSE
include AUTHORS
include README.rst
include CONTRIBUTING.rst
include HOWTORELEASE.rst
include tox.ini
include setup.py
include .coveragerc
include plugin-test.sh
include requirements-docs.txt
include runtox.py
recursive-include bench *.py
recursive-include extra *.py
graft testing
graft doc
exclude _pytest/impl
graft _pytest/vendored_packages
recursive-exclude * *.pyc *.pyo
exclude appveyor/install.ps1
exclude appveyor.yml
exclude appveyor

View File

@@ -6,13 +6,20 @@
------
.. image:: https://img.shields.io/pypi/v/pytest.svg
:target: https://pypi.python.org/pypi/pytest
:target: https://pypi.python.org/pypi/pytest
.. image:: https://anaconda.org/conda-forge/pytest/badges/version.svg
:target: https://anaconda.org/conda-forge/pytest
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
:target: https://pypi.python.org/pypi/pytest
:target: https://pypi.python.org/pypi/pytest
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
:target: https://coveralls.io/r/pytest-dev/pytest
:target: https://coveralls.io/r/pytest-dev/pytest
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest
@@ -24,31 +31,31 @@ An example of a simple test:
.. code-block:: python
# content of test_sample.py
def func(x):
def inc(x):
return x + 1
def test_answer():
assert func(3) == 5
assert inc(3) == 5
To execute it::
$ pytest
======= test session starts ========
============================= test session starts =============================
collected 1 items
test_sample.py F
======= FAILURES ========
_______ test_answer ________
================================== FAILURES ===================================
_________________________________ test_answer _________________________________
def test_answer():
> assert func(3) == 5
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = func(3)
E + where 4 = inc(3)
test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
========================== 1 failed in 0.04 seconds ===========================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.
@@ -69,9 +76,9 @@ 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 150+ `external plugins <http://docs.pytest.org/en/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
Documentation
@@ -89,13 +96,13 @@ Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issue
Changelog
---------
Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`_ page for fixes and enhancements of each version.
Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version.
License
-------
Copyright Holger Krekel and others, 2004-2016.
Copyright Holger Krekel and others, 2004-2017.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@@ -1,2 +1,8 @@
#
__version__ = '3.0.2'
__all__ = ['__version__']
try:
from ._version import version as __version__
except ImportError:
# broken installation, we don't even try
# unknown only works because we do poor mans version compare
__version__ = 'unknown'

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().
@@ -57,26 +54,29 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script).
"""
from __future__ import absolute_import, division, print_function
import sys
import os
from glob import glob
class FastFilesCompleter:
'Fast file completer class'
def __init__(self, directories=True):
self.directories = directories
def __call__(self, prefix, **kwargs):
"""only called on non option completions"""
if os.path.sep in prefix[1:]: #
if os.path.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
else:
prefix_dir = 0
completion = []
globbed = []
if '*' not in prefix and '?' not in prefix:
if prefix[-1] == os.path.sep: # we are on unix, otherwise no bash
# we are on unix, otherwise no bash
if not prefix or prefix[-1] == os.path.sep:
globbed.extend(glob(prefix + '.*'))
prefix += '*'
globbed.extend(glob(prefix))
@@ -87,6 +87,7 @@ class FastFilesCompleter:
completion.append(x[prefix_dir:])
return completion
if os.environ.get('_ARGCOMPLETE'):
try:
import argcomplete.completers
@@ -95,7 +96,8 @@ if os.environ.get('_ARGCOMPLETE'):
filescompleter = FastFilesCompleter()
def try_argcomplete(parser):
argcomplete.autocomplete(parser)
argcomplete.autocomplete(parser, always_complete_options=False)
else:
def try_argcomplete(parser): pass
def try_argcomplete(parser):
pass
filescompleter = None

View File

@@ -1,4 +1,5 @@
""" python inspection/code generation API """
from __future__ import absolute_import, division, print_function
from .code import Code # noqa
from .code import ExceptionInfo # noqa
from .code import Frame # noqa

View File

@@ -2,8 +2,10 @@
# CHANGES:
# - some_str is replaced, trying to create unicode strings
#
from __future__ import absolute_import, division, print_function
import types
def format_exception_only(etype, value):
"""Format the exception part of a traceback.
@@ -29,7 +31,7 @@ def format_exception_only(etype, value):
# would throw another exception and mask the original problem.
if (isinstance(etype, BaseException) or
isinstance(etype, types.InstanceType) or
etype is None or type(etype) is str):
etype is None or type(etype) is str):
return [_format_final_exc_line(etype, value)]
stype = etype.__name__
@@ -61,6 +63,7 @@ def format_exception_only(etype, value):
lines.append(_format_final_exc_line(stype, value))
return lines
def _format_final_exc_line(etype, value):
"""Return a list of a single line -- normal case for format_exception_only"""
valuestr = _some_str(value)
@@ -70,6 +73,7 @@ def _format_final_exc_line(etype, value):
line = "%s: %s\n" % (etype, valuestr)
return line
def _some_str(value):
try:
return unicode(value)

View File

@@ -1,19 +1,22 @@
from __future__ import absolute_import, division, print_function
import sys
from inspect import CO_VARARGS, CO_VARKEYWORDS
import re
from weakref import ref
from _pytest.compat import _PY2, _PY3, PY35, safe_str
import py
builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib')
if sys.version_info[0] >= 3:
if _PY3:
from traceback import format_exception_only
else:
from ._py2traceback import format_exception_only
class Code(object):
""" wrapper around Python code objects """
def __init__(self, rawcode):
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
@@ -22,12 +25,14 @@ class Code(object):
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
except AttributeError:
raise TypeError("not a code object: %r" %(rawcode,))
raise TypeError("not a code object: %r" % (rawcode,))
self.raw = rawcode
def __eq__(self, other):
return self.raw == other.raw
__hash__ = None
def __ne__(self, other):
return not self == other
@@ -76,6 +81,7 @@ class Code(object):
argcount += raw.co_flags & CO_VARKEYWORDS
return raw.co_varnames[:argcount]
class Frame(object):
"""Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated."""
@@ -113,7 +119,7 @@ class Frame(object):
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
py.builtin.exec_(code, self.f_globals, f_locals )
py.builtin.exec_(code, self.f_globals, f_locals)
def repr(self, object):
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
@@ -137,6 +143,7 @@ class Frame(object):
pass # this can occur when using Psyco
return retval
class TracebackEntry(object):
""" a single entry in a traceback """
@@ -162,7 +169,7 @@ class TracebackEntry(object):
return self.lineno - self.frame.code.firstlineno
def __repr__(self):
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
@property
def statement(self):
@@ -226,8 +233,8 @@ class TracebackEntry(object):
except KeyError:
return False
if py.builtin.callable(tbh):
return tbh(self._excinfo)
if callable(tbh):
return tbh(None if self._excinfo is None else self._excinfo())
else:
return tbh
@@ -241,19 +248,21 @@ 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)
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
def name(self):
return self.frame.code.raw.co_name
name = property(name, None, None, "co_name of underlaying code")
class Traceback(list):
""" Traceback objects encapsulate and offer higher level
access to Traceback entries.
"""
Entry = TracebackEntry
def __init__(self, tb, excinfo=None):
""" initialize from given python traceback object and ExceptionInfo """
self._excinfo = excinfo
@@ -283,7 +292,7 @@ class Traceback(list):
(excludepath is None or not hasattr(codepath, 'relto') or
not codepath.relto(excludepath)) and
(lineno is None or x.lineno == lineno) and
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
return Traceback(x._rawentry, self._excinfo)
return self
@@ -309,7 +318,7 @@ class Traceback(list):
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
for i in range(-1, -len(self)-1, -1):
for i in range(-1, -len(self) - 1, -1):
entry = self[i]
if not entry.ishidden():
return entry
@@ -324,29 +333,33 @@ class Traceback(list):
# id for the code.raw is needed to work around
# the strange metaprogramming in the decorator lib from pypi
# which generates code objects that have hash/value equality
#XXX needs a test
# 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:
# print "checking for recursion at", key
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)):
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
return i
l.append(entry.frame.f_locals)
values.append(entry.frame.f_locals)
return None
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
'?', 'eval')
class ExceptionInfo(object):
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
"""
_striptext = ''
_assert_start_repr = "AssertionError(u\'assert " if _PY2 else "AssertionError(\'assert "
def __init__(self, tup=None, exprinfo=None):
import _pytest._code
if tup is None:
@@ -354,8 +367,8 @@ class ExceptionInfo(object):
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], 'msg', None)
if exprinfo is None:
exprinfo = py._builtin._totext(tup[1])
if exprinfo and exprinfo.startswith('assert '):
exprinfo = py.io.saferepr(tup[1])
if exprinfo and exprinfo.startswith(self._assert_start_repr):
self._striptext = 'AssertionError: '
self._excinfo = tup
#: the exception class
@@ -367,7 +380,7 @@ class ExceptionInfo(object):
#: the exception type name
self.typename = self.type.__name__
#: the exception traceback (_pytest._code.Traceback instance)
self.traceback = _pytest._code.Traceback(self.tb, excinfo=self)
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
def __repr__(self):
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
@@ -396,10 +409,10 @@ class ExceptionInfo(object):
exconly = self.exconly(tryshort=True)
entry = self.traceback.getcrashentry()
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
return ReprFileLocation(path, lineno+1, exconly)
return ReprFileLocation(path, lineno + 1, exconly)
def getrepr(self, showlocals=False, style="long",
abspath=False, tbfilter=True, funcargs=False):
abspath=False, tbfilter=True, funcargs=False):
""" return str()able representation of this exception info.
showlocals: show locals per traceback entry
style: long|short|no|native traceback style
@@ -416,7 +429,7 @@ class ExceptionInfo(object):
)), self._getreprcrash())
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
return fmt.repr_excinfo(self)
def __str__(self):
@@ -460,15 +473,15 @@ class FormattedExcinfo(object):
def _getindent(self, source):
# figure out indent for given source
try:
s = str(source.getstatement(len(source)-1))
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()))
@@ -504,7 +517,7 @@ class FormattedExcinfo(object):
for line in source.lines[:line_index]:
lines.append(space_prefix + line)
lines.append(self.flow_marker + " " + source.lines[line_index])
for line in source.lines[line_index+1:]:
for line in source.lines[line_index + 1:]:
lines.append(space_prefix + line)
if excinfo is not None:
indent = 4 if short else self._getindent(source)
@@ -537,10 +550,10 @@ class FormattedExcinfo(object):
# _repr() function, which is only reprlib.Repr in
# disguise, so is very configurable.
str_repr = self._saferepr(value)
#if len(str_repr) < 70 or not isinstance(value,
# if len(str_repr) < 70 or not isinstance(value,
# (list, tuple, dict)):
lines.append("%-10s = %s" %(name, str_repr))
#else:
lines.append("%-10s = %s" % (name, str_repr))
# else:
# self._line("%-10s =\\" % (name,))
# # XXX
# py.std.pprint.pprint(value, stream=self.excinfowriter)
@@ -566,14 +579,14 @@ class FormattedExcinfo(object):
s = self.get_source(source, line_index, excinfo, short=short)
lines.extend(s)
if short:
message = "in %s" %(entry.name)
message = "in %s" % (entry.name)
else:
message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path)
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = None
if not short:
localsrepr = self.repr_locals(entry.locals)
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
if excinfo:
lines.extend(self.get_exconly(excinfo, indent=4))
@@ -593,24 +606,54 @@ class FormattedExcinfo(object):
traceback = excinfo.traceback
if self.tbfilter:
traceback = traceback.filter()
recursionindex = None
if is_recursion_error(excinfo):
recursionindex = traceback.recursionindex()
traceback, extraline = self._truncate_recursive_traceback(traceback)
else:
extraline = None
last = traceback[-1]
entries = []
extraline = None
for index, entry in enumerate(traceback):
einfo = (last == entry) and excinfo or None
reprentry = self.repr_traceback_entry(entry, einfo)
entries.append(reprentry)
if index == recursionindex:
extraline = "!!! Recursion detected (same locals & position)"
break
return ReprTraceback(entries, extraline, style=self.style)
def _truncate_recursive_traceback(self, traceback):
"""
Truncate the given recursive traceback trying to find the starting point
of the recursion.
The detection is done by going through each traceback entry and finding the
point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
Handle the situation where the recursion process might raise an exception (for example
comparing numpy arrays using equality raises a TypeError), in which case we do our best to
warn the user of the error and show a limited traceback.
"""
try:
recursionindex = traceback.recursionindex()
except Exception as e:
max_frames = 10
extraline = (
'!!! Recursion error detected, but an error occurred locating the origin of recursion.\n'
' The following exception happened when comparing locals in the stack frame:\n'
' {exc_type}: {exc_msg}\n'
' Displaying first and last {max_frames} stack frames out of {total}.'
).format(exc_type=type(e).__name__, exc_msg=safe_str(e), max_frames=max_frames, total=len(traceback))
traceback = traceback[:max_frames] + traceback[-max_frames:]
else:
if recursionindex is not None:
extraline = "!!! Recursion detected (same locals & position)"
traceback = traceback[:recursionindex + 1]
else:
extraline = None
return traceback, extraline
def repr_excinfo(self, excinfo):
if sys.version_info[0] < 3:
if _PY2:
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
@@ -620,16 +663,23 @@ class FormattedExcinfo(object):
e = excinfo.value
descr = None
while e is not None:
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
if excinfo:
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
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))
reprcrash = None
repr_chain += [(reprtraceback, reprcrash, descr)]
if e.__cause__ is not None:
e = e.__cause__
excinfo = ExceptionInfo((type(e), e, e.__traceback__))
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
descr = 'The above exception was the direct cause of the following exception:'
elif e.__context__ is not None:
elif (e.__context__ is not None and not e.__suppress_context__):
e = e.__context__
excinfo = ExceptionInfo((type(e), e, e.__traceback__))
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
descr = 'During handling of the above exception, another exception occurred:'
else:
e = None
@@ -640,7 +690,7 @@ class FormattedExcinfo(object):
class TerminalRepr(object):
def __str__(self):
s = self.__unicode__()
if sys.version_info[0] < 3:
if _PY2:
s = s.encode('utf-8')
return s
@@ -653,7 +703,7 @@ class TerminalRepr(object):
return io.getvalue().strip()
def __repr__(self):
return "<%s instance at %0x>" %(self.__class__, id(self))
return "<%s instance at %0x>" % (self.__class__, id(self))
class ExceptionRepr(TerminalRepr):
@@ -697,6 +747,7 @@ class ReprExceptionInfo(ExceptionRepr):
self.reprtraceback.toterminal(tw)
super(ReprExceptionInfo, self).toterminal(tw)
class ReprTraceback(TerminalRepr):
entrysep = "_ "
@@ -712,7 +763,7 @@ class ReprTraceback(TerminalRepr):
tw.line("")
entry.toterminal(tw)
if i < len(self.reprentries) - 1:
next_entry = self.reprentries[i+1]
next_entry = self.reprentries[i + 1]
if entry.style == "long" or \
entry.style == "short" and next_entry.style == "long":
tw.sep(self.entrysep)
@@ -720,12 +771,14 @@ class ReprTraceback(TerminalRepr):
if self.extraline:
tw.line(self.extraline)
class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines):
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None
class ReprEntryNative(TerminalRepr):
style = "native"
@@ -735,6 +788,7 @@ class ReprEntryNative(TerminalRepr):
def toterminal(self, tw):
tw.write("".join(self.lines))
class ReprEntry(TerminalRepr):
localssep = "_ "
@@ -751,7 +805,7 @@ class ReprEntry(TerminalRepr):
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
#tw.line("")
# tw.line("")
return
if self.reprfuncargs:
self.reprfuncargs.toterminal(tw)
@@ -759,7 +813,7 @@ class ReprEntry(TerminalRepr):
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
if self.reprlocals:
#tw.sep(self.localssep, "Locals")
# tw.sep(self.localssep, "Locals")
tw.line("")
self.reprlocals.toterminal(tw)
if self.reprfileloc:
@@ -772,6 +826,7 @@ class ReprEntry(TerminalRepr):
self.reprlocals,
self.reprfileloc)
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno, message):
self.path = str(path)
@@ -788,6 +843,7 @@ class ReprFileLocation(TerminalRepr):
tw.write(self.path, bold=True, red=True)
tw.line(":%s: %s" % (self.lineno, msg))
class ReprLocals(TerminalRepr):
def __init__(self, lines):
self.lines = lines
@@ -796,6 +852,7 @@ class ReprLocals(TerminalRepr):
for line in self.lines:
tw.line(line)
class ReprFuncArgs(TerminalRepr):
def __init__(self, args):
self.args = args
@@ -804,11 +861,11 @@ class ReprFuncArgs(TerminalRepr):
if self.args:
linesofar = ""
for name, value in self.args:
ns = "%s = %s" %(name, value)
ns = "%s = %s" % (safe_str(name), safe_str(value))
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
if linesofar:
tw.line(linesofar)
linesofar = ns
linesofar = ns
else:
if linesofar:
linesofar += ", " + ns
@@ -835,7 +892,8 @@ def getrawcode(obj, trycall=True):
return x
return obj
if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
if PY35: # RecursionError introduced in 3.5
def is_recursion_error(excinfo):
return excinfo.errisinstance(RecursionError) # noqa
else:

View File

@@ -1,10 +1,11 @@
from __future__ import generators
from __future__ import absolute_import, division, generators, print_function
from bisect import bisect_right
import sys
import inspect, tokenize
import six
import inspect
import tokenize
import py
from types import ModuleType
cpy_compile = compile
try:
@@ -20,6 +21,7 @@ class Source(object):
possibly deindenting it.
"""
_compilecounter = 0
def __init__(self, *parts, **kwargs):
self.lines = lines = []
de = kwargs.get('deindent', True)
@@ -31,7 +33,7 @@ class Source(object):
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:
@@ -52,22 +54,21 @@ class Source(object):
return str(self) == other
return False
__hash__ = None
def __getitem__(self, key):
if isinstance(key, int):
return self.lines[key]
else:
if key.step not in (None, 1):
raise IndexError("cannot slice a Source with a step")
return self.__getslice__(key.start, key.stop)
newsource = Source()
newsource.lines = self.lines[key.start:key.stop]
return newsource
def __len__(self):
return len(self.lines)
def __getslice__(self, start, end):
newsource = Source()
newsource.lines = self.lines[start:end]
return newsource
def strip(self):
""" return new source object with trailing
and leading blank lines removed.
@@ -75,7 +76,7 @@ class Source(object):
start, end = 0, len(self)
while start < end and not self.lines[start].strip():
start += 1
while end > start and not self.lines[end-1].strip():
while end > start and not self.lines[end - 1].strip():
end -= 1
source = Source()
source.lines[:] = self.lines[start:end]
@@ -88,8 +89,8 @@ class Source(object):
before = Source(before)
after = Source(after)
newsource = Source()
lines = [ (indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
lines = [(indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
return newsource
def indent(self, indent=' ' * 4):
@@ -97,7 +98,7 @@ class Source(object):
all lines indented by the given indent-string.
"""
newsource = Source()
newsource.lines = [(indent+line) for line in self.lines]
newsource.lines = [(indent + line) for line in self.lines]
return newsource
def getstatement(self, lineno, assertion=False):
@@ -136,7 +137,8 @@ class Source(object):
try:
import parser
except ImportError:
syntax_checker = lambda x: compile(x, 'asd', 'exec')
def syntax_checker(x):
return compile(x, 'asd', 'exec')
else:
syntax_checker = parser.suite
@@ -145,8 +147,8 @@ class Source(object):
else:
source = str(self)
try:
#compile(source+'\n', "x", "exec")
syntax_checker(source+'\n')
# compile(source+'\n', "x", "exec")
syntax_checker(source + '\n')
except KeyboardInterrupt:
raise
except Exception:
@@ -166,8 +168,8 @@ class Source(object):
"""
if not filename or py.path.local(filename).check(file=0):
if _genframe is None:
_genframe = sys._getframe(1) # the caller
fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
_genframe = sys._getframe(1) # the caller
fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
base = "<%d-codegen " % self._compilecounter
self.__class__._compilecounter += 1
if not filename:
@@ -182,7 +184,7 @@ class Source(object):
# re-represent syntax errors from parsing python strings
msglines = self.lines[:ex.lineno]
if ex.offset:
msglines.append(" "*ex.offset + '^')
msglines.append(" " * ex.offset + '^')
msglines.append("(code was compiled probably from here: %s)" % filename)
newex = SyntaxError('\n'.join(msglines))
newex.offset = ex.offset
@@ -193,14 +195,6 @@ class Source(object):
if flag & _AST_FLAG:
return co
lines = [(x + "\n") for x in self.lines]
if sys.version_info[0] >= 3:
# XXX py3's inspect.getsourcefile() checks for a module
# and a pep302 __loader__ ... we don't have a module
# at code compile-time so we need to fake it here
m = ModuleType("_pycodecompile_pseudo_module")
py.std.inspect.modulesbyfile[filename] = None
py.std.sys.modules[None] = m
m.__loader__ = 1
py.std.linecache.cache[filename] = (1, None, lines, filename)
return co
@@ -208,8 +202,8 @@ class Source(object):
# public API shortcut functions
#
def compile_(source, filename=None, mode='exec', flags=
generators.compiler_flag, dont_inherit=0):
def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag, dont_inherit=0):
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object
@@ -218,7 +212,7 @@ def compile_(source, filename=None, mode='exec', flags=
if _ast is not None and 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
_genframe = sys._getframe(1) # the caller
s = Source(source)
co = s.compile(filename, mode, flags, _genframe=_genframe)
return co
@@ -255,17 +249,19 @@ def getfslineno(obj):
# helper functions
#
def findsource(obj):
try:
sourcelines, lineno = py.std.inspect.findsource(obj)
except py.builtin._sysex:
raise
except:
except: # noqa
return None, -1
source = Source()
source.lines = [line.rstrip() for line in sourcelines]
return source, lineno
def getsource(obj, **kwargs):
import _pytest._code
obj = _pytest._code.getrawcode(obj)
@@ -276,19 +272,21 @@ def getsource(obj, **kwargs):
assert isinstance(strsrc, str)
return Source(strsrc, **kwargs)
def deindent(lines, offset=None):
if offset is None:
for line in lines:
line = line.expandtabs()
s = line.lstrip()
if s:
offset = len(line)-len(s)
offset = len(line) - len(s)
break
else:
offset = 0
if offset == 0:
return list(lines)
newlines = []
def readline_generator(lines):
for line in lines:
yield line + '\n'
@@ -300,11 +298,11 @@ def deindent(lines, offset=None):
try:
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
if sline > len(lines):
break # End of input reached
break # End of input reached
if sline > len(newlines):
line = lines[sline - 1].expandtabs()
if line.lstrip() and line[:offset].isspace():
line = line[offset:] # Deindent
line = line[offset:] # Deindent
newlines.append(line)
for i in range(sline, eline):
@@ -322,30 +320,28 @@ 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)
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:
@@ -400,7 +396,7 @@ def getstatementrange_old(lineno, source, assertion=False):
raise IndexError("likely a subclass")
if "assert" not in line and "raise" not in line:
continue
trylines = source.lines[start:lineno+1]
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():')
@@ -412,10 +408,8 @@ def getstatementrange_old(lineno, source, assertion=False):
continue
# 2. find the end of the statement
for end in range(lineno+1, len(source)+1):
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.
"""
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

@@ -1,12 +1,13 @@
"""
support for presenting detailed information in failing assertions.
"""
import py
import os
from __future__ import absolute_import, division, print_function
import sys
import six
from _pytest.assertion import util
from _pytest.assertion import rewrite
from _pytest.assertion import truncate
def pytest_addoption(parser):
@@ -24,12 +25,8 @@ def pytest_addoption(parser):
expression information.""")
def pytest_namespace():
return {'register_assert_rewrite': register_assert_rewrite}
def register_assert_rewrite(*names):
"""Register a module name to be rewritten on import.
"""Register one or more module names to be rewritten on import.
This function will make sure that this module or all modules inside
the package will get their assert statements rewritten.
@@ -70,20 +67,20 @@ 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')
config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
sys.meta_path.insert(0, hook)
config._assertstate.trace('installed rewrite import hook')
def undo():
hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
config.add_cleanup(undo)
return hook
@@ -98,12 +95,6 @@ def pytest_collection(session):
assertstate.hook.set_session(session)
def _running_on_ci():
"""Check if we're currently running on a CI system."""
env_vars = ['CI', 'BUILD_NUMBER']
return any(var in os.environ for var in env_vars)
def pytest_runtest_setup(item):
"""Setup the pytest_assertrepr_compare hook
@@ -117,8 +108,8 @@ def pytest_runtest_setup(item):
This uses the first result from the hook and then ensures the
following:
* Overly verbose explanations are dropped unless -vv was used or
running on a CI.
* Overly verbose explanations are truncated unless configured otherwise
(eg. if running in verbose mode).
* Embedded newlines are escaped to help util.format_explanation()
later.
* If the rewrite mode is used embedded %-characters are replaced
@@ -131,16 +122,9 @@ def pytest_runtest_setup(item):
config=item.config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
if (sum(len(p) for p in new_expl[1:]) > 80*8 and
item.config.option.verbose < 2 and
not _running_on_ci()):
show_max = 10
truncated_lines = len(new_expl) - show_max
new_expl[show_max:] = [py.builtin._totext(
'Detailed information truncated (%d more lines)'
', use "-vv" to show' % truncated_lines)]
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,5 +1,5 @@
"""Rewrite assertion AST to produce nice error messages"""
from __future__ import absolute_import, division, print_function
import ast
import _ast
import errno
@@ -8,10 +8,10 @@ import imp
import marshal
import os
import re
import six
import struct
import sys
import types
from fnmatch import fnmatch
import py
from _pytest.assertion import util
@@ -34,13 +34,13 @@ 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):
if sys.version_info >= (3, 5):
ast_Call = ast.Call
else:
ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
def ast_Call(a, b, c):
return ast.Call(a, b, c, None, None)
class AssertionRewritingHook(object):
@@ -51,6 +51,7 @@ class AssertionRewritingHook(object):
self.fnpats = config.getini("python_files")
self.session = None
self.modules = {}
self._rewritten_names = set()
self._register_with_pkg_resources()
self._must_rewrite = set()
@@ -79,7 +80,12 @@ class AssertionRewritingHook(object):
tp = desc[2]
if tp == imp.PY_COMPILED:
if hasattr(imp, "source_from_cache"):
fn = imp.source_from_cache(fn)
try:
fn = imp.source_from_cache(fn)
except ValueError:
# Python 3 doesn't like orphaned but still-importable
# .pyc files.
fn = fn[:-1]
else:
fn = fn[:-1]
elif tp != imp.PY_SOURCE:
@@ -92,6 +98,8 @@ class AssertionRewritingHook(object):
if not self._should_rewrite(name, fn_pypath, state):
return None
self._rewritten_names.add(name)
# The requested module looks like a test file, so rewrite it. This is
# the most magical part of the process: load the source, rewrite the
# asserts, and load the rewritten source. We also cache the rewritten
@@ -155,16 +163,12 @@ class AssertionRewritingHook(object):
# modules not passed explicitly on the command line are only
# rewritten if they match the naming convention for test files
for pat in self.fnpats:
# use fnmatch instead of fn_pypath.fnmatch because the
# latter might trigger an import to fnmatch.fnmatch
# internally, which would cause this method to be
# called recursively
if fnmatch(fn_pypath.basename, pat):
if fn_pypath.fnmatch(pat):
state.trace("matched test file %r" % (fn,))
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
@@ -178,14 +182,15 @@ class AssertionRewritingHook(object):
"""
already_imported = set(names).intersection(set(sys.modules))
if already_imported:
self._warn_already_imported(already_imported)
for name in already_imported:
if name not in self._rewritten_names:
self._warn_already_imported(name)
self._must_rewrite.update(names)
def _warn_already_imported(self, names):
def _warn_already_imported(self, name):
self.config.warn(
'P1',
'Modules are already imported so can not be re-written: %s' %
','.join(names))
'Module already imported so can not be re-written: %s' % name)
def load_module(self, name):
# If there is an existing module object named 'fullname' in
@@ -205,13 +210,12 @@ class AssertionRewritingHook(object):
mod.__cached__ = pyc
mod.__loader__ = self
py.builtin.exec_(co, mod.__dict__)
except:
del sys.modules[name]
except: # noqa
if name in sys.modules:
del sys.modules[name]
raise
return sys.modules[name]
def is_package(self, name):
try:
fd, fn, desc = imp.find_module(name)
@@ -256,7 +260,7 @@ def _write_pyc(state, co, source_stat, pyc):
fp = open(pyc, "wb")
except IOError:
err = sys.exc_info()[1].errno
state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
state.trace("error writing pyc file at %s: errno=%s" % (pyc, err))
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, __pycache__ being a
# file etc.
@@ -271,12 +275,14 @@ def _write_pyc(state, co, source_stat, pyc):
fp.close()
return True
RN = "\r\n".encode("utf-8")
N = "\n".encode("utf-8")
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
BOM_UTF8 = '\xef\xbb\xbf'
def _rewrite_test(config, fn):
"""Try to read and rewrite *fn* and return the code object."""
state = config._assertstate
@@ -301,7 +307,7 @@ def _rewrite_test(config, fn):
end2 = source.find("\n", end1 + 1)
if (not source.startswith(BOM_UTF8) and
cookie_re.match(source[0:end1]) is None and
cookie_re.match(source[end1 + 1:end2]) is None):
cookie_re.match(source[end1 + 1:end2]) is None):
if hasattr(state, "_indecode"):
# encodings imported us again, so don't rewrite.
return None, None
@@ -314,10 +320,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:
@@ -326,7 +328,7 @@ def _rewrite_test(config, fn):
return None, None
rewrite_asserts(tree, fn, config)
try:
co = compile(tree, fn.strpath, "exec")
co = compile(tree, fn.strpath, "exec", dont_inherit=True)
except SyntaxError:
# It's possible that this error is from some bug in the
# assertion rewriting, but I don't know of a fast way to tell.
@@ -334,6 +336,7 @@ def _rewrite_test(config, fn):
return None, None
return stat, co
def _make_rewritten_pyc(state, source_stat, pyc, co):
"""Try to dump rewritten code to *pyc*."""
if sys.platform.startswith("win"):
@@ -347,6 +350,7 @@ def _make_rewritten_pyc(state, source_stat, pyc, co):
if _write_pyc(state, co, source_stat, proc_pyc):
os.rename(proc_pyc, pyc)
def _read_pyc(source, pyc, trace=lambda x: None):
"""Possibly read a pytest pyc containing rewritten code.
@@ -397,14 +401,15 @@ 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"))
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
def _format_assertmsg(obj):
"""Format the custom assertion message given.
@@ -418,32 +423,35 @@ 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~"))
return s
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('%%'))
def _call_reprcompare(ops, results, expls, each_obj):
for i, res, expl in zip(range(len(ops)), results, expls):
try:
@@ -477,7 +485,7 @@ binop_map = {
ast.Mult: "*",
ast.Div: "/",
ast.FloorDiv: "//",
ast.Mod: "%%", # escaped for string formatting
ast.Mod: "%%", # escaped for string formatting
ast.Eq: "==",
ast.NotEq: "!=",
ast.Lt: "<",
@@ -583,23 +591,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
@@ -625,6 +636,9 @@ class AssertionRewriter(ast.NodeVisitor):
not isinstance(field, ast.expr)):
nodes.append(field)
def is_rewrite_disabled(self, docstring):
return "PYTEST_DONT_REWRITE" in docstring
def variable(self):
"""Get a new variable."""
# Use a character invalid in python identifiers to avoid clashing.
@@ -717,7 +731,7 @@ class AssertionRewriter(ast.NodeVisitor):
if isinstance(assert_.test, ast.Tuple) and self.config is not None:
fslocation = (self.module_path, assert_.lineno)
self.config.warn('R1', 'assertion is always true, perhaps '
'remove parentheses?', fslocation=fslocation)
'remove parentheses?', fslocation=fslocation)
self.statements = []
self.variables = []
self.variable_counter = itertools.count()
@@ -781,7 +795,7 @@ class AssertionRewriter(ast.NodeVisitor):
if i:
fail_inner = []
# cond is set in a prior loop iteration below
self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
self.on_failure = fail_inner
self.push_format_context()
res, expl = self.visit(v)
@@ -833,7 +847,7 @@ class AssertionRewriter(ast.NodeVisitor):
new_kwargs.append(ast.keyword(keyword.arg, res))
if keyword.arg:
arg_expls.append(keyword.arg + "=" + expl)
else: ## **args have `arg` keywords with an .arg of None
else: # **args have `arg` keywords with an .arg of None
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
@@ -887,7 +901,6 @@ class AssertionRewriter(ast.NodeVisitor):
else:
visit_Call = visit_Call_legacy
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)

View File

@@ -0,0 +1,102 @@
"""
Utilities for truncating assertion output.
Current default behaviour is to truncate assertion explanations at
~8 terminal lines, unless running in "-vv" mode or running on CI.
"""
from __future__ import absolute_import, division, print_function
import os
import six
DEFAULT_MAX_LINES = 8
DEFAULT_MAX_CHARS = 8 * 80
USAGE_MSG = "use '-vv' to show"
def truncate_if_required(explanation, item, max_length=None):
"""
Truncate this assertion explanation if the given test item is eligible.
"""
if _should_truncate_item(item):
return _truncate_explanation(explanation)
return explanation
def _should_truncate_item(item):
"""
Whether or not this test item is eligible for truncation.
"""
verbose = item.config.option.verbose
return verbose < 2 and not _running_on_ci()
def _running_on_ci():
"""Check if we're currently running on a CI system."""
env_vars = ['CI', 'BUILD_NUMBER']
return any(var in os.environ for var in env_vars)
def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
"""
Truncate given list of strings that makes up the assertion explanation.
Truncates to either 8 lines, or 640 characters - whichever the input reaches
first. The remaining lines will be replaced by a usage message.
"""
if max_lines is None:
max_lines = DEFAULT_MAX_LINES
if max_chars is None:
max_chars = DEFAULT_MAX_CHARS
# Check if truncation required
input_char_count = len("".join(input_lines))
if len(input_lines) <= max_lines and input_char_count <= max_chars:
return input_lines
# Truncate first to max_lines, and then truncate to max_chars if max_chars
# is exceeded.
truncated_explanation = input_lines[:max_lines]
truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
# Add ellipsis to final line
truncated_explanation[-1] = truncated_explanation[-1] + "..."
# Append useful message to explanation
truncated_line_count = len(input_lines) - len(truncated_explanation)
truncated_line_count += 1 # Account for the part-truncated final line
msg = '...Full output truncated'
if truncated_line_count == 1:
msg += ' ({0} line hidden)'.format(truncated_line_count)
else:
msg += ' ({0} lines hidden)'.format(truncated_line_count)
msg += ", {0}" .format(USAGE_MSG)
truncated_explanation.extend([
six.text_type(""),
six.text_type(msg),
])
return truncated_explanation
def _truncate_by_char_count(input_lines, max_chars):
# Check if truncation required
if len("".join(input_lines)) <= max_chars:
return input_lines
# Find point at which input length exceeds total allowed length
iterated_char_count = 0
for iterated_index, input_line in enumerate(input_lines):
if iterated_char_count + len(input_line) > max_chars:
break
iterated_char_count += len(input_line)
# Create truncated explanation with modified final line
truncated_result = input_lines[:iterated_index]
final_line = input_lines[iterated_index]
if final_line:
final_line_truncate_point = max_chars - iterated_char_count
final_line = final_line[:final_line_truncate_point]
truncated_result.append(final_line)
return truncated_result

View File

@@ -1,15 +1,17 @@
"""Utilities for assertion debugging"""
from __future__ import absolute_import, division, print_function
import pprint
import _pytest._code
import py
import six
try:
from collections import Sequence
except ImportError:
Sequence = list
BuiltinAssertionError = py.builtin.builtins.AssertionError
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
@@ -52,11 +54,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
@@ -81,7 +83,7 @@ def _format_lines(lines):
stack.append(len(result))
stackcnt[-1] += 1
stackcnt.append(0)
result.append(u(' +') + u(' ')*(len(stack)-1) + s + line[1:])
result.append(u(' +') + u(' ') * (len(stack) - 1) + s + line[1:])
elif line.startswith('}'):
stack.pop()
stackcnt.pop()
@@ -90,7 +92,7 @@ def _format_lines(lines):
assert line[0] in ['~', '>']
stack[-1] += 1
indent = len(stack) if line.startswith('~') else len(stack) - 1
result.append(u(' ')*indent + line[1:])
result.append(u(' ') * indent + line[1:])
assert len(stack) == 1
return result
@@ -105,16 +107,22 @@ except NameError:
def assertrepr_compare(config, op, left, right):
"""Return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
left_repr = py.io.saferepr(left, maxsize=int(width // 2))
right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
not isinstance(x, basestring))
istext = lambda x: isinstance(x, basestring)
isdict = lambda x: isinstance(x, dict)
isset = lambda x: isinstance(x, (set, frozenset))
def issequence(x):
return (isinstance(x, (list, tuple, Sequence)) and not isinstance(x, basestring))
def istext(x):
return isinstance(x, basestring)
def isdict(x):
return isinstance(x, dict)
def isset(x):
return isinstance(x, (set, frozenset))
def isiterable(obj):
try:
@@ -167,9 +175,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
@@ -256,8 +264,8 @@ def _compare_eq_dict(left, right, verbose=False):
explanation = []
common = set(left).intersection(set(right))
same = dict((k, left[k]) for k in common if left[k] == right[k])
if same and not verbose:
explanation += [u('Omitting %s identical items, use -v to show') %
if same and verbose < 2:
explanation += [u('Omitting %s identical items, use -vv to show') %
len(same)]
elif same:
explanation += [u('Common items:')]
@@ -284,7 +292,7 @@ def _compare_eq_dict(left, right, verbose=False):
def _notin_text(term, text, verbose=False):
index = text.find(term)
head = text[:index]
tail = text[index+len(term):]
tail = text[index + len(term):]
correct_text = head + tail
diff = _diff_text(correct_text, text, verbose)
newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]

View File

@@ -1,20 +1,21 @@
"""
merged implementation of the cache provider
the name cache was not choosen to ensure pluggy automatically
the name cache was not chosen to ensure pluggy automatically
ignores the external pytest-cache
"""
from __future__ import absolute_import, division, print_function
import py
import pytest
import json
import os
from os.path import sep as _sep, altsep as _altsep
class Cache(object):
def __init__(self, config):
self.config = config
self._cachedir = config.rootdir.join(".cache")
self._cachedir = Cache.cache_dir_from_config(config)
self.trace = config.trace.root.get("cache")
if config.getvalue("cacheclear"):
self.trace("clearing cachedir")
@@ -22,6 +23,16 @@ class Cache(object):
self._cachedir.remove()
self._cachedir.mkdir()
@staticmethod
def cache_dir_from_config(config):
cache_dir = config.getini("cache_dir")
cache_dir = os.path.expanduser(cache_dir)
cache_dir = os.path.expandvars(cache_dir)
if os.path.isabs(cache_dir):
return py.path.local(cache_dir)
else:
return config.rootdir.join(cache_dir)
def makedir(self, name):
""" return a directory path object with the given name. If the
directory does not yet exist, it will be created. You can use it
@@ -89,31 +100,31 @@ class Cache(object):
class LFPlugin:
""" 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)
if self.active:
self.lastfailed = config.cache.get("cache/lastfailed", {})
else:
self.lastfailed = {}
self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = None
def pytest_report_header(self):
def pytest_report_collectionfinish(self):
if self.active:
if not self.lastfailed:
if not self._previously_failed_count:
mode = "run all (no recorded failures)"
else:
mode = "rerun last %d failures%s" % (
len(self.lastfailed),
" first" if self.config.getvalue("failedfirst") else "")
noun = 'failure' if self._previously_failed_count == 1 else 'failures'
suffix = " first" if self.config.getvalue("failedfirst") else ""
mode = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
)
return "run-last-failure: %s" % mode
def pytest_runtest_logreport(self, report):
if report.failed and "xfail" not in report.keywords:
if (report.when == 'call' and report.passed) or report.skipped:
self.lastfailed.pop(report.nodeid, None)
elif report.failed:
self.lastfailed[report.nodeid] = True
elif not report.failed:
if report.when == "call":
self.lastfailed.pop(report.nodeid, None)
def pytest_collectreport(self, report):
passed = report.outcome in ('passed', 'skipped')
@@ -135,22 +146,24 @@ class LFPlugin:
previously_failed.append(item)
else:
previously_passed.append(item)
if not previously_failed and previously_passed:
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
pass
elif self.config.getvalue("failedfirst"):
items[:] = previously_failed + previously_passed
else:
return
if self.config.getvalue("lf"):
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
else:
items[:] = previously_failed + previously_passed
def pytest_sessionfinish(self, session):
config = self.config
if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
return
prev_failed = config.cache.get("cache/lastfailed", None) is not None
if (session.testscollected and prev_failed) or self.lastfailed:
saved_lastfailed = config.cache.get("cache/lastfailed", {})
if saved_lastfailed != self.lastfailed:
config.cache.set("cache/lastfailed", self.lastfailed)
@@ -171,6 +184,9 @@ def pytest_addoption(parser):
group.addoption(
'--cache-clear', action='store_true', dest="cacheclear",
help="remove all cache contents at start of test run.")
parser.addini(
"cache_dir", default='.cache',
help="cache directory path.")
def pytest_cmdline_main(config):
@@ -179,7 +195,6 @@ def pytest_cmdline_main(config):
return wrap_session(config, cacheshow)
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
config.cache = Cache(config)
@@ -219,12 +234,12 @@ def cacheshow(config, session):
basedir = config.cache._cachedir
vdir = basedir.join("v")
tw.sep("-", "cache values")
for valpath in vdir.visit(lambda x: x.isfile()):
for valpath in sorted(vdir.visit(lambda x: x.isfile())):
key = valpath.relto(vdir).replace(valpath.sep, "/")
val = config.cache.get(key, dummy)
if val is dummy:
tw.line("%s contains unreadable content, "
"will be ignored" % key)
"will be ignored" % key)
else:
tw.line("%s contains:" % key)
stream = py.io.TextIO()
@@ -235,8 +250,8 @@ def cacheshow(config, session):
ddir = basedir.join("d")
if ddir.isdir() and ddir.listdir():
tw.sep("-", "cache directories")
for p in basedir.join("d").visit():
#if p.check(dir=1):
for p in sorted(basedir.join("d").visit()):
# if p.check(dir=1):
# print("%s/" % p.relto(basedir))
if p.isfile():
key = p.relto(basedir)

View File

@@ -2,18 +2,20 @@
per-test stdout/stderr capturing mechanism.
"""
from __future__ import with_statement
from __future__ import absolute_import, division, print_function
import collections
import contextlib
import sys
import os
import io
from io import UnsupportedOperation
from tempfile import TemporaryFile
import py
import six
import pytest
from _pytest.compat import CaptureIO
from py.io import TextIO
unicode = py.builtin.text
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -32,14 +34,17 @@ def pytest_addoption(parser):
@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args):
_readline_workaround()
ns = early_config.known_args_namespace
if ns.capture == "fd":
_py36_windowsconsoleio_workaround(sys.stdout)
_colorama_workaround()
_readline_workaround()
pluginmanager = early_config.pluginmanager
capman = CaptureManager(ns.capture)
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():
@@ -48,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:
"""
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":
@@ -70,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()
@@ -94,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))
@@ -121,63 +143,132 @@ 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
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
which return a ``(out, err)`` tuple. ``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/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
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
@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:
@@ -204,12 +295,14 @@ class CaptureFixture:
@contextlib.contextmanager
def disabled(self):
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"):
@@ -233,12 +326,13 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
class EncodedFile(object):
errors = "strict" # possibly needed by py3 code (issue555)
def __init__(self, buffer, encoding):
self.buffer = buffer
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)
@@ -246,10 +340,18 @@ class EncodedFile(object):
data = ''.join(linelist)
self.write(data)
@property
def name(self):
"""Ensure that file.name is a string."""
return repr(self.buffer)
def __getattr__(self, name):
return getattr(object.__getattribute__(self, "buffer"), name)
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
class MultiCapture(object):
out = err = in_ = None
@@ -310,14 +412,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:
__init__ = start = done = suspend = resume = lambda *args: None
class FDCapture:
""" Capture IO to/from a given os-level filedescriptor. """
class FDCaptureBinary:
"""Capture IO to/from a given os-level filedescriptor.
snap() produces `bytes`
"""
def __init__(self, targetfd, tmpfile=None):
self.targetfd = targetfd
@@ -356,17 +463,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,
@@ -387,11 +488,24 @@ class FDCapture:
def writeorg(self, data):
""" write to original file descriptor. """
if py.builtin._istext(data):
data = data.encode("utf8") # XXX use encoding of original stream
if isinstance(data, six.text_type):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
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:
def __init__(self, fd, tmpfile=None):
name = patchsysdict[fd]
@@ -401,17 +515,16 @@ class SysCapture:
if name == "stdin":
tmpfile = DontReadFromInput()
else:
tmpfile = TextIO()
tmpfile = CaptureIO()
self.tmpfile = tmpfile
def start(self):
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):
@@ -430,6 +543,14 @@ class SysCapture:
self._old.flush()
class SysCaptureBinary(SysCapture):
def snap(self):
res = self.tmpfile.buffer.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
class DontReadFromInput:
"""Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured
@@ -447,7 +568,8 @@ class DontReadFromInput:
__iter__ = read
def fileno(self):
raise ValueError("redirected Stdin is pseudofile, has no fileno()")
raise UnsupportedOperation("redirected stdin is pseudofile, "
"has no fileno()")
def isatty(self):
return False
@@ -457,12 +579,30 @@ class DontReadFromInput:
@property
def buffer(self):
if sys.version_info >= (3,0):
if sys.version_info >= (3, 0):
return self
else:
raise AttributeError('redirected stdin has no attribute buffer')
def _colorama_workaround():
"""
Ensure colorama is imported so that it attaches to the correct stdio
handles on Windows.
colorama uses the terminal on import time. So if something does the
first import of colorama while I/O capture is active, colorama will
fail in various ways.
"""
if not sys.platform.startswith('win32'):
return
try:
import colorama # noqa
except ImportError:
pass
def _readline_workaround():
"""
Ensure readline is imported so that it attaches to the correct stdio
@@ -488,3 +628,56 @@ def _readline_workaround():
import readline # noqa
except ImportError:
pass
def _py36_windowsconsoleio_workaround(stream):
"""
Python 3.6 implemented unicode console handling for Windows. This works
by reading/writing to the raw console handle using
``{Read,Write}ConsoleW``.
The problem is that we are going to ``dup2`` over the stdio file
descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
handles used by Python to write to the console. Though there is still some
weirdness and the console handle seems to only be closed randomly and not
on the first call to ``CloseHandle``, or maybe it gets reopened with the
same handle value when we suspend capturing.
The workaround in this case will reopen stdio with a different fd which
also means a different handle by replicating the logic in
"Py_lifecycle.c:initstdio/create_stdio".
:param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
here as parameter for unittesting purposes.
See https://github.com/pytest-dev/py/issues/103
"""
if not sys.platform.startswith('win32') or sys.version_info[:2] < (3, 6):
return
# bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
if not hasattr(stream, 'buffer'):
return
buffered = hasattr(stream.buffer, 'raw')
raw_stdout = stream.buffer.raw if buffered else stream.buffer
if not isinstance(raw_stdout, io._WindowsConsoleIO):
return
def _reopen_stdio(f, mode):
if not buffered and mode[0] == 'w':
buffering = 0
else:
buffering = -1
return io.TextIOWrapper(
open(os.dup(f.fileno()), mode, buffering),
f.encoding,
f.errors,
f.newlines,
f.line_buffering)
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, 'rb')
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, 'wb')
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, 'wb')

View File

@@ -1,17 +1,18 @@
"""
python version compatibility code
"""
import sys
import inspect
import types
import re
from __future__ import absolute_import, division, print_function
import codecs
import functools
import inspect
import re
import sys
import py
import _pytest
import _pytest
from _pytest.outcomes import TEST_OUTCOME
try:
import enum
@@ -19,19 +20,28 @@ except ImportError: # pragma: no cover
# Only available in Python 3.4+ or as a backport
enum = None
_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()
if hasattr(inspect, 'signature'):
def _format_args(func):
return str(inspect.signature(func))
else:
def _format_args(func):
return inspect.formatargspec(*inspect.getargspec(func))
PY35 = sys.version_info[:2] >= (3, 5)
PY36 = sys.version_info[:2] >= (3, 6)
MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError'
def _format_args(func):
return str(signature(func))
isfunction = inspect.isfunction
isclass = inspect.isclass
@@ -42,20 +52,26 @@ REGEX_TYPE = type(re.compile(''))
def is_generator(func):
try:
return _pytest._code.getrawcode(func).co_flags & 32 # generator function
except AttributeError: # builtin functions have no bytecode
# assume them to not be generators
return False
genfunc = inspect.isgeneratorfunction(func)
return genfunc and not iscoroutinefunction(func)
def iscoroutinefunction(func):
"""Return True if func is a decorated coroutine function.
Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
which in turns also initializes the "logging" module as side-effect (see issue #8).
"""
return (getattr(func, '_is_coroutine', False) or
(hasattr(inspect, 'iscoroutinefunction') and inspect.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):
fn = fn.relto(curdir)
return "%s:%d" %(fn, lineno+1)
return "%s:%d" % (fn, lineno + 1)
def num_mock_patch_args(function):
@@ -66,55 +82,68 @@ def num_mock_patch_args(function):
mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
if mock 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 is mock.DEFAULT])
return len(patchings)
def getfuncargnames(function, startindex=None):
# 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.
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.
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))
@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
STRING_TYPES = bytes, str
UNICODE_TYPES = str,
def _escape_strings(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:
@@ -133,19 +162,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,
def _escape_strings(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.
@@ -167,8 +191,18 @@ def get_real_func(obj):
""" gets the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial.
"""
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
start_obj = obj
for i in range(100):
new_obj = getattr(obj, '__wrapped__', None)
if new_obj is None:
break
obj = new_obj
else:
raise ValueError(
("could not find real function of {start}"
"\nstopped at {current}").format(
start=py.io.saferepr(start_obj),
current=py.io.saferepr(obj)))
if isinstance(obj, functools.partial):
obj = obj.func
return obj
@@ -188,21 +222,20 @@ 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):
""" Like getattr but return default upon any Exception.
""" Like getattr but return default upon any Exception or any OutcomeException.
Attribute access can potentially fail for 'evil' Python objects.
See issue214
See issue #214.
It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
instead of Exception (for more details check #2707)
"""
try:
return getattr(object, name, default)
except Exception:
except TEST_OUTCOME:
return default
@@ -213,4 +246,77 @@ def _is_unittest_unexpected_success_a_failure():
Changed in version 3.4: Returns False if there were any
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
"""
return sys.version_info >= (3, 4)
return sys.version_info >= (3, 4)
if _PY3:
def safe_str(v):
"""returns v as string"""
return str(v)
else:
def safe_str(v):
"""returns v as string, converting to ascii if necessary"""
try:
return str(v)
except UnicodeError:
if not isinstance(v, unicode):
v = unicode(v)
errors = 'replace'
return v.encode('utf-8', errors)
COLLECT_FAKEMODULE_ATTRIBUTES = (
'Collector',
'Module',
'Generator',
'Function',
'Instance',
'Session',
'Item',
'Class',
'File',
'_fillfuncargs',
)
def _setup_collect_fakemodule():
from types import ModuleType
import pytest
pytest.collect = ModuleType('pytest.collect')
pytest.collect.__all__ = [] # used for setns
for attr in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attr, getattr(pytest, attr))
if _PY2:
# Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
from py.io import TextIO
class CaptureIO(TextIO):
@property
def encoding(self):
return getattr(self, '_encoding', 'UTF-8')
else:
import io
class CaptureIO(io.TextIOWrapper):
def __init__(self):
super(CaptureIO, self).__init__(
io.BytesIO(),
encoding='UTF-8', newline='', write_through=True,
)
def getvalue(self):
return self.buffer.getvalue().decode('UTF-8')
class FuncargnamesCompatAttr(object):
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
return self.fixturenames

View File

@@ -1,18 +1,21 @@
""" command line options, ini-file and conftest.py processing. """
from __future__ import absolute_import, division, print_function
import argparse
import shlex
import traceback
import types
import warnings
import pkg_resources
import six
import py
# DON't import pytest here because it causes import cycle troubles
import sys, os
import sys
import os
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")
hookspec = HookspecMarker("pytest")
@@ -53,40 +56,63 @@ def main(args=None, plugins=None):
return 4
else:
try:
config.pluginmanager.check_pending()
return config.hook.pytest_cmdline_main(config=config)
finally:
config._ensure_unconfigure()
except UsageError as e:
for msg in e.args:
sys.stderr.write("ERROR: %s\n" %(msg,))
sys.stderr.write("ERROR: %s\n" % (msg,))
return 4
class cmdline: # compatibility namespace
main = staticmethod(main)
class UsageError(Exception):
""" error in pytest usage or invocation"""
_preinit = []
class PrintHelp(Exception):
"""Raised when pytest should print it's help to skip the rest of the
argument parsing and validation."""
pass
def filename_arg(path, optname):
""" Argparse type validator for filename arguments.
:path: path of filename
:optname: name of the option
"""
if os.path.isdir(path):
raise UsageError("{0} must be a filename, given: {1}".format(optname, path))
return path
def directory_arg(path, optname):
"""Argparse type validator for directory arguments.
:path: path of directory
:optname: name of the option
"""
if not os.path.isdir(path):
raise UsageError("{0} must be a directory, given: {1}".format(optname, path))
return path
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").split()
"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 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)
@@ -94,6 +120,7 @@ def get_config():
pluginmanager.import_plugin(spec)
return config
def get_plugin_manager():
"""
Obtain a new instance of the
@@ -105,6 +132,7 @@ def get_plugin_manager():
"""
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
warning = None
if args is None:
@@ -122,14 +150,14 @@ 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)
if warning:
config.warn('C1', warning)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
pluginmanager=pluginmanager, args=args)
except BaseException:
config._ensure_unconfigure()
raise
@@ -137,13 +165,14 @@ def _prepareconfig(args=None, plugins=None):
class PytestPluginManager(PluginManager):
"""
Overwrites :py:class:`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
``pytest_plugins`` global variables found in plugins being loaded;
* ``conftest.py`` loading during start-up;
"""
def __init__(self):
super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
self._conftest_plugins = set()
@@ -174,7 +203,8 @@ class PytestPluginManager(PluginManager):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
instead.
"""
warning = dict(code="I2",
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
@@ -203,7 +233,7 @@ class PytestPluginManager(PluginManager):
def parse_hookspec_opts(self, module_or_class, name):
opts = super(PytestPluginManager, self).parse_hookspec_opts(
module_or_class, name)
module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
@@ -211,22 +241,18 @@ 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 == 'pytest_catchlog':
self._warn('pytest-catchlog plugin has been merged into the core, '
'please remove it from your requirements.')
return
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self))
kwargs=dict(plugin=plugin, manager=self))
if isinstance(plugin, types.ModuleType):
self.consider_module(plugin)
return ret
def getplugin(self, name):
@@ -241,11 +267,11 @@ class PytestPluginManager(PluginManager):
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
# we should remove tryfirst/trylast as markers
config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
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.")
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
@@ -269,7 +295,7 @@ class PytestPluginManager(PluginManager):
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
if namespace.confcutdir else None
self._noconftest = namespace.noconftest
testpaths = namespace.file_or_dir
foundanchor = False
@@ -280,7 +306,7 @@ class PytestPluginManager(PluginManager):
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
@@ -347,7 +373,7 @@ class PytestPluginManager(PluginManager):
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" %(mod))
self.trace("loaded conftestmodule %r" % (mod))
self.consider_conftest(mod)
return mod
@@ -357,7 +383,7 @@ class PytestPluginManager(PluginManager):
#
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
for opt1, opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
@@ -371,56 +397,68 @@ class PytestPluginManager(PluginManager):
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
self.consider_module(conftestmodule)
self.register(conftestmodule, name=conftestmodule.__file__)
def consider_env(self):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
def consider_module(self, mod):
plugins = getattr(mod, 'pytest_plugins', [])
if isinstance(plugins, str):
plugins = [plugins]
self.rewrite_hook.mark_rewrite(*plugins)
self._import_plugin_specs(plugins)
self._import_plugin_specs(getattr(mod, 'pytest_plugins', []))
def _import_plugin_specs(self, spec):
if spec:
if isinstance(spec, str):
spec = spec.split(",")
for import_spec in spec:
self.import_plugin(import_spec)
plugins = _get_plugin_specs_as_list(spec)
for import_spec in plugins:
self.import_plugin(import_spec)
def import_plugin(self, modname):
# most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, str)
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:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
else:
importspec = modname
self.rewrite_hook.mark_rewrite(importspec)
try:
__import__(importspec)
except ImportError as e:
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
# 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
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 Exception as e:
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warn("skipped plugin %r: %s" %((modname, e.msg)))
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
else:
mod = sys.modules[importspec]
self.register(mod, modname)
self.consider_module(mod)
def _get_plugin_specs_as_list(specs):
"""
Parses a list of "plugin specs" and returns a list of plugin names.
Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
which case it is returned as a list. Specs can also be `None` in which case an
empty list is returned.
"""
if specs is not None:
if isinstance(specs, str):
specs = specs.split(',') if specs else []
if not isinstance(specs, (list, tuple)):
raise UsageError("Plugin specs must be a ','-separated string or a "
"list/tuple of strings for plugin names. Given: %r" % specs)
return list(specs)
return []
class Parser:
@@ -464,7 +502,7 @@ class Parser:
for i, grp in enumerate(self._groups):
if grp.name == after:
break
self._groups.insert(i+1, group)
self._groups.insert(i + 1, group)
return group
def addoption(self, *opts, **attrs):
@@ -502,7 +540,7 @@ class Parser:
a = option.attrs()
arggroup.add_argument(*n, **a)
# bash like autocompletion for dirs (appending '/')
optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
optparser.add_argument(FILE_OR_DIR, nargs='*').completer = filescompleter
return optparser
def parse_setoption(self, args, option, namespace=None):
@@ -590,11 +628,11 @@ 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.'
' For parsearg this is optional and when supplied '
' For parsearg this is optional and when supplied'
' should be a type.'
' (options: %s)' % (typ, names),
DeprecationWarning,
@@ -646,7 +684,7 @@ class Argument:
if self._attrs.get('help'):
a = self._attrs['help']
a = a.replace('%default', '%(default)s')
#a = a.replace('%prog', '%(prog)s')
# a = a.replace('%prog', '%(prog)s')
self._attrs['help'] = a
return self._attrs
@@ -730,7 +768,7 @@ class MyOptionParser(argparse.ArgumentParser):
extra_info = {}
self._parser = parser
argparse.ArgumentParser.__init__(self, usage=parser._usage,
add_help=False, formatter_class=DropShorterLongHelpFormatter)
add_help=False, formatter_class=DropShorterLongHelpFormatter)
# extra_info is a dict of (param -> value) to display if there's
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
@@ -758,9 +796,10 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
- shortcut if there are only two options and one of them is a short one
- cache result on action object as this is called at least 2 times
"""
def _format_action_invocation(self, action):
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != '-': # only optional arguments
if orgstr and orgstr[0] != '-': # only optional arguments
return orgstr
res = getattr(action, '_formatted_action_invocation', None)
if res:
@@ -771,7 +810,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
action._formatted_action_invocation = orgstr
return orgstr
return_list = []
option_map = getattr(action, 'map_long_option', {})
option_map = getattr(action, 'map_long_option', {})
if option_map is None:
option_map = {}
short_long = {}
@@ -789,38 +828,56 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
short_long[shortened] = xxoption
# now short_long has been filled out to the longest with dashes
# **and** we keep the right option ordering from add_argument
for option in options: #
for option in options:
if len(option) == 2 or option[2] == ' ':
return_list.append(option)
if option[2:] == short_long.get(option.replace('-', '')):
return_list.append(option.replace(' ', '='))
return_list.append(option.replace(' ', '=', 1))
action._formatted_action_invocation = ', '.join(return_list)
return action._formatted_action_invocation
def _ensure_removed_sysmodule(modname):
try:
del sys.modules[modname]
except KeyError:
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__,)
return "<CmdOptions %r>" % (self.__dict__,)
def copy(self):
return CmdOptions(self.__dict__)
class Notset:
def __repr__(self):
return "<NOTSET>"
notset = Notset()
FILE_OR_DIR = 'file_or_dir'
def _iter_rewritable_modules(package_files):
for fn in package_files:
is_simple_module = '/' not in fn and fn.endswith('.py')
is_package = fn.count('/') == 1 and fn.endswith('__init__.py')
if is_simple_module:
module_name, _ = os.path.splitext(fn)
yield module_name
elif is_package:
package_name = os.path.dirname(fn)
yield package_name
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
@@ -838,14 +895,17 @@ class Config(object):
self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook
self._inicache = {}
self._override_ini = ()
self._opt2dest = {}
self._cleanup = []
self._warn = self.pluginmanager._warn
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
def do_setns(dic):
import pytest
setns(pytest, dic)
self.hook.pytest_namespace.call_historic(do_setns, {})
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
@@ -868,11 +928,11 @@ class Config(object):
fin = self._cleanup.pop()
fin()
def warn(self, code, message, fslocation=None):
def warn(self, code, message, fslocation=None, nodeid=None):
""" generate a warning for this test session. """
self.hook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
fslocation=fslocation, nodeid=None))
fslocation=fslocation, nodeid=nodeid))
def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw
@@ -888,14 +948,14 @@ class Config(object):
else:
style = "native"
excrepr = excinfo.getrepr(funcargs=True,
showlocals=getattr(option, 'showlocals', False),
style=style,
)
showlocals=getattr(option, 'showlocals', False),
style=style,
)
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.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
def cwd_relative_nodeid(self, nodeid):
@@ -936,8 +996,9 @@ class Config(object):
self.invocation_dir = py.path.local()
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args, entrypoint_name):
def _consider_importhook(self, args):
"""Install the PEP 302 import hook if using assertion re-writing.
Needs to parse the --assert=<mode> option from the commandline
@@ -952,20 +1013,34 @@ class Config(object):
except SystemError:
mode = 'plain'
else:
self.pluginmanager.rewrite_hook = hook
for entrypoint in pkg_resources.iter_entry_points('pytest11'):
for entry in entrypoint.dist._get_metadata('RECORD'):
fn = entry.split(',')[0]
is_simple_module = os.sep not in fn and fn.endswith('.py')
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
if is_simple_module:
module_name, ext = os.path.splitext(fn)
hook.mark_rewrite(module_name)
elif is_package:
package_name = os.path.dirname(fn)
hook.mark_rewrite(package_name)
self._mark_plugins_for_rewrite(hook)
self._warn_about_missing_assertion(mode)
def _mark_plugins_for_rewrite(self, hook):
"""
Given an importhook, mark for rewrite any top-level
modules or packages in the distribution package for
all pytest plugins.
"""
import pkg_resources
self.pluginmanager.rewrite_hook = hook
# 'RECORD' available for plugins installed normally (pip install)
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
# so it shouldn't be an issue
metadata_files = 'RECORD', 'SOURCES.txt'
package_files = (
entry.split(',')[0]
for entrypoint in pkg_resources.iter_entry_points('pytest11')
for metadata in metadata_files
for entry in entrypoint.dist._get_metadata(metadata)
)
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _warn_about_missing_assertion(self, mode):
try:
assert False
@@ -984,15 +1059,15 @@ class Config(object):
"(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()
entrypoint_name = 'pytest11'
self._consider_importhook(args, entrypoint_name)
self._consider_importhook(args)
self.pluginmanager.consider_preparse(args)
self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
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())
if self.known_args_namespace.confcutdir is None and self.inifile:
@@ -1000,7 +1075,7 @@ class Config(object):
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(early_config=self,
args=args, parser=self._parser)
args=args, parser=self._parser)
except ConftestImportFailure:
e = sys.exc_info()[1]
if ns.help or ns.version:
@@ -1018,28 +1093,32 @@ class Config(object):
myver = pytest.__version__.split(".")
if myver < ver:
raise pytest.UsageError(
"%s:%d: requires pytest-%s, actual pytest-%s'" %(
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
"%s:%d: requires pytest-%s, actual pytest-%s'" % (
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args, addopts=True):
# parse given cmdline arguments into this config object.
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
"can only parse cmdline args at most once per Config object")
self._origargs = args
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager))
kwargs=dict(pluginmanager=self.pluginmanager))
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
if not args:
cwd = os.getcwd()
if cwd == self.rootdir:
args = self.getini('testpaths')
self._parser.after_preparse = True
try:
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
if not args:
args = [cwd]
self.args = args
cwd = os.getcwd()
if cwd == self.rootdir:
args = self.getini('testpaths')
if not args:
args = [cwd]
self.args = args
except PrintHelp:
pass
def addinivalue_line(self, name, line):
""" add a line to an ini-file option. The option must have been
@@ -1047,12 +1126,12 @@ class Config(object):
the first line in its value. """
x = self.getini(name)
assert isinstance(x, list)
x.append(line) # modifies the cached list inline
x.append(line) # modifies the cached list inline
def getini(self, name):
""" return configuration value from an :ref:`ini file <inifiles>`. If the
specified name hasn't been registered through a prior
:py:func:`parser.addini <pytest.config.Parser.addini>`
:py:func:`parser.addini <_pytest.config.Parser.addini>`
call (usually from a plugin), a ValueError is raised. """
try:
return self._inicache[name]
@@ -1064,7 +1143,7 @@ class Config(object):
try:
description, type, default = self._parser._inidict[name]
except KeyError:
raise ValueError("unknown configuration value: %r" %(name,))
raise ValueError("unknown configuration value: %r" % (name,))
value = self._get_override_ini_value(name)
if value is None:
try:
@@ -1077,10 +1156,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":
@@ -1097,13 +1176,13 @@ 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
@@ -1111,12 +1190,14 @@ class Config(object):
# and -o foo1=bar1 -o foo2=bar2 options
# always use the last item if multiple value set for same ini-name,
# e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
if self.getoption("override_ini", None):
for ini_config_list in self.option.override_ini:
for ini_config in ini_config_list:
for ini_config_list in self._override_ini:
for ini_config in ini_config_list:
try:
(key, user_ini_value) = ini_config.split("=", 1)
if key == name:
value = user_ini_value
except ValueError:
raise UsageError("-o/--override-ini expects option=value style.")
if key == name:
value = user_ini_value
return value
def getoption(self, name, default=notset, skip=False):
@@ -1139,7 +1220,7 @@ class Config(object):
return default
if skip:
import pytest
pytest.skip("no %r option found" %(name,))
pytest.skip("no %r option found" % (name,))
raise ValueError("no option named %r" % (name,))
def getvalue(self, name, path=None):
@@ -1150,12 +1231,14 @@ class Config(object):
""" (deprecated, use getoption(skip=True)) """
return self.getoption(name, skip=True)
def exists(path, ignore=EnvironmentError):
try:
return path.check()
except ignore:
return False
def getcfg(args, warnfunc=None):
"""
Search the list of arguments for a valid ini-file for pytest,
@@ -1190,25 +1273,20 @@ def getcfg(args, warnfunc=None):
return None, None, None
def get_common_ancestor(args):
# args are what we get after early command line parsing (usually
# strings, but can be py.path.local objects as well)
def get_common_ancestor(paths):
common_ancestor = None
for arg in args:
if str(arg)[0] == "-":
continue
p = py.path.local(arg)
if not p.exists():
for path in paths:
if not path.exists():
continue
if common_ancestor is None:
common_ancestor = p
common_ancestor = path
else:
if p.relto(common_ancestor) or p == common_ancestor:
if path.relto(common_ancestor) or path == common_ancestor:
continue
elif common_ancestor.relto(p):
common_ancestor = p
elif common_ancestor.relto(path):
common_ancestor = path
else:
shared = p.common(common_ancestor)
shared = path.common(common_ancestor)
if shared is not None:
common_ancestor = shared
if common_ancestor is None:
@@ -1219,9 +1297,29 @@ def get_common_ancestor(args):
def get_dirs_from_args(args):
return [d for d in (py.path.local(x) for x in args
if not str(x).startswith("-"))
if d.exists()]
def is_option(x):
return str(x).startswith('-')
def get_file_part_from_node_id(x):
return str(x).split('::')[0]
def get_dir_from_path(path):
if path.isdir():
return path
return py.path.local(path.dirname)
# These look like paths but may not exist
possible_paths = (
py.path.local(get_file_part_from_node_id(arg))
for arg in args
if not is_option(arg)
)
return [
get_dir_from_path(path)
for path in possible_paths
if path.exists()
]
def determine_setup(inifile, args, warnfunc=None):
@@ -1244,7 +1342,7 @@ def determine_setup(inifile, args, warnfunc=None):
rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
is_fs_root = os.path.splitdrive(str(rootdir))[1] == '/'
if is_fs_root:
rootdir = ancestor
return rootdir, inifile, inicfg or {}
@@ -1266,7 +1364,7 @@ def setns(obj, dic):
else:
setattr(obj, name, value)
obj.__all__.append(name)
#if obj != pytest:
# if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)

View File

@@ -1,10 +1,8 @@
""" interactive debugging with PDB, the Python Debugger. """
from __future__ import absolute_import
from __future__ import absolute_import, division, print_function
import pdb
import sys
import pytest
def pytest_addoption(parser):
group = parser.getgroup("general")
@@ -16,63 +14,66 @@ def pytest_addoption(parser):
help="start a custom interactive Python debugger on errors. "
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb")
def pytest_namespace():
return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config):
if config.getvalue("usepdb") or config.getvalue("usepdb_cls"):
if config.getvalue("usepdb_cls"):
modname, classname = config.getvalue("usepdb_cls").split(":")
__import__(modname)
pdb_cls = getattr(sys.modules[modname], classname)
else:
pdb_cls = pdb.Pdb
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
if config.getvalue("usepdb_cls"):
modname, classname = config.getvalue("usepdb_cls").split(":")
__import__(modname)
pdb_cls = getattr(sys.modules[modname], classname)
else:
pdb_cls = pdb.Pdb
pytestPDB._pdb_cls = pdb_cls
old = (pdb.set_trace, pytestPDB._pluginmanager)
def fin():
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytest.set_trace
pdb.set_trace = pytestPDB.set_trace
pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config
pytestPDB._pdb_cls = pdb_cls
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None
_config = None
_pdb_cls = pdb.Pdb
def set_trace(self):
@classmethod
def set_trace(cls):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config
frame = sys._getframe().f_back
if self._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager")
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
tw = _pytest.config.create_terminal_writer(self._config)
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)")
self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
self._pdb_cls().set_trace(frame)
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
cls._pdb_cls().set_trace(frame)
class PdbInvoke:
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)
def pytest_internalerror(self, excrepr, excinfo):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
tb = _postmortem_traceback(excinfo)
post_mortem(tb)

View File

@@ -5,10 +5,15 @@ that is planned to be removed in the next pytest release.
Keeping it in a central location makes it easy to track what is deprecated and should
be removed when the time comes.
"""
from __future__ import absolute_import, division, print_function
class RemovedInPytest4Warning(DeprecationWarning):
"""warning class for features removed in pytest 4.0"""
MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
'pass a list of arguments instead.'
'pass a list of arguments instead.'
YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
@@ -21,4 +26,27 @@ SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool
GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
RESULT_LOG = '--result-log is deprecated and scheduled for removal in pytest 4.0'
RESULT_LOG = (
'--result-log is deprecated and scheduled for removal in pytest 4.0.\n'
'See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information.'
)
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"MarkInfo objects are deprecated as they contain the merged marks"
)
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"Applying marks directly to parameters is deprecated,"
" please use pytest.param(..., marks=...) instead.\n"
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)
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."
)

View File

@@ -1,5 +1,5 @@
""" discover and run doctests in modules and test files."""
from __future__ import absolute_import
from __future__ import absolute_import, division, print_function
import traceback
@@ -22,38 +22,47 @@ DOCTEST_REPORT_CHOICES = (
DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
)
def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests',
type="args", default=["ELLIPSIS"])
type="args", default=["ELLIPSIS"])
parser.addini("doctest_encoding", 'encoding used for doctest files', default="utf-8")
group = parser.getgroup("collect")
group.addoption("--doctest-modules",
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
group.addoption("--doctest-report",
type=str.lower, default="udiff",
help="choose another output format for diffs on doctest failure",
choices=DOCTEST_REPORT_CHOICES,
dest="doctestreport")
type=str.lower, default="udiff",
help="choose another output format for diffs on doctest failure",
choices=DOCTEST_REPORT_CHOICES,
dest="doctestreport")
group.addoption("--doctest-glob",
action="append", default=[], metavar="pat",
help="doctests file matching pattern, default: test*.txt",
dest="doctestglob")
action="append", default=[], metavar="pat",
help="doctests file matching pattern, default: test*.txt",
dest="doctestglob")
group.addoption("--doctest-ignore-import-errors",
action="store_true", default=False,
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
action="store_true", default=False,
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
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
@@ -118,7 +127,7 @@ class DoctestItem(pytest.Item):
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]
lines = lines[max(example.lineno - 9, 0):example.lineno + 1]
else:
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
indent = '>>>'
@@ -127,18 +136,18 @@ class DoctestItem(pytest.Item):
indent = '...'
if excinfo.errisinstance(doctest.DocTestFailure):
lines += checker.output_difference(example,
doctestfailure.got, report_choice).split("\n")
doctestfailure.got, report_choice).split("\n")
else:
inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
repr(inner_excinfo.value)]
lines += traceback.format_exception(*excinfo.value.exc_info)
return ReprFailDoctest(reprlocation, lines)
else:
return super(DoctestItem, self).repr_failure(excinfo)
def reportinfo(self):
return self.fspath, None, "[doctest] %s" % self.name
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
def _get_flag_lookup():
@@ -171,15 +180,16 @@ class DoctestTextfile(pytest.Module):
# inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker
text = self.fspath.read()
encoding = self.config.getini("doctest_encoding")
text = self.fspath.read_text(encoding)
filename = str(self.fspath)
name = self.fspath.basename
globs = {'__name__': '__main__'}
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
_fix_spoof_python2(runner, encoding)
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
@@ -215,6 +225,7 @@ class DoctestModule(pytest.Module):
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
@@ -323,6 +334,33 @@ def _get_report_choice(key):
DOCTEST_REPORT_CHOICE_NONE: 0,
}[key]
def _fix_spoof_python2(runner, encoding):
"""
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
should patch only doctests for text files because they don't have a way to declare their
encoding. Doctests in docstrings from Python modules don't have the same problem given that
Python already decoded the strings.
This fixes the problem related in issue #2434.
"""
from _pytest.compat import _PY2
if not _PY2:
return
from doctest import _SpoofOut
class UnicodeSpoof(_SpoofOut):
def getvalue(self):
result = _SpoofOut.getvalue(self)
if encoding:
result = result.decode(encoding)
return result
runner._fakeout = UnicodeSpoof()
@pytest.fixture(scope='session')
def doctest_namespace():
"""

View File

@@ -1,22 +1,38 @@
import sys
from __future__ import absolute_import, division, print_function
import functools
import inspect
import sys
import warnings
from collections import OrderedDict
import attr
import py
from py._code.code import FormattedExcinfo
import py
import pytest
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,
getfslineno, get_real_func,
is_generator, isclass, getimfunc,
getlocation, getfuncargnames,
safe_getattr,
FuncargnamesCompatAttr,
)
from _pytest.outcomes import fail, TEST_OUTCOME
def pytest_sessionstart(session):
import _pytest.python
scopename2class.update({
'class': _pytest.python.Class,
'module': _pytest.python.Module,
'function': _pytest.main.Item,
'session': _pytest.main.Session,
})
session._fixturemanager = FixtureManager(session)
@@ -29,36 +45,24 @@ scope2props["class"] = scope2props["module"] + ("cls",)
scope2props["instance"] = scope2props["class"] + ("instance", )
scope2props["function"] = scope2props["instance"] + ("function", "keywords")
def scopeproperty(name=None, doc=None):
def decoratescope(func):
scopename = name or func.__name__
def provide(self):
if func.__name__ in scope2props[self.scope]:
return func(self)
raise AttributeError("%s not available in %s-scoped context" % (
scopename, self.scope))
return property(provide, None, None, func.__doc__)
return decoratescope
def pytest_namespace():
scopename2class.update({
'class': pytest.Class,
'module': pytest.Module,
'function': pytest.Item,
})
return {
'fixture': fixture,
'yield_fixture': yield_fixture,
'collect': {'_fillfuncargs': fillfixtures}
}
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)
@@ -71,7 +75,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
# XXX we can probably avoid this algorithm if we modify CallSpec2
# to directly care for creating the fixturedefs within its methods.
if not metafunc._calls[0].funcargs:
return # this function call does not have direct parametrization
return # this function call does not have direct parametrization
# collect funcargs of all callspecs into a list of values
arg2params = {}
arg2scope = {}
@@ -101,36 +105,32 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
if scope != "function":
node = get_scope_node(collector, scope)
if node is None:
assert scope == "class" and isinstance(collector, pytest.Module)
assert scope == "class" and isinstance(collector, _pytest.python.Module)
# use module-level collector for class-scope (for now)
node = collector
if node and argname in node._name2pseudofixturedef:
arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
else:
fixturedef = FixtureDef(fixturemanager, '', argname,
get_direct_param_fixture_func,
arg2scope[argname],
valuelist, False, False)
fixturedef = FixtureDef(fixturemanager, '', argname,
get_direct_param_fixture_func,
arg2scope[argname],
valuelist, False, False)
arg2fixturedefs[argname] = [fixturedef]
if node is not None:
node._name2pseudofixturedef[argname] = fixturedef
def getfixturemarker(obj):
""" return fixturemarker or None if it doesn't exist or raised
exceptions."""
try:
return getattr(obj, "_pytestfixturefunction", None)
except KeyboardInterrupt:
raise
except Exception:
except TEST_OUTCOME:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
return None
def get_parametrized_fixture_keys(item, scopenum):
""" return list of keys for all parametrized arguments which match
the specified scope. """
@@ -140,10 +140,10 @@ def get_parametrized_fixture_keys(item, scopenum):
except AttributeError:
pass
else:
# cs.indictes.items() is random order of argnames but
# then again different functions (items) can change order of
# arguments so it doesn't matter much probably
for argname, param_index in cs.indices.items():
# cs.indices.items() is random order of argnames. Need to
# sort this so that different calls to
# get_parametrized_fixture_keys will be deterministic.
for argname, param_index in sorted(cs.indices.items()):
if cs._arg2scopenum[argname] != scopenum:
continue
if scopenum == 0: # session
@@ -165,20 +165,21 @@ def reorder_items(items):
for scopenum in range(0, scopenum_function):
argkeys_cache[scopenum] = d = {}
for item in items:
keys = set(get_parametrized_fixture_keys(item, scopenum))
keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
if keys:
d[item] = keys
return reorder_items_atscope(items, set(), argkeys_cache, 0)
def reorder_items_atscope(items, ignore, argkeys_cache, 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])
slice_items(items, ignore, argkeys_cache[scopenum])
items_before = reorder_items_atscope(
items_before, ignore, argkeys_cache,scopenum+1)
items_before, ignore, argkeys_cache, scopenum + 1)
if items_same is None:
# nothing to reorder in this scope
assert items_other is None
@@ -199,9 +200,9 @@ def slice_items(items, ignore, scoped_argkeys_cache):
for i, item in enumerate(it):
argkeys = scoped_argkeys_cache.get(item)
if argkeys is not None:
argkeys = argkeys.difference(ignore)
if argkeys: # found a slicing key
slicing_argkey = argkeys.pop()
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 = []
@@ -209,7 +210,7 @@ def slice_items(items, ignore, scoped_argkeys_cache):
for item in it:
argkeys = scoped_argkeys_cache.get(item)
if argkeys and slicing_argkey in argkeys and \
slicing_argkey not in ignore:
slicing_argkey not in ignore:
items_same.append(item)
else:
items_other.append(item)
@@ -219,17 +220,6 @@ def slice_items(items, ignore, scoped_argkeys_cache):
return items, None, None, None
class FuncargnamesCompatAttr:
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
return self.fixturenames
def fillfixtures(function):
""" fill missing funcargs for a test function. """
try:
@@ -252,10 +242,10 @@ def fillfixtures(function):
request._fillfixtures()
def get_direct_param_fixture_func(request):
return request.param
class FuncFixtureInfo:
def __init__(self, argnames, names_closure, name2fixturedefs):
self.argnames = argnames
@@ -294,7 +284,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
""" underlying collection node (depends on current request scope)"""
return self._getscopeitem(self.scope)
def _getnextfixturedef(self, argname):
fixturedefs = self._arg2fixturedefs.get(argname, None)
if fixturedefs is None:
@@ -316,7 +305,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@scopeproperty()
def function(self):
""" test function object if the request has a per-function scope. """
@@ -325,7 +313,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty("class")
def cls(self):
""" class (can be None) where the test function was collected. """
clscol = self._pyfuncitem.getparent(pytest.Class)
clscol = self._pyfuncitem.getparent(_pytest.python.Class)
if clscol:
return clscol.obj
@@ -343,7 +331,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty()
def module(self):
""" python module object where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
return self._pyfuncitem.getparent(_pytest.python.Module).obj
@scopeproperty()
def fspath(self):
@@ -412,7 +400,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {} # XXX weakref?
self.config._setupcache = {} # XXX weakref?
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
@@ -443,7 +431,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
from _pytest import deprecated
warnings.warn(
deprecated.GETFUNCARGVALUE,
DeprecationWarning)
DeprecationWarning,
stacklevel=2)
return self.getfixturevalue(argname)
def _get_active_fixturedef(self, argname):
@@ -468,13 +457,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
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):
@@ -506,7 +495,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
source_lineno,
)
)
pytest.fail(msg)
fail(msg)
else:
# indices might not be set if old-style metafunc.addcall() was used
param_index = funcitem.callspec.indices.get(argname, 0)
@@ -529,7 +518,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
val = 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
@@ -539,11 +528,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
if scopemismatch(invoking_scope, requested_scope):
# try to report something helpful
lines = self._factorytraceback()
pytest.fail("ScopeMismatch: You tried to access the %r scoped "
"fixture %r with a %r scoped request object, "
"involved factories\n%s" %(
(requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False)
fail("ScopeMismatch: You tried to access the %r scoped "
"fixture %r with a %r scoped request object, "
"involved factories\n%s" % (
(requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False)
def _factorytraceback(self):
lines = []
@@ -552,7 +541,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = _format_args(factory)
lines.append("%s:%d: def %s%s" %(
lines.append("%s:%d: def %s%s" % (
p, lineno, factory.__name__, args))
return lines
@@ -568,12 +557,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
return node
def __repr__(self):
return "<FixtureRequest for %r>" %(self.node)
return "<FixtureRequest for %r>" % (self.node)
class SubRequest(FixtureRequest):
""" a sub request for handling getting a fixture from a
test function/fixture. """
def __init__(self, request, scope, param, param_index, fixturedef):
self._parent_request = request
self.fixturename = fixturedef.argname
@@ -582,9 +572,8 @@ 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_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index
@@ -593,20 +582,41 @@ 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
which has a lower scope (e.g. a Session one calls a function one)
"""
scopes = "session module class function".split()
scopenum_function = scopes.index("function")
def scopemismatch(currentscope, newscope):
return scopes.index(newscope) > scopes.index(currentscope)
def scope2index(scope, descr, where=None):
"""Look up the index of ``scope`` and raise a descriptive value error
if not defined.
"""
try:
return scopes.index(scope)
except ValueError:
raise ValueError(
"{0} {1}has an unsupported scope value '{2}'".format(
descr, 'from {0} '.format(where) if where else '',
scope)
)
class FixtureLookupError(LookupError):
""" could not return a requested Fixture (missing or invalid). """
def __init__(self, argname, request, msg=None):
self.argname = argname
self.request = request
@@ -629,9 +639,9 @@ class FixtureLookupError(LookupError):
lines, _ = inspect.getsourcelines(get_real_func(function))
except (IOError, IndexError, TypeError):
error_msg = "file %s, line %s: source code not available"
addline(error_msg % (fspath, lineno+1))
addline(error_msg % (fspath, lineno + 1))
else:
addline("file %s, line %s" % (fspath, lineno+1))
addline("file %s, line %s" % (fspath, lineno + 1))
for i, line in enumerate(lines):
line = line.rstrip()
addline(" " + line)
@@ -647,7 +657,7 @@ class FixtureLookupError(LookupError):
if faclist and name not in available:
available.append(name)
msg = "fixture %r not found" % (self.argname,)
msg += "\n available fixtures: %s" %(", ".join(sorted(available)),)
msg += "\n available fixtures: %s" % (", ".join(sorted(available)),)
msg += "\n use 'pytest --fixtures [testpath]' for help on them."
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
@@ -673,15 +683,16 @@ class FixtureLookupErrorRepr(TerminalRepr):
tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker,
line.strip()), red=True)
tw.line()
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
def fail_fixturefunc(fixturefunc, msg):
fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno+1)
location = "%s:%s" % (fs, lineno + 1)
source = _pytest._code.Source(fixturefunc)
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
pytrace=False)
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
pytrace=False)
def call_fixture_func(fixturefunc, request, kwargs):
yieldctx = is_generator(fixturefunc)
@@ -696,15 +707,17 @@ def call_fixture_func(fixturefunc, request, kwargs):
pass
else:
fail_fixturefunc(fixturefunc,
"yield_fixture function has more than one 'yield'")
"yield_fixture function has more than one 'yield'")
request.addfinalizer(teardown)
else:
res = fixturefunc(**kwargs)
return res
class FixtureDef:
""" A container for a factory definition. """
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
unittest=False, ids=None):
self._fixturemanager = fixturemanager
@@ -713,29 +726,44 @@ class FixtureDef:
self.func = func
self.argname = argname
self.scope = scope
self.scopenum = scopes.index(scope or "function")
self.scopenum = scope2index(
scope or "function",
descr='fixture {0}'.format(func.__name__),
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:
func = self._finalizer.pop()
func()
while self._finalizers:
try:
func = self._finalizers.pop()
func()
except: # noqa
exceptions.append(sys.exc_info())
if exceptions:
e = exceptions[0]
del exceptions # ensure we don't keep all frames alive because of the traceback
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()
@@ -743,7 +771,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)
@@ -756,16 +784,17 @@ 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 >" %
(self.argname, self.scope, self.baseid))
def pytest_fixture_setup(fixturedef, request):
""" Execution of fixture setup. """
kwargs = {}
@@ -791,35 +820,42 @@ def pytest_fixture_setup(fixturedef, request):
my_cache_key = request.param_index
try:
result = call_fixture_func(fixturefunc, request, kwargs)
except Exception:
except TEST_OUTCOME:
fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
raise
fixturedef.cached_result = (result, my_cache_key, None)
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(convert=attr.converters.optional(tuple))
autouse = attr.ib(default=False)
ids = attr.ib(default=None, convert=_ensure_immutable_ids)
name = attr.ib(default=None)
def __call__(self, function):
if isclass(function):
raise ValueError(
"class fixtures not supported (may be in the future)")
"class fixtures not supported (may be in the future)")
function._pytestfixturefunction = self
return function
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
""" (return a) decorator to mark a fixture factory function.
This decorator can be used (with or or without parameters) to define
a fixture function. The name of the fixture function can later be
This decorator can be used (with or without parameters) to define a
fixture function. The name of the fixture function can later be
referenced to cause its invocation ahead of running tests: test
modules or classes can use the pytest.mark.usefixtures(fixturename)
marker. Test functions can directly use fixture names as input
@@ -838,25 +874,25 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
reference is needed to activate the fixture.
:arg ids: list of string ids each corresponding to the params
so that they are part of the test id. If no ids are provided
they will be generated automatically from the params.
so that they are part of the test id. If no ids are provided
they will be generated automatically from the params.
:arg name: the name of the fixture. This defaults to the name of the
decorated function. If a fixture is used in the same module in
which it is defined, the function name of the fixture will be
shadowed by the function arg that requests the fixture; one way
to resolve this is to name the decorated function
``fixture_<fixturename>`` and then use
``@pytest.fixture(name='<fixturename>')``.
decorated function. If a fixture is used in the same module in
which it is defined, the function name of the fixture will be
shadowed by the function arg that requests the fixture; one way
to resolve this is to name the decorated function
``fixture_<fixturename>`` and then use
``@pytest.fixture(name='<fixturename>')``.
Fixtures can optionally provide their values to test functions using a ``yield`` statement,
instead of ``return``. In this case, the code block after the ``yield`` statement is executed
as teardown code regardless of the test outcome. A fixture function must yield exactly once.
"""
if callable(scope) and params is None and autouse == False:
if callable(scope) and params is None and autouse is False:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, name=name)(scope)
"function", params, autouse, name=name)(scope)
if params is not None and not isinstance(params, (list, tuple)):
params = list(params)
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
@@ -871,7 +907,7 @@ def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=N
if callable(scope) and params is None and not autouse:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, ids=ids, name=name)(scope)
"function", params, autouse, ids=ids, name=name)(scope)
else:
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
@@ -930,14 +966,9 @@ class FixtureManager:
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
session.config.pluginmanager.register(self, "funcmanage")
def getfixtureinfo(self, node, func, cls, funcargs=True):
if funcargs and not hasattr(node, "nofuncargs"):
if cls is not None:
startindex = 1
else:
startindex = None
argnames = getfuncargnames(func, startindex)
argnames = getfuncargnames(func, cls=cls)
else:
argnames = ()
usefixtures = getattr(func, "usefixtures", None)
@@ -961,8 +992,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):
@@ -972,7 +1003,7 @@ class FixtureManager:
if nodeid.startswith(baseid):
if baseid:
i = len(baseid)
nextchar = nodeid[i:i+1]
nextchar = nodeid[i:i + 1]
if nextchar and nextchar not in ":/":
continue
autousenames.extend(basenames)
@@ -1017,9 +1048,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:
@@ -1044,7 +1080,9 @@ class FixtureManager:
self._holderobjseen.add(holderobj)
autousenames = []
for name in dir(holderobj):
obj = getattr(holderobj, name, None)
# The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions.
obj = safe_getattr(holderobj, name, None)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked
marker = getfixturemarker(obj)
@@ -1055,7 +1093,7 @@ class FixtureManager:
continue
marker = defaultfuncargprefixmarker
from _pytest import deprecated
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name))
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid)
name = name[len(self._argprefix):]
elif not isinstance(marker, FixtureFunctionMarker):
# magic globals with __getattr__ might have got us a wrong
@@ -1105,6 +1143,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

@@ -2,9 +2,7 @@
Provides a function to report all internal modules for using freezing tools
pytest
"""
def pytest_namespace():
return {'freeze_includes': freeze_includes}
from __future__ import absolute_import, division, print_function
def freeze_includes():
@@ -42,4 +40,4 @@ def _iter_all_modules(package, prefix=''):
for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'):
yield prefix + m
else:
yield prefix + name
yield prefix + name

View File

@@ -1,29 +1,65 @@
""" version info, help messages, tracing configuration. """
from __future__ import absolute_import, division, print_function
import py
import pytest
import os, sys
from _pytest.config import PrintHelp
import os
import sys
from argparse import Action
class HelpAction(Action):
"""This is an argparse Action that will raise an exception in
order to skip the rest of the argument parsing when --help is passed.
This prevents argparse from quitting due to missing required arguments
when any are defined, for example by ``pytest_addoption``.
This is similar to the way that the builtin argparse --help option is
implemented by raising SystemExit.
"""
def __init__(self,
option_strings,
dest=None,
default=False,
help=None):
super(HelpAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=True,
default=default,
nargs=0,
help=help)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
# We should only skip the rest of the parsing after preparse is done
if getattr(parser._parser, 'after_preparse', False):
raise PrintHelp
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
group.addoption('--version', action="store_true",
help="display pytest lib version and import information.")
group._addoption("-h", "--help", action="store_true", dest="help",
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default = [],
metavar="name",
help="early-load given plugin (multi-allowed). "
"To avoid loading of plugins, use the `no:` prefix, e.g. "
"`no:doctest`.")
help="display pytest lib version and import information.")
group._addoption("-h", "--help", action=HelpAction, dest="help",
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default=[],
metavar="name",
help="early-load given plugin (multi-allowed). "
"To avoid loading of plugins, use the `no:` prefix, e.g. "
"`no:doctest`.")
group.addoption('--traceconfig', '--trace-config',
action="store_true", default=False,
help="trace considerations of conftest.py files."),
action="store_true", default=False,
help="trace considerations of conftest.py files."),
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="store internal tracing debug information in 'pytestdebug.log'.")
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",
action="append",
help="override config option, e.g. `-o xfail_strict=True`.")
help="override config option with option=value style, e.g. `-o xfail_strict=True`.")
@pytest.hookimpl(hookwrapper=True)
@@ -34,26 +70,29 @@ def pytest_cmdline_parse():
path = os.path.abspath("pytestdebug.log")
debugfile = open(path, 'w')
debugfile.write("versions pytest-%s, py-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
"python-%s\ncwd=%s\nargs=%s\n\n" % (
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
config.trace.root.setwriter(debugfile.write)
undo_tracing = config.pluginmanager.enable_tracing()
sys.stderr.write("writing pytestdebug information to %s\n" % path)
def unset_tracing():
debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
debugfile.name)
config.trace.root.setwriter(None)
undo_tracing()
config.add_cleanup(unset_tracing)
def pytest_cmdline_main(config):
if config.option.version:
p = py.path.local(pytest.__file__)
sys.stderr.write("This is pytest version %s, imported from %s\n" %
(pytest.__version__, p))
(pytest.__version__, p))
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
@@ -65,14 +104,15 @@ def pytest_cmdline_main(config):
config._ensure_unconfigure()
return 0
def showhelp(config):
reporter = config.pluginmanager.get_plugin('terminalreporter')
tw = reporter._tw
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
tw.line("[pytest] ini-options in the next "
"pytest.ini|tox.ini|setup.cfg file:")
tw.line("[pytest] ini-options in the first "
"pytest.ini|tox.ini|setup.cfg file found:")
tw.line()
for name in config._parser._ininames:
@@ -80,7 +120,7 @@ def showhelp(config):
if type is None:
type = "string"
spec = "%s (%s)" % (name, type)
line = " %-24s %s" %(spec, help)
line = " %-24s %s" % (spec, help)
tw.line(line[:tw.fullwidth])
tw.line()
@@ -109,6 +149,7 @@ conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
]
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager.list_plugin_distinfo()
@@ -120,11 +161,12 @@ def getpluginversioninfo(config):
lines.append(" " + content)
return lines
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
(pytest.__version__, py.__version__))
verinfo = getpluginversioninfo(config)
if verinfo:
@@ -138,5 +180,5 @@ def pytest_report_header(config):
r = plugin.__file__
else:
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
lines.append(" %-20s: %s" % (name, r))
return lines

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")
@@ -8,6 +8,7 @@ hookspec = HookspecMarker("pytest")
# Initialization hooks called for every plugin
# -------------------------------------------------------------------------
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to
@@ -16,11 +17,14 @@ def pytest_addhooks(pluginmanager):
@hookspec(historic=True)
def pytest_namespace():
"""return dict of name->object to be made globally available in
"""
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.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
@@ -56,11 +60,20 @@ def pytest_addoption(parser):
via (deprecated) ``pytest.config``.
"""
@hookspec(historic=True)
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
This hook is called for every plugin.
"""
Allows plugins and conftest files to perform initial configuration.
This hook is called for every plugin and initial conftest file
after command line options have been parsed.
After that, the hook is called for other conftest files as they are
imported.
:arg config: pytest config object
:type config: _pytest.config.Config
"""
# -------------------------------------------------------------------------
@@ -69,17 +82,25 @@ def pytest_configure(config):
# discoverable conftest.py local plugins.
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@hookspec(firstresult=True)
def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop. """
implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
@@ -92,88 +113,124 @@ 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` """
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
@hookspec(firstresult=True)
def pytest_ignore_collect(path, config):
""" return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
"""
@hookspec(firstresult=True)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files. """
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
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."""
# logging hooks for collection
def pytest_collectstart(collector):
""" collector starts collecting. """
def pytest_itemcollected(item):
""" we just collected a test item. """
def pytest_collectreport(report):
""" collector finished collecting. """
def pytest_deselected(items):
""" called for test items deselected by keyword. """
@hookspec(firstresult=True)
def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport. """
""" perform ``collector.collect()`` and return a CollectReport.
Stops at first non-None result, see :ref:`firstresult` """
# -------------------------------------------------------------------------
# Python test function related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path.
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
create test modules for files that do not match as a test module.
"""
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
""" return custom item/collector for a python object in a module, or None.
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """
""" call underlying test function.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
@hookspec(firstresult=True)
def pytest_make_parametrize_id(config, val):
def pytest_make_parametrize_id(config, val, argname):
"""Return a user-friendly string representation of the given ``val`` that will be used
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` """
# -------------------------------------------------------------------------
# generic runtest related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
(after collection finished).
Stops at first non-None result, see :ref:`firstresult` """
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
@hookspec(firstresult=True)
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
@@ -187,17 +244,23 @@ def pytest_runtest_protocol(item, nextitem):
:py:func:`pytest_runtest_teardown`.
:return boolean: True if no further hook implementations should be invoked.
"""
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
def pytest_runtest_setup(item):
""" called before ``pytest_runtest_call(item)``. """
def pytest_runtest_call(item):
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item, nextitem):
""" called after ``pytest_runtest_call``.
@@ -207,12 +270,15 @@ def pytest_runtest_teardown(item, nextitem):
so that nextitem only needs to call setup-functions.
"""
@hookspec(firstresult=True)
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item` and
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
:py:class:`_pytest.runner.CallInfo`.
"""
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to
@@ -222,11 +288,15 @@ def pytest_runtest_logreport(report):
# Fixture related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_fixture_setup(fixturedef, request):
""" performs fixture setup execution. """
""" performs fixture setup execution.
def pytest_fixture_post_finalizer(fixturedef):
Stops at first non-None result, see :ref:`firstresult` """
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."""
@@ -235,18 +305,21 @@ def pytest_fixture_post_finalizer(fixturedef):
# test session related hooks
# -------------------------------------------------------------------------
def pytest_sessionstart(session):
""" before session.main() is called. """
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
def pytest_unconfigure(config):
""" called before test process is exited. """
# -------------------------------------------------------------------------
# hooks for customising the assert methods
# hooks for customizing the assert methods
# -------------------------------------------------------------------------
def pytest_assertrepr_compare(config, op, left, right):
@@ -255,19 +328,48 @@ def pytest_assertrepr_compare(config, op, left, right):
Return None for no custom explanation, otherwise return a list
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 sligthly, the intention is for the first line to be a summary.
be indented slightly, the intention is for the first line to be a summary.
"""
# -------------------------------------------------------------------------
# hooks for influencing reporting (invoked from _pytest_terminal)
# -------------------------------------------------------------------------
def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting."""
""" return a string or list of strings to be displayed as header info for terminal reporting.
:param config: the pytest config object.
:param startdir: py.path object with the starting dir
.. note::
This function should be implemented only in plugins or ``conftest.py``
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
"""
def pytest_report_collectionfinish(config, startdir, items):
"""
.. versionadded:: 3.2
return a string or list of strings to be displayed after collection has finished successfully.
This strings will be displayed after the standard "collected X items" message.
:param config: the 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.
"""
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting."""
""" return result-category, shortletter and verbose word for reporting.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_terminal_summary(terminalreporter, exitstatus):
""" add additional section in terminal summary reporting. """
@@ -283,20 +385,26 @@ def pytest_logwarning(message, code, nodeid, fslocation):
# doctest hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
""" return processed content for a given doctest
Stops at first non-None result, see :ref:`firstresult` """
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """
def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """
def pytest_exception_interact(node, call, report):
"""called when an exception was raised which can potentially be
interactively handled.
@@ -305,6 +413,7 @@ def pytest_exception_interact(node, call, report):
that is not an internal exception like ``skip.Exception``.
"""
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.

View File

@@ -1,254 +0,0 @@
Sorting per-resource
-----------------------------
for any given set of items:
- collect items per session-scoped parametrized funcarg
- re-order until items no parametrizations are mixed
examples:
test()
test1(s1)
test1(s2)
test2()
test3(s1)
test3(s2)
gets sorted to:
test()
test2()
test1(s1)
test3(s1)
test1(s2)
test3(s2)
the new @setup functions
--------------------------------------
Consider a given @setup-marked function::
@pytest.mark.setup(maxscope=SCOPE)
def mysetup(request, arg1, arg2, ...)
...
request.addfinalizer(fin)
...
then FUNCARGSET denotes the set of (arg1, arg2, ...) funcargs and
all of its dependent funcargs. The mysetup function will execute
for any matching test item once per scope.
The scope is determined as the minimum scope of all scopes of the args
in FUNCARGSET and the given "maxscope".
If mysetup has been called and no finalizers have been called it is
called "active".
Furthermore the following rules apply:
- if an arg value in FUNCARGSET is about to be torn down, the
mysetup-registered finalizers will execute as well.
- There will never be two active mysetup invocations.
Example 1, session scope::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.setup
def mysetup(request, db):
request.addfinalizer(mysetup_finalize)
...
And a given test module:
def test_something():
...
def test_otherthing():
pass
Here is what happens::
db(request) executes with request.param == 1
mysetup(request, db) executes
test_something() executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
db(request) executes with request.param == 2
mysetup(request, db) executes
test_something() executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
Example 2, session/function scope::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.setup(scope="function")
def mysetup(request, db):
...
request.addfinalizer(mysetup_finalize)
...
And a given test module:
def test_something():
...
def test_otherthing():
pass
Here is what happens::
db(request) executes with request.param == 1
mysetup(request, db) executes
test_something() executes
mysetup_finalize() executes
mysetup(request, db) executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
db(request) executes with request.param == 2
mysetup(request, db) executes
test_something() executes
mysetup_finalize() executes
mysetup(request, db) executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
Example 3 - funcargs session-mix
----------------------------------------
Similar with funcargs, an example::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.funcarg(scope="function")
def table(request, db):
...
request.addfinalizer(table_finalize)
...
And a given test module:
def test_something(table):
...
def test_otherthing(table):
pass
def test_thirdthing():
pass
Here is what happens::
db(request) executes with param == 1
table(request, db)
test_something(table)
table_finalize()
table(request, db)
test_otherthing(table)
table_finalize()
db_finalize
db(request) executes with param == 2
table(request, db)
test_something(table)
table_finalize()
table(request, db)
test_otherthing(table)
table_finalize()
db_finalize
test_thirdthing()
Data structures
--------------------
pytest internally maintains a dict of active funcargs with cache, param,
finalizer, (scopeitem?) information:
active_funcargs = dict()
if a parametrized "db" is activated:
active_funcargs["db"] = FuncargInfo(dbvalue, paramindex,
FuncargFinalize(...), scopeitem)
if a test is torn down and the next test requires a differently
parametrized "db":
for argname in item.callspec.params:
if argname in active_funcargs:
funcarginfo = active_funcargs[argname]
if funcarginfo.param != item.callspec.params[argname]:
funcarginfo.callfinalizer()
del node2funcarg[funcarginfo.scopeitem]
del active_funcargs[argname]
nodes_to_be_torn_down = ...
for node in nodes_to_be_torn_down:
if node in node2funcarg:
argname = node2funcarg[node]
active_funcargs[argname].callfinalizer()
del node2funcarg[node]
del active_funcargs[argname]
if a test is setup requiring a "db" funcarg:
if "db" in active_funcargs:
return active_funcargs["db"][0]
funcarginfo = setup_funcarg()
active_funcargs["db"] = funcarginfo
node2funcarg[funcarginfo.scopeitem] = "db"
Implementation plan for resources
------------------------------------------
1. Revert FuncargRequest to the old form, unmerge item/request
(done)
2. make funcarg factories be discovered at collection time
3. Introduce funcarg marker
4. Introduce funcarg scope parameter
5. Introduce funcarg parametrize parameter
6. make setup functions be discovered at collection time
7. (Introduce a pytest_fixture_protocol/setup_funcargs hook)
methods and data structures
--------------------------------
A FuncarcManager holds all information about funcarg definitions
including parametrization and scope definitions. It implements
a pytest_generate_tests hook which performs parametrization as appropriate.
as a simple example, let's consider a tree where a test function requires
a "abc" funcarg and its factory defines it as parametrized and scoped
for Modules. When collections hits the function item, it creates
the metafunc object, and calls funcargdb.pytest_generate_tests(metafunc)
which looks up available funcarg factories and their scope and parametrization.
This information is equivalent to what can be provided today directly
at the function site and it should thus be relatively straight forward
to implement the additional way of defining parametrization/scoping.
conftest loading:
each funcarg-factory will populate the session.funcargmanager
When a test item is collected, it grows a dictionary
(funcargname2factorycalllist). A factory lookup is performed
for each required funcarg. The resulting factory call is stored
with the item. If a function is parametrized multiple items are
created with respective factory calls. Else if a factory is parametrized
multiple items and calls to the factory function are created as well.
At setup time, an item populates a funcargs mapping, mapping names
to values. If a value is funcarg factories are queried for a given item
test functions and setup functions are put in a class
which looks up required funcarg factories.

View File

@@ -4,16 +4,21 @@
Based on initial code from Ross Lawley.
"""
# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
"""
from __future__ import absolute_import, division, print_function
import functools
import py
import os
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
if sys.version_info[0] < 3:
@@ -27,6 +32,7 @@ else:
class Junit(py.xml.Namespace):
pass
# We need to get the subset of the invalid unicode ranges according to
# XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid
@@ -102,6 +108,8 @@ class _NodeReporter(object):
}
if testreport.location[1] is not None:
attrs["line"] = testreport.location[1]
if hasattr(testreport, "url"):
attrs["url"] = testreport.url
self.attrs = attrs
def to_xml(self):
@@ -116,7 +124,7 @@ class _NodeReporter(object):
node = kind(data, message=message)
self.append(node)
def _write_captured_output(self, report):
def write_captured_output(self, report):
for capname in ('out', 'err'):
content = getattr(report, 'capstd' + capname)
if content:
@@ -125,7 +133,6 @@ class _NodeReporter(object):
def append_pass(self, report):
self.add_stats('passed')
self._write_captured_output(report)
def append_failure(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
@@ -144,7 +151,6 @@ class _NodeReporter(object):
fail = Junit.failure(message=message)
fail.append(bin_xml_escape(report.longrepr))
self.append(fail)
self._write_captured_output(report)
def append_collect_error(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
@@ -156,9 +162,12 @@ class _NodeReporter(object):
Junit.skipped, "collection skipped", report.longrepr)
def append_error(self, report):
if getattr(report, 'when', None) == 'teardown':
msg = "test teardown failure"
else:
msg = "test setup failure"
self._add_simple(
Junit.error, "test setup failure", report.longrepr)
self._write_captured_output(report)
Junit.error, msg, report.longrepr)
def append_skipped(self, report):
if hasattr(report, "wasxfail"):
@@ -173,7 +182,7 @@ class _NodeReporter(object):
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip",
message=skipreason))
self._write_captured_output(report)
self.write_captured_output(report)
def finalize(self):
data = self.to_xml().unicode(indent=0)
@@ -209,6 +218,7 @@ def pytest_addoption(parser):
action="store",
dest="xmlpath",
metavar="path",
type=functools.partial(filename_arg, optname="--junitxml"),
default=None,
help="create junit-xml style report file at given path.")
group.addoption(
@@ -217,13 +227,14 @@ def pytest_addoption(parser):
metavar="str",
default=None,
help="prepend prefix to classnames in junit-xml output")
parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest")
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._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name"))
config.pluginmanager.register(config._xml)
@@ -242,7 +253,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
@@ -250,10 +261,11 @@ def mangle_test_address(address):
class LogXML(object):
def __init__(self, logfile, prefix):
def __init__(self, logfile, prefix, suite_name="pytest"):
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.stats = dict.fromkeys([
'error',
'passed',
@@ -263,6 +275,9 @@ class LogXML(object):
self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = []
self.global_properties = []
# List of reports that failed on call but teardown is pending.
self.open_reports = []
self.cnt_double_fail_tests = 0
def finalize(self, report):
nodeid = getattr(report, 'nodeid', report)
@@ -322,14 +337,33 @@ class LogXML(object):
-> teardown node2
-> teardown node1
"""
close_report = None
if report.passed:
if report.when == "call": # ignore setup/teardown
reporter = self._opentestcase(report)
reporter.append_pass(report)
elif report.failed:
if report.when == "teardown":
# The following vars are needed when xdist plugin is used
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
# We need to open new testcase in case we have failure in
# call and error in teardown in order to follow junit
# schema
self.finalize(close_report)
self.cnt_double_fail_tests += 1
reporter = self._opentestcase(report)
if report.when == "call":
reporter.append_failure(report)
self.open_reports.append(report)
else:
reporter.append_error(report)
elif report.skipped:
@@ -337,7 +371,20 @@ class LogXML(object):
reporter.append_skipped(report)
self.update_testcase_duration(report)
if report.when == "teardown":
reporter = self._opentestcase(report)
reporter.write_captured_output(report)
self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
self.open_reports.remove(close_report)
def update_testcase_duration(self, report):
"""accumulates total duration for nodeid from given report and updates
@@ -370,14 +417,15 @@ class LogXML(object):
suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error']
numtests = (self.stats['passed'] + self.stats['failure'] +
self.stats['skipped'] + self.stats['error'] -
self.cnt_double_fail_tests)
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write(Junit.testsuite(
self._get_global_properties_node(),
[x.to_xml() for x in self.node_reporters_ordered],
name="pytest",
name=self.suite_name,
errors=self.stats['error'],
failures=self.stats['failure'],
skips=self.stats['skipped'],
@@ -397,9 +445,9 @@ class LogXML(object):
"""
if self.global_properties:
return Junit.properties(
[
Junit.property(name=name, value=value)
for name, value in self.global_properties
]
[
Junit.property(name=name, value=value)
for name, value in self.global_properties
]
)
return ''

337
_pytest/logging.py Normal file
View File

@@ -0,0 +1,337 @@
from __future__ import absolute_import, division, print_function
import logging
from contextlib import closing, contextmanager
import sys
import six
import pytest
import py
DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'
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.')
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 logging_using_handler(handler, logger=None):
"""Context manager that safely registers a given handler."""
logger = logger or logging.getLogger(logger)
if handler in logger.handlers: # reentrancy
# Adding the same handler twice would confuse logging system.
# Just don't do that.
yield
else:
logger.addHandler(handler)
try:
yield
finally:
logger.removeHandler(handler)
@contextmanager
def catching_logs(handler, formatter=None,
level=logging.NOTSET, logger=None):
"""Context manager that prepares the whole logging machinery properly."""
logger = logger or logging.getLogger(logger)
if formatter is not None:
handler.setFormatter(formatter)
handler.setLevel(level)
with logging_using_handler(handler, logger):
orig_level = logger.level
logger.setLevel(min(orig_level, level))
try:
yield handler
finally:
logger.setLevel(orig_level)
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)
class LogCaptureFixture(object):
"""Provides access and control of log capturing."""
def __init__(self, item):
"""Creates a new funcarg."""
self._item = item
@property
def handler(self):
return self._item.catch_log_handler
@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."""
self.handler.records = []
def set_level(self, level, logger=None):
"""Sets the level for capturing of logs.
By default, the level is set on the handler used to capture
logs. Specify a logger name to instead set the level of any
logger.
"""
if logger is None:
logger = self.handler
else:
logger = logging.getLogger(logger)
logger.setLevel(level)
@contextmanager
def at_level(self, level, logger=None):
"""Context manager that sets the level for capturing of logs.
By default, the level is set on the handler used to capture
logs. Specify a logger name to instead set the level of any
logger.
"""
if logger is None:
logger = self.handler
else:
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
"""
return LogCaptureFixture(request.node)
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')
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.log_cli_level = get_actual_log_level(
config, 'log_cli_level', 'log_level') or logging.WARNING
self.print_logs = get_option_ini(config, 'log_print')
self.formatter = logging.Formatter(
get_option_ini(config, 'log_format'),
get_option_ini(config, 'log_date_format'))
log_cli_handler = logging.StreamHandler(sys.stderr)
log_cli_format = get_option_ini(
config, 'log_cli_format', 'log_format')
log_cli_date_format = get_option_ini(
config, 'log_cli_date_format', 'log_date_format')
log_cli_formatter = logging.Formatter(
log_cli_format,
datefmt=log_cli_date_format)
self.log_cli_handler = log_cli_handler # needed for a single unittest
self.live_logs = catching_logs(log_cli_handler,
formatter=log_cli_formatter,
level=self.log_cli_level)
log_file = get_option_ini(config, 'log_file')
if log_file:
self.log_file_level = get_actual_log_level(
config, 'log_file_level') or logging.WARNING
log_file_format = get_option_ini(
config, 'log_file_format', 'log_format')
log_file_date_format = get_option_ini(
config, 'log_file_date_format', 'log_date_format')
self.log_file_handler = logging.FileHandler(
log_file,
# Each pytest runtests session will write to a clean logfile
mode='w')
log_file_formatter = logging.Formatter(
log_file_format,
datefmt=log_file_date_format)
self.log_file_handler.setFormatter(log_file_formatter)
else:
self.log_file_handler = None
@contextmanager
def _runtest_for(self, item, when):
"""Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(LogCaptureHandler(),
formatter=self.formatter) as log_handler:
item.catch_log_handler = log_handler
try:
yield # run test
finally:
del item.catch_log_handler
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_runtestloop(self, session):
"""Runs all collected test items."""
with self.live_logs:
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

View File

@@ -1,16 +1,22 @@
""" core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function
import functools
import os
import six
import sys
import _pytest
from _pytest import nodes
import _pytest._code
import py
import pytest
try:
from collections import MutableMapping as MappingMixin
except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@@ -23,63 +29,64 @@ EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
type="args", default=[])
#parser.addini("dirpatterns",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the "
"command line.",
type="args", default=[])
# parser.addini("dirpatterns",
# "patterns specifying possible locations of test files",
# type="linelist", default=["**/test_*.txt",
# "**/test_*.py", "**/*_test.py"]
#)
# )
group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_const",
dest="maxfail", const=1,
help="exit instantly on first error or failed test."),
dest="maxfail", const=1,
help="exit instantly on first error or failed test."),
group._addoption('--maxfail', metavar="num",
action="store", type=int, dest="maxfail", default=0,
help="exit after first num failures or errors.")
action="store", type=int, dest="maxfail", default=0,
help="exit after first num failures or errors.")
group._addoption('--strict', action="store_true",
help="run pytest in strict mode, warnings become errors.")
help="marks not registered in configuration file raise errors.")
group._addoption("-c", metavar="file", type=str, dest="inifilename",
help="load configuration from `file` instead of trying to locate one of the implicit configuration files.")
help="load configuration from `file` instead of trying to locate one of the implicit "
"configuration files.")
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.")
default=False, dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur.")
group = parser.getgroup("collect", "collection")
group.addoption('--collectonly', '--collect-only', action="store_true",
help="only collect tests, don't execute them."),
help="only collect tests, don't execute them."),
group.addoption('--pyargs', action="store_true",
help="try to interpret all arguments as python packages.")
help="try to interpret all arguments as python packages.")
group.addoption("--ignore", action="append", metavar="path",
help="ignore path during collection (multi-allowed).")
help="ignore path 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,
metavar="dir",
help="only load conftest.py's relative to specified dir.")
metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"),
help="only load conftest.py's relative to specified dir.")
group.addoption('--noconftest', action="store_true",
dest="noconftest", default=False,
help="Don't load any conftest.py files.")
dest="noconftest", default=False,
help="Don't load any conftest.py files.")
group.addoption('--keepduplicates', '--keep-duplicates', action="store_true",
dest="keepduplicates", default=False,
help="Keep duplicate tests.")
dest="keepduplicates", default=False,
help="Keep duplicate tests.")
group.addoption('--collect-in-virtualenv', action='store_true',
dest='collect_in_virtualenv', default=False,
help="Don't ignore tests in a local virtualenv directory")
group = parser.getgroup("debugconfig",
"test session debugging and configuration")
"test session debugging and configuration")
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
def pytest_namespace():
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
return dict(collect=collect)
help="base temporary directory for this test run.")
def pytest_configure(config):
pytest.config = config # compatibiltiy
__import__('pytest').config = config # compatibiltiy
def wrap_session(config, doit):
@@ -94,17 +101,18 @@ def wrap_session(config, doit):
config.hook.pytest_sessionstart(session=session)
initstate = 2
session.exitstatus = doit(config, session) or 0
except pytest.UsageError:
except UsageError:
raise
except Failed:
session.exitstatus = EXIT_TESTSFAILED
except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo()
if initstate < 2 and isinstance(
excinfo.value, pytest.exit.Exception):
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
sys.stderr.write('{0}: {1}\n'.format(
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
@@ -121,9 +129,11 @@ def wrap_session(config, doit):
config._ensure_unconfigure()
return session.exitstatus
def pytest_cmdline_main(config):
return wrap_session(config, _main)
def _main(config, session):
""" default command line protocol for initialization, session,
running tests and reporting. """
@@ -135,9 +145,11 @@ def _main(config, session):
elif session.testscollected == 0:
return EXIT_NOTESTSCOLLECTED
def pytest_collection(session):
return session.perform_collect()
def pytest_runtestloop(session):
if (session.testsfailed and
not session.config.option.continue_on_collection_errors):
@@ -148,21 +160,38 @@ def pytest_runtestloop(session):
return True
for i, item in enumerate(session.items):
nextitem = session.items[i+1] if i+1 < len(session.items) else None
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
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():
return False
activates = ('activate', 'activate.csh', 'activate.fish',
'Activate', 'Activate.bat', 'Activate.ps1')
return any([fname.basename in activates for fname in bindir.listdir()])
def pytest_ignore_collect(path, config):
p = path.dirpath()
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
ignore_paths = ignore_paths or []
excludeopt = config.getoption("ignore")
if excludeopt:
ignore_paths.extend([py.path.local(x) for x in excludeopt])
if path in ignore_paths:
if py.path.local(path) in ignore_paths:
return True
allow_in_venv = config.getoption("collect_in_virtualenv")
if _in_venv(path) and not allow_in_venv:
return True
# Skip duplicate paths.
@@ -188,12 +217,22 @@ class FSHookProxy:
self.__dict__[name] = x
return x
def compatproperty(name):
def fget(self):
# deprecated - use pytest.name
return getattr(pytest, name)
return property(fget)
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):
@@ -265,24 +304,28 @@ class Node(object):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
Module = compatproperty("Module")
Class = compatproperty("Class")
Instance = compatproperty("Instance")
Function = compatproperty("Function")
File = compatproperty("File")
Item = compatproperty("Item")
Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")
def _getcustomclass(self, name):
cls = getattr(self, name)
if cls != getattr(pytest, name):
py.log._apiwarn("2.0", "use of node.%s is deprecated, "
"use pytest_pycollect_makeitem(...) to create custom "
"collection nodes" % 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))
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
@@ -291,9 +334,6 @@ class Node(object):
fslocation = getattr(self, "location", None)
if fslocation is None:
fslocation = getattr(self, "fspath", None)
else:
fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1)
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation))
@@ -320,24 +360,6 @@ class Node(object):
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. """
@@ -354,9 +376,9 @@ class Node(object):
``marker`` can be a string or pytest.mark.* instance.
"""
from _pytest.mark import MarkDecorator
if isinstance(marker, py.builtin._basestring):
marker = MarkDecorator(marker)
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
@@ -406,7 +428,7 @@ class Node(object):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.option.fulltrace:
style="long"
style = "long"
else:
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
self._prunetraceback(excinfo)
@@ -434,6 +456,7 @@ class Node(object):
repr_failure = _repr_failure_py
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.
@@ -455,10 +478,6 @@ class Collector(Node):
return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short")
def _memocollect(self):
""" internal helper method to cache results of calling collect(). """
return self._memoizedcall('_collected', lambda: list(self.collect()))
def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'):
traceback = excinfo.traceback
@@ -467,27 +486,38 @@ class Collector(Node):
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?
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, "/")
name = name.replace(os.sep, nodes.SEP)
super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath
def _check_initialpaths_for_relpath(self):
for initialpath in self.session._initialpaths:
if self.fspath.common(initialpath) == initialpath:
return self.fspath.relto(initialpath.dirname)
def _makeid(self):
relpath = self.fspath.relto(self.config.rootdir)
if os.sep != "/":
relpath = relpath.replace(os.sep, "/")
if not relpath:
relpath = self._check_initialpaths_for_relpath()
if os.sep != nodes.SEP:
relpath = relpath.replace(os.sep, nodes.SEP)
return relpath
class File(FSCollector):
""" base class for collecting tests from a file. """
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
@@ -499,6 +529,21 @@ class Item(Node):
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))
@@ -522,23 +567,31 @@ class Item(Node):
self._location = location
return location
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
__module__ = 'builtins' # for py3
class Failed(Exception):
""" signals an stop as failed test run. """
class Session(FSCollector):
Interrupted = Interrupted
Failed = Failed
def __init__(self, config):
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
self._fs2hookproxy = {}
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()
@@ -547,18 +600,20 @@ class Session(FSCollector):
def _makeid(self):
return ""
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_collectstart(self):
if self.shouldfail:
raise self.Failed(self.shouldfail)
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'):
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
@@ -566,30 +621,26 @@ class Session(FSCollector):
return path in self._initialpaths
def gethookproxy(self, fspath):
try:
return self._fs2hookproxy[fspath]
except KeyError:
# check if we have the common case of running
# hooks with all conftest.py filesall conftest.py
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
proxy = self.config.hook
self._fs2hookproxy[fspath] = proxy
return proxy
# check if we have the common case of running
# hooks with all conftest.py filesall conftest.py
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
proxy = self.config.hook
return proxy
def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
try:
items = self._perform_collect(args, genitems)
self.config.pluginmanager.check_pending()
hook.pytest_collection_modifyitems(session=self,
config=self.config, items=items)
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
self.testscollected = len(items)
@@ -616,8 +667,8 @@ class Session(FSCollector):
for arg, exc in self._notfound:
line = "(no name %r in any of %r)" % (arg, exc.args[0])
errors.append("not found: %s\n%s" % (arg, line))
#XXX: test this
raise pytest.UsageError(*errors)
# XXX: test this
raise UsageError(*errors)
if not genitems:
return rep.result
else:
@@ -645,7 +696,7 @@ class Session(FSCollector):
names = self._parsearg(arg)
path = names.pop(0)
if path.check(dir=1):
assert not names, "invalid arg %r" %(arg,)
assert not names, "invalid arg %r" % (arg,)
for path in path.visit(fil=lambda x: x.check(file=1),
rec=self._recurse, bf=True, sort=True):
for x in self._collectfile(path):
@@ -704,10 +755,11 @@ class Session(FSCollector):
path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check():
if self.config.option.pyargs:
msg = "file or package not found: "
raise UsageError(
"file or package not found: " + arg +
" (missing __init__.py?)")
else:
msg = "file not found: "
raise pytest.UsageError(msg + arg)
raise UsageError("file not found: " + arg)
parts[0] = path
return parts
@@ -730,11 +782,11 @@ class Session(FSCollector):
nextnames = names[1:]
resultnodes = []
for node in matching:
if isinstance(node, pytest.Item):
if isinstance(node, Item):
if not names:
resultnodes.append(node)
continue
assert isinstance(node, pytest.Collector)
assert isinstance(node, Collector)
rep = collect_one_node(node)
if rep.passed:
has_matched = False
@@ -747,16 +799,20 @@ class Session(FSCollector):
if not has_matched and len(rep.result) == 1 and x.name == "()":
nextnames.insert(0, name)
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
else:
# report collection failures here to avoid failing to run some test
# specified in the command line because the module could not be
# imported (#134)
node.ihook.pytest_collectreport(report=rep)
return resultnodes
def genitems(self, node):
self.trace("genitems", node)
if isinstance(node, pytest.Item):
if isinstance(node, Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, pytest.Collector)
assert isinstance(node, Collector)
rep = collect_one_node(node)
if rep.passed:
for subnode in rep.result:

View File

@@ -1,5 +1,97 @@
""" generic mechanism for marking and selecting python functions. """
from __future__ import absolute_import, division, print_function
import inspect
import warnings
import attr
from collections import namedtuple
from operator import attrgetter
from six.moves import map
from .deprecated import MARK_PARAMETERSET_UNPACKING
from .compat import NOTSET, getfslineno
def alias(name, warning=None):
getter = attrgetter(name)
def warned(self):
warnings.warn(warning, stacklevel=2)
return getter(self)
return property(getter if warning is None else warned, doc='alias for ' + name)
class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
@classmethod
def param(cls, *values, **kw):
marks = kw.pop('marks', ())
if isinstance(marks, MarkDecorator):
marks = marks,
else:
assert isinstance(marks, (tuple, list, set))
def param_extract_id(id=None):
return id
id = param_extract_id(**kw)
return cls(values, marks, id)
@classmethod
def extract_from(cls, parameterset, legacy_force_tuple=False):
"""
:param parameterset:
a legacy style parameterset that may or may not be a tuple,
and may or may not be wrapped into a mess of mark objects
:param legacy_force_tuple:
enforce tuple wrapping so single argument tuple values
don't get decomposed and break tests
"""
if isinstance(parameterset, cls):
return parameterset
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
return cls.param(parameterset)
newmarks = []
argval = parameterset
while isinstance(argval, MarkDecorator):
newmarks.append(MarkDecorator(Mark(
argval.markname, argval.args[:-1], argval.kwargs)))
argval = argval.args[-1]
assert not isinstance(argval, ParameterSet)
if legacy_force_tuple:
argval = argval,
if newmarks:
warnings.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
@classmethod
def _for_parameterize(cls, argnames, argvalues, function):
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]
del argvalues
if not parameters:
fs, lineno = getfslineno(function)
reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, function.__name__, fs, lineno)
mark = MARK_GEN.skip(reason=reason)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
id=None,
))
return argnames, parameters
class MarkerError(Exception):
@@ -7,8 +99,8 @@ class MarkerError(Exception):
"""Error in use of a pytest marker/attribute."""
def pytest_namespace():
return {'mark': MarkGenerator()}
def param(*values, **kw):
return ParameterSet.param(*values, **kw)
def pytest_addoption(parser):
@@ -19,9 +111,10 @@ def pytest_addoption(parser):
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 "
"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'. "
"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."
@@ -48,12 +141,16 @@ def pytest_cmdline_main(config):
config._do_configure()
tw = _pytest.config.create_terminal_writer(config)
for line in config.getini("markers"):
name, rest = line.split(":", 1)
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
@@ -64,7 +161,7 @@ def pytest_collection_modifyitems(items, config):
return
# pytest used to allow "-" for negating
# but today we just allow "-" at the beginning, use "not" instead
# we probably remove "-" alltogether soon
# we probably remove "-" altogether soon
if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
@@ -91,24 +188,30 @@ def pytest_collection_modifyitems(items, config):
items[:] = remaining
class MarkMapping:
@attr.s
class MarkMapping(object):
"""Provides a local mapping for markers where item access
resolves to True if the marker is present. """
def __init__(self, keywords):
mymarks = set()
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):
mymarks.add(key)
self._mymarks = mymarks
mark_names.add(key)
return cls(mark_names)
def __getitem__(self, name):
return name in self._mymarks
return name in self.own_mark_names
class KeywordMapping:
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
@@ -121,7 +224,7 @@ class KeywordMapping:
def matchmark(colitem, markexpr):
"""Tries to match on any marker names, attached to the given colitem."""
return eval(markexpr, {}, MarkMapping(colitem.keywords))
return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
def matchkeyword(colitem, keywordexpr):
@@ -160,9 +263,13 @@ def matchkeyword(colitem, keywordexpr):
def pytest_configure(config):
import pytest
config._old_mark_config = MARK_GEN._config
if config.option.strict:
pytest.mark._config = config
MARK_GEN._config = config
def pytest_unconfigure(config):
MARK_GEN._config = getattr(config, '_old_mark_config', None)
class MarkGenerator:
@@ -176,13 +283,14 @@ class MarkGenerator:
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 hasattr(self, '_config'):
if self._config is not None:
self._check(name)
return MarkDecorator(name)
return MarkDecorator(Mark(name, (), {}))
def _check(self, name):
try:
@@ -190,19 +298,36 @@ class MarkGenerator:
return
except AttributeError:
pass
self._markers = l = set()
self._markers = values = set()
for line in self._config.getini("markers"):
beginning = line.split(":", 1)
x = beginning[0].split("(", 1)[0]
l.add(x)
marker = line.split(":", 1)[0]
marker = marker.rstrip()
x = marker.split("(", 1)[0]
values.add(x)
if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,))
def istestfunc(func):
return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>"
class MarkDecorator:
@attr.s(frozen=True)
class Mark(object):
name = attr.ib()
args = attr.ib()
kwargs = attr.ib()
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
@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>`.
@@ -235,19 +360,33 @@ class MarkDecorator:
additional keyword or positional arguments.
"""
def __init__(self, name, args=None, kwargs=None):
self.name = name
self.args = args or ()
self.kwargs = kwargs or {}
mark = attr.ib(validator=attr.validators.instance_of(Mark))
name = alias('mark.name')
args = alias('mark.args')
kwargs = alias('mark.kwargs')
@property
def markname(self):
return self.name # for backward-compat (2.4.1 had this attr)
return self.name # for backward-compat (2.4.1 had this attr)
def __eq__(self, other):
return self.mark == other.mark if isinstance(other, MarkDecorator) else False
def __repr__(self):
d = self.__dict__.copy()
name = d.pop('name')
return "<MarkDecorator %r %r>" % (name, d)
return "<MarkDecorator %r>" % (self.mark,)
def with_args(self, *args, **kwargs):
""" return a MarkDecorator with extra arguments added
unlike call this can be used even if the sole argument is a callable/class
:return: MarkDecorator
"""
mark = Mark(self.name, args, kwargs)
return self.__class__(self.mark.combined_with(mark))
def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
@@ -257,70 +396,101 @@ class MarkDecorator:
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
if hasattr(func, 'pytestmark'):
mark_list = func.pytestmark
if not isinstance(mark_list, list):
mark_list = [mark_list]
# always work on a copy to avoid updating pytestmark
# from a superclass by accident
mark_list = mark_list + [self]
func.pytestmark = mark_list
else:
func.pytestmark = [self]
store_mark(func, self.mark)
else:
holder = getattr(func, self.name, None)
if holder is None:
holder = MarkInfo(
self.name, self.args, self.kwargs
)
setattr(func, self.name, holder)
else:
holder.add(self.args, self.kwargs)
store_legacy_markinfo(func, self.mark)
store_mark(func, self.mark)
return func
kw = self.kwargs.copy()
kw.update(kwargs)
args = self.args + args
return self.__class__(self.name, args=args, kwargs=kw)
return self.with_args(*args, **kwargs)
def extract_argvalue(maybe_marked_args):
# TODO: incorrect mark data, the old code wanst able to collect lists
# individual parametrized argument sets can be wrapped in a series
# of markers in which case we unwrap the values and apply the mark
# at Function init
newmarks = {}
argval = maybe_marked_args
while isinstance(argval, MarkDecorator):
newmark = MarkDecorator(argval.markname,
argval.args[:-1], argval.kwargs)
newmarks[newmark.markname] = newmark
argval = argval.args[-1]
return argval, newmarks
def get_unpacked_marks(obj):
"""
obtain the unpacked marks that are stored on a object
"""
mark_list = getattr(obj, 'pytestmark', [])
if not isinstance(mark_list, list):
mark_list = [mark_list]
return [
getattr(mark, 'mark', mark) # unpack MarkDecorator
for mark in mark_list
]
class MarkInfo:
def store_mark(obj, mark):
"""store a Mark on a object
this is used to implement the Mark declarations/decorators correctly
"""
assert isinstance(mark, Mark), mark
# always reassign name to avoid updating pytestmark
# in a reference that was only borrowed
obj.pytestmark = get_unpacked_marks(obj) + [mark]
def store_legacy_markinfo(func, mark):
"""create the legacy MarkInfo objects and put them onto the function
"""
if not isinstance(mark, Mark):
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
holder = getattr(func, mark.name, None)
if holder is None:
holder = MarkInfo(mark)
setattr(func, mark.name, holder)
else:
holder.add_mark(mark)
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, name, args, kwargs):
#: name of attribute
self.name = name
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.combined = mark
self._marks = [mark]
name = alias('combined.name')
args = alias('combined.args')
kwargs = alias('combined.kwargs')
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self.name, self.args, self.kwargs
)
return "<MarkInfo {0!r}>".format(self.combined)
def add(self, args, kwargs):
def add_mark(self, mark):
""" add a MarkInfo with the given args and kwargs. """
self._arglist.append((args, kwargs))
self.args += args
self.kwargs.update(kwargs)
self._marks.append(mark)
self.combined = self.combined.combined_with(mark)
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist:
yield MarkInfo(self.name, args, kwargs)
return map(MarkInfo, self._marks)
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
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)

View File

@@ -1,17 +1,17 @@
""" monkeypatching and mocking functionality. """
from __future__ import absolute_import, division, print_function
import os, sys
import os
import sys
import re
from py.builtin import _basestring
import pytest
import six
from _pytest.fixtures import fixture
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@pytest.fixture
def monkeypatch(request):
@fixture
def monkeypatch():
"""The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
@@ -30,8 +30,8 @@ def monkeypatch(request):
will be raised if the set/deletion operation has no target.
"""
mpatch = MonkeyPatch()
request.addfinalizer(mpatch.undo)
return mpatch
yield mpatch
mpatch.undo()
def resolve(name):
@@ -70,15 +70,15 @@ def annotated_getattr(obj, name, ann):
obj = getattr(obj, name)
except AttributeError:
raise AttributeError(
'%r object at %s has no attribute %r' % (
type(obj).__name__, ann, name
)
'%r object at %s has no attribute %r' % (
type(obj).__name__, ann, name
)
)
return obj
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)
@@ -124,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")
@@ -154,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")

37
_pytest/nodes.py Normal file
View File

@@ -0,0 +1,37 @@
SEP = "/"
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

View File

@@ -1,10 +1,10 @@
""" run test suites written for nose. """
from __future__ import absolute_import, division, print_function
import sys
import py
import pytest
from _pytest import unittest
from _pytest import unittest, runner, python
from _pytest.config import hookimpl
def get_skip_exceptions():
@@ -19,52 +19,53 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda:
pytest.skip(str(call.excinfo.value)), call.when)
call2 = call.__class__(
lambda: runner.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo
@pytest.hookimpl(trylast=True)
@hookimpl(trylast=True)
def pytest_runtest_setup(item):
if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator):
if isinstance(item.parent, python.Generator):
gen = item.parent
if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup')
if isinstance(gen.parent, pytest.Instance):
if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True
if not call_optional(item.obj, 'setup'):
# call module level setup if there is no object level one
call_optional(item.parent.obj, 'setup')
#XXX this implies we only call teardown when setup worked
# XXX this implies we only call teardown when setup worked
item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
def teardown_nose(item):
if is_potential_nosetest(item):
if not call_optional(item.obj, 'teardown'):
call_optional(item.parent.obj, 'teardown')
#if hasattr(item.parent, '_nosegensetup'):
# if hasattr(item.parent, '_nosegensetup'):
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, pytest.Generator):
if isinstance(collector, python.Generator):
call_optional(collector.obj, 'setup')
def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes
return isinstance(item, pytest.Function) and \
return isinstance(item, python.Function) and \
not isinstance(item, unittest.TestCaseFunction)
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()

147
_pytest/outcomes.py Normal file
View File

@@ -0,0 +1,147 @@
"""
exception classes and constants handling test outcomes
as well as functions creating them
"""
from __future__ import absolute_import, division, print_function
import py
import sys
class OutcomeException(BaseException):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
BaseException.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace
def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" % (self.__class__.__name__,)
__str__ = __repr__
TEST_OUTCOME = (OutcomeException, Exception)
class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level
class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'
class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)
# exposed helper methods
def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)
exit.Exception = Exit
def skip(msg="", **kwargs):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
: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
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
def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed
class XFailed(fail.Exception):
""" raised from an explicit call to pytest.xfail() """
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)
xfail.Exception = XFailed
def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False
with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" % (
modname, verattr, minversion), allow_module_level=True)
return mod

View File

@@ -1,5 +1,8 @@
""" submit failure or test session information to a pastebin service. """
from __future__ import absolute_import, division, print_function
import pytest
import six
import sys
import tempfile
@@ -7,13 +10,13 @@ import tempfile
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group._addoption('--pastebin', metavar="mode",
action='store', dest="pastebin", default=None,
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
action='store', dest="pastebin", default=None,
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
@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;
@@ -23,13 +26,16 @@ def pytest_configure(config):
# pastebin file will be utf-8 encoded binary file
config._pastebinfile = tempfile.TemporaryFile('w+b')
oldwrite = tr._tw.write
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)
tr._tw.write = tee_write
def pytest_unconfigure(config):
if hasattr(config, '_pastebinfile'):
# get terminal contents and delete file
@@ -45,6 +51,7 @@ def pytest_unconfigure(config):
pastebinurl = create_new_paste(sessionlog)
tr.write_line("pastebin session-log: %s\n" % pastebinurl)
def create_new_paste(contents):
"""
Creates a new paste using bpaste.net service.
@@ -72,6 +79,7 @@ def create_new_paste(contents):
else:
return 'bad response: ' + response
def pytest_terminal_summary(terminalreporter):
import _pytest.config
if terminalreporter.config.option.pastebin != "failed":
@@ -89,4 +97,4 @@ def pytest_terminal_summary(terminalreporter):
s = tw.stringio.getvalue()
assert len(s)
pastebinurl = create_new_paste(s)
tr.write_line("%s --> %s" %(msg, pastebinurl))
tr.write_line("%s --> %s" % (msg, pastebinurl))

View File

@@ -1,17 +1,21 @@
""" (disabled by default) support for testing pytest and pytest plugins. """
from __future__ import absolute_import, division, print_function
import codecs
import gc
import os
import platform
import re
import subprocess
import six
import sys
import time
import traceback
from fnmatch import fnmatch
from py.builtin import print_
from weakref import WeakKeyDictionary
from _pytest.capture import MultiCapture, SysCapture
from _pytest._code import Source
import py
import pytest
@@ -19,27 +23,22 @@ from _pytest.main import Session, EXIT_OK
from _pytest.assertion.rewrite import AssertionRewritingHook
PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace("$py.class", ".py")
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
choices=("inprocess", "subprocess", ),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
def pytest_configure(config):
# This might be called multiple times. Only take the first.
global _pytest_fullpath
try:
_pytest_fullpath
except NameError:
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
if config.getvalue("lsof"):
checker = LsofFdLeakChecker()
if checker.matching_platform():
@@ -59,7 +58,7 @@ class LsofFdLeakChecker(object):
def _parse_lsof_output(self, out):
def isopen(line):
return line.startswith('f') and ("deleted" not in line and
'mem' not in line and "txt" not in line and 'cwd' not in line)
'mem' not in line and "txt" not in line and 'cwd' not in line)
open_files = []
@@ -85,7 +84,7 @@ class LsofFdLeakChecker(object):
return True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_item(self, item):
def pytest_runtest_protocol(self, item):
lines1 = self.get_open_files()
yield
if hasattr(sys, "pypy_version_info"):
@@ -104,20 +103,19 @@ class LsofFdLeakChecker(object):
error.extend([str(f) for f in lines2])
error.append(error[0])
error.append("*** function %s:%s: %s " % item.location)
pytest.fail("\n".join(error), pytrace=False)
error.append("See issue #2366")
item.warn('', "\n".join(error))
# XXX copied from execnet's conftest.py - needs to be merged
winpymap = {
'python2.7': r'C:\Python27\python.exe',
'python2.6': r'C:\Python26\python.exe',
'python3.1': r'C:\Python31\python.exe',
'python3.2': r'C:\Python32\python.exe',
'python3.3': r'C:\Python33\python.exe',
'python3.4': r'C:\Python34\python.exe',
'python3.5': r'C:\Python35\python.exe',
'python3.6': r'C:\Python36\python.exe',
}
def getexecutable(name, cache={}):
try:
return cache[name]
@@ -126,21 +124,21 @@ def getexecutable(name, cache={}):
if executable:
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if name == "jython":
if not err or "2.5" not in err:
executable = None
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# Handle pyenv's 127.
executable = None
cache[name] = executable
return executable
@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
'pypy', 'pypy3'])
@pytest.fixture(params=['python2.7', 'python3.4', 'pypy', 'pypy3'])
def anypython(request):
name = request.param
executable = getexecutable(name)
@@ -155,6 +153,8 @@ def anypython(request):
return executable
# used at least by pytest-xdist plugin
@pytest.fixture
def _pytest(request):
""" Return a helper which offers a gethookrecorder(hook)
@@ -163,6 +163,7 @@ def _pytest(request):
"""
return PytestArg(request)
class PytestArg:
def __init__(self, request):
self.request = request
@@ -173,9 +174,9 @@ class PytestArg:
return hookrecorder
def get_public_names(l):
"""Only return names from iterator l without a leading underscore."""
return [x for x in l if x[0] != "_"]
def get_public_names(values):
"""Only return names from iterator values without a leading underscore."""
return [x for x in values if x[0] != "_"]
class ParsedCall:
@@ -186,7 +187,7 @@ class ParsedCall:
def __repr__(self):
d = self.__dict__.copy()
del d['_name']
return "<ParsedCall %r(**%r)>" %(self._name, d)
return "<ParsedCall %r(**%r)>" % (self._name, d)
class HookRecorder:
@@ -226,15 +227,15 @@ class HookRecorder:
name, check = entries.pop(0)
for ind, call in enumerate(self.calls[i:]):
if call._name == name:
print_("NAMEMATCH", name, call)
print("NAMEMATCH", name, call)
if eval(check, backlocals, call.__dict__):
print_("CHECKERMATCH", repr(check), "->", call)
print("CHECKERMATCH", repr(check), "->", call)
else:
print_("NOCHECKERMATCH", repr(check), "-", call)
print("NOCHECKERMATCH", repr(check), "-", call)
continue
i += ind + 1
break
print_("NONAMEMATCH", name, "with", call)
print("NONAMEMATCH", name, "with", call)
else:
pytest.fail("could not find %r check %r" % (name, check))
@@ -249,9 +250,9 @@ class HookRecorder:
pytest.fail("\n".join(lines))
def getcall(self, name):
l = self.getcalls(name)
assert len(l) == 1, (name, l)
return l[0]
values = self.getcalls(name)
assert len(values) == 1, (name, values)
return values[0]
# functionality for test reports
@@ -260,9 +261,9 @@ class HookRecorder:
return [x.report for x in self.getcalls(names)]
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
values = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
@@ -273,14 +274,14 @@ class HookRecorder:
if when and getattr(rep, 'when', None) != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):
l.append(rep)
if not l:
values.append(rep)
if not values:
raise ValueError("could not find test report matching %r: "
"no test reports at all!" % (inamepart,))
if len(l) > 1:
if len(values) > 1:
raise ValueError(
"found 2 or more testreports matching %r: %s" %(inamepart, l))
return l[0]
"found 2 or more testreports matching %r: %s" % (inamepart, values))
return values[0]
def getfailures(self,
names='pytest_runtest_logreport pytest_collectreport'):
@@ -294,7 +295,7 @@ class HookRecorder:
skipped = []
failed = []
for rep in self.getreports(
"pytest_collectreport pytest_runtest_logreport"):
"pytest_collectreport pytest_runtest_logreport"):
if rep.passed:
if getattr(rep, "when", None) == "call":
passed.append(rep)
@@ -332,7 +333,9 @@ def testdir(request, tmpdir_factory):
return Testdir(request, tmpdir_factory)
rex_outcome = re.compile("(\d+) ([\w-]+)")
rex_outcome = re.compile(r"(\d+) ([\w-]+)")
class RunResult:
"""The result of running a command.
@@ -348,6 +351,7 @@ class RunResult:
:duration: Duration in seconds.
"""
def __init__(self, ret, outlines, errlines, duration):
self.ret = ret
self.outlines = outlines
@@ -367,15 +371,19 @@ class RunResult:
for num, cat in outcomes:
d[cat] = int(num)
return d
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0):
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
d = self.parseoutcomes()
assert passed == d.get("passed", 0)
assert skipped == d.get("skipped", 0)
assert failed == d.get("failed", 0)
obtained = {
'passed': d.get('passed', 0),
'skipped': d.get('skipped', 0),
'failed': d.get('failed', 0),
'error': d.get('error', 0),
}
assert obtained == dict(passed=passed, skipped=skipped, failed=failed, error=error)
class Testdir:
@@ -401,20 +409,13 @@ class Testdir:
def __init__(self, request, tmpdir_factory):
self.request = request
# XXX remove duplication with tmpdir plugin
basetmp = tmpdir_factory.ensuretemp("testdir")
self._mod_collections = WeakKeyDictionary()
name = request.function.__name__
for i in range(100):
try:
tmpdir = basetmp.mkdir(name + str(i))
except py.error.EEXIST:
continue
break
self.tmpdir = tmpdir
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
self.plugins = []
self._savesyspath = (list(sys.path), list(sys.meta_path))
self._savemodulekeys = set(sys.modules)
self.chdir() # always chdir
self.chdir() # always chdir
self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
@@ -446,9 +447,10 @@ class Testdir:
the module is re-imported.
"""
for name in set(sys.modules).difference(self._savemodulekeys):
# it seems zope.interfaces is keeping some state
# (used by twisted related tests)
if name != "zope.interface":
# some zope modules used by twisted-related tests keeps internal
# state and can't be deleted; we had some trouble in the past
# with zope.interface for example
if not name.startswith("zope"):
del sys.modules[name]
def make_hook_recorder(self, pluginmanager):
@@ -468,26 +470,24 @@ class Testdir:
if not hasattr(self, '_olddir'):
self._olddir = old
def _makefile(self, ext, args, kwargs):
def _makefile(self, ext, args, kwargs, encoding='utf-8'):
items = list(kwargs.items())
def to_text(s):
return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s)
if args:
source = py.builtin._totext("\n").join(
map(py.builtin._totext, args)) + py.builtin._totext("\n")
source = u"\n".join(to_text(x) for x in args)
basename = self.request.function.__name__
items.insert(0, (basename, source))
ret = None
for name, value in items:
p = self.tmpdir.join(name).new(ext=ext)
for basename, value in items:
p = self.tmpdir.join(basename).new(ext=ext)
p.dirpath().ensure_dir()
source = Source(value)
def my_totext(s, encoding="utf-8"):
if py.builtin._isbytes(s):
s = py.builtin._totext(s, encoding=encoding)
return s
source_unicode = "\n".join([my_totext(line) for line in source.lines])
source = py.builtin._totext(source_unicode)
content = source.strip().encode("utf-8") # + "\n"
#content = content.rstrip() + "\n"
p.write(content, "wb")
source = u"\n".join(to_text(line) for line in source.lines)
p.write(source.strip().encode(encoding), "wb")
if ret is None:
ret = p
return ret
@@ -562,7 +562,7 @@ class Testdir:
def mkpydir(self, name):
"""Create a new python package.
This creates a (sub)direcotry with an empty ``__init__.py``
This creates a (sub)directory with an empty ``__init__.py``
file so that is recognised as a python package.
"""
@@ -571,6 +571,7 @@ class Testdir:
return p
Session = Session
def getnode(self, config, arg):
"""Return the collection node of a file.
@@ -651,13 +652,13 @@ class Testdir:
"""
p = self.makepyfile(source)
l = list(cmdlineargs) + [p]
return self.inline_run(*l)
values = list(cmdlineargs) + [p]
return self.inline_run(*values)
def inline_genitems(self, *args):
"""Run ``pytest.main(['--collectonly'])`` in-process.
Retuns a tuple of the collected items and a
Returns a tuple of the collected items and a
:py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of
@@ -692,12 +693,15 @@ class Testdir:
# warning which will trigger to say they can no longer be
# re-written, which is fine as they are already re-written.
orig_warn = AssertionRewritingHook._warn_already_imported
def revert():
AssertionRewritingHook._warn_already_imported = orig_warn
self.request.addfinalizer(revert)
AssertionRewritingHook._warn_already_imported = lambda *a: None
rec = []
class Collect:
def pytest_configure(x, config):
rec.append(self.make_hook_recorder(config.pluginmanager))
@@ -727,25 +731,30 @@ class Testdir:
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
capture = py.io.StdCapture()
capture = MultiCapture(Capture=SysCapture)
capture.start_capturing()
try:
try:
reprec = self.inline_run(*args, **kwargs)
except SystemExit as e:
class reprec:
ret = e.args[0]
except Exception:
traceback.print_exc()
class reprec:
ret = 3
finally:
out, err = capture.reset()
out, err = capture.readouterr()
capture.stop_capturing()
sys.stdout.write(out)
sys.stderr.write(err)
res = RunResult(reprec.ret,
out.split("\n"), err.split("\n"),
time.time()-now)
time.time() - now)
res.reprec = reprec
return res
@@ -761,11 +770,11 @@ class Testdir:
args = [str(x) for x in args]
for x in args:
if str(x).startswith('--basetemp'):
#print ("basedtemp exists: %s" %(args,))
# print("basedtemp exists: %s" %(args,))
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
#print ("added basetemp: %s" %(args,))
# print("added basetemp: %s" %(args,))
return args
def parseconfig(self, *args):
@@ -803,7 +812,7 @@ class Testdir:
self.request.addfinalizer(config._ensure_unconfigure)
return config
def getitem(self, source, funcname="test_func"):
def getitem(self, source, funcname="test_func"):
"""Return the test item for a test function.
This writes the source to a python file and runs pytest's
@@ -820,10 +829,10 @@ class Testdir:
for item in items:
if item.name == funcname:
return item
assert 0, "%r item not found in module:\n%s\nitems: %s" %(
assert 0, "%r item not found in module:\n%s\nitems: %s" % (
funcname, source, items)
def getitems(self, source):
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs pytest's
@@ -834,7 +843,7 @@ class Testdir:
modcol = self.getmodulecol(source)
return self.genitems([modcol])
def getmodulecol(self, source, configargs=(), withinit=False):
def getmodulecol(self, source, configargs=(), withinit=False):
"""Return the module collection node for ``source``.
This writes ``source`` to a file using :py:meth:`makepyfile`
@@ -847,15 +856,16 @@ class Testdir:
:py:meth:`parseconfigure`.
:param withinit: Whether to also write a ``__init__.py`` file
to the temporarly directory to ensure it is a package.
to the temporary directory to ensure it is a package.
"""
kw = {self.request.function.__name__: Source(source).strip()}
path = self.makepyfile(**kw)
if withinit:
self.makepyfile(__init__ = "#")
self.makepyfile(__init__="#")
self.config = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path)
return node
def collect_by_name(self, modcol, name):
@@ -870,7 +880,9 @@ class Testdir:
:param name: The name of the node to return.
"""
for colitem in modcol._memocollect():
if modcol not in self._mod_collections:
self._mod_collections[modcol] = list(modcol.collect())
for colitem in self._mod_collections[modcol]:
if colitem.name == name:
return colitem
@@ -887,8 +899,11 @@ class Testdir:
env['PYTHONPATH'] = os.pathsep.join(filter(None, [
str(os.getcwd()), env.get('PYTHONPATH', '')]))
kw['env'] = env
return subprocess.Popen(cmdargs,
stdout=stdout, stderr=stderr, **kw)
popen = subprocess.Popen(cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw)
popen.stdin.close()
return popen
def run(self, *cmdargs):
"""Run a command with arguments.
@@ -905,14 +920,14 @@ class Testdir:
cmdargs = [str(x) for x in cmdargs]
p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr")
print_("running:", ' '.join(cmdargs))
print_(" in:", str(py.path.local()))
print("running:", ' '.join(cmdargs))
print(" in:", str(py.path.local()))
f1 = codecs.open(str(p1), "w", encoding="utf8")
f2 = codecs.open(str(p2), "w", encoding="utf8")
try:
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
close_fds=(sys.platform != "win32"))
ret = popen.wait()
finally:
f1.close()
@@ -927,19 +942,19 @@ class Testdir:
f2.close()
self._dump_lines(out, sys.stdout)
self._dump_lines(err, sys.stderr)
return RunResult(ret, out, err, time.time()-now)
return RunResult(ret, out, err, time.time() - now)
def _dump_lines(self, lines, fp):
try:
for line in lines:
py.builtin.print_(line, file=fp)
print(line, file=fp)
except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a pytest.exe
return (sys.executable, _pytest_fullpath,) # noqa
return (sys.executable, PYTEST_FULLPATH) # noqa
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
@@ -966,12 +981,12 @@ class Testdir:
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
#for x in args:
# for x in args:
# if '--confcutdir' in str(x):
# break
#else:
# else:
# pass
# args = ('--confcutdir=.',) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
@@ -989,7 +1004,7 @@ class Testdir:
The pexpect child is returned.
"""
basetemp = self.tmpdir.mkdir("pexpect")
basetemp = self.tmpdir.mkdir("temp-pexpect")
invoke = " ".join(map(str, self._getpytestargs()))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout)
@@ -1002,8 +1017,6 @@ class Testdir:
pexpect = pytest.importorskip("pexpect", "3.0")
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
pytest.skip("pypy-64 bit not supported")
if sys.platform == "darwin":
pytest.xfail("pexpect does not work reliably on darwin?!")
if sys.platform.startswith("freebsd"):
pytest.xfail("pexpect does not work reliably on freebsd")
logfile = self.tmpdir.join("spawn.out").open("wb")
@@ -1012,12 +1025,13 @@ class Testdir:
child.timeout = expect_timeout
return child
def getdecoded(out):
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
class LineComp:
@@ -1047,7 +1061,7 @@ class LineMatcher:
"""
def __init__(self, lines):
def __init__(self, lines):
self.lines = lines
self._log_output = []
@@ -1063,6 +1077,23 @@ class LineMatcher:
return lines2
def fnmatch_lines_random(self, lines2):
"""Check lines exist in the output using ``fnmatch.fnmatch``, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
"""
self._match_lines_random(lines2, fnmatch)
def re_match_lines_random(self, lines2):
"""Check lines exist in the output using ``re.match``, in any order.
The argument is a list of lines which have to occur in the
output, in any order.
"""
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
def _match_lines_random(self, lines2, match_func):
"""Check lines exist in the output.
The argument is a list of lines which have to occur in the
@@ -1072,7 +1103,7 @@ class LineMatcher:
lines2 = self._getlines(lines2)
for line in lines2:
for x in self.lines:
if line == x or fnmatch(x, line):
if line == x or match_func(x, line):
self._log("matched: ", repr(line))
break
else:
@@ -1086,7 +1117,7 @@ class LineMatcher:
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
return self.lines[i+1:]
return self.lines[i + 1:]
raise ValueError("line %r not found in output" % fnline)
def _log(self, *args):
@@ -1097,13 +1128,37 @@ class LineMatcher:
return '\n'.join(self._log_output)
def fnmatch_lines(self, lines2):
"""Search the text for matching lines.
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
The argument is a list of lines which have to match and can
use glob wildcards. If they do not match an pytest.fail() is
use glob wildcards. If they do not match a pytest.fail() is
called. The matches and non-matches are also printed on
stdout.
"""
self._match_lines(lines2, fnmatch, 'fnmatch')
def re_match_lines(self, lines2):
"""Search captured text for matching lines using ``re.match``.
The argument is a list of lines which have to match using ``re.match``.
If they do not match a pytest.fail() is called.
The matches and non-matches are also printed on
stdout.
"""
self._match_lines(lines2, lambda name, pat: re.match(pat, name), 're.match')
def _match_lines(self, lines2, match_func, match_nickname):
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
:param list[str] lines2: list of string patterns to match. The actual format depends on
``match_func``.
:param match_func: a callable ``match_func(line, pattern)`` where line is the captured
line from stdout/stderr and pattern is the matching pattern.
:param str match_nickname: the nickname for the match function that will be logged
to stdout when a match occurs.
"""
lines2 = self._getlines(lines2)
lines1 = self.lines[:]
@@ -1117,8 +1172,8 @@ class LineMatcher:
if line == nextline:
self._log("exact match:", repr(line))
break
elif fnmatch(nextline, line):
self._log("fnmatch:", repr(line))
elif match_func(nextline, line):
self._log("%s:" % match_nickname, repr(line))
self._log(" with:", repr(nextline))
break
else:

File diff suppressed because it is too large Load Diff

619
_pytest/python_api.py Normal file
View File

@@ -0,0 +1,619 @@
import math
import sys
import py
from six.moves import zip
from _pytest.compat import isclass
from _pytest.outcomes import fail
import _pytest._code
def _cmp_raises_type_error(self, other):
"""__cmp__ implementation which raises TypeError. Used
by Approx base classes to implement only == and != and raise a
TypeError for other comparisons.
Needed in Python 2 only, Python 3 all it takes is not implementing the
other operators at all.
"""
__tracebackhide__ = True
raise TypeError('Comparison operators other than == and != not supported by approx objects')
# builtin pytest.approx helper
class ApproxBase(object):
"""
Provide shared utilities for making approximate comparisons between numbers
or sequences of numbers.
"""
def __init__(self, expected, rel=None, abs=None, nan_ok=False):
self.expected = expected
self.abs = abs
self.rel = rel
self.nan_ok = nan_ok
def __repr__(self):
raise NotImplementedError
def __eq__(self, actual):
return all(
a == self._approx_scalar(x)
for a, x in self._yield_comparisons(actual))
__hash__ = None
def __ne__(self, actual):
return not (actual == self)
if sys.version_info[0] == 2:
__cmp__ = _cmp_raises_type_error
def _approx_scalar(self, x):
return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
def _yield_comparisons(self, actual):
"""
Yield all the pairs of numbers to be compared. This is used to
implement the `__eq__` method.
"""
raise NotImplementedError
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...
return "approx({0!r})".format(list(
self._approx_scalar(x) for x in self.expected))
if sys.version_info[0] == 2:
__cmp__ = _cmp_raises_type_error
def __eq__(self, actual):
import numpy as np
try:
actual = np.asarray(actual)
except: # noqa
raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
if actual.shape != self.expected.shape:
return False
return ApproxBase.__eq__(self, actual)
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]
class ApproxMapping(ApproxBase):
"""
Perform approximate comparisons for mappings where the values are numbers
(the keys can be anything).
"""
def __repr__(self):
return "approx({0!r})".format(dict(
(k, self._approx_scalar(v))
for k, v in self.expected.items()))
def __eq__(self, actual):
if set(actual.keys()) != set(self.expected.keys()):
return False
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
for k in self.expected.keys():
yield actual[k], self.expected[k]
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):
seq_type = list
return "approx({0!r})".format(seq_type(
self._approx_scalar(x) for x in self.expected))
def __eq__(self, actual):
if len(actual) != len(self.expected):
return False
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
return zip(actual, self.expected)
class ApproxScalar(ApproxBase):
"""
Perform approximate comparisons for single numbers only.
"""
def __repr__(self):
"""
Return a string communicating both the expected value and the tolerance
for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
plus/minus symbol if this is python3 (it's too hard to get right for
python2).
"""
if isinstance(self.expected, complex):
return str(self.expected)
# Infinities aren't compared using tolerances, so don't show a
# tolerance.
if math.isinf(self.expected):
return str(self.expected)
# If a sensible tolerance can't be calculated, self.tolerance will
# raise a ValueError. In this case, display '???'.
try:
vetted_tolerance = '{:.1e}'.format(self.tolerance)
except ValueError:
vetted_tolerance = '???'
if sys.version_info[0] == 2:
return '{0} +- {1}'.format(self.expected, vetted_tolerance)
else:
return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
def __eq__(self, actual):
"""
Return true if the given value is equal to the expected value within
the pre-specified tolerance.
"""
# Short-circuit exact equality.
if actual == self.expected:
return True
# Allow the user to control whether NaNs are considered equal to each
# other or not. The abs() calls are for compatibility with complex
# numbers.
if math.isnan(abs(self.expected)):
return self.nan_ok and math.isnan(abs(actual))
# Infinity shouldn't be approximately equal to anything but itself, but
# if there's a relative tolerance, it will be infinite and infinity
# will seem approximately equal to everything. The equal-to-itself
# case would have been short circuited above, so here we can just
# return false if the expected value is infinite. The abs() call is
# for compatibility with complex numbers.
if math.isinf(abs(self.expected)):
return False
# Return true if the two numbers are within the tolerance.
return abs(self.expected - actual) <= self.tolerance
__hash__ = None
@property
def tolerance(self):
"""
Return the tolerance for the comparison. This could be either an
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
# 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)
if absolute_tolerance < 0:
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(absolute_tolerance):
raise ValueError("absolute tolerance can't be NaN.")
# If the user specified an absolute tolerance but not a relative one,
# just return the absolute tolerance.
if self.rel is None:
if self.abs is not None:
return absolute_tolerance
# Figure out what the relative tolerance should be. ``self.rel`` is
# either None or a value specified by the user. This is done after
# 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)
if relative_tolerance < 0:
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(relative_tolerance):
raise ValueError("relative tolerance can't be NaN.")
# Return the larger of the relative and absolute tolerances.
return max(relative_tolerance, absolute_tolerance)
def approx(expected, rel=None, abs=None, nan_ok=False):
"""
Assert that two numbers (or two sets of numbers) are equal to each other
within some tolerance.
Due to the `intricacies of floating-point arithmetic`__, numbers that we
would intuitively expect to be equal are not always so::
>>> 0.1 + 0.2 == 0.3
False
__ https://docs.python.org/3/tutorial/floatingpoint.html
This problem is commonly encountered when writing tests, e.g. when making
sure that floating-point values are what you expect them to be. One way to
deal with this problem is to assert that two floating-point numbers are
equal to within some appropriate tolerance::
>>> abs((0.1 + 0.2) - 0.3) < 1e-6
True
However, comparisons like this are tedious to write and difficult to
understand. Furthermore, absolute comparisons like the one above are
usually discouraged because there's no tolerance that works well for all
situations. ``1e-6`` is good for numbers around ``1``, but too small for
very big numbers and too big for very small ones. It's better to express
the tolerance as a fraction of the expected value, but relative comparisons
like that are even more difficult to write correctly and concisely.
The ``approx`` class performs floating-point comparisons using a syntax
that's as intuitive as possible::
>>> from pytest import approx
>>> 0.1 + 0.2 == approx(0.3)
True
The same syntax also works for sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
Dictionary *values*::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
And ``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
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
``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
To handle this case less surprisingly, ``approx`` also considers numbers
within an absolute tolerance of ``1e-12`` of its expected value to be
equal. Infinity and NaN are special cases. Infinity is only considered
equal to itself, regardless of the relative tolerance. NaN is not
considered equal to anything by default, but you can make it be equal to
itself by setting the ``nan_ok`` argument to True. (This is meant to
facilitate comparing arrays that use NaN to mean "no data".)
Both the relative and absolute tolerances can be changed by passing
arguments to the ``approx`` constructor::
>>> 1.0001 == approx(1)
False
>>> 1.0001 == approx(1, rel=1e-3)
True
>>> 1.0001 == approx(1, abs=1e-3)
True
If you specify ``abs`` but not ``rel``, the comparison will not consider
the relative tolerance at all. In other words, two numbers that are within
the default relative tolerance of ``1e-6`` will still be considered unequal
if they exceed the specified absolute tolerance. If you specify both
``abs`` and ``rel``, the numbers will be considered equal if either
tolerance is met::
>>> 1 + 1e-8 == approx(1)
True
>>> 1 + 1e-8 == approx(1, abs=1e-12)
False
>>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
True
If you're thinking about using ``approx``, then you might want to know how
it compares to other good ways of comparing floating-point numbers. All of
these algorithms are based on relative and absolute tolerances and should
agree for the most part, but they do have meaningful differences:
- ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
tolerance is met. Because the relative tolerance is calculated w.r.t.
both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
``b`` is a "reference value"). You have to specify an absolute tolerance
if you want to compare to ``0.0`` because there is no tolerance by
default. Only available in python>=3.5. `More information...`__
__ https://docs.python.org/3/library/math.html#math.isclose
- ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
between ``a`` and ``b`` is less that the sum of the relative tolerance
w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
is only calculated w.r.t. ``b``, this test is asymmetric and you can
think of ``b`` as the reference value. Support for comparing sequences
is provided by ``numpy.allclose``. `More information...`__
__ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
- ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
are within an absolute tolerance of ``1e-7``. No relative tolerance is
considered and the absolute tolerance cannot be changed, so this function
is not appropriate for very large or very small numbers. Also, it's only
available in subclasses of ``unittest.TestCase`` and it's ugly because it
doesn't follow PEP8. `More information...`__
__ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
- ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
Because the relative tolerance is only calculated w.r.t. ``b``, this test
is asymmetric and you can think of ``b`` as the reference value. In the
special case that you explicitly specify an absolute tolerance but not a
relative tolerance, only the absolute tolerance is considered.
.. warning::
.. versionchanged:: 3.2
In order to avoid inconsistent behavior, ``TypeError`` is
raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
The example below illustrates the problem::
assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10)
assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10)
In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
comparison. This is because the call hierarchy of rich comparisons
follows a fixed behavior. `More information...`__
__ https://docs.python.org/3/reference/datamodel.html#object.__ge__
"""
from collections import Mapping, Sequence
from _pytest.compat import STRING_TYPES as String
# 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).
#
# This architecture is really driven by the need to support numpy arrays.
# The only way to override `==` for arrays without requiring that approx be
# the left operand is to inherit the approx object from `numpy.ndarray`.
# But that can't be a general solution, because it requires (1) numpy to be
# installed and (2) the expected value to be a numpy array. So the general
# solution is to delegate each type of expected value to a different class.
#
# This has the advantage that it made it easy to support mapping types
# (i.e. dict). The old code accepted mapping types, but would only compare
# their keys, which is probably not what most people would expect.
if _is_numpy_array(expected):
cls = ApproxNumpy
elif isinstance(expected, Mapping):
cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, String):
cls = ApproxSequence
else:
cls = ApproxScalar
return cls(expected, rel, abs, nan_ok)
def _is_numpy_array(obj):
"""
Return true if the given object is a numpy array. Make a special effort to
avoid importing numpy unless it's really necessary.
"""
import inspect
for cls in inspect.getmro(type(obj)):
if cls.__module__ == 'numpy':
try:
import numpy as np
return isinstance(obj, np.ndarray)
except ImportError:
pass
return False
# builtin pytest.raises helper
def raises(expected_exception, *args, **kwargs):
"""
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
This helper produces a ``ExceptionInfo()`` object (see below).
You may use this function as a context manager::
>>> with raises(ZeroDivisionError):
... 1/0
.. versionchanged:: 2.10
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
.. note::
When using ``pytest.raises`` as a context manager, it's worthwhile to
note that normal context manager rules apply and that the exception
raised *must* be the final line in the scope of the context manager.
Lines of code after that, within the scope of the context manager will
not be executed. For example::
>>> value = 15
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert exc_info.type == ValueError
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
**Legacy forms**
The forms below are fully supported but are discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
It is possible to specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>
or you can specify an arbitrary callable with arguments::
>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
It is also possible to pass a string to be evaluated at runtime::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
The string will be evaluated using the same ``locals()`` and ``globals()``
at the moment of the ``raises`` call.
.. autoclass:: _pytest._code.ExceptionInfo
:members:
.. note::
Similar to caught exception objects in Python, explicitly clearing
local references to returned ``ExceptionInfo`` objects can
help the Python interpreter speed up its garbage collection.
Clearing those references breaks a reference cycle
(``ExceptionInfo`` --> caught exception --> frame stack raising
the exception --> current frame stack --> local variables -->
``ExceptionInfo``) which makes Python keep all objects referenced
from that cycle (including all local variables in the current
frame) alive until the next cyclic garbage collection run. See the
official Python ``try`` statement documentation for more detailed
information.
"""
__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))
message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
if not args:
if "message" in 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
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
# print "raises frame scope: %r" % frame.f_locals
try:
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except expected_exception:
return _pytest._code.ExceptionInfo()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except expected_exception:
return _pytest._code.ExceptionInfo()
fail(message)
raises.Exception = fail.Exception
class RaisesContext(object):
def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception
self.message = message
self.match_expr = match_expr
self.excinfo = None
def __enter__(self):
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
return self.excinfo
def __exit__(self, *tp):
__tracebackhide__ = True
if tp[0] is None:
fail(self.message)
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:
self.excinfo.match(self.match_expr)
return suppress_exception

View File

@@ -1,4 +1,5 @@
""" recording warnings during test function execution. """
from __future__ import absolute_import, division, print_function
import inspect
@@ -6,11 +7,15 @@ import _pytest._code
import py
import sys
import warnings
import pytest
import re
from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail
@pytest.yield_fixture
def recwarn(request):
@yield_fixture
def recwarn():
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
@@ -25,54 +30,59 @@ def recwarn(request):
yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func=None, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)`` triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``.
"""context manager that can be used to ensure a block of code triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``::
This function can be used as a context manager::
>>> import warnings
>>> def api_call_v2():
... warnings.warn('use v3 of this api', DeprecationWarning)
... return 200
>>> with deprecated_call():
... myobject.deprecated_method()
... assert api_call_v2() == 200
Note: we cannot use WarningsRecorder here because it is still subject
to the mechanism that prevents warnings of the same type from being
triggered twice for the same module. See #1190.
``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
types above.
"""
if not func:
return WarningsChecker(expected_warning=DeprecationWarning)
categories = []
def warn_explicit(message, category, *args, **kwargs):
categories.append(category)
old_warn_explicit(message, category, *args, **kwargs)
def warn(message, category=None, *args, **kwargs):
if isinstance(message, Warning):
categories.append(message.__class__)
else:
categories.append(category)
old_warn(message, category, *args, **kwargs)
old_warn = warnings.warn
old_warn_explicit = warnings.warn_explicit
warnings.warn_explicit = warn_explicit
warnings.warn = warn
try:
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = old_warn_explicit
warnings.warn = old_warn
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
if not any(issubclass(c, deprecation_categories) for c in categories):
return _DeprecatedCallContext()
else:
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
return ret
with _DeprecatedCallContext():
return func(*args, **kwargs)
class _DeprecatedCallContext(object):
"""Implements the logic to capture deprecation warnings as a context manager."""
def __enter__(self):
self._captured_categories = []
self._old_warn = warnings.warn
self._old_warn_explicit = warnings.warn_explicit
warnings.warn_explicit = self._warn_explicit
warnings.warn = self._warn
def _warn_explicit(self, message, category, *args, **kwargs):
self._captured_categories.append(category)
def _warn(self, message, category=None, *args, **kwargs):
if isinstance(message, Warning):
self._captured_categories.append(message.__class__)
else:
self._captured_categories.append(category)
def __exit__(self, exc_type, exc_val, exc_tb):
warnings.warn_explicit = self._old_warn_explicit
warnings.warn = self._old_warn
if exc_type is None:
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
if not any(issubclass(c, deprecation_categories) for c in self._captured_categories):
__tracebackhide__ = True
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
raise AssertionError(msg)
def warns(expected_warning, *args, **kwargs):
@@ -90,10 +100,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)
@@ -101,33 +129,23 @@ 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)
class RecordedWarning(object):
def __init__(self, message, category, filename, lineno, file, line):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.file = file
self.line = line
class WarningsRecorder(object):
class WarningsRecorder(warnings.catch_warnings):
"""A context manager to record raised warnings.
Adapted from `warnings.catch_warnings`.
"""
def __init__(self, module=None):
self._module = sys.modules['warnings'] if module is None else module
def __init__(self):
super(WarningsRecorder, self).__init__(record=True)
self._entered = False
self._list = []
@@ -164,38 +182,20 @@ class WarningsRecorder(object):
if self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
def showwarning(message, category, filename, lineno,
file=None, line=None):
self._list.append(RecordedWarning(
message, category, filename, lineno, file, line))
# still perform old showwarning functionality
self._showwarning(
message, category, filename, lineno, file=file, line=line)
self._module.showwarning = showwarning
# allow the same warning to be raised more than once
self._module.simplefilter('always')
self._list = super(WarningsRecorder, self).__enter__()
warnings.simplefilter('always')
return self
def __exit__(self, *exc_info):
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
super(WarningsRecorder, self).__exit__(*exc_info)
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None):
super(WarningsChecker, self).__init__(module=module)
def __init__(self, expected_warning=None, match_expr=None):
super(WarningsChecker, self).__init__()
msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s")
@@ -209,6 +209,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)
@@ -216,6 +217,20 @@ class WarningsChecker(WarningsRecorder):
# only check if we're not currently handling an exception
if all(a is None for a in exc_info):
if self.expected_warning is not None:
if not any(r.category in self.expected_warning for r in self):
if not any(issubclass(r.category, self.expected_warning)
for r in self):
__tracebackhide__ = True
pytest.fail("DID NOT WARN")
fail("DID NOT WARN. No warnings of type {0} was emitted. "
"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

@@ -1,15 +1,18 @@
""" log machine-parseable test session result information in a plain
text file.
"""
from __future__ import absolute_import, division, print_function
import py
import os
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "resultlog plugin options")
group.addoption('--resultlog', '--result-log', action="store",
metavar="path", default=None,
help="DEPRECATED path for machine-readable result log.")
metavar="path", default=None,
help="DEPRECATED path for machine-readable result log.")
def pytest_configure(config):
resultlog = config.option.resultlog
@@ -18,13 +21,14 @@ def pytest_configure(config):
dirname = os.path.dirname(os.path.abspath(resultlog))
if not os.path.isdir(dirname):
os.makedirs(dirname)
logfile = open(resultlog, 'w', 1) # line buffered
logfile = open(resultlog, 'w', 1) # line buffered
config._resultlog = ResultLog(config, logfile)
config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG
config.warn('C1', RESULT_LOG)
def pytest_unconfigure(config):
resultlog = getattr(config, '_resultlog', None)
if resultlog:
@@ -32,6 +36,7 @@ def pytest_unconfigure(config):
del config._resultlog
config.pluginmanager.unregister(resultlog)
def generic_path(item):
chain = item.listchain()
gpath = [chain[0].name]
@@ -55,15 +60,16 @@ def generic_path(item):
fspath = newfspath
return ''.join(gpath)
class ResultLog(object):
def __init__(self, config, logfile):
self.config = config
self.logfile = logfile # preferably line buffered
self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, lettercode, longrepr):
py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
print("%s %s" % (lettercode, testpath), file=self.logfile)
for line in longrepr.splitlines():
py.builtin.print_(" %s" % line, file=self.logfile)
print(" %s" % line, file=self.logfile)
def log_outcome(self, report, lettercode, longrepr):
testpath = getattr(report, 'nodeid', None)

View File

@@ -1,29 +1,26 @@
""" basic collect and runtest protocol implementations """
from __future__ import absolute_import, division, print_function
import bdb
import os
import sys
from time import time
import py
import pytest
from _pytest.compat import _PY2
from _pytest._code.code import TerminalRepr, ExceptionInfo
def pytest_namespace():
return {
'fail' : fail,
'skip' : skip,
'importorskip' : importorskip,
'exit' : exit,
}
from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
#
# pytest plugin hooks
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group.addoption('--durations',
action="store", type=int, default=None, metavar="N",
help="show N slowest setup/test durations (N=0 for all)."),
action="store", type=int, default=None, metavar="N",
help="show N slowest setup/test durations (N=0 for all)."),
def pytest_terminal_summary(terminalreporter):
durations = terminalreporter.config.option.durations
@@ -48,16 +45,16 @@ def pytest_terminal_summary(terminalreporter):
for rep in dlist:
nodeid = rep.nodeid.replace("::()::", "::")
tr.write_line("%02.2fs %-8s %s" %
(rep.duration, rep.when, nodeid))
(rep.duration, rep.when, nodeid))
def pytest_sessionstart(session):
session._setupstate = SetupState()
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(
@@ -66,6 +63,7 @@ def pytest_runtest_protocol(item, nextitem):
runtestprotocol(item, nextitem=nextitem)
return True
def runtestprotocol(item, log=True, nextitem=None):
hasrequest = hasattr(item, "_request")
if hasrequest and not item._request:
@@ -78,7 +76,7 @@ def runtestprotocol(item, log=True, nextitem=None):
if not item.config.option.setuponly:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem))
nextitem=nextitem))
# after all teardown hooks have been called
# want funcargs and request info to go away
if hasrequest:
@@ -86,6 +84,7 @@ def runtestprotocol(item, log=True, nextitem=None):
item.funcargs = None
return reports
def show_test_item(item):
"""Show test function, parameters and the fixtures of the test item."""
tw = item.config.get_terminal_writer()
@@ -96,10 +95,14 @@ def show_test_item(item):
if used_fixtures:
tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
def pytest_runtest_setup(item):
_update_current_test_var(item, 'setup')
item.session._setupstate.prepare(item)
def pytest_runtest_call(item):
_update_current_test_var(item, 'call')
try:
item.runtest()
except Exception:
@@ -112,8 +115,29 @@ def pytest_runtest_call(item):
del tb # Get rid of it in this namespace
raise
def pytest_runtest_teardown(item, nextitem):
_update_current_test_var(item, 'teardown')
item.session._setupstate.teardown_exact(item, nextitem)
_update_current_test_var(item, None)
def _update_current_test_var(item, when):
"""
Update PYTEST_CURRENT_TEST to reflect the current item and stage.
If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
"""
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)')
os.environ[var_name] = value
else:
os.environ.pop(var_name)
def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"):
@@ -139,21 +163,25 @@ def call_and_report(item, when, log=True, **kwds):
hook.pytest_exception_interact(node=item, call=call, report=report)
return report
def check_interactive_exception(call, report):
return call.excinfo and not (
hasattr(report, "wasxfail") or
call.excinfo.errisinstance(skip.Exception) or
call.excinfo.errisinstance(bdb.BdbQuit))
hasattr(report, "wasxfail") or
call.excinfo.errisinstance(skip.Exception) or
call.excinfo.errisinstance(bdb.BdbQuit))
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
class CallInfo:
""" Result/Exception info a function invocation. """
#: None or ExceptionInfo object.
excinfo = None
def __init__(self, func, when):
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
@@ -164,7 +192,7 @@ class CallInfo:
except KeyboardInterrupt:
self.stop = time()
raise
except:
except: # noqa
self.excinfo = ExceptionInfo()
self.stop = time()
@@ -175,6 +203,7 @@ class CallInfo:
status = "result: %r" % (self.result,)
return "<CallInfo when=%r %s>" % (self.when, status)
def getslaveinfoline(node):
try:
return node._slaveinfocache
@@ -185,6 +214,7 @@ def getslaveinfoline(node):
d['id'], d['sysplatform'], ver, d['executable'])
return s
class BaseReport(object):
def __init__(self, **kw):
@@ -249,10 +279,11 @@ class BaseReport(object):
def fspath(self):
return self.nodeid.split("::")[0]
def pytest_runtest_makereport(item, call):
when = call.when
duration = call.stop-call.start
keywords = dict([(x,1) for x in item.keywords])
duration = call.stop - call.start
keywords = dict([(x, 1) for x in item.keywords])
excinfo = call.excinfo
sections = []
if not call.excinfo:
@@ -262,7 +293,7 @@ def pytest_runtest_makereport(item, call):
if not isinstance(excinfo, ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(pytest.skip.Exception):
elif excinfo.errisinstance(skip.Exception):
outcome = "skipped"
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
@@ -270,19 +301,21 @@ def pytest_runtest_makereport(item, call):
outcome = "failed"
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
style=item.config.option.tbstyle)
for rwhen, key, content in item._report_sections:
sections.append(("Captured %s %s" %(key, rwhen), content))
sections.append(("Captured %s %s" % (key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
sections, duration)
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
def __init__(self, nodeid, location, keywords, outcome,
longrepr, when, sections=(), duration=0, **extra):
#: normalized collection node id
@@ -321,16 +354,21 @@ class TestReport(BaseReport):
return "<TestReport %r when=%r outcome=%r>" % (
self.nodeid, self.when, self.outcome)
class TeardownErrorReport(BaseReport):
outcome = "failed"
when = "teardown"
def __init__(self, longrepr, **extra):
self.longrepr = longrepr
self.sections = []
self.__dict__.update(extra)
def pytest_make_collect_report(collector):
call = CallInfo(collector._memocollect, "memocollect")
call = CallInfo(
lambda: list(collector.collect()),
'collect')
longrepr = None
if not call.excinfo:
outcome = "passed"
@@ -348,7 +386,7 @@ def pytest_make_collect_report(collector):
errorinfo = CollectErrorRepr(errorinfo)
longrepr = errorinfo
rep = CollectReport(collector.nodeid, outcome, longrepr,
getattr(call, 'result', None))
getattr(call, 'result', None))
rep.call = call # see collect_one_node
return rep
@@ -369,16 +407,20 @@ class CollectReport(BaseReport):
def __repr__(self):
return "<CollectReport %r lenresult=%s outcome=%r>" % (
self.nodeid, len(self.result), self.outcome)
self.nodeid, len(self.result), self.outcome)
class CollectErrorRepr(TerminalRepr):
def __init__(self, msg):
self.longrepr = msg
def toterminal(self, out):
out.line(self.longrepr, red=True)
class SetupState(object):
""" shared state for setting up/tearing down test items or collectors. """
def __init__(self):
self.stack = []
self._finalizers = {}
@@ -389,8 +431,8 @@ class SetupState(object):
is called at the end of teardown_all().
"""
assert colitem and not isinstance(colitem, tuple)
assert py.builtin.callable(finalizer)
#assert colitem in self.stack # some unit tests don't setup stack :/
assert callable(finalizer)
# assert colitem in self.stack # some unit tests don't setup stack :/
self._finalizers.setdefault(colitem, []).append(finalizer)
def _pop_and_teardown(self):
@@ -404,7 +446,7 @@ class SetupState(object):
fin = finalizers.pop()
try:
fin()
except Exception:
except TEST_OUTCOME:
# XXX Only first exception will be seen by user,
# ideally all should be reported.
if exc is None:
@@ -418,7 +460,7 @@ class SetupState(object):
colitem.teardown()
for colitem in self._finalizers:
assert colitem is None or colitem in self.stack \
or isinstance(colitem, tuple)
or isinstance(colitem, tuple)
def teardown_all(self):
while self.stack:
@@ -451,10 +493,11 @@ class SetupState(object):
self.stack.append(col)
try:
col.setup()
except Exception:
except TEST_OUTCOME:
col._prepare_exc = sys.exc_info()
raise
def collect_one_node(collector):
ihook = collector.ihook
ihook.pytest_collectstart(collector=collector)
@@ -463,109 +506,3 @@ def collect_one_node(collector):
if call and check_interactive_exception(call, rep):
ihook.pytest_exception_interact(node=collector, call=call, report=rep)
return rep
# =============================================================
# Test OutcomeExceptions and helpers for creating them.
class OutcomeException(Exception):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
Exception.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace
def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" %(self.__class__.__name__,)
__str__ = __repr__
class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level
class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'
class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)
# exposed helper methods
def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)
exit.Exception = Exit
def skip(msg=""):
""" 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.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)
skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed
def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion), allow_module_level=True)
return mod

View File

@@ -1,3 +1,5 @@
from __future__ import absolute_import, division, print_function
import pytest
import sys
@@ -5,9 +7,9 @@ import sys
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption('--setuponly', '--setup-only', action="store_true",
help="only setup fixtures, don't execute the tests.")
help="only setup fixtures, do not execute tests.")
group.addoption('--setupshow', '--setup-show', action="store_true",
help="show setup fixtures while executing the tests.")
help="show setup of fixtures while executing tests.")
@pytest.hookimpl(hookwrapper=True)
@@ -42,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()
@@ -61,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,3 +1,5 @@
from __future__ import absolute_import, division, print_function
import pytest

View File

@@ -1,18 +1,21 @@
""" support for skip/xfail functions and markers. """
from __future__ import absolute_import, division, print_function
import os
import six
import sys
import traceback
import py
import pytest
from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.outcomes import fail, skip, xfail, TEST_OUTCOME
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption('--runxfail',
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
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: "
@@ -23,67 +26,65 @@ def pytest_addoption(parser):
def pytest_configure(config):
if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old))
def nop(*args, **kwargs):
pass
nop.Exception = XFailed
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
config.addinivalue_line("markers",
"skip(reason=None): skip the given test function with an optional reason. "
"Example: skip(reason=\"no way of currently testing this\") skips the "
"test."
)
"skip(reason=None): skip the given test function with an optional reason. "
"Example: skip(reason=\"no way of currently testing this\") skips the "
"test."
)
config.addinivalue_line("markers",
"skipif(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html"
)
"skipif(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html"
)
config.addinivalue_line("markers",
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
"mark the the test function as an expected failure if eval(condition) "
"has a True value. Optionally specify a reason for better reporting "
"and run=False if you don't even want to execute the test function. "
"If only specific exception(s) are expected, you can list them in "
"raises, and if the test fails in other ways, it will be reported as "
"a true failure. See http://pytest.org/latest/skipping.html"
)
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
"mark the test function as an expected failure if eval(condition) "
"has a True value. Optionally specify a reason for better reporting "
"and run=False if you don't even want to execute the test function. "
"If only specific exception(s) are expected, you can list them in "
"raises, and if the test fails in other ways, it will be reported as "
"a true failure. See http://pytest.org/latest/skipping.html"
)
def pytest_namespace():
return dict(xfail=xfail)
class XFailed(pytest.fail.Exception):
""" raised from an explicit call to pytest.xfail() """
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)
xfail.Exception = XFailed
class MarkEvaluator:
class MarkEvaluator(object):
def __init__(self, item, name):
self.item = item
self.name = name
@property
def holder(self):
return self.item.keywords.get(self.name)
self._marks = None
self._mark = None
self._mark_name = name
def __bool__(self):
return bool(self.holder)
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:
@@ -93,63 +94,67 @@ class MarkEvaluator:
def istrue(self):
try:
return self._istrue()
except Exception:
except TEST_OUTCOME:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",]
msg = [" " * (self.exc[1].offset + 4) + "^", ]
msg.append("SyntaxError: invalid syntax")
else:
msg = traceback.format_exception_only(*self.exc[:2])
pytest.fail("Error evaluating %r expression\n"
" %s\n"
"%s"
%(self.name, self.expr, "\n".join(msg)),
pytrace=False)
fail("Error evaluating %r expression\n"
" %s\n"
"%s"
% (self._mark_name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config}
d.update(self.item.obj.__globals__)
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:
d = self._getglobals()
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
if hasattr(self.holder, '_arglist'):
arglist = self.holder._arglist
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:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
if 'condition' in kwargs:
args = (kwargs['condition'],)
for expr in args:
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
if isinstance(expr, py.builtin._basestring):
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."
pytest.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)
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):
return self.holder.kwargs.get(attr, default)
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)
@@ -161,32 +166,32 @@ class MarkEvaluator:
return expl
@pytest.hookimpl(tryfirst=True)
@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
pytest.skip(eval_skipif.getexplanation())
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:
pytest.skip(skip_info.kwargs['reason'])
skip(skip_info.kwargs['reason'])
elif skip_info.args:
pytest.skip(skip_info.args[0])
skip(skip_info.args[0])
else:
pytest.skip("unconditional skip")
skip("unconditional skip")
item._evalxfail = MarkEvaluator(item, 'xfail')
check_xfail_no_run(item)
@pytest.mark.hookwrapper
@hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem)
outcome = yield
@@ -201,7 +206,7 @@ def check_xfail_no_run(item):
evalxfail = item._evalxfail
if evalxfail.istrue():
if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
xfail("[NOTRUN] " + evalxfail.getexplanation())
def check_strict_xfail(pyfuncitem):
@@ -213,15 +218,14 @@ def check_strict_xfail(pyfuncitem):
if is_strict_xfail:
del pyfuncitem._evalxfail
explanation = evalxfail.getexplanation()
pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False)
fail('[XPASS(strict)] ' + explanation, pytrace=False)
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
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
@@ -236,11 +240,11 @@ def pytest_runtest_makereport(item, call):
rep.wasxfail = rep.longrepr
elif item.config.option.runxfail:
pass # don't interefere
elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
evalxfail.istrue():
evalxfail.istrue():
if call.excinfo:
if evalxfail.invalidraise(call.excinfo.value):
rep.outcome = "failed"
@@ -257,7 +261,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 item._skipped_by_mark 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
@@ -266,6 +270,8 @@ def pytest_runtest_makereport(item, call):
rep.longrepr = filename, line, reason
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
if hasattr(report, "wasxfail"):
if report.skipped:
@@ -274,10 +280,12 @@ def pytest_report_teststatus(report):
return "xpassed", "X", ("XPASS", {'yellow': True})
# called by the terminalreporter instance/plugin
def pytest_terminal_summary(terminalreporter):
tr = terminalreporter
if not tr.reportchars:
#for name in "xfailed skipped failed xpassed":
# for name in "xfailed skipped failed xpassed":
# if not tr.stats.get(name, 0):
# tr.write_line("HINT: use '-r' option to see extra "
# "summary info about tests")
@@ -304,12 +312,14 @@ def pytest_terminal_summary(terminalreporter):
for line in lines:
tr._tw.line(line)
def show_simple(terminalreporter, lines, stat, format):
failed = terminalreporter.stats.get(stat)
if failed:
for rep in failed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
lines.append(format %(pos,))
lines.append(format % (pos,))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")
@@ -321,13 +331,15 @@ def show_xfailed(terminalreporter, lines):
if reason:
lines.append(" " + str(reason))
def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed")
if xpassed:
for rep in xpassed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
lines.append("XPASS %s %s" %(pos, reason))
lines.append("XPASS %s %s" % (pos, reason))
def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'):
@@ -346,26 +358,40 @@ def folded_skips(skipped):
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):
tr = terminalreporter
skipped = tr.stats.get('skipped', [])
if skipped:
#if not tr.hasopt('skipped'):
# if not tr.hasopt('skipped'):
# tr.write_line(
# "%d skipped tests, specify -rs for more info" %
# len(skipped))
# return
fskips = folded_skips(skipped)
if fskips:
#tr.write_sep("_", "skipped test summary")
# tr.write_sep("_", "skipped test summary")
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
lines.append("SKIP [%d] %s:%d: %s" %
(num, fspath, lineno, 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))

View File

@@ -2,47 +2,58 @@
This is a good source for looking at the various reporting hooks.
"""
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import py
from __future__ import absolute_import, division, print_function
import itertools
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
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."),
dest="verbose", default=0, help="increase verbosity."),
group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decrease verbosity."),
dest="quiet", default=0, help="decrease verbosity."),
group._addoption('-r',
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. "
"The pytest warnings are displayed at all times except when "
"--disable-pytest-warnings is set")
group._addoption('--disable-pytest-warnings', default=False,
dest='disablepytestwarnings', action='store_true',
help='disable warnings summary, overrides -r w flag')
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. "
"Warnings are displayed at all times except when "
"--disable-warnings is set")
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
dest='disable_warnings', action='store_true',
help='disable warnings summary')
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (auto/long/short/line/native/no).")
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('--fulltrace', '--full-trace',
action="store_true", default=False,
help="don't cut any tracebacks (default is to cut).")
action="store_true", default=False,
help="don't cut any tracebacks (default is to cut).")
group._addoption('--color', metavar="color",
action="store", dest="color", default='auto',
choices=['yes', 'no', 'auto'],
help="color terminal output (yes/no/auto).")
action="store", dest="color", default='auto',
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
@@ -54,12 +65,13 @@ def pytest_configure(config):
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
def getreportopt(config):
reportopts = ""
reportchars = config.option.reportchars
if not config.option.disablepytestwarnings and 'w' not in reportchars:
if not config.option.disable_warnings and 'w' not in reportchars:
reportchars += 'w'
elif config.option.disablepytestwarnings and 'w' in reportchars:
elif config.option.disable_warnings and 'w' in reportchars:
reportchars = reportchars.replace('w', '')
if reportchars:
for char in reportchars:
@@ -69,6 +81,7 @@ def getreportopt(config):
reportopts = 'fEsxXw'
return reportopts
def pytest_report_teststatus(report):
if report.passed:
letter = "."
@@ -80,13 +93,41 @@ def pytest_report_teststatus(report):
letter = "f"
return report.outcome, letter, report.outcome.upper()
class WarningReport:
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
"""
def __init__(self, code, message, nodeid=None, fslocation=None):
"""
:param code: unused
:param str message: user friendly message about the warning
:param str|None nodeid: node id that generated the warning (see ``get_location``).
:param tuple|py.path.local fslocation:
file system location of the source of the warning (see ``get_location``).
"""
self.code = code
self.message = message
self.nodeid = nodeid
self.fslocation = fslocation
def get_location(self, config):
"""
Returns the more user-friendly information about the location
of a warning, or None.
"""
if self.nodeid:
return self.nodeid
if self.fslocation:
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
filename, linenum = self.fslocation[:2]
relpath = py.path.local(filename).relto(config.invocation_dir)
return '%s:%s' % (relpath, linenum)
else:
return str(self.fslocation)
return None
class TerminalReporter:
def __init__(self, config, file=None):
@@ -97,17 +138,20 @@ 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._screen_width = self._tw.fullwidth
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
self._progress_items_reported = 0
self._show_progress_info = self.config.getini('console_output_style') == 'progress'
def hasopt(self, char):
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
@@ -116,6 +160,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()
@@ -130,6 +176,7 @@ class TerminalReporter:
if extra:
self._tw.write(extra, **kwargs)
self.currentfspath = -2
self._write_progress_information_filling_space()
def ensure_newline(self):
if self.currentfspath:
@@ -140,14 +187,28 @@ 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)
def rewrite(self, line, **markup):
"""
Rewinds the terminal cursor to the beginning and writes the given line.
:kwarg erase: if True, will also add spaces until the full terminal width to ensure
previous lines are properly erased.
The rest of the keyword arguments are markup instructions.
"""
erase = markup.pop('erase', False)
if erase:
fill_count = self._tw.fullwidth - len(line) - 1
fill = ' ' * fill_count
else:
fill = ''
line = str(line)
self._tw.write("\r" + line, **markup)
self._tw.write("\r" + line + fill, **markup)
def write_sep(self, sep, title=None, **markup):
self.ensure_newline()
@@ -160,14 +221,12 @@ 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
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
if isinstance(fslocation, tuple):
fslocation = "%s:%d" % fslocation
warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid)
warnings.append(warning)
@@ -197,38 +256,73 @@ 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')
self._progress_items_reported += 1
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)
self._write_progress_if_past_edge()
else:
if isinstance(word, tuple):
word, markup = word
else:
if markup is None:
if rep.passed:
markup = {'green':True}
markup = {'green': True}
elif rep.failed:
markup = {'red':True}
markup = {'red': True}
elif rep.skipped:
markup = {'yellow':True}
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)
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 _write_progress_if_past_edge(self):
if not self._show_progress_info:
return
last_item = self._progress_items_reported == self._session.testscollected
if last_item:
self._write_progress_information_filling_space()
return
past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
if past_edge:
msg = self._get_progress_information_message()
self._tw.write(msg + '\n', cyan=True)
_PROGRESS_LENGTH = len(' [100%]')
def _get_progress_information_message(self):
progress = self._progress_items_reported * 100 // self._session.testscollected
return ' [{:3d}%]'.format(progress)
def _write_progress_information_filling_space(self):
if not self._show_progress_info:
return
msg = self._get_progress_information_message()
fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
self.write(fill + msg, cyan=True)
def pytest_collection(self):
if not self.isatty and self.config.option.verbose >= 1:
self.write("collecting ... ", bold=True)
@@ -241,7 +335,7 @@ class TerminalReporter:
items = [x for x in report.result if isinstance(x, pytest.Item)]
self._numcollected += len(items)
if self.isatty:
#self.write_fspath_result(report.nodeid, 'E')
# self.write_fspath_result(report.nodeid, 'E')
self.report_collect()
def report_collect(self, final=False):
@@ -254,15 +348,15 @@ class TerminalReporter:
line = "collected "
else:
line = "collecting "
line += str(self._numcollected) + " items"
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
if errors:
line += " / %d errors" % errors
if skipped:
line += " / %d skipped" % skipped
if self.isatty:
self.rewrite(line, bold=True, erase=True)
if final:
line += " \n"
self.rewrite(line, bold=True)
self.write('\n')
else:
self.write_line(line)
@@ -271,6 +365,7 @@ class TerminalReporter:
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session):
self._session = session
self._sessionstarttime = time.time()
if not self.showheader:
return
@@ -288,6 +383,9 @@ class TerminalReporter:
self.write_line(msg)
lines = self.config.hook.pytest_report_header(
config=self.config, startdir=self.startdir)
self._write_report_lines_from_hooks(lines)
def _write_report_lines_from_hooks(self, lines):
lines.reverse()
for line in flatten(lines):
self.write_line(line)
@@ -295,8 +393,8 @@ class TerminalReporter:
def pytest_report_header(self, config):
inifile = ""
if config.inifile:
inifile = config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
inifile = " " + config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
@@ -314,10 +412,9 @@ class TerminalReporter:
rep.toterminal(self._tw)
return 1
return 0
if not self.showheader:
return
#for i, testarg in enumerate(self.config.args):
# self.write_line("test path %d: %s" %(i+1, testarg))
lines = self.config.hook.pytest_report_collectionfinish(
config=self.config, startdir=self.startdir, items=session.items)
self._write_report_lines_from_hooks(lines)
def _printcollecteditems(self, items):
# to print out items and their parent collectors
@@ -340,14 +437,14 @@ class TerminalReporter:
stack = []
indent = ""
for item in items:
needed_collectors = item.listchain()[1:] # strip root node
needed_collectors = item.listchain()[1:] # strip root node
while stack:
if stack == needed_collectors[:len(stack)]:
break
stack.pop()
for col in needed_collectors[len(stack):]:
stack.append(col)
#if col.name == "()":
# if col.name == "()":
# continue
indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col))
@@ -396,15 +493,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]"
@@ -415,7 +512,7 @@ class TerminalReporter:
fspath, lineno, domain = rep.location
return domain
else:
return "test session" # XXX?
return "test session" # XXX?
def _getcrashline(self, rep):
try:
@@ -430,21 +527,29 @@ 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"):
warnings = self.stats.get("warnings")
if not warnings:
all_warnings = self.stats.get("warnings")
if not all_warnings:
return
self.write_sep("=", "pytest-warning summary")
for w in warnings:
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message))
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
self.write_sep("=", "warnings summary", yellow=True, bold=False)
for location, warning_records in grouped:
self._tw.line(str(location) or '<undetermined location>')
for w in warning_records:
lines = w.message.splitlines()
indented = '\n'.join(' ' + x for x in lines)
self._tw.line(indented)
self._tw.line()
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
def summary_passes(self):
if self.config.option.tbstyle != "no":
@@ -458,6 +563,14 @@ class TerminalReporter:
self.write_sep("_", msg)
self._outrep_summary(rep)
def print_teardown_sections(self, rep):
for secname, content in rep.sections:
if 'teardown' in secname:
self._tw.sep('-', secname)
if content[-1:] == "\n":
content = content[:-1]
self._tw.line(content)
def summary_failures(self):
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
@@ -473,6 +586,9 @@ class TerminalReporter:
markup = {'red': True, 'bold': True}
self.write_sep("_", msg, **markup)
self._outrep_summary(rep)
for report in self.getreports(''):
if report.nodeid == rep.nodeid and report.when == 'teardown':
self.print_teardown_sections(report)
def summary_errors(self):
if self.config.option.tbstyle != "no":
@@ -516,6 +632,7 @@ class TerminalReporter:
self.write_sep("=", "%d tests deselected" % (
len(self.stats['deselected'])), bold=True)
def repr_pythonversion(v=None):
if v is None:
v = sys.version_info
@@ -524,30 +641,30 @@ def repr_pythonversion(v=None):
except (TypeError, ValueError):
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
else:
yield x
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings error").split()
key_translation = {'warnings': 'pytest-warnings'}
"xfailed xpassed warnings error").split()
unknown_key_seen = False
for key in stats.keys():
if key not in keys:
if key: # setup/teardown reports have an empty key, ignore them
if key: # setup/teardown reports have an empty key, ignore them
keys.append(key)
unknown_key_seen = True
parts = []
for key in keys:
val = stats.get(key, None)
if val:
key_name = key_translation.get(key, key)
parts.append("%d %s" % (len(val), key_name))
parts.append("%d %s" % (len(val), key))
if parts:
line = ", ".join(parts)
@@ -567,7 +684,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)
@@ -576,6 +693,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

@@ -1,4 +1,6 @@
""" support for providing temporary directories to test functions. """
from __future__ import absolute_import, division, print_function
import re
import pytest
@@ -23,7 +25,7 @@ class TempdirFactory:
provides an empty unique-per-test-invocation directory
and is guaranteed to be empty.
"""
#py.log._apiwarn(">1.1", "use tmpdir function argument")
# py.log._apiwarn(">1.1", "use tmpdir function argument")
return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True):
@@ -36,7 +38,7 @@ class TempdirFactory:
p = basetemp.mkdir(basename)
else:
p = py.path.local.make_numbered_dir(prefix=basename,
keep=0, rootdir=basetemp, lock_timeout=None)
keep=0, rootdir=basetemp, lock_timeout=None)
self.trace("mktemp", p)
return p
@@ -81,6 +83,7 @@ def get_user():
except (ImportError, KeyError):
return None
# backward compatibility
TempdirHandler = TempdirFactory
@@ -115,7 +118,7 @@ def tmpdir(request, tmpdir_factory):
path object.
"""
name = request.node.name
name = re.sub("[\W]", "_", name)
name = re.sub(r"[\W]", "_", name)
MAXVAL = 30
if len(name) > MAXVAL:
name = name[:MAXVAL]

View File

@@ -1,14 +1,14 @@
""" discovery and running of std-library "unittest" style tests. """
from __future__ import absolute_import
from __future__ import absolute_import, division, print_function
import sys
import traceback
import pytest
# for transfering markers
# for transferring markers
import _pytest._code
from _pytest.python import transfer_markers
from _pytest.skipping import MarkEvaluator
from _pytest.config import hookimpl
from _pytest.outcomes import fail, skip, xfail
from _pytest.python import transfer_markers, Class, Module, Function
def pytest_pycollect_makeitem(collector, name, obj):
@@ -22,11 +22,11 @@ def pytest_pycollect_makeitem(collector, name, obj):
return UnitTestCase(name, parent=collector)
class UnitTestCase(pytest.Class):
class UnitTestCase(Class):
# marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs
nofuncargs = True
def setup(self):
cls = self.obj
if getattr(cls, '__unittest_skip__', False):
@@ -46,7 +46,7 @@ class UnitTestCase(pytest.Class):
return
self.session._fixturemanager.parsefactories(self, unittest=True)
loader = TestLoader()
module = self.getparent(pytest.Module).obj
module = self.getparent(Module).obj
foundsomething = False
for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name)
@@ -65,8 +65,7 @@ class UnitTestCase(pytest.Class):
yield TestCaseFunction('runTest', parent=self)
class TestCaseFunction(pytest.Function):
class TestCaseFunction(Function):
_excinfo = None
def setup(self):
@@ -94,6 +93,9 @@ class TestCaseFunction(pytest.Function):
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):
self._testcase.teardown_method(self._obj)
# Allow garbage collection on TestCase instance attributes.
self._testcase = None
self._obj = None
def startTest(self, testcase):
pass
@@ -106,38 +108,38 @@ class TestCaseFunction(pytest.Function):
except TypeError:
try:
try:
l = traceback.format_exception(*rawexcinfo)
l.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n")
pytest.fail("".join(l), pytrace=False)
except (pytest.fail.Exception, KeyboardInterrupt):
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:
pytest.fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" %(rawexcinfo,), pytrace=False)
except: # noqa
fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" % (rawexcinfo,), pytrace=False)
except KeyboardInterrupt:
raise
except pytest.fail.Exception:
except fail.Exception:
excinfo = _pytest._code.ExceptionInfo()
self.__dict__.setdefault('_excinfo', []).append(excinfo)
def addError(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo)
def addFailure(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo)
def addSkip(self, testcase, reason):
try:
pytest.skip(reason)
except pytest.skip.Exception:
self._evalskip = MarkEvaluator(self, 'SkipTest')
self._evalskip.result = True
skip(reason)
except skip.Exception:
self._skipped_by_mark = True
self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
try:
pytest.xfail(str(reason))
except pytest.xfail.Exception:
xfail(str(reason))
except xfail.Exception:
self._addexcinfo(sys.exc_info())
def addUnexpectedSuccess(self, testcase, reason=""):
@@ -149,22 +151,42 @@ class TestCaseFunction(pytest.Function):
def stopTest(self, testcase):
pass
def _handle_skip(self):
# implements the skipping machinery (see #2137)
# analog to pythons Lib/unittest/case.py:run
testMethod = getattr(self._testcase, self._testcase._testMethodName)
if (getattr(self._testcase.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
try: # PY3, unittest2 on PY2
self._testcase._addSkip(self, self._testcase, skip_why)
except TypeError: # PY2
if sys.version_info[0] != 2:
raise
self._testcase._addSkip(self, skip_why)
return True
return False
def runtest(self):
if self.config.pluginmanager.get_plugin("pdbinvoke") is None:
self._testcase(result=self)
else:
# disables tearDown and cleanups for post mortem debugging (see #1890)
if self._handle_skip():
return
self._testcase.debug()
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter(
lambda x:not x.frame.f_globals.get('__unittest'))
lambda x: not x.frame.f_globals.get('__unittest'))
if traceback:
excinfo.traceback = traceback
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
if item._excinfo:
@@ -176,15 +198,17 @@ def pytest_runtest_makereport(item, call):
# twisted trial support
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules:
ut = sys.modules['twisted.python.failure']
Failure__init__ = ut.Failure.__init__
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
captureVars=None):
if exc_value is None:
self._rawexcinfo = sys.exc_info()
else:
@@ -193,9 +217,10 @@ def pytest_runtest_protocol(item):
self._rawexcinfo = (exc_type, exc_value, exc_tb)
try:
Failure__init__(self, exc_value, exc_type, exc_tb,
captureVars=captureVars)
captureVars=captureVars)
except TypeError:
Failure__init__(self, exc_value, exc_type, exc_tb)
ut.Failure.__init__ = excstore
yield
ut.Failure.__init__ = Failure__init__

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 ignored.

View File

@@ -1,10 +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,39 +0,0 @@
Metadata-Version: 2.0
Name: pluggy
Version: 0.3.1
Summary: plugin and hook calling mechanisms for python
Home-page: UNKNOWN
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,8 +0,0 @@
pluggy.py,sha256=v_RfWzyW6DPU1cJu_EFoL_OHq3t13qloVdR6UaMCXQA,29862
pluggy-0.3.1.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
pluggy-0.3.1.dist-info/pbr.json,sha256=xX3s6__wOcAyF-AZJX1sdZyW6PUXT-FkfBlM69EEUCg,47
pluggy-0.3.1.dist-info/RECORD,,
pluggy-0.3.1.dist-info/metadata.json,sha256=nLKltOT78dMV-00uXD6Aeemp4xNsz2q59j6ORSDeLjw,1027
pluggy-0.3.1.dist-info/METADATA,sha256=1b85Ho2u4iK30M099k7axMzcDDhLcIMb-A82JUJZnSo,1334
pluggy-0.3.1.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
pluggy-0.3.1.dist-info/DESCRIPTION.rst,sha256=P5Akh1EdIBR6CeqtV2P8ZwpGSpZiTKPw0NyS7jEiD-g,306

View File

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

View File

@@ -1 +0,0 @@
{"license": "MIT license", "name": "pluggy", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "plugin and hook calling mechanisms for python", "platform": "unix", "version": "0.3.1", "extensions": {"python.details": {"document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "Holger Krekel"}]}}, "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"]}

View File

@@ -1 +0,0 @@
{"is_release": false, "git_version": "7d4c9cd"}

View File

@@ -1,777 +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.3.1'
__all__ = ["PluginManager", "PluginValidationError",
"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 ``addhooks(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)
res = getattr(method, self.project_name + "_impl", None)
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 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
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
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`` 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 explicitly 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()
args = [all_kwargs[argname] for argname in hook_impl.argnames]
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):
func = getattr(func, '__call__', func)
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. """
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))
)

94
_pytest/warnings.py Normal file
View File

@@ -0,0 +1,94 @@
from __future__ import absolute_import, division, print_function
import warnings
from contextlib import contextmanager
import pytest
from _pytest import compat
def _setoption(wmod, arg):
"""
Copy of the warning._setoption function but does not escape arguments.
"""
parts = arg.split(':')
if len(parts) > 5:
raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
while len(parts) < 5:
parts.append('')
action, message, category, module, lineno = [s.strip()
for s in parts]
action = wmod._getaction(action)
category = wmod._getcategory(category)
if lineno:
try:
lineno = int(lineno)
if lineno < 0:
raise ValueError
except (ValueError, OverflowError):
raise wmod._OptionError("invalid lineno %r" % (lineno,))
else:
lineno = 0
wmod.filterwarnings(action, message, category, module, lineno)
def pytest_addoption(parser):
group = parser.getgroup("pytest-warnings")
group.addoption(
'-W', '--pythonwarnings', action='append',
help="set which warnings to report, see -W option of python itself.")
parser.addini("filterwarnings", type="linelist",
help="Each line specifies a pattern for "
"warnings.filterwarnings. "
"Processed after -W and --pythonwarnings.")
@contextmanager
def catch_warnings_for_item(item):
"""
catches the warnings generated during setup/call/teardown execution
of the given item and after it is done posts them as warnings to this
item.
"""
args = item.config.getoption('pythonwarnings') or []
inifilters = item.config.getini("filterwarnings")
with warnings.catch_warnings(record=True) as log:
for arg in args:
warnings._setoption(arg)
for arg in inifilters:
_setoption(warnings, arg)
mark = item.get_marker('filterwarnings')
if mark:
for arg in mark.args:
warnings._setoption(arg)
yield
for warning in log:
warn_msg = warning.message
unicode_warning = False
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
new_args = [compat.ascii_escaped(m) for m in warn_msg.args]
unicode_warning = list(warn_msg.args) != new_args
warn_msg.args = new_args
msg = warnings.formatwarning(
warn_msg, warning.category,
warning.filename, warning.lineno, warning.line)
item.warn("unused", msg)
if unicode_warning:
warnings.warn(
"Warning is using unicode non convertible to ascii, "
"converting to a safe representation:\n %s" % msg,
UnicodeWarning)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
with catch_warnings_for_item(item):
yield

View File

@@ -6,30 +6,39 @@ environment:
# https://www.appveyor.com/docs/build-configuration#secure-variables
matrix:
# create multiple jobs to execute a set of tox runs on each; this is to workaround having
# builds timing out in AppVeyor
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
- TOXENV: "py27-nobyte,doctesting,freeze,docs"
# coveralls is not in the default env list
- TOXENV: "coveralls"
# note: please use "tox --listenvs" to populate the build matrix below
- TOXENV: "linting"
- TOXENV: "py27"
- TOXENV: "py34"
- TOXENV: "py35"
- TOXENV: "py36"
- TOXENV: "pypy"
- TOXENV: "py27-pexpect"
- 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"
- TOXENV: "docs"
install:
- echo Installed Pythons
- dir c:\Python*
# install pypy using choco (redirect to a file and write to console in case
# choco install returns non-zero, because choco install python.pypy is too
# noisy)
- choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
- set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
- echo PyPy installed
- pypy --version
- if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
- C:\Python35\python -m pip install tox
- C:\Python36\python -m pip install --upgrade --pre tox
build: false # Not a C# project, build stuff at the test step instead.
test_script:
- C:\Python35\python -m tox
# coveralls is not in tox's envlist, plus for PRs the secure variable
# is not defined so we have to check for it
- if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls
- call scripts\call-tox.bat

40
changelog/_template.rst Normal file
View File

@@ -0,0 +1,40 @@
{% for section in sections %}
{% set underline = "-" %}
{% if section %}
{{section}}
{{ underline * section|length }}{% set underline = "~" %}
{% endif %}
{% if sections[section] %}
{% for category, val in definitions.items() if category in sections[section] %}
{{ definitions[category]['name'] }}
{{ underline * definitions[category]['name']|length }}
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category]|dictsort(by='value') %}
{% set issue_joiner = joiner(', ') %}
- {{ text }}{% if category != 'vendor' %} ({% for value in values|sort %}{{ issue_joiner() }}`{{ value }} <https://github.com/pytest-dev/pytest/issues/{{ value[1:] }}>`_{% endfor %}){% endif %}
{% endfor %}
{% else %}
- {{ sections[section][category]['']|sort|join(', ') }}
{% endif %}
{% if sections[section][category]|length == 0 %}
No significant changes.
{% else %}
{% endif %}
{% endfor %}
{% else %}
No significant changes.
{% endif %}
{% endfor %}

View File

@@ -13,16 +13,18 @@ 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@" \
--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \
--normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \
--normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \
--normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \
--normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@"
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@@ -36,24 +38,8 @@ help:
clean:
-rm -rf $(BUILDDIR)/*
SITETARGET=$(shell ./_getdoctarget.py)
showtarget:
@echo $(SITETARGET)
install: html
# for access talk to someone with login rights to
# pytest-dev@pytest.org to add your ssh key
rsync -avz _build/html/ pytest-dev@pytest.org:pytest.org/$(SITETARGET)
installpdf: latexpdf
@scp $(BUILDDIR)/latex/pytest.pdf pytest-dev@pytest.org:pytest.org/$(SITETARGET)
installall: clean install installpdf
@echo "done"
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env python
import py
def get_version_string():
fn = py.path.local(__file__).join("..", "..", "..",
"_pytest", "__init__.py")
for line in fn.readlines():
if "version" in line and not line.strip().startswith('#'):
return eval(line.split("=")[-1])
def get_minor_version_string():
return ".".join(get_version_string().split(".")[:2])
if __name__ == "__main__":
print (get_minor_version_string())

View File

@@ -9,6 +9,7 @@
<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('backwards-compatibility') }}">Backwards Compatibility</a></li>
<li><a href="{{ pathto('license') }}">License</a></li>
</ul>

View File

@@ -6,6 +6,6 @@
<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>
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
<li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
<li><a href="https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf">PDF Documentation</a>
</ul>

View File

@@ -6,6 +6,22 @@ Release announcements
:maxdepth: 2
release-3.3.0
release-3.2.5
release-3.2.4
release-3.2.3
release-3.2.2
release-3.2.1
release-3.2.0
release-3.1.3
release-3.1.2
release-3.1.1
release-3.1.0
release-3.0.7
release-3.0.6
release-3.0.5
release-3.0.4
release-3.0.3
release-3.0.2
release-3.0.1
release-3.0.0

View File

@@ -63,9 +63,9 @@ Changes between 2.0.1 and 2.0.2
this.
- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
thanks to Laura Creighton who also revieved parts of the documentation.
thanks to Laura Creighton who also reviewed parts of the documentation.
- fix slighly wrong output of verbose progress reporting for classes
- fix slightly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses

View File

@@ -13,7 +13,7 @@ If you want to install or upgrade pytest, just type one of::
easy_install -U pytest
There also is a bugfix release 1.6 of pytest-xdist, the plugin
that enables seemless distributed and "looponfail" testing for Python.
that enables seamless distributed and "looponfail" testing for Python.
best,
holger krekel
@@ -33,7 +33,7 @@ Changes between 2.0.2 and 2.0.3
- don't require zlib (and other libs) for genscript plugin without
--genscript actually being used.
- speed up skips (by not doing a full traceback represenation
- speed up skips (by not doing a full traceback representation
internally)
- fix issue37: avoid invalid characters in junitxml's output

View File

@@ -2,7 +2,7 @@ pytest-2.2.1: bug fixes, perfect teardowns
===========================================================================
pytest-2.2.1 is a minor backward-compatible release of the the py.test
pytest-2.2.1 is a minor backward-compatible release of the py.test
testing tool. It contains bug fixes and little improvements, including
documentation fixes. If you are using the distributed testing
pluginmake sure to upgrade it to pytest-xdist-1.8.

View File

@@ -29,7 +29,7 @@ Changes between 2.2.3 and 2.2.4
- fix issue with unittest: now @unittest.expectedFailure markers should
be processed correctly (you can also use @pytest.mark markers)
- document integration with the extended distribute/setuptools test commands
- fix issue 140: propperly get the real functions
- fix issue 140: properly get the real functions
of bound classmethods for setup/teardown_class
- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
- fix issue #143: call unconfigure/sessionfinish always when

View File

@@ -89,7 +89,7 @@ Changes between 2.2.4 and 2.3.0
- fix issue128: show captured output when capsys/capfd are used
- fix issue179: propperly show the dependency chain of factories
- fix issue179: properly show the dependency chain of factories
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
@@ -130,5 +130,5 @@ Changes between 2.2.4 and 2.3.0
- don't show deselected reason line if there is none
- py.test -vv will show all of assert comparisations instead of truncating
- py.test -vv will show all of assert comparisons instead of truncating

View File

@@ -1,7 +1,7 @@
pytest-2.3.2: some fixes and more traceback-printing speed
===========================================================================
pytest-2.3.2 is a another stabilization release:
pytest-2.3.2 is another stabilization release:
- issue 205: fixes a regression with conftest detection
- issue 208/29: fixes traceback-printing speed in some bad cases

View File

@@ -1,7 +1,7 @@
pytest-2.3.3: integration fixes, py24 suport, ``*/**`` shown in traceback
pytest-2.3.3: integration fixes, py24 support, ``*/**`` shown in traceback
===========================================================================
pytest-2.3.3 is a another stabilization release of the py.test tool
pytest-2.3.3 is another stabilization release of the py.test tool
which offers uebersimple assertions, scalable fixture mechanisms
and deep customization for testing with Python. Particularly,
this release provides:
@@ -46,7 +46,7 @@ Changes between 2.3.2 and 2.3.3
- fix issue209 - reintroduce python2.4 support by depending on newer
pylib which re-introduced statement-finding for pre-AST interpreters
- nose support: only call setup if its a callable, thanks Andrew
- nose support: only call setup if it's a callable, thanks Andrew
Taumoefolau
- fix issue219 - add py2.4-3.3 classifiers to TROVE list

View File

@@ -44,11 +44,11 @@ Changes between 2.3.4 and 2.3.5
(thanks Adam Goucher)
- Issue 265 - integrate nose setup/teardown with setupstate
so it doesnt try to teardown if it did not setup
so it doesn't try to teardown if it did not setup
- issue 271 - dont write junitxml on slave nodes
- issue 271 - don't write junitxml on slave nodes
- Issue 274 - dont try to show full doctest example
- Issue 274 - don't try to show full doctest example
when doctest does not know the example location
- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
@@ -84,7 +84,7 @@ Changes between 2.3.4 and 2.3.5
- allow to specify prefixes starting with "_" when
customizing python_functions test discovery. (thanks Graham Horler)
- improve PYTEST_DEBUG tracing output by puting
- improve PYTEST_DEBUG tracing output by putting
extra data on a new lines with additional indent
- ensure OutcomeExceptions like skip/fail have initialized exception attributes

View File

@@ -36,7 +36,7 @@ a full list of details. A few feature highlights:
- reporting: color the last line red or green depending if
failures/errors occurred or everything passed.
The documentation has been updated to accomodate the changes,
The documentation has been updated to accommodate the changes,
see `http://pytest.org <http://pytest.org>`_
To install or upgrade pytest::
@@ -118,7 +118,7 @@ new features:
- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
Mathieu Agopian for the initial fix. Also make all of pytest/nose
finalizer mimick the same generic behaviour: if a setupX exists and
finalizer mimic the same generic behaviour: if a setupX exists and
fails, don't run teardownX. This internally introduces a new method
"node.addfinalizer()" helper which can only be called during the setup
phase of a node.

View File

@@ -70,7 +70,7 @@ holger krekel
to problems for more than >966 non-function scoped parameters).
- fix issue290 - there is preliminary support now for parametrizing
with repeated same values (sometimes useful to to test if calling
with repeated same values (sometimes useful to test if calling
a second time works as with the first time).
- close issue240 - document precisely how pytest module importing
@@ -149,7 +149,7 @@ holger krekel
would not work correctly because pytest assumes @pytest.mark.some
gets a function to be decorated already. We now at least detect if this
arg is an lambda and thus the example will work. Thanks Alex Gaynor
arg is a lambda and thus the example will work. Thanks Alex Gaynor
for bringing it up.
- xfail a test on pypy that checks wrong encoding/ascii (pypy does

View File

@@ -60,5 +60,5 @@ holger krekel
- fix issue429: comparing byte strings with non-ascii chars in assert
expressions now work better. Thanks Floris Bruynooghe.
- make capfd/capsys.capture private, its unused and shouldnt be exposed
- make capfd/capsys.capture private, its unused and shouldn't be exposed

View File

@@ -42,7 +42,7 @@ Changes 2.6.3
- fix conftest related fixture visibility issue: when running with a
CWD outside of a test package pytest would get fixture discovery wrong.
Thanks to Wolfgang Schnerring for figuring out a reproducable example.
Thanks to Wolfgang Schnerring for figuring out a reproducible example.
- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
timeout when interactively entering pdb). Thanks Wolfgang Schnerring.

View File

@@ -32,7 +32,7 @@ The py.test Development Team
explanations. Thanks Carl Meyer for the report and test case.
- fix issue553: properly handling inspect.getsourcelines failures in
FixtureLookupError which would lead to to an internal error,
FixtureLookupError which would lead to an internal error,
obfuscating the original problem. Thanks talljosh for initial
diagnose/patch and Bruno Oliveira for final patch.

View File

@@ -46,7 +46,7 @@ The py.test Development Team
Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_
for PR the (`#1524`_).
* Fix win32 path issue when puttinging custom config file with absolute path
* Fix win32 path issue when putting custom config file with absolute path
in ``pytest.main("-c your_absolute_path")``.
* Fix maximum recursion depth detection when raised error class is not aware

View File

@@ -12,15 +12,13 @@ The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Adam Chainz
* Andrew Svetlov
* Ahn Ki-Wook
* Bruno Oliveira
* Daniel Hahler
* Dmitry Dygalo
* Florian Bruhin
* Marcin Bachry
* Jordan Guymon
* Raphael Pierzina
* Ronny Pfannschmidt
* matthiasha
* mbyt
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,27 @@
pytest-3.0.3
============
pytest 3.0.3 has just been released to PyPI.
This release fixes some regressions and bugs reported in the last version,
being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Huayi Zhang
* Lev Maximov
* Raquel Alegre
* Ronny Pfannschmidt
* Roy Williams
* Tyler Goodlet
* mbyt
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,29 @@
pytest-3.0.4
============
pytest 3.0.4 has just been released to PyPI.
This release fixes some regressions and bugs reported in the last version,
being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Bruno Oliveira
* Dan Wandschneider
* Florian Bruhin
* Georgy Dyuldin
* Grigorii Eremeev
* Jason R. Coombs
* Manuel Jacob
* Mathieu Clabaut
* Michael Seifert
* Nikolaus Rath
* Ronny Pfannschmidt
* Tom V
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,27 @@
pytest-3.0.5
============
pytest 3.0.5 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Ana Vojnovic
* Bruno Oliveira
* Daniel Hahler
* Duncan Betts
* Igor Starikov
* Ismail
* Luke Murphy
* Ned Batchelder
* Ronny Pfannschmidt
* Sebastian Ramacher
* nmundar
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,33 @@
pytest-3.0.6
============
pytest 3.0.6 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Andreas Pelme
* Bruno Oliveira
* Dmitry Malinovsky
* Eli Boyarski
* Jakub Wilk
* Jeff Widman
* Loïc Estève
* Luke Murphy
* Miro Hrončok
* Oscar Hellström
* Peter Heatwole
* Philippe Ombredanne
* Ronny Pfannschmidt
* Rutger Prins
* Stefan Scherfke
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,33 @@
pytest-3.0.7
============
pytest 3.0.7 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Barney Gale
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Ionel Cristian Mărieș
* Katerina Koukiou
* NODA, Kai
* Omer Hadari
* Patrick Hayes
* Ran Benita
* Ronny Pfannschmidt
* Victor Uriarte
* Vidar Tonaas Fauske
* Ville Skyttä
* fbjorn
* mbyt
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,61 @@
pytest-3.1.0
=======================================
The pytest team is proud to announce the 3.1.0 release!
pytest is a mature Python testing tool with more than a 1600 tests
against itself, passing on many different interpreters and platforms.
This release contains a bugs fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Ben Lloyd
* Bruno Oliveira
* David Giese
* David Szotten
* Dmitri Pribysh
* Florian Bruhin
* Florian Schulze
* Floris Bruynooghe
* John Towler
* Jonas Obrist
* Katerina Koukiou
* Kodi Arfer
* Krzysztof Szularz
* Lev Maximov
* Loïc Estève
* Luke Murphy
* Manuel Krebber
* Matthew Duck
* Matthias Bussonnier
* Michael Howitz
* Michal Wajszczuk
* Paweł Adamczak
* Rafael Bertoldi
* Ravi Chandra
* Ronny Pfannschmidt
* Skylar Downes
* Thomas Kriechbaumer
* Vitaly Lashmanov
* Vlad Dragos
* Wheerd
* Xander Johnson
* mandeep
* reut
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,23 @@
pytest-3.1.1
=======================================
pytest 3.1.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Jason R. Coombs
* Ronny Pfannschmidt
* wanghui
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,23 @@
pytest-3.1.2
=======================================
pytest 3.1.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Andreas Pelme
* ApaDoctor
* Bruno Oliveira
* Florian Bruhin
* Ronny Pfannschmidt
* Segev Finer
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,23 @@
pytest-3.1.3
=======================================
pytest 3.1.3 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Antoine Legrand
* Bruno Oliveira
* Max Moroz
* Raphael Pierzina
* Ronny Pfannschmidt
* Ryan Fitzpatrick
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,48 @@
pytest-3.2.0
=======================================
The pytest team is proud to announce the 3.2.0 release!
pytest is a mature Python testing tool with more than a 1600 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bugs fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Alex Hartoto
* Andras Tim
* Bruno Oliveira
* Daniel Hahler
* Florian Bruhin
* Floris Bruynooghe
* John Still
* Jordan Moldow
* Kale Kundert
* Lawrence Mitchell
* Llandy Riveron Del Risco
* Maik Figura
* Martin Altmayer
* Mihai Capotă
* Nathaniel Waisbrot
* Nguyễn Hồng Quân
* Pauli Virtanen
* Raphael Pierzina
* Ronny Pfannschmidt
* Segev Finer
* V.Kuznetsov
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,22 @@
pytest-3.2.1
=======================================
pytest 3.2.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Alex Gaynor
* Bruno Oliveira
* Florian Bruhin
* Ronny Pfannschmidt
* Srinivas Reddy Thatiparthy
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-3.2.2
=======================================
pytest 3.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Andreas Pelme
* Antonio Hidalgo
* Bruno Oliveira
* Felipe Dau
* Fernando Macedo
* Jesús Espino
* Joan Massich
* Joe Talbott
* Kirill Pinchuk
* Ronny Pfannschmidt
* Xuan Luong
Happy testing,
The pytest Development Team

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