Compare commits

...

413 Commits
3.2.3 ... 3.3.2

Author SHA1 Message Date
Bruno Oliveira
bd2d0d2c3c Preparing release version 3.3.2 2017-12-25 20:38:27 +00:00
Bruno Oliveira
08997279f4 Merge pull request #3059 from asottile/yesqa
Remove unnecessary `# noqa` comments.
2017-12-22 17:03:02 -02:00
Anthony Sottile
205e29d843 Remove unnecessary # noqa comments.
Commit automated with https://github.com/asottile/yesqa
2017-12-22 08:29:48 -08:00
Ronny Pfannschmidt
672c901c70 Merge pull request #2784 from RonnyPfannschmidt/fix-fdleaks
pytester: ignore files used to obtain current user metadata
2017-12-19 17:08:53 +01:00
Ronny Pfannschmidt
d370e7788d Merge pull request #2068 from flub/commitaccess
[proposal] Add section about how you become a committer
2017-12-18 18:20:49 +01:00
Ronny Pfannschmidt
ed293ec3e9 pytester: no ignore files used to obtain current user metadata in the fd leak detector 2017-12-18 17:52:55 +01:00
Ronny Pfannschmidt
2f8427bb4e Merge pull request #3043 from awbdallas/parameterize_documentation_fix
Parameterize documentation fix
2017-12-18 17:48:41 +01:00
Florian Bruhin
30729b7c3c Merge pull request #3045 from thisch/revert_accidental_push
Revert accidental push
2017-12-17 22:06:38 +01:00
Thomas Hisch
dfc5399cd7 Revert "WIP #3013"
This reverts commit 28a93b9eeb.
2017-12-17 21:50:17 +01:00
Thomas Hisch
76489d30f7 Revert "WIP"
This reverts commit d6f75d2836.
2017-12-17 21:49:57 +01:00
Thomas Hisch
d6f75d2836 WIP 2017-12-17 20:11:24 +01:00
Thomas Hisch
28a93b9eeb WIP #3013 2017-12-17 06:54:57 +01:00
Austin
70461d1ead Update parametrize.rst 2017-12-16 09:31:48 -06:00
Austin
73eccb4c36 Adding news fragment 2017-12-16 09:21:27 -06:00
Austin
d7a76a4d07 Updated for more clarity
- Changed original wording for parameterize. Should help be a little more clear
2017-12-16 09:04:18 -06:00
Bruno Oliveira
924b5e2e3d Merge pull request #3031 from nicoddemus/symlink-privileges
Check if symlink does have privileges on Windows for test_cmdline_python_package_symlink
2017-12-16 12:36:11 -02:00
Austin
c90e76c371 Clarifiyng documentation for parameterize
- Added snippet dealing with parameterize run order of tests
2017-12-16 08:25:02 -06:00
Bruno Oliveira
586ecea6f2 Check if symlink does not privileges on Windows for test_cmdline_python_package_symlink
os.symlink might fail on Windows because users require a special policy
to create symlinks (argh).

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

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

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

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

logger = logger or logging.getLogger(logger)

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

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

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

* isinstance((), Sequence) is True
2017-12-12 00:10:59 +05:30
Srinivas Reddy Thatiparthy
7792587b3f remove unnecessary import 2017-12-12 00:03:38 +05:30
Srinivas Reddy Thatiparthy
c2cd239d35 remove _ast reference from test cases as well 2017-12-11 23:24:47 +05:30
Srinivas Reddy Thatiparthy
cb0ba18f53 remove '_ast' module; and redirect '_ast' references to 'ast' 2017-12-11 23:14:17 +05:30
Jurko Gospodnetić
3b85e0c3a9 simplify test_conftest_found_with_double_dash() test code 2017-12-11 15:10:04 +01:00
Jurko Gospodnetić
73bc6bacfa add changelog entry 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
8e8a953ac6 fix test name typo 2017-12-09 13:34:58 +01:00
Jurko Gospodnetić
852b96714e use spaces consistently instead of some of them being   2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
dd64f1a4a9 fix tox.ini comment typos 2017-12-09 13:34:57 +01:00
Jurko Gospodnetić
41a6ec6f31 touch up test_cmdline_python_namespace_package() test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
7feab7391d simplify test_pdb_collection_failure_is_shown test data setup code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
f0bfe9de3d shorten some test code 2017-12-09 13:34:56 +01:00
Jurko Gospodnetić
596937e610 remove extra whitespace 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
57fcd3f57e remove corpse code comments 2017-12-09 13:34:55 +01:00
Jurko Gospodnetić
65f5383106 fix comment & docstring typos, line wrapping & wording 2017-12-09 13:34:54 +01:00
Ronny Pfannschmidt
964c29cb93 Merge pull request #3014 from nicoddemus/cap-named-tuple
Change capture docs to use namedtuple
2017-12-09 09:38:39 +01:00
Bruno Oliveira
38fb6aae78 Change capture docs to use namedtuple 2017-12-08 18:34:29 -02:00
Bruno Oliveira
1c5b887dfd Fix spelling of pytest in CHANGELOG 2017-12-08 09:04:42 -02:00
Thomas Hisch
ba209b5230 Integrate logging_using_handler into catching_logs
logging_using_handler is only used in catching_logs. Therefore it makes
sense to remove one of the many context managers from the logging
plugin.
2017-12-07 16:34:53 +01:00
Henk-Jaap Wagenaar
b62fd79c0c Fix issue 2985. 2017-12-07 15:12:44 +00:00
Bruno Oliveira
88f2cc9b64 Small formatting fixes in CHANGELOG 2017-12-05 22:28:56 -02:00
Bruno Oliveira
ed2bb9d723 Merge pull request #2997 from nicoddemus/release-3.3.1
Preparing release version 3.3.1
2017-12-05 22:21:41 -02:00
Bruno Oliveira
2a111ff700 Preparing release version 3.3.1 2017-12-05 20:41:57 +00:00
Bruno Oliveira
5c6758fde4 Merge pull request #3006 from The-Compiler/template-fix
Fix example in PR template
2017-12-05 18:35:42 -02:00
Bruno Oliveira
9bd8420a6b Merge pull request #3005 from The-Compiler/blacklist-catchlog
Also blacklist pytest-capturelog plugin
2017-12-05 18:35:09 -02:00
Florian Bruhin
cbdab02d05 Fix example in PR template 2017-12-05 20:05:35 +01:00
Florian Bruhin
ce30896cd2 Also blacklist pytest-capturelog plugin
This is the older plugin before pytest-catchlog was around. Apparently there are
people still using it.

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

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

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

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

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

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

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

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

@@ -3,7 +3,7 @@ Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs:
- [ ] Add a new news fragment into the changelog folder
* name it `$issue_id.$type` for example (588.bug)
* name it `$issue_id.$type` for example (588.bugfix)
* if you don't have an issue_id change it to the pr id after creating the pr
* ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
* Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
@@ -12,4 +12,4 @@ Here's a quick checklist that should be present in PRs:
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`;
- [ ] Add yourself to `AUTHORS`, in alphabetical order;

View File

@@ -19,20 +19,18 @@ env:
- TOXENV=py27-xdist
- TOXENV=py27-trial
- TOXENV=py27-numpy
- TOXENV=py27-pluggymaster
- TOXENV=py36-pexpect
- TOXENV=py36-xdist
- TOXENV=py36-trial
- TOXENV=py36-numpy
- TOXENV=py36-pluggymaster
- TOXENV=py27-nobyte
- TOXENV=doctesting
- TOXENV=docs
matrix:
include:
- env: TOXENV=py26
python: '2.6'
- env: TOXENV=py33
python: '3.3'
- env: TOXENV=pypy
python: 'pypy-5.4'
- env: TOXENV=py35

12
AUTHORS
View File

@@ -30,6 +30,7 @@ Brianna Laugher
Bruno Oliveira
Cal Leeming
Carl Friedrich Bolz
Ceridwen
Charles Cloud
Charnjit SiNGH (CCSJ)
Chris Lamb
@@ -46,6 +47,7 @@ Dave Hunt
David Díaz-Barquero
David Mohr
David Vierra
Daw-Ran Liou
Denis Kirisov
Diego Russo
Dmitry Dygalo
@@ -64,6 +66,7 @@ Feng Ma
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
George Kussumoto
Georgy Dyuldin
Graham Horler
Greg Price
@@ -71,6 +74,8 @@ Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
Harald Armin Massa
Henk-Jaap Wagenaar
Hugo van Kemenade
Hui Wang (coldnight)
Ian Bicking
Jaap Broekhuizen
@@ -80,6 +85,7 @@ Jason R. Coombs
Javier Domingo Cansino
Javier Romero
Jeff Widman
John Eddie Ayson
John Towler
Jon Sonesen
Jonas Obrist
@@ -117,6 +123,7 @@ Matt Bachmann
Matt Duck
Matt Williams
Matthias Hafner
Maxim Filipenko
mbyt
Michael Aquilina
Michael Birtwell
@@ -151,6 +158,7 @@ Ronny Pfannschmidt
Ross Lawley
Russel Winder
Ryan Wooden
Samuel Dion-Girardeau
Samuele Pedroni
Segev Finer
Simon Gomizelj
@@ -161,9 +169,12 @@ 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
@@ -176,3 +187,4 @@ Wouter van Ackooy
Xuan Luong
Xuecong Liao
Zoltán Máté
Roland Puntaier

View File

@@ -8,6 +8,323 @@
.. towncrier release notes start
Pytest 3.3.2 (2017-12-25)
=========================
Bug Fixes
---------
- pytester: ignore files used to obtain current user metadata in the fd leak
detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_)
- Fix **memory leak** where objects returned by fixtures were never destructed
by the garbage collector. (`#2981
<https://github.com/pytest-dev/pytest/issues/2981>`_)
- Fix conversion of pyargs to filename to not convert symlinks and not use
deprecated features on Python 3. (`#2985
<https://github.com/pytest-dev/pytest/issues/2985>`_)
- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for
test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_)
Improved Documentation
----------------------
- Add clarifying note about behavior of multiple parametrized arguments (`#3001
<https://github.com/pytest-dev/pytest/issues/3001>`_)
Trivial/Internal Changes
------------------------
- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_,
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018
<https://github.com/pytest-dev/pytest/issues/3018>`_)
Pytest 3.3.1 (2017-12-05)
=========================
Bug Fixes
---------
- Fix issue about ``-p no:<plugin>`` having no effect. (`#2920
<https://github.com/pytest-dev/pytest/issues/2920>`_)
- Fix regression with warnings that contained non-strings in their arguments in
Python 2. (`#2956 <https://github.com/pytest-dev/pytest/issues/2956>`_)
- Always escape null bytes when setting ``PYTEST_CURRENT_TEST``. (`#2957
<https://github.com/pytest-dev/pytest/issues/2957>`_)
- Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests
were actually collected. (`#2971
<https://github.com/pytest-dev/pytest/issues/2971>`_)
- Bring back ``TerminalReporter.writer`` as an alias to
``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0``
release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_)
- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when
running pytest with it still installed. (`#3004
<https://github.com/pytest-dev/pytest/issues/3004>`_)
Improved Documentation
----------------------
- Fix broken link to plugin ``pytest-localserver``. (`#2963
<https://github.com/pytest-dev/pytest/issues/2963>`_)
Trivial/Internal Changes
------------------------
- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949
<https://github.com/pytest-dev/pytest/issues/2949>`_)
Pytest 3.3.0 (2017-11-23)
=========================
Deprecations and Removals
-------------------------
- Pytest no longer supports Python **2.6** and **3.3**. Those Python versions
are EOL for some time now and incur maintenance and compatibility costs on
the pytest core team, and following up with the rest of the community we
decided that they will no longer be supported starting on this version. Users
which still require those versions should pin pytest to ``<3.3``. (`#2812
<https://github.com/pytest-dev/pytest/issues/2812>`_)
- Remove internal ``_preloadplugins()`` function. This removal is part of the
``pytest_namespace()`` hook deprecation. (`#2636
<https://github.com/pytest-dev/pytest/issues/2636>`_)
- Internally change ``CallSpec2`` to have a list of marks instead of a broken
mapping of keywords. This removes the keywords attribute of the internal
``CallSpec2`` class. (`#2672
<https://github.com/pytest-dev/pytest/issues/2672>`_)
- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack
of the underscore was a naming error. (`#2675
<https://github.com/pytest-dev/pytest/issues/2675>`_)
- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it
with the boolean ``Node._skipped_by_mark``. (`#2767
<https://github.com/pytest-dev/pytest/issues/2767>`_)
- The ``params`` list passed to ``pytest.fixture`` is now for
all effects considered immutable and frozen at the moment of the ``pytest.fixture``
call. Previously the list could be changed before the first invocation of the fixture
allowing for a form of dynamic parametrization (for example, updated from command-line options),
but this was an unwanted implementation detail which complicated the internals and prevented
some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_
for details and a recommended workaround.
Features
--------
- ``pytest_fixture_post_finalizer`` hook can now receive a ``request``
argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_)
- Replace the old introspection code in compat.py that determines the available
arguments of fixtures with inspect.signature on Python 3 and
funcsigs.signature on Python 2. This should respect ``__signature__``
declarations on functions. (`#2267
<https://github.com/pytest-dev/pytest/issues/2267>`_)
- Report tests with global ``pytestmark`` variable only once. (`#2549
<https://github.com/pytest-dev/pytest/issues/2549>`_)
- Now pytest displays the total progress percentage while running tests. The
previous output style can be set by configuring the ``console_output_style``
setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_)
- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708
<https://github.com/pytest-dev/pytest/issues/2708>`_)
- Pytest now captures and displays output from the standard `logging` module.
The user can control the logging level to be captured by specifying options
in ``pytest.ini``, the command line and also during individual tests using
markers. Also, a ``caplog`` fixture is available that enables users to test
the captured log during specific tests (similar to ``capsys`` for example).
For more information, please see the `logging docs
<https://docs.pytest.org/en/latest/logging.html>`_. This feature was
introduced by merging the popular `pytest-catchlog
<https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch
<https://github.com/thisch>`_. Be advised that during the merging the
backward compatibility interface with the defunct ``pytest-capturelog`` has
been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_)
- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the
whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_)
- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824
<https://github.com/pytest-dev/pytest/issues/2824>`_)
- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and
``err`` can be accessed by attribute. (`#2879
<https://github.com/pytest-dev/pytest/issues/2879>`_)
- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from
``readouterr()``. (`#2923
<https://github.com/pytest-dev/pytest/issues/2923>`_)
- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from
``readouterr()``. (`#2934
<https://github.com/pytest-dev/pytest/issues/2934>`_)
- Implement feature to skip ``setup.py`` files when run with
``--doctest-modules``. (`#502
<https://github.com/pytest-dev/pytest/issues/502>`_)
Bug Fixes
---------
- Resume output capturing after ``capsys/capfd.disabled()`` context manager.
(`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_)
- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now
called for all ``conftest.py`` files. (`#2124
<https://github.com/pytest-dev/pytest/issues/2124>`_)
- If an exception happens while loading a plugin, pytest no longer hides the
original traceback. In Python 2 it will show the original traceback with a new
message that explains in which plugin. In Python 3 it will show 2 canonized
exceptions, the original exception while loading the plugin in addition to an
exception that pytest throws about loading a plugin. (`#2491
<https://github.com/pytest-dev/pytest/issues/2491>`_)
- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709
<https://github.com/pytest-dev/pytest/issues/2709>`_)
- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to
``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_)
- ``testdir`` now uses use the same method used by ``tmpdir`` to create its
temporary directory. This changes the final structure of the ``testdir``
directory slightly, but should not affect usage in normal scenarios and
avoids a number of potential problems. (`#2751
<https://github.com/pytest-dev/pytest/issues/2751>`_)
- Pytest no longer complains about warnings with unicode messages being
non-ascii compatible even for ascii-compatible messages. As a result of this,
warnings with unicode messages are converted first to an ascii representation
for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_)
- Change return value of pytest command when ``--maxfail`` is reached from
``2`` (interrupted) to ``1`` (failed). (`#2845
<https://github.com/pytest-dev/pytest/issues/2845>`_)
- Fix issue in assertion rewriting which could lead it to rewrite modules which
should not be rewritten. (`#2939
<https://github.com/pytest-dev/pytest/issues/2939>`_)
- Handle marks without description in ``pytest.ini``. (`#2942
<https://github.com/pytest-dev/pytest/issues/2942>`_)
Trivial/Internal Changes
------------------------
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal
structures to ease code maintainability. (`#2641
<https://github.com/pytest-dev/pytest/issues/2641>`_)
- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642
<https://github.com/pytest-dev/pytest/issues/2642>`_)
- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not
much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_)
- Internal refactor: simplify ascii string escaping by using the
backslashreplace error handler in newer Python 3 versions. (`#2734
<https://github.com/pytest-dev/pytest/issues/2734>`_)
- Remove unnecessary mark evaluator in unittest plugin (`#2767
<https://github.com/pytest-dev/pytest/issues/2767>`_)
- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function
is scheduled to be removed in ``pytest-4.0``. (`#2876
<https://github.com/pytest-dev/pytest/issues/2876>`_)
- Internal move of the parameterset extraction to a more maintainable place.
(`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_)
- Internal refactoring to simplify scope node lookup. (`#2910
<https://github.com/pytest-dev/pytest/issues/2910>`_)
- Configure ``pytest`` to prevent pip from installing pytest in unsupported
Python versions. (`#2922
<https://github.com/pytest-dev/pytest/issues/2922>`_)
Pytest 3.2.5 (2017-11-15)
=========================
Bug Fixes
---------
- Remove ``py<1.5`` restriction from ``pytest`` as this can cause version
conflicts in some installations. (`#2926
<https://github.com/pytest-dev/pytest/issues/2926>`_)
Pytest 3.2.4 (2017-11-13)
=========================
Bug Fixes
---------
- Fix the bug where running with ``--pyargs`` will result in items with
empty ``parent.nodeid`` if run from a different root directory. (`#2775
<https://github.com/pytest-dev/pytest/issues/2775>`_)
- Fix issue with ``@pytest.parametrize`` if argnames was specified as keyword arguments.
(`#2819 <https://github.com/pytest-dev/pytest/issues/2819>`_)
- Strip whitespace from marker names when reading them from INI config. (`#2856
<https://github.com/pytest-dev/pytest/issues/2856>`_)
- Show full context of doctest source in the pytest output, if the line number of
failed example in the docstring is < 9. (`#2882
<https://github.com/pytest-dev/pytest/issues/2882>`_)
- Match fixture paths against actual path segments in order to avoid matching folders which share a prefix.
(`#2836 <https://github.com/pytest-dev/pytest/issues/2836>`_)
Improved Documentation
----------------------
- Introduce a dedicated section about conftest.py. (`#1505
<https://github.com/pytest-dev/pytest/issues/1505>`_)
- Explicitly mention ``xpass`` in the documentation of ``xfail``. (`#1997
<https://github.com/pytest-dev/pytest/issues/1997>`_)
- Append example for pytest.param in the example/parametrize document. (`#2658
<https://github.com/pytest-dev/pytest/issues/2658>`_)
- Clarify language of proposal for fixtures parameters (`#2893
<https://github.com/pytest-dev/pytest/issues/2893>`_)
- List python 3.6 in the documented supported versions in the getting started
document. (`#2903 <https://github.com/pytest-dev/pytest/issues/2903>`_)
- Clarify the documentation of available fixture scopes. (`#538
<https://github.com/pytest-dev/pytest/issues/538>`_)
- Add documentation about the ``python -m pytest`` invocation adding the
current directory to sys.path. (`#911
<https://github.com/pytest-dev/pytest/issues/911>`_)
Pytest 3.2.3 (2017-10-03)
=========================
@@ -132,7 +449,7 @@ Deprecations and Removals
-------------------------
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
operators to avoid surprising/inconsistent behavior. See `the docs
operators to avoid surprising/inconsistent behavior. See `the approx docs
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
@@ -2019,7 +2336,7 @@ time or change existing behaviors in order to make them less surprising/more use
- fix issue655: work around different ways that cause python2/3
to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
- fix issue615: assertion re-writing did not correctly escape % signs
- fix issue615: assertion rewriting did not correctly escape % signs
when formatting boolean operations, which tripped over mixing
booleans with modulo operators. Thanks to Tom Viner for the report,
triaging and fix.

View File

@@ -49,7 +49,7 @@ Fix bugs
--------
Look through the GitHub issues for bugs. Here is a filter you can use:
https://github.com/pytest-dev/pytest/labels/bug
https://github.com/pytest-dev/pytest/labels/type%3A%20bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
@@ -177,7 +177,8 @@ Short version
#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number
and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or
``trivial`` for the issue type.
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
#. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please
add yourself to the ``AUTHORS`` file, in alphabetical order;
Long version
@@ -275,3 +276,15 @@ Here is a simple overview, with pytest-specific bits:
base: features # if it's a feature
Joining the Development Team
----------------------------
Anyone who has successfully seen through a pull request which did not
require any extra work from the development team to merge will
themselves gain commit access if they so wish (if we forget to ask please send a friendly
reminder). This does not mean your workflow to contribute changes,
everyone goes through the same pull-request-and-review process and
no-one merges their own pull requests unless already approved. It does however mean you can
participate in the development process more fully since you can merge
pull requests from other contributors yourself after having reviewed
them.

View File

@@ -76,7 +76,7 @@ Features
- Can run `unittest <http://docs.pytest.org/en/latest/unittest.html>`_ (or trial),
`nose <http://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box;
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

View File

@@ -4,9 +4,6 @@ needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
to find the magic string, so _ARGCOMPLETE env. var is never set, and
this does not need special code.
argcomplete does not support python 2.5 (although the changes for that
are minor).
Function try_argcomplete(parser) should be called directly before
the call to ArgumentParser.parse_args().

View File

@@ -8,8 +8,6 @@ from _pytest.compat import _PY2, _PY3, PY35, safe_str
import py
builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib')
if _PY3:
from traceback import format_exception_only
else:
@@ -235,7 +233,7 @@ class TracebackEntry(object):
except KeyError:
return False
if py.builtin.callable(tbh):
if callable(tbh):
return tbh(None if self._excinfo is None else self._excinfo())
else:
return tbh
@@ -250,7 +248,7 @@ class TracebackEntry(object):
line = str(self.statement).lstrip()
except KeyboardInterrupt:
raise
except:
except: # noqa
line = "???"
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
@@ -338,16 +336,16 @@ class Traceback(list):
# XXX needs a test
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
# print "checking for recursion at", key
l = cache.setdefault(key, [])
if l:
values = cache.setdefault(key, [])
if values:
f = entry.frame
loc = f.f_locals
for otherloc in l:
for otherloc in values:
if f.is_true(f.eval(co_equal,
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
return i
l.append(entry.frame.f_locals)
values.append(entry.frame.f_locals)
return None
@@ -478,12 +476,12 @@ class FormattedExcinfo(object):
s = str(source.getstatement(len(source) - 1))
except KeyboardInterrupt:
raise
except:
except: # noqa
try:
s = str(source[-1])
except KeyboardInterrupt:
raise
except:
except: # noqa
return 0
return 4 + (len(s) - len(s.lstrip()))

View File

@@ -1,18 +1,15 @@
from __future__ import absolute_import, division, generators, print_function
import ast
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
import sys
import six
import inspect
import tokenize
import py
cpy_compile = compile
try:
import _ast
from _ast import PyCF_ONLY_AST as _AST_FLAG
except ImportError:
_AST_FLAG = 0
_ast = None
cpy_compile = compile
class Source(object):
@@ -32,7 +29,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:
@@ -208,7 +205,7 @@ def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag,
retrieval of the source code for the code object
and any recursively created code objects.
"""
if _ast is not None and isinstance(source, _ast.AST):
if isinstance(source, ast.AST):
# XXX should Source support having AST?
return cpy_compile(source, filename, mode, flags, dont_inherit)
_genframe = sys._getframe(1) # the caller
@@ -254,7 +251,7 @@ def findsource(obj):
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]
@@ -319,30 +316,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)
if isinstance(x, ast.stmt) or isinstance(x, ast.ExceptHandler):
values.append(x.lineno - 1)
for name in "finalbody", "orelse":
val = getattr(x, name, None)
if val:
# treat the finally/orelse part as its own statement
l.append(val[0].lineno - 1 - 1)
l.sort()
insert_index = bisect_right(l, lineno)
start = l[insert_index - 1]
if insert_index >= len(l):
values.append(val[0].lineno - 1 - 1)
values.sort()
insert_index = bisect_right(values, lineno)
start = values[insert_index - 1]
if insert_index >= len(values):
end = None
else:
end = l[insert_index]
end = values[insert_index]
return start, end
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
if astnode is None:
content = str(source)
if sys.version_info < (2, 7):
content += "\n"
try:
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
except ValueError:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ per-test stdout/stderr capturing mechanism.
"""
from __future__ import absolute_import, division, print_function
import collections
import contextlib
import sys
import os
@@ -11,11 +12,10 @@ import io
from io import UnsupportedOperation
from tempfile import TemporaryFile
import py
import six
import pytest
from _pytest.compat import CaptureIO
unicode = py.builtin.text
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@@ -44,7 +44,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
early_config.add_cleanup(capman.reset_capturings)
early_config.add_cleanup(capman.stop_global_capturing)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
@@ -53,17 +53,30 @@ def pytest_load_initial_conftests(early_config, parser, args):
early_config.add_cleanup(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.init_capturings()
capman.start_global_capturing()
outcome = yield
out, err = capman.suspendcapture()
out, err = capman.suspend_global_capture()
if outcome.excinfo is not None:
sys.stdout.write(out)
sys.stderr.write(err)
class CaptureManager:
"""
Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
test phase (setup, call, teardown). After each of those points, the captured output is obtained and
attached to the collection/runtest report.
There are two levels of capture:
* global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
during collection and each test phase.
* fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
case special handling is needed to ensure the fixtures take precedence over the global capture.
"""
def __init__(self, method):
self._method = method
self._global_capturing = None
def _getcapture(self, method):
if method == "fd":
@@ -75,23 +88,24 @@ class CaptureManager:
else:
raise ValueError("unknown capturing method: %r" % method)
def init_capturings(self):
assert not hasattr(self, "_capturing")
self._capturing = self._getcapture(self._method)
self._capturing.start_capturing()
def start_global_capturing(self):
assert self._global_capturing is None
self._global_capturing = self._getcapture(self._method)
self._global_capturing.start_capturing()
def reset_capturings(self):
cap = self.__dict__.pop("_capturing", None)
if cap is not None:
cap.pop_outerr_to_orig()
cap.stop_capturing()
def stop_global_capturing(self):
if self._global_capturing is not None:
self._global_capturing.pop_outerr_to_orig()
self._global_capturing.stop_capturing()
self._global_capturing = None
def resumecapture(self):
self._capturing.resume_capturing()
def resume_global_capture(self):
self._global_capturing.resume_capturing()
def suspendcapture(self, in_=False):
self.deactivate_funcargs()
cap = getattr(self, "_capturing", None)
def suspend_global_capture(self, item=None, in_=False):
if item is not None:
self.deactivate_fixture(item)
cap = getattr(self, "_global_capturing", None)
if cap is not None:
try:
outerr = cap.readouterr()
@@ -99,23 +113,26 @@ class CaptureManager:
cap.suspend_capturing(in_=in_)
return outerr
def activate_funcargs(self, pyfuncitem):
capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
if capfuncarg is not None:
capfuncarg._start()
self._capfuncarg = capfuncarg
def activate_fixture(self, item):
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
the global capture.
"""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture._start()
def deactivate_funcargs(self):
capfuncarg = self.__dict__.pop("_capfuncarg", None)
if capfuncarg is not None:
capfuncarg.close()
def deactivate_fixture(self, item):
"""Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
fixture = getattr(item, "_capture_fixture", None)
if fixture is not None:
fixture.close()
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector):
if isinstance(collector, pytest.File):
self.resumecapture()
self.resume_global_capture()
outcome = yield
out, err = self.suspendcapture()
out, err = self.suspend_global_capture()
rep = outcome.get_result()
if out:
rep.sections.append(("Captured stdout", out))
@@ -126,65 +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:
@@ -211,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"):
@@ -246,7 +332,7 @@ class EncodedFile(object):
self.encoding = encoding
def write(self, obj):
if isinstance(obj, unicode):
if isinstance(obj, six.text_type):
obj = obj.encode(self.encoding, "replace")
self.buffer.write(obj)
@@ -263,6 +349,9 @@ class EncodedFile(object):
return getattr(object.__getattribute__(self, "buffer"), name)
CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
class MultiCapture(object):
out = err = in_ = None
@@ -323,16 +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
@@ -371,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,
@@ -402,11 +488,24 @@ class FDCapture:
def writeorg(self, data):
""" write to original file descriptor. """
if py.builtin._istext(data):
if isinstance(data, six.text_type):
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
class 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]
@@ -423,10 +522,9 @@ class SysCapture:
setattr(sys, self.name, self.tmpfile)
def snap(self):
f = self.tmpfile
res = f.getvalue()
f.truncate(0)
f.seek(0)
res = self.tmpfile.getvalue()
self.tmpfile.seek(0)
self.tmpfile.truncate()
return res
def done(self):
@@ -445,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

View File

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

View File

@@ -6,6 +6,7 @@ import traceback
import types
import warnings
import six
import py
# DON't import pytest here because it causes import cycle troubles
import sys
@@ -13,7 +14,7 @@ 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")
@@ -100,27 +101,18 @@ def directory_arg(path, optname):
return path
_preinit = []
default_plugins = (
"mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan warnings").split()
"setuponly setupplan warnings logging").split()
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
def _preloadplugins():
assert not _preinit
_preinit.append(get_config())
def get_config():
if _preinit:
return _preinit.pop(0)
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
config = Config(pluginmanager)
@@ -158,7 +150,7 @@ def _prepareconfig(args=None, plugins=None):
try:
if plugins:
for plugin in plugins:
if isinstance(plugin, py.builtin._basestring):
if isinstance(plugin, six.string_types):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
@@ -173,7 +165,7 @@ def _prepareconfig(args=None, plugins=None):
class PytestPluginManager(PluginManager):
"""
Overwrites :py:class:`pluggy.PluginManager <_pytest.vendored_packages.pluggy.PluginManager>` to add pytest-specific
Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
functionality:
* loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
@@ -211,7 +203,7 @@ class PytestPluginManager(PluginManager):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>`
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
instead.
"""
warning = dict(code="I2",
@@ -249,18 +241,12 @@ class PytestPluginManager(PluginManager):
"historic": hasattr(method, "historic")}
return opts
def _verify_hook(self, hook, hookmethod):
super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
if "__multicall__" in hookmethod.argnames:
fslineno = _pytest._code.getfslineno(hookmethod.function)
warning = dict(code="I1",
fslocation=fslineno,
nodeid=None,
message="%r hook uses deprecated __multicall__ "
"argument" % (hook.name))
self._warn(warning)
def register(self, plugin, name=None):
if name in ['pytest_catchlog', 'pytest_capturelog']:
self._warn('{0} plugin has been merged into the core, '
'please remove it from your requirements.'.format(
name.replace('_', '-')))
return
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
@@ -430,9 +416,9 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, (py.builtin.text, str)), "module name as text required, got %r" % modname
assert isinstance(modname, (six.text_type, str)), "module name as text required, got %r" % modname
modname = str(modname)
if self.get_plugin(modname) is not None:
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
@@ -442,12 +428,12 @@ class PytestPluginManager(PluginManager):
try:
__import__(importspec)
except ImportError as e:
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, safe_str(e.args[0])))
# copy over name and path attributes
for attr in ('name', 'path'):
if hasattr(e, attr):
setattr(new_exc, attr, getattr(e, attr))
raise new_exc
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):
@@ -643,7 +629,7 @@ class Argument:
pass
else:
# this might raise a keyerror as well, don't want to catch that
if isinstance(typ, py.builtin._basestring):
if isinstance(typ, six.string_types):
if typ == 'choice':
warnings.warn(
'type argument to addoption() is a string %r.'
@@ -968,7 +954,7 @@ class Config(object):
)
res = self.hook.pytest_internalerror(excrepr=excrepr,
excinfo=excinfo)
if not py.builtin.any(res):
if not any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
@@ -1014,10 +1000,10 @@ class Config(object):
self._override_ini = ns.override_ini or ()
def _consider_importhook(self, args):
"""Install the PEP 302 import hook if using assertion re-writing.
"""Install the PEP 302 import hook if using assertion rewriting.
Needs to parse the --assert=<mode> option from the commandline
and find all the installed plugins to mark them for re-writing
and find all the installed plugins to mark them for rewriting
by the importhook.
"""
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
@@ -1074,9 +1060,10 @@ 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()
self._consider_importhook(args)
@@ -1170,10 +1157,10 @@ class Config(object):
return []
if type == "pathlist":
dp = py.path.local(self.inicfg.config.path).dirpath()
l = []
values = []
for relpath in shlex.split(value):
l.append(dp.join(relpath, abs=True))
return l
values.append(dp.join(relpath, abs=True))
return values
elif type == "args":
return shlex.split(value)
elif type == "linelist":
@@ -1190,13 +1177,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

View File

@@ -54,7 +54,7 @@ class pytestPDB:
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
capman.suspend_global_capture(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
@@ -66,7 +66,7 @@ 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)

View File

@@ -40,3 +40,13 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
" 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

@@ -50,12 +50,19 @@ def pytest_addoption(parser):
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
@@ -120,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 = '>>>'

View File

@@ -1,13 +1,17 @@
from __future__ import absolute_import, division, print_function
import sys
import functools
import inspect
import sys
import warnings
from collections import OrderedDict
import attr
import py
from py._code.code import FormattedExcinfo
import py
import warnings
import inspect
import _pytest
from _pytest import nodes
from _pytest._code.code import TerminalRepr
from _pytest.compat import (
NOTSET, exc_clear, _format_args,
@@ -15,22 +19,19 @@ from _pytest.compat import (
is_generator, isclass, getimfunc,
getlocation, getfuncargnames,
safe_getattr,
FuncargnamesCompatAttr,
)
from _pytest.outcomes import fail, TEST_OUTCOME
from _pytest.compat import FuncargnamesCompatAttr
if sys.version_info[:2] == (2, 6):
from ordereddict import OrderedDict
else:
from collections import OrderedDict
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)
@@ -62,8 +63,6 @@ def scopeproperty(name=None, doc=None):
def get_scope_node(node, scope):
cls = scopename2class.get(scope)
if cls is None:
if scope == "session":
return node.session
raise ValueError("unknown scope")
return node.getparent(cls)
@@ -268,7 +267,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
self.fixturename = None
#: Scope string, one of "function", "class", "module", "session"
self.scope = "function"
self._fixture_values = {} # argname -> fixture value
self._fixture_defs = {} # argname -> FixtureDef
fixtureinfo = pyfuncitem._fixtureinfo
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
@@ -451,23 +449,29 @@ class FixtureRequest(FuncargnamesCompatAttr):
raise
# remove indent to prevent the python3 exception
# from leaking into the call
result = self._getfixturevalue(fixturedef)
self._fixture_values[argname] = result
self._compute_fixture_value(fixturedef)
self._fixture_defs[argname] = fixturedef
return fixturedef
def _get_fixturestack(self):
current = self
l = []
values = []
while 1:
fixturedef = getattr(current, "_fixturedef", None)
if fixturedef is None:
l.reverse()
return l
l.append(fixturedef)
values.reverse()
return values
values.append(fixturedef)
current = current._parent_request
def _getfixturevalue(self, fixturedef):
def _compute_fixture_value(self, fixturedef):
"""
Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will
force the FixtureDef object to throw away any previous results and compute a new fixture value, which
will be stored into the FixtureDef object itself.
:param FixtureDef fixturedef:
"""
# prepare a subrequest object before calling fixture function
# (latter managed by fixturedef)
argname = fixturedef.argname
@@ -516,12 +520,11 @@ class FixtureRequest(FuncargnamesCompatAttr):
exc_clear()
try:
# call the fixture function
val = fixturedef.execute(request=subrequest)
fixturedef.execute(request=subrequest)
finally:
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(fixturedef.finish,
self.session._setupstate.addfinalizer(functools.partial(fixturedef.finish, request=subrequest),
subrequest.node)
return val
def _check_scope(self, argname, invoking_scope, requested_scope):
if argname == "request":
@@ -554,7 +557,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
if node is None and scope == "class":
# fallback to function item itself
node = self._pyfuncitem
assert node
assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(scope, self._pyfuncitem)
return node
def __repr__(self):
@@ -573,9 +576,7 @@ class SubRequest(FixtureRequest):
self.param_index = param_index
self.scope = scope
self._fixturedef = fixturedef
self.addfinalizer = fixturedef.addfinalizer
self._pyfuncitem = request._pyfuncitem
self._fixture_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index
@@ -584,6 +585,9 @@ class SubRequest(FixtureRequest):
def __repr__(self):
return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem)
def addfinalizer(self, finalizer):
self._fixturedef.addfinalizer(finalizer)
class ScopeMismatchError(Exception):
""" A fixture function tries to use a different fixture function which
@@ -731,23 +735,22 @@ class FixtureDef:
where=baseid
)
self.params = params
startindex = unittest and 1 or None
self.argnames = getfuncargnames(func, startindex=startindex)
self.argnames = getfuncargnames(func, is_method=unittest)
self.unittest = unittest
self.ids = ids
self._finalizer = []
self._finalizers = []
def addfinalizer(self, finalizer):
self._finalizer.append(finalizer)
self._finalizers.append(finalizer)
def finish(self):
def finish(self, request):
exceptions = []
try:
while self._finalizer:
while self._finalizers:
try:
func = self._finalizer.pop()
func = self._finalizers.pop()
func()
except:
except: # noqa
exceptions.append(sys.exc_info())
if exceptions:
e = exceptions[0]
@@ -755,12 +758,15 @@ class FixtureDef:
py.builtin._reraise(*e)
finally:
ihook = self._fixturemanager.session.ihook
ihook.pytest_fixture_post_finalizer(fixturedef=self)
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# even if finalization fails, we invalidate
# the cached fixture value
# the cached fixture value and remove
# all finalizers because they may be bound methods which will
# keep instances alive
if hasattr(self, "cached_result"):
del self.cached_result
self._finalizers = []
def execute(self, request):
# get required arguments and register our own finish()
@@ -768,7 +774,7 @@ class FixtureDef:
for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname)
if argname != "request":
fixturedef.addfinalizer(self.finish)
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
my_cache_key = request.param_index
cached_result = getattr(self, "cached_result", None)
@@ -781,11 +787,11 @@ class FixtureDef:
return result
# we have a previous but differently parametrized fixture instance
# so we need to tear it down before creating a new one
self.finish()
self.finish(request)
assert not hasattr(self, "cached_result")
ihook = self._fixturemanager.session.ihook
return ihook.pytest_fixture_setup(fixturedef=self, request=request)
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)
def __repr__(self):
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
@@ -824,13 +830,21 @@ def pytest_fixture_setup(fixturedef, request):
return result
class FixtureFunctionMarker:
def __init__(self, scope, params, autouse=False, ids=None, name=None):
self.scope = scope
self.params = params
self.autouse = autouse
self.ids = ids
self.name = name
def _ensure_immutable_ids(ids):
if ids is None:
return
if callable(ids):
return ids
return tuple(ids)
@attr.s(frozen=True)
class FixtureFunctionMarker(object):
scope = attr.ib()
params = attr.ib(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):
@@ -981,8 +995,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):
@@ -1037,9 +1051,14 @@ class FixtureManager:
if faclist:
fixturedef = faclist[-1]
if fixturedef.params is not None:
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
parametrize_func = getattr(metafunc.function, 'parametrize', None)
func_params = getattr(parametrize_func, 'args', [[None]])
func_kwargs = getattr(parametrize_func, 'kwargs', {})
# skip directly parametrized arguments
argnames = func_params[0]
if "argnames" in func_kwargs:
argnames = parametrize_func.kwargs["argnames"]
else:
argnames = func_params[0]
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if argname not in func_params and argname not in argnames:
@@ -1127,5 +1146,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

@@ -1,6 +1,6 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from _pytest._pluggy import HookspecMarker
from pluggy import HookspecMarker
hookspec = HookspecMarker("pytest")
@@ -12,22 +12,31 @@ hookspec = HookspecMarker("pytest")
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to
pluginmanager.add_hookspecs(module_or_class, prefix)."""
``pluginmanager.add_hookspecs(module_or_class, prefix)``.
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
"""
@hookspec(historic=True)
def pytest_namespace():
"""
DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged
(**Deprecated**) this hook causes direct monkeypatching on pytest, its use is strongly discouraged
return dict of name->object to be made globally available in
the pytest namespace. This hook is called at plugin registration
time.
the pytest namespace.
This hook is called at plugin registration time.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
""" a new pytest plugin got registered.
:param plugin: the plugin module or instance
:param _pytest.config.PytestPluginManager manager: pytest plugin manager
"""
@hookspec(historic=True)
@@ -41,7 +50,7 @@ def pytest_addoption(parser):
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
:arg parser: To add command line options, call
:arg _pytest.config.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.Parser.addini>`.
@@ -56,8 +65,7 @@ def pytest_addoption(parser):
a value read from an ini-style file.
The config object is passed around on many internal objects via the ``.config``
attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
via (deprecated) ``pytest.config``.
attribute or can be retrieved as the ``pytestconfig`` fixture.
"""
@@ -72,8 +80,7 @@ def pytest_configure(config):
After that, the hook is called for other conftest files as they are
imported.
:arg config: pytest config object
:type config: _pytest.config.Config
:arg _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -87,11 +94,22 @@ def pytest_configure(config):
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
:param list[str] args: list of arguments passed on the command line
"""
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
"""(**Deprecated**) modify command line arguments before option parsing.
This hook is considered deprecated and will be removed in a future pytest version. Consider
using :func:`pytest_load_initial_conftests` instead.
:param _pytest.config.Config config: pytest config object
:param list[str] args: list of arguments passed on the command line
"""
@hookspec(firstresult=True)
@@ -99,12 +117,20 @@ def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
"""
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
of command line option parsing.
:param _pytest.config.Config early_config: pytest config object
:param list[str] args: list of arguments passed on the command line
:param _pytest.config.Parser parser: to add command line options
"""
# -------------------------------------------------------------------------
@@ -113,18 +139,29 @@ def pytest_load_initial_conftests(early_config, parser, args):
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session.
"""Perform the collection protocol for the given session.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
the items in-place.
:param _pytest.main.Session session: the pytest session object
:param _pytest.config.Config config: pytest config object
:param List[_pytest.main.Item] items: list of item objects
"""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
""" called after collection has been performed and modified.
:param _pytest.main.Session session: the pytest session object
"""
@hookspec(firstresult=True)
@@ -134,6 +171,9 @@ def pytest_ignore_collect(path, config):
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
:param _pytest.config.Config config: pytest config object
"""
@@ -141,12 +181,18 @@ def pytest_ignore_collect(path, config):
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param str path: the path to analyze
"""
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
needs to have the specified ``parent`` as a parent.
:param str path: the path to collect
"""
# logging hooks for collection
@@ -212,7 +258,12 @@ def pytest_make_parametrize_id(config, val, argname):
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
The parameter name is available as ``argname``, if required.
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.config.Config config: pytest config object
:param val: the parametrized value
:param str argname: the automatic parameter name produced by pytest
"""
# -------------------------------------------------------------------------
# generic runtest related hooks
@@ -224,11 +275,14 @@ def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished).
Stops at first non-None result, see :ref:`firstresult` """
Stops at first non-None result, see :ref:`firstresult`
:param _pytest.main.Session session: the pytest session object
"""
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
"""(**Deprecated**) use pytest_runtest_logstart. """
@hookspec(firstresult=True)
@@ -296,7 +350,7 @@ def pytest_fixture_setup(fixturedef, request):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_fixture_post_finalizer(fixturedef):
def pytest_fixture_post_finalizer(fixturedef, request):
""" called after fixture teardown, but before the cache is cleared so
the fixture result cache ``fixturedef.cached_result`` can
still be accessed."""
@@ -307,15 +361,25 @@ def pytest_fixture_post_finalizer(fixturedef):
def pytest_sessionstart(session):
""" before session.main() is called. """
""" before session.main() is called.
:param _pytest.main.Session session: the pytest session object
"""
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
""" whole test run finishes.
:param _pytest.main.Session session: the pytest session object
:param int exitstatus: the status which pytest will return to the system
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
""" called before test process is exited.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -329,6 +393,8 @@ def pytest_assertrepr_compare(config, op, left, right):
of strings. The strings will be joined by newlines but any newlines
*in* a string will be escaped. Note that all but the first line will
be indented slightly, the intention is for the first line to be a summary.
:param _pytest.config.Config config: pytest config object
"""
# -------------------------------------------------------------------------
@@ -339,7 +405,7 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir):
""" return a string or list of strings to be displayed as header info for terminal reporting.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
.. note::
@@ -358,7 +424,7 @@ def pytest_report_collectionfinish(config, startdir, items):
This strings will be displayed after the standard "collected X items" message.
:param config: the pytest config object.
:param _pytest.config.Config config: pytest config object
:param startdir: py.path object with the starting dir
:param items: list of pytest items that are going to be executed; this list should not be modified.
"""
@@ -418,6 +484,5 @@ def pytest_enter_pdb(config):
""" called upon pdb.set_trace(), can be used by plugins to take special
action just before the python debugger enters in interactive mode.
:arg config: pytest config object
:type config: _pytest.config.Config
:param _pytest.config.Config config: pytest config object
"""

View File

@@ -17,6 +17,7 @@ import re
import sys
import time
import pytest
from _pytest import nodes
from _pytest.config import filename_arg
# Python 2.X and 3.X compatibility
@@ -252,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

326
_pytest/logging.py Normal file
View File

@@ -0,0 +1,326 @@
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 catching_logs(handler, formatter=None, level=logging.NOTSET):
"""Context manager that prepares the whole logging machinery properly."""
root_logger = logging.getLogger()
if formatter is not None:
handler.setFormatter(formatter)
handler.setLevel(level)
# Adding the same handler twice would confuse logging system.
# Just don't do that.
add_new_handler = handler not in root_logger.handlers
if add_new_handler:
root_logger.addHandler(handler)
orig_level = root_logger.level
root_logger.setLevel(min(orig_level, level))
try:
yield handler
finally:
root_logger.setLevel(orig_level)
if add_new_handler:
root_logger.removeHandler(handler)
class LogCaptureHandler(logging.StreamHandler):
"""A logging handler that stores log records and the log text."""
def __init__(self):
"""Creates a new log handler."""
logging.StreamHandler.__init__(self, py.io.TextIO())
self.records = []
def emit(self, record):
"""Keep the log records in a list in addition to the log text."""
self.records.append(record)
logging.StreamHandler.emit(self, record)
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,11 +1,15 @@
""" core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function
import contextlib
import functools
import os
import pkgutil
import six
import sys
import _pytest
from _pytest import nodes
import _pytest._code
import py
try:
@@ -14,8 +18,8 @@ except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.runner import collect_one_node
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@@ -83,15 +87,6 @@ def pytest_addoption(parser):
help="base temporary directory for this test run.")
def pytest_namespace():
"""keeping this one works around a deeper startup issue in pytest
i tried to find it for a while but the amount of time turned unsustainable,
so i put a hack in to revisit later
"""
return {}
def pytest_configure(config):
__import__('pytest').config = config # compatibiltiy
@@ -110,6 +105,8 @@ def wrap_session(config, doit):
session.exitstatus = doit(config, session) or 0
except UsageError:
raise
except Failed:
session.exitstatus = EXIT_TESTSFAILED
except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo()
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
@@ -117,7 +114,7 @@ def wrap_session(config, doit):
excinfo.typename, excinfo.value.msg))
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = EXIT_INTERRUPTED
except:
except: # noqa
excinfo = _pytest._code.ExceptionInfo()
config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
@@ -167,6 +164,8 @@ def pytest_runtestloop(session):
for i, item in enumerate(session.items):
nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldfail:
raise session.Failed(session.shouldfail)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
@@ -209,6 +208,46 @@ def pytest_ignore_collect(path, config):
return False
@contextlib.contextmanager
def _patched_find_module():
"""Patch bug in pkgutil.ImpImporter.find_module
When using pkgutil.find_loader on python<3.4 it removes symlinks
from the path due to a call to os.path.realpath. This is not consistent
with actually doing the import (in these versions, pkgutil and __import__
did not share the same underlying code). This can break conftest
discovery for pytest where symlinks are involved.
The only supported python<3.4 by pytest is python 2.7.
"""
if six.PY2: # python 3.4+ uses importlib instead
def find_module_patched(self, fullname, path=None):
# Note: we ignore 'path' argument since it is only used via meta_path
subname = fullname.split(".")[-1]
if subname != fullname and self.path is None:
return None
if self.path is None:
path = None
else:
# original: path = [os.path.realpath(self.path)]
path = [self.path]
try:
file, filename, etc = pkgutil.imp.find_module(subname,
path)
except ImportError:
return None
return pkgutil.ImpLoader(fullname, file, filename, etc)
old_find_module = pkgutil.ImpImporter.find_module
pkgutil.ImpImporter.find_module = find_module_patched
try:
yield
finally:
pkgutil.ImpImporter.find_module = old_find_module
else:
yield
class FSHookProxy:
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
@@ -363,24 +402,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. """
@@ -398,7 +419,7 @@ class Node(object):
``marker`` can be a string or pytest.mark.* instance.
"""
from _pytest.mark import MarkDecorator, MARK_GEN
if isinstance(marker, py.builtin._basestring):
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")
@@ -516,14 +537,22 @@ class FSCollector(Collector):
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
@@ -590,8 +619,13 @@ class Interrupted(KeyboardInterrupt):
__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,
@@ -599,6 +633,7 @@ class Session(FSCollector):
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()
@@ -609,6 +644,8 @@ class Session(FSCollector):
@hookimpl(tryfirst=True)
def pytest_collectstart(self):
if self.shouldfail:
raise self.Failed(self.shouldfail)
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
@@ -618,7 +655,7 @@ class Session(FSCollector):
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self.testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self.shouldfail = "stopping after %d failures" % (
self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
@@ -733,9 +770,10 @@ class Session(FSCollector):
"""Convert a dotted module name to path.
"""
import pkgutil
try:
loader = pkgutil.find_loader(x)
with _patched_find_module():
loader = pkgutil.find_loader(x)
except ImportError:
return x
if loader is None:
@@ -743,7 +781,8 @@ class Session(FSCollector):
# This method is sometimes invoked when AssertionRewritingHook, which
# does not define a get_filename method, is already in place:
try:
path = loader.get_filename(x)
with _patched_find_module():
path = loader.get_filename(x)
except AttributeError:
# Retrieve path from AssertionRewritingHook:
path = loader.modules[x][0].co_filename

View File

@@ -3,10 +3,12 @@ from __future__ import absolute_import, division, print_function
import inspect
import warnings
import attr
from collections import namedtuple
from operator import attrgetter
from .compat import imap
from six.moves import map
from .deprecated import MARK_PARAMETERSET_UNPACKING
from .compat import NOTSET, getfslineno
def alias(name, warning=None):
@@ -67,9 +69,29 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
return cls(argval, marks=newmarks, id=None)
@property
def deprecated_arg_dict(self):
return dict((mark.name, mark) for mark in self.marks)
@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):
@@ -119,7 +141,9 @@ 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()
@@ -164,22 +188,26 @@ 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.
"""
@@ -196,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):
@@ -270,11 +298,12 @@ 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,))
@@ -284,7 +313,21 @@ def istestfunc(func):
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>`.
@@ -318,9 +361,7 @@ class MarkDecorator:
"""
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.mark = mark
mark = attr.ib(validator=attr.validators.instance_of(Mark))
name = alias('mark.name')
args = alias('mark.args')
@@ -383,7 +424,7 @@ def store_mark(obj, mark):
"""
assert isinstance(mark, Mark), mark
# always reassign name to avoid updating pytestmark
# in a referene that was only borrowed
# in a reference that was only borrowed
obj.pytestmark = get_unpacked_marks(obj) + [mark]
@@ -400,15 +441,6 @@ def store_legacy_markinfo(func, mark):
holder.add_mark(mark)
class Mark(namedtuple('Mark', 'name, args, kwargs')):
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
@@ -431,7 +463,7 @@ class MarkInfo(object):
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
return imap(MarkInfo, self._marks)
return map(MarkInfo, self._marks)
MARK_GEN = MarkGenerator()

View File

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

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

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
""" (disabled by default) support for testing pytest and pytest plugins. """
"""(disabled by default) support for testing pytest and pytest plugins."""
from __future__ import absolute_import, division, print_function
import codecs
@@ -7,6 +7,7 @@ import os
import platform
import re
import subprocess
import six
import sys
import time
import traceback
@@ -22,27 +23,26 @@ 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")
IGNORE_PAM = [ # filenames added when obtaining details about the current user
u'/var/lib/sss/mc/passwd'
]
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
choices=("inprocess", "subprocess"),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
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():
@@ -71,6 +71,8 @@ class LsofFdLeakChecker(object):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename in IGNORE_PAM:
continue
if filename.startswith('/'):
open_files.append((fd, filename))
@@ -80,8 +82,8 @@ class LsofFdLeakChecker(object):
try:
py.process.cmdexec("lsof -v")
except (py.process.cmdexec.Error, UnicodeDecodeError):
# cmdexec may raise UnicodeDecodeError on Windows systems
# with locale other than english:
# cmdexec may raise UnicodeDecodeError on Windows systems with
# locale other than English:
# https://bitbucket.org/pytest-dev/py/issues/66
return False
else:
@@ -114,12 +116,9 @@ class LsofFdLeakChecker(object):
# 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',
}
@@ -139,14 +138,13 @@ def getexecutable(name, cache={}):
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# Handle pyenv's 127.
# handle pyenv's 127
executable = None
cache[name] = executable
return executable
@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)
@@ -165,9 +163,10 @@ def anypython(request):
@pytest.fixture
def _pytest(request):
""" Return a helper which offers a gethookrecorder(hook)
method which returns a HookRecorder instance which helps
to make assertions about called hooks.
"""Return a helper which offers a gethookrecorder(hook) method which
returns a HookRecorder instance which helps to make assertions about called
hooks.
"""
return PytestArg(request)
@@ -182,9 +181,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:
@@ -201,8 +200,8 @@ class ParsedCall:
class HookRecorder:
"""Record all hooks called in a plugin manager.
This wraps all the hook calls in the plugin manager, recording
each call before propagating the normal calls.
This wraps all the hook calls in the plugin manager, recording each call
before propagating the normal calls.
"""
@@ -258,9 +257,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
@@ -270,8 +269,8 @@ class HookRecorder:
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
"""return a testreport whose dotted import path matches"""
values = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
@@ -282,14 +281,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'):
@@ -349,14 +348,14 @@ class RunResult:
Attributes:
:ret: The return value.
:outlines: List of lines captured from stdout.
:errlines: List of lines captures from stderr.
:ret: the return value
:outlines: list of lines captured from stdout
:errlines: list of lines captures from stderr
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
reconstruct stdout or the commonly used
``stdout.fnmatch_lines()`` method.
:stderrr: :py:class:`LineMatcher` of stderr.
:duration: Duration in seconds.
reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
method
:stderr: :py:class:`LineMatcher` of stderr
:duration: duration in seconds
"""
@@ -369,8 +368,10 @@ class RunResult:
self.duration = duration
def parseoutcomes(self):
""" Return a dictionary of outcomestring->num from parsing
the terminal output that the test process produced."""
"""Return a dictionary of outcomestring->num from parsing the terminal
output that the test process produced.
"""
for line in reversed(self.outlines):
if 'seconds' in line:
outcomes = rex_outcome.findall(line)
@@ -382,8 +383,10 @@ class RunResult:
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
"""
d = self.parseoutcomes()
obtained = {
'passed': d.get('passed', 0),
@@ -397,37 +400,26 @@ class RunResult:
class Testdir:
"""Temporary test directory with tools to test/run pytest itself.
This is based on the ``tmpdir`` fixture but provides a number of
methods which aid with testing pytest itself. Unless
:py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
current working directory.
This is based on the ``tmpdir`` fixture but provides a number of methods
which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
methods will use :py:attr:`tmpdir` as their current working directory.
Attributes:
:tmpdir: The :py:class:`py.path.local` instance of the temporary
directory.
:tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
:py:meth:`runpytest`. Initially this is an empty list but
plugins can be added to the list. The type of items to add to
the list depend on the method which uses them so refer to them
for details.
:py:meth:`runpytest`. Initially this is an empty list but plugins can
be added to the list. The type of items to add to the list depends on
the method using them so refer to them for details.
"""
def __init__(self, request, tmpdir_factory):
self.request = request
self._mod_collections = WeakKeyDictionary()
# XXX remove duplication with tmpdir plugin
basetmp = tmpdir_factory.ensuretemp("testdir")
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)
@@ -445,10 +437,9 @@ class Testdir:
def finalize(self):
"""Clean up global state artifacts.
Some methods modify the global interpreter state and this
tries to clean this up. It does not remove the temporary
directory however so it can be looked at after the test run
has finished.
Some methods modify the global interpreter state and this tries to
clean this up. It does not remove the temporary directory however so
it can be looked at after the test run has finished.
"""
sys.path[:], sys.meta_path[:] = self._savesyspath
@@ -486,29 +477,24 @@ class Testdir:
if not hasattr(self, '_olddir'):
self._olddir = old
def _makefile(self, ext, args, kwargs, encoding="utf-8"):
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(encoding) # + "\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
@@ -516,17 +502,15 @@ class Testdir:
def makefile(self, ext, *args, **kwargs):
"""Create a new file in the testdir.
ext: The extension the file should use, including the dot.
E.g. ".py".
ext: The extension the file should use, including the dot, e.g. `.py`.
args: All args will be treated as strings and joined using
newlines. The result will be written as contents to the
file. The name of the file will be based on the test
function requesting this fixture.
args: All args will be treated as strings and joined using newlines.
The result will be written as contents to the file. The name of the
file will be based on the test function requesting this fixture.
E.g. "testdir.makefile('.txt', 'line1', 'line2')"
kwargs: Each keyword is the name of a file, while the value of
it will be written as contents of the file.
kwargs: Each keyword is the name of a file, while the value of it will
be written as contents of the file.
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
"""
@@ -556,14 +540,16 @@ class Testdir:
def syspathinsert(self, path=None):
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
This is undone automatically after the test.
This is undone automatically when this object dies at the end of each
test.
"""
if path is None:
path = self.tmpdir
sys.path.insert(0, str(path))
# a call to syspathinsert() usually means that the caller
# wants to import some dynamically created files.
# with python3 we thus invalidate import caches.
# a call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches
self._possibly_invalidate_import_caches()
def _possibly_invalidate_import_caches(self):
@@ -583,8 +569,8 @@ class Testdir:
def mkpydir(self, name):
"""Create a new python package.
This creates a (sub)directory with an empty ``__init__.py``
file so that is recognised as a python package.
This creates a (sub)directory with an empty ``__init__.py`` file so it
gets recognised as a python package.
"""
p = self.mkdir(name)
@@ -597,10 +583,10 @@ class Testdir:
"""Return the collection node of a file.
:param config: :py:class:`_pytest.config.Config` instance, see
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to
create the configuration.
:py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the
configuration
:param arg: A :py:class:`py.path.local` instance of the file.
:param arg: a :py:class:`py.path.local` instance of the file
"""
session = Session(config)
@@ -614,11 +600,10 @@ class Testdir:
def getpathnode(self, path):
"""Return the collection node of a file.
This is like :py:meth:`getnode` but uses
:py:meth:`parseconfigure` to create the (configured) pytest
Config instance.
This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
create the (configured) pytest Config instance.
:param path: A :py:class:`py.path.local` instance of the file.
:param path: a :py:class:`py.path.local` instance of the file
"""
config = self.parseconfigure(path)
@@ -632,8 +617,8 @@ class Testdir:
def genitems(self, colitems):
"""Generate all test items from a collection node.
This recurses into the collection node and returns a list of
all the test items contained within.
This recurses into the collection node and returns a list of all the
test items contained within.
"""
session = colitems[0].session
@@ -645,10 +630,10 @@ class Testdir:
def runitem(self, source):
"""Run the "test_func" Item.
The calling test instance (the class which contains the test
method) must provide a ``.getrunner()`` method which should
return a runner which can run the test protocol for a single
item, like e.g. :py:func:`_pytest.runner.runtestprotocol`.
The calling test instance (class containing the test method) must
provide a ``.getrunner()`` method which should return a runner which
can run the test protocol for a single item, e.g.
:py:func:`_pytest.runner.runtestprotocol`.
"""
# used from runner functional tests
@@ -662,30 +647,26 @@ class Testdir:
"""Run a test module in process using ``pytest.main()``.
This run writes "source" into a temporary file and runs
``pytest.main()`` on it, returning a :py:class:`HookRecorder`
instance for the result.
``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
for the result.
:param source: The source code of the test module.
:param source: the source code of the test module
:param cmdlineargs: Any extra command line arguments to use.
:param cmdlineargs: any extra command line arguments to use
:return: :py:class:`HookRecorder` instance of the result.
:return: :py:class:`HookRecorder` instance of the result
"""
p = self.makepyfile(source)
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.
Returns a tuple of the collected items and a
:py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself like
:py:meth:`inline_run`. However the return value is a tuple of
the collection items and a :py:class:`HookRecorder` instance.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself like :py:meth:`inline_run`, but returns a
tuple of the collected items and a :py:class:`HookRecorder` instance.
"""
rec = self.inline_run("--collect-only", *args)
@@ -695,24 +676,24 @@ class Testdir:
def inline_run(self, *args, **kwargs):
"""Run ``pytest.main()`` in-process, returning a HookRecorder.
This runs the :py:func:`pytest.main` function to run all of
pytest inside the test process itself. This means it can
return a :py:class:`HookRecorder` instance which gives more
detailed results from then run then can be done by matching
stdout/stderr from :py:meth:`runpytest`.
Runs the :py:func:`pytest.main` function to run all of pytest inside
the test process itself. This means it can return a
:py:class:`HookRecorder` instance which gives more detailed results
from that run than can be done by matching stdout/stderr from
:py:meth:`runpytest`.
:param args: Any command line arguments to pass to
:py:func:`pytest.main`.
:param args: command line arguments to pass to :py:func:`pytest.main`
:param plugin: (keyword-only) Extra plugin instances the
``pytest.main()`` instance should use.
:param plugin: (keyword-only) extra plugin instances the
``pytest.main()`` instance should use
:return: a :py:class:`HookRecorder` instance
:return: A :py:class:`HookRecorder` instance.
"""
# When running py.test inline any plugins active in the main
# test process are already imported. So this disables the
# warning which will trigger to say they can no longer be
# re-written, which is fine as they are already re-written.
# When running py.test inline any plugins active in the main test
# process are already imported. So this disables the warning which
# will trigger to say they can no longer be rewritten, which is fine as
# they have already been rewritten.
orig_warn = AssertionRewritingHook._warn_already_imported
def revert():
@@ -738,8 +719,8 @@ class Testdir:
pass
reprec.ret = ret
# typically we reraise keyboard interrupts from the child run
# because it's our user requesting interruption of the testing
# typically we reraise keyboard interrupts from the child run because
# it's our user requesting interruption of the testing
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
calls = reprec.getcalls("pytest_keyboard_interrupt")
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
@@ -747,8 +728,10 @@ class Testdir:
return reprec
def runpytest_inprocess(self, *args, **kwargs):
""" Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides. """
"""Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides.
"""
if kwargs.get("syspathinsert"):
self.syspathinsert()
now = time.time()
@@ -780,7 +763,7 @@ class Testdir:
return res
def runpytest(self, *args, **kwargs):
""" Run pytest inline or in a subprocess, depending on the command line
"""Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
@@ -801,13 +784,13 @@ class Testdir:
def parseconfig(self, *args):
"""Return a new pytest Config instance from given commandline args.
This invokes the pytest bootstrapping code in _pytest.config
to create a new :py:class:`_pytest.core.PluginManager` and
call the pytest_cmdline_parse hook to create new
This invokes the pytest bootstrapping code in _pytest.config to create
a new :py:class:`_pytest.core.PluginManager` and call the
pytest_cmdline_parse hook to create a new
:py:class:`_pytest.config.Config` instance.
If :py:attr:`plugins` has been populated they should be plugin
modules which will be registered with the PluginManager.
If :py:attr:`plugins` has been populated they should be plugin modules
to be registered with the PluginManager.
"""
args = self._ensure_basetemp(args)
@@ -823,9 +806,8 @@ class Testdir:
def parseconfigure(self, *args):
"""Return a new pytest configured Config instance.
This returns a new :py:class:`_pytest.config.Config` instance
like :py:meth:`parseconfig`, but also calls the
pytest_configure hook.
This returns a new :py:class:`_pytest.config.Config` instance like
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
"""
config = self.parseconfig(*args)
@@ -836,14 +818,14 @@ class Testdir:
def getitem(self, source, funcname="test_func"):
"""Return the test item for a test function.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning the test item
for the requested function name.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning the test item for the requested
function name.
:param source: The module source.
:param source: the module source
:param funcname: The name of the test function for which the
Item must be returned.
:param funcname: the name of the test function for which to return a
test item
"""
items = self.getitems(source)
@@ -856,9 +838,8 @@ class Testdir:
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs pytest's
collection on the resulting module, returning all test items
contained within.
This writes the source to a python file and runs pytest's collection on
the resulting module, returning all test items contained within.
"""
modcol = self.getmodulecol(source)
@@ -867,17 +848,17 @@ class Testdir:
def getmodulecol(self, source, configargs=(), withinit=False):
"""Return the module collection node for ``source``.
This writes ``source`` to a file using :py:meth:`makepyfile`
and then runs the pytest collection on it, returning the
collection node for the test module.
This writes ``source`` to a file using :py:meth:`makepyfile` and then
runs the pytest collection on it, returning the collection node for the
test module.
:param source: The source code of the module to collect.
:param source: the source code of the module to collect
:param configargs: Any extra arguments to pass to
:py:meth:`parseconfigure`.
:param configargs: any extra arguments to pass to
:py:meth:`parseconfigure`
:param withinit: Whether to also write a ``__init__.py`` file
to the temporary directory to ensure it is a package.
:param withinit: whether to also write an ``__init__.py`` file to the
same directory to ensure it is a package
"""
kw = {self.request.function.__name__: Source(source).strip()}
@@ -892,13 +873,12 @@ class Testdir:
def collect_by_name(self, modcol, name):
"""Return the collection node for name from the module collection.
This will search a module collection node for a collection
node matching the given name.
This will search a module collection node for a collection node
matching the given name.
:param modcol: A module collection node, see
:py:meth:`getmodulecol`.
:param modcol: a module collection node; see :py:meth:`getmodulecol`
:param name: The name of the node to return.
:param name: the name of the node to return
"""
if modcol not in self._mod_collections:
@@ -910,8 +890,8 @@ class Testdir:
def popen(self, cmdargs, stdout, stderr, **kw):
"""Invoke subprocess.Popen.
This calls subprocess.Popen making sure the current working
directory is the PYTHONPATH.
This calls subprocess.Popen making sure the current working directory
is in the PYTHONPATH.
You probably want to use :py:meth:`run` instead.
@@ -929,8 +909,7 @@ class Testdir:
def run(self, *cmdargs):
"""Run a command with arguments.
Run a process using subprocess.Popen saving the stdout and
stderr.
Run a process using subprocess.Popen saving the stdout and stderr.
Returns a :py:class:`RunResult`.
@@ -973,14 +952,15 @@ class Testdir:
print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a pytest.exe
return (sys.executable, _pytest_fullpath,) # noqa
# we cannot use `(sys.executable, script)` because on Windows the
# script is e.g. `pytest.exe`
return (sys.executable, PYTEST_FULLPATH) # noqa
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
Returns a :py:class:`RunResult`.
"""
return self.run(sys.executable, script)
@@ -991,25 +971,18 @@ class Testdir:
def runpytest_subprocess(self, *args, **kwargs):
"""Run pytest as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will added
using the ``-p`` command line option. Addtionally
``--basetemp`` is used put any temporary files and directories
in a numbered directory prefixed with "runpytest-" so they do
not conflict with the normal numberd pytest location for
temporary files and directories.
Any plugins added to the :py:attr:`plugins` list will added using the
``-p`` command line option. Additionally ``--basetemp`` is used put
any temporary files and directories in a numbered directory prefixed
with "runpytest-" so they do not conflict with the normal numbered
pytest location for temporary files and directories.
Returns a :py:class:`RunResult`.
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
# for x in args:
# if '--confcutdir' in str(x):
# break
# else:
# pass
# args = ('--confcutdir=.',) + args
args = ('--basetemp=%s' % p,) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
args = ('-p', plugins[0]) + args
@@ -1019,8 +992,8 @@ class Testdir:
def spawn_pytest(self, string, expect_timeout=10.0):
"""Run pytest using pexpect.
This makes sure to use the right pytest and sets up the
temporary directory locations.
This makes sure to use the right pytest and sets up the temporary
directory locations.
The pexpect child is returned.
@@ -1034,6 +1007,7 @@ class Testdir:
"""Run a command using pexpect.
The pexpect child is returned.
"""
pexpect = pytest.importorskip("pexpect", "3.0")
if hasattr(sys, 'pypy_version_info') and '64' in platform.machine():
@@ -1060,8 +1034,10 @@ class LineComp:
self.stringio = py.io.TextIO()
def assert_contains_lines(self, lines2):
""" assert that lines2 are contained (linearly) in lines1.
return a list of extralines found.
"""Assert that lines2 are contained (linearly) in lines1.
Return a list of extralines found.
"""
__tracebackhide__ = True
val = self.stringio.getvalue()
@@ -1077,8 +1053,8 @@ class LineMatcher:
This is a convenience class to test large texts like the output of
commands.
The constructor takes a list of lines without their trailing
newlines, i.e. ``text.splitlines()``.
The constructor takes a list of lines without their trailing newlines, i.e.
``text.splitlines()``.
"""
@@ -1098,16 +1074,34 @@ class LineMatcher:
return lines2
def fnmatch_lines_random(self, lines2):
"""Check lines exist in the output using in any order.
Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
lines which have to occur in the output, in any order.
"""
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
output, in any order. Each line can contain glob whildcards.
The argument is a list of lines which have to occur in the output, in
any order. Each line can contain glob whildcards.
"""
lines2 = self._getlines(lines2)
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:
@@ -1118,6 +1112,7 @@ class LineMatcher:
"""Return all lines following the given line in the text.
The given line can contain glob wildcards.
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
@@ -1132,12 +1127,36 @@ 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
called. The matches and non-matches are also printed on
stdout.
The argument is a list of lines which have to match and can use glob
wildcards. If they do not match a pytest.fail() is called. The
matches and non-matches are also printed on stdout.
"""
self._match_lines(lines2, fnmatch, 'fnmatch')
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)
@@ -1152,8 +1171,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:

View File

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

View File

@@ -2,8 +2,9 @@ import math
import sys
import py
from six.moves import zip
from _pytest.compat import isclass, izip
from _pytest.compat import isclass
from _pytest.outcomes import fail
import _pytest._code
@@ -84,7 +85,7 @@ class ApproxNumpy(ApproxBase):
try:
actual = np.asarray(actual)
except:
except: # noqa
raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
if actual.shape != self.expected.shape:
@@ -145,7 +146,7 @@ class ApproxSequence(ApproxBase):
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
return izip(actual, self.expected)
return zip(actual, self.expected)
class ApproxScalar(ApproxBase):
@@ -217,7 +218,8 @@ class ApproxScalar(ApproxBase):
absolute tolerance or a relative tolerance, depending on what the user
specified or which would be larger.
"""
def set_default(x, default): return x if x is not None else default
def set_default(x, default):
return x if x is not None else default
# Figure out what the absolute tolerance should be. ``self.abs`` is
# either None or a value specified by the user.
@@ -453,8 +455,7 @@ def raises(expected_exception, *args, **kwargs):
This helper produces a ``ExceptionInfo()`` object (see below).
If using Python 2.5 or above, you may use this function as a
context manager::
You may use this function as a context manager::
>>> with raises(ZeroDivisionError):
... 1/0
@@ -609,13 +610,6 @@ class RaisesContext(object):
__tracebackhide__ = True
if tp[0] is None:
fail(self.message)
if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the
# exception value.
# http://bugs.python.org/issue7853
if not isinstance(tp[1], BaseException):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception:

View File

@@ -8,6 +8,8 @@ import py
import sys
import warnings
import re
from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail
@@ -98,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)
@@ -109,12 +129,12 @@ def warns(expected_warning, *args, **kwargs):
loc = frame.f_locals.copy()
loc.update(kwargs)
with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
with WarningsChecker(expected_warning, match_expr=match_expr):
return func(*args[1:], **kwargs)
@@ -174,7 +194,7 @@ class WarningsRecorder(warnings.catch_warnings):
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None):
def __init__(self, expected_warning=None, match_expr=None):
super(WarningsChecker, self).__init__()
msg = ("exceptions must be old-style classes or "
@@ -189,6 +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)
@@ -203,3 +224,13 @@ class WarningsChecker(WarningsRecorder):
"The list of emitted warnings is: {1}.".format(
self.expected_warning,
[each.message for each in self]))
elif self.match_expr is not None:
for r in self:
if issubclass(r.category, self.expected_warning):
if re.compile(self.match_expr).search(str(r.message)):
break
else:
fail("DID NOT WARN. No warnings of type {0} matching"
" ('{1}') was emitted. The list of emitted warnings"
" is: {2}.".format(self.expected_warning, self.match_expr,
[each.message for each in self]))

View File

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

View File

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

View File

@@ -2,10 +2,10 @@
from __future__ import absolute_import, division, print_function
import os
import six
import sys
import traceback
import py
from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.outcomes import fail, skip, xfail, TEST_OUTCOME
@@ -60,22 +60,31 @@ def pytest_configure(config):
)
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:
@@ -95,7 +104,7 @@ class MarkEvaluator:
fail("Error evaluating %r expression\n"
" %s\n"
"%s"
% (self.name, self.expr, "\n".join(msg)),
% (self._mark_name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
@@ -107,40 +116,45 @@ class MarkEvaluator:
def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder:
if self.holder.args or 'condition' in self.holder.kwargs:
self.result = False
# "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an
# _arglist attribute
marks = getattr(self.holder, '_marks', None) \
or [self.holder.mark]
for _, args, kwargs in marks:
if 'condition' in kwargs:
args = (kwargs['condition'],)
for expr in args:
self._marks = self._get_marks()
if self._marks:
self.result = False
for mark in self._marks:
self._mark = mark
if 'condition' in mark.kwargs:
args = (mark.kwargs['condition'],)
else:
args = mark.args
for expr in args:
self.expr = expr
if isinstance(expr, six.string_types):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in mark.kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = mark.kwargs.get('reason', None)
self.expr = expr
if isinstance(expr, py.builtin._basestring):
d = self._getglobals()
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = kwargs.get('reason', None)
self.expr = expr
return self.result
else:
self.result = True
return getattr(self, 'result', False)
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)
@@ -155,17 +169,17 @@ class MarkEvaluator:
@hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
# Check if skip or skipif are specified as pytest marks
item._skipped_by_mark = False
skipif_info = item.keywords.get('skipif')
if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
eval_skipif = MarkEvaluator(item, 'skipif')
if eval_skipif.istrue():
item._evalskip = eval_skipif
item._skipped_by_mark = True
skip(eval_skipif.getexplanation())
skip_info = item.keywords.get('skip')
if isinstance(skip_info, (MarkInfo, MarkDecorator)):
item._evalskip = True
item._skipped_by_mark = True
if 'reason' in skip_info.kwargs:
skip(skip_info.kwargs['reason'])
elif skip_info.args:
@@ -212,7 +226,6 @@ def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
evalxfail = getattr(item, '_evalxfail', None)
evalskip = getattr(item, '_evalskip', None)
# unitttest special case, see setting of _unexpectedsuccess
if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
from _pytest.compat import _is_unittest_unexpected_success_a_failure
@@ -248,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
@@ -345,11 +358,18 @@ 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):
@@ -367,6 +387,11 @@ def show_skipped(terminalreporter, lines):
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno + 1, reason))
if lineno is not None:
lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno + 1, reason))
else:
lines.append(
"SKIP [%d] %s: %s" %
(num, fspath, reason))

View File

@@ -5,15 +5,18 @@ This is a good source for looking at the various reporting hooks.
from __future__ import absolute_import, division, print_function
import itertools
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import py
import platform
import sys
import time
import platform
import _pytest._pluggy as pluggy
import pluggy
import py
import six
import pytest
from _pytest import nodes
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
def pytest_addoption(parser):
@@ -47,6 +50,10 @@ def pytest_addoption(parser):
choices=['yes', 'no', 'auto'],
help="color terminal output (yes/no/auto).")
parser.addini("console_output_style",
help="console output: classic or with additional progress information (classic|progress).",
default='progress')
def pytest_configure(config):
config.option.verbose -= config.option.quiet
@@ -131,17 +138,22 @@ class TerminalReporter:
self.showfspath = self.verbosity >= 0
self.showlongtestinfo = self.verbosity > 0
self._numcollected = 0
self._session = None
self.stats = {}
self.startdir = py.path.local()
if file is None:
file = sys.stdout
self._tw = self.writer = _pytest.config.create_terminal_writer(config,
file)
self._tw = _pytest.config.create_terminal_writer(config, file)
# self.writer will be deprecated in pytest-3.4
self.writer = self._tw
self._screen_width = self._tw.fullwidth
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
self._progress_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)
@@ -150,6 +162,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()
@@ -164,6 +178,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:
@@ -174,8 +189,8 @@ class TerminalReporter:
self._tw.write(content, **markup)
def write_line(self, line, **markup):
if not py.builtin._istext(line):
line = py.builtin.text(line, errors="replace")
if not isinstance(line, six.text_type):
line = six.text_type(line, errors="replace")
self.ensure_newline()
self._tw.line(line, **markup)
@@ -190,7 +205,7 @@ class TerminalReporter:
"""
erase = markup.pop('erase', False)
if erase:
fill_count = self._tw.fullwidth - len(line)
fill_count = self._tw.fullwidth - len(line) - 1
fill = ' ' * fill_count
else:
fill = ''
@@ -208,7 +223,7 @@ class TerminalReporter:
self._tw.line(msg, **kw)
def pytest_internalerror(self, excrepr):
for line in py.builtin.text(excrepr).split("\n"):
for line in six.text_type(excrepr).split("\n"):
self.write_line("INTERNALERROR> " + line)
return 1
@@ -243,38 +258,76 @@ class TerminalReporter:
rep = report
res = self.config.hook.pytest_report_teststatus(report=rep)
cat, letter, word = res
if isinstance(word, tuple):
word, markup = word
else:
markup = None
self.stats.setdefault(cat, []).append(rep)
self._tests_ran = True
if not letter and not word:
# probably passed setup/teardown
return
running_xdist = hasattr(rep, 'node')
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}
elif rep.failed:
markup = {'red': True}
elif rep.skipped:
markup = {'yellow': True}
else:
markup = {}
line = self._locationline(rep.nodeid, *rep.location)
if not hasattr(rep, 'node'):
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
# self._tw.write(word, **markup)
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):
collected = self._session.testscollected
if collected:
progress = self._progress_items_reported * 100 // collected
return ' [{:3d}%]'.format(progress)
return ' [100%]'
def _write_progress_information_filling_space(self):
if not self._show_progress_info:
return
msg = self._get_progress_information_message()
fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
self.write(fill + msg, cyan=True)
def pytest_collection(self):
if not self.isatty and self.config.option.verbose >= 1:
self.write("collecting ... ", bold=True)
@@ -317,6 +370,7 @@ class TerminalReporter:
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session):
self._session = session
self._sessionstarttime = time.time()
if not self.showheader:
return
@@ -444,15 +498,15 @@ class TerminalReporter:
line = self.config.cwd_relative_nodeid(nodeid)
if domain and line.endswith(domain):
line = line[:-len(domain)]
l = domain.split("[")
l[0] = l[0].replace('.', '::') # don't replace '.' in params
line += "[".join(l)
values = domain.split("[")
values[0] = values[0].replace('.', '::') # don't replace '.' in params
line += "[".join(values)
return line
# collect_fspath comes from testid which has a "/"-normalized path
if fspath:
res = mkrel(nodeid).replace("::()", "") # parens-normalization
if nodeid.split("::")[0] != fspath.replace("\\", "/"):
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
res += " <- " + self.startdir.bestrelpath(fspath)
else:
res = "[location]"
@@ -478,11 +532,11 @@ class TerminalReporter:
# summaries for sessionfinish
#
def getreports(self, name):
l = []
values = []
for x in self.stats.get(name, []):
if not hasattr(x, '_pdbshown'):
l.append(x)
return l
values.append(x)
return values
def summary_warnings(self):
if self.hasopt("w"):
@@ -493,9 +547,9 @@ class TerminalReporter:
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
self.write_sep("=", "warnings summary", yellow=True, bold=False)
for location, warnings in grouped:
for location, warning_records in grouped:
self._tw.line(str(location) or '<undetermined location>')
for w in warnings:
for w in warning_records:
lines = w.message.splitlines()
indented = '\n'.join(' ' + x for x in lines)
self._tw.line(indented)
@@ -593,8 +647,8 @@ def repr_pythonversion(v=None):
return str(v)
def flatten(l):
for x in l:
def flatten(values):
for x in values:
if isinstance(x, (list, tuple)):
for y in flatten(x):
yield y
@@ -635,7 +689,7 @@ def build_summary_stats_line(stats):
def _plugin_nameversions(plugininfo):
l = []
values = []
for plugin, dist in plugininfo:
# gets us name and version!
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
@@ -644,6 +698,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,11 @@ Release announcements
:maxdepth: 2
release-3.3.2
release-3.3.1
release-3.3.0
release-3.2.5
release-3.2.4
release-3.2.3
release-3.2.2
release-3.2.1

View File

@@ -62,7 +62,7 @@ holger krekel
- fix issue655: work around different ways that cause python2/3
to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
- fix issue615: assertion re-writing did not correctly escape % signs
- fix issue615: assertion rewriting did not correctly escape % signs
when formatting boolean operations, which tripped over mixing
booleans with modulo operators. Thanks to Tom Viner for the report,
triaging and fix.

View File

@@ -0,0 +1,36 @@
pytest-3.2.4
=======================================
pytest 3.2.4 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Bruno Oliveira
* Christian Boelsen
* Christoph Buchner
* Daw-Ran Liou
* Florian Bruhin
* Franck Michea
* Leonard Lausen
* Matty G
* Owen Tuz
* Pavel Karateev
* Pierre GIRAUD
* Ronny Pfannschmidt
* Stephen Finucane
* Sviatoslav Abakumov
* Thomas Hisch
* Tom Dalton
* Xuan Luong
* Yorgos Pagles
* Семён Марьясин
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,18 @@
pytest-3.2.5
=======================================
pytest 3.2.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 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
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,50 @@
pytest-3.3.0
=======================================
The pytest team is proud to announce the 3.3.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:
* Anthony Sottile
* Bruno Oliveira
* Ceridwen
* Daniel Hahler
* Dirk Thomas
* Dmitry Malinovsky
* Florian Bruhin
* George Y. Kussumoto
* Hugo
* Jesús Espino
* Joan Massich
* Ofir
* OfirOshir
* Ronny Pfannschmidt
* Samuel Dion-Girardeau
* Srinivas Reddy Thatiparthy
* Sviatoslav Abakumov
* Tarcisio Fischer
* Thomas Hisch
* Tyler Goodlet
* hugovk
* je
* prokaktus
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,25 @@
pytest-3.3.1
=======================================
pytest 3.3.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
* Daniel Hahler
* Eugene Prikazchikov
* Florian Bruhin
* Roland Puntaier
* Ronny Pfannschmidt
* Sebastian Rahlf
* Tom Viner
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-3.3.2
=======================================
pytest 3.3.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Antony Lee
* Austin
* Bruno Oliveira
* Florian Bruhin
* Floris Bruynooghe
* Henk-Jaap Wagenaar
* Jurko Gospodnetić
* Ronny Pfannschmidt
* Srinivas Reddy Thatiparthy
* Thomas Hisch
Happy testing,
The pytest Development Team

View File

@@ -25,15 +25,15 @@ to assert that your function returns a certain value. If this assertion fails
you will see the return value of the function call::
$ pytest test_assert1.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_assert1.py F
test_assert1.py F [100%]
======= FAILURES ========
_______ test_function ________
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
@@ -41,7 +41,7 @@ you will see the return value of the function call::
E + where 3 = f()
test_assert1.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
``pytest`` has support for showing the values of the most common subexpressions
including calls, attributes, comparisons, and binary and unary
@@ -168,15 +168,15 @@ when it encounters comparisons. For example::
if you run this module::
$ pytest test_assert2.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_assert2.py F
test_assert2.py F [100%]
======= FAILURES ========
_______ test_set_comparison ________
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
@@ -190,7 +190,7 @@ if you run this module::
E Use -v to get the full diff
test_assert2.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
Special comparisons are done for a number of cases:
@@ -209,8 +209,8 @@ the ``pytest_assertrepr_compare`` hook.
.. autofunction:: _pytest.hookspec.pytest_assertrepr_compare
:noindex:
As an example consider adding the following hook in a conftest.py which
provides an alternative explanation for ``Foo`` objects::
As an example consider adding the following hook in a :ref:`conftest.py <conftest.py>`
file which provides an alternative explanation for ``Foo`` objects::
# content of conftest.py
from test_foocompare import Foo
@@ -238,9 +238,9 @@ you can run the test module and get the custom output defined in
the conftest file::
$ pytest -q test_foocompare.py
F
======= FAILURES ========
_______ test_compare ________
F [100%]
================================= FAILURES =================================
_______________________________ test_compare _______________________________
def test_compare():
f1 = Foo(1)

View File

@@ -10,3 +10,96 @@ With the pytest 3.0 release we introduced a clear communication scheme for when
To communicate changes we are already issuing deprecation warnings, but they are not displayed by default. In pytest 3.0 we changed the default setting so that pytest deprecation warnings are displayed if not explicitly silenced (with ``--disable-pytest-warnings``).
We will only remove deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we will not remove it in 4.0 but in 5.0).
Deprecation Roadmap
-------------------
This page lists deprecated features and when we plan to remove them. It is important to list the feature, the version where it got deprecated and the version we plan to remove it.
Following our deprecation policy, we should aim to keep features for *at least* two minor versions after it was considered deprecated.
Future Releases
~~~~~~~~~~~~~~~
3.4
^^^
**Old style classes**
Issue: `#2147 <https://github.com/pytest-dev/pytest/issues/2147>`_.
Deprecated in ``3.2``.
4.0
^^^
**Yield tests**
Deprecated in ``3.0``.
**pytest-namespace hook**
deprecated in ``3.2``.
**Marks in parameter sets**
Deprecated in ``3.2``.
**--result-log**
Deprecated in ``3.0``.
See `#830 <https://github.com/pytest-dev/pytest/issues/830>`_ for more information. Suggested alternative: `pytest-tap <https://pypi.python.org/pypi/pytest-tap>`_.
**metafunc.addcall**
Issue: `#2876 <https://github.com/pytest-dev/pytest/issues/2876>`_.
Deprecated in ``3.3``.
**pytest_plugins in non-toplevel conftests**
There is a deep conceptual confusion as ``conftest.py`` files themselves are activated/deactivated based on path, but the plugins they depend on aren't.
Issue: `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_.
Not yet officially deprecated.
**passing a single string to pytest.main()**
Pass a list of strings to ``pytest.main()`` instead.
Deprecated in ``3.1``.
**[pytest] section in setup.cfg**
Use ``[tool:pytest]`` instead for compatibility with other tools.
Deprecated in ``3.0``.
Past Releases
~~~~~~~~~~~~~
3.0
^^^
* The following deprecated commandline options were removed:
* ``--genscript``: no longer supported;
* ``--no-assert``: use ``--assert=plain`` instead;
* ``--nomagic``: use ``--assert=plain`` instead;
* ``--report``: use ``-r`` instead;
* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
were never documented and a leftover from a pre-virtualenv era. These entry
points also created broken entry points in wheels, so removing them also
removes a source of confusion for users.
3.3
^^^
* Dropped support for EOL Python 2.6 and 3.3.

View File

@@ -91,11 +91,23 @@ You can ask for available builtin or project-custom
capsys
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.
capsysbinary
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.
capfd
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.
capfdbinary
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.
doctest_namespace
Inject names into the doctest namespace.
pytestconfig
@@ -104,6 +116,14 @@ You can ask for available builtin or project-custom
Add extra xml properties to the tag for the calling test.
The fixture is callable with ``(name, value)``, with value being automatically
xml-encoded.
caplog
Access and control log capturing.
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
monkeypatch
The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::

View File

@@ -46,9 +46,9 @@ First, let's create 50 test invocation of which only 2 fail::
If you run this for the first time you will see two failures::
$ pytest -q
.................F.......F........................
======= FAILURES ========
_______ test_num[17] ________
.................F.......F........................ [100%]
================================= FAILURES =================================
_______________________________ test_num[17] _______________________________
i = 17
@@ -59,7 +59,7 @@ If you run this for the first time you will see two failures::
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
_______________________________ test_num[25] _______________________________
i = 25
@@ -75,16 +75,16 @@ If you run this for the first time you will see two failures::
If you then run it with ``--lf``::
$ pytest --lf
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
run-last-failure: rerun previous 2 failures
test_50.py FF
test_50.py FF [100%]
======= FAILURES ========
_______ test_num[17] ________
================================= FAILURES =================================
_______________________________ test_num[17] _______________________________
i = 17
@@ -95,7 +95,7 @@ If you then run it with ``--lf``::
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
_______________________________ test_num[25] _______________________________
i = 25
@@ -106,8 +106,8 @@ If you then run it with ``--lf``::
E Failed: bad luck
test_50.py:6: Failed
======= 48 tests deselected ========
======= 2 failed, 48 deselected in 0.12 seconds ========
=========================== 48 tests deselected ============================
================= 2 failed, 48 deselected in 0.12 seconds ==================
You have run only the two failing test from the last run, while 48 tests have
not been run ("deselected").
@@ -117,16 +117,16 @@ previous failures will be executed first (as can be seen from the series
of ``FF`` and dots)::
$ pytest --ff
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
run-last-failure: rerun previous 2 failures first
test_50.py FF................................................
test_50.py FF................................................ [100%]
======= FAILURES ========
_______ test_num[17] ________
================================= FAILURES =================================
_______________________________ test_num[17] _______________________________
i = 17
@@ -137,7 +137,7 @@ of ``FF`` and dots)::
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
_______________________________ test_num[25] _______________________________
i = 25
@@ -148,7 +148,7 @@ of ``FF`` and dots)::
E Failed: bad luck
test_50.py:6: Failed
======= 2 failed, 48 passed in 0.12 seconds ========
=================== 2 failed, 48 passed in 0.12 seconds ====================
.. _`config.cache`:
@@ -182,9 +182,9 @@ If you run this command once, it will take a while because
of the sleep::
$ pytest -q
F
======= FAILURES ========
_______ test_function ________
F [100%]
================================= FAILURES =================================
______________________________ test_function _______________________________
mydata = 42
@@ -199,9 +199,9 @@ If you run it a second time the value will be retrieved from
the cache and this will be quick::
$ pytest -q
F
======= FAILURES ========
_______ test_function ________
F [100%]
================================= FAILURES =================================
______________________________ test_function _______________________________
mydata = 42
@@ -222,7 +222,7 @@ You can always peek at the content of the cache using the
``--cache-show`` command line option::
$ py.test --cache-show
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
cachedir: $REGENDOC_TMPDIR/.cache
@@ -232,7 +232,7 @@ You can always peek at the content of the cache using the
example/value contains:
42
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
Clearing Cache content
-------------------------------

View File

@@ -63,15 +63,15 @@ and running this module will show you precisely the output
of the failing function and hide the other one::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .F
test_module.py .F [100%]
======= FAILURES ========
_______ test_func2 ________
================================= FAILURES =================================
________________________________ test_func2 ________________________________
def test_func2():
> assert False
@@ -80,26 +80,26 @@ of the failing function and hide the other one::
test_module.py:9: AssertionError
-------------------------- Captured stdout setup ---------------------------
setting up <function test_func2 at 0xdeadbeef>
======= 1 failed, 1 passed in 0.12 seconds ========
==================== 1 failed, 1 passed in 0.12 seconds ====================
Accessing captured output from a test function
---------------------------------------------------
The ``capsys`` and ``capfd`` fixtures allow to access stdout/stderr
output created during test execution. Here is an example test function
that performs some output related checks:
The ``capsys``, ``capsysbinary``, ``capfd``, and ``capfdbinary`` fixtures
allow access to stdout/stderr output created during test execution. Here is
an example test function that performs some output related checks:
.. code-block:: python
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")
print("hello")
sys.stderr.write("world\n")
out, err = capsys.readouterr()
assert out == "hello\n"
assert err == "world\n"
print ("next")
out, err = capsys.readouterr()
assert out == "next\n"
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert captured.err == "world\n"
print("next")
captured = capsys.readouterr()
assert captured.out == "next\n"
The ``readouterr()`` call snapshots the output so far -
and capturing will be continued. After the test
@@ -110,11 +110,30 @@ output streams and also interacts well with pytest's
own per-test capturing.
If you want to capture on filedescriptor level you can use
the ``capfd`` function argument which offers the exact
the ``capfd`` fixture which offers the exact
same interface but allows to also capture output from
libraries or subprocesses that directly write to operating
system level output streams (FD1 and FD2).
.. versionadded:: 3.3
The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
.. versionadded:: 3.3
If the code under test writes non-textual data, you can capture this using
the ``capsysbinary`` fixture which instead returns ``bytes`` from
the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only
available in python 3.
.. versionadded:: 3.3
If the code under test writes non-textual data, you can capture this using
the ``capfdbinary`` fixture which instead returns ``bytes`` from
the ``readouterr`` method. The ``capfdbinary`` fixture operates on the
filedescriptor level.
.. versionadded:: 3.0

View File

@@ -30,6 +30,7 @@ Full pytest documentation
xunit_setup
plugins
writing_plugins
logging
goodpractices
pythonpath

View File

@@ -312,3 +312,22 @@ Builtin configuration file options
relative to :ref:`rootdir <rootdir>`. Additionally path may contain environment
variables, that will be expanded. For more information about cache plugin
please refer to :ref:`cache_provider`.
.. confval:: console_output_style
.. versionadded:: 3.3
Sets the console output style while running tests:
* ``classic``: classic pytest output.
* ``progress``: like classic pytest output, but with a progress indicator.
The default is ``progress``, but you can fallback to ``classic`` if you prefer or
the new mode is causing unexpected problems:
.. code-block:: ini
# content of pytest.ini
[pytest]
console_output_style = classic

View File

@@ -61,14 +61,14 @@ and another like this::
then you can just invoke ``pytest`` without command line options::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 1 item
mymodule.py .
mymodule.py . [100%]
======= 1 passed in 0.12 seconds ========
========================= 1 passed in 0.12 seconds =========================
It is possible to use fixtures using the ``getfixture`` helper::

View File

@@ -30,32 +30,32 @@ You can "mark" a test function with custom metadata like this::
You can then restrict a test run to only run tests marked with ``webtest``::
$ pytest -v -m webtest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
test_server.py::test_send_http PASSED
test_server.py::test_send_http PASSED [100%]
======= 3 tests deselected ========
======= 1 passed, 3 deselected in 0.12 seconds ========
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
Or the inverse, running all tests except the webtest ones::
$ pytest -v -m "not webtest"
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
test_server.py::test_something_quick PASSED
test_server.py::test_another PASSED
test_server.py::TestClass::test_method PASSED
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
test_server.py::TestClass::test_method PASSED [100%]
======= 1 tests deselected ========
======= 3 passed, 1 deselected in 0.12 seconds ========
============================ 1 tests deselected ============================
================== 3 passed, 1 deselected in 0.12 seconds ==================
Selecting tests based on their node ID
--------------------------------------
@@ -65,42 +65,42 @@ arguments to select only specified tests. This makes it easy to select
tests based on their module, class, method, or function name::
$ pytest -v test_server.py::TestClass::test_method
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
test_server.py::TestClass::test_method PASSED
test_server.py::TestClass::test_method PASSED [100%]
======= 1 passed in 0.12 seconds ========
========================= 1 passed in 0.12 seconds =========================
You can also select on the class::
$ pytest -v test_server.py::TestClass
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 1 item
test_server.py::TestClass::test_method PASSED
test_server.py::TestClass::test_method PASSED [100%]
======= 1 passed in 0.12 seconds ========
========================= 1 passed in 0.12 seconds =========================
Or select multiple nodes::
$ pytest -v test_server.py::TestClass test_server.py::test_send_http
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
test_server.py::TestClass::test_method PASSED
test_server.py::test_send_http PASSED
test_server.py::TestClass::test_method PASSED [ 50%]
test_server.py::test_send_http PASSED [100%]
======= 2 passed in 0.12 seconds ========
========================= 2 passed in 0.12 seconds =========================
.. _node-id:
@@ -129,47 +129,47 @@ exact match on markers that ``-m`` provides. This makes it easy to
select tests based on their names::
$ pytest -v -k http # running with the above defined example module
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
test_server.py::test_send_http PASSED
test_server.py::test_send_http PASSED [100%]
======= 3 tests deselected ========
======= 1 passed, 3 deselected in 0.12 seconds ========
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
And you can also run all tests except the ones that match the keyword::
$ pytest -k "not send_http" -v
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
test_server.py::test_something_quick PASSED
test_server.py::test_another PASSED
test_server.py::TestClass::test_method PASSED
test_server.py::test_something_quick PASSED [ 33%]
test_server.py::test_another PASSED [ 66%]
test_server.py::TestClass::test_method PASSED [100%]
======= 1 tests deselected ========
======= 3 passed, 1 deselected in 0.12 seconds ========
============================ 1 tests deselected ============================
================== 3 passed, 1 deselected in 0.12 seconds ==================
Or to select "http" and "quick" tests::
$ pytest -k "http or quick" -v
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items
test_server.py::test_send_http PASSED
test_server.py::test_something_quick PASSED
test_server.py::test_send_http PASSED [ 50%]
test_server.py::test_something_quick PASSED [100%]
======= 2 tests deselected ========
======= 2 passed, 2 deselected in 0.12 seconds ========
============================ 2 tests deselected ============================
================== 2 passed, 2 deselected in 0.12 seconds ==================
.. note::
@@ -354,26 +354,26 @@ and an example invocations specifying a different environment than what
the test needs::
$ pytest -E stage2
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_someenv.py s
test_someenv.py s [100%]
======= 1 skipped in 0.12 seconds ========
======================== 1 skipped in 0.12 seconds =========================
and here is one that specifies exactly the environment needed::
$ pytest -E stage1
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_someenv.py .
test_someenv.py . [100%]
======= 1 passed in 0.12 seconds ========
========================= 1 passed in 0.12 seconds =========================
The ``--markers`` option always gives you a list of available markers::
@@ -432,7 +432,7 @@ The output is as follows::
$ pytest -q -s
Marker info name=my_marker args=(<function hello_world at 0xdeadbeef>,) kwars={}
.
. [100%]
1 passed in 0.12 seconds
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
@@ -477,7 +477,7 @@ Let's run this without capturing output and see what we get::
glob args=('function',) kwargs={'x': 3}
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}
.
. [100%]
1 passed in 0.12 seconds
marking platform specific tests with pytest
@@ -530,29 +530,29 @@ Let's do a little test file to show how this looks like::
then you will see two tests skipped and two executed tests as expected::
$ pytest -rs # this option reports skip reasons
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_plat.py s.s.
======= short test summary info ========
test_plat.py s.s. [100%]
========================= short test summary info ==========================
SKIP [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
======= 2 passed, 2 skipped in 0.12 seconds ========
=================== 2 passed, 2 skipped in 0.12 seconds ====================
Note that if you specify a platform via the marker-command line option like this::
$ pytest -m linux
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_plat.py .
test_plat.py . [100%]
======= 3 tests deselected ========
======= 1 passed, 3 deselected in 0.12 seconds ========
============================ 3 tests deselected ============================
================== 1 passed, 3 deselected in 0.12 seconds ==================
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.
@@ -596,47 +596,47 @@ We want to dynamically define two markers and can do it in a
We can now use the ``-m option`` to select one set::
$ pytest -m interface --tb=short
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_module.py FF
test_module.py FF [100%]
======= FAILURES ========
_______ test_interface_simple ________
================================= FAILURES =================================
__________________________ test_interface_simple ___________________________
test_module.py:3: in test_interface_simple
assert 0
E assert 0
_______ test_interface_complex ________
__________________________ test_interface_complex __________________________
test_module.py:6: in test_interface_complex
assert 0
E assert 0
======= 2 tests deselected ========
======= 2 failed, 2 deselected in 0.12 seconds ========
============================ 2 tests deselected ============================
================== 2 failed, 2 deselected in 0.12 seconds ==================
or to select both "event" and "interface" tests::
$ pytest -m "interface or event" --tb=short
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_module.py FFF
test_module.py FFF [100%]
======= FAILURES ========
_______ test_interface_simple ________
================================= FAILURES =================================
__________________________ test_interface_simple ___________________________
test_module.py:3: in test_interface_simple
assert 0
E assert 0
_______ test_interface_complex ________
__________________________ test_interface_complex __________________________
test_module.py:6: in test_interface_complex
assert 0
E assert 0
_______ test_event_simple ________
____________________________ test_event_simple _____________________________
test_module.py:9: in test_event_simple
assert 0
E assert 0
======= 1 tests deselected ========
======= 3 failed, 1 deselected in 0.12 seconds ========
============================ 1 tests deselected ============================
================== 3 failed, 1 deselected in 0.12 seconds ==================

View File

@@ -6,7 +6,7 @@ import py
import pytest
import _pytest._code
pythonlist = ['python2.6', 'python2.7', 'python3.4', 'python3.5']
pythonlist = ['python2.7', 'python3.4', 'python3.5']
@pytest.fixture(params=pythonlist)
def python1(request, tmpdir):
picklefile = tmpdir.join("data.pickle")

View File

@@ -26,19 +26,19 @@ and if you installed `PyYAML`_ or a compatible YAML-parser you can
now execute the test specification::
nonpython $ pytest test_simple.yml
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items
test_simple.yml F.
test_simple.yml F. [100%]
======= FAILURES ========
_______ usecase: hello ________
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
======= 1 failed, 1 passed in 0.12 seconds ========
==================== 1 failed, 1 passed in 0.12 seconds ====================
.. regendoc:wipe
@@ -58,21 +58,21 @@ your own domain specific testing language this way.
consulted when reporting in ``verbose`` mode::
nonpython $ pytest -v
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items
test_simple.yml::hello FAILED
test_simple.yml::ok PASSED
test_simple.yml::hello FAILED [ 50%]
test_simple.yml::ok PASSED [100%]
======= FAILURES ========
_______ usecase: hello ________
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
======= 1 failed, 1 passed in 0.12 seconds ========
==================== 1 failed, 1 passed in 0.12 seconds ====================
.. regendoc:wipe
@@ -80,7 +80,7 @@ While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ pytest --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items
@@ -88,4 +88,4 @@ interesting to just look at the collection tree::
<YamlItem 'hello'>
<YamlItem 'ok'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================

View File

@@ -45,16 +45,16 @@ Now we add a test configuration like this::
This means that we only run 2 tests if we do not pass ``--all``::
$ pytest -q test_compute.py
..
.. [100%]
2 passed in 0.12 seconds
We run only two computations, so we see two dots.
let's run the full monty::
$ pytest -q --all
....F
======= FAILURES ========
_______ test_compute[4] ________
....F [100%]
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
@@ -138,7 +138,7 @@ objects, they are still using the default pytest representation::
$ pytest test_time.py --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 8 items
@@ -152,7 +152,7 @@ objects, they are still using the default pytest representation::
<Function 'test_timedistance_v3[forward]'>
<Function 'test_timedistance_v3[backward]'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
together with the actual data, instead of listing them separately.
@@ -194,20 +194,20 @@ only have to work a bit to construct the correct arguments for pytest's
this is a fully self-contained example which you can run with::
$ pytest test_scenarios.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_scenarios.py ....
test_scenarios.py .... [100%]
======= 4 passed in 0.12 seconds ========
========================= 4 passed in 0.12 seconds =========================
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
$ pytest --collect-only test_scenarios.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
@@ -219,7 +219,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
<Function 'test_demo1[advanced]'>
<Function 'test_demo2[advanced]'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
Note that we told ``metafunc.parametrize()`` that your scenario values
should be considered class-scoped. With pytest-2.3 this leads to a
@@ -272,7 +272,7 @@ creates a database object for the actual test invocations::
Let's first see how it looks like at collection time::
$ pytest test_backends.py --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
@@ -280,14 +280,14 @@ Let's first see how it looks like at collection time::
<Function 'test_db_initialized[d1]'>
<Function 'test_db_initialized[d2]'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
And then when we run the test::
$ pytest -q test_backends.py
.F
======= FAILURES ========
_______ test_db_initialized[d2] ________
.F [100%]
================================= FAILURES =================================
_________________________ test_db_initialized[d2] __________________________
db = <conftest.DB2 object at 0xdeadbeef>
@@ -333,14 +333,14 @@ will be passed to respective fixture function::
The result of this test will be successful::
$ pytest test_indirect_list.py --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
<Module 'test_indirect_list.py'>
<Function 'test_indirect[a-b]'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
.. regendoc:wipe
@@ -350,7 +350,7 @@ Parametrizing test methods through per-class configuration
.. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py
Here is an example ``pytest_generate_function`` function implementing a
Here is an example ``pytest_generate_tests`` function implementing a
parametrization scheme similar to Michael Foord's `unittest
parametrizer`_ but in a lot less code::
@@ -381,9 +381,9 @@ Our test generator looks up a class-level definition which specifies which
argument sets to use for each test function. Let's run it::
$ pytest -q
F..
======= FAILURES ========
_______ TestClass.test_equals[1-2] ________
F.. [100%]
================================= FAILURES =================================
________________________ TestClass.test_equals[1-2] ________________________
self = <test_parametrize.TestClass object at 0xdeadbeef>, a = 1, b = 2
@@ -411,10 +411,8 @@ is to be run with different sets of arguments for its three arguments:
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
. $ pytest -rs -q multipython.py
sssssssssssssss.........sss.........sss.........
======= short test summary info ========
SKIP [21] $REGENDOC_TMPDIR/CWD/multipython.py:24: 'python2.6' not found
27 passed, 21 skipped in 0.12 seconds
........................... [100%]
27 passed in 0.12 seconds
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------
@@ -460,16 +458,16 @@ And finally a little test module::
If you run this with reporting for skips enabled::
$ pytest -rs test_module.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .s
======= short test summary info ========
test_module.py .s [100%]
========================= short test summary info ==========================
SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
======= 1 passed, 1 skipped in 0.12 seconds ========
=================== 1 passed, 1 skipped in 0.12 seconds ====================
You'll see that we don't have a ``opt2`` module and thus the second test run
of our ``test_func1`` was skipped. A few notes:
@@ -485,4 +483,54 @@ of our ``test_func1`` was skipped. A few notes:
values as well.
Set marks or test ID for individual parametrized test
--------------------------------------------------------------------
Use ``pytest.param`` to apply marks or set test ID to individual parametrized test.
For example::
# content of test_pytest_param_example.py
import pytest
@pytest.mark.parametrize('test_input,expected', [
('3+5', 8),
pytest.param('1+7', 8,
marks=pytest.mark.basic),
pytest.param('2+4', 6,
marks=pytest.mark.basic,
id='basic_2+4'),
pytest.param('6*9', 42,
marks=[pytest.mark.basic, pytest.mark.xfail],
id='basic_6*9'),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
In this example, we have 4 parametrized tests. Except for the first test,
we mark the rest three parametrized tests with the custom marker ``basic``,
and for the fourth test we also use the built-in mark ``xfail`` to indicate this
test is expected to fail. For explicitness, we set test ids for some tests.
Then run ``pytest`` with verbose mode and with only the ``basic`` marker::
pytest -v -m basic
============================================ test session starts =============================================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_pytest_param_example.py::test_eval[1+7-8] PASSED
test_pytest_param_example.py::test_eval[basic_2+4] PASSED
test_pytest_param_example.py::test_eval[basic_6*9] xfail
========================================== short test summary info ===========================================
XFAIL test_pytest_param_example.py::test_eval[basic_6*9]
============================================= 1 tests deselected =============================================
As the result:
- Four tests were collected
- One test was deselected because it doesn't have the ``basic`` mark.
- Three tests with the ``basic`` mark was selected.
- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
- The test ``test_eval[basic_2+4]`` passed.
- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.

View File

@@ -116,7 +116,7 @@ that match ``*_check``. For example, if we have::
then the test collection looks like this::
$ pytest --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 2 items
@@ -126,7 +126,7 @@ then the test collection looks like this::
<Function 'simple_check'>
<Function 'complex_check'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
.. note::
@@ -162,7 +162,7 @@ Finding out what is collected
You can always peek at the collection tree without running tests like this::
. $ pytest --collect-only pythoncollection.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 3 items
@@ -173,23 +173,25 @@ You can always peek at the collection tree without running tests like this::
<Function 'test_method'>
<Function 'test_anothermethod'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
customizing test collection to find all .py files
---------------------------------------------------------
.. _customizing-test-collection:
Customizing test collection
---------------------------
.. regendoc:wipe
You can easily instruct ``pytest`` to discover tests from every python file::
You can easily instruct ``pytest`` to discover tests from every Python file::
# content of pytest.ini
[pytest]
python_files = *.py
However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python version.
For such cases you can dynamically define files to be ignored by listing
them in a ``conftest.py`` file::
However, many projects will have a ``setup.py`` which they don't want to be
imported. Moreover, there may files only importable by a specific python
version. For such cases you can dynamically define files to be ignored by
listing them in a ``conftest.py`` file::
# content of conftest.py
import sys
@@ -198,7 +200,7 @@ them in a ``conftest.py`` file::
if sys.version_info[0] > 2:
collect_ignore.append("pkg/module_py2.py")
And then if you have a module file like this::
and then if you have a module file like this::
# content of pkg/module_py2.py
def test_only_on_python2():
@@ -207,13 +209,13 @@ And then if you have a module file like this::
except Exception, e:
pass
and a setup.py dummy file like this::
and a ``setup.py`` dummy file like this::
# content of setup.py
0/0 # will raise exception if imported
then a pytest run on Python2 will find the one test and will leave out the
setup.py file::
If you run with a Python 2 interpreter then you will find the one test and will
leave out the ``setup.py`` file::
#$ pytest --collect-only
====== test session starts ======
@@ -225,13 +227,13 @@ setup.py file::
====== no tests ran in 0.04 seconds ======
If you run with a Python3 interpreter both the one test and the setup.py file
will be left out::
If you run with a Python 3 interpreter both the one test and the ``setup.py``
file will be left out::
$ pytest --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 0 items
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================

View File

@@ -10,15 +10,15 @@ not showing the nice colors here in the HTML that you
get on the terminal - we are working on that)::
assertion $ pytest failure_demo.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
collected 42 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
======= FAILURES ========
_______ test_generative[0] ________
================================= FAILURES =================================
____________________________ test_generative[0] ____________________________
param1 = 3, param2 = 6
@@ -27,7 +27,7 @@ get on the terminal - we are working on that)::
E assert (3 * 2) < 6
failure_demo.py:16: AssertionError
_______ TestFailing.test_simple ________
_________________________ TestFailing.test_simple __________________________
self = <failure_demo.TestFailing object at 0xdeadbeef>
@@ -43,7 +43,7 @@ get on the terminal - we are working on that)::
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
failure_demo.py:29: AssertionError
_______ TestFailing.test_simple_multiline ________
____________________ TestFailing.test_simple_multiline _____________________
self = <failure_demo.TestFailing object at 0xdeadbeef>
@@ -63,7 +63,7 @@ get on the terminal - we are working on that)::
E assert 42 == 54
failure_demo.py:12: AssertionError
_______ TestFailing.test_not ________
___________________________ TestFailing.test_not ___________________________
self = <failure_demo.TestFailing object at 0xdeadbeef>
@@ -75,7 +75,7 @@ get on the terminal - we are working on that)::
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
failure_demo.py:39: AssertionError
_______ TestSpecialisedExplanations.test_eq_text ________
_________________ TestSpecialisedExplanations.test_eq_text _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -86,7 +86,7 @@ get on the terminal - we are working on that)::
E + eggs
failure_demo.py:43: AssertionError
_______ TestSpecialisedExplanations.test_eq_similar_text ________
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -99,7 +99,7 @@ get on the terminal - we are working on that)::
E ? ^
failure_demo.py:46: AssertionError
_______ TestSpecialisedExplanations.test_eq_multiline_text ________
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -112,7 +112,7 @@ get on the terminal - we are working on that)::
E bar
failure_demo.py:49: AssertionError
_______ TestSpecialisedExplanations.test_eq_long_text ________
______________ TestSpecialisedExplanations.test_eq_long_text _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -129,7 +129,7 @@ get on the terminal - we are working on that)::
E ? ^
failure_demo.py:54: AssertionError
_______ TestSpecialisedExplanations.test_eq_long_text_multiline ________
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -149,7 +149,7 @@ get on the terminal - we are working on that)::
E ...Full output truncated (7 lines hidden), use '-vv' to show
failure_demo.py:59: AssertionError
_______ TestSpecialisedExplanations.test_eq_list ________
_________________ TestSpecialisedExplanations.test_eq_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -160,7 +160,7 @@ get on the terminal - we are working on that)::
E Use -v to get the full diff
failure_demo.py:62: AssertionError
_______ TestSpecialisedExplanations.test_eq_list_long ________
______________ TestSpecialisedExplanations.test_eq_list_long _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -173,7 +173,7 @@ get on the terminal - we are working on that)::
E Use -v to get the full diff
failure_demo.py:67: AssertionError
_______ TestSpecialisedExplanations.test_eq_dict ________
_________________ TestSpecialisedExplanations.test_eq_dict _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -191,7 +191,7 @@ get on the terminal - we are working on that)::
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:70: AssertionError
_______ TestSpecialisedExplanations.test_eq_set ________
_________________ TestSpecialisedExplanations.test_eq_set __________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -209,7 +209,7 @@ get on the terminal - we are working on that)::
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:73: AssertionError
_______ TestSpecialisedExplanations.test_eq_longer_list ________
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -220,7 +220,7 @@ get on the terminal - we are working on that)::
E Use -v to get the full diff
failure_demo.py:76: AssertionError
_______ TestSpecialisedExplanations.test_in_list ________
_________________ TestSpecialisedExplanations.test_in_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -229,7 +229,7 @@ get on the terminal - we are working on that)::
E assert 1 in [0, 2, 3, 4, 5]
failure_demo.py:79: AssertionError
_______ TestSpecialisedExplanations.test_not_in_text_multiline ________
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -248,7 +248,7 @@ get on the terminal - we are working on that)::
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:83: AssertionError
_______ TestSpecialisedExplanations.test_not_in_text_single ________
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -261,7 +261,7 @@ get on the terminal - we are working on that)::
E ? +++
failure_demo.py:87: AssertionError
_______ TestSpecialisedExplanations.test_not_in_text_single_long ________
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
@@ -287,7 +287,7 @@ get on the terminal - we are working on that)::
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:95: AssertionError
_______ test_attribute ________
______________________________ test_attribute ______________________________
def test_attribute():
class Foo(object):
@@ -298,7 +298,7 @@ get on the terminal - we are working on that)::
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
failure_demo.py:102: AssertionError
_______ test_attribute_instance ________
_________________________ test_attribute_instance __________________________
def test_attribute_instance():
class Foo(object):
@@ -309,7 +309,7 @@ get on the terminal - we are working on that)::
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
failure_demo.py:108: AssertionError
_______ test_attribute_failure ________
__________________________ test_attribute_failure __________________________
def test_attribute_failure():
class Foo(object):
@@ -329,7 +329,7 @@ get on the terminal - we are working on that)::
E Exception: Failed to get attrib
failure_demo.py:114: Exception
_______ test_attribute_multiple ________
_________________________ test_attribute_multiple __________________________
def test_attribute_multiple():
class Foo(object):
@@ -344,7 +344,7 @@ get on the terminal - we are working on that)::
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
failure_demo.py:125: AssertionError
_______ TestRaises.test_raises ________
__________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -358,8 +358,8 @@ get on the terminal - we are working on that)::
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:579>:1: ValueError
_______ TestRaises.test_raises_doesnt ________
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:580>:1: ValueError
______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -368,7 +368,7 @@ get on the terminal - we are working on that)::
E Failed: DID NOT RAISE <class 'OSError'>
failure_demo.py:137: Failed
_______ TestRaises.test_raise ________
__________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -377,7 +377,7 @@ get on the terminal - we are working on that)::
E ValueError: demo error
failure_demo.py:140: ValueError
_______ TestRaises.test_tupleerror ________
________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -399,7 +399,7 @@ get on the terminal - we are working on that)::
failure_demo.py:148: TypeError
--------------------------- Captured stdout call ---------------------------
l is [1, 2, 3]
_______ TestRaises.test_some_error ________
________________________ TestRaises.test_some_error ________________________
self = <failure_demo.TestRaises object at 0xdeadbeef>
@@ -408,7 +408,7 @@ get on the terminal - we are working on that)::
E NameError: name 'namenotexi' is not defined
failure_demo.py:151: NameError
_______ test_dynamic_compile_shows_nicely ________
____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely():
src = 'def foo():\n assert 1 == 0\n'
@@ -427,7 +427,7 @@ get on the terminal - we are working on that)::
E AssertionError
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:163>:2: AssertionError
_______ TestMoreErrors.test_complex_error ________
____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -451,7 +451,7 @@ get on the terminal - we are working on that)::
E assert 44 == 43
failure_demo.py:6: AssertionError
_______ TestMoreErrors.test_z1_unpack_error ________
___________________ TestMoreErrors.test_z1_unpack_error ____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -461,7 +461,7 @@ get on the terminal - we are working on that)::
E ValueError: not enough values to unpack (expected 2, got 0)
failure_demo.py:180: ValueError
_______ TestMoreErrors.test_z2_type_error ________
____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -471,7 +471,7 @@ get on the terminal - we are working on that)::
E TypeError: 'int' object is not iterable
failure_demo.py:184: TypeError
_______ TestMoreErrors.test_startswith ________
______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -484,7 +484,7 @@ get on the terminal - we are working on that)::
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
failure_demo.py:189: AssertionError
_______ TestMoreErrors.test_startswith_nested ________
__________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -501,7 +501,7 @@ get on the terminal - we are working on that)::
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
failure_demo.py:196: AssertionError
_______ TestMoreErrors.test_global_func ________
_____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -512,7 +512,7 @@ get on the terminal - we are working on that)::
E + where 43 = globf(42)
failure_demo.py:199: AssertionError
_______ TestMoreErrors.test_instance ________
_______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -523,7 +523,7 @@ get on the terminal - we are working on that)::
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
failure_demo.py:203: AssertionError
_______ TestMoreErrors.test_compare ________
_______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -533,7 +533,7 @@ get on the terminal - we are working on that)::
E + where 11 = globf(10)
failure_demo.py:206: AssertionError
_______ TestMoreErrors.test_try_finally ________
_____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@@ -544,7 +544,7 @@ get on the terminal - we are working on that)::
E assert 1 == 0
failure_demo.py:211: AssertionError
_______ TestCustomAssertMsg.test_single_line ________
___________________ TestCustomAssertMsg.test_single_line ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -558,7 +558,7 @@ get on the terminal - we are working on that)::
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
failure_demo.py:222: AssertionError
_______ TestCustomAssertMsg.test_multiline ________
____________________ TestCustomAssertMsg.test_multiline ____________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -575,7 +575,7 @@ get on the terminal - we are working on that)::
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
failure_demo.py:228: AssertionError
_______ TestCustomAssertMsg.test_custom_repr ________
___________________ TestCustomAssertMsg.test_custom_repr ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@@ -595,4 +595,10 @@ get on the terminal - we are working on that)::
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
failure_demo.py:238: AssertionError
======= 42 failed in 0.12 seconds ========
============================= warnings summary =============================
None
Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.
Please use Metafunc.parametrize instead.
-- Docs: http://doc.pytest.org/en/latest/warnings.html
================== 42 failed, 1 warnings in 0.12 seconds ===================

View File

@@ -41,9 +41,9 @@ provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`:
Let's run this without supplying our new option::
$ pytest -q test_sample.py
F
======= FAILURES ========
_______ test_answer ________
F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
cmdopt = 'type1'
@@ -63,9 +63,9 @@ Let's run this without supplying our new option::
And now with supplying a command line option::
$ pytest -q --cmdopt=type2
F
======= FAILURES ========
_______ test_answer ________
F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
cmdopt = 'type2'
@@ -112,12 +112,12 @@ of subprocesses close to your CPU. Running in an empty
directory with the above conftest.py::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
.. _`excontrolskip`:
@@ -166,28 +166,28 @@ We can now write a test module like this:
and when running it will see a skipped "slow" test::
$ pytest -rs # "-rs" means report details on the little 's'
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .s
======= short test summary info ========
test_module.py .s [100%]
========================= short test summary info ==========================
SKIP [1] test_module.py:8: need --runslow option to run
======= 1 passed, 1 skipped in 0.12 seconds ========
=================== 1 passed, 1 skipped in 0.12 seconds ====================
Or run it including the ``slow`` marked test::
$ pytest --runslow
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py ..
test_module.py .. [100%]
======= 2 passed in 0.12 seconds ========
========================= 2 passed in 0.12 seconds =========================
Writing well integrated assertion helpers
--------------------------------------------------
@@ -218,9 +218,9 @@ unless the ``--full-trace`` command line option is specified.
Let's run our little function::
$ pytest -q test_checkconfig.py
F
======= FAILURES ========
_______ test_something ________
F [100%]
================================= FAILURES =================================
______________________________ test_something ______________________________
def test_something():
> checkconfig(42)
@@ -305,13 +305,13 @@ It's easy to present extra information in a ``pytest`` run:
which will add the string to the test header accordingly::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
project deps: mylib-1.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
.. regendoc:wipe
@@ -330,7 +330,7 @@ display more information if applicable:
which will add info only when run with "--v"::
$ pytest -v
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
info1: did you know that ...
@@ -338,17 +338,17 @@ which will add info only when run with "--v"::
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 0 items
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
and nothing when run plainly::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
profiling test duration
--------------------------
@@ -377,18 +377,18 @@ out which tests are the slowest. Let's make an artificial test suite:
Now we can profile which test functions execute the slowest::
$ pytest --durations=3
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
test_some_are_slow.py ...
test_some_are_slow.py ... [100%]
======= slowest 3 test durations ========
0.30s call test_some_are_slow.py::test_funcslow2
========================= slowest 3 test durations =========================
0.31s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
======= 3 passed in 0.12 seconds ========
0.17s call test_some_are_slow.py::test_funcfast
========================= 3 passed in 0.12 seconds =========================
incremental testing - test steps
---------------------------------------------------
@@ -443,18 +443,18 @@ tests in a class. Here is a test module example:
If we run this::
$ pytest -rx
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items
test_step.py .Fx.
======= short test summary info ========
test_step.py .Fx. [100%]
========================= short test summary info ==========================
XFAIL test_step.py::TestUserHandling::()::test_deletion
reason: previous test failed (test_modification)
======= FAILURES ========
_______ TestUserHandling.test_modification ________
================================= FAILURES =================================
____________________ TestUserHandling.test_modification ____________________
self = <test_step.TestUserHandling object at 0xdeadbeef>
@@ -463,7 +463,7 @@ If we run this::
E assert 0
test_step.py:9: AssertionError
======= 1 failed, 2 passed, 1 xfailed in 0.12 seconds ========
============== 1 failed, 2 passed, 1 xfailed in 0.12 seconds ===============
We'll see that ``test_deletion`` was not executed because ``test_modification``
failed. It is reported as an "expected failure".
@@ -522,27 +522,27 @@ the ``db`` fixture:
We can run this::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 7 items
test_step.py .Fx.
a/test_db.py F
a/test_db2.py F
b/test_error.py E
test_step.py .Fx. [ 57%]
a/test_db.py F [ 71%]
a/test_db2.py F [ 85%]
b/test_error.py E [100%]
======= ERRORS ========
_______ ERROR at setup of test_root ________
================================== ERRORS ==================================
_______________________ ERROR at setup of test_root ________________________
file $REGENDOC_TMPDIR/b/test_error.py, line 1
def test_root(db): # no db here, will error out
E fixture 'db' not found
> available fixtures: cache, capfd, capsys, doctest_namespace, monkeypatch, pytestconfig, record_xml_property, recwarn, tmpdir, tmpdir_factory
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_property, recwarn, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
$REGENDOC_TMPDIR/b/test_error.py:1
======= FAILURES ========
_______ TestUserHandling.test_modification ________
================================= FAILURES =================================
____________________ TestUserHandling.test_modification ____________________
self = <test_step.TestUserHandling object at 0xdeadbeef>
@@ -551,7 +551,7 @@ We can run this::
E assert 0
test_step.py:9: AssertionError
_______ test_a1 ________
_________________________________ test_a1 __________________________________
db = <conftest.DB object at 0xdeadbeef>
@@ -561,7 +561,7 @@ We can run this::
E assert 0
a/test_db.py:2: AssertionError
_______ test_a2 ________
_________________________________ test_a2 __________________________________
db = <conftest.DB object at 0xdeadbeef>
@@ -571,7 +571,7 @@ We can run this::
E assert 0
a/test_db2.py:2: AssertionError
======= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ========
========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ==========
The two test modules in the ``a`` directory see the same ``db`` fixture instance
while the one test in the sister-directory ``b`` doesn't see it. We could of course
@@ -630,15 +630,15 @@ if you then have failing tests:
and run them::
$ pytest test_module.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py FF
test_module.py FF [100%]
======= FAILURES ========
_______ test_fail1 ________
================================= FAILURES =================================
________________________________ test_fail1 ________________________________
tmpdir = local('PYTEST_TMPDIR/test_fail10')
@@ -647,14 +647,14 @@ and run them::
E assert 0
test_module.py:2: AssertionError
_______ test_fail2 ________
________________________________ test_fail2 ________________________________
def test_fail2():
> assert 0
E assert 0
test_module.py:4: AssertionError
======= 2 failed in 0.12 seconds ========
========================= 2 failed in 0.12 seconds =========================
you will have a "failures" file which contains the failing test ids::
@@ -724,17 +724,17 @@ if you then have failing tests:
and run it::
$ pytest -s test_module.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
test_module.py Esetting up a test failed! test_module.py::test_setup_fails
Fexecuting test failed test_module.py::test_call_fails
F
F [100%]
======= ERRORS ========
_______ ERROR at setup of test_setup_fails ________
================================== ERRORS ==================================
____________________ ERROR at setup of test_setup_fails ____________________
@pytest.fixture
def other():
@@ -742,8 +742,8 @@ and run it::
E assert 0
test_module.py:6: AssertionError
======= FAILURES ========
_______ test_call_fails ________
================================= FAILURES =================================
_____________________________ test_call_fails ______________________________
something = None
@@ -752,14 +752,14 @@ and run it::
E assert 0
test_module.py:12: AssertionError
_______ test_fail2 ________
________________________________ test_fail2 ________________________________
def test_fail2():
> assert 0
E assert 0
test_module.py:15: AssertionError
======= 2 failed, 1 error in 0.12 seconds ========
==================== 2 failed, 1 error in 0.12 seconds =====================
You'll see that the fixture finalizers could use the precise reporting
information.

View File

@@ -68,5 +68,5 @@ If you run this without output capturing::
.test_method1 called
.test other
.test_unit1 method called
.
. [100%]
4 passed in 0.12 seconds

View File

@@ -27,7 +27,7 @@ functions:
* fixture management scales from simple unit to complex
functional testing, allowing to parametrize fixtures and tests according
to configuration and component options, or to re-use fixtures
across class, module or whole test session scopes.
across function, class, module or whole test session scopes.
In addition, pytest continues to support :ref:`xunitsetup`. You can mix
both styles, moving incrementally from classic to new style, as you
@@ -69,15 +69,15 @@ will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
marked ``smtp`` fixture function. Running the test looks like this::
$ pytest test_smtpsimple.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_smtpsimple.py F
test_smtpsimple.py F [100%]
======= FAILURES ========
_______ test_ehlo ________
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -88,7 +88,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
E assert 0
test_smtpsimple.py:11: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
In the failure traceback we see that the test function was called with a
``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture
@@ -127,10 +127,39 @@ It's a prime example of `dependency injection`_ where fixture
functions take the role of the *injector* and test functions are the
*consumers* of fixture objects.
.. _`conftest.py`:
.. _`conftest`:
``conftest.py``: sharing fixture functions
------------------------------------------
If during implementing your tests you realize that you
want to use a fixture function from multiple test files you can move it
to a ``conftest.py`` file.
You don't need to import the fixture you want to use in a test, it
automatically gets discovered by pytest. The discovery of
fixture functions starts at test classes, then test modules, then
``conftest.py`` files and finally builtin and third party plugins.
You can also use the ``conftest.py`` file to implement
:ref:`local per-directory plugins <conftest.py plugins>`.
Sharing test data
-----------------
If you want to make test data from files available to your tests, a good way
to do this is by loading these data in a fixture for use by your tests.
This makes use of the automatic caching mechanisms of pytest.
Another good approach is by adding the data files in the ``tests`` folder.
There are also community plugins available to help managing this aspect of
testing, e.g. `pytest-datadir <https://github.com/gabrielcnr/pytest-datadir>`__
and `pytest-datafiles <https://pypi.python.org/pypi/pytest-datafiles>`__.
.. _smtpshared:
Sharing a fixture across tests in a module (or class/session)
-----------------------------------------------------------------
Scope: sharing a fixture instance across tests in a class, module or session
----------------------------------------------------------------------------
.. regendoc:wipe
@@ -139,10 +168,12 @@ usually time-expensive to create. Extending the previous example, we
can add a ``scope='module'`` parameter to the
:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
to cause the decorated ``smtp`` fixture function to only be invoked once
per test module. Multiple test functions in a test module will thus
each receive the same ``smtp`` fixture instance. The next example puts
the fixture function into a separate ``conftest.py`` file so
that tests from multiple test modules in the directory can
per test *module* (the default is to invoke once per test *function*).
Multiple test functions in a test module will thus
each receive the same ``smtp`` fixture instance, thus saving time.
The next example puts the fixture function into a separate ``conftest.py`` file
so that tests from multiple test modules in the directory can
access the fixture function::
# content of conftest.py
@@ -174,15 +205,15 @@ We deliberately insert failing ``assert 0`` statements in order to
inspect what is going on and can now run the tests::
$ pytest test_module.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py FF
test_module.py FF [100%]
======= FAILURES ========
_______ test_ehlo ________
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -194,7 +225,7 @@ inspect what is going on and can now run the tests::
E assert 0
test_module.py:6: AssertionError
_______ test_noop ________
________________________________ test_noop _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -205,7 +236,7 @@ inspect what is going on and can now run the tests::
E assert 0
test_module.py:11: AssertionError
======= 2 failed in 0.12 seconds ========
========================= 2 failed in 0.12 seconds =========================
You see the two ``assert 0`` failing and more importantly you can also see
that the same (module-scoped) ``smtp`` object was passed into the two
@@ -223,6 +254,8 @@ instance, you can simply declare it:
# the returned fixture value will be shared for
# all tests needing it
Finally, the ``class`` scope will invoke the fixture once per test *class*.
.. _`finalization`:
Fixture finalization / executing teardown code
@@ -253,7 +286,7 @@ tests.
Let's execute it::
$ pytest -s -q --tb=no
FFteardown smtp
FF [100%]teardown smtp
2 failed in 0.12 seconds
@@ -358,7 +391,7 @@ We use the ``request.module`` attribute to optionally obtain an
again, nothing much has changed::
$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
FF [100%]finalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
2 failed in 0.12 seconds
@@ -375,9 +408,9 @@ server URL in its module namespace::
Running it::
$ pytest -qq --tb=short test_anothersmtp.py
F
======= FAILURES ========
_______ test_showhelo ________
F [100%]
================================= FAILURES =================================
______________________________ test_showhelo _______________________________
test_anothersmtp.py:5: in test_showhelo
assert 0, smtp.helo()
E AssertionError: (250, b'mail.python.org')
@@ -424,9 +457,9 @@ a value via ``request.param``. No test function code needs to change.
So let's just do another run::
$ pytest -q test_module.py
FFFF
======= FAILURES ========
_______ test_ehlo[smtp.gmail.com] ________
FFFF [100%]
================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -438,7 +471,7 @@ So let's just do another run::
E assert 0
test_module.py:6: AssertionError
_______ test_noop[smtp.gmail.com] ________
________________________ test_noop[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -449,7 +482,7 @@ So let's just do another run::
E assert 0
test_module.py:11: AssertionError
_______ test_ehlo[mail.python.org] ________
________________________ test_ehlo[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -462,7 +495,7 @@ So let's just do another run::
test_module.py:5: AssertionError
-------------------------- Captured stdout setup ---------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
_______ test_noop[mail.python.org] ________
________________________ test_noop[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
@@ -526,7 +559,7 @@ return ``None`` then pytest's auto-generated ID will be used.
Running the above tests results in the following test IDs being used::
$ pytest --collect-only
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 10 items
@@ -544,7 +577,7 @@ Running the above tests results in the following test IDs being used::
<Function 'test_ehlo[mail.python.org]'>
<Function 'test_noop[mail.python.org]'>
======= no tests ran in 0.12 seconds ========
======================= no tests ran in 0.12 seconds =======================
.. _`interdependent fixtures`:
@@ -577,16 +610,16 @@ Here we declare an ``app`` fixture which receives the previously defined
``smtp`` fixture and instantiates an ``App`` object with it. Let's run it::
$ pytest -v test_appsetup.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED [100%]
======= 2 passed in 0.12 seconds ========
========================= 2 passed in 0.12 seconds =========================
Due to the parametrization of ``smtp`` the test will run twice with two
different ``App`` instances and respective smtp servers. There is no
@@ -646,7 +679,7 @@ to show the setup/teardown flow::
Let's run the tests in verbose mode and with looking at the print-output::
$ pytest -v -s test_module.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile:
@@ -654,38 +687,38 @@ Let's run the tests in verbose mode and with looking at the print-output::
test_module.py::test_0[1] SETUP otherarg 1
RUN test0 with otherarg 1
PASSED TEARDOWN otherarg 1
PASSED [ 12%] TEARDOWN otherarg 1
test_module.py::test_0[2] SETUP otherarg 2
RUN test0 with otherarg 2
PASSED TEARDOWN otherarg 2
PASSED [ 25%] TEARDOWN otherarg 2
test_module.py::test_1[mod1] SETUP modarg mod1
RUN test1 with modarg mod1
PASSED
PASSED [ 37%]
test_module.py::test_2[1-mod1] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
PASSED TEARDOWN otherarg 1
PASSED [ 50%] TEARDOWN otherarg 1
test_module.py::test_2[2-mod1] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
PASSED TEARDOWN otherarg 2
PASSED [ 62%] TEARDOWN otherarg 2
test_module.py::test_1[mod2] TEARDOWN modarg mod1
SETUP modarg mod2
RUN test1 with modarg mod2
PASSED
PASSED [ 75%]
test_module.py::test_2[1-mod2] SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
PASSED TEARDOWN otherarg 1
PASSED [ 87%] TEARDOWN otherarg 1
test_module.py::test_2[2-mod2] SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
PASSED TEARDOWN otherarg 2
PASSED [100%] TEARDOWN otherarg 2
TEARDOWN modarg mod2
======= 8 passed in 0.12 seconds ========
========================= 8 passed in 0.12 seconds =========================
You can see that the parametrized module-scoped ``modarg`` resource caused an
ordering of test execution that lead to the fewest possible "active" resources.
@@ -748,7 +781,7 @@ you specified a "cleandir" function argument to each of them. Let's run it
to verify our fixture is activated and the tests pass::
$ pytest -q
..
.. [100%]
2 passed in 0.12 seconds
You can specify multiple fixtures like this:
@@ -829,7 +862,7 @@ class-level ``usefixtures`` decorator.
If we run it, we get two passing tests::
$ pytest -q
..
.. [100%]
2 passed in 0.12 seconds
Here is how autouse fixtures work in other scopes:
@@ -858,7 +891,7 @@ into a conftest.py file **without** using ``autouse``::
# content of conftest.py
@pytest.fixture
def transact(self, request, db):
def transact(request, db):
db.begin()
yield
db.rollback()
@@ -874,17 +907,6 @@ All test methods in this TestClass will use the transaction fixture while
other test classes or functions in the module will not use it unless
they also add a ``transact`` reference.
Shifting (visibility of) fixture functions
----------------------------------------------------
If during implementing your tests you realize that you
want to use a fixture function from multiple test files you can move it
to a :ref:`conftest.py <conftest.py>` file or even separately installable
:ref:`plugins <plugins>` without changing test code. The discovery of
fixtures functions starts at test classes, then test modules, then
``conftest.py`` files and finally builtin and third party plugins.
Overriding fixtures on various levels
-------------------------------------

View File

@@ -1,7 +1,7 @@
Installation and Getting Started
===================================
**Pythons**: Python 2.6,2.7,3.3,3.4,3.5, Jython, PyPy-2.3
**Pythons**: Python 2.7, 3.4, 3.5, 3.6, Jython, PyPy-2.3
**Platforms**: Unix/Posix and Windows
@@ -9,8 +9,6 @@ Installation and Getting Started
**dependencies**: `py <http://pypi.python.org/pypi/py>`_,
`colorama (Windows) <http://pypi.python.org/pypi/colorama>`_,
`argparse (py26) <http://pypi.python.org/pypi/argparse>`_,
`ordereddict (py26) <http://pypi.python.org/pypi/ordereddict>`_.
**documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
@@ -46,15 +44,15 @@ Let's create a first test file with a simple test function::
That's it. You can execute the test function now::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_sample.py F
test_sample.py F [100%]
======= FAILURES ========
_______ test_answer ________
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert func(3) == 5
@@ -62,7 +60,7 @@ That's it. You can execute the test function now::
E + where 4 = func(3)
test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
We got a failure report because our little ``func(3)`` call did not return ``5``.
@@ -101,7 +99,7 @@ use the ``raises`` helper::
Running it with, this time in "quiet" reporting mode::
$ pytest -q test_sysexit.py
.
. [100%]
1 passed in 0.12 seconds
Grouping multiple tests in a class
@@ -126,9 +124,9 @@ There is no need to subclass anything. We can simply
run the module by passing its filename::
$ pytest -q test_class.py
.F
======= FAILURES ========
_______ TestClass.test_two ________
.F [100%]
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass object at 0xdeadbeef>
@@ -163,9 +161,9 @@ We list the name ``tmpdir`` in the test function signature and
before performing the test function call. Let's just run it::
$ pytest -q test_tmpdir.py
F
======= FAILURES ========
_______ test_needsfiles ________
F [100%]
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')

View File

@@ -24,15 +24,15 @@ An example of a simple test:
To execute it::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_sample.py F
test_sample.py F [100%]
======= FAILURES ========
_______ test_answer ________
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert inc(3) == 5
@@ -40,7 +40,7 @@ To execute it::
E + where 4 = inc(3)
test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.
See :ref:`Getting Started <getstarted>` for more examples.
@@ -57,7 +57,7 @@ Features
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested);
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

192
doc/en/logging.rst Normal file
View File

@@ -0,0 +1,192 @@
.. _logging:
Logging
-------
.. versionadded 3.3.0
.. note::
This feature is a drop-in replacement for the `pytest-catchlog
<https://pypi.org/project/pytest-catchlog/>`_ plugin and they will conflict
with each other. The backward compatibility API with ``pytest-capturelog``
has been dropped when this feature was introduced, so if for that reason you
still need ``pytest-catchlog`` you can disable the internal feature by
adding to your ``pytest.ini``:
.. code-block:: ini
[pytest]
addopts=-p no:logging
Log messages are captured by default and for each failed test will be shown in
the same manner as captured stdout and stderr.
Running without options::
pytest
Shows failed tests like so::
----------------------- Captured stdlog call ----------------------
test_reporting.py 26 INFO text going to logger
----------------------- Captured stdout call ----------------------
text going to stdout
----------------------- Captured stderr call ----------------------
text going to stderr
==================== 2 failed in 0.02 seconds =====================
By default each captured log message shows the module, line number, log level
and message. Showing the exact module and line number is useful for testing and
debugging. If desired the log format and date format can be specified to
anything that the logging module supports.
Running pytest specifying formatting options::
pytest --log-format="%(asctime)s %(levelname)s %(message)s" \
--log-date-format="%Y-%m-%d %H:%M:%S"
Shows failed tests like so::
----------------------- Captured stdlog call ----------------------
2010-04-10 14:48:44 INFO text going to logger
----------------------- Captured stdout call ----------------------
text going to stdout
----------------------- Captured stderr call ----------------------
text going to stderr
==================== 2 failed in 0.02 seconds =====================
These options can also be customized through a configuration file:
.. code-block:: ini
[pytest]
log_format = %(asctime)s %(levelname)s %(message)s
log_date_format = %Y-%m-%d %H:%M:%S
Further it is possible to disable reporting logs on failed tests completely
with::
pytest --no-print-logs
Or in you ``pytest.ini``:
.. code-block:: ini
[pytest]
log_print = False
Shows failed tests in the normal manner as no logs were captured::
----------------------- Captured stdout call ----------------------
text going to stdout
----------------------- Captured stderr call ----------------------
text going to stderr
==================== 2 failed in 0.02 seconds =====================
Inside tests it is possible to change the log level for the captured log
messages. This is supported by the ``caplog`` fixture::
def test_foo(caplog):
caplog.set_level(logging.INFO)
pass
By default the level is set on the handler used to catch the log messages,
however as a convenience it is also possible to set the log level of any
logger::
def test_foo(caplog):
caplog.set_level(logging.CRITICAL, logger='root.baz')
pass
It is also possible to use a context manager to temporarily change the log
level::
def test_bar(caplog):
with caplog.at_level(logging.INFO):
pass
Again, by default the level of the handler is affected but the level of any
logger can be changed instead with::
def test_bar(caplog):
with caplog.at_level(logging.CRITICAL, logger='root.baz'):
pass
Lastly all the logs sent to the logger during the test run are made available on
the fixture in the form of both the LogRecord instances and the final log text.
This is useful for when you want to assert on the contents of a message::
def test_baz(caplog):
func_under_test()
for record in caplog.records:
assert record.levelname != 'CRITICAL'
assert 'wally' not in caplog.text
For all the available attributes of the log records see the
``logging.LogRecord`` class.
You can also resort to ``record_tuples`` if all you want to do is to ensure,
that certain messages have been logged under a given logger name with a given
severity and message::
def test_foo(caplog):
logging.getLogger().info('boo %s', 'arg')
assert caplog.record_tuples == [
('root', logging.INFO, 'boo arg'),
]
You can call ``caplog.clear()`` to reset the captured log records in a test::
def test_something_with_clearing_records(caplog):
some_method_that_creates_log_records()
caplog.clear()
your_test_method()
assert ['Foo'] == [rec.message for rec in caplog.records]
Live Logs
^^^^^^^^^
By default, pytest will output any logging records with a level higher or
equal to WARNING. In order to actually see these logs in the console you have to
disable pytest output capture by passing ``-s``.
You can specify the logging level for which log records with equal or higher
level are printed to the console by passing ``--log-cli-level``. This setting
accepts the logging level names as seen in python's documentation or an integer
as the logging level num.
Additionally, you can also specify ``--log-cli-format`` and
``--log-cli-date-format`` which mirror and default to ``--log-format`` and
``--log-date-format`` if not provided, but are applied only to the console
logging handler.
All of the CLI log options can also be set in the configuration INI file. The
option names are:
* ``log_cli_level``
* ``log_cli_format``
* ``log_cli_date_format``
If you need to record the whole test suite logging calls to a file, you can pass
``--log-file=/path/to/log/file``. This log file is opened in write mode which
means that it will be overwritten at each run tests session.
You can also specify the logging level for the log file by passing
``--log-file-level``. This setting accepts the logging level names as seen in
python's documentation(ie, uppercased level names) or an integer as the logging
level num.
Additionally, you can also specify ``--log-file-format`` and
``--log-file-date-format`` which are equal to ``--log-format`` and
``--log-date-format`` but are applied to the log file logging handler.
All of the log file options can also be set in the configuration INI file. The
option names are:
* ``log_file``
* ``log_file_level``
* ``log_file_format``
* ``log_file_date_format``

View File

@@ -53,15 +53,15 @@ tuples so that the ``test_eval`` function will run three times using
them in turn::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
test_expectation.py ..F
test_expectation.py ..F [100%]
======= FAILURES ========
_______ test_eval[6*9-42] ________
================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________
test_input = '6*9', expected = 42
@@ -76,7 +76,7 @@ them in turn::
E + where 54 = eval('6*9')
test_expectation.py:8: AssertionError
======= 1 failed, 2 passed in 0.12 seconds ========
==================== 1 failed, 2 passed in 0.12 seconds ====================
As designed in this example, only one pair of input/output values fails
the simple test function. And as usual with test function arguments,
@@ -102,14 +102,14 @@ for example with the builtin ``mark.xfail``::
Let's run this::
$ pytest
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items
test_expectation.py ..x
test_expectation.py ..x [100%]
======= 2 passed, 1 xfailed in 0.12 seconds ========
=================== 2 passed, 1 xfailed in 0.12 seconds ====================
The one parameter set which caused a failure previously now
shows up as an "xfailed (expected to fail)" test.
@@ -123,8 +123,8 @@ To get all combinations of multiple parametrized arguments you can stack
def test_foo(x, y):
pass
This will run the test with the arguments set to ``x=0/y=2``, ``x=0/y=3``, ``x=1/y=2`` and
``x=1/y=3``.
This will run the test with the arguments set to ``x=0/y=2``,``x=1/y=2``,
``x=0/y=3``, and ``x=1/y=3`` exhausting parameters in the order of the decorators.
.. _`pytest_generate_tests`:
@@ -165,15 +165,15 @@ command line option and the parametrization of our test function::
If we now pass two stringinput values, our test will run twice::
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
..
.. [100%]
2 passed in 0.12 seconds
Let's also run with a stringinput that will lead to a failing test::
$ pytest -q --stringinput="!" test_strings.py
F
======= FAILURES ========
_______ test_valid_string[!] ________
F [100%]
================================= FAILURES =================================
___________________________ test_valid_string[!] ___________________________
stringinput = '!'
@@ -193,14 +193,17 @@ If you don't specify a stringinput it will be skipped because
list::
$ pytest -q -rs test_strings.py
s
======= short test summary info ========
SKIP [1] test_strings.py:2: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
s [100%]
========================= short test summary info ==========================
SKIP [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
1 skipped in 0.12 seconds
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
those sets cannot be duplicated, otherwise an error will be raised.
More examples
-------------
For further examples, you might want to look at :ref:`more
parametrization examples <paramexamples>`.

View File

@@ -27,9 +27,6 @@ Here is a little annotated list for some popular plugins:
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
processing deferreds from test functions.
* `pytest-catchlog <http://pypi.python.org/pypi/pytest-catchlog>`_:
to capture and assert about messages from the logging module
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
coverage reporting, compatible with distributed testing
@@ -94,7 +91,7 @@ environment you can type::
and will get an extended test header which shows activated plugins
and their names. It will also print local plugins aka
:ref:`conftest.py <conftest>` files when they are loaded.
:ref:`conftest.py <conftest.py plugins>` files when they are loaded.
.. _`cmdunregister`:
@@ -155,4 +152,3 @@ in the `pytest repository <https://github.com/pytest-dev/pytest>`_.
_pytest.terminal
_pytest.tmpdir
_pytest.unittest

View File

@@ -58,7 +58,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
* `pyudev <https://pyudev.readthedocs.io/en/latest/tests/plugins.html>`_ a pure Python binding to the Linux library libudev
* `pytest-localserver <https://bitbucket.org/basti/pytest-localserver/>`_ a plugin for pytest that provides an httpserver and smtpserver
* `pytest-localserver <https://bitbucket.org/pytest-dev/pytest-localserver/>`_ a plugin for pytest that provides an httpserver and smtpserver
* `pytest-monkeyplus <http://pypi.python.org/pypi/pytest-monkeyplus/>`_ a plugin that extends monkeypatch
These projects help integrate ``pytest`` into other Python frameworks:

View File

@@ -1,8 +1,13 @@
:orphan:
=========================
Parametrize with fixtures
=========================
===================================
PROPOSAL: Parametrize with fixtures
===================================
.. warning::
This document outlines a proposal around using fixtures as input
of parametrized tests or fixtures.
Problem
-------
@@ -108,8 +113,13 @@ the following values.
Alternative approach
--------------------
A new helper function named ``fixture_request`` tells pytest to yield all
parameters of a fixture.
A new helper function named ``fixture_request`` would tell pytest to yield
all parameters marked as a fixture.
.. note::
The `pytest-lazy-fixture <https://pypi.python.org/pypi/pytest-lazy-fixture>`_ plugin implements a very
similar solution to the proposal below, make sure to check it out.
.. code-block:: python

View File

@@ -68,4 +68,9 @@ imported in the global import namespace.
This is also discussed in details in :ref:`test discovery`.
Invoking ``pytest`` versus ``python -m pytest``
-----------------------------------------------
Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly
equivalent behaviour, except that the former call will add the current directory to ``sys.path``.
See also :ref:`cmdline`.

View File

@@ -3,7 +3,7 @@
.. _skipping:
Skip and xfail: dealing with tests that cannot succeed
=====================================================================
======================================================
You can mark test functions that cannot be run on certain platforms
or that you expect to fail so pytest can deal with them accordingly and
@@ -16,13 +16,17 @@ resource which is not available at the moment (for example a database).
A **xfail** means that you expect a test to fail for some reason.
A common example is a test for a feature not yet implemented, or a bug not yet fixed.
When a test passes despite being expected to fail (marked with ``pytest.mark.xfail``),
it's an **xpass** and will be reported in the test summary.
``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed
information about skipped/xfailed tests is not shown by default to avoid
cluttering the output. You can use the ``-r`` option to see details
corresponding to the "short" letters shown in the test progress::
pytest -rxs # show extra info on skips and xfails
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
More details on the ``-r`` option can be found by running ``pytest -h``.
(See :ref:`how to change command line options defaults`)
@@ -54,6 +58,16 @@ by calling the ``pytest.skip(reason)`` function:
if not valid_config():
pytest.skip("unsupported configuration")
It is also possible to skip the whole module using
``pytest.skip(reason, allow_module_level=True)`` at the module level:
.. code-block:: python
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
The imperative method is useful when it is not possible to evaluate the skip condition
during import time.
@@ -64,11 +78,11 @@ during import time.
If you wish to skip something conditionally then you can use ``skipif`` instead.
Here is an example of marking a test function to be skipped
when run on a Python3.3 interpreter::
when run on a Python3.6 interpreter::
import sys
@pytest.mark.skipif(sys.version_info < (3,3),
reason="requires python3.3")
@pytest.mark.skipif(sys.version_info < (3,6),
reason="requires python3.6")
def test_function():
...
@@ -138,6 +152,16 @@ will be skipped if any of the skip conditions is true.
.. _`whole class- or module level`: mark.html#scoped-marking
Skipping files or directories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes you may need to skip an entire file or directory, for example if the
tests rely on Python version-specific features or contain code that you do not
wish pytest to run. In this case, you must exclude the files and directories
from collection. Refer to :ref:`customizing-test-collection` for more
information.
Skipping on a missing import dependency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -250,8 +274,8 @@ You can change the default value of the ``strict`` parameter using the
As with skipif_ you can also mark your expectation of a failure
on a particular platform::
@pytest.mark.xfail(sys.version_info >= (3,3),
reason="python3.3 api changes")
@pytest.mark.xfail(sys.version_info >= (3,6),
reason="python3.6 api changes")
def test_function():
...
@@ -307,13 +331,13 @@ Here is a simple test file with the several usages:
Running it with the report-on-xfail option gives this output::
example $ pytest -rx xfail_demo.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/example, inifile:
collected 7 items
xfail_demo.py xxxxxxx
======= short test summary info ========
xfail_demo.py xxxxxxx [100%]
========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN]
@@ -327,7 +351,7 @@ Running it with the report-on-xfail option gives this output::
reason: reason
XFAIL xfail_demo.py::test_hello7
======= 7 xfailed in 0.12 seconds ========
======================== 7 xfailed in 0.12 seconds =========================
.. _`skip/xfail with parametrize`:

View File

@@ -28,15 +28,15 @@ Running this would result in a passed test except for the last
``assert 0`` line which we use to look at values::
$ pytest test_tmpdir.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_tmpdir.py F
test_tmpdir.py F [100%]
======= FAILURES ========
_______ test_create_file ________
================================= FAILURES =================================
_____________________________ test_create_file _____________________________
tmpdir = local('PYTEST_TMPDIR/test_create_file0')
@@ -49,7 +49,7 @@ Running this would result in a passed test except for the last
E assert 0
test_tmpdir.py:7: AssertionError
======= 1 failed in 0.12 seconds ========
========================= 1 failed in 0.12 seconds =========================
The 'tmpdir_factory' fixture
----------------------------

View File

@@ -126,15 +126,15 @@ Due to the deliberately failing assert statements, we can take a look at
the ``self.db`` values in the traceback::
$ pytest test_unittest_db.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_unittest_db.py FF
test_unittest_db.py FF [100%]
======= FAILURES ========
_______ MyTest.test_method1 ________
================================= FAILURES =================================
___________________________ MyTest.test_method1 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method1>
@@ -145,7 +145,7 @@ the ``self.db`` values in the traceback::
E assert 0
test_unittest_db.py:9: AssertionError
_______ MyTest.test_method2 ________
___________________________ MyTest.test_method2 ____________________________
self = <test_unittest_db.MyTest testMethod=test_method2>
@@ -155,7 +155,7 @@ the ``self.db`` values in the traceback::
E assert 0
test_unittest_db.py:12: AssertionError
======= 2 failed in 0.12 seconds ========
========================= 2 failed in 0.12 seconds =========================
This default pytest traceback shows that the two test methods
share the same ``self.db`` instance which was our intention
@@ -203,7 +203,7 @@ on the class like in the previous example.
Running this test module ...::
$ pytest -q test_unittest_cleandir.py
.
. [100%]
1 passed in 0.12 seconds
... gives us one passed test because the ``initdir`` fixture function
@@ -233,3 +233,13 @@ was executed ahead of the ``test_method``.
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
to overwrite ``debug`` in the same way (this is also true for standard
unittest).
.. note::
Due to architectural differences between the two frameworks, setup and
teardown for ``unittest``-based tests is performed during the ``call`` phase
of testing instead of in ``pytest``'s standard ``setup`` and ``teardown``
stages. This can be important to understand in some situations, particularly
when reasoning about errors. For example, if a ``unittest``-based suite
exhibits errors during setup, ``pytest`` will report no errors during its
``setup`` phase and will instead raise the error during ``call``.

View File

@@ -17,7 +17,7 @@ You can invoke testing through the Python interpreter from the command line::
python -m pytest [...]
This is almost equivalent to invoking the command line script ``pytest [...]``
directly, except that Python will also add the current directory to ``sys.path``.
directly, except that calling via ``python`` will also add the current directory to ``sys.path``.
Possible exit codes
--------------------------------------------------------------
@@ -189,7 +189,6 @@ in your code and pytest automatically disables its output capture for that test:
for test output occurring after you exit the interactive PDB_ tracing session
and continue with the regular test run.
.. _durations:
Profiling test execution duration

View File

@@ -21,20 +21,20 @@ and displays them at the end of the session::
Running pytest now produces this output::
$ pytest test_show_warnings.py
======= test session starts ========
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item
test_show_warnings.py .
test_show_warnings.py . [100%]
======= warnings summary ========
============================= warnings summary =============================
test_show_warnings.py::test_one
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
warnings.warn(UserWarning("api v1, should use functions from v2"))
-- Docs: http://doc.pytest.org/en/latest/warnings.html
======= 1 passed, 1 warnings in 0.12 seconds ========
=================== 1 passed, 1 warnings in 0.12 seconds ===================
Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
@@ -42,9 +42,9 @@ The ``-W`` flag can be passed to control which warnings will be displayed or eve
them into errors::
$ pytest -q test_show_warnings.py -W error::UserWarning
F
======= FAILURES ========
_______ test_one ________
F [100%]
================================= FAILURES =================================
_________________________________ test_one _________________________________
def test_one():
> assert api_v1() == 1
@@ -109,7 +109,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
.. code-block:: python
# turns all warnings into errors for this module
pytestmark = @pytest.mark.filterwarnings('error')
pytestmark = pytest.mark.filterwarnings('error')
.. note::
@@ -168,7 +168,20 @@ which works in a similar manner to :ref:`raises <assertraises>`::
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
The test will fail if the warning in question is not raised.
The test will fail if the warning in question is not raised. 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...
You can also call ``pytest.warns`` on a function or code string::

View File

@@ -57,9 +57,7 @@ Plugin discovery order at tool startup
.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
.. _`conftest.py plugins`:
.. _`conftest.py`:
.. _`localplugin`:
.. _`conftest`:
.. _`local conftest plugins`:
conftest.py: local per-directory plugins
@@ -186,16 +184,16 @@ statements and the detailed introspection of expressions upon
assertion failures. This is provided by "assertion rewriting" which
modifies the parsed AST before it gets compiled to bytecode. This is
done via a :pep:`302` import hook which gets installed early on when
``pytest`` starts up and will perform this re-writing when modules get
``pytest`` starts up and will perform this rewriting when modules get
imported. However since we do not want to test different bytecode
then you will run in production this hook only re-writes test modules
then you will run in production this hook only rewrites test modules
themselves as well as any modules which are part of plugins. Any
other imported module will not be re-written and normal assertion
other imported module will not be rewritten and normal assertion
behaviour will happen.
If you have assertion helpers in other modules where you would need
assertion rewriting to be enabled you need to ask ``pytest``
explicitly to re-write this module before it gets imported.
explicitly to rewrite this module before it gets imported.
.. autofunction:: pytest.register_assert_rewrite
@@ -218,10 +216,10 @@ With the following typical ``setup.py`` extract:
...
)
In this case only ``pytest_foo/plugin.py`` will be re-written. If the
In this case only ``pytest_foo/plugin.py`` will be rewritten. If the
helper module also contains assert statements which need to be
re-written it needs to be marked as such, before it gets imported.
This is easiest by marking it for re-writing inside the
rewritten it needs to be marked as such, before it gets imported.
This is easiest by marking it for rewriting inside the
``__init__.py`` module, which will always be imported first when a
module inside a package is imported. This way ``plugin.py`` can still
import ``helper.py`` normally. The contents of
@@ -265,7 +263,7 @@ for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
However for this to have any effect the module must not be
imported already; if it was already imported at the time the
``pytest_plugins`` statement is processed, a warning will result and
assertions inside the plugin will not be re-written. To fix this you
assertions inside the plugin will not be rewritten. To fix this you
can either call :func:`pytest.register_assert_rewrite` yourself before
the module is imported, or you can arrange the code to delay the
importing until after the plugin is registered.
@@ -454,7 +452,7 @@ hook wrappers and passes the same arguments as to the regular hooks.
At the yield point of the hook wrapper pytest will execute the next hook
implementations and return their result to the yield point in the form of
a :py:class:`CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` instance which encapsulates a result or
a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
exception info. The yield point itself will thus typically not raise
exceptions (unless there are bugs).
@@ -519,7 +517,7 @@ Here is the order of execution:
Plugin1).
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
point. The yield receives a :py:class:`CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` instance which encapsulates
point. The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
the result from calling the non-wrappers. Wrappers shall not modify the result.
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
@@ -618,6 +616,7 @@ Collection hooks
``pytest`` calls the following hooks for collecting files and directories:
.. autofunction:: pytest_collection
.. autofunction:: pytest_ignore_collect
.. autofunction:: pytest_collect_directory
.. autofunction:: pytest_collect_file
@@ -689,6 +688,14 @@ Reference of objects involved in hooks
:members:
:show-inheritance:
.. autoclass:: _pytest.main.FSCollector()
:members:
:show-inheritance:
.. autoclass:: _pytest.main.Session()
:members:
:show-inheritance:
.. autoclass:: _pytest.main.Item()
:members:
:show-inheritance:
@@ -716,7 +723,7 @@ Reference of objects involved in hooks
:members:
:inherited-members:
.. autoclass:: _pytest.vendored_packages.pluggy._CallOutcome()
.. autoclass:: pluggy._Result
:members:
.. autofunction:: _pytest.config.get_plugin_manager()
@@ -726,7 +733,7 @@ Reference of objects involved in hooks
:undoc-members:
:show-inheritance:
.. autoclass:: _pytest.vendored_packages.pluggy.PluginManager()
.. autoclass:: pluggy.PluginManager()
:members:
.. currentmodule:: _pytest.pytester

View File

@@ -7,7 +7,7 @@ pytest: unit and functional testing with Python.
# else we are imported
from _pytest.config import (
main, UsageError, _preloadplugins, cmdline,
main, UsageError, cmdline,
hookspec, hookimpl
)
from _pytest.fixtures import fixture, yield_fixture
@@ -74,5 +74,4 @@ if __name__ == '__main__':
else:
from _pytest.compat import _setup_collect_fakemodule
_preloadplugins() # to populate pytest.* namespace so help(pytest) works
_setup_collect_fakemodule()

View File

@@ -16,7 +16,7 @@ classifiers = [
'Topic :: Utilities',
] + [
('Programming Language :: Python :: %s' % x)
for x in '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()
for x in '2 2.7 3 3.4 3.5 3.6'.split()
]
with open('README.rst') as fd:
@@ -43,17 +43,25 @@ def has_environment_marker_support():
def main():
install_requires = ['py>=1.4.33', 'setuptools'] # pluggy is vendored in _pytest.vendored_packages
extras_require = {}
install_requires = [
'py>=1.5.0',
'six>=1.10.0',
'setuptools',
'attrs>=17.2.0',
]
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
# used by tox.ini to test with pluggy master
if '_PYTEST_SETUP_SKIP_PLUGGY_DEP' not in os.environ:
install_requires.append('pluggy>=0.5,<0.7')
if has_environment_marker_support():
extras_require[':python_version=="2.6"'] = ['argparse', 'ordereddict']
extras_require[':python_version<"3.0"'] = ['funcsigs']
extras_require[':sys_platform=="win32"'] = ['colorama']
else:
if sys.version_info < (2, 7):
install_requires.append('argparse')
install_requires.append('ordereddict')
if sys.platform == 'win32':
install_requires.append('colorama')
if sys.version_info < (3, 0):
install_requires.append('funcsigs')
setup(
name='pytest',
@@ -65,17 +73,20 @@ def main():
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
author='Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others',
entry_points={'console_scripts':
['pytest=pytest:main', 'py.test=pytest:main']},
author=(
'Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, '
'Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others'),
entry_points={'console_scripts': [
'pytest=pytest:main', 'py.test=pytest:main']},
classifiers=classifiers,
keywords="test unittest",
cmdclass={'test': PyTest},
# the following should be enabled for release
setup_requires=['setuptools-scm'],
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
install_requires=install_requires,
extras_require=extras_require,
packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'],
packages=['_pytest', '_pytest.assertion', '_pytest._code'],
py_modules=['pytest'],
zip_safe=False,
)
@@ -83,10 +94,13 @@ def main():
class PyTest(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import subprocess
PPATH = [x for x in os.environ.get('PYTHONPATH', '').split(':') if x]

View File

@@ -4,10 +4,9 @@ Invoke tasks to help with pytest development and release process.
import invoke
from . import generate, vendoring
from . import generate
ns = invoke.Collection(
generate,
vendoring
)

View File

@@ -151,7 +151,7 @@ def publish_release(ctx, version, user, pypi_name):
@invoke.task(help={
'version': 'version being released',
'write_out': 'write changes to the actial changelog'
'write_out': 'write changes to the actual changelog'
})
def changelog(ctx, version, write_out=False):
if write_out:

View File

@@ -1,23 +0,0 @@
from __future__ import absolute_import, print_function
import py
import invoke
VENDOR_TARGET = py.path.local("_pytest/vendored_packages")
GOOD_FILES = 'README.md', '__init__.py'
@invoke.task()
def remove_libs(ctx):
print("removing vendored libs")
for path in VENDOR_TARGET.listdir():
if path.basename not in GOOD_FILES:
print(" ", path)
path.remove()
@invoke.task(pre=[remove_libs])
def update_libs(ctx):
print("installing libs")
ctx.run("pip install -t {target} pluggy".format(target=VENDOR_TARGET))
ctx.run("git add {target}".format(target=VENDOR_TARGET))
print("Please commit to finish the update after running the tests:")
print()
print(' git commit -am "Updated vendored libs"')

View File

@@ -3,6 +3,8 @@ from __future__ import absolute_import, division, print_function
import os
import sys
import six
import _pytest._code
import py
import pytest
@@ -215,8 +217,8 @@ class TestGeneralUsage(object):
assert not result.ret
def test_issue109_sibling_conftests_not_loaded(self, testdir):
sub1 = testdir.tmpdir.mkdir("sub1")
sub2 = testdir.tmpdir.mkdir("sub2")
sub1 = testdir.mkdir("sub1")
sub2 = testdir.mkdir("sub2")
sub1.join("conftest.py").write("assert 0")
result = testdir.runpytest(sub2)
assert result.ret == EXIT_NOTESTSCOLLECTED
@@ -344,7 +346,7 @@ class TestGeneralUsage(object):
Importing a module that didn't exist, even if the ImportError was
gracefully handled, would make our test crash.
Use recwarn here to silence this warning in Python 2.6 and 2.7:
Use recwarn here to silence this warning in Python 2.7:
ImportWarning: Not importing directory '...\not_a_package': missing __init__.py
"""
testdir.mkdir('not_a_package')
@@ -415,17 +417,17 @@ class TestGeneralUsage(object):
])
def test_parametrized_with_null_bytes(self, testdir):
"""Test parametrization with values that contain null bytes and unicode characters (#2644)"""
"""Test parametrization with values that contain null bytes and unicode characters (#2644, #2957)"""
p = testdir.makepyfile(u"""
# encoding: UTF-8
import pytest
@pytest.mark.parametrize("data", ["\\x00", u'ação'])
@pytest.mark.parametrize("data", [b"\\x00", "\\x00", u'ação'])
def test_foo(data):
assert data
""")
res = testdir.runpytest(p)
res.assert_outcomes(passed=2)
res.assert_outcomes(passed=3)
class TestInvocationVariants(object):
@@ -603,11 +605,11 @@ class TestInvocationVariants(object):
# The structure of the test directory is now:
# .
# ├── hello
# │   └── ns_pkg
# │   ├── __init__.py
# │   └── hello
# │   ├── __init__.py
# │   └── test_hello.py
# │ └── ns_pkg
# │ ├── __init__.py
# │ └── hello
# │ ├── __init__.py
# │ └── test_hello.py
# └── world
# └── ns_pkg
# ├── __init__.py
@@ -625,24 +627,96 @@ class TestInvocationVariants(object):
monkeypatch.syspath_prepend(p)
# mixed module and filenames:
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "world/ns_pkg")
os.chdir('world')
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*test_hello.py::test_hello*PASSED",
"*test_hello.py::test_other*PASSED",
"*test_world.py::test_world*PASSED",
"*test_world.py::test_other*PASSED",
"*test_hello.py::test_hello*PASSED*",
"*test_hello.py::test_other*PASSED*",
"*test_world.py::test_world*PASSED*",
"*test_world.py::test_other*PASSED*",
"*4 passed*"
])
# specify tests within a module
testdir.chdir()
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*test_world.py::test_other*PASSED",
"*test_world.py::test_other*PASSED*",
"*1 passed*"
])
@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks")
def test_cmdline_python_package_symlink(self, testdir, monkeypatch):
"""
test --pyargs option with packages with path containing symlink can
have conftest.py in their package (#2985)
"""
# dummy check that we can actually create symlinks: on Windows `os.symlink` is available,
# but normal users require special admin privileges to create symlinks.
if sys.platform == 'win32':
try:
os.symlink(str(testdir.tmpdir.ensure('tmpfile')), str(testdir.tmpdir.join('tmpfile2')))
except OSError as e:
pytest.skip(six.text_type(e.args[0]))
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False)
search_path = ["lib", os.path.join("local", "lib")]
dirname = "lib"
d = testdir.mkdir(dirname)
foo = d.mkdir("foo")
foo.ensure("__init__.py")
lib = foo.mkdir('bar')
lib.ensure("__init__.py")
lib.join("test_bar.py"). \
write("def test_bar(): pass\n"
"def test_other(a_fixture):pass")
lib.join("conftest.py"). \
write("import pytest\n"
"@pytest.fixture\n"
"def a_fixture():pass")
d_local = testdir.mkdir("local")
symlink_location = os.path.join(str(d_local), "lib")
if six.PY2:
os.symlink(str(d), symlink_location)
else:
os.symlink(str(d), symlink_location, target_is_directory=True)
# The structure of the test directory is now:
# .
# ├── local
# │ └── lib -> ../lib
# └── lib
# └── foo
# ├── __init__.py
# └── bar
# ├── __init__.py
# ├── conftest.py
# └── test_bar.py
def join_pythonpath(*dirs):
cur = os.getenv('PYTHONPATH')
if cur:
dirs += (cur,)
return os.pathsep.join(str(p) for p in dirs)
monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path))
for p in search_path:
monkeypatch.syspath_prepend(p)
# module picked up in symlink-ed directory:
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
testdir.chdir()
assert result.ret == 0
result.stdout.fnmatch_lines([
"*lib/foo/bar/test_bar.py::test_bar*PASSED*",
"*lib/foo/bar/test_bar.py::test_other*PASSED*",
"*2 passed*"
])
def test_cmdline_python_package_not_exists(self, testdir):
result = testdir.runpytest("--pyargs", "tpkgwhatv")
assert result.ret
@@ -846,3 +920,46 @@ def test_deferred_hook_checking(testdir):
})
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 passed *'])
def test_fixture_values_leak(testdir):
"""Ensure that fixture objects are properly destroyed by the garbage collector at the end of their expected
life-times (#2981).
"""
testdir.makepyfile("""
import attr
import gc
import pytest
import weakref
@attr.s
class SomeObj(object):
name = attr.ib()
fix_of_test1_ref = None
session_ref = None
@pytest.fixture(scope='session')
def session_fix():
global session_ref
obj = SomeObj(name='session-fixture')
session_ref = weakref.ref(obj)
return obj
@pytest.fixture
def fix(session_fix):
global fix_of_test1_ref
obj = SomeObj(name='local-fixture')
fix_of_test1_ref = weakref.ref(obj)
return obj
def test1(fix):
assert fix_of_test1_ref() is fix
def test2():
gc.collect()
# fixture "fix" created during test1 must have been destroyed by now
assert fix_of_test1_ref() is None
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 2 passed *'])

View File

@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import sys
import operator
import _pytest
import py
@@ -77,8 +76,8 @@ def test_excinfo_getstatement():
linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
_pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ]
l = list(excinfo.traceback)
foundlinenumbers = [x.lineno for x in l]
values = list(excinfo.traceback)
foundlinenumbers = [x.lineno for x in values]
assert foundlinenumbers == linenumbers
# for x in info:
# print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement)
@@ -244,7 +243,7 @@ class TestTraceback_f_g_h(object):
def f(n):
try:
do_stuff()
except:
except: # noqa
reraise_me()
excinfo = pytest.raises(RuntimeError, f, 8)
@@ -345,10 +344,7 @@ def test_excinfo_no_sourcecode():
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
s = str(excinfo.traceback[-1])
if py.std.sys.version_info < (2, 5):
assert s == " File '<string>':1 in ?\n ???\n"
else:
assert s == " File '<string>':1 in <module>\n ???\n"
assert s == " File '<string>':1 in <module>\n ???\n"
def test_excinfo_no_python_sourcecode(tmpdir):
@@ -434,7 +430,7 @@ class TestFormattedExcinfo(object):
exec(source.compile())
except KeyboardInterrupt:
raise
except:
except: # noqa
return _pytest._code.ExceptionInfo()
assert 0, "did not raise"
@@ -546,7 +542,7 @@ raise ValueError()
tb = FakeRawTB()
excinfo.traceback = Traceback(tb)
fail = IOError() # noqa
fail = IOError()
repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[0].lines[0] == "> ???"
if py.std.sys.version_info[0] >= 3:
@@ -1217,7 +1213,7 @@ def test_exception_repr_extraction_error_on_recursion():
try:
a(numpy_like())
except:
except: # noqa
from _pytest._code.code import ExceptionInfo
from _pytest.pytester import LineMatcher
exc_info = ExceptionInfo()
@@ -1241,12 +1237,9 @@ def test_no_recursion_index_on_recursion_error():
return getattr(self, '_' + attr)
RecursionDepthError().trigger
except:
except: # noqa
from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo()
if sys.version_info[:2] == (2, 6):
assert "'RecursionDepthError' object has no attribute '___" in str(exc_info.getrepr())
else:
assert 'maximum recursion' in str(exc_info.getrepr())
assert 'maximum recursion' in str(exc_info.getrepr())
else:
assert 0

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