Compare commits

...

666 Commits
2.7.2 ... 2.8.3

Author SHA1 Message Date
Ronny Pfannschmidt
e4a000bb00 refer more explicit to osx el cap in the changelogs 2015-11-18 17:57:41 +01:00
Ronny Pfannschmidt
d04505553e update authors list for 2.8.3 and add a log command to the howto 2015-11-18 17:55:18 +01:00
Ronny Pfannschmidt
2b5c46b2ab correct drop in version in announcement 2015-11-18 17:48:35 +01:00
Ronny Pfannschmidt
063f90f0d2 run regendoc 2015-11-18 17:47:17 +01:00
Ronny Pfannschmidt
6096cae3dd release announcements for 2.8.3 2015-11-18 17:38:31 +01:00
Ronny Pfannschmidt
3dc57d99f2 Changelog groming and release preps for 2.8.3 2015-11-18 17:34:30 +01:00
Florian Bruhin
3cc5a4ecb0 Merge pull request #1175 from gabehollombe/add-pyconsg2015-talk
Add 'Improve your testing with Pytest and Mock' video from PyCon SG 2015
2015-11-11 06:16:46 +01:00
Gabe Hollombe
ac0b7b9803 Add 'Improve your testing with Pytest and Mock' video from PyCon SG 2015 2015-11-11 11:28:59 +08:00
Bruno Oliveira
a1d226b751 Merge pull request #1168 from hackebrot/write-docs-on-ignore-cli-option
Write docs on ignore cli option
2015-11-07 12:38:21 -02:00
Raphael Pierzina
d667259e31 Change pytest ignore invocation as suggested by @nicoddemus 2015-11-04 23:45:29 +01:00
Bruno Oliveira
98668c943d Merge pull request #1171 from nicoddemus/fix-1169
Allow @unittest.skip decorators in Python 2.7
2015-11-04 20:27:00 -02:00
Bruno Oliveira
a7a470b56f Give credit where it is due 2015-11-04 19:55:05 -02:00
Bruno Oliveira
7d6edb9ca5 Fix unittest.skip decorator test and separate the fix into a different, self-doc function 2015-11-04 19:54:18 -02:00
Lee Kamentsky
313050b15b Suggested edits by Bruno.
Moved fix to TestCaseFunction.setup. Added myself to AUTHORS and added entry to CHANGELOG
2015-11-04 15:30:16 -05:00
Lee Kamentsky
1833547936 Added test for issue #1169
(I undid my fix, checked for failure, redid my fix and it passes)
2015-11-04 14:24:22 -05:00
LeeKamentsky
8de2c035e2 Fixes #1169
The unittest.skip decorator runs @functools.wraps on self._testcase. functools.wraps expects a "__name__" attribute and this patch adds one. It might not be the correct fix, but it works for me.
2015-11-04 13:52:40 -05:00
Raphael Pierzina
a2d07bfa93 Add pytest invocation along with result 2015-11-04 15:34:18 +01:00
Raphael Pierzina
586fdbcbbd Add example tests directory structure 2015-11-04 15:00:37 +01:00
Raphael Pierzina
04079f9ae5 Start doc section on `--ignore` in pytestcollection.rst 2015-11-04 14:49:02 +01:00
Bruno Oliveira
af2d391903 Merge pull request #1166 from Toilal/pytest-runner
Use pytest-runner for setuptools integration
2015-11-02 12:50:21 -02:00
Toilal
8abf85e96c Use pytest-runner for setuptools integration 2015-11-02 12:22:40 +01:00
Ronny Pfannschmidt
9013cb1b4c Merge pull request #1144 from The-Compiler/elcapitan-sip
Handle EPERM when writing rewritten bytecode.
2015-11-01 08:20:00 +01:00
Florian Bruhin
2eb345f9b7 Merge pull request #1159 from zoidbergwill/patch-1
Fix typo in parametrize.rst
2015-10-30 09:02:29 +01:00
William Martin Stewart
1376d75fbe Fix typo in parametrize.rst 2015-10-30 09:58:41 +02:00
Bruno Oliveira
4fe7cca44e Merge pull request #1157 from gabrielcnr/master
Color highlight on the name of the failed tests
2015-10-28 17:36:47 -02:00
gabriel.reis
a83d5c0f08 Updated entries on CHANGELOG and AUTHORS. Ref: #1157 2015-10-28 19:35:04 +00:00
Gabriel Reis
f3f61fb5d1 Color highlight on the name of the failed tests 2015-10-28 08:12:57 +00:00
Bruno Oliveira
8e54d07c40 Fix link in FAQ about parametrizing tests
Fix #1154
2015-10-26 22:46:35 -02:00
Florian Bruhin
bd36e00174 Merge pull request #1153 from b-jazz/patch-2
Update usage.rst
2015-10-24 15:41:24 +02:00
Ronny Pfannschmidt
da6b1557f0 Merge pull request #1152 from b-jazz/patch-1
Update CONTRIBUTING.rst
2015-10-24 12:00:33 +02:00
b-jazz
f174026f53 Update usage.rst
Remove double "the" in usage comments
2015-10-24 02:36:02 -07:00
b-jazz
390e1c93ed Update CONTRIBUTING.rst
Clean up the grammar of one of the lines.
2015-10-24 02:21:22 -07:00
Bruno Oliveira
8f31a1a64b Merge pull request #1147 from jayvdb/remove-extrainit
Remove commented out extrainit
2015-10-20 10:25:54 -02:00
John Vandenberg
9a04879129 Remove commented out extrainit
Unused since 2012, and the docstring syntax is incorrect with
four quotes.
2015-10-20 21:08:21 +11:00
Bruno Oliveira
7469b5591e Update plugins_index.rst 2015-10-17 13:37:38 -03:00
Bruno Oliveira
75932a92d0 Make pytest-version mandatory
Fix #1118
2015-10-17 13:37:01 -03:00
Bruno Oliveira
cf56f59f58 Merge pull request #1145 from htgoebel/patch-1
Add link to unittest2pytest
2015-10-17 13:16:55 -03:00
Hartmut Goebel
4fba20e544 Add link to unittest2pytest
Closes #991.
2015-10-17 18:14:52 +02:00
Florian Bruhin
2956627b8b Handle EPERM when writing rewritten bytecode.
Fixes #1143.
2015-10-17 11:55:55 +02:00
Ronny Pfannschmidt
3404d2a99b Merge pull request #1142 from nicoddemus/issue-1133-str-relto
Fix situation where a traceback entry "path" returns a str object
2015-10-17 08:58:48 +02:00
Bruno Oliveira
311b0a9683 Fix situation where a traceback entry "path" returns a str object
Fix #1133
2015-10-16 20:18:14 -03:00
Ronny Pfannschmidt
3b11995dbe Merge pull request #1053 from mbirtwell/issue_331_failure_for_test_with_no_name
Prevent non-function callables from being collected
2015-10-13 23:35:32 +02:00
Michael Birtwell
a3bda59a30 collection: Prevent non-function callables from being collected
Fixes issue 331
previously to this change the collection code would issue a warning for
when ever it encountered a variable that looked like a test but wasn't a
function saying that it wouldn't collect it because it wasn't a function.
This fixes the logic so that if that warning is issued it really isn't
collected.
However previously special cases existed to support tests that were
created using functools.wraps and functools.partial. So the condition for
issuing that warning has been updated to take that in to account

Also try the old way of detecting functions just for proper integration
with mock.path in python 2.7 the get_real_func returned the unbound method
2015-10-12 22:23:18 +01:00
Bruno Oliveira
0c21533cc5 Merge pull request #1130 from The-Compiler/pytest_doc_fix
Use a string for pytest_plugins in docs.
2015-10-12 10:58:39 -03:00
Florian Bruhin
dadf03baea Use a string for pytest_plugins in docs. 2015-10-12 06:57:43 +02:00
Ronny Pfannschmidt
1525cc78f6 Merge pull request #1127 from michael-k/indentation
[doc] Fixed indentation
2015-10-10 14:22:18 +02:00
Ronny Pfannschmidt
db077555f6 Merge pull request #1102 from nicoddemus/doctest-fixtures-fix
Fix autouse fixtures and doctest modules
2015-10-10 14:21:53 +02:00
Michael K
37e96c9335 [doc] Fixed indentation 2015-10-10 13:29:22 +02:00
Bruno Oliveira
b052becba9 Merge pull request #1121 from tomviner/issue1035-getattr
Issue1035 getattr
2015-10-09 16:45:52 -03:00
TomV
88c8dd96f9 issue1035 Override inspect.isclass for python 2.6 2015-10-09 19:44:47 +01:00
TomV
707226298a issue1035 add test for classes setting __getattr__ 2015-10-08 09:08:32 +01:00
Florian Bruhin
1f6988bdec Add empty 2.8.3.dev section to CHANGELOG. 2015-10-07 22:47:22 +02:00
Florian Bruhin
ec74a8deb8 Fix contents.rst.
This was broken in 787c866191.
2015-10-07 18:10:13 +02:00
Florian Bruhin
b5b53b6aec start 2.8.3 development 2015-10-07 17:56:05 +02:00
Florian Bruhin
7c529e0afe Merge branch '2.8.2-release' 2015-10-07 17:51:34 +02:00
Florian Bruhin
b955473533 Add 2.8.2 release annoucement. 2015-10-06 22:37:59 +02:00
Florian Bruhin
2026ce0ed2 Update plugin_index. 2015-10-06 22:35:16 +02:00
Florian Bruhin
42937d4bb6 Prepare 2.8.2 release. 2015-10-06 21:06:25 +02:00
Bruno Oliveira
cfd259ae6f Merge pull request #1113 from demianbrecht/typo_fix
typo fix, changed position of context manager notes
2015-10-05 17:21:59 -03:00
Demian Brecht
493530ec6d typo fix, changed position of context manager notes 2015-10-05 12:13:25 -07:00
Florian Bruhin
4dea0892cb Merge pull request #1108 from nicoddemus/plugins_index
plugins_index was still generating ".txt" files, so it was effectively being ignored
2015-10-05 09:37:31 -07:00
Bruno Oliveira
a14c77aeba Fix problems when mixing autouse fixtures and doctest modules
The main problem was that previously DoctestModule was setting
up its fixtures during collection, instead of letting
each DoctestItem make its own fixture setup

Fix #1100
Fix #1057
2015-10-03 16:07:14 -03:00
Bruno Oliveira
9deab6c0fd Add a reminder on HOWTORELEASE to update plugins_index 2015-10-02 19:44:00 -03:00
Bruno Oliveira
f9e9413f52 plugins_index was still generating ".txt" files, so it was effectively being ignored
Fix #1105
2015-10-02 19:21:55 -03:00
Bruno Oliveira
5171d167ce Move "note" to the end of the main text
Fix #1097
2015-10-02 18:14:17 -03:00
Demian Brecht
2981bece55 pyflakes fix 2015-10-02 13:03:43 -07:00
Demian Brecht
fd211bf490 Add a note about usage of the context manager
Addesses #1097
2015-10-02 12:40:58 -07:00
Ronny Pfannschmidt
9263b3a051 Merge pull request #1096 from hackebrot/update-docs-skip-with-cli-option
Update docs skip with cli option
2015-10-01 18:31:55 +02:00
holger krekel
88209c6ac6 Merge pull request #1092 from nicoddemus/param-ids-fix
Param ids fix
2015-10-01 15:09:11 +02:00
Ronny Pfannschmidt
688f955f5e Merge pull request #1058 from nicoddemus/doc-how-disable-cache
Add docs on how to disable cache provider
2015-10-01 13:06:02 +02:00
Bruno Oliveira
f3cee8f0b5 Merge remote-tracking branch 'upstream/master' into param-ids-fix 2015-10-01 07:58:55 -03:00
Ronny Pfannschmidt
b7fd3f0031 Merge pull request #1093 from nicoddemus/relto-bug
Fix internal error when filtering tracebacks where one entry was generated by an exec() statement
Fixes #995
2015-10-01 06:00:40 +02:00
Bruno Oliveira
d1e00f6e19 Detect dynamic code explicitly in filter_traceback 2015-09-30 17:32:49 -03:00
Bruno Oliveira
c9480c5b8b Move imports outside _escape_bytes as suggested in review 2015-09-30 17:02:19 -03:00
Raphael Pierzina
4829eac1e1 Use a variable for the skipif marker as suggested by @nicoddemus 2015-09-30 21:53:34 +02:00
Raphael Pierzina
a10da0e540 Implement skipif marker for slow test based on pytest.config 2015-09-30 19:41:47 +02:00
Raphael Pierzina
7ac8a88a05 Remove pytest_runtest_setup from conftest.py 2015-09-30 19:25:57 +02:00
Bruno Oliveira
639ae0cfe0 Merge pull request #1095 from ionelmc/fix-hook-docs
Correct hook examples in docs.
2015-09-30 11:28:43 -03:00
Ionel Cristian Mărieș
c8f5a40fd9 Edit examples again to use hookimpl. 2015-09-30 16:58:12 +03:00
Ionel Cristian Mărieș
f4f23e8e09 Correct hook examples in docs. 2015-09-30 16:12:33 +03:00
Florian Bruhin
dc9ad12182 Merge pull request #1094 from nicoddemus/cache-fixture-docstring-fix
fix docstring for cache fixture regarding ``/`` on keys
2015-09-30 06:02:38 +02:00
Bruno Oliveira
a808e09204 fix docstring for cache fixture regarding `/` on keys 2015-09-30 00:27:52 -03:00
Bruno Oliveira
6ae16eba36 add entries for #1085 and #1087 to the CHANGELOG 2015-09-29 22:37:11 -03:00
Bruno Oliveira
11f100813e Fix internal error when filtering tracebacks where one entry was generated by an exec() statement
Fix #995
2015-09-29 22:29:43 -03:00
Bruno Oliveira
b64470443f Fix SystemError when using unicode_escape on Python 3
Fix #1087
2015-09-29 18:20:30 -03:00
Bruno Oliveira
8633c4cefd Fix encoding errors for parametrized tests with unicode parameters in py2
Fix #1085
2015-09-29 17:57:49 -03:00
Florian Bruhin
e9240f7eee Merge pull request #1090 from RonnyPfannschmidt/fix-flaky-getusertest
fix flaky get_user fallback tests
2015-09-29 21:35:45 +02:00
Ronny Pfannschmidt
04deea3c6d fix flaky get_user fallback tests 2015-09-29 21:00:12 +02:00
Bruno Oliveira
6ca8923ec7 Merge pull request #1088 from hpk42/docfix
remove unclear todo that irritates in the online docs
2015-09-29 13:26:54 -03:00
holger krekel
e3562be530 remove unclear todo that irritates in the online docs 2015-09-29 18:23:44 +02:00
holger krekel
c5fd42b699 start 2.8.2 development 2015-09-29 15:32:33 +02:00
holger krekel
afdbb6b17a Merge pull request #1084 from hpk42/branch-281
2.8.1 release
2015-09-29 15:19:07 +02:00
holger krekel
a0076460db avoid pullrequest picture and fix bugfix/feature target info.
I could not get the pullrequest picture to be included correctly
in both latex and html sphinx builders due to the "include" directive
from doc/en/contributing.rst
2015-09-29 13:48:38 +02:00
holger krekel
787c866191 hide some parts from generating latexpdf 2015-09-29 13:37:30 +02:00
holger krekel
95245b935c - fix a flaky test on py35-xdist by calling
importlib.invalidate_caches()

- bump version to 2.8.1

- regen docs

- amend changelog, authors
2015-09-29 13:10:59 +02:00
holger krekel
f61f39efdd Merge pull request #1052 from The-Compiler/no-std-prefix
Don't hardcode 'std' for captured stdout/stderr.
2015-09-29 09:19:40 +02:00
holger krekel
a73d517bee Merge pull request #1080 from vodik/master
fix #1034: Add missing nodeid on pytest_logwarning call in addhook.  Thanks Simon Gomizelj for the PR.
2015-09-29 09:07:43 +02:00
Simon Gomizelj
b3727438d6 Add missing nodeid on pytest_logwarning call in addhook.
Otherwise KeyError: 'nodeid' gets thrown, killing pytest. This may fix
issue 1034, but the details of it may be caused by something similar
somewhere else.
2015-09-28 20:23:54 -04:00
Bruno Oliveira
b184f391c6 Show known environment variables in py.test --help 2015-09-28 18:34:16 -03:00
Bruno Oliveira
5f860181b6 Remove print left by accident 2015-09-28 18:25:20 -03:00
Bruno Oliveira
6caa7083db Moved more detailed options on how to disable a plugin to plugins.rst 2015-09-28 18:23:08 -03:00
holger krekel
839909f3f6 Merge pull request #1081 from hunse/fix-pendingdep
`deprecated_call` detects pending warnings again
2015-09-28 19:05:44 +02:00
Eric Hunsberger
4194c9cce2 Check deprecated_call specific to deprecation
`deprecated_call` used to accept any warning. As of #897, it
is now specific to DeprecationWarnings, and another commit in
this PR extends this to PendingDeprecationWarnings. This commit
makes sure this stays the case.
2015-09-28 12:35:24 -04:00
Eric Hunsberger
e8261e0c77 deprecated_call detects pending warnings again
`deprecated_call` now looks for PendingDeprecationWarnings,
as it did previously but was broken by #897. Fixes #1037.

Also added a test so this does not happen again.
2015-09-28 12:11:52 -04:00
Ronny Pfannschmidt
22e1f4946e Merge pull request #1079 from hpk42/issue1073
fix issue #1073 -- shortcut plugin hook lookup if the attrname is not prefixed with pytest_.
2015-09-28 16:15:42 +02:00
holger krekel
971ebcbd77 simplify by removing the single-call "exclude_pytest_names" function 2015-09-28 15:43:55 +02:00
holger krekel
03aca9ea79 add changelog entry for fix #1073 2015-09-28 14:17:58 +02:00
holger krekel
1c0ffc5caf seems like pypy's callable builtin calls __getattr__ so we do the check
later.
2015-09-28 14:02:30 +02:00
holger krekel
4e3a807733 fix issue #1073 -- shortcut plugin hook lookup if the attrname is not
prefixed with pytest_.
2015-09-28 13:34:28 +02:00
holger krekel
d29084ec2c add changelog note for fix #704 2015-09-28 11:45:51 +02:00
holger krekel
c5faa00ace Merge pull request #1077 from nicoddemus/pluggy-0.3.1
Vendor pluggy-0.3.1
2015-09-28 11:44:07 +02:00
Bruno Oliveira
910b25d416 Vendor pluggy-0.3.1
Fix #704
2015-09-27 16:04:29 -03:00
Ronny Pfannschmidt
cb4b5bd684 Merge pull request #1072 from nicoddemus/use-more-py35-tox
Use py35 as preferred py3 interpreter for special test environments
2015-09-26 09:26:16 +02:00
Ronny Pfannschmidt
842aa5746f Merge pull request #1069 from nicoddemus/docs-contents-reorg
Reorganize "Contents" page
2015-09-26 09:18:42 +02:00
Ronny Pfannschmidt
c2c2451788 Merge pull request #1071 from nicoddemus/xml-xdist
Wrong xml report when used with pytest-xdist
2015-09-26 09:05:25 +02:00
Bruno Oliveira
cd7ca3de68 Test py27-xdist and py35 environments on AppVeyor 2015-09-26 03:46:03 -03:00
Bruno Oliveira
d52d0251b2 Test py35 on AppVeyor 2015-09-26 03:44:12 -03:00
Bruno Oliveira
cdead1a991 Update travis with new tox environments 2015-09-26 03:43:37 -03:00
Bruno Oliveira
031a20699c Use py35 as preferred py3 interpreter for special test environments 2015-09-26 03:37:21 -03:00
Bruno Oliveira
e4b18ea5c3 Update CHANGELOG 2015-09-26 03:28:30 -03:00
Bruno Oliveira
b72c3310bc Add note on docs about record_xml_property not working with xdist 2015-09-26 03:24:07 -03:00
Bruno Oliveira
748da0e5d7 Fix xml generation when used with pytest-xdist
pytest_runtest_logreport must be careful to not depend on setup/call/teardown
being called sequentially in that order, as xdist will call them as they are reported
from the slaves

Fix #1064
2015-09-26 03:21:24 -03:00
Bruno Oliveira
bc501a28af Merge pull request #1070 from oleg-alexandrov/master
Documentation clarification
2015-09-26 03:16:44 -03:00
Oleg Alexandrov
9d2d2d17af Clarify documentation
Fix doc markup
2015-09-25 21:46:06 -07:00
Bruno Oliveira
b403395cdb Reorganize "Contents" page
Change the organization in a more logical way to a newcomer
2015-09-26 01:09:50 -03:00
Bruno Oliveira
fbce3bb48f Small format fixes in HOWTORELEASE 2015-09-25 23:10:35 -03:00
Ronny Pfannschmidt
bf23a0f4b8 Merge pull request #1065 from The-Compiler/doc-fixes
Doc fixes
2015-09-25 19:18:09 +02:00
Florian Bruhin
448ec8b740 Mark sources correctly in parametrize.rst.
Sphinx didn't pick this up as monospace text.
2015-09-25 18:43:33 +02:00
Florian Bruhin
272f987b0c Fix test_time in parametrize doc.
The source was indented with a mixture of tabs and spaces, causing syntax
errors when executed.
2015-09-25 18:42:06 +02:00
Florian Bruhin
0ebac22bb2 Regenerate fixture list doc with cache docstring. 2015-09-25 18:41:07 +02:00
Bruno Oliveira
c55e42f856 Merge pull request #1063 from timstaley/docs-module-level-fixtures-pytestmark
Docs: Fixtures page - clarify usage of module-level pytestmark.
2015-09-25 12:42:35 -03:00
Tim Staley
eb2caa554c Docs: Fixtures page - clarify usage of module-level pytestmark.
It certainly wasn't clear to me that the variable assigned by
pytest.mark.usefixtures should also be called pytestmark,
so I was confused when::

  foo = pytest.mark.usefixtures('fixture_bar')

wasn't behaving as expected; correct usage is of course::

  pytestmark = pytest.mark.usefixtures('fixture_bar')

I assume this is by design, otherwise that's a separate issue, at
least current behaviour should be documented.
2015-09-25 14:57:08 +01:00
Florian Bruhin
7c5d2ea81d Merge pull request #1046 from RonnyPfannschmidt/docs
fix documentation regeneration, kill Makefile
2015-09-25 08:35:18 +02:00
Bruno Oliveira
b09d3724a0 Add docs on how to disable cache provider 2015-09-23 19:15:44 -03:00
Ronny Pfannschmidt
acb6b186f2 Merge pull request #1056 from icemac/patch-1
Remove duplicate line.
2015-09-23 09:10:20 +02:00
Ronny Pfannschmidt
a3fdcd9b17 Merge pull request #1031 from pytest-dev/unmarshable-parametrize
Parametrized values containing non-ascii bytes break cache
2015-09-23 09:03:51 +02:00
Ronny Pfannschmidt
e5c76cb22d Merge pull request #1054 from nicoddemus/cache-fixture-docstring
Add docstring to cache fixture
2015-09-23 09:00:01 +02:00
Michael Howitz
730114d088 Remove duplicate line. 2015-09-23 08:53:13 +02:00
Florian Bruhin
c0c685a5de Merge pull request #1055 from nicoddemus/fix-help-pytest-warnings
(w)warnings -> (w)pytest-warnings in "-r chars" help
2015-09-23 06:53:46 +02:00
Bruno Oliveira
79ebca3f30 (w)warnings -> (w)pytest-warnings in "-r chars" help 2015-09-23 01:09:09 -03:00
Bruno Oliveira
25d2cc4604 Add docstring to cache fixture
Fixes #1049
2015-09-22 23:46:05 -03:00
Bruno Oliveira
716fa97fa1 Update CHANGELOG 2015-09-22 23:21:36 -03:00
Bruno Oliveira
e1063678f1 escape bytes when creating ids for parametrized values 2015-09-22 23:18:09 -03:00
Bruno Oliveira
661495e5c5 Write failing test for parametrized tests with unmarshable parameters
Related to #1030; committing directly to pytest repository to
get feedback from others on how to proceed.
2015-09-22 23:18:07 -03:00
Bruno Oliveira
9d1ae0a149 Merge pull request #1048 from RonnyPfannschmidt/pytest-1029
fixes #1029 by handling access errors for cache dirs as pytest warning
2015-09-22 19:04:04 -03:00
Ronny Pfannschmidt
7f776fe19a skip chmod using cache access warning tests on windows 2015-09-22 20:49:11 +02:00
Florian Bruhin
c10f483b9f Don't hardcode 'std' for captured stdout/stderr.
This will make Item.add_report_sect more usable for plugins.
See https://github.com/eisensheng/pytest-catchlog/pull/7
2015-09-22 20:29:16 +02:00
Ronny Pfannschmidt
ea9a491fb3 add an acceptance test for cache write errors 2015-09-22 20:24:37 +02:00
holger krekel
ca460e11e6 Merge pull request #1051 from Akasurde/issue_1027
Added minor documentation change in python.py
2015-09-22 17:09:25 +02:00
Ronny Pfannschmidt
c3588b545f generate docs on python3.4 2015-09-22 16:52:35 +02:00
Abhijeet Kasurde
dc1ce51ac2 Added minor documentation change
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-22 20:15:49 +05:30
Ronny Pfannschmidt
6e51918353 update changelog 2015-09-22 16:33:27 +02:00
Bruno Oliveira
1d2afada83 Minor typo in CHANGELOG 2015-09-22 11:30:00 -03:00
Ronny Pfannschmidt
29f4da93d4 handle access errors when writing cache files silently as pytest warning, fixes #1039 2015-09-22 16:28:19 +02:00
Bruno Oliveira
5acbd7bc86 Fix git checkout command in CONTRIBUTING.rst 2015-09-22 10:54:37 -03:00
Ronny Pfannschmidt
5ff75a41ea fix erroring doc example 2015-09-22 15:05:56 +02:00
Ronny Pfannschmidt
89badfec0c update regendoc, substitutes PYTHON_PREFIX 2015-09-22 14:48:04 +02:00
Ronny Pfannschmidt
662d755974 fix documentation gegeneration, kill Makefile 2015-09-22 14:22:37 +02:00
holger krekel
bf9b94595c fix changelog 2015-09-22 11:18:19 +02:00
holger krekel
41d61ed221 Merge pull request #1023 from RonnyPfannschmidt/fix-877
Fix 877
2015-09-22 11:17:02 +02:00
holger krekel
e2f72ffed8 start bugfix changelog on master 2015-09-22 10:59:23 +02:00
Florian Bruhin
79c2abf531 Merge pull request #1038 from hpk42/newbranching
adapt contributing to new versioning and to python3.5 as latest.
2015-09-22 10:55:30 +02:00
holger krekel
a5ff345f7b fix another occurence of "macro" to "major" 2015-09-22 10:45:55 +02:00
holger krekel
f19ba6c2b1 fix naming 2015-09-22 10:29:12 +02:00
holger krekel
745cd26850 Merge branch 'master' into newbranching 2015-09-22 10:22:05 +02:00
holger krekel
71c5883d52 update according to discussion on pytest-dev and in PR 2015-09-22 10:21:01 +02:00
Floris Bruynooghe
c30eafa254 Update changelog for merged PR. 2015-09-21 14:59:48 +01:00
Floris Bruynooghe
7c3be72ac7 Merge pull request #1042 from Bjwebb/issue411
Add __eq__ method to assertion comparison example
2015-09-21 14:57:59 +01:00
Ben Webb
622e64320a Add __eq__ method to assertion comparison example
This makes the example make more sense, because now Foo(1) == Foo(1) is
true. Fixes #411
2015-09-21 14:23:26 +01:00
holger krekel
0efcfeed17 adapt contributing to new versioning and to python3.5 as latest. 2015-09-21 14:04:30 +02:00
Floris Bruynooghe
65de43e67d Mention fix for issue 766 in changelog
Fixes issue #766.
2015-09-21 12:21:24 +01:00
Floris Bruynooghe
cd4557cce8 Merge pull request #1036 from russel/rlw_766
Remove references to distutils as per Issue 766.
2015-09-21 12:19:14 +01:00
Russel Winder
714a97e452 Remove references to distutils as per Issue 766. 2015-09-21 12:00:48 +01:00
Floris Bruynooghe
80897f62a6 Merge pull request #974 from nicoddemus/addoption-conftest
Add warning about implementing pytest_addoption in conftest files
2015-09-21 11:28:31 +01:00
Floris Bruynooghe
dfd83b59f8 Merge pull request #1033 from mbirtwell/changes_to_contributing_rst
contributing: Add instructions from PyCon
2015-09-21 11:13:14 +01:00
Michael Birtwell
bb42e43ee7 contributing: Add instructions from PyCon
At PyCon we realised that the virtualenv created in the make file wasn't
used in the subsequent steps in CONTRIBUTING.rst. This change removes the
reference the make and suggesting installing tox directly.
Also changed the explanation that follows to make more sense with this
change
2015-09-21 10:56:05 +01:00
Bruno Oliveira
8fe5c704e3 Merge pull request #1024 from rouge8/tmpdir-uid-not-found
Make tmpdir resiliant to user ids that do not exist
2015-09-18 22:53:17 -03:00
Andy Freeland
b1c9b8b415 Make tmpdir resiliant to user ids that do not exist
Previously, the tmpdir fixture would fail if the current process's user
id does not correspond to a valid user (e.g. running pytest in a Docker
container with 'docker run -u').
2015-09-18 21:26:19 -04:00
Ronny Pfannschmidt
7658f60146 #877 - reencoding causes typeerrors on python2 2015-09-19 00:30:01 +02:00
Ronny Pfannschmidt
49c99a41ea reencode non-ascii python2 assertion reprs, fixes #877
i decided against using a warning since the problem goes away with python3
the support code can be removed once we drop python2 in 10 years or so
2015-09-19 00:30:01 +02:00
Bruno Oliveira
f02d9425f9 Bump version to 2.8.0 2015-09-18 19:04:20 -03:00
Ronny Pfannschmidt
1aca998f3f Merge pull request #1012 from lukas-bednar/junitxml_plugin
junitxml: align custom properties with junit format
2015-09-18 16:01:22 +02:00
holger krekel
2ba0fe2876 Merge pull request #1022 from nicoddemus/cxfreeze-tmpdir
Use temporary directory for cx_freeze testing
2015-09-18 09:28:33 +02:00
Bruno Oliveira
546093175e White list rm for cxfreeze env 2015-09-17 23:34:09 -03:00
Bruno Oliveira
e4d5f88257 Fix install cx_freeze 2015-09-17 23:32:27 -03:00
Bruno Oliveira
3a8e375efe Fix import 2015-09-17 14:52:51 -03:00
Bruno Oliveira
f5f3a2a928 Fix flaky failure about unused shutil 2015-09-17 12:54:02 -03:00
Bruno Oliveira
6bca5a1c25 Use temporary directory for cx_freeze testing 2015-09-17 11:54:44 -03:00
Lukas Bednar
02a2272cfe junitxml: move custom properties to properties element
<testsuite>
  <testcase>
    <properties>
      <property name="ABC" value="XYZ" />
      <property name="DEF" value="ZYX" />
    </properties>
  </testcase>
</testsuite>
2015-09-17 15:56:41 +02:00
Florian Bruhin
fe8def98e4 Merge pull request #1020 from hpk42/pluggy031
re-vendor pluggy to 0.3.1 release and also fix the README
2015-09-17 14:41:32 +02:00
holger krekel
f4bfd571ee re-vendor pluggy to 0.3.1 release and also fix the README 2015-09-17 13:54:23 +02:00
holger krekel (rather uses bitbucket/hpk42)
f32f3f1980 Merge pull request #1019 from mdboom/python3.5-docs
Various updates to the docs to mention support for Python 3.5
2015-09-17 13:45:35 +02:00
holger krekel
baf5b5d005 update changelog/author info, bump version number 2015-09-17 13:43:39 +02:00
holger krekel (rather uses bitbucket/hpk42)
3bcda48ba4 Merge pull request #1009 from mdboom/deprecation-warnings
avoid deprecation warnings, introduce helpers.
2015-09-17 13:37:26 +02:00
Michael Droettboom
16f90ffc92 Various Python 3.5 documentation mentions 2015-09-17 07:32:52 -04:00
Ronny Pfannschmidt
6520cf00e9 Merge pull request #1018 from hpk42/no_scm
revert setuptools_scm addition
2015-09-17 13:25:48 +02:00
holger krekel
7eb1211192 revert setuptools_scm addition introduced with f22d14b105 2015-09-17 12:58:04 +02:00
Florian Bruhin
f978b545c5 Merge pull request #1016 from hpk42/importappend3
revert importing test module behaviour to prepend by default
2015-09-17 12:41:28 +02:00
holger krekel
6192e74f03 remove __init__.py as advised by ronny 2015-09-17 10:51:03 +02:00
Michael Droettboom
7f71ce0ab2 Reorganize tests 2015-09-16 16:52:37 -04:00
holger krekel
a62d12634c revert importing test module behaviour to prepend by default 2015-09-16 22:12:20 +02:00
holger krekel (rather uses bitbucket/hpk42)
886ac82c43 Merge pull request #1013 from nicoddemus/issue1010
Make tmpdir more resilient in case environment variables required by getpass are missing
2015-09-16 21:57:44 +02:00
Florian Bruhin
2575053697 Merge pull request #1015 from hpk42/cache-integration
refined pytest-cache integration
2015-09-16 21:46:46 +02:00
Bruno Oliveira
130e6cf8a2 Use temproot as a fallback if the current user couldn't be obtained 2015-09-16 16:42:07 -03:00
holger krekel
3841e99720 avoid oldstyle funcarg usage 2015-09-16 21:06:44 +02:00
holger krekel
79d22bf334 some more doc refinements 2015-09-16 20:44:41 +02:00
holger krekel
45065e4e2e refine command line option naming and docs 2015-09-16 20:41:22 +02:00
Bruno Oliveira
558e5406e8 test_get_user should execute on windows only 2015-09-16 13:06:39 -03:00
Bruno Oliveira
1150e87e31 Extract get_user logic into a separate function 2015-09-16 12:47:50 -03:00
Bruno Oliveira
6676aeda5a Make tmpdir more resilient in case environment variables required by getpass are missing
Fix #1010
2015-09-16 12:20:07 -03:00
holger krekel
9a90aaca96 improve and integrate docs 2015-09-16 17:15:31 +02:00
Michael Droettboom
bf74401fd3 Use __code__.co_argcount 2015-09-16 11:05:54 -04:00
Michael Droettboom
9a84c9edb6 Add tests for argument formatting 2015-09-16 11:02:48 -04:00
Michael Droettboom
08a572086b Fix lookup of special values 2015-09-16 11:02:36 -04:00
Bruno Oliveira
2093889ac2 Merge pull request #1011 from hpk42/master
merge latest pytest-2.7 changes
2015-09-16 11:22:05 -03:00
Michael Droettboom
9c19728d2b Fix checking for args 2015-09-16 06:45:51 -04:00
Michael Droettboom
859259ddae Simplify 2015-09-16 06:43:16 -04:00
Michael Droettboom
a0cefb3213 Use inspect.signature instead of inspect.getargspec 2015-09-16 06:33:53 -04:00
holger krekel
fdd23878ec Merge latest changes from branch 'pytest-2.7'
Conflicts:
	_pytest/__init__.py
	doc/en/fixture.rst
	setup.cfg
	testing/test_runner.py
2015-09-16 09:26:55 +02:00
Floris Bruynooghe
b4c47c0ac0 Merge pull request #1006 from hpk42/pytest-2.7
some fixes, streamlines to get 2.7.3 out the door
2015-09-15 11:37:38 +01:00
holger krekel
5af262daa3 fix flakes problem, configure devpi upload to always upload wheels and sdist 2015-09-15 12:28:09 +02:00
holger krekel
16720b96b4 fix a few issues with pytest-2.7 branch and bump version number
- importorskip: skip a test if we have a minversion but cannot parse
  version numbers due to pkg_resources not present
- make runner tests work with latest xdist
2015-09-15 12:17:52 +02:00
Bruno Oliveira
b2b003dcac Merge pull request #1001 from RonnyPfannschmidt/exitcode-refactor
restrucure pytest.main.wrap_session to allow for non-testrun wraps
2015-09-14 15:56:04 -03:00
Ronny Pfannschmidt
c5bf148ed7 Merge pull request #959 from nicoddemus/vendor-pluggy
Vendor pluggy
2015-09-14 18:37:52 +02:00
Ronny Pfannschmidt
81018f1996 restrucure pytest.main.wrap_session to allow for non-testrun wraps 2015-09-14 18:34:29 +02:00
Bruno Oliveira
a13f23d218 Merge branch 'master' into vendor-pluggy 2015-09-14 12:22:18 -03:00
Anatoly Bubenkov
6d5641afce Merge pull request #999 from nicoddemus/fix-appveyor
Use pip and tox from py27 in appveyor
2015-09-14 17:20:46 +02:00
Bruno Oliveira
9396489b4c Remove unused run_with_env script 2015-09-14 11:28:07 -03:00
Ronny Pfannschmidt
2eb3bdf132 Merge pull request #998 from nicoddemus/py35
Use python "3.5" on travis
2015-09-14 16:16:42 +02:00
Bruno Oliveira
a0dd478637 Use pip and tox from py27 in appveyor 2015-09-14 10:59:10 -03:00
Bruno Oliveira
1d1eac4c6b Use python "3.5" on travis 2015-09-14 10:36:20 -03:00
Bruno Oliveira
d6bb5cb6c1 Ignore vendored_packages folder for coverage purposes 2015-09-14 10:30:58 -03:00
Bruno Oliveira
d79c89beba Merge pull request #997 from RonnyPfannschmidt/monkeypatch-cleanup
monkeypatch: ensure the internal _savesyspath attribute always exists
2015-09-14 10:25:09 -03:00
Ronny Pfannschmidt
2cfc183029 monkeypatch: ensure the internal _savesyspath attribute always exists 2015-09-14 07:42:23 +02:00
Ronny Pfannschmidt
e0645564fe more pep8 fixes 2015-09-13 21:49:01 +02:00
Ronny Pfannschmidt
cd475c7b27 minor flake8 fixes 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
c06ff2a992 update docs on the json usage of builtin cache 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
1e107e6bd1 restrucure pytest.main.wrap_session to allow for non-testrun wraps 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
3d843edc69 minor doc reference fixes 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
2e87cf4a62 create the previously missing cache fixture
there was a disabled test
2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
1de38a25fc use flake8 in the flakes testenv
and extend the ignored errors list so pytest is clean
we def have to trim down that one
2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
e035f57535 s/--cache/--show-cache/ 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
e20216a1a8 merge the pytest-cache plugin into core 2015-09-13 19:55:44 +02:00
Bruno Oliveira
c7888d1d97 Add note in changelog about issue 960 2015-09-12 11:17:50 -03:00
Bruno Oliveira
41d2faccea Merge branch 'issue_960' of https://github.com/Akasurde/pytest 2015-09-12 10:57:23 -03:00
Abhijeet Kasurde
dd69394598 Added testcase and help for report option
Fix added to show help of new reporting option 'a'.
Also, added testcase for checking reporting functionality
with option 'a'.

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-11 12:24:45 +05:30
Abhijeet Kasurde
c40947e651 Added fix for specifying every option for reporting
The fix will add option 'a' to reporting which will ease task of
adding all options or reporting at once.

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-10 15:43:43 +05:30
Bruno Oliveira
b6f876e721 Fix --target parameter for pip install 2015-09-07 14:13:41 -03:00
Bruno Oliveira
5a1c679f78 Keep *dist-info directory on vendored_packages 2015-09-07 14:09:59 -03:00
Bruno Oliveira
0b3f1b4a7c Create _pytest.vendored_packages and _pytest._pluggy
- Replace imports from "pluggy" to "_pytest._pluggy"
- Fallback to import "pluggy" from global namespace if
  not found in _pytest.vendored_packges
2015-09-07 13:01:54 -03:00
Bruno Oliveira
e622975ffd Merge pull request #987 from benauthor/master
Document single-method usage
2015-09-04 17:24:15 -03:00
Evan Bender
6ffb659282 Document single-method usage
It is possible to run a single test method in a class, but the syntax is
not glaringly obvious and documentation would help people out.
2015-09-04 15:11:57 -04:00
Bruno Oliveira
8625eb643e Merge pull request #986 from RonnyPfannschmidt/early-logging
replay initial warnings when terminalreporter is loaded
2015-09-02 19:10:53 -03:00
Ronny Pfannschmidt
76f0988551 turn the pytest_logwarning hook historic 2015-09-02 18:49:49 +02:00
Ronny Pfannschmidt
348e519437 Merge pull request #962 from nicoddemus/doctest-skip
Make doctest SKIP option register doctests as SKIPPED rather than PASSED
2015-09-01 12:31:35 +02:00
Ronny Pfannschmidt
7292212d5a Merge pull request #976 from nicoddemus/ren-warnings-on-terminal
Rename "warnings" to "pytest-warnings" in terminal output
2015-09-01 07:56:42 +02:00
Bruno Oliveira
4ad56e84a8 Make doctest SKIP option register doctests as SKIPPED rather than PASSED
Fix 957
2015-08-31 21:25:11 -03:00
Bruno Oliveira
e3734ef337 Add CHANGELOG entry about renaming "warnings" to "pytest-warnings" 2015-08-31 21:21:29 -03:00
Bruno Oliveira
06a70b6d6d Merge pull request #982 from codewarrior0/patch-1
Add notes to docstring of `monkeypatch.undo()`
2015-08-31 21:18:53 -03:00
Ronny Pfannschmidt
3fa261564b replay initial warnings when terminalreporter is loaded 2015-08-31 21:54:12 +02:00
Anatoly Bubenkov
18d175708c Merge pull request #983 from The-Compiler/issue-979
Fix overriding of fixtures with parametrization.
2015-08-31 15:21:45 +02:00
Florian Bruhin
19791546da Add docstring. 2015-08-31 15:11:57 +02:00
Florian Bruhin
885e461ae3 Fix overriding of fixtures with parametrization.
Fixes #979, see #926.
2015-08-31 14:38:39 +02:00
David Vierra
3a27cd87ce Adjust phrasing in monkeypatch.undo docstring
s/test invocation/test function invocation/
2015-08-29 19:00:00 -10:00
David Vierra
6380e19f07 Add notes to docstring of monkeypatch.undo()
Note that calling `undo()` is generally not needed, and describe the "gotcha" discovered in #981.
2015-08-29 16:53:57 -10:00
Bruno Oliveira
c0eec5d61c Merge pull request #975 from nicoddemus/fix-regression-parse-known-args
Fix regression caused by changing the signature for parse_known_args
2015-08-29 23:44:32 -03:00
Brianna Laugher
c493f263b6 Merge branch 'Elizaveta239-master' closes #908 2015-08-29 17:34:35 +10:00
Floris Bruynooghe
553aef57aa Merge pull request #978 from nicoddemus/string-diff
Show a few diff lines when truncating string diffs
2015-08-28 09:04:39 +01:00
Florian Bruhin
9f30ae9850 Merge pull request #977 from nicoddemus/marks-fixtures-note
Add note that using marks on fixtures is not supported
2015-08-28 06:16:49 +02:00
Bruno Oliveira
dd7112d5ea Show a few diff lines when truncating string diffs
Fix #934
2015-08-27 22:20:13 -03:00
Brianna Laugher
b49bedcf0c Merge branch 'master' of git://github.com/Elizaveta239/pytest into Elizaveta239-master 2015-08-28 11:10:22 +10:00
Bruno Oliveira
87cb5bc5b7 Add note that using marks on fixtures is not supported
Fix #964
2015-08-27 20:11:18 -03:00
Bruno Oliveira
e59471766a Rename "warnings" to "pytest-warnings" in terminal output
Fix #970
2015-08-27 19:59:52 -03:00
Bruno Oliveira
4533a50542 Fix regression caused by changing the signature for parse_known_args
Fix #973
2015-08-27 19:35:32 -03:00
Bruno Oliveira
ccf7584fac Add warning about implementing pytest_addoption from conftest files 2015-08-27 19:14:56 -03:00
Bruno Oliveira
06b1b69fb7 Merge pull request #963 from nicoddemus/cookiecutter-docs
Mention cookie-cutter in the docs and fix broken links to <extplugin>
2015-08-26 16:41:56 -03:00
Bruno Oliveira
333cb27272 Merge pull request #961 from nicoddemus/args-after-options
Paths after normal options are now properly used to discover rootdir and ini files
2015-08-26 12:50:19 -03:00
Bruno Oliveira
d988d2006f Mention cookie-cutter in the docs and fix broken links to <extplugin> 2015-08-25 23:59:18 -03:00
Bruno Oliveira
42b43a7d7b Paths after normal options are now properly used to discover rootdir and ini files
Fix #949
2015-08-25 22:08:05 -03:00
elizabeth
e67d66a5d4 Merge branch 'pytest-dev'
# Conflicts:
#	AUTHORS
2015-08-24 22:55:11 +03:00
Ronny Pfannschmidt
b25e41e348 Merge pull request #956 from nicoddemus/record-xml-property
add preliminary support for extended junit xml properties
2015-08-24 09:13:04 +02:00
Bruno Oliveira
70da93145d Improve docs and using warning system for record_xml_property fixture 2015-08-23 11:56:27 -03:00
Bruno Oliveira
a20c6d072d Fix getdoctarget to ignore comment lines 2015-08-23 11:20:34 -03:00
elizabeth
06585f5bdd Always report error about parametrize data that doesn't correspond to fixtures in test functions. 2015-08-23 13:42:40 +03:00
David Diaz
44d9365da0 Add warning of "preliminary feature" to record_xml_property 2015-08-21 15:21:12 -06:00
David Diaz
2ddbac1f98 Correcting implementation based on pull request feed back 2015-08-21 14:31:53 -06:00
Ronny Pfannschmidt
287df16c9c Merge pull request #817 from nicoddemus/non-collection-exit-error
return non-zero exit code if no tests are collected
2015-08-21 18:30:57 +02:00
Bruno Oliveira
5f0e92a432 Merge pull request #942 from RonnyPfannschmidt/adapt-print
Adapt plugin name printing
2015-08-21 12:02:58 -03:00
Bruno Oliveira
d7d418cd47 Fix forked_run_report in pytest.xdist 1.13 2015-08-20 21:17:27 -04:00
David Diaz
24212fd97f Add support to record custom properties on xml output 2015-08-20 17:55:38 -06:00
Bruno Oliveira
3e41c3cbb3 Using packages from pypi 2015-08-18 09:19:51 -03:00
Bruno Oliveira
359f248729 Force to use xdist >= 1.13 2015-08-18 07:35:03 -03:00
Bruno Oliveira
f730291904 Make testsfailed and testscollected public Session attributes
As suggested by @flub in review
2015-08-18 07:35:02 -03:00
Bruno Oliveira
2ffd37b816 return non-zero exit code if no tests are collected
Fix #812
Fix #500
2015-08-18 07:35:02 -03:00
Ronny Pfannschmidt
7758bcd141 terminalwriter: extract plugin printing logic and add positive unittests 2015-08-17 09:10:01 +02:00
Ronny Pfannschmidt
740a97a8cc terinalwriter: use dash between plugin name and version 2015-08-17 08:48:38 +02:00
Ronny Pfannschmidt
2c42f15e00 adapt plugin printing
* print each distribution only once(xdist now has 3 entrypoints)
* include the distribution version
2015-08-17 08:41:24 +02:00
Ronny Pfannschmidt
37ed391cc2 Merge pull request #938 from nicoddemus/doctest-unicode
New ALLOW_UNICODE doctest option
2015-08-16 11:35:10 +02:00
Floris Bruynooghe
f22d14b105 Merge pull request #879 from RonnyPfannschmidt/setuptools-scm
use setuptools_scm to determine the version
2015-08-15 00:56:16 +01:00
Bruno Oliveira
d749021a31 Fix coverage
Also make sure a test that doesn't set ALLOW_UNICODE fails
on Python 2 and passes Python 3.
2015-08-12 22:49:41 -03:00
Bruno Oliveira
93aee0f814 Add docs and CHANGELOG for ALLOW_UNICODE option 2015-08-12 22:17:15 -03:00
Bruno Oliveira
420823070b Add ALLOW_UNICODE doctest option
When enabled, the ``u`` prefix is stripped from unicode strings in
expected doctest output. This allows doctests which use unicode
to run in Python 2 and 3 unchanged.

Fix #710
2015-08-12 22:16:44 -03:00
Ronny Pfannschmidt
ddbdcab522 Merge pull request #937 from ctheune/patch-1
Fix accidental inversion in skip example.
2015-08-12 07:51:59 +02:00
Christian Theune
2174f3ce37 Fix accidental inversion in skip example. 2015-08-12 07:38:04 +02:00
Bruno Oliveira
73fdda0e45 Merge pull request #926 from untitaker/multifixture
Don't skip fixtures that are substrings of params
2015-08-10 21:12:51 -03:00
Markus Unterwaditzer
1d5215ab4f Add myself to authors 2015-08-11 00:47:18 +02:00
Markus Unterwaditzer
cafd71eb29 Update changelog for #926
Also fix some inconsistencies in the changelog on the way.
2015-08-11 00:47:18 +02:00
Markus Unterwaditzer
41cef6f5f2 Don't skip fixtures that are substrings of params
Bug introduced with
https://bitbucket.org/pytest-dev/pytest/pull-requests/257/allow-to-override-parametrized-fixtures

Fix #736
2015-08-11 00:41:21 +02:00
Bruno Oliveira
681e502c12 Use "union" merge strategy for CHANGELOG
Credits: @The-Compiler 😄
2015-08-10 19:27:22 -03:00
Ronny Pfannschmidt
bc509b42e8 Merge pull request #933 from nicoddemus/hasplugin
Reintroduce hasplugin to PytestPluginManager
2015-08-10 09:40:52 +02:00
Bruno Oliveira
e103932aad Reintroduce hasplugin to PytestPluginManager
Fix #932
2015-08-09 19:30:49 -03:00
Ronny Pfannschmidt
1c680210c2 Merge pull request #931 from nicoddemus/docs-hookwrapper
Change docs to use hookwraper instead of __multicall__
2015-08-09 09:22:31 +02:00
Ronny Pfannschmidt
eb989c8257 Merge pull request #921 from tomviner/issue562-noseistest
Issue #562 - Ensure @nose.tools.istest is respected
2015-08-09 07:54:19 +02:00
TomV
14625907ae update changelog 2015-08-09 00:11:16 +01:00
TomV
353360dbe5 use local isclass 2015-08-09 00:11:15 +01:00
TomV
3497aa0766 check nose.istest __test__ attr issue526 2015-08-09 00:11:15 +01:00
Bruno Oliveira
d49fb8a2d5 Change cx_freeze example to not use regendoc 2015-08-08 19:17:50 -03:00
Bruno Oliveira
e8583f01a0 Replaced __multicall__ examples in docs by hookwrapper
Fix #929
2015-08-08 19:07:27 -03:00
TomV
e130a0257d add test for @nose.tools.istest 2015-08-08 21:08:05 +01:00
Bruno Oliveira
729b5e9b2f Merge pull request #923 from The-Compiler/parametrize-idfunc
Generate parametrize IDs for enum/re/class objects.
2015-08-08 15:09:54 -03:00
Brianna Laugher
36b86af4b9 Added more test cases 2015-08-08 19:20:09 +02:00
Brianna Laugher
40fa7b25c5 Merge branch 'master' of git://github.com/Elizaveta239/pytest into Elizaveta239-master
Fixed merge conflict in CHANGELOG
2015-08-08 18:54:53 +02:00
Florian Bruhin
4e21d1d77b Move enum import and adjust comments. 2015-08-08 15:25:10 +02:00
Florian Bruhin
84fdba129a More style changes. 2015-08-08 12:57:54 +02:00
Bruno Oliveira
9ae40e393a Fix PR image link 2015-08-07 22:32:22 -03:00
Florian Bruhin
18125c7d1f Clean up type comparisons. 2015-08-07 23:10:22 +02:00
Florian Bruhin
03d8a6c05d Update CHANGELOG. 2015-08-07 23:08:22 +02:00
Brianna Laugher
fd4d35d9a2 Merge pull request #924 from The-Compiler/parametrise
Alert user about other parametrize spellings.
2015-08-07 13:32:04 +02:00
Florian Bruhin
91a29932a6 Add CHANGELOG entry. 2015-08-07 07:58:13 +02:00
Florian Bruhin
b59376bea4 Alert user about other parametrize spellings. 2015-08-07 07:51:59 +02:00
Florian Bruhin
13c5456868 Generate parametrize IDs for enum/re/class objects. 2015-08-07 07:37:59 +02:00
Bruno Oliveira
4f83586f55 Give credit where it is due in CHANGELOG 😄
Related to #890
2015-08-06 22:51:32 -03:00
Bruno Oliveira
77f54dbdae Merge pull request #915 from Akasurde/issue-890
Converted docs from txt to rst
2015-08-06 22:47:53 -03:00
Bruno Oliveira
2bbe709bce Use testdir fixture in test_double_test to ensure controlled environment for execution
Because the test relies that two subsequent tests try to use pytest.warns to capture
the exact same warning, it is better to use testdir to ensure test execution
occurs in the order we expect (which might be different with pytest-xdist or
pytest-random for example)
2015-08-06 22:30:01 -03:00
Eric Hunsberger
39f1471e93 Warnings always raised in WarningsRecorder
This ensures that if tests for warnings are run more than once,
the warning is still raised as expected.
2015-08-06 15:05:01 -04:00
Abhijeet Kasurde
ab9befb197 Updated CHANGELOG and AUTHORS 2015-08-05 15:21:37 +05:30
Abhijeet Kasurde
6d446e7167 Converted documentation from txt to rst 2015-08-05 10:24:14 +05:30
Bruno Oliveira
02e742b7a6 Move WarningsChecker import in test to a local import to workaround flakes issue
See #897 for discussion
2015-08-04 19:27:13 -03:00
Abhijeet Kasurde
185e730feb Added makefile changes for docs
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-08-04 18:44:52 +05:30
Abhijeet Kasurde
e79413acc4 Updated documentation from txt to rst
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-08-04 16:41:02 +05:30
Bruno Oliveira
d761bfffd7 Merge pull request #897 from hunse/warns
Warns
2015-08-03 22:48:20 -03:00
elizabeth
f7bacd169e Update docs 2015-08-04 00:48:41 +03:00
Bruno Oliveira
8fca4781f1 Merge pull request #910 from oleksmarkh/patch-1
fix docs folder name in HOWTORELEASE
2015-08-03 18:12:14 -03:00
elizabeth
657ca97dbd Some refactorings after code review 2015-08-04 00:02:03 +03:00
Oleksii Markhovskyi
3868b61443 fix docs folder name in HOWTORELEASE 2015-08-03 23:01:03 +02:00
Bruno Oliveira
c605cf92f9 Merge pull request #909 from flub/assertdoc
Update docs to represent reality
2015-08-02 18:53:47 -03:00
Floris Bruynooghe
b097d8c0a6 Update docs to represent reality
This was changed after EuroPython 2014, now both the message and the
introspection is presented.
2015-08-02 22:36:19 +01:00
elizabeth
db9809d6dc Update test for error in 'indirect' parameter 2015-08-02 23:53:44 +03:00
elizabeth
63bac67fb5 Add test for bad value in argument 'indirect' 2015-08-02 23:06:24 +03:00
elizabeth
54a3d6210b Update CHANGELOG and AUTHORS 2015-08-02 20:39:31 +03:00
elizabeth
15b865f502 Rewrite test using @pytest.fixture 2015-08-02 20:30:23 +03:00
elizabeth
9846953597 Support for python 2.6 2015-08-02 17:28:27 +03:00
elizabeth
2fc7aede0b Request #714: Apply indirect=True on particular argnames 2015-08-02 16:40:40 +03:00
Ronny Pfannschmidt
f6506fa6ca Merge pull request #907 from nicoddemus/regendocs-fixture
Use smtp.gmail.com server for examples and fixes examples for python 3
2015-07-31 06:55:08 +02:00
Eric Hunsberger
52b4eb6c46 Added warns to assert warnings are thrown
Works in a similar manner to `raises`, but for warnings instead
of exceptions. Also refactored `recwarn.py` so that all the
warning recording and checking use the same core code.
2015-07-30 23:28:32 -04:00
Eric Hunsberger
aac371cf07 Added myself to AUTHORS 2015-07-30 23:28:32 -04:00
Bruno Oliveira
4d8e3cbcb0 Use smtp.gmail.com server for examples and fixes examples for python 3
Examples in py3 where showing the wrong message:

    TypeError: Type str doesn't support the buffer API
2015-07-30 23:54:24 -03:00
Bruno Oliveira
7a918a1617 Fix regendoc section in fixture.txt
Noticed thanks to #903
2015-07-30 23:21:19 -03:00
Ronny Pfannschmidt
0b83b4076a Merge pull request #898 from nicoddemus/tmpdir-factory-new-name
Use pytest-of-$user as base directory for tmpdir_factory
2015-07-29 13:58:42 +02:00
Bruno Oliveira
4302972c23 Use pytest-of-$user as base directory for tmpdir_factory
Before tmpdir_factory, pytest used to create a link named "pytest-$user" to the current numbered directory. Use
 a different name so there's no conflict when running different pytest versions.

Fix #894
2015-07-28 20:57:48 -03:00
Ronny Pfannschmidt
235f9da432 special-case _pytest.__init__ in genscript to avoid a python3 bug 2015-07-27 11:33:27 +02:00
Bruno Oliveira
9cf9900721 Merge pull request #892 from The-Compiler/multi-parametrize
Document and test stacking of parametrize.
2015-07-26 21:00:29 -03:00
Bruno Oliveira
2035c5f8be Merge pull request #893 from pfctdayelise/dupereleaseguides
Delete duplicate release guide
2015-07-26 20:59:07 -03:00
Brianna Laugher
5ebad5c96d Delete release.txt from docs, merge extra comments to HOWTORELEASE.rst 2015-07-26 15:20:32 +02:00
Florian Bruhin
2ab4bf13ab Document and test stacking of parametrize.
Closes #815.
2015-07-26 14:39:13 +02:00
Ronny Pfannschmidt
5098e5fc47 fix version import 2015-07-26 14:04:52 +02:00
Brianna Laugher
017ceffb76 minor tweaks to CONTRIBUTING 2015-07-26 12:19:39 +02:00
Floris Bruynooghe
e69e785b23 Mention fix for issue #683 2015-07-26 12:15:29 +02:00
jakubo
e8f4819876 Test file run twice fails if it contains marked class #683 2015-07-26 12:15:29 +02:00
Brianna Laugher
985fd0cc2b Mention Contributors team by name in CONTRIBUTING 2015-07-26 12:07:50 +02:00
Floris Bruynooghe
97e5f7d7e9 Merge merge branch
I suspect I don't yet know how to use git...  Surely I don't need two
merges to merge a contribution.
2015-07-26 11:49:42 +02:00
Floris Bruynooghe
011565ca10 Add changelog entry for capture improvement 2015-07-26 11:42:33 +02:00
Floris Bruynooghe
e68fa641ff Merge reset-capgture-on-reeadouterr-errors
Merge branch 'reset-capture-on-readouterr-errors' of github.com:davidszotten/pytest into merge-capmerge
2015-07-26 11:39:03 +02:00
David Szotten
3e6bee2fc6 fail if snap is removed in the future 2015-07-26 01:28:00 +02:00
David Szotten
983d249680 reset capture even if readouterr throws
otherwise that exception (and all following output) end up in /dev/null
2015-07-26 01:15:51 +02:00
Floris Bruynooghe
8338c692a3 Merge pull request #887 from maartenq/753content_of
changed contents -> content but still #753 did not solved, might be i…
2015-07-25 19:11:53 +02:00
Floris Bruynooghe
f64d5f1209 Merge pull request #884 from cpommranz/deprecate_genscript
Deprecate and warn about --genscript, fixes #730
2015-07-25 19:09:52 +02:00
Floris Bruynooghe
f38c632635 Merge branch 'jb-fix-751' 2015-07-25 19:04:59 +02:00
Floris Bruynooghe
251fc68ef9 Merge fix for #751
Merge branch 'fix-751' of github.com:JanBednarik/pytest into jb-fix-751
2015-07-25 19:03:44 +02:00
Maarten
df8a83b2a1 changed contents -> content but still #753 did not solved, might be issues with regendoc 2015-07-25 17:48:12 +02:00
Jan Bednařík
0b92650494 Update changelog. 2015-07-25 16:25:29 +02:00
Jan Bednařík
9906a19e29 Issue #751 - fix. 2015-07-25 15:38:27 +02:00
Jan Bednařík
f78d87ee38 Issue #751 - test. 2015-07-25 15:38:11 +02:00
Christian Pommranz
00e1a2122a Deprecate and warn about --genscript, fixes #730 2015-07-25 15:10:22 +02:00
Floris Bruynooghe
80ee620459 Merge pull request #881 from maartenq/master
setuptools not present issue deprecating gentest
2015-07-25 15:03:50 +02:00
Floris Bruynooghe
023687d8d0 Merge pull request #872 from nicoddemus/confcutdir-inifile
Avoid detecting conftest files upwards from setup.cfg/pytest.ini/tox.ini by default
2015-07-25 14:03:24 +02:00
Maarten
e3d60024aa setuptools not present issue deprecating gentest 2015-07-25 13:50:40 +02:00
Ronny Pfannschmidt
a060b8ff73 use setuptools_scm to determine the version 2015-07-25 10:44:18 +02:00
Bruno Oliveira
ae28e4ba0f Merge remote-tracking branch 'upstream/pytest-2.7'
Conflicts:
	.travis.yml
2015-07-24 20:02:12 -03:00
Bruno Oliveira
92c61b0de3 Merge pull request #876 from takluyver/travis-reenable-35-pt27
Re-enable Python 3.5 testing on Travis
2015-07-24 19:59:43 -03:00
Thomas Kluyver
ed2c06c8cd Re-enable Python 3.5 testing on Travis
This is PR #875, but reissued against the pytest-2.7 branch.

According to the docs, it looks like this should work:
http://docs.travis-ci.com/user/languages/python/

I've explicitly specified beta 3, so it will need updating when future
betas come out. You could instead specify '3.5-dev', which would give
less need to update, but then it's less clear whether a change in status
is due to a change in pytest or in Python itself.
2015-07-24 15:50:45 -07:00
Bruno Oliveira
033def0a7a Workaround for cmdexec bug on Windows
This bug fails the entire pytest suite when executed with the
--lsof option in Python 2 on Windows.
2015-07-24 19:24:31 -03:00
Bruno Oliveira
9f94e443ff Merge remote-tracking branch 'upstream/pytest-2.7'
Conflicts:
	testing/conftest.py
2015-07-24 19:24:21 -03:00
Bruno Oliveira
91e8e59cea Merge pull request #860 from nicoddemus/warn-plugins-as-str-main
Make passing plugins as str objects a more obvious failure
2015-07-24 19:13:39 -03:00
Bruno Oliveira
4f1ae8c45e Workaround for cmdexec bug on Windows
This bug fails the entire pytest suite when executed with the
--lsof option in Python 2 on Windows.
2015-07-24 18:05:30 -03:00
Bruno Oliveira
b84fcbc85e Merge pull request #873 from hunse/fix-dev-versions
Allow non-integer version strings in `importorskip`
2015-07-24 16:08:15 -03:00
Eric Hunsberger
3cd19a7e45 Use monkeypatch for setting modules in tests
Instead of directly setting `sys.modules`. This ensures that they
get removed at the end of the test.
2015-07-24 12:34:06 -04:00
Eric Hunsberger
d104487282 importorskip: Allow non-integer version strings
Use `pkg_resources.parse_version` to parse version strings.
This can handle 'dev', 'rc', alpha and beta version strings,
among others.
2015-07-24 12:34:06 -04:00
Bruno Oliveira
35bbcc39a2 Interpret strings to "plugins" arg in pytest.main() as module names
See #855
2015-07-23 23:37:09 -03:00
Bruno Oliveira
ab9e246ab0 Avoid detecting conftest files upwards from setup.cfg/pytest.ini/tox.ini files by default
As discussed in #82
2015-07-23 23:21:07 -03:00
Bruno Oliveira
df29120abe Merge branch 'pytest-2.7'
Conflicts:
	_pytest/config.py
	_pytest/genscript.py
	_pytest/mark.py
	tox.ini
2015-07-23 22:35:51 -03:00
Bruno Oliveira
5a17e797c7 Fix basepython for tox envs "doctesting" and "coveralls"
Because we are currently using "nighly" python for travis, which
uses python 3.6 as the default interpreter. This breaks the environments
listed above because "inspect.getargspec" has been removed in py36.
2015-07-23 21:15:45 -03:00
Bruno Oliveira
ce96973ed5 Merge pull request #864 from pytest-dev/consider-color-yes-everywhere
Consider --color option in more places which deal with TerminalWriters
2015-07-23 08:03:52 -03:00
Bruno Oliveira
77844ec5f3 Fixed typo in CHANGELOG: "testdirs" -> "testpaths" 2015-07-22 12:14:49 -03:00
Floris Bruynooghe
0e26de2218 Merge pull request #867 from nicoddemus/autouse-yield-fixture-class
Fix autouse fixtures defined in a TestCase subclass
2015-07-22 01:25:21 +02:00
Bruno Oliveira
81f18f8a0f Fix flakes 2015-07-21 19:55:01 -03:00
Bruno Oliveira
0769bb4898 Make pastebin use _pytest.config.create_terminal_writer 2015-07-21 12:55:18 -03:00
Bruno Oliveira
31cfbac1f4 Fix autouse fixtures defined in a TestCase subclass 2015-07-20 19:28:54 -03:00
Anatoly Bubenkov
045274e647 remove merge artifact 2015-07-19 15:49:43 +02:00
Anatoly Bubenkov
6af7172204 Merge branch 'pytest-2.7' 2015-07-19 15:25:04 +02:00
Anatoly Bubenkov
412b43b216 Merge pull request #865 from nicoddemus/skipif-reason-multiple
Report correct reason when using multiple skip/xfail markers
2015-07-19 15:14:47 +02:00
Bruno Oliveira
de65737cb1 Fix flakes 2015-07-18 18:23:17 -03:00
Bruno Oliveira
953916df49 Report correct reason when using multiple skip/xfail markers 2015-07-18 18:18:45 -03:00
Bruno Oliveira
2f7d0f8bd9 Consider --color option in more places which deal with TerminalWriters 2015-07-18 16:39:55 -03:00
Bruno Oliveira
23aaa8a62c Allow py35 to fail on Travis until it is properly supported (fix2)
Conflicts:
	.travis.yml
2015-07-18 15:39:22 -03:00
Bruno Oliveira
604a39f5ef Allow py35 to fail on Travis until it is properly supported (fix2) 2015-07-18 15:30:16 -03:00
Bruno Oliveira
a5bc98136d Allow py35 to fail on Travis until it is properly supported (fix) 2015-07-18 15:18:57 -03:00
Bruno Oliveira
7dab2e1efe Allow py35 to fail on Travis until it is properly supported 2015-07-18 14:46:36 -03:00
Bruno Oliveira
da1d5712cf Fix broken links
Fix #857
2015-07-18 14:15:07 -03:00
Bruno Oliveira
30ff723d57 Merge pull request #859 from pytest-dev/py35fix-port
Py35fix port
2015-07-18 13:53:11 -03:00
Thomas Kluyver
e227950b06 Style fix 2015-07-18 13:18:38 -03:00
Thomas Kluyver
6719a818e7 Match .travis.yml env list to tox envs
Conflicts:
	.travis.yml
2015-07-18 13:18:36 -03:00
Thomas Kluyver
08432c3e97 No more failures 2015-07-18 13:17:29 -03:00
Thomas Kluyver
15497dcd77 OK, try running flakes with 2.7 2015-07-18 13:16:47 -03:00
Thomas Kluyver
8a0867c580 Try running flakes tests with Python 3.4 2015-07-18 13:16:47 -03:00
Thomas Kluyver
d4789f1ac4 Fix AST rewriting with starred expressions in function calls 2015-07-18 13:16:47 -03:00
Thomas Kluyver
26e7532756 Move Interrupted exception class out of Session 2015-07-18 13:16:47 -03:00
Thomas Kluyver
570c4cc55a No Starred node type on Python 2 2015-07-18 13:16:46 -03:00
Matthias Bussonnier
728d8fbdc5 generify 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
fad569ae1b simplify + fix 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
a4dbb27fab a test 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
ec5286ea8c nigh 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
195afa0733 try isntall 35 on tox 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
8bde0c5957 allow faillure on 35 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
b18e6439bd Ast Call signature changed on 3.5
fix issue 744 on bitbucket

port of merge request 296

https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35
https://bitbucket.org/pytest-dev/pytest/issue/744/

Conflicts:
	CHANGELOG
2015-07-18 13:15:46 -03:00
Floris Bruynooghe
5e0235946b Merge pull request #801 from Carreau/test-35
Support Python 3.5
2015-07-18 11:30:36 +01:00
Anatoly Bubenkov
360c09a1e7 appveyor scripts 2015-07-16 21:21:55 -03:00
Anatoly Bubenkov
85f7aa2f9b appveyor integration 2015-07-16 21:21:47 -03:00
Bruno Oliveira
a7b4ed89da Use functools.partial name explicitly and simplify the code a bit as asked in review 2015-07-16 20:37:04 -03:00
Bruno Oliveira
dcdc823dd2 Support for tests created with functools.partial
Fix #811
2015-07-16 20:37:03 -03:00
Bruno Oliveira
30e7104b05 Merge pull request #845 from nicoddemus/tmpdir-improvements
Introduce tmpdir_factory session fixture
2015-07-16 20:02:08 -03:00
Bruno Oliveira
330de0a93d Use a subdirectory in the TEMP directory to speed up tmpdir creation
Fix #105
2015-07-16 19:50:49 -03:00
Bruno Oliveira
aa25fb05a9 Make sure marks in subclasses don't change marks in superclasses
Fix #842
2015-07-16 19:29:16 -03:00
Thomas Kluyver
4462b83258 Style fix 2015-07-16 11:04:36 -07:00
Bruno Oliveira
94cdec2cfe Update sidebar links from Bitbucket to GitHub 2015-07-16 03:08:06 +00:00
Bruno Oliveira
ebf32ae8a9 Update CONTRIBUTING documentation with GitHub instructions 2015-07-16 03:07:16 +00:00
Bruno Oliveira
74bc50e97c Fix flakes 2015-07-15 20:45:35 -03:00
Bruno Oliveira
0f52856f99 Use a subdirectory in the TEMP directory to speed up tmpdir creation
Fix #105
2015-07-15 20:03:58 -03:00
Thomas Kluyver
077f0d3d66 Match .travis.yml env list to tox envs 2015-07-15 16:03:14 -07:00
Thomas Kluyver
37a09a6c30 No more failures 2015-07-15 15:58:42 -07:00
Thomas Kluyver
4e98d2b7f1 OK, try running flakes with 2.7 2015-07-15 15:52:28 -07:00
Thomas Kluyver
d73c8e6a5e Try running flakes tests with Python 3.4 2015-07-15 15:49:15 -07:00
Thomas Kluyver
195422f9c0 Fix AST rewriting with starred expressions in function calls 2015-07-15 15:31:35 -07:00
Thomas Kluyver
62ca4ae963 Move Interrupted exception class out of Session 2015-07-15 14:28:43 -07:00
Thomas Kluyver
35bea86c9f No Starred node type on Python 2 2015-07-15 14:11:21 -07:00
Matthias Bussonnier
d774f3ca86 generify 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
167625d24d simplify + fix 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
3bc6c0f936 a test 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
3cf82c6594 nigh 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
71a00c3223 try isntall 35 on tox 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
e2e29284f0 allow faillure on 35 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
26e64fc45c Ast Call signature changed on 3.5
fix issue 744 on bitbucket

port of merge request 296

https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35
https://bitbucket.org/pytest-dev/pytest/issue/744/
2015-07-15 13:49:40 -07:00
Bruno Oliveira
8f4f2c665d Mention pytest-runner in the docs
Fix #147
2015-07-13 21:59:37 -03:00
Bruno Oliveira
7a71b69a87 Pinning mock module to < 1.1 fo py26
It has been announced that mock>=1.1 will be
supported for python 2.7 only.

Conflicts:
	tox.ini
2015-07-13 13:43:03 -03:00
holger krekel (rather uses bitbucket/hpk42)
8910ec2870 Merge pull request #836 from nicoddemus/doctest-modules-fixtures
Consider session fixtures for doctest docstrings in modules
2015-07-13 18:02:51 +02:00
holger krekel (rather uses bitbucket/hpk42)
c5d26ae1bb Merge pull request #834 from nicoddemus/show-fixtures-bug
Fix: --fixtures only shows fixtures from first file
2015-07-13 17:59:42 +02:00
Bruno Oliveira
d6033037ac Consider session fixtures for doctest docstrings in modules
Fixes #768
2015-07-13 12:29:09 -03:00
Bruno Oliveira
5750ae784a Reword CHANGELOG a bit 2015-07-13 12:01:54 -03:00
holger krekel (rather uses bitbucket/hpk42)
194581ab5f Merge pull request #829 from nicoddemus/pep302-get-data-support
Add support for PEP302 get_data API
2015-07-13 16:55:16 +02:00
Bruno Oliveira
5ec2a17f08 --fixtures only shows fixtures from first file
Fix #833
2015-07-12 17:32:39 -03:00
Bruno Oliveira
7445c5bd70 Add support for PEP302 get_data API
Fix #808
2015-07-11 14:13:43 -03:00
Bruno Oliveira
1baa1a4d01 Merge pull request #822 from nicoddemus/extra-usage-info
Print inifile and rootdir when there's usage errors
2015-07-11 13:43:20 -03:00
Bruno Oliveira
40ec0ec97d Merge pull request #823 from nicoddemus/testdirs-option
Add new testdirs ini option
2015-07-11 13:42:54 -03:00
Bruno Oliveira
8a6aa5e17e Print inifile and rootdir when there's usage errors
Related to #821
2015-07-10 21:56:12 -03:00
Bruno Oliveira
854e603f84 Add new testdirs ini option
Fix #694
2015-07-10 21:52:47 -03:00
Bruno Oliveira
65c56d4c00 Pinning mock module to < 1.1 fo py26
It has been announced that mock>=1.1 will be
supported for python 2.7 only.
2015-07-10 19:57:48 -03:00
holger krekel (rather uses bitbucket/hpk42)
e7374c39ba Merge pull request #825 from nicoddemus/syntax-highlight-docs
Add syntax highlighting to missing snippets in the documentation and other fixes
2015-07-10 13:02:52 +02:00
Bruno Oliveira
0ee3ee7333 Add syntax highlighting to missing snippets in the documentation and other fixes
- Add explicit "code-block" for sessions without syntax highlight
- Moved Metafunc documentation to the class docstring and reused that in the
   docs;
2015-07-09 21:50:38 -03:00
Bruno Oliveira
0624a69964 Merge pull request #824 from hpk42/devpiupload
configure a plain "devpi upload" to create sdist and universal wheel
2015-07-09 12:08:52 -03:00
holger krekel
fe897c81be configure a plain "devpi upload" to create sdist and universal wheel 2015-07-09 14:56:56 +02:00
Bruno Oliveira
ae4c8b8635 Merge pull request #787 from pytest-dev/pluggy-bc-fix
Reintroduce get_plugin_manager() for backward-compatibility
2015-07-08 19:28:01 -03:00
Bruno Oliveira
d70a31168b Merge pull request #820 from jeffwidman/patch-1
Fix typo + grammar
2015-07-07 21:13:50 -03:00
Jeff Widman
4fa64a962e Fix typo + grammar 2015-07-07 16:35:21 -07:00
Bruno Oliveira
438ea86137 Fix flaky test_timing_function in Windows
This tests fails consistently on Windows due to small time
difference:

>       assert float(val) >= 0.03
E       assert 0.0299999713898 >= 0.03
E        +  where 0.0299999713898 = float('0.0299999713898')

Windows time.time() function is not as accurate as linux's,
so relaxed the test a bit.
2015-07-04 15:05:45 -03:00
Bruno Oliveira
d773c7498f Reword entry in the changelog to show case the new status bar colors better 2015-07-03 18:36:15 -03:00
Bruno Oliveira
4ef6cdfb68 Add small thanks note to Eric Siegerman 2015-07-03 18:31:15 -03:00
Bruno Oliveira
d10054a38d Merge branch 'esiegerman/summary_colors' of github.com:esiegerman/pytest 2015-07-03 18:29:17 -03:00
Bruno Oliveira
9293062221 Merge pull request #809 from kevincox/junitxml-file-attribute
Add `file` and `line` attributes to junit-xml output.
2015-07-03 08:24:36 -03:00
Kevin Cox
7fa27af408 Add file and line attributes to junit-xml output.
This adds the `file` and `line` attributes to the junit-xml output
which can be used by tooling to identify where tests come from. This can be
used for many things such as IDEs jumping to failures and test
runners evenly balancing tests among multiple executors.

Update test_junitxml.py

Foo.
2015-07-02 18:41:52 -04:00
Eric Siegerman
2c419c4790 Add CHANGELOG and AUTHORS entries 2015-07-02 15:47:03 -04:00
Eric Siegerman
afcad74be8 PEP8ify parametrized-test parameters 2015-07-02 13:39:05 -04:00
Eric Siegerman
6c395cb58c Default color is now yellow
Passing tests override that default, making the color green; but several other
"boring" statuses (xfailed, xpassed, deselected, skipped) have no effect.

Net effect: if only "boring" tests are seen, or no tests at all, the summary
bar is yellow.
2015-07-02 13:39:05 -04:00
Eric Siegerman
044d874c5b If there are warnings, make the status bar yellow
Also if we see any statuses the code doesn't know about.
2015-07-02 13:39:05 -04:00
Eric Siegerman
cb21d844d9 Add missing "error" status to the list 2015-07-02 13:39:05 -04:00
Eric Siegerman
0282da9ddf Refactor slightly
Check for the empty-key special case in the first loop,
not the second.
2015-07-02 13:39:05 -04:00
Eric Siegerman
e07144aeb4 Add tests for the empty-key special case 2015-07-02 13:39:05 -04:00
Eric Siegerman
7993afae46 test_summary_stats() now prints its parameter values
This makes it easier to identify failing tests.
2015-07-02 13:39:05 -04:00
Eric Siegerman
bfc3e48fd5 Factor out build_summary_stats_line(), and add tests
--HG--
branch : esiegerman/summary_colors
2015-07-02 13:39:04 -04:00
Bruno Oliveira
76497c2542 Fix plugins_index script and update plugins_index.txt 2015-06-30 19:01:28 -03:00
Bruno Oliveira
27a98788a8 start 2.7.3 CHANGELOG 2015-06-26 00:30:26 -03:00
Bruno Oliveira
9fb1637ce2 Test that deprecated_call keeps internal warnings structures intact 2015-06-26 00:26:59 -03:00
Pieter Mulder
75679f08c9 Update AUTHORS and CHANGELOG 2015-06-25 17:38:45 +02:00
Pieter Mulder
444cdfe6e3 Correct test_deprecated_call_preserves test.
Test if we preserve the correct functions.
2015-06-25 17:33:40 +02:00
Pieter Mulder
89b00d714f deprecated_call dit not revert functions back to original
Make sure that the warnings.warn and warnings.warn_explicit are reverted
to there original function after running the function in deprecated_call
2015-06-25 17:11:50 +02:00
Bruno Oliveira
3c2fd833ca Reintroduce get_plugin_manager() for backward-compatibility
PyCharm pytest runner depends on this function existing (see #787).

Added reference to get_plugin_manager() and PluginManager/PytestPluginManager to docs
2015-06-25 00:48:47 -03:00
holger krekel
01f5913826 try to fix coveralls env 2015-06-23 23:25:19 +02:00
holger krekel
0998170a01 check webhook again 2015-06-23 17:22:37 +02:00
holger krekel
28cf4c3226 small fix to test webhook 2015-06-23 17:14:08 +02:00
holger krekel (rather uses bitbucket/hpk42)
08613b621e Merge pull request #799 from pytest-dev/noconftest
Add a --noconftest option.
2015-06-23 16:53:22 +02:00
holger krekel
ee40ea5f6d Merge branch 'pytest-2.7'
Conflicts:
	AUTHORS
	_pytest/__init__.py
	setup.py
	testing/conftest.py
	tox.ini
2015-06-23 16:49:05 +02:00
Florian Bruhin
f78b6df8bc Return an empty list directly. 2015-06-23 14:05:44 +02:00
Florian Bruhin
8e0589af69 Add changelog entry. 2015-06-23 09:47:07 +02:00
Florian Bruhin
8664fc4102 Add a --noconftest option. 2015-06-23 09:38:54 +02:00
holger krekel (rather uses bitbucket/hpk42)
755d7b3787 Merge pull request #798 from radarhere/patch-1
Fixed typo
2015-06-23 09:00:42 +02:00
Andrew Murray
cba1ca6244 Fixed typo 2015-06-23 10:29:37 +10:00
Anatoly Bubenkov
e947732bde Merge pull request #791 from pytest-dev/coverage-config
Improve coverage score by tweaking options
2015-06-21 00:54:25 +02:00
Bruno Oliveira
b0491e1a5e Merge pull request #792 from iKevinY/svg-badges
Use SVGs for all README badges
2015-06-20 16:10:55 -03:00
Kevin Yap
190326c186 Use SVGs for all README badges 2015-06-20 10:58:38 -07:00
Bruno Oliveira
0008bcb877 Add coveragerc file for coverage options 2015-06-20 00:58:41 -03:00
Bruno Oliveira
80b5ebc398 Merge pull request #790 from pytest-dev/irc-notifications
Adjust IRC notifications to use #pytest.
2015-06-19 07:31:50 -03:00
Florian Bruhin
94332ed1d8 Adjust IRC notifications to use #pytest.
It seems #pytest-dev is quite dead. Also I set it to only send notifications on
changes (i.e. success -> failure and vice-versa).
2015-06-19 11:37:10 +02:00
holger krekel
13c01193d6 Merge remote-tracking branch 'origin/pytest-2.7' 2015-06-19 10:03:37 +02:00
Anatoly Bubenkov
4d6fef36f4 Merge branch 'pytest-2.7' 2015-06-17 13:42:41 +02:00
Bruno Oliveira
a0da7b9774 Mention issue 713 in the test's docstring 2015-06-16 20:42:49 -03:00
Punyashloka Biswal
0b0406fa85 Handle reports that don't have a reprcrash
Closes #713 (which happens because ReprFailDoctest
doesn't have a reprcrash)
2015-06-16 18:53:20 -04:00
Anatoly Bubenkov
f1245b094f Merge pull request #779 from pytest-dev/coveralls-shields-badge
Use shields.io badge for coveralls
2015-06-16 04:09:49 +02:00
Bruno Oliveira
c840e5c06e Use shields.io badge for coveralls
All badges have a similar look
2015-06-15 23:08:17 -03:00
Anatoly Bubenkov
c7573a92b2 Merge pull request #778 from pytest-dev/pypi-badge
Use pypi version badge from shields.io
2015-06-16 04:05:30 +02:00
Bruno Oliveira
84a476af87 Use pypi version badge from shields.io
The old one from pypip.in seems to always be broken as of late
2015-06-15 23:03:53 -03:00
Anatoly Bubenkov
e890d60459 appveyor badge 2015-06-16 03:28:12 +02:00
Anatoly Bubenkov
5dbe565b6d Merge branch 'master' of github.com:pytest-dev/pytest 2015-06-16 03:25:39 +02:00
Anatoly Bubenkov
8f09904c06 appveyor scripts 2015-06-16 03:25:28 +02:00
Anatoly Bubenkov
c0db143d4a Merge pull request #777 from bubenkoff/appveyor-integration
appveyor integration
2015-06-16 03:05:14 +02:00
Anatoly Bubenkov
534dbacc80 appveyor integration 2015-06-16 02:58:07 +02:00
Anatoly Bubenkov
af439e7d74 Merge pull request #775 from bubenkoff/pass-coveralls-env
pass coveralls env
2015-06-16 02:39:17 +02:00
Anatoly Bubenkov
b38cadbecd pass coveralls env 2015-06-16 02:38:52 +02:00
Anatoly Bubenkov
edfadf3a7c pass coveralls env 2015-06-16 01:12:24 +02:00
Anatoly Bubenkov
ae66683998 Merge pull request #773 from pytest-dev/remove-hg-files
Remove hgignore and hgtags files
2015-06-16 01:06:47 +02:00
Bruno Oliveira
4a8a4cb21b Merge pull request #770 from bubenkoff/add-travis-badge
add travis badge
2015-06-15 20:04:32 -03:00
Anatoly Bubenkov
ccebf8e1d6 add travis badge 2015-06-16 01:03:38 +02:00
Bruno Oliveira
8663be34b4 Remove hgignore and hgtags files 2015-06-15 20:02:41 -03:00
Bruno Oliveira
502a372405 Merge pull request #771 from pytest-dev/use-travis-containers
use travis containers
2015-06-15 19:56:34 -03:00
Bruno Oliveira
088ead2477 Merge pull request #772 from bubenkoff/split-travis-jobs
split travis jobs
2015-06-15 19:56:20 -03:00
Anatoly Bubenkov
f2494ecb3d split travis jobs 2015-06-16 00:49:55 +02:00
Anatoly Bubenkov
e9e01f2c9c use travis containers 2015-06-16 00:22:16 +02:00
Bruno Oliveira
d4e77faea7 Update sidebar links from Bitbucket to GitHub 2015-06-15 19:18:03 -03:00
Bruno Oliveira
ff58ae66c0 Update CONTRIBUTING documentation with GitHub instructions 2015-06-15 18:54:44 -03:00
Bruno Oliveira
b40f760cc3 Change links from bitbucket to GitHub 2015-06-15 18:28:31 -03:00
Floris Bruynooghe
42e7456780 Merged in RonnyPfannschmidt/pytest/regendoc-upgrade (pull request #303)
use regendoc normalization and regenerate docs
2015-06-10 10:44:38 +01:00
Ronny Pfannschmidt
43d27ec7ed use regendoc normalization and regenerate docs
--HG--
branch : regendoc-upgrade
2015-06-06 23:30:49 +02:00
Ronny Pfannschmidt
645ddc917f make regendoc a direct requirement 2015-06-06 14:15:23 +02:00
Ronny Pfannschmidt
33a7f03ccc sort AUTHORS and add Janne 2015-06-06 13:38:25 +02:00
Ronny Pfannschmidt
8b53a72dde add Janne`s changes to CHANGELOG 2015-06-06 11:53:34 +02:00
Ronny Pfannschmidt
8ee73e028f Merged in jpvanhal/pytest/include-setup-teardown-duration-in-junitxml (pull request #287)
Include setup and teardown in junitxml test durations
2015-06-06 11:50:37 +02:00
holger krekel
304fc4f222 Merged in issue735 (pull request #302)
use NameConstant node when it exists (fixes #735)
2015-06-04 07:48:53 +02:00
holger krekel
09edb813bc Close branch issue735
--HG--
branch : issue735
2015-06-04 07:48:53 +02:00
Benjamin Peterson
f3ad2e4ad2 use NameConstant node when it exists (fixes #735)
--HG--
branch : issue735
2015-06-03 18:07:10 -05:00
holger krekel
a2bb2ff164 port typo fix 2015-06-03 23:43:12 +02:00
holger krekel
c037f52878 fix issue741: make running output from testdir.run copy/pasteable 2015-06-03 23:40:42 +02:00
holger krekel
584cccf7ec Merged in nicoddemus/pytest/issue-741-pytester-output (pull request #293)
Make "running" output from testdir.run copy/pastable
2015-06-03 23:40:08 +02:00
holger krekel
c92467df2f add changelog for latest merge 2015-06-03 23:39:00 +02:00
holger krekel
ad08bf79e7 Merged in The-Compiler/pytest (pull request #298)
Fix monkeypatch.setenv with string and raising=False.
2015-06-03 23:37:26 +02:00
holger krekel
22c62dbd90 Merged in flub/pytest (pull request #299)
Ignore emacs' .dir-locals.el file
2015-06-03 23:34:13 +02:00
Floris Bruynooghe
d220be8468 Use platform.python_version() to show Python version number
This results in something like "3.5.0b2" for non-final releases
while still being "3.5.0" for final releases.
2015-05-31 20:31:31 +01:00
Floris Bruynooghe
b09be4e3ef Merged in flub/pytest (pull request #297)
Merge 2.7.1 release changes into default
2015-05-31 20:27:13 +01:00
Darren Yin
efce2ababa add in missing finalizer to doc example 2015-05-21 19:15:52 -07:00
Floris Bruynooghe
773b8b69dd Ignore emacs' .dir-locals.el file
This can be used to e.g. automatically set a specific syntax
checker or virtualenv to activate.
2015-05-20 10:33:13 +01:00
Florian Bruhin
ff8d787cd5 Fix monkeypatch.setenv with string and raising=False.
Fixes #746.
2015-05-19 22:59:49 +02:00
Floris Bruynooghe
d299f0d99f Merge changes from 2.7.1 release
This is mostly doc examples regeneration, other changes are:
- Add 2.7.1 tag to .hgtags
- Update changelog to use real versions
- Update HOWTORELEASE.rst
- Fix rst in doc/en/adopt.txt
- Fix CHANGELOG and release-2.7.0 rst formatting
2015-05-19 21:43:50 +01:00
holger krekel
3401049dea remove adopt pytest note 2015-05-12 13:36:43 +02:00
Bruno Oliveira
a7f880fa1f Make "running" output from testdir.run copy/pastable
fix 741

--HG--
branch : issue-741-pytester-output
2015-05-09 10:24:33 -03:00
holger krekel
c4bd3c672b another try at ignoring cx_freeze during other tox envs 2015-05-07 12:19:35 +02:00
holger krekel
52354b9ab5 try to ignore cx_freeze from regular pytest runs and see if travis is happy 2015-05-07 12:02:14 +02:00
holger krekel
3c187c844e make default depend on just published py/pluggy releases 2015-05-07 11:50:08 +02:00
Ronny Pfannschmidt
45ed4c726e Merged in hpk42/pytest-patches/plug30 (pull request #291)
use new pluggy api (now at 0.3.0) for adding hookcall monitoring
2015-05-07 11:24:39 +02:00
holger krekel
d9a44098ce use new pluggy api (now at 0.3.0) for adding hookcall monitoring
and reraise real keyboard interrupts during inline pytest runs
to allow for better stopping of the pytest tests.

--HG--
branch : plug30
2015-05-07 11:02:55 +02:00
holger krekel
b93abfb3d1 Merged in hpk42/pytest-patches/pluggy1 (pull request #290)
integrate pluggy as external plugin manager
2015-05-06 15:02:05 +02:00
holger krekel
bb8141e27c - make sure sub pytest runs use the same basetemp
- depend on pluggy < 0.3

--HG--
branch : pluggy1
2015-05-06 14:58:46 +02:00
holger krekel
bddc88f09e adapt to pluggy naming, rename pytest.hookspec_opts to pytest.hookspec,s ame with hookimpl_opts
--HG--
branch : pluggy1
2015-05-06 10:08:08 +02:00
holger krekel
5ea7f0342b Merged default into pluggy1
--HG--
branch : pluggy1
2015-05-05 22:29:53 +02:00
holger krekel
13db045fc4 merge 2015-05-05 21:55:39 +02:00
holger krekel
23538bcd31 - some more adaptation to most recent pluggy API
- avoid using pluggin underscore api
- show pluggy version in header

--HG--
branch : pluggy1
2015-05-05 21:53:04 +02:00
holger krekel
a4f2236b36 merge default
--HG--
branch : pluggy1
2015-05-05 14:52:16 +02:00
holger krekel
88538f13ba adapt for current API changes of pluggy
--HG--
branch : pluggy1
2015-05-04 15:08:41 +02:00
holger krekel
920b5afe45 now that we are going to have wheels, py source code might be not be installed
and the resulting genscript is useless

--HG--
branch : pluggy1
2015-05-04 14:42:01 +02:00
holger krekel
e4b9603fa8 Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288)
monkeypatch.chdir docstring punctuation
2015-05-04 14:28:32 +02:00
holger krekel
ef7f627573 merging pytest-2.7 2015-05-04 14:27:09 +02:00
Janne Vanhala
93628fc0eb Include setup and teardown in junitxml test durations
--HG--
branch : include-setup-teardown-duration-in-junitxml
2015-05-01 14:55:52 +03:00
famousgarkin
47936643c9 monkeypatch.chdir docstring punctuation
--HG--
branch : famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841
2015-05-01 04:13:06 +00:00
Floris Bruynooghe
aa2ffb9805 Merge fix for issue 731 from pytest-2.7 2015-04-30 21:13:03 +01:00
holger krekel
25963e0544 adapt pytest to pluggy's decoratorclass branch
--HG--
branch : pluggy1
2015-04-29 16:40:52 +02:00
holger krekel
5ee7ee0850 adapt pytest to using pluggy (current master)
--HG--
branch : pluggy1
2015-04-29 16:40:51 +02:00
holger krekel
d6670bd6a8 Merged in hpk42/pytest-patches/testrefactor (pull request #284)
majorly refactor pytester and speed/streamline  tests
2015-04-29 16:32:28 +02:00
holger krekel
9aec5cd52d use runpytest() instead of runpytest_inprocess if a test can run as subprocess as well
--HG--
branch : testrefactor
2015-04-28 12:05:08 +02:00
holger krekel
4d5161444c merge
--HG--
branch : testrefactor
2015-04-28 11:56:57 +02:00
holger krekel
db5649ec6a streamline pytester API majorly:
- integrate conftest into pytester plugin
- introduce runpytest() to either call runpytest_inline (default) or
  runpytest_subprocess (python -m pytest)
- move testdir.inline_runsource1 to pdb tests
- strike some unneccessary methods.
- a new section "writing plugins" and some better pytester docs

--HG--
branch : testrefactor
2015-04-28 11:54:53 +02:00
holger krekel
a8afba054a - refine lsof checking
- make runpytest() create an inline testing process instead of
  a subprocess one

--HG--
branch : testrefactor
2015-04-28 11:54:46 +02:00
holger krekel
d3e363b97a - make API between runpytest() and inline_run() more similar
- shift a number of tests to become inline_run() tests

--HG--
branch : testrefactor
2015-04-28 11:54:45 +02:00
holger krekel
2e3e1e9b7e Merged in hpk42/pytest-patches/plugtestfix (pull request #283)
make test suite more tolerable against xdist causing warnings itself
2015-04-28 11:52:53 +02:00
holger krekel
424e5d1394 make test suite more tolerable against xdist causing warnings itself (which it does
currently)

--HG--
branch : plugtestfix
2015-04-27 15:06:47 +02:00
Floris Bruynooghe
2d8f115d8c Merged in hpk42/pytest-patches/more_plugin (pull request #282)
another major pluginmanager refactor and docs
2015-04-27 13:17:40 +01:00
holger krekel
b2d66b9e7b simplify load_setuptools_entrypoints and refine comments/docstrings
--HG--
branch : more_plugin
2015-04-27 14:10:33 +02:00
holger krekel
c54afbe42e deprecate and warn about __multicall__ usage in hooks, refine docs about hook ordering,
make hookwrappers respect tryfirst/trylast

--HG--
branch : more_plugin
2015-04-27 12:50:34 +02:00
holger krekel
dea1c96031 actually revert back to using older simpler method for subset hook
calling.  It is slightly more inefficient but easier to implement and
read.

--HG--
branch : more_plugin
2015-04-26 17:17:59 +02:00
holger krekel
16c922bf53 merge contribution fix 2015-04-26 01:06:17 +02:00
holger krekel
d2ea7387f2 re-add tryfirst/trylast marker documentation, mark it as to be removed
--HG--
branch : more_plugin
2015-04-26 00:47:24 +02:00
holger krekel
8e009ee31c move consider_setuptools_entrypoints to core pluginmanager
--HG--
branch : more_plugin
2015-04-26 00:41:29 +02:00
holger krekel
0c961deeaa fix some doc strings
--HG--
branch : more_plugin
2015-04-26 00:22:34 +02:00
holger krekel
32165d82b1 introduce a new subset_hook_caller instead of remove make_hook_caller
and adapat and refine conftest/global plugin management accordingly

--HG--
branch : more_plugin
2015-04-26 00:10:52 +02:00
holger krekel
d422247433 specialize make_hook_caller to work with a subset of the registered plugins.
--HG--
branch : more_plugin
2015-04-25 22:13:42 +02:00
holger krekel
a042c57227 ensure proper get_name references
--HG--
branch : more_plugin
2015-04-25 20:42:41 +02:00
holger krekel
3a1374e69c simplify plugins bookkeeping further, refine API
--HG--
branch : more_plugin
2015-04-25 20:17:32 +02:00
holger krekel
1c0582eaa7 simplify addition of method and scanning of plugins
--HG--
branch : more_plugin
2015-04-25 18:15:42 +02:00
holger krekel
1e883f5979 simplify tracing mechanics by simply going through an indirection
--HG--
branch : more_plugin
2015-04-25 18:15:39 +02:00
holger krekel
9c5495832c avoid direct circular reference between config and pluginmanager
--HG--
branch : more_plugin
2015-04-25 18:14:41 +02:00
holger krekel
7364647f2f fix issue732: make sure removed plugins remove all hook callers.
--HG--
branch : more_plugin
2015-04-25 18:14:39 +02:00
holger krekel
4e116ed503 make pytest_plugin_registered a historic hook
--HG--
branch : more_plugin
2015-04-25 13:38:30 +02:00
holger krekel
e7a2e53108 Streamline data structures
--HG--
branch : more_plugin
2015-04-25 13:38:29 +02:00
holger krekel
2f8a1aed6e properly perform hook calls with extra methods
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
a63585dcab introduce historic hook spec which will memorize calls to a hook
in order to call them on later registered plugins

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
ea50ef1588 split plugin documentation into "using" and "writing plugins",
referencing each other.  Also add tryfirst/trylast examples.

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
d2a5c7f99b add documented hookimpl_opts and hookspec_opts decorators
so that one doesn't have to use pytest.mark or function-attribute setting anymore

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
bbbb6dc2e3 remove _do_register indirection between PluginManager and PytestPluginManager
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
f41528433b also incrementally remove plugins from hook callers
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
02a4042dca incrementally update hook call lists instead of regenerating the whole
list on each registered plugin

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
b03c1342ac allow to register plugins with hooks that are only added later
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
d8e91d9fee Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)
Refactor pluginmanagement
2015-04-25 09:08:21 +02:00
holger krekel
9020bf48b7 remove useless check
--HG--
branch : plugin_no_pytest
2015-04-24 14:09:57 +02:00
holger krekel
237ac8562f minimize HookCaller attributes: avoid passing in hookrelay to HookCallers
--HG--
branch : plugin_no_pytest
2015-04-24 13:02:49 +02:00
holger krekel
feb4b2249a remove some redundancy when parsing import spec
--HG--
branch : plugin_no_pytest
2015-04-23 13:15:34 +02:00
holger krekel
95dd2eb1da streamline and document handling of builtin module special casing.
--HG--
branch : plugin_no_pytest
2015-04-23 12:39:11 +02:00
holger krekel
8fde2f98ae merge default
--HG--
branch : plugin_no_pytest
2015-04-23 12:35:15 +02:00
holger krekel
2b80caf1af merge cxfreeze fix 2015-04-23 12:07:12 +02:00
Floris Bruynooghe
e450218daa Merge _pytest fixture reintroduction from pytest-2.7 branch
This was accidentally removed while some plugins depend on it.
2015-04-23 00:55:58 +01:00
holger krekel
27589eb7e1 reshuffle pluginmanager methods and add some docstrings.
--HG--
branch : plugin_no_pytest
2015-04-22 16:42:41 +02:00
holger krekel
d5db9faba8 shuffle PluginManager method order to first have the public API
and then the internal.

--HG--
branch : plugin_no_pytest
2015-04-22 16:34:42 +02:00
holger krekel
715a235b45 remove shutdown logic from PluginManager and add a add_cleanup() API
for the already existing cleanup logic of the config object.
This simplifies lifecycle management as we don't keep two
layers of shutdown functions and also simplifies the pluginmanager
interface.

also add some docstrings.

--HG--
branch : plugin_no_pytest
2015-04-22 16:33:20 +02:00
holger krekel
f746c190ac slight cleanup of plugin register() functionality
--HG--
branch : plugin_no_pytest
2015-04-22 14:54:28 +02:00
holger krekel
d632a0d5c2 merge conftest management into PytestPluginManager
--HG--
branch : plugin_no_pytest
2015-04-22 14:15:42 +02:00
holger krekel
894d7dca22 avoid undocumented special casing of "pytest_" prefix
--HG--
branch : plugin_no_pytest
2015-04-22 13:44:37 +02:00
holger krekel
20d6c0b560 simplify exception capturing
--HG--
branch : plugin_no_pytest
2015-04-22 13:37:42 +02:00
holger krekel
c08dfdc330 move bookkeeping of conftest plugins in core pluginmanager to PytestPluginManager
--HG--
branch : plugin_no_pytest
2015-04-22 13:33:01 +02:00
holger krekel
7049ebe4e2 avoid prepend to register api as it's redundant wrt to hooks
--HG--
branch : plugin_no_pytest
2015-04-22 13:31:46 +02:00
holger krekel
1ef49ac5ab minimize HookRelay to become a pure container, refactor initialization and
tests of plugin management to be a bit better split between pytest
and pytest-independent bits

--HG--
branch : plugin_no_pytest
2015-04-22 13:31:46 +02:00
holger krekel
db650de372 remove redundant py check as our setup.py excludes py <=1.4 already
--HG--
branch : plugin_no_pytest
2015-04-22 10:04:13 +02:00
Floris Bruynooghe
d9885388d0 Merge pull request #277 from pytest-2.7 branch
This removes the drone.io badge from the README as it doesn't make
sense to have it on the cheeseshop status page.

Fixes issue #726.
2015-04-21 15:04:08 +01:00
Floris Bruynooghe
240cd1f28d Merge cleaning of sys.modules after pytester.inline_run()
Merged in schettino72/pytest/pytester-inline-run-clean-sys-modules
(pull request #278).
2015-04-21 11:00:32 +01:00
Floris Bruynooghe
31af381c56 Merged in hpk42/pytest-patches/prefer_installed (pull request #275)
change test module importing behaviour to append to sys.path
2015-04-21 10:47:33 +01:00
Eduardo Schettino
bc0ecd1d06 pytester: add method `TmpTestdir.delete_loaded_modules()`
, and call it from ``inline_run()`` to allow temporary modules to be reloaded.

--HG--
branch : pytester-inline-run-clean-sys-modules
2015-04-21 10:18:04 +08:00
Eduardo Schettino
c792ab8aed fix regendoc repository location on requirements-docs.txt.
--HG--
branch : pytester-inline-run-clean-sys-modules
2015-04-21 10:16:04 +08:00
holger krekel
bac1ccd5b3 merge fix of issue553 on pytest-2.7 2015-04-17 22:32:49 +02:00
holger krekel
5c8e5acf9d change test module importing behaviour to append to sys.path
instead of prepending.  This better allows to run test modules
against installated versions of a package even if the package
under test has the same import root.  In this example::

   testing/__init__.py
   testing/test_pkg_under_test.py
   pkg_under_test/

the tests will preferrably run against the installed version
of pkg_under_test whereas before they would always pick
up the local version.

--HG--
branch : prefer_installed
2015-04-17 22:25:35 +02:00
Floris Bruynooghe
fb07a09964 Merge pull request #274 from pytest-2.7
fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
2015-04-17 11:10:47 +01:00
Floris Bruynooghe
c7a60af666 Use capital Y as the tests look for that 2015-04-13 09:08:10 +01:00
Floris Bruynooghe
09dff73607 Merge pytest-2.7 for issue660 fix 2015-04-13 00:04:53 +01:00
Floris Bruynooghe
ddffa9d1cc Merge wheel support from pytest-2.7 branch
Merged in pytest-2.7 (pull request #269).
2015-04-12 23:47:10 +01:00
Floris Bruynooghe
bf0b70aa66 Some docstrings for the pytester plugin
These aren't quite complete but are a jolly good start anyway.  It
seems better to commit this now then leave it lingering until it
gets lost.
2015-04-11 17:07:37 +01:00
holger krekel
74019025b9 shift default to 2.8.0.dev 2015-04-04 16:32:25 +02:00
holger krekel
1ab93a5f1d Merged in release-checklist (pull request #266)
add a release checklist
2015-04-04 16:27:07 +02:00
holger krekel
d9d932aef5 Merged in yield-experimental-docs (pull request #267)
Removed note about yield fixtures being experimental
2015-04-04 16:25:24 +02:00
holger krekel
421c43a873 Close branch yield-experimental-docs
--HG--
branch : yield-experimental-docs
2015-04-04 16:25:24 +02:00
Bruno Oliveira
6558c7245f Brought back discussion session
Reworded it a bit to bring it to par with the current status

--HG--
branch : yield-experimental-docs
2015-04-03 19:44:06 -03:00
Bruno Oliveira
b3d646455a Fixed straightforward spelling
--HG--
branch : yield-experimental-docs
2015-04-03 17:06:51 -03:00
Bruno Oliveira
161d9e5971 Removed "discussion" session
Kept a note about exceptions after yield not being reraised

--HG--
branch : yield-experimental-docs
2015-04-03 16:59:33 -03:00
Bruno Oliveira
30260361dd Reviewed wording about yield being a "new" feature
--HG--
branch : yield-experimental-docs
2015-04-03 16:55:10 -03:00
Bruno Oliveira
c1b935adbd Removed note about yield fixtures being experimental
--HG--
branch : yield-experimental-docs
2015-04-03 16:28:20 -03:00
203 changed files with 9108 additions and 4243 deletions

7
.coveragerc Normal file
View File

@@ -0,0 +1,7 @@
[run]
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

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
CHANGELOG merge=union

4
.gitignore vendored
View File

@@ -17,16 +17,18 @@ include/
*.orig
*~
.eggs/
doc/*/_build
build/
dist/
*.egg-info
issue/
env/
.env/
3rdparty/
.tox
.cache
.coverage
.ropeproject
.idea

View File

@@ -1,39 +0,0 @@
# Automatically generated by `hgimportsvn`
syntax:glob
.svn
.hgsvn
# Ignore local virtualenvs
syntax:glob
lib/
bin/
include/
.Python/
.env/
# These lines are suggested according to the svn:ignore property
# Feel free to enable them by uncommenting them
syntax:glob
*.pyc
*.pyo
*.swp
*.html
*.class
*.orig
*~
doc/*/_build
build/
dist/
testing/cx_freeze/build
testing/cx_freeze/cx_freeze_source
*.egg-info
issue/
env/
env3/
3rdparty/
.tox
.cache
.coverage
.ropeproject
*.sublime-*

77
.hgtags
View File

@@ -1,77 +0,0 @@
52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
e9e127acd6f0497324ef7f40cfb997cad4c4cd17 2.0.0
e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
84e5c54b72448194a0f6f815da7e048ac8019d50 2.0.2
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
363e5a5a59c803e6bc176a6f9cc4bf1a1ca2dab0 2.0.3
e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
5864412c6f3c903384243bd315639d101d7ebc67 2.1.2
12a05d59249f80276e25fd8b96e8e545b1332b7a 2.1.3
1522710369337d96bf9568569d5f0ca9b38a74e0 2.2.0
3da8cec6c5326ed27c144c9b6d7a64a648370005 2.2.1
92b916483c1e65a80dc80e3f7816b39e84b36a4d 2.2.2
3c11c5c9776f3c678719161e96cc0a08169c1cb8 2.2.3
ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1
8738b828dec53937765db71951ef955cca4c51f6 2.3.2
7fe44182c434f8ac89149a3c340479872a5d5ccb 2.3.3
ef299e57f24218dbdd949498d7e660723636bcc3 2.3.4
fc3a793e87ec907000a47ea0d3a372a2fe218c0a 2.3.5
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
0000000000000000000000000000000000000000 1.4.14
0000000000000000000000000000000000000000 1.4.14
0000000000000000000000000000000000000000 1.4.14
af860de70cc3f157ac34ca1d4bf557a057bff775 2.4.0
8828c924acae0b4cad2e2cb92943d51da7cb744a 2.4.1
8d051f89184bfa3033f5e59819dff9f32a612941 2.4.2
a064ad64d167508a8e9e73766b1a4e6bd10c85db 2.5.0
039d543d1ca02a716c0b0de9a7131beb8021e8a2 2.5.1
421d3b4d150d901de24b1cbeb8955547b1420483 2.5.2
60725b17a9d1af4100abb8be3f9f4ddf6262bf34 2.6.0
60725b17a9d1af4100abb8be3f9f4ddf6262bf34 2.6.0
88af949b9611494e2c65d528f9e565b00fb7e8ca 2.6.0
a4f9639702baa3eb4f3b16e162f74f7b69f3f9e1 2.6.1
a4f25c5e649892b5cc746d21be971e4773478af9 2.6.2
2967aa416a4f3cdb65fc75073a2a148e1f372742 2.6.3
f03b6de8325f5b6c35cea7c3de092f134ea8ef07 2.6.4
7ed701fa2fb554bfc0618d447dfec700cc697407 2.7.0
edc1d080bab5a970da8f6c776be50768829a7b09 2.7.1

View File

@@ -1,35 +1,40 @@
sudo: false
language: python
python:
- '3.5'
# command to install dependencies
install: "pip install -U tox"
# # command to run tests
env:
matrix:
- TESTENV=coveralls
- TESTENV=doctesting
- TESTENV=flakes
- TESTENV=py26
- TESTENV=py27
- TESTENV=py34
- TESTENV=pypy
- TESTENV=py27-pexpect
- TESTENV=py33-pexpect
- TESTENV=py27-nobyte
- TESTENV=py33
- TESTENV=py27-xdist
- TESTENV=py33-xdist
- TESTENV=py27
- TESTENV=py27-trial
- TESTENV=py33
- TESTENV=py33-trial
# inprocess tests by default were introduced in 2.8 only;
# this TESTENV should be enabled when merged back to master
#- TESTENV=py27-subprocess
- TESTENV=doctesting
- TESTENV=py27-cxfreeze
- TESTENV=coveralls
script: tox --recreate -i ALL=https://devpi.net/hpk/dev/ -e $TESTENV
- TESTENV=py27-nobyte
- TESTENV=py27-pexpect
- TESTENV=py27-subprocess
- TESTENV=py27-trial
- TESTENV=py27-xdist
- TESTENV=py33
- TESTENV=py33
- TESTENV=py34
- TESTENV=py35-pexpect
- TESTENV=py35-trial
- TESTENV=py35-xdist
- TESTENV=py35
- TESTENV=pypy
script: tox --recreate -e $TESTENV
notifications:
irc:
- "chat.freenode.net#pytest-dev"
channels:
- "chat.freenode.net#pytest"
on_success: change
on_failure: change
skip_join: true
email:
- pytest-commit@python.org

20
AUTHORS
View File

@@ -3,6 +3,7 @@ merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Abhijeet Kasurde
Anatoly Bubenkoff
Andreas Zeidler
Andy Freeland
@@ -14,6 +15,7 @@ Bob Ippolito
Brian Dorsey
Brian Okken
Brianna Laugher
Bruno Oliveira
Carl Friedrich Bolz
Charles Cloud
Chris Lamb
@@ -25,7 +27,13 @@ Daniel Nuri
Dave Hunt
David Mohr
Edison Gustavo Muenz
Eduardo Schettino
Elizaveta Shashkova
Eric Hunsberger
Eric Siegerman
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
Graham Horler
Grig Gheorghiu
Guido Wesdorp
@@ -33,15 +41,22 @@ Harald Armin Massa
Ian Bicking
Jaap Broekhuizen
Jan Balster
Janne Vanhala
Jason R. Coombs
Jurko Gospodnetić
Katarzyna Jachim
Kevin Cox
Lee Kamentsky
Maciek Fijalkowski
Maho
Marc Schlaich
Mark Abramowitz
Markus Unterwaditzer
Martijn Faassen
Michael Birtwell
Michael Droettboom
Nicolas Delaby
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Ralf Schmitt
@@ -51,3 +66,8 @@ Samuele Pedroni
Tom Viner
Trevor Bekolay
Wouter van Ackooy
David Díaz-Barquero
Eric Hunsberger
Simon Gomizelj
Russel Winder
Ben Webb

335
CHANGELOG
View File

@@ -1,3 +1,332 @@
2.8.3
-----
- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
support the @unittest.skip decorator on functions and methods.
Thanks Lee Kamentsky for the PR.
- fix #1035: collecting tests if test module level obj has __getattr__().
Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
- fix #331: don't collect tests if their failure cannot be reported correctly
e.g. they are a callable instance of a class.
- fix #1133: fixed internal error when filtering tracebacks where one entry
belongs to a file which is no longer available.
Thanks Bruno Oliveira for the PR.
- enhancement made to highlight in red the name of the failing tests so
they stand out in the output.
Thanks Gabriel Reis for the PR.
- add more talks to the documentation
- extend documentation on the --ignore cli option
- use pytest-runner for setuptools integration
- minor fixes for interaction with OS X El Capitan
system integrity protection (thanks Florian)
2.8.2
-----
- fix #1085: proper handling of encoding errors when passing encoded byte
strings to pytest.parametrize in Python 2.
Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
- fix #1087: handling SystemError when passing empty byte strings to
pytest.parametrize in Python 3.
Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
- fix #995: fixed internal error when filtering tracebacks where one entry
was generated by an exec() statement.
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
for contributing and Bruno Oliveira for the PR.
- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
Oliveira for the PR.
2.8.1
-----
- fix #1034: Add missing nodeid on pytest_logwarning call in
addhook. Thanks Simon Gomizelj for the PR.
- 'deprecated_call' is now only satisfied with a DeprecationWarning or
PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0
made it accept only DeprecationWarning (but not PendingDeprecationWarning).
Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR.
- fix issue #1073: avoid calling __getattr__ on potential plugin objects.
This fixes an incompatibility with pytest-django. Thanks Andreas Pelme,
Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel
for the fix.
- Fix issue #704: handle versionconflict during plugin loading more
gracefully. Thanks Bruno Oliveira for the PR.
- Fix issue #1064: ""--junitxml" regression when used with the
"pytest-xdist" plugin, with test reports being assigned to the wrong tests.
Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
- (experimental) adapt more SEMVER style versioning and change meaning of
master branch in git repo: "master" branch now keeps the bugfixes, changes
aimed for micro releases. "features" branch will only be be released
with minor or major pytest releases.
- Fix issue #766 by removing documentation references to distutils.
Thanks Russel Winder.
- Fix issue #1030: now byte-strings are escaped to produce item node ids
to make them always serializable.
Thanks Andy Freeland for the report and Bruno Oliveira for the PR.
- Python 2: if unicode parametrized values are convertible to ascii, their
ascii representation is used for the node id.
- Fix issue #411: Add __eq__ method to assertion comparison example.
Thanks Ben Webb.
- fix issue 877: properly handle assertion explanations with non-ascii repr
Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
- fix issue 1029: transform errors when writing cache values into pytest-warnings
2.8.0
-----------------------------
- new ``--lf`` and ``-ff`` options to run only the last failing tests or
"failing tests first" from the last run. This functionality is provided
through porting the formerly external pytest-cache plugin into pytest core.
BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist
data between test runs be aware that we don't serialize sets anymore.
Thanks Ronny Pfannschmidt for most of the merging work.
- "-r" option now accepts "a" to include all possible reports, similar
to passing "fEsxXw" explicitly (isse960).
Thanks Abhijeet Kasurde for the PR.
- avoid python3.5 deprecation warnings by introducing version
specific inspection helpers, thanks Michael Droettboom.
- fix issue562: @nose.tools.istest now fully respected.
- fix issue934: when string comparison fails and a diff is too large to display
without passing -vv, still show a few lines of the diff.
Thanks Florian Bruhin for the report and Bruno Oliveira for the PR.
- fix issue736: Fix a bug where fixture params would be discarded when combined
with parametrization markers.
Thanks to Markus Unterwaditzer for the PR.
- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
``u`` prefix is stripped from unicode strings in expected doctest output. This
allows doctests which use unicode to run in Python 2 and 3 unchanged.
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
- parametrize now also generates meaningful test IDs for enum, regex and class
objects (as opposed to class instances).
Thanks to Florian Bruhin for the PR.
- Add 'warns' to assert that warnings are thrown (like 'raises').
Thanks to Eric Hunsberger for the PR.
- Fix issue683: Do not apply an already applied mark. Thanks ojake for the PR.
- Deal with capturing failures better so fewer exceptions get lost to
/dev/null. Thanks David Szotten for the PR.
- fix issue730: deprecate and warn about the --genscript option.
Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR.
- fix issue751: multiple parametrize with ids bug if it parametrizes class with
two or more test methods. Thanks Sergey Chipiga for reporting and Jan
Bednarik for PR.
- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini
files and upwards by default (--confcutdir can still be set to override this).
Thanks Bruno Oliveira for the PR.
- fix issue768: docstrings found in python modules were not setting up session
fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
- added `tmpdir_factory`, a session-scoped fixture that can be used to create
directories under the base temporary directory. Previously this object was
installed as a `_tmpdirhandler` attribute of the `config` object, but now it
is part of the official API and using `config._tmpdirhandler` is
deprecated.
Thanks Bruno Oliveira for the PR.
- fix issue808: pytest's internal assertion rewrite hook now implements the
optional PEP302 get_data API so tests can access data files next to them.
Thanks xmo-odoo for request and example and Bruno Oliveira for
the PR.
- rootdir and inifile are now displayed during usage errors to help
users diagnose problems such as unexpected ini files which add
unknown options being picked up by pytest. Thanks to Pavel Savchenko for
bringing the problem to attention in #821 and Bruno Oliveira for the PR.
- Summary bar now is colored yellow for warning
situations such as: all tests either were skipped or xpass/xfailed,
or no tests were run at all (this is a partial fix for issue500).
- fix issue812: pytest now exits with status code 5 in situations where no
tests were run at all, such as the directory given in the command line does
not contain any tests or as result of a command line option filters
all out all tests (-k for example).
Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR.
- Summary bar now is colored yellow for warning
situations such as: all tests either were skipped or xpass/xfailed,
or no tests were run at all (related to issue500).
Thanks Eric Siegerman.
- New `testpaths` ini option: list of directories to search for tests
when executing pytest from the root directory. This can be used
to speed up test collection when a project has well specified directories
for tests, being usually more practical than configuring norecursedirs for
all directories that do not contain tests.
Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR.
- fix issue713: JUnit XML reports for doctest failures.
Thanks Punyashloka Biswal.
- fix issue970: internal pytest warnings now appear as "pytest-warnings" in
the terminal instead of "warnings", so it is clear for users that those
warnings are from pytest and not from the builtin "warnings" module.
Thanks Bruno Oliveira.
- Include setup and teardown in junitxml test durations.
Thanks Janne Vanhala.
- fix issue735: assertion failures on debug versions of Python 3.4+
- new option ``--import-mode`` to allow to change test module importing
behaviour to append to sys.path instead of prepending. This better allows
to run test modules against installated versions of a package even if the
package under test has the same import root. In this example::
testing/__init__.py
testing/test_pkg_under_test.py
pkg_under_test/
the tests will run against the installed version
of pkg_under_test when ``--import-mode=append`` is used whereas
by default they would always pick up the local version. Thanks Holger Krekel.
- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it
from ``inline_run()`` to allow temporary modules to be reloaded.
Thanks Eduardo Schettino.
- internally refactor pluginmanager API and code so that there
is a clear distinction between a pytest-agnostic rather simple
pluginmanager and the PytestPluginManager which adds a lot of
behaviour, among it handling of the local conftest files.
In terms of documented methods this is a backward compatible
change but it might still break 3rd party plugins which relied on
details like especially the pluginmanager.add_shutdown() API.
Thanks Holger Krekel.
- pluginmanagement: introduce ``pytest.hookimpl`` and
``pytest.hookspec`` decorators for setting impl/spec
specific parameters. This substitutes the previous
now deprecated use of ``pytest.mark`` which is meant to
contain markers for test functions only.
- write/refine docs for "writing plugins" which now have their
own page and are separate from the "using/installing plugins`` page.
- fix issue732: properly unregister plugins from any hook calling
sites allowing to have temporary plugins during test execution.
- deprecate and warn about ``__multicall__`` argument in hook
implementations. Use the ``hookwrapper`` mechanism instead already
introduced with pytest-2.7.
- speed up pytest's own test suite considerably by using inprocess
tests by default (testrun can be modified with --runpytest=subprocess
to create subprocesses in many places instead). The main
APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess"
and "runpytest_inprocess" if you need a particular way of running
the test. In all cases you get back a RunResult but the inprocess
one will also have a "reprec" attribute with the recorded events/reports.
- fix monkeypatch.setattr("x.y", raising=False) to actually not raise
if "y" is not a pre-existing attribute. Thanks Florian Bruhin.
- fix issue741: make running output from testdir.run copy/pasteable
Thanks Bruno Oliveira.
- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
- add ``file`` and ``line`` attributes to JUnit-XML output.
- fix issue890: changed extension of all documentation files from ``txt`` to
``rst``. Thanks to Abhijeet for the PR.
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
Thanks Elizaveta239.
- fix issue890: changed extension of all documentation files from ``txt`` to
``rst``. Thanks to Abhijeet for the PR.
- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED
rather than PASSED.
Thanks Thomas Grainger for the report and Bruno Oliveira for the PR.
- issue951: add new record_xml_property fixture, that supports logging
additional information on xml output. Thanks David Diaz for the PR.
- issue949: paths after normal options (for example `-s`, `-v`, etc) are now
properly used to discover `rootdir` and `ini` files.
Thanks Peter Lauri for the report and Bruno Oliveira for the PR.
2.7.3 (compared to 2.7.2)
-----------------------------
- Allow 'dev', 'rc', or other non-integer version strings in `importorskip`.
Thanks to Eric Hunsberger for the PR.
- fix issue856: consider --color parameter in all outputs (for example
--fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR.
- fix issue855: passing str objects as `plugins` argument to pytest.main
is now interpreted as a module name to be imported and registered as a
plugin, instead of silently having no effect.
Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks
Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
Thomas Kluyver.
- fix issue842: applying markers in classes no longer propagate this markers
to superclasses which also have markers.
Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
- preserve warning functions after call to pytest.deprecated_call. Thanks
Pieter Mulder for PR.
- fix issue854: autouse yield_fixtures defined as class members of
unittest.TestCase subclasses now work as expected.
Thannks xmo-odoo for the report and Bruno Oliveira for the PR.
- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
fixtures declared on the first one.
Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR.
- fix issue863: skipped tests now report the correct reason when a skip/xfail
condition is met when using multiple markers.
Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR.
- optimized tmpdir fixture initialization, which should make test sessions
faster (specially when using pytest-xdist). The only visible effect
is that now pytest uses a subdirectory in the $TEMP directory for all
directories created by this fixture (defaults to $TEMP/pytest-$USER).
Thanks Bruno Oliveira for the PR.
2.7.2 (compared to 2.7.1)
-----------------------------
@@ -21,7 +350,7 @@
Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
- fix issue718: failed to create representation of sets containing unsortable
elements in python 2. Thanks Edison Gustavo Muenz
elements in python 2. Thanks Edison Gustavo Muenz.
- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
which has a refined algorithm for traceback generation.
@@ -120,7 +449,7 @@
it from the "decorator" case. Thanks Tom Viner.
- "python_classes" and "python_functions" options now support glob-patterns
for test discovery, as discussed in issue600. Thanks Ldiary Translations.
for test discovery, as discussed in issue600. Thanks Ldiary Translations.
- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
@@ -1929,7 +2258,7 @@ v1.0.0b1
* introduced new "funcarg" setup method,
see doc/test/funcarg.txt
* introduced plugin architecuture and many
* introduced plugin architecture and many
new py.test plugins, see
doc/test/plugins.txt

View File

@@ -17,11 +17,11 @@ Submit a plugin, co-develop pytest
Pytest development of the core, some plugins and support code happens
in repositories living under:
- `the pytest-dev bitbucket team <https://bitbucket.org/pytest-dev>`_
- `the pytest-dev github organisation <https://github.com/pytest-dev>`_
All pytest-dev team members have write access to all contained
- `the pytest-dev bitbucket team <https://bitbucket.org/pytest-dev>`_
All pytest-dev Contributors team members have write access to all contained
repositories. pytest core and plugins are generally developed
using `pull requests`_ to respective repositories.
@@ -46,9 +46,9 @@ the following:
If no contributor strongly objects and two agree, the repo will be
transferred to the ``pytest-dev`` organisation and you'll become a
member of the ``pytest-dev`` team, with commit rights to all projects.
We recommend that each plugin has at least three people who have the
right to release to pypi.
member of the ``pytest-dev Contributors`` team, with commit rights
to all projects. We recommend that each plugin has at least three
people who have the right to release to pypi.
.. _reportbugs:
@@ -56,7 +56,7 @@ right to release to pypi.
Report bugs
-----------
Report bugs for pytest at https://bitbucket.org/pytest-dev/pytest/issues
Report bugs for pytest at https://github.com/pytest-dev/pytest/issues
If you are reporting a bug, please include:
@@ -66,6 +66,10 @@ If you are reporting a bug, please include:
installed libraries and pytest version.
* Detailed steps to reproduce the bug.
If you can write a demonstration test that currently fails but should pass (xfail),
that is a very useful commit to make as well, even if you can't find how
to fix the bug yet.
.. _submitfeedback:
Submit feedback for developers
@@ -74,7 +78,7 @@ Submit feedback for developers
Do you like pytest? Share some love on Twitter or in your blog posts!
We'd also like to hear about your propositions and suggestions. Feel free to
`submit them as issues <https://bitbucket.org/pytest-dev/pytest/issues>`__ and:
`submit them as issues <https://github.com/pytest-dev/pytest/issues>`__ and:
* Set the "kind" to "enhancement" or "proposal" so that we can quickly find
about them.
@@ -88,19 +92,21 @@ We'd also like to hear about your propositions and suggestions. Feel free to
Fix bugs
--------
Look through the BitBucket issues for bugs. Here is sample filter you can use:
https://bitbucket.org/pytest-dev/pytest/issues?status=new&status=open&kind=bug
Look through the GitHub issues for bugs. Here is sample filter you can use:
https://github.com/pytest-dev/pytest/labels/bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
Don't forget to check the issue trackers of your favourite plugins, too!
.. _writeplugins:
Implement features
------------------
Look through the BitBucket issues for enhancements. Here is sample filter you
Look through the GitHub issues for enhancements. Here is sample filter you
can use:
https://bitbucket.org/pytest-dev/pytest/issues?status=new&status=open&kind=enhancement
https://github.com/pytest-dev/pytest/labels/enhancement
:ref:`Talk <contact>` to developers to find out how you can implement specific
features.
@@ -111,104 +117,105 @@ Write documentation
pytest could always use more documentation. What exactly is needed?
* More complementary documentation. Have you perhaps found something unclear?
* Documentation translations. We currently have English and Japanese versions.
* Docstrings. There's never too much of them.
* Documentation translations. We currently have only English.
* Docstrings. There can never be too many of them.
* Blog posts, articles and such -- they're all very appreciated.
You can also edit documentation files directly in the Github web interface
without needing to make a fork and local copy. This can be convenient for
small fixes.
.. _`pull requests`:
.. _pull-requests:
Preparing Pull Requests on Bitbucket
------------------------------------
Preparing Pull Requests on GitHub
---------------------------------
.. note::
What is a "pull request"? It informs project's core developers about the
changes you want to review and merge. Pull requests are stored on
`BitBucket servers <https://bitbucket.org/pytest-dev/pytest/pull-requests>`__.
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send pull request, we can discuss it's potential modifications and
even add more commits to it later on.
The primary development platform for pytest is BitBucket. You can find all
the issues there and submit your pull requests.
There's an excellent tutorial on how Pull Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_,
but here is a simple overview:
#. Fork the
`pytest BitBucket repository <https://bitbucket.org/pytest-dev/pytest>`__. It's
`pytest GitHub repository <https://github.com/pytest-dev/pytest>`__. It's
fine to use ``pytest`` as your fork repository name because it will live
under your user.
#. Clone your fork locally using `Mercurial <http://mercurial.selenic.com/>`_
(``hg``) and create a branch::
#. Clone your fork locally using `git <https://git-scm.com/>`_ and create a branch::
$ hg clone ssh://hg@bitbucket.org/YOUR_BITBUCKET_USERNAME/pytest
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
$ cd pytest
$ hg up pytest-2.7 # if you want to fix a bug for the pytest-2.7 series
$ hg up default # if you want to add a feature bound for the next minor release
$ hg branch your-branch-name # your feature/bugfix branch
# now, to fix a bug create your own branch off "master":
$ git checkout -b your-bugfix-branch-name master
If you need some help with Mercurial, follow this quick start
guide: http://mercurial.selenic.com/wiki/QuickStart
# or to instead add a feature create your own branch off "features":
$ git checkout -b your-feature-branch-name features
#. Create a development environment
Given we have "major.minor.micro" version numbers, bugfixes will usually
be released in micro releases whereas features will be released in
minor releases and incompatible changes in major releases.
If you need some help with Git, follow this quick start
guide: https://git.wiki.kernel.org/index.php/QuickStart
#. Install tox
Tox is used to run all the tests and will automatically setup virtualenvs
to run the tests in.
(will implicitly use http://www.virtualenv.org/en/latest/)::
$ make develop
$ source .env/bin/activate
$ pip install tox
#. You can now edit your local working copy.
#. Run all the tests
You need to have Python 2.7 and 3.4 available in your system. Now
You need to have Python 2.7 and 3.5 available in your system. Now
running tests is as simple as issuing this command::
$ python runtox.py -e py27,py34,flakes
$ python runtox.py -e py27,py35,flakes
This command will run tests via the "tox" tool against Python 2.7 and 3.4
This command will run tests via the "tox" tool against Python 2.7 and 3.5
and also perform "flakes" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to pypi) versions of dependencies
(especially ``py``) might be present.
To run tests on py27 and pass options (e.g. enter pdb on failure)
#. You can now edit your local working copy.
You can now make the changes you want and run the tests again as necessary.
To run tests on py27 and pass options to pytest (e.g. enter pdb on failure)
to pytest you can do::
$ python runtox.py -e py27 -- --pdb
or to only run tests in a particular test module on py34::
or to only run tests in a particular test module on py35::
$ python runtox.py -e py34 -- testing/test_config.py
$ python runtox.py -e py35 -- testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s)::
$ hg commit -m"<commit message>"
$ hg push -b .
$ git commit -a -m "<commit message>"
$ git push -u
#. Finally, submit a pull request through the BitBucket website:
Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you
are unsure about either of these steps, submit your pull request and we'll
help you fix it up.
.. image:: img/pullrequest.png
:width: 700px
:align: center
#. Finally, submit a pull request through the GitHub website using this data::
::
head-fork: YOUR_GITHUB_USERNAME/pytest
compare: your-branch-name
source: YOUR_BITBUCKET_USERNAME/pytest
branch: your-branch-name
target: pytest-dev/pytest
branch: default # if it's a feature
branch: pytest-VERSION # if it's a bugfix
base-fork: pytest-dev/pytest
base: master # if it's a bugfix
base: feature # if it's a feature
.. _contribution-using-git:
Using git with bitbucket/hg
-------------------------------
There used to be the pytest GitHub mirror. It was removed in favor of the
Mercurial one, to remove confusion of people not knowing where it's better to
put their issues and pull requests. Also it wasn't easily possible to automate
the mirroring process.
In general we recommend to work with the same version control system of the
original repository. If you insist on using git with bitbucket/hg you
may try `gitifyhg <https://github.com/buchuki/gitifyhg>`_ but are on your
own and need to submit pull requests through the respective platform,
nevertheless.

View File

@@ -1,59 +1,90 @@
How to release pytest (draft)
How to release pytest
--------------------------------------------
1. bump version numbers in _pytest/__init__.py (setup.py reads it)
Note: this assumes you have already registered on pypi.
2. check and finalize CHANGELOG
1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
3. write doc/en/announce/release-VERSION.txt and include
it in doc/en/announce/index.txt
2. Check and finalize CHANGELOG
4. use devpi for uploading a release tarball to a staging area:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi upload --formats sdist,bdist_wheel``
3. Write doc/en/announce/release-VERSION.txt and include
it in doc/en/announce/index.txt::
5. run from multiple machines:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi test pytest==VERSION``
git log 2.8.2..HEAD --format='%aN' | sort -u # lists the names of authors involved
4. Use devpi for uploading a release tarball to a staging area::
devpi use https://devpi.net/USER/dev
devpi upload --formats sdist,bdist_wheel
5. Run from multiple machines::
devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION
6. Check that tests pass for relevant combinations with::
devpi list pytest
6. check that tests pass for relevant combinations with
``devpi list pytest``
or look at failures with "devpi list -f pytest".
There will be some failed environments like e.g. the py33-trial
or py27-pexpect tox environments on Win32 platforms
which is ok (tox does not support skipping on
per-platform basis yet).
7. Regenerate the docs examples using tox::
# Create and activate a virtualenv with regendoc installed
# (currently needs revision 4a9ec1035734)
tox -e regen
7. Regenerate the docs examples using tox, and check for regressions::
8. Build the docs, you need a virtualenv with, py and sphinx
tox -e regen
git diff
8. Build the docs, you need a virtualenv with py and sphinx
installed::
cd docs/en
cd doc/en
python plugins_index/plugins_index.py
make html
9. Tag the release::
hg tag VERSION
Commit any changes before tagging the release.
9. Tag the release::
git tag VERSION
git push
10. Upload the docs using doc/en/Makefile::
cd doc/en
make install # or "installall" if you have LaTeX installed for PDF
10. Upload the docs using docs/en/Makefile::
cd docs/en
make install # or "installall" if you have LaTeX installed
This requires ssh-login permission on pytest.org because it uses
rsync.
Note that the "install" target of doc/en/Makefile defines where the
Note that the ``install`` target of ``doc/en/Makefile`` defines where the
rsync goes to, typically to the "latest" section of pytest.org.
11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME
is the name of pypi.python.org as configured in your
~/.pypirc file -- it's the same you would use with
"setup.py upload -r NAME"
If you are making a minor release (e.g. 5.4), you also need to manually
create a symlink for "latest"::
12. send release announcement to mailing lists:
ssh pytest-dev@pytest.org
ln -s 5.4 latest
pytest-dev
testing-in-python
python-announce-list@python.org
Browse to pytest.org to verify.
11. Publish to pypi::
devpi push pytest-VERSION pypi:NAME
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
12. Send release announcement to mailing lists:
- pytest-dev
- testing-in-python
- python-announce-list@python.org
13. **after the release** Bump the version number in ``_pytest/__init__.py``,
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
set it to ``pytest-2.9.0.dev1``).

View File

@@ -1,25 +0,0 @@
# Set of targets useful for development/release process
PYTHON = python2.7
PATH := $(PWD)/.env/bin:$(PATH)
# prepare virtual python environment
.env:
virtualenv .env -p $(PYTHON)
# install all needed for development
develop: .env
pip install -e . tox -r requirements-docs.txt
# clean the development envrironment
clean:
-rm -rf .env
# generate documentation
docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc
cd doc/en; make html
# upload documentation
upload-docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc --update
cd doc/en; make install

View File

@@ -1,16 +1,27 @@
.. image:: https://pypip.in/v/pytest/badge.png
======
pytest
======
The ``pytest`` testing tool makes it easy to write small tests, yet
scales to support complex functional testing.
.. image:: http://img.shields.io/pypi/v/pytest.svg
:target: https://pypi.python.org/pypi/pytest
.. image:: http://img.shields.io/coveralls/pytest-dev/pytest/master.svg
:target: https://coveralls.io/r/pytest-dev/pytest
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest
Documentation: http://pytest.org/latest/
Changelog: http://pytest.org/latest/changelog.html
Issues: https://bitbucket.org/pytest-dev/pytest/issues?status=open
Issues: https://github.com/pytest-dev/pytest/issues
CI: https://drone.io/bitbucket.org/pytest-dev/pytest
The ``pytest`` testing tool makes it easy to write small tests, yet
scales to support complex functional testing. It provides
Features
--------
- `auto-discovery
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
@@ -22,12 +33,14 @@ scales to support complex functional testing. It provides
on `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
`nose <http://pytest.org/latest/nose.html>`_
- single-source compatibility from Python2.6 all the way up to
Python3.4, PyPy-2.3, (jython-2.5 untested)
Python3.5, PyPy-2.3, (jython-2.5 untested)
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
A simple example for a test::
A simple example for a test:
.. code-block:: python
# content of test_module.py
def test_function():
@@ -42,12 +55,12 @@ For much more info, including PDF docs, see
and report bugs at:
http://bitbucket.org/pytest-dev/pytest/issues/
https://github.com/pytest-dev/pytest/issues
and checkout or fork repo at:
http://bitbucket.org/pytest-dev/pytest/
https://github.com/pytest-dev/pytest
Copyright Holger Krekel and others, 2004-2014
Copyright Holger Krekel and others, 2004-2015
Licensed under the MIT license.

View File

@@ -1,2 +1,2 @@
#
__version__ = '2.7.2'
__version__ = '2.8.3'

11
_pytest/_pluggy.py Normal file
View File

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

View File

@@ -70,12 +70,11 @@ def pytest_configure(config):
config._assertstate = AssertionState(config, mode)
config._assertstate.hook = hook
config._assertstate.trace("configured with mode set to %r" % (mode,))
def pytest_unconfigure(config):
hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
def undo():
hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
config.add_cleanup(undo)
def pytest_collection(session):
@@ -115,8 +114,11 @@ def pytest_runtest_setup(item):
if new_expl:
if (sum(len(p) for p in new_expl[1:]) > 80*8
and item.config.option.verbose < 2):
new_expl[1:] = [py.builtin._totext(
'Detailed information truncated, use "-vv" to show')]
show_max = 10
truncated_lines = len(new_expl) - show_max
new_expl[show_max:] = [py.builtin._totext(
'Detailed information truncated (%d more lines)'
', use "-vv" to show' % truncated_lines)]
new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = py.builtin._totext("\n~").join(new_expl)
if item.config.getvalue("assertmode") == "rewrite":

View File

@@ -33,6 +33,12 @@ else:
def _is_ast_stmt(node):
return isinstance(node, ast.stmt)
try:
_Starred = ast.Starred
except AttributeError:
# Python 2. Define a dummy class so isinstance() will always be False.
class _Starred(object): pass
class Failure(Exception):
"""Error found while interpreting AST."""
@@ -232,24 +238,38 @@ class DebugInterpreter(ast.NodeVisitor):
arguments = []
for arg in call.args:
arg_explanation, arg_result = self.visit(arg)
arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
arguments.append(arg_name)
arg_explanations.append(arg_explanation)
if isinstance(arg, _Starred):
arg_name = "__exprinfo_star"
ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,))
else:
arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
arguments.append(arg_name)
arg_explanations.append(arg_explanation)
for keyword in call.keywords:
arg_explanation, arg_result = self.visit(keyword.value)
arg_name = "__exprinfo_%s" % (len(ns),)
if keyword.arg:
arg_name = "__exprinfo_%s" % (len(ns),)
keyword_source = "%s=%%s" % (keyword.arg)
arguments.append(keyword_source % (arg_name,))
arg_explanations.append(keyword_source % (arg_explanation,))
else:
arg_name = "__exprinfo_kwds"
arguments.append("**%s" % (arg_name,))
arg_explanations.append("**%s" % (arg_explanation,))
ns[arg_name] = arg_result
keyword_source = "%s=%%s" % (keyword.arg)
arguments.append(keyword_source % (arg_name,))
arg_explanations.append(keyword_source % (arg_explanation,))
if call.starargs:
if getattr(call, 'starargs', None):
arg_explanation, arg_result = self.visit(call.starargs)
arg_name = "__exprinfo_star"
ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,))
if call.kwargs:
if getattr(call, 'kwargs', None):
arg_explanation, arg_result = self.visit(call.kwargs)
arg_name = "__exprinfo_kwds"
ns[arg_name] = arg_result

View File

@@ -35,6 +35,12 @@ 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):
ast_Call = ast.Call
else:
ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts."""
@@ -122,7 +128,7 @@ class AssertionRewritingHook(object):
# One of the path components was not a directory, likely
# because we're in a zip file.
write = False
elif e in [errno.EACCES, errno.EROFS]:
elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
state.trace("read only directory: %r" % fn_pypath.dirname)
write = False
else:
@@ -197,6 +203,12 @@ class AssertionRewritingHook(object):
# DefaultProvider is appropriate.
pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
def get_data(self, pathname):
"""Optional PEP302 get_data API.
"""
with open(pathname, 'rb') as f:
return f.read()
def _write_pyc(state, co, source_stat, pyc):
# Technically, we don't have to have the same pyc format as
@@ -587,7 +599,7 @@ class AssertionRewriter(ast.NodeVisitor):
"""Call a helper in this module."""
py_name = ast.Name("@pytest_ar", ast.Load())
attr = ast.Attribute(py_name, "_" + name, ast.Load())
return ast.Call(attr, list(args), [], None, None)
return ast_Call(attr, list(args), [])
def builtin(self, name):
"""Return the builtin called *name*."""
@@ -677,7 +689,7 @@ class AssertionRewriter(ast.NodeVisitor):
msg = self.pop_format_context(template)
fmt = self.helper("format_explanation", msg)
err_name = ast.Name("AssertionError", ast.Load())
exc = ast.Call(err_name, [fmt], [], None, None)
exc = ast_Call(err_name, [fmt], [])
if sys.version_info[0] >= 3:
raise_ = ast.Raise(exc, None)
else:
@@ -697,7 +709,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Name(self, name):
# Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable.
locs = ast.Call(self.builtin("locals"), [], [], None, None)
locs = ast_Call(self.builtin("locals"), [], [])
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
dorepr = self.helper("should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
@@ -724,7 +736,7 @@ class AssertionRewriter(ast.NodeVisitor):
res, expl = self.visit(v)
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
expl_format = self.pop_format_context(ast.Str(expl))
call = ast.Call(app, [expl_format], [], None, None)
call = ast_Call(app, [expl_format], [])
self.on_failure.append(ast.Expr(call))
if i < levels:
cond = res
@@ -753,7 +765,42 @@ class AssertionRewriter(ast.NodeVisitor):
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation
def visit_Call(self, call):
def visit_Call_35(self, call):
"""
visit `ast.Call` nodes on Python3.5 and after
"""
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
new_kwargs = []
for arg in call.args:
res, expl = self.visit(arg)
arg_expls.append(expl)
new_args.append(res)
for keyword in call.keywords:
res, expl = self.visit(keyword.value)
new_kwargs.append(ast.keyword(keyword.arg, res))
if keyword.arg:
arg_expls.append(keyword.arg + "=" + expl)
else: ## **args have `arg` keywords with an .arg of None
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
new_call = ast.Call(new_func, new_args, new_kwargs)
res = self.assign(new_call)
res_expl = self.explanation_param(self.display(res))
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
def visit_Starred(self, starred):
# From Python 3.5, a Starred node can appear in a function call
res, expl = self.visit(starred.value)
return starred, '*' + expl
def visit_Call_legacy(self, call):
"""
visit `ast.Call nodes on 3.4 and below`
"""
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
@@ -781,6 +828,15 @@ class AssertionRewriter(ast.NodeVisitor):
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
# ast.Call signature changed on 3.5,
# conditionally change which methods is named
# visit_Call depending on Python version
if sys.version_info >= (3, 5):
visit_Call = visit_Call_35
else:
visit_Call = visit_Call_legacy
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)

View File

@@ -129,7 +129,16 @@ def assertrepr_compare(config, op, left, right):
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
summary = u('%s %s %s') % (left_repr, op, right_repr)
# the re-encoding is needed for python2 repr
# with non-ascii characters (see issue 877)
def ecu(s):
try:
return u(s, 'utf-8', 'replace')
except TypeError:
return s
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
issequence = lambda x: (isinstance(x, (list, tuple, Sequence))
and not isinstance(x, basestring))

243
_pytest/cacheprovider.py Executable file
View File

@@ -0,0 +1,243 @@
"""
merged implementation of the cache provider
the name cache was not choosen to ensure pluggy automatically
ignores the external pytest-cache
"""
import py
import pytest
import json
from os.path import sep as _sep, altsep as _altsep
class Cache(object):
def __init__(self, config):
self.config = config
self._cachedir = config.rootdir.join(".cache")
self.trace = config.trace.root.get("cache")
if config.getvalue("cacheclear"):
self.trace("clearing cachedir")
if self._cachedir.check():
self._cachedir.remove()
self._cachedir.mkdir()
def makedir(self, name):
""" return a directory path object with the given name. If the
directory does not yet exist, it will be created. You can use it
to manage files likes e. g. store/retrieve database
dumps across test sessions.
:param name: must be a string not containing a ``/`` separator.
Make sure the name contains your plugin or application
identifiers to prevent clashes with other cache users.
"""
if _sep in name or _altsep is not None and _altsep in name:
raise ValueError("name is not allowed to contain path separators")
return self._cachedir.ensure_dir("d", name)
def _getvaluepath(self, key):
return self._cachedir.join('v', *key.split('/'))
def get(self, key, default):
""" return cached value for the given key. If no value
was yet cached or the value cannot be read, the specified
default is returned.
:param key: must be a ``/`` separated value. Usually the first
name is the name of your plugin or your application.
:param default: must be provided in case of a cache-miss or
invalid cache values.
"""
path = self._getvaluepath(key)
if path.check():
try:
with path.open("r") as f:
return json.load(f)
except ValueError:
self.trace("cache-invalid at %s" % (path,))
return default
def set(self, key, value):
""" save value for the given key.
:param key: must be a ``/`` separated value. Usually the first
name is the name of your plugin or your application.
:param value: must be of any combination of basic
python types, including nested types
like e. g. lists of dictionaries.
"""
path = self._getvaluepath(key)
try:
path.dirpath().ensure_dir()
except (py.error.EEXIST, py.error.EACCES):
self.config.warn(
code='I9', message='could not create cache path %s' % (path,)
)
return
try:
f = path.open('w')
except py.error.ENOTDIR:
self.config.warn(
code='I9', message='cache could not write path %s' % (path,))
else:
with f:
self.trace("cache-write %s: %r" % (key, value,))
json.dump(value, f, indent=2, sort_keys=True)
class LFPlugin:
""" Plugin which implements the --lf (run last-failing) option """
def __init__(self, config):
self.config = config
active_keys = 'lf', 'failedfirst'
self.active = any(config.getvalue(key) for key in active_keys)
if self.active:
self.lastfailed = config.cache.get("cache/lastfailed", {})
else:
self.lastfailed = {}
def pytest_report_header(self):
if self.active:
if not self.lastfailed:
mode = "run all (no recorded failures)"
else:
mode = "rerun last %d failures%s" % (
len(self.lastfailed),
" first" if self.config.getvalue("failedfirst") else "")
return "run-last-failure: %s" % mode
def pytest_runtest_logreport(self, report):
if report.failed and "xfail" not in report.keywords:
self.lastfailed[report.nodeid] = True
elif not report.failed:
if report.when == "call":
self.lastfailed.pop(report.nodeid, None)
def pytest_collectreport(self, report):
passed = report.outcome in ('passed', 'skipped')
if passed:
if report.nodeid in self.lastfailed:
self.lastfailed.pop(report.nodeid)
self.lastfailed.update(
(item.nodeid, True)
for item in report.result)
else:
self.lastfailed[report.nodeid] = True
def pytest_collection_modifyitems(self, session, config, items):
if self.active and self.lastfailed:
previously_failed = []
previously_passed = []
for item in items:
if item.nodeid in self.lastfailed:
previously_failed.append(item)
else:
previously_passed.append(item)
if not previously_failed and previously_passed:
# running a subset of all tests with recorded failures outside
# of the set of tests currently executing
pass
elif self.config.getvalue("failedfirst"):
items[:] = previously_failed + previously_passed
else:
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
def pytest_sessionfinish(self, session):
config = self.config
if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
return
config.cache.set("cache/lastfailed", self.lastfailed)
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption(
'--lf', action='store_true', dest="lf",
help="rerun only the tests that failed "
"at the last run (or all if none failed)")
group.addoption(
'--ff', action='store_true', dest="failedfirst",
help="run all tests but run the last failures first. "
"This may re-order tests and thus lead to "
"repeated fixture setup/teardown")
group.addoption(
'--cache-show', action='store_true', dest="cacheshow",
help="show cache contents, don't perform collection or tests")
group.addoption(
'--cache-clear', action='store_true', dest="cacheclear",
help="remove all cache contents at start of test run.")
def pytest_cmdline_main(config):
if config.option.cacheshow:
from _pytest.main import wrap_session
return wrap_session(config, cacheshow)
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
config.cache = Cache(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
@pytest.fixture
def cache(request):
"""
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
cache.set(key, value)
Keys must be a ``/`` separated value, where the first part is usually the
name of your plugin or application to avoid clashes with other cache users.
Values can be any object handled by the json stdlib module.
"""
return request.config.cache
def pytest_report_header(config):
if config.option.verbose:
relpath = py.path.local().bestrelpath(config.cache._cachedir)
return "cachedir: %s" % relpath
def cacheshow(config, session):
from pprint import pprint
tw = py.io.TerminalWriter()
tw.line("cachedir: " + str(config.cache._cachedir))
if not config.cache._cachedir.check():
tw.line("cache is empty")
return 0
dummy = object()
basedir = config.cache._cachedir
vdir = basedir.join("v")
tw.sep("-", "cache values")
for valpath in vdir.visit(lambda x: x.isfile()):
key = valpath.relto(vdir).replace(valpath.sep, "/")
val = config.cache.get(key, dummy)
if val is dummy:
tw.line("%s contains unreadable content, "
"will be ignored" % key)
else:
tw.line("%s contains:" % key)
stream = py.io.TextIO()
pprint(val, stream=stream)
for line in stream.getvalue().splitlines():
tw.line(" " + line)
ddir = basedir.join("d")
if ddir.isdir() and ddir.listdir():
tw.sep("-", "cache directories")
for p in basedir.join("d").visit():
#if p.check(dir=1):
# print("%s/" % p.relto(basedir))
if p.isfile():
key = p.relto(basedir)
tw.line("%s is a file of length %d" % (
key, p.size()))
return 0

View File

@@ -29,7 +29,7 @@ def pytest_addoption(parser):
help="shortcut for --capture=no.")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args):
ns = early_config.known_args_namespace
pluginmanager = early_config.pluginmanager
@@ -37,13 +37,13 @@ def pytest_load_initial_conftests(early_config, parser, args):
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
pluginmanager.add_shutdown(capman.reset_capturings)
early_config.add_cleanup(capman.reset_capturings)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
if "logging" in sys.modules:
sys.modules["logging"].raiseExceptions = False
pluginmanager.add_shutdown(silence_logging_at_shutdown)
early_config.add_cleanup(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.init_capturings()
@@ -86,8 +86,10 @@ class CaptureManager:
self.deactivate_funcargs()
cap = getattr(self, "_capturing", None)
if cap is not None:
outerr = cap.readouterr()
cap.suspend_capturing(in_=in_)
try:
outerr = cap.readouterr()
finally:
cap.suspend_capturing(in_=in_)
return outerr
def activate_funcargs(self, pyfuncitem):
@@ -101,7 +103,7 @@ class CaptureManager:
if capfuncarg is not None:
capfuncarg.close()
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector):
if isinstance(collector, pytest.File):
self.resumecapture()
@@ -115,13 +117,13 @@ class CaptureManager:
else:
yield
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item):
self.resumecapture()
yield
self.suspendcapture_item(item, "setup")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item):
self.resumecapture()
self.activate_funcargs(item)
@@ -129,24 +131,24 @@ class CaptureManager:
#self.deactivate_funcargs() called from suspendcapture()
self.suspendcapture_item(item, "call")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item):
self.resumecapture()
yield
self.suspendcapture_item(item, "teardown")
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo):
self.reset_capturings()
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo):
self.reset_capturings()
def suspendcapture_item(self, item, when):
out, err = self.suspendcapture()
item.add_report_section(when, "out", out)
item.add_report_section(when, "err", err)
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"

View File

@@ -8,11 +8,16 @@ import warnings
import py
# DON't import pytest here because it causes import cycle troubles
import sys, os
from _pytest import hookspec # the extension point definitions
from _pytest.core import PluginManager
import _pytest.hookspec # the extension point definitions
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
# pytest startup
#
class ConftestImportFailure(Exception):
def __init__(self, path, excinfo):
Exception.__init__(self, path, excinfo)
@@ -29,16 +34,24 @@ def main(args=None, plugins=None):
initialization.
"""
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure:
e = sys.exc_info()[1]
tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True)
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True)
return 4
else:
try:
config.pluginmanager.check_pending()
return config.hook.pytest_cmdline_main(config=config)
finally:
config._ensure_unconfigure()
except UsageError as e:
for msg in e.args:
sys.stderr.write("ERROR: %s\n" %(msg,))
return 4
else:
return config.hook.pytest_cmdline_main(config=config)
class cmdline: # compatibility namespace
main = staticmethod(main)
@@ -51,21 +64,36 @@ _preinit = []
default_plugins = (
"mark main terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml resultlog doctest").split()
"junitxml resultlog doctest cacheprovider").split()
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
def _preloadplugins():
assert not _preinit
_preinit.append(get_plugin_manager())
_preinit.append(get_config())
def get_plugin_manager():
def get_config():
if _preinit:
return _preinit.pop(0)
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
pluginmanager.config = Config(pluginmanager) # XXX attr needed?
config = Config(pluginmanager)
for spec in default_plugins:
pluginmanager.import_plugin(spec)
return pluginmanager
return config
def get_plugin_manager():
"""
Obtain a new instance of the
:py:class:`_pytest.config.PytestPluginManager`, with default plugins
already loaded.
This function can be used by integration with other tools, like hooking
into pytest to run tests into an IDE.
"""
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
if args is None:
@@ -76,20 +104,42 @@ def _prepareconfig(args=None, plugins=None):
if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args)
pluginmanager = get_plugin_manager()
config = get_config()
pluginmanager = config.pluginmanager
try:
if plugins:
for plugin in plugins:
pluginmanager.register(plugin)
if isinstance(plugin, py.builtin._basestring):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
except Exception:
pluginmanager.ensure_shutdown()
except BaseException:
config._ensure_unconfigure()
raise
class PytestPluginManager(PluginManager):
def __init__(self, hookspecs=[hookspec]):
super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
"""
Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
functionality:
* loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
``pytest_plugins`` global variables found in plugins being loaded;
* ``conftest.py`` loading during start-up;
"""
def __init__(self):
super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
self._conftest_plugins = set()
# state related to local conftest plugins
self._path2confmods = {}
self._conftestpath2mod = {}
self._confcutdir = None
self._noconftest = False
self.add_hookspecs(_pytest.hookspec)
self.register(self)
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
@@ -98,21 +148,259 @@ class PytestPluginManager(PluginManager):
err = py.io.dupfile(err, encoding=encoding)
except Exception:
pass
self.set_tracing(err.write)
self.trace.root.setwriter(err.write)
self.enable_tracing()
def addhooks(self, module_or_class):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
"""
warning = dict(code="I2",
fslocation=py.code.getfslineno(sys._getframe(1)),
nodeid=None,
message="use pluginmanager.add_hookspecs instead of "
"deprecated addhooks() method.")
self._warn(warning)
return self.add_hookspecs(module_or_class)
def parse_hookimpl_opts(self, plugin, name):
# pytest hooks are always prefixed with pytest_
# so we avoid accessing possibly non-readable attributes
# (see issue #1073)
if not name.startswith("pytest_"):
return
# ignore some historic special names which can not be hooks anyway
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
return
method = getattr(plugin, name)
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
if opts is not None:
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
opts.setdefault(name, hasattr(method, name))
return opts
def parse_hookspec_opts(self, module_or_class, name):
opts = super(PytestPluginManager, self).parse_hookspec_opts(
module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
opts = {"firstresult": hasattr(method, "firstresult"),
"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 = py.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):
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self))
return ret
def getplugin(self, name):
# support deprecated naming because plugins (xdist e.g.) use it
return self.get_plugin(name)
def hasplugin(self, name):
"""Return True if the plugin with the given name is registered."""
return bool(self.get_plugin(name))
def pytest_configure(self, config):
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
# we should remove tryfirst/trylast as markers
config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
for warning in self._warnings:
config.warn(code="I1", message=warning)
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
'code': 'I1',
'message': message,
'fslocation': None,
'nodeid': None,
}
self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
#
# internal API for local conftest plugin handling
#
def _set_initial_conftests(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
self._noconftest = namespace.noconftest
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self._getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self._getconftestmodules(x)
def _getconftestmodules(self, path):
if self._noconftest:
return []
try:
return self._path2confmods[path]
except KeyError:
if path.isfile():
clist = self._getconftestmodules(path.dirpath())
else:
# XXX these days we may rather want to use config.rootdir
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
mod = self._importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def _rget_with_confmod(self, name, path):
modules = self._getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def _importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftest_plugins.add(mod)
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" %(mod))
self.consider_conftest(mod)
return mod
#
# API for bootstrapping plugin loading
#
#
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
name = arg[3:]
self.set_blocked(name)
if not name.startswith("pytest_"):
self.set_blocked("pytest_" + name)
else:
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
self.consider_module(conftestmodule)
def consider_env(self):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
def consider_module(self, mod):
self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
def _import_plugin_specs(self, spec):
if spec:
if isinstance(spec, str):
spec = spec.split(",")
for import_spec in spec:
self.import_plugin(import_spec)
def import_plugin(self, modname):
# most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, str)
if self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
else:
importspec = modname
try:
__import__(importspec)
except ImportError:
raise
except Exception as e:
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warn("skipped plugin %r: %s" %((modname, e.msg)))
else:
mod = sys.modules[importspec]
self.register(mod, modname)
self.consider_module(mod)
class Parser:
""" Parser for command line arguments and ini-file values. """
""" Parser for command line arguments and ini-file values.
:ivar extra_info: dict of generic param -> value to display in case
there's an error processing the command line arguments.
"""
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
@@ -121,6 +409,7 @@ class Parser:
self._usage = usage
self._inidict = {}
self._ininames = []
self.extra_info = {}
def processoption(self, option):
if self._processopt:
@@ -174,7 +463,7 @@ class Parser:
def _getparser(self):
from _pytest._argcomplete import filescompleter
optparser = MyOptionParser(self)
optparser = MyOptionParser(self, self.extra_info)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@@ -195,9 +484,18 @@ class Parser:
return getattr(parsedoption, FILE_OR_DIR)
def parse_known_args(self, args):
"""parses and returns a namespace object with known arguments at this
point.
"""
return self.parse_known_and_unknown_args(args)[0]
def parse_known_and_unknown_args(self, args):
"""parses and returns a namespace object with known arguments, and
the remaining arguments unknown at this point.
"""
optparser = self._getparser()
args = [str(x) for x in args]
return optparser.parse_known_args(args)[0]
return optparser.parse_known_args(args)
def addini(self, name, help, type=None, default=None):
""" register an ini-file option.
@@ -399,10 +697,15 @@ class OptionGroup:
class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser):
def __init__(self, parser, extra_info=None):
if not extra_info:
extra_info = {}
self._parser = parser
argparse.ArgumentParser.__init__(self, usage=parser._usage,
add_help=False, formatter_class=DropShorterLongHelpFormatter)
# extra_info is a dict of (param -> value) to display if there's
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
def parse_args(self, args=None, namespace=None):
"""allow splitting of positional arguments"""
@@ -410,11 +713,14 @@ class MyOptionParser(argparse.ArgumentParser):
if argv:
for arg in argv:
if arg and arg[0] == '-':
msg = argparse._('unrecognized arguments: %s')
self.error(msg % ' '.join(argv))
lines = ['unrecognized arguments: %s' % (' '.join(argv))]
for k, v in sorted(self.extra_info.items()):
lines.append(' %s: %s' % (k, v))
self.error('\n'.join(lines))
getattr(args, FILE_OR_DIR).extend(argv)
return args
class DropShorterLongHelpFormatter(argparse.HelpFormatter):
"""shorten help for long options that differ only in extra hyphens
@@ -464,96 +770,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return action._formatted_action_invocation
class Conftest(object):
""" the single place for accessing values and interacting
towards conftest modules from pytest objects.
"""
def __init__(self, onimport=None):
self._path2confmods = {}
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = None
def setinitial(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self.getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self.getconftestmodules(x)
def getconftestmodules(self, path):
try:
return self._path2confmods[path]
except KeyError:
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.check(file=1):
mod = self.importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def rget_with_confmod(self, name, path):
modules = self.getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
if self._onimport:
self._onimport(mod)
return mod
def _ensure_removed_sysmodule(modname):
try:
@@ -574,6 +790,7 @@ class Notset:
notset = Notset()
FILE_OR_DIR = 'file_or_dir'
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
@@ -589,58 +806,52 @@ class Config(object):
#: a pluginmanager instance
self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config")
self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook
self._inicache = {}
self._opt2dest = {}
self._cleanup = []
self._warn = self.pluginmanager._warn
self.pluginmanager.register(self, "pytestconfig")
self.pluginmanager.set_register_callback(self._register_plugin)
self._configured = False
def _register_plugin(self, plugin, name):
call_plugin = self.pluginmanager.call_plugin
call_plugin(plugin, "pytest_addhooks",
{'pluginmanager': self.pluginmanager})
self.hook.pytest_plugin_registered(plugin=plugin,
manager=self.pluginmanager)
dic = call_plugin(plugin, "pytest_namespace", {}) or {}
if dic:
def do_setns(dic):
import pytest
setns(pytest, dic)
call_plugin(plugin, "pytest_addoption", {'parser': self._parser})
if self._configured:
call_plugin(plugin, "pytest_configure", {'config': self})
self.hook.pytest_namespace.call_historic(do_setns, {})
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
def do_configure(self):
def add_cleanup(self, func):
""" Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
self._cleanup.append(func)
def _do_configure(self):
assert not self._configured
self._configured = True
self.hook.pytest_configure(config=self)
self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
def do_unconfigure(self):
assert self._configured
self._configured = False
self.hook.pytest_unconfigure(config=self)
self.pluginmanager.ensure_shutdown()
def _ensure_unconfigure(self):
if self._configured:
self._configured = False
self.hook.pytest_unconfigure(config=self)
self.hook.pytest_configure._call_history = []
while self._cleanup:
fin = self._cleanup.pop()
fin()
def warn(self, code, message):
def warn(self, code, message, fslocation=None):
""" generate a warning for this test session. """
self.hook.pytest_logwarning(code=code, message=message,
fslocation=None, nodeid=None)
self.hook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
fslocation=fslocation, nodeid=None))
def get_terminal_writer(self):
return self.pluginmanager.getplugin("terminalreporter")._tw
return self.pluginmanager.get_plugin("terminalreporter")._tw
def pytest_cmdline_parse(self, pluginmanager, args):
assert self == pluginmanager.config, (self, pluginmanager.config)
# REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args)
return self
def pytest_unconfigure(config):
while config._cleanup:
fin = config._cleanup.pop()
fin()
def notify_exception(self, excinfo, option=None):
if option and option.fulltrace:
style = "long"
@@ -667,18 +878,13 @@ class Config(object):
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
pluginmanager = get_plugin_manager()
config = pluginmanager.config
config = get_config()
config._preparse(args, addopts=False)
config.option.__dict__.update(option_dict)
for x in config.option.plugins:
config.pluginmanager.consider_pluginarg(x)
return config
def _onimportconftest(self, conftestmodule):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
self.pluginmanager.consider_conftest(conftestmodule)
def _processopt(self, opt):
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
@@ -687,18 +893,16 @@ class Config(object):
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath):
return self.pluginmanager._plugins + \
self._conftest.getconftestmodules(fspath)
@hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace)
pytest_load_initial_conftests.trylast = True
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args):
parsed_args = self._parser.parse_known_args(args)
r = determine_setup(parsed_args.inifilename, parsed_args.file_or_dir)
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info['rootdir'] = self.rootdir
self._parser.extra_info['inifile'] = self.inifile
self.invocation_dir = py.path.local()
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
@@ -710,9 +914,15 @@ class Config(object):
args[:] = self.getini("addopts") + args
self._checkversion()
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
try:
self.pluginmanager.load_setuptools_entrypoints("pytest11")
except ImportError as e:
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args)
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(early_config=self,
args=args, parser=self._parser)
@@ -721,8 +931,7 @@ class Config(object):
if ns.help or ns.version:
# we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end
self.pluginmanager._warnings.append(
"could not load initial conftests (%s)\n" % e.path)
self._warn("could not load initial conftests (%s)\n" % e.path)
else:
raise
@@ -743,12 +952,18 @@ class Config(object):
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
self._origargs = args
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager))
self._preparse(args)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
args = self._parser.parse_setoption(args, self.option)
if not args:
args.append(os.getcwd())
cwd = os.getcwd()
if cwd == self.rootdir:
args = self.getini('testpaths')
if not args:
args = [cwd]
self.args = args
def addinivalue_line(self, name, line):
@@ -799,7 +1014,7 @@ class Config(object):
def _getconftest_pathlist(self, name, path):
try:
mod, relroots = self._conftest.rget_with_confmod(name, path)
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
@@ -933,3 +1148,16 @@ def setns(obj, dic):
#if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)
def create_terminal_writer(config, *args, **kwargs):
"""Create a TerminalWriter instance configured according to the options
in the config object. Every code which requires a TerminalWriter object
and has access to a config object should use this function.
"""
tw = py.io.TerminalWriter(*args, **kwargs)
if config.option.color == 'yes':
tw.hasmarkup = True
if config.option.color == 'no':
tw.hasmarkup = False
return tw

View File

@@ -1,543 +0,0 @@
"""
pytest PluginManager, basic initialization and tracing.
"""
import os
import sys
import inspect
import py
# don't import pytest to avoid circular imports
assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
py3 = sys.version_info > (3,0)
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 add_method_wrapper(cls, wrapper_func):
""" Substitute the function named "wrapperfunc.__name__" at class
"cls" with a function that wraps the call to the original function.
Return an undo function which can be called to reset the class to use
the old method again.
wrapper_func is called with the same arguments as the method
it wraps and its result is used as a wrap_controller for
calling the original function.
"""
name = wrapper_func.__name__
oldcall = getattr(cls, name)
def wrap_exec(*args, **kwargs):
gen = wrapper_func(*args, **kwargs)
return wrapped_call(gen, lambda: oldcall(*args, **kwargs))
setattr(cls, name, wrap_exec)
return lambda: setattr(cls, name, oldcall)
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])
py.builtin._reraise(*ex)
class PluginManager(object):
def __init__(self, hookspecs=None, prefix="pytest_"):
self._name2plugin = {}
self._plugins = []
self._conftestplugins = []
self._plugin2hookcallers = {}
self._warnings = []
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
self._shutdown = []
self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
def set_tracing(self, writer):
self.trace.root.setwriter(writer)
# reconfigure HookCalling to perform tracing
assert not hasattr(self, "_wrapping")
self._wrapping = True
def _docall(self, methods, kwargs):
trace = self.hookrelay.trace
trace.root.indent += 1
trace(self.name, kwargs)
box = yield
if box.excinfo is None:
trace("finish", self.name, "-->", box.result)
trace.root.indent -= 1
undo = add_method_wrapper(HookCaller, _docall)
self.add_shutdown(undo)
def do_configure(self, config):
# backward compatibility
config.do_configure()
def set_register_callback(self, callback):
assert not hasattr(self, "_registercallback")
self._registercallback = callback
def register(self, plugin, name=None, prepend=False, conftest=False):
if self._name2plugin.get(name, None) == -1:
return
name = name or getattr(plugin, '__name__', str(id(plugin)))
if self.isregistered(plugin, name):
raise ValueError("Plugin already registered: %s=%s\n%s" %(
name, plugin, self._name2plugin))
#self.trace("registering", name, plugin)
reg = getattr(self, "_registercallback", None)
if reg is not None:
reg(plugin, name) # may call addhooks
hookcallers = list(self.hook._scan_plugin(plugin))
self._plugin2hookcallers[plugin] = hookcallers
self._name2plugin[name] = plugin
if conftest:
self._conftestplugins.append(plugin)
else:
if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
# finally make sure that the methods of the new plugin take part
for hookcaller in hookcallers:
hookcaller.scan_methods()
return True
def unregister(self, plugin):
try:
self._plugins.remove(plugin)
except KeyError:
self._conftestplugins.remove(plugin)
for name, value in list(self._name2plugin.items()):
if value == plugin:
del self._name2plugin[name]
hookcallers = self._plugin2hookcallers.pop(plugin)
for hookcaller in hookcallers:
hookcaller.scan_methods()
def add_shutdown(self, func):
self._shutdown.append(func)
def ensure_shutdown(self):
while self._shutdown:
func = self._shutdown.pop()
func()
self._plugins = self._conftestplugins = []
self._name2plugin.clear()
def isregistered(self, plugin, name=None):
if self.getplugin(name) is not None:
return True
return plugin in self._plugins or plugin in self._conftestplugins
def addhooks(self, spec, prefix="pytest_"):
self.hook._addhooks(spec, prefix=prefix)
def getplugins(self):
return self._plugins + self._conftestplugins
def skipifmissing(self, name):
if not self.hasplugin(name):
import pytest
pytest.skip("plugin %r is missing" % name)
def hasplugin(self, name):
return bool(self.getplugin(name))
def getplugin(self, name):
if name is None:
return None
try:
return self._name2plugin[name]
except KeyError:
return self._name2plugin.get("_pytest." + name, None)
# API for bootstrapping
#
def _envlist(self, varname):
val = os.environ.get(varname, None)
if val is not None:
return val.split(',')
return ()
def consider_env(self):
for spec in self._envlist("PYTEST_PLUGINS"):
self.import_plugin(spec)
def consider_setuptools_entrypoints(self):
try:
from pkg_resources import iter_entry_points, DistributionNotFound
except ImportError:
return # XXX issue a warning
for ep in iter_entry_points('pytest11'):
name = ep.name
if name.startswith("pytest_"):
name = name[7:]
if ep.name in self._name2plugin or name in self._name2plugin:
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self._plugin_distinfo.append((ep.dist, plugin))
self.register(plugin, name=name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
name = arg[3:]
plugin = self.getplugin(name)
if plugin is not None:
self.unregister(plugin)
self._name2plugin[name] = -1
else:
if self.getplugin(arg) is None:
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__,
conftest=True):
self.consider_module(conftestmodule)
def consider_module(self, mod):
attr = getattr(mod, "pytest_plugins", ())
if attr:
if not isinstance(attr, (list, tuple)):
attr = (attr,)
for spec in attr:
self.import_plugin(spec)
def import_plugin(self, modname):
assert isinstance(modname, str)
if self.getplugin(modname) is not None:
return
try:
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except ImportError:
if modname.startswith("pytest_"):
return self.import_plugin(modname[7:])
raise
except:
e = sys.exc_info()[1]
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
else:
self.register(mod, modname)
self.consider_module(mod)
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins + self._conftestplugins
l = []
last = []
wrappers = []
for plugin in plugins:
try:
meth = getattr(plugin, attrname)
except AttributeError:
continue
if hasattr(meth, 'hookwrapper'):
wrappers.append(meth)
elif hasattr(meth, 'tryfirst'):
last.append(meth)
elif hasattr(meth, 'trylast'):
l.insert(0, meth)
else:
l.append(meth)
l.extend(last)
l.extend(wrappers)
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def importplugin(importspec):
name = importspec
try:
mod = "_pytest." + name
__import__(mod)
return sys.modules[mod]
except ImportError:
__import__(importspec)
return sys.modules[importspec]
class MultiCall:
""" execute a call into multiple python functions/methods. """
def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods)
self.kwargs = kwargs
self.kwargs["__multicall__"] = self
self.results = []
self.firstresult = firstresult
def __repr__(self):
status = "%d results, %d meths" % (len(self.results), len(self.methods))
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self):
all_kwargs = self.kwargs
while self.methods:
method = self.methods.pop()
args = [all_kwargs[argname] for argname in varnames(method)]
if hasattr(method, "hookwrapper"):
return wrapped_call(method(*args), self.execute)
res = method(*args)
if res is not None:
self.results.append(res)
if self.firstresult:
return res
if not self.firstresult:
return self.results
def varnames(func, startindex=None):
""" return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered.
For methods the "self" parameter is not included unless you are passing
an unbound method with Python3 (which has no supports for unbound methods)
"""
cache = getattr(func, "__dict__", {})
try:
return cache["_varnames"]
except KeyError:
pass
if inspect.isclass(func):
try:
func = func.__init__
except AttributeError:
return ()
startindex = 1
else:
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
if startindex is None:
startindex = int(inspect.ismethod(func))
rawcode = py.code.getrawcode(func)
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:
def __init__(self, hookspecs, pm, prefix="pytest_"):
if not isinstance(hookspecs, list):
hookspecs = [hookspecs]
self._pm = pm
self.trace = pm.trace.root.get("hook")
self.prefix = prefix
for hookspec in hookspecs:
self._addhooks(hookspec, prefix)
def _addhooks(self, hookspec, prefix):
added = False
isclass = int(inspect.isclass(hookspec))
for name, method in vars(hookspec).items():
if name.startswith(prefix):
firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult,
argnames=varnames(method, startindex=isclass))
setattr(self, name, hc)
added = True
#print ("setting new hook", name)
if not added:
raise ValueError("did not find new %r hooks in %r" %(
prefix, hookspec,))
def _getcaller(self, name, plugins):
caller = getattr(self, name)
methods = self._pm.listattr(name, plugins=plugins)
if methods:
return caller.new_cached_caller(methods)
return caller
def _scan_plugin(self, plugin):
def fail(msg, *args):
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
for name in dir(plugin):
if not name.startswith(self.prefix):
continue
hook = getattr(self, name, None)
method = getattr(plugin, name)
if hook is None:
is_optional = getattr(method, 'optionalhook', False)
if not isgenerichook(name) and not is_optional:
fail("found unknown hook: %r", name)
continue
for arg in varnames(method):
if arg not in hook.argnames:
fail("argument %r not available\n"
"actual definition: %s\n"
"available hookargs: %s",
arg, formatdef(method),
", ".join(hook.argnames))
yield hook
class HookCaller:
def __init__(self, hookrelay, name, firstresult, argnames, methods=()):
self.hookrelay = hookrelay
self.name = name
self.firstresult = firstresult
self.argnames = ["__multicall__"]
self.argnames.extend(argnames)
assert "self" not in argnames # sanity check
self.methods = methods
def new_cached_caller(self, methods):
return HookCaller(self.hookrelay, self.name, self.firstresult,
argnames=self.argnames, methods=methods)
def __repr__(self):
return "<HookCaller %r>" %(self.name,)
def scan_methods(self):
self.methods = self.hookrelay._pm.listattr(self.name)
def __call__(self, **kwargs):
return self._docall(self.methods, kwargs)
def callextra(self, methods, **kwargs):
return self._docall(self.methods + methods, kwargs)
def _docall(self, methods, kwargs):
return MultiCall(methods, kwargs,
firstresult=self.firstresult).execute()
class PluginValidationError(Exception):
""" plugin failed validation. """
def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@@ -2,9 +2,10 @@
from __future__ import absolute_import
import traceback
import pytest, py
from _pytest.python import FixtureRequest, FuncFixtureInfo
from _pytest.python import FixtureRequest
from py._code.code import TerminalRepr, ReprFileLocation
def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests',
type="args", default=["ELLIPSIS"])
@@ -22,6 +23,7 @@ def pytest_addoption(parser):
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
def pytest_collect_file(path, parent):
config = parent.config
if path.ext == ".py":
@@ -31,22 +33,36 @@ def pytest_collect_file(path, parent):
path.check(fnmatch=config.getvalue("doctestglob")):
return DoctestTextfile(path, parent)
class ReprFailDoctest(TerminalRepr):
def __init__(self, reprlocation, lines):
self.reprlocation = reprlocation
self.lines = lines
def toterminal(self, tw):
for line in self.lines:
tw.line(line)
self.reprlocation.toterminal(tw)
class DoctestItem(pytest.Item):
def __init__(self, name, parent, runner=None, dtest=None):
super(DoctestItem, self).__init__(name, parent)
self.runner = runner
self.dtest = dtest
self.obj = None
self.fixture_request = None
def setup(self):
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
globs = dict(getfixture=self.fixture_request.getfuncargvalue)
self.dtest.globs.update(globs)
def runtest(self):
_check_all_skipped(self.dtest)
self.runner.run(self.dtest)
def repr_failure(self, excinfo):
@@ -63,7 +79,7 @@ class DoctestItem(pytest.Item):
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = doctest.OutputChecker()
checker = _get_unicode_checker()
REPORT_UDIFF = doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
lines = []
@@ -93,6 +109,7 @@ class DoctestItem(pytest.Item):
def reportinfo(self):
return self.fspath, None, "[doctest] %s" % self.name
def _get_flag_lookup():
import doctest
return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
@@ -100,7 +117,9 @@ def _get_flag_lookup():
NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
ELLIPSIS=doctest.ELLIPSIS,
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS)
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
ALLOW_UNICODE=_get_allow_unicode_flag())
def get_optionflags(parent):
optionflags_str = parent.config.getini("doctest_optionflags")
@@ -110,29 +129,47 @@ def get_optionflags(parent):
flag_acc |= flag_lookup_table[flag]
return flag_acc
class DoctestTextfile(DoctestItem, pytest.File):
class DoctestTextfile(DoctestItem, pytest.Module):
def runtest(self):
import doctest
# satisfy `FixtureRequest` constructor...
self.funcargs = {}
fm = self.session._fixturemanager
def func():
pass
self._fixtureinfo = fm.getfixtureinfo(node=self, func=func,
cls=None, funcargs=False)
fixture_request = FixtureRequest(self)
fixture_request._fillfixtures()
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=get_optionflags(self),
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=0)
fixture_request = _setup_fixtures(self)
class DoctestModule(pytest.File):
# inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker
text = self.fspath.read()
filename = str(self.fspath)
name = self.fspath.basename
globs = dict(getfixture=fixture_request.getfuncargvalue)
if '__name__' not in globs:
globs['__name__'] = '__main__'
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
_check_all_skipped(test)
runner.run(test)
def _check_all_skipped(test):
"""raises pytest.skip() if all examples in the given DocTest have the SKIP
option set.
"""
import doctest
all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
if all_skipped:
pytest.skip('all tests skipped by +SKIP option')
class DoctestModule(pytest.Module):
def collect(self):
import doctest
if self.fspath.basename == "conftest.py":
module = self.config._conftest.importconftest(self.fspath)
module = self.config.pluginmanager._importconftest(self.fspath)
else:
try:
module = self.fspath.pyimport()
@@ -141,16 +178,83 @@ class DoctestModule(pytest.File):
pytest.skip('unable to import module %r' % self.fspath)
else:
raise
# satisfy `FixtureRequest` constructor...
self.funcargs = {}
self._fixtureinfo = FuncFixtureInfo((), [], {})
fixture_request = FixtureRequest(self)
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags)
for test in finder.find(module, module.__name__,
extraglobs=doctest_globals):
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
def _setup_fixtures(doctest_item):
"""
Used by DoctestTextfile and DoctestItem to setup fixture information.
"""
def func():
pass
doctest_item.funcargs = {}
fm = doctest_item.session._fixturemanager
doctest_item._fixtureinfo = fm.getfixtureinfo(node=doctest_item, func=func,
cls=None, funcargs=False)
fixture_request = FixtureRequest(doctest_item)
fixture_request._fillfixtures()
return fixture_request
def _get_unicode_checker():
"""
Returns a doctest.OutputChecker subclass that takes in account the
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
when the same doctest should run in Python 2 and Python 3.
An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
return _get_unicode_checker.UnicodeOutputChecker()
import doctest
import re
class UnicodeOutputChecker(doctest.OutputChecker):
"""
Copied from doctest_nose_plugin.py from the nltk project:
https://github.com/nltk/nltk
"""
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
def check_output(self, want, got, optionflags):
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
if res:
return True
if not (optionflags & _get_allow_unicode_flag()):
return False
else: # pragma: no cover
# the code below will end up executed only in Python 2 in
# our tests, and our coverage check runs in Python 3 only
def remove_u_prefixes(txt):
return re.sub(self._literal_re, r'\1\2', txt)
want = remove_u_prefixes(want)
got = remove_u_prefixes(got)
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
return res
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
return _get_unicode_checker.UnicodeOutputChecker()
def _get_allow_unicode_flag():
"""
Registers and returns the ALLOW_UNICODE flag.
"""
import doctest
return doctest.register_optionflag('ALLOW_UNICODE')

View File

@@ -1,10 +1,9 @@
""" generate a single-file self-contained version of pytest """
""" (deprecated) generate a single-file self-contained version of pytest """
import os
import sys
import pkgutil
import py
import _pytest
@@ -33,6 +32,9 @@ def pkg_to_mapping(name):
for pyfile in toplevel.visit('*.py'):
pkg = pkgname(name, toplevel, pyfile)
name2src[pkg] = pyfile.read()
# with wheels py source code might be not be installed
# and the resulting genscript is useless, just bail out.
assert name2src, "no source code found for %r at %r" %(name, toplevel)
return name2src
def compress_mapping(mapping):
@@ -66,10 +68,13 @@ def pytest_addoption(parser):
help="create standalone pytest script at given target path.")
def pytest_cmdline_main(config):
import _pytest.config
genscript = config.getvalue("genscript")
if genscript:
tw = py.io.TerminalWriter()
deps = ['py', '_pytest', 'pytest']
tw = _pytest.config.create_terminal_writer(config)
tw.line("WARNING: usage of genscript is deprecated.",
red=True)
deps = ['py', '_pytest', 'pytest'] # pluggy is vendored
if sys.version_info < (2,7):
deps.append("argparse")
tw.line("generated script will run on python2.6-python3.3++")

View File

@@ -22,30 +22,28 @@ def pytest_addoption(parser):
help="store internal tracing debug information in 'pytestdebug.log'.")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_cmdline_parse():
outcome = yield
config = outcome.get_result()
if config.option.debug:
path = os.path.abspath("pytestdebug.log")
f = open(path, 'w')
config._debugfile = f
f.write("versions pytest-%s, py-%s, "
debugfile = open(path, 'w')
debugfile.write("versions pytest-%s, py-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
config.pluginmanager.set_tracing(f.write)
config.trace.root.setwriter(debugfile.write)
undo_tracing = config.pluginmanager.enable_tracing()
sys.stderr.write("writing pytestdebug information to %s\n" % path)
@pytest.mark.trylast
def pytest_unconfigure(config):
if hasattr(config, '_debugfile'):
config._debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
config._debugfile.name)
config.trace.root.setwriter(None)
def unset_tracing():
debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
debugfile.name)
config.trace.root.setwriter(None)
undo_tracing()
config.add_cleanup(unset_tracing)
def pytest_cmdline_main(config):
if config.option.version:
@@ -58,13 +56,14 @@ def pytest_cmdline_main(config):
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
config.do_configure()
config._do_configure()
showhelp(config)
config.do_unconfigure()
config._ensure_unconfigure()
return 0
def showhelp(config):
tw = py.io.TerminalWriter()
reporter = config.pluginmanager.get_plugin('terminalreporter')
tw = reporter._tw
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
@@ -81,14 +80,25 @@ def showhelp(config):
line = " %-24s %s" %(spec, help)
tw.line(line[:tw.fullwidth])
tw.line() ; tw.line()
#tw.sep("=")
tw.line()
tw.line("environment variables:")
vars = [
("PYTEST_ADDOPTS", "extra command line options"),
("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
]
for name, help in vars:
tw.line(" %-24s %s" % (name, help))
tw.line()
tw.line()
tw.line("to see available markers type: py.test --markers")
tw.line("to see available fixtures type: py.test --fixtures")
tw.line("(shown according to specified file_or_dir or current dir "
"if not specified)")
for warning in config.pluginmanager._warnings:
tw.line("warning: %s" % (warning,), red=True)
for warningreport in reporter.stats.get('warnings', []):
tw.line("warning : " + warningreport.message, red=True)
return
@@ -98,10 +108,10 @@ conftest_options = [
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager._plugin_distinfo
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
lines.append("setuptools registered plugins:")
for dist, plugin in plugininfo:
for plugin, dist in plugininfo:
loc = getattr(plugin, '__file__', repr(plugin))
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
lines.append(" " + content)
@@ -119,7 +129,7 @@ def pytest_report_header(config):
if config.option.traceconfig:
lines.append("active plugins:")
items = config.pluginmanager._name2plugin.items()
items = config.pluginmanager.list_name_plugin()
for name, plugin in items:
if hasattr(plugin, '__file__'):
r = plugin.__file__
@@ -127,5 +137,3 @@ def pytest_report_header(config):
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
return lines

View File

@@ -1,32 +1,44 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from _pytest._pluggy import HookspecMarker
hookspec = HookspecMarker("pytest")
# -------------------------------------------------------------------------
# Initialization
# Initialization hooks called for every plugin
# -------------------------------------------------------------------------
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin load time to allow adding new hooks via a call to
pluginmanager.registerhooks(module)."""
"""called at plugin registration time to allow adding new hooks via a call to
pluginmanager.add_hookspecs(module_or_class, prefix)."""
@hookspec(historic=True)
def pytest_namespace():
"""return dict of name->object to be made globally available in
the pytest namespace. This hook is called before command line options
are parsed.
the pytest namespace. This hook is called at plugin registration
time.
"""
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
pytest_cmdline_parse.firstresult = True
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@hookspec(historic=True)
def pytest_addoption(parser):
"""register argparse-style options and ini-style config values.
This function must be implemented in a :ref:`plugin <pluginorder>` and is
called once at the beginning of a test run.
.. warning::
This function must be implemented in a :ref:`plugin <pluginorder>`
and is called once at the beginning of a test run.
Implementing this hook from ``conftest.py`` files is **strongly**
discouraged because ``conftest.py`` files are lazily loaded and
may give strange *unknown option* errors depending on the directory
``py.test`` is invoked from.
:arg parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
@@ -47,35 +59,43 @@ def pytest_addoption(parser):
via (deprecated) ``pytest.config``.
"""
@hookspec(historic=True)
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
This hook is called for every plugin.
"""
# -------------------------------------------------------------------------
# Bootstrapping hooks called for plugins registered early enough:
# internal and 3rd party plugins as well as directly
# discoverable conftest.py local plugins.
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@hookspec(firstresult=True)
def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop. """
pytest_cmdline_main.firstresult = True
def pytest_load_initial_conftests(args, early_config, parser):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
pytest_runtestloop.firstresult = True
# -------------------------------------------------------------------------
# collection hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session. """
pytest_collection.firstresult = True
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
@@ -84,16 +104,16 @@ def pytest_collection_modifyitems(session, config, items):
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
@hookspec(firstresult=True)
def pytest_ignore_collect(path, config):
""" return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling
more specific hooks.
"""
pytest_ignore_collect.firstresult = True
@hookspec(firstresult=True)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
@@ -112,29 +132,29 @@ def pytest_collectreport(report):
def pytest_deselected(items):
""" called for test items deselected by keyword. """
@hookspec(firstresult=True)
def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport. """
pytest_make_collect_report.firstresult = True
# -------------------------------------------------------------------------
# Python test function related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path.
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
create test modules for files that do not match as a test module.
"""
pytest_pycollect_makemodule.firstresult = True
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
pytest_pycollect_makeitem.firstresult = True
@hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """
pytest_pyfunc_call.firstresult = True
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
@@ -142,9 +162,16 @@ def pytest_generate_tests(metafunc):
# -------------------------------------------------------------------------
# generic runtest related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
@hookspec(firstresult=True)
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
the given test item, including capturing exceptions and calling
@@ -158,7 +185,6 @@ def pytest_runtest_protocol(item, nextitem):
:return boolean: True if no further hook implementations should be invoked.
"""
pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
@@ -178,12 +204,12 @@ def pytest_runtest_teardown(item, nextitem):
so that nextitem only needs to call setup-functions.
"""
@hookspec(firstresult=True)
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item` and
:py:class:`_pytest.runner.CallInfo`.
"""
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to
@@ -199,6 +225,9 @@ def pytest_sessionstart(session):
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
def pytest_unconfigure(config):
""" called before test process is exited. """
# -------------------------------------------------------------------------
# hooks for customising the assert methods
@@ -220,13 +249,15 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting."""
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting."""
pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """
@hookspec(historic=True)
def pytest_logwarning(message, code, nodeid, fslocation):
""" process a warning specified by a message, a code string,
a nodeid and fslocation (both of which may be None
@@ -236,17 +267,14 @@ def pytest_logwarning(message, code, nodeid, fslocation):
# doctest hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """

View File

@@ -1,5 +1,7 @@
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
Based on initial code from Ross Lawley.
"""
import py
@@ -7,6 +9,7 @@ import os
import re
import sys
import time
import pytest
# Python 2.X and 3.X compatibility
if sys.version_info[0] < 3:
@@ -51,6 +54,20 @@ def bin_xml_escape(arg):
return unicode('#x%04X') % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
@pytest.fixture
def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically
xml-encoded.
"""
def inner(name, value):
if hasattr(request.config, "_xml"):
request.config._xml.add_custom_property(name, value)
msg = 'record_xml_property is an experimental feature'
request.config.warn(code='C3', message=msg,
fslocation=request.node.location[:2])
return inner
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', '--junit-xml', action="store",
@@ -73,7 +90,6 @@ def pytest_unconfigure(config):
del config._xml
config.pluginmanager.unregister(xml)
def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()']
names[0] = names[0].replace("/", '.')
@@ -85,19 +101,34 @@ class LogXML(object):
self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix
self.tests = []
self.tests_by_nodeid = {} # nodeid -> Junit.testcase
self.durations = {} # nodeid -> total duration (setup+call+teardown)
self.passed = self.skipped = 0
self.failed = self.errors = 0
self.custom_properties = {}
def add_custom_property(self, name, value):
self.custom_properties[str(name)] = bin_xml_escape(str(value))
def _opentestcase(self, report):
names = mangle_testnames(report.nodeid.split("::"))
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
self.tests.append(Junit.testcase(
classname=".".join(classnames),
name=bin_xml_escape(names[-1]),
time=getattr(report, 'duration', 0)
))
attrs = {
"classname": ".".join(classnames),
"name": bin_xml_escape(names[-1]),
"file": report.location[0],
"time": self.durations.get(report.nodeid, 0),
}
if report.location[1] is not None:
attrs["line"] = report.location[1]
testcase = Junit.testcase(**attrs)
custom_properties = self.pop_custom_properties()
if custom_properties:
testcase.append(custom_properties)
self.tests.append(testcase)
self.tests_by_nodeid[report.nodeid] = testcase
def _write_captured_output(self, report):
for capname in ('out', 'err'):
@@ -112,6 +143,21 @@ class LogXML(object):
def append(self, obj):
self.tests[-1].append(obj)
def pop_custom_properties(self):
"""Return a Junit node containing custom properties set for
the current test, if any, and reset the current custom properties.
"""
if self.custom_properties:
result = Junit.properties(
[
Junit.property(name=name, value=value)
for name, value in self.custom_properties.items()
]
)
self.custom_properties.clear()
return result
return None
def append_pass(self, report):
self.passed += 1
self._write_captured_output(report)
@@ -170,8 +216,30 @@ class LogXML(object):
self._write_captured_output(report)
def pytest_runtest_logreport(self, report):
"""handle a setup/call/teardown report, generating the appropriate
xml tags as necessary.
note: due to plugins like xdist, this hook may be called in interlaced
order with reports from other nodes. for example:
usual call order:
-> setup node1
-> call node1
-> teardown node1
-> setup node2
-> call node2
-> teardown node2
possible call order in xdist:
-> setup node1
-> call node1
-> setup node2
-> call node2
-> teardown node2
-> teardown node1
"""
if report.passed:
if report.when == "call": # ignore setup/teardown
if report.when == "call": # ignore setup/teardown
self._opentestcase(report)
self.append_pass(report)
elif report.failed:
@@ -183,6 +251,19 @@ class LogXML(object):
elif report.skipped:
self._opentestcase(report)
self.append_skipped(report)
self.update_testcase_duration(report)
def update_testcase_duration(self, report):
"""accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created.
"""
total = self.durations.get(report.nodeid, 0.0)
total += getattr(report, 'duration', 0.0)
self.durations[report.nodeid] = total
testcase = self.tests_by_nodeid.get(report.nodeid)
if testcase is not None:
testcase.attr.time = total
def pytest_collectreport(self, report):
if not report.passed:

View File

@@ -19,12 +19,15 @@ EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5
name_re = re.compile("^[a-zA-Z_]\w*$")
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
type="args", default=[])
#parser.addini("dirpatterns",
# "patterns specifying possible locations of test files",
# type="linelist", default=["**/test_*.txt",
@@ -54,6 +57,9 @@ def pytest_addoption(parser):
group.addoption('--confcutdir', dest="confcutdir", default=None,
metavar="dir",
help="only load conftest.py's relative to specified dir.")
group.addoption('--noconftest', action="store_true",
dest="noconftest", default=False,
help="Don't load any conftest.py files.")
group = parser.getgroup("debugconfig",
"test session debugging and configuration")
@@ -77,16 +83,13 @@ def wrap_session(config, doit):
initstate = 0
try:
try:
config.do_configure()
config._do_configure()
initstate = 1
config.hook.pytest_sessionstart(session=session)
initstate = 2
doit(config, session)
session.exitstatus = doit(config, session) or 0
except pytest.UsageError:
args = sys.exc_info()[1].args
for msg in args:
sys.stderr.write("ERROR: %s\n" %(msg,))
session.exitstatus = EXIT_USAGEERROR
raise
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
@@ -97,9 +100,7 @@ def wrap_session(config, doit):
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
else:
if session._testsfailed:
session.exitstatus = EXIT_TESTSFAILED
finally:
excinfo = None # Explicitly break reference cycle.
session.startdir.chdir()
@@ -107,9 +108,7 @@ def wrap_session(config, doit):
config.hook.pytest_sessionfinish(
session=session,
exitstatus=session.exitstatus)
if initstate >= 1:
config.do_unconfigure()
config.pluginmanager.ensure_shutdown()
config._ensure_unconfigure()
return session.exitstatus
def pytest_cmdline_main(config):
@@ -121,6 +120,11 @@ def _main(config, session):
config.hook.pytest_collection(session=session)
config.hook.pytest_runtestloop(session=session)
if session.testsfailed:
return EXIT_TESTSFAILED
elif session.testscollected == 0:
return EXIT_NOTESTSCOLLECTED
def pytest_collection(session):
return session.perform_collect()
@@ -153,18 +157,17 @@ def pytest_ignore_collect(path, config):
ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths
class FSHookProxy(object):
def __init__(self, fspath, config):
class FSHookProxy:
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
self.config = config
self.pm = pm
self.remove_mods = remove_mods
def __getattr__(self, name):
plugins = self.config._getmatchingplugins(self.fspath)
x = self.config.hook._getcaller(name, plugins)
x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
self.__dict__[name] = x
return x
def compatproperty(name):
def fget(self):
# deprecated - use pytest.name
@@ -237,17 +240,11 @@ class Node(object):
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
#self.extrainit()
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
#def extrainit(self):
# """"extra initialization after Node is initialized. Implemented
# by some subclasses. """
Module = compatproperty("Module")
Class = compatproperty("Class")
Instance = compatproperty("Instance")
@@ -277,9 +274,9 @@ class Node(object):
else:
fslocation = "%s:%s" % fslocation[:2]
self.ihook.pytest_logwarning(code=code, message=message,
nodeid=self.nodeid,
fslocation=fslocation)
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation))
# methods for ordering nodes
@property
@@ -364,9 +361,6 @@ class Node(object):
def listnames(self):
return [x.name for x in self.listchain()]
def getplugins(self):
return self.config._getmatchingplugins(self.fspath)
def addfinalizer(self, fin):
""" register a function to be called when this node is finalized.
@@ -502,36 +496,41 @@ class Item(Node):
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
class Session(FSCollector):
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
Interrupted = Interrupted
def __init__(self, config):
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self._fs2hookproxy = {}
self.testsfailed = 0
self.testscollected = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
self._fs2hookproxy = {}
self.config.pluginmanager.register(self, name="session")
def _makeid(self):
return ""
@pytest.hookimpl(tryfirst=True)
def pytest_collectstart(self):
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
if maxfail and self.testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(self, path):
@@ -541,8 +540,20 @@ class Session(FSCollector):
try:
return self._fs2hookproxy[fspath]
except KeyError:
self._fs2hookproxy[fspath] = x = FSHookProxy(fspath, self.config)
return x
# check if we have the common case of running
# hooks with all conftest.py filesall conftest.py
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
proxy = self.config.hook
self._fs2hookproxy[fspath] = proxy
return proxy
def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
@@ -552,6 +563,7 @@ class Session(FSCollector):
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
self.testscollected = len(items)
return items
def _perform_collect(self, args, genitems):
@@ -725,5 +737,3 @@ class Session(FSCollector):
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)

View File

@@ -1,5 +1,5 @@
""" generic mechanism for marking and selecting python functions. """
import py
import inspect
class MarkerError(Exception):
@@ -43,15 +43,16 @@ def pytest_addoption(parser):
def pytest_cmdline_main(config):
import _pytest.config
if config.option.markers:
config.do_configure()
tw = py.io.TerminalWriter()
config._do_configure()
tw = _pytest.config.create_terminal_writer(config)
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
config.do_unconfigure()
config._ensure_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
@@ -253,15 +254,17 @@ class MarkDecorator:
otherwise add *args/**kwargs in-place to mark information. """
if args and not kwargs:
func = args[0]
if len(args) == 1 and (istestfunc(func) or
hasattr(func, '__bases__')):
if hasattr(func, '__bases__'):
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
if hasattr(func, 'pytestmark'):
l = func.pytestmark
if not isinstance(l, list):
func.pytestmark = [l, self]
else:
l.append(self)
mark_list = func.pytestmark
if not isinstance(mark_list, list):
mark_list = [mark_list]
# always work on a copy to avoid updating pytestmark
# from a superclass by accident
mark_list = mark_list + [self]
func.pytestmark = mark_list
else:
func.pytestmark = [self]
else:
@@ -288,7 +291,7 @@ class MarkInfo:
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]
def __repr__(self):

View File

@@ -27,7 +27,7 @@ def pytest_funcarg__monkeypatch(request):
def derive_importpath(import_path):
def derive_importpath(import_path, raising):
import pytest
if not isinstance(import_path, _basestring) or "." not in import_path:
raise TypeError("must be absolute import path string, not %r" %
@@ -51,7 +51,8 @@ def derive_importpath(import_path):
attr = rest.pop()
obj = getattr(obj, attr)
attr = rest[0]
getattr(obj, attr)
if raising:
getattr(obj, attr)
except AttributeError:
__tracebackhide__ = True
pytest.fail("object %r has no attribute %r" % (obj, attr))
@@ -71,6 +72,7 @@ class monkeypatch:
self._setattr = []
self._setitem = []
self._cwd = None
self._savesyspath = None
def setattr(self, target, name, value=notset, raising=True):
""" Set attribute value on target, memorizing the old value.
@@ -95,7 +97,7 @@ class monkeypatch:
"setattr(target, value) with target being a dotted "
"import string")
value = name
name, target = derive_importpath(target)
name, target = derive_importpath(target, raising)
oldval = getattr(target, name, notset)
if raising and oldval is notset:
@@ -124,7 +126,7 @@ class monkeypatch:
raise TypeError("use delattr(target, name) or "
"delattr(target) with target being a dotted "
"import string")
name, target = derive_importpath(target)
name, target = derive_importpath(target, raising)
if not hasattr(target, name):
if raising:
@@ -172,13 +174,13 @@ class monkeypatch:
def syspath_prepend(self, path):
""" Prepend ``path`` to ``sys.path`` list of import locations. """
if not hasattr(self, '_savesyspath'):
if self._savesyspath is None:
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
def chdir(self, path):
""" Change the current working directory to the specified path
path can be a string or a py.path.local object
""" Change the current working directory to the specified path.
Path can be a string or a py.path.local object.
"""
if self._cwd is None:
self._cwd = os.getcwd()
@@ -190,7 +192,17 @@ class monkeypatch:
def undo(self):
""" Undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
you do more monkeypatching after the undo call."""
you do more monkeypatching after the undo call.
There is generally no need to call `undo()`, since it is
called automatically during tear-down.
Note that the same `monkeypatch` fixture is used across a
single test function invocation. If `monkeypatch` is used both by
the test function itself and one of the test fixtures,
calling `undo()` will undo all of the changes made in
both functions.
"""
for obj, name, value in self._setattr:
if value is not notset:
setattr(obj, name, value)
@@ -206,9 +218,9 @@ class monkeypatch:
else:
dictionary[name] = value
self._setitem[:] = []
if hasattr(self, '_savesyspath'):
if self._savesyspath is not None:
sys.path[:] = self._savesyspath
del self._savesyspath
self._savesyspath = None
if self._cwd is not None:
os.chdir(self._cwd)

View File

@@ -24,7 +24,7 @@ def pytest_runtest_makereport(item, call):
call.excinfo = call2.excinfo
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_runtest_setup(item):
if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator):

View File

@@ -1,6 +1,6 @@
""" submit failure or test session information to a pastebin service. """
import pytest
import py, sys
import sys
import tempfile
@@ -11,7 +11,7 @@ def pytest_addoption(parser):
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
if config.option.pastebin == "all":
tr = config.pluginmanager.getplugin('terminalreporter')
@@ -69,6 +69,7 @@ def create_new_paste(contents):
return 'bad response: ' + response
def pytest_terminal_summary(terminalreporter):
import _pytest.config
if terminalreporter.config.option.pastebin != "failed":
return
tr = terminalreporter
@@ -79,7 +80,7 @@ def pytest_terminal_summary(terminalreporter):
msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
except AttributeError:
msg = tr._getfailureheadline(rep)
tw = py.io.TerminalWriter(stringio=True)
tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
rep.toterminal(tw)
s = tw.stringio.getvalue()
assert len(s)

View File

@@ -4,7 +4,6 @@ import pdb
import sys
import pytest
import py
def pytest_addoption(parser):
@@ -23,23 +22,27 @@ def pytest_configure(config):
old = (pdb.set_trace, pytestPDB._pluginmanager)
def fin():
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
pdb.set_trace = pytest.set_trace
pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None
_config = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config
frame = sys._getframe().f_back
capman = None
if self._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
tw = py.io.TerminalWriter()
tw = _pytest.config.create_terminal_writer(self._config)
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb()

View File

@@ -1,5 +1,7 @@
""" (disabled by default) support for testing pytest and pytest plugins. """
import gc
import sys
import traceback
import os
import codecs
import re
@@ -11,10 +13,142 @@ import subprocess
import py
import pytest
from py.builtin import print_
from _pytest.core import HookCaller, add_method_wrapper
from _pytest.main import Session, EXIT_OK
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", ),
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():
config.pluginmanager.register(checker)
class LsofFdLeakChecker(object):
def get_open_files(self):
out = self._exec_lsof()
open_files = self._parse_lsof_output(out)
return open_files
def _exec_lsof(self):
pid = os.getpid()
return py.process.cmdexec("lsof -Ffn0 -p %d" % pid)
def _parse_lsof_output(self, out):
def isopen(line):
return line.startswith('f') and ("deleted" not in line and
'mem' not in line and "txt" not in line and 'cwd' not in line)
open_files = []
for line in out.split("\n"):
if isopen(line):
fields = line.split('\0')
fd = fields[0][1:]
filename = fields[1][1:]
if filename.startswith('/'):
open_files.append((fd, filename))
return open_files
def matching_platform(self):
try:
py.process.cmdexec("lsof -v")
except (py.process.cmdexec.Error, UnicodeDecodeError):
# cmdexec may raise UnicodeDecodeError on Windows systems
# with locale other than english:
# https://bitbucket.org/pytest-dev/py/issues/66
return False
else:
return True
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_item(self, item):
lines1 = self.get_open_files()
yield
if hasattr(sys, "pypy_version_info"):
gc.collect()
lines2 = self.get_open_files()
new_fds = set([t[0] for t in lines2]) - set([t[0] for t in lines1])
leaked_files = [t for t in lines2 if t[0] in new_fds]
if leaked_files:
error = []
error.append("***** %s FD leakage detected" % len(leaked_files))
error.extend([str(f) for f in leaked_files])
error.append("*** Before:")
error.extend([str(f) for f in lines1])
error.append("*** After:")
error.extend([str(f) for f in lines2])
error.append(error[0])
error.append("*** function %s:%s: %s " % item.location)
pytest.fail("\n".join(error), pytrace=False)
# 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',
}
def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = py.path.local.sysfind(name)
if executable:
if name == "jython":
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err:
executable = None
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
cache[name] = executable
return executable
@pytest.fixture(params=['python2.6', 'python2.7', 'python3.3', "python3.4",
'pypy', 'pypy3'])
def anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
pytest.skip("no suitable %s found" % (name,))
return executable
# used at least by pytest-xdist plugin
@pytest.fixture
def _pytest(request):
@@ -39,23 +173,6 @@ def get_public_names(l):
return [x for x in l if x[0] != "_"]
def pytest_addoption(parser):
group = parser.getgroup("pylib")
group.addoption('--no-tools-on-path',
action="store_true", dest="notoolsonpath", default=False,
help=("discover tools on PATH instead of going through py.cmdline.")
)
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")
class ParsedCall:
def __init__(self, name, kwargs):
self.__dict__.update(kwargs)
@@ -68,15 +185,24 @@ 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.
"""
def __init__(self, pluginmanager):
self._pluginmanager = pluginmanager
self.calls = []
def _docall(hookcaller, methods, kwargs):
self.calls.append(ParsedCall(hookcaller.name, kwargs))
yield
self._undo_wrapping = add_method_wrapper(HookCaller, _docall)
pluginmanager.add_shutdown(self._undo_wrapping)
def before(hook_name, hook_impls, kwargs):
self.calls.append(ParsedCall(hook_name, kwargs))
def after(outcome, hook_name, hook_impls, kwargs):
pass
self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
def finish_recording(self):
self._undo_wrapping()
@@ -186,18 +312,36 @@ class HookRecorder:
self.calls[:] = []
def pytest_funcarg__linecomp(request):
@pytest.fixture
def linecomp(request):
return LineComp()
def pytest_funcarg__LineMatcher(request):
return LineMatcher
def pytest_funcarg__testdir(request):
tmptestdir = TmpTestdir(request)
return tmptestdir
rex_outcome = re.compile("(\d+) (\w+)")
@pytest.fixture
def testdir(request, tmpdir_factory):
return Testdir(request, tmpdir_factory)
rex_outcome = re.compile("(\d+) ([\w-]+)")
class RunResult:
"""The result of running a command.
Attributes:
:ret: The return value.
:outlines: List of lines captured from stdout.
:errlines: List of lines captures from stderr.
: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.
"""
def __init__(self, ret, outlines, errlines, duration):
self.ret = ret
self.outlines = outlines
@@ -207,6 +351,8 @@ class RunResult:
self.duration = duration
def parseoutcomes(self):
""" 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)
@@ -216,12 +362,41 @@ class RunResult:
d[cat] = int(num)
return d
class TmpTestdir:
def __init__(self, request):
def assert_outcomes(self, passed=0, skipped=0, failed=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
d = self.parseoutcomes()
assert passed == d.get("passed", 0)
assert skipped == d.get("skipped", 0)
assert failed == d.get("failed", 0)
class Testdir:
"""Temporary test directory with tools to test/run py.test itself.
This is based on the ``tmpdir`` fixture but provides a number of
methods which aid with testing py.test itself. Unless
:py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as
current working directory.
Attributes:
: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.
"""
def __init__(self, request, tmpdir_factory):
self.request = request
self.Config = request.config.__class__
# XXX remove duplication with tmpdir plugin
basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
basetmp = tmpdir_factory.ensuretemp("testdir")
name = request.function.__name__
for i in range(100):
try:
@@ -231,32 +406,58 @@ class TmpTestdir:
break
self.tmpdir = tmpdir
self.plugins = []
self._syspathremove = []
self._savesyspath = (list(sys.path), list(sys.meta_path))
self._savemodulekeys = set(sys.modules)
self.chdir() # always chdir
self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
self._runpytest_method = self.runpytest_inprocess
elif method == "subprocess":
self._runpytest_method = self.runpytest_subprocess
def __repr__(self):
return "<TmpTestdir %r>" % (self.tmpdir,)
return "<Testdir %r>" % (self.tmpdir,)
def finalize(self):
for p in self._syspathremove:
sys.path.remove(p)
"""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.
"""
sys.path[:], sys.meta_path[:] = self._savesyspath
if hasattr(self, '_olddir'):
self._olddir.chdir()
# delete modules that have been loaded from tmpdir
for name, mod in list(sys.modules.items()):
if mod:
fn = getattr(mod, '__file__', None)
if fn and fn.startswith(str(self.tmpdir)):
del sys.modules[name]
self.delete_loaded_modules()
def delete_loaded_modules(self):
"""Delete modules that have been loaded during a test.
This allows the interpreter to catch module changes in case
the module is re-imported.
"""
for name in set(sys.modules).difference(self._savemodulekeys):
# it seems zope.interfaces is keeping some state
# (used by twisted related tests)
if name != "zope.interface":
del sys.modules[name]
def make_hook_recorder(self, pluginmanager):
"""Create a new :py:class:`HookRecorder` for a PluginManager."""
assert not hasattr(pluginmanager, "reprec")
pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
self.request.addfinalizer(reprec.finish_recording)
return reprec
def chdir(self):
"""Cd into the temporary directory.
This is done automatically upon instantiation.
"""
old = self.tmpdir.chdir()
if not hasattr(self, '_olddir'):
self._olddir = old
@@ -285,42 +486,95 @@ class TmpTestdir:
ret = p
return ret
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".
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.
E.g. "testdir.makefile('.ini', pytest='[pytest]\naddopts=-rs\n')"
"""
return self._makefile(ext, args, kwargs)
def makeconftest(self, source):
"""Write a contest.py file with 'source' as contents."""
return self.makepyfile(conftest=source)
def makeini(self, source):
"""Write a tox.ini file with 'source' as contents."""
return self.makefile('.ini', tox=source)
def getinicfg(self, source):
"""Return the pytest section from the tox.ini config file."""
p = self.makeini(source)
return py.iniconfig.IniConfig(p)['pytest']
def makepyfile(self, *args, **kwargs):
"""Shortcut for .makefile() with a .py extension."""
return self._makefile('.py', args, kwargs)
def maketxtfile(self, *args, **kwargs):
"""Shortcut for .makefile() with a .txt extension."""
return self._makefile('.txt', args, kwargs)
def syspathinsert(self, path=None):
"""Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
This is undone automatically after the test.
"""
if path is None:
path = self.tmpdir
sys.path.insert(0, str(path))
self._syspathremove.append(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.
self._possibly_invalidate_import_caches()
def _possibly_invalidate_import_caches(self):
# invalidate caches if we can (py33 and above)
try:
import importlib
except ImportError:
pass
else:
if hasattr(importlib, "invalidate_caches"):
importlib.invalidate_caches()
def mkdir(self, name):
"""Create a new (sub)directory."""
return self.tmpdir.mkdir(name)
def mkpydir(self, name):
"""Create a new python package.
This creates a (sub)direcotry with an empty ``__init__.py``
file so that is recognised as a python package.
"""
p = self.mkdir(name)
p.ensure("__init__.py")
return p
Session = Session
def getnode(self, config, arg):
"""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.
:param arg: A :py:class:`py.path.local` instance of the file.
"""
session = Session(config)
assert '::' not in str(arg)
p = py.path.local(arg)
@@ -330,6 +584,15 @@ class TmpTestdir:
return res
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) py.test
Config instance.
:param path: A :py:class:`py.path.local` instance of the file.
"""
config = self.parseconfigure(path)
session = Session(config)
x = session.fspath.bestrelpath(path)
@@ -339,6 +602,12 @@ class TmpTestdir:
return res
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.
"""
session = colitems[0].session
result = []
for colitem in colitems:
@@ -346,6 +615,14 @@ class TmpTestdir:
return result
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`.
"""
# used from runner functional tests
item = self.getitem(source)
# the test class where we are called from wants to provide the runner
@@ -354,68 +631,176 @@ class TmpTestdir:
return runner(item)
def inline_runsource(self, source, *cmdlineargs):
"""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.
:param source: The source code of the test module.
:param cmdlineargs: Any extra command line arguments to use.
:return: :py:class:`HookRecorder` instance of the result.
"""
p = self.makepyfile(source)
l = list(cmdlineargs) + [p]
return self.inline_run(*l)
def inline_runsource1(self, *args):
args = list(args)
source = args.pop()
p = self.makepyfile(source)
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 3, reports # setup/call/teardown
return reports[1]
def inline_genitems(self, *args):
return self.inprocess_run(list(args) + ['--collectonly'])
"""Run ``pytest.main(['--collectonly'])`` in-process.
def inprocess_run(self, args, plugins=()):
rec = self.inline_run(*args, plugins=plugins)
Retuns a tuple of the collected items and a
:py:class:`HookRecorder` instance.
This runs the :py:func:`pytest.main` function to run all of
py.test 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.
"""
rec = self.inline_run("--collect-only", *args)
items = [x.item for x in rec.getcalls("pytest_itemcollected")]
return items, rec
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
py.test 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`.
:param args: Any command line arguments to pass to
:py:func:`pytest.main`.
:param plugin: (keyword-only) Extra plugin instances the
``pytest.main()`` instance should use.
:return: A :py:class:`HookRecorder` instance.
"""
rec = []
class Collect:
def pytest_configure(x, config):
rec.append(self.make_hook_recorder(config.pluginmanager))
plugins = kwargs.get("plugins") or []
plugins.append(Collect())
ret = pytest.main(list(args), plugins=plugins)
assert len(rec) == 1
reprec = rec[0]
self.delete_loaded_modules()
if len(rec) == 1:
reprec = rec.pop()
else:
class reprec:
pass
reprec.ret = ret
# typically we reraise keyboard interrupts from the child run
# because it's our user requesting interruption of the testing
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
calls = reprec.getcalls("pytest_keyboard_interrupt")
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
raise KeyboardInterrupt()
return reprec
def parseconfig(self, *args):
def runpytest_inprocess(self, *args, **kwargs):
""" 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()
capture = py.io.StdCapture()
try:
try:
reprec = self.inline_run(*args, **kwargs)
except SystemExit as e:
class reprec:
ret = e.args[0]
except Exception:
traceback.print_exc()
class reprec:
ret = 3
finally:
out, err = capture.reset()
sys.stdout.write(out)
sys.stderr.write(err)
res = RunResult(reprec.ret,
out.split("\n"), err.split("\n"),
time.time()-now)
res.reprec = reprec
return res
def runpytest(self, *args, **kwargs):
""" Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
args = self._ensure_basetemp(args)
return self._runpytest_method(*args, **kwargs)
def _ensure_basetemp(self, args):
args = [str(x) for x in args]
for x in args:
if str(x).startswith('--basetemp'):
#print ("basedtemp exists: %s" %(args,))
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
#print ("added basetemp: %s" %(args,))
return args
def parseconfig(self, *args):
"""Return a new py.test Config instance from given commandline args.
This invokes the py.test bootstrapping code in _pytest.config
to create a new :py:class:`_pytest.core.PluginManager` and
call the pytest_cmdline_parse hook to create 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.
"""
args = self._ensure_basetemp(args)
import _pytest.config
config = _pytest.config._prepareconfig(args, self.plugins)
# we don't know what the test will do with this half-setup config
# object and thus we make sure it gets unconfigured properly in any
# case (otherwise capturing could still be active, for example)
def ensure_unconfigure():
if hasattr(config.pluginmanager, "_config"):
config.pluginmanager.do_unconfigure(config)
config.pluginmanager.ensure_shutdown()
self.request.addfinalizer(ensure_unconfigure)
self.request.addfinalizer(config._ensure_unconfigure)
return config
def parseconfigure(self, *args):
"""Return a new py.test configured Config instance.
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)
config.do_configure()
self.request.addfinalizer(config.do_unconfigure)
config._do_configure()
self.request.addfinalizer(config._ensure_unconfigure)
return config
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 py.test's
collection on the resulting module, returning the test item
for the requested function name.
:param source: The module source.
:param funcname: The name of the test function for which the
Item must be returned.
"""
items = self.getitems(source)
for item in items:
if item.name == funcname:
@@ -424,10 +809,32 @@ class TmpTestdir:
funcname, source, items)
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs py.test's
collection on the resulting module, returning all test items
contained within.
"""
modcol = self.getmodulecol(source)
return self.genitems([modcol])
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 py.test collection on it, returning the
collection node for the test module.
:param source: The source code of the module to collect.
:param configargs: Any extra arguments to pass to
:py:meth:`parseconfigure`.
:param withinit: Whether to also write a ``__init__.py`` file
to the temporarly directory to ensure it is a package.
"""
kw = {self.request.function.__name__: py.code.Source(source).strip()}
path = self.makepyfile(**kw)
if withinit:
@@ -437,11 +844,30 @@ class TmpTestdir:
return node
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.
:param modcol: A module collection node, see
:py:meth:`getmodulecol`.
:param name: The name of the node to return.
"""
for colitem in modcol._memocollect():
if colitem.name == name:
return colitem
def popen(self, cmdargs, stdout, stderr, **kw):
"""Invoke subprocess.Popen.
This calls subprocess.Popen making sure the current working
directory is the PYTHONPATH.
You probably want to use :py:meth:`run` instead.
"""
env = os.environ.copy()
env['PYTHONPATH'] = os.pathsep.join(filter(None, [
str(os.getcwd()), env.get('PYTHONPATH', '')]))
@@ -450,13 +876,22 @@ class TmpTestdir:
stdout=stdout, stderr=stderr, **kw)
def run(self, *cmdargs):
"""Run a command with arguments.
Run a process using subprocess.Popen saving the stdout and
stderr.
Returns a :py:class:`RunResult`.
"""
return self._run(*cmdargs)
def _run(self, *cmdargs):
cmdargs = [str(x) for x in cmdargs]
p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr")
print_("running", cmdargs, "curdir=", py.path.local())
print_("running:", ' '.join(cmdargs))
print_(" in:", str(py.path.local()))
f1 = codecs.open(str(p1), "w", encoding="utf8")
f2 = codecs.open(str(p2), "w", encoding="utf8")
try:
@@ -486,38 +921,35 @@ class TmpTestdir:
except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,))
def runpybin(self, scriptname, *args):
fullargs = self._getpybinargs(scriptname) + args
return self.run(*fullargs)
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a py.test.exe
return (sys.executable, _pytest_fullpath,) # noqa
def _getpybinargs(self, scriptname):
if not self.request.config.getvalue("notoolsonpath"):
# XXX we rely on script referring to the correct environment
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a py.test.exe
return (sys.executable, _pytest_fullpath,) # noqa
else:
pytest.skip("cannot run %r with --no-tools-on-path" % scriptname)
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
def runpython(self, script, prepend=True):
if prepend:
s = self._getsysprepend()
if s:
script.write(s + "\n" + script.read())
Returns a :py:class:`RunResult`.
"""
return self.run(sys.executable, script)
def _getsysprepend(self):
if self.request.config.getvalue("notoolsonpath"):
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
else:
s = ""
return s
def runpython_c(self, command):
command = self._getsysprepend() + command
"""Run python -c "command", return a :py:class:`RunResult`."""
return self.run(sys.executable, "-c", command)
def runpytest(self, *args):
def runpytest_subprocess(self, *args, **kwargs):
"""Run py.test 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.
Returns a :py:class:`RunResult`.
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
@@ -530,17 +962,28 @@ class TmpTestdir:
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
args = ('-p', plugins[0]) + args
return self.runpybin("py.test", *args)
args = self._getpytestargs() + args
return self.run(*args)
def spawn_pytest(self, string, expect_timeout=10.0):
if self.request.config.getvalue("notoolsonpath"):
pytest.skip("--no-tools-on-path prevents running pexpect-spawn tests")
"""Run py.test using pexpect.
This makes sure to use the right py.test and sets up the
temporary directory locations.
The pexpect child is returned.
"""
basetemp = self.tmpdir.mkdir("pexpect")
invoke = " ".join(map(str, self._getpybinargs("py.test")))
invoke = " ".join(map(str, self._getpytestargs()))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout)
def spawn(self, cmd, expect_timeout=10.0):
"""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():
pytest.skip("pypy-64 bit not supported")
@@ -577,11 +1020,23 @@ class LineComp:
lines1 = val.split("\n")
return LineMatcher(lines1).fnmatch_lines(lines2)
class LineMatcher:
"""Flexible matching of text.
This is a convenience class to test large texts like the output of
commands.
The constructor takes a list of lines without their trailing
newlines, i.e. ``text.splitlines()``.
"""
def __init__(self, lines):
self.lines = lines
def str(self):
"""Return the entire original text."""
return "\n".join(self.lines)
def _getlines(self, lines2):
@@ -592,6 +1047,12 @@ class LineMatcher:
return lines2
def fnmatch_lines_random(self, lines2):
"""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.
"""
lines2 = self._getlines(lines2)
for line in lines2:
for x in self.lines:
@@ -602,12 +1063,24 @@ class LineMatcher:
raise ValueError("line %r not found in output" % line)
def get_lines_after(self, fnline):
"""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):
return self.lines[i+1:]
raise ValueError("line %r not found in output" % fnline)
def fnmatch_lines(self, lines2):
"""Search the text for matching lines.
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.
"""
def show(arg1, arg2):
py.builtin.print_(arg1, arg2, file=sys.stderr)
lines2 = self._getlines(lines2)

View File

@@ -1,14 +1,27 @@
""" Python test discovery, setup and run of test functions. """
import re
import fnmatch
import functools
import py
import inspect
import types
import sys
import pytest
from _pytest.mark import MarkDecorator, MarkerError
from py._code.code import TerminalRepr
try:
import enum
except ImportError: # pragma: no cover
# Only available in Python 3.4+ or as a backport
enum = None
import _pytest
cutdir = py.path.local(_pytest.__file__).dirpath()
import _pytest._pluggy as pluggy
cutdir2 = py.path.local(_pytest.__file__).dirpath()
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
NoneType = type(None)
NOTSET = object()
@@ -17,11 +30,60 @@ isclass = inspect.isclass
callable = py.builtin.callable
# used to work around a python2 exception info leak
exc_clear = getattr(sys, 'exc_clear', lambda: None)
# The type of re.compile objects is not exposed in Python.
REGEX_TYPE = type(re.compile(''))
_PY3 = sys.version_info > (3, 0)
_PY2 = not _PY3
if hasattr(inspect, 'signature'):
def _format_args(func):
return str(inspect.signature(func))
else:
def _format_args(func):
return inspect.formatargspec(*inspect.getargspec(func))
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))
def _has_positional_arg(func):
return func.__code__.co_argcount
def filter_traceback(entry):
# entry.path might sometimes return a str object when the entry
# points to dynamically generated code
# see https://bitbucket.org/pytest-dev/py/issues/71
raw_filename = entry.frame.code.raw.co_filename
is_generated = '<' in raw_filename and '>' in raw_filename
if is_generated:
return False
# entry.path might point to an inexisting file, in which case it will
# alsso return a str object. see #1133
p = py.path.local(entry.path)
return p != cutdir1 and not p.relto(cutdir2)
def get_real_func(obj):
""" gets the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial.
"""
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
if isinstance(obj, functools.partial):
obj = obj.func
return obj
def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
obj = get_real_func(obj)
if hasattr(obj, 'place_as'):
obj = obj.place_as
fslineno = py.code.getfslineno(obj)
@@ -37,6 +99,17 @@ def getimfunc(func):
except AttributeError:
return func
def safe_getattr(object, name, default):
""" Like getattr but return default upon any Exception.
Attribute access can potentially fail for 'evil' Python objects.
See issue214
"""
try:
return getattr(object, name, default)
except Exception:
return default
class FixtureFunctionMarker:
def __init__(self, scope, params,
@@ -135,6 +208,12 @@ def pytest_addoption(parser):
help="prefixes or glob names for Python test function and "
"method discovery")
group.addoption("--import-mode", default="prepend",
choices=["prepend", "append"], dest="importmode",
help="prepend/append to sys.path when importing test modules, "
"default is to prepend.")
def pytest_cmdline_main(config):
if config.option.showfixtures:
showfixtures(config)
@@ -142,10 +221,13 @@ def pytest_cmdline_main(config):
def pytest_generate_tests(metafunc):
# this misspelling is common - raise a specific error to alert the user
if hasattr(metafunc.function, 'parameterize'):
msg = "{0} has 'parameterize', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__))
# those alternative spellings are common - raise a specific error to alert
# the user
alt_spellings = ['parameterize', 'parametrise', 'parameterise']
for attr in alt_spellings:
if hasattr(metafunc.function, attr):
msg = "{0} has '{1}', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__, attr))
try:
markers = metafunc.function.parametrize
except AttributeError:
@@ -172,7 +254,7 @@ def pytest_configure(config):
def pytest_sessionstart(session):
session._fixturemanager = FixtureManager(session)
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_namespace():
raises.Exception = pytest.fail.Exception
return {
@@ -191,7 +273,7 @@ def pytestconfig(request):
return request.config
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction():
@@ -219,7 +301,7 @@ def pytest_collect_file(path, parent):
def pytest_pycollect_makemodule(path, parent):
return Module(path, parent)
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield
res = outcome.get_result()
@@ -227,18 +309,20 @@ def pytest_pycollect_makeitem(collector, name, obj):
raise StopIteration
# nothing was collected elsewhere, let's do it here
if isclass(obj):
if collector.classnamefilter(name):
if collector.istestclass(obj, name):
Class = collector._getcustomclass("Class")
outcome.force_result(Class(name, parent=collector))
elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and\
getfixturemarker(obj) is None:
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj)
if not isfunction(obj):
# We need to try and unwrap the function if it's a functools.partial
# or a funtools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only)
if not (isfunction(obj) or isfunction(get_real_func(obj))):
collector.warn(code="C2", message=
"cannot collect %r because it is not a function."
% name, )
if getattr(obj, "__test__", True):
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Generator(name, parent=collector)
else:
@@ -317,9 +401,24 @@ class PyCollector(PyobjMixin, pytest.Collector):
def funcnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_functions', name)
def isnosetest(self, obj):
""" Look for the __test__ attribute, which is applied by the
@nose.tools.istest decorator
"""
return safe_getattr(obj, '__test__', False)
def classnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_classes', name)
def istestfunction(self, obj, name):
return (
(self.funcnamefilter(name) or self.isnosetest(obj))
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
)
def istestclass(self, obj, name):
return self.classnamefilter(name) or self.isnosetest(obj)
def _matches_prefix_or_glob_option(self, option_name, name):
"""
checks if the given name matches the prefix or glob-pattern defined
@@ -375,13 +474,16 @@ class PyCollector(PyobjMixin, pytest.Collector):
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
cls=cls, module=module)
try:
methods = [module.pytest_generate_tests]
except AttributeError:
methods = []
methods = []
if hasattr(module, "pytest_generate_tests"):
methods.append(module.pytest_generate_tests)
if hasattr(cls, "pytest_generate_tests"):
methods.append(cls().pytest_generate_tests)
self.ihook.pytest_generate_tests.callextra(methods, metafunc=metafunc)
if methods:
self.ihook.pytest_generate_tests.call_extra(methods,
dict(metafunc=metafunc))
else:
self.ihook.pytest_generate_tests(metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls:
@@ -459,6 +561,19 @@ class FuncFixtureInfo:
self.names_closure = names_closure
self.name2fixturedefs = name2fixturedefs
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark
# plugin should merge with the python plugin.
@@ -469,9 +584,11 @@ def transfer_markers(funcobj, cls, mod):
continue
if isinstance(pytestmark, list):
for mark in pytestmark:
mark(funcobj)
if not _marked(funcobj, mark):
mark(funcobj)
else:
pytestmark(funcobj)
if not _marked(funcobj, pytestmark):
pytestmark(funcobj)
class Module(pytest.File, PyCollector):
""" Collector for test classes and functions. """
@@ -484,8 +601,9 @@ class Module(pytest.File, PyCollector):
def _importtestmodule(self):
# we assume we are only called once per module
importmode = self.config.getoption("--import-mode")
try:
mod = self.fspath.pyimport(ensuresyspath=True)
mod = self.fspath.pyimport(ensuresyspath=importmode)
except SyntaxError:
raise self.CollectError(
py.code.ExceptionInfo().getrepr(style="short"))
@@ -513,7 +631,7 @@ class Module(pytest.File, PyCollector):
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, its probably a pytest style one
# so we pass the current module object
if inspect.getargspec(setup_module)[0]:
if _has_positional_arg(setup_module):
setup_module(self.obj)
else:
setup_module()
@@ -524,7 +642,7 @@ class Module(pytest.File, PyCollector):
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, it's probably a pytest style one
# so we pass the current module object
if inspect.getargspec(fin)[0]:
if _has_positional_arg(fin):
finalizer = lambda: fin(self.obj)
else:
finalizer = fin
@@ -594,14 +712,18 @@ class FunctionMixin(PyobjMixin):
def _prunetraceback(self, excinfo):
if hasattr(self, '_obj') and not self.config.option.fulltrace:
code = py.code.Code(self.obj)
code = py.code.Code(get_real_func(self.obj))
path, firstlineno = code.path, code.firstlineno
traceback = excinfo.traceback
ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
if ntraceback == traceback:
ntraceback = ntraceback.cut(path=path)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=cutdir)
#ntraceback = ntraceback.cut(excludepath=cutdir2)
ntraceback = ntraceback.filter(filter_traceback)
if not ntraceback:
ntraceback = traceback
excinfo.traceback = ntraceback.filter()
# issue364: mark all but first and last frames to
# only show a single-line message for each frame
@@ -736,11 +858,12 @@ class CallSpec2(object):
def id(self):
return "-".join(map(str, filter(None, self._idlist)))
def setmulti(self, valtype, argnames, valset, id, keywords, scopenum,
def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
param_index):
for arg,val in zip(argnames, valset):
self._checkargnotcontained(arg)
getattr(self, valtype)[arg] = val
valtype_for_arg = valtypes[arg]
getattr(self, valtype_for_arg)[arg] = val
self.indices[arg] = param_index
self._arg2scopenum[arg] = scopenum
if val is _notexists:
@@ -771,6 +894,27 @@ class FuncargnamesCompatAttr:
return self.fixturenames
class Metafunc(FuncargnamesCompatAttr):
"""
Metafunc objects are passed to the ``pytest_generate_tests`` hook.
They help to inspect a test function and to generate tests according to
test configuration or values specified in the class or module where a
test function is defined.
:ivar fixturenames: set of fixture names required by the test function
:ivar function: underlying python test function
:ivar cls: class object where the test function is defined in or ``None``.
:ivar module: the module object where the test function is defined in.
:ivar config: access to the :class:`_pytest.config.Config` object for the
test session.
:ivar funcargnames:
.. deprecated:: 2.3
Use ``fixturenames`` instead.
"""
def __init__(self, function, fixtureinfo, config, cls=None, module=None):
self.config = config
self.module = module
@@ -786,19 +930,21 @@ class Metafunc(FuncargnamesCompatAttr):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
during the collection phase. If you need to setup expensive resources
see about setting indirect=True to do it rather at test setup time.
see about setting indirect to do it rather at test setup time.
:arg argnames: a comma-separated string denoting one or more argument
names, or a list/tuple of argument strings.
:arg argvalues: The list of argvalues determines how often a
test is invoked with different argument values. If only one
argname was specified argvalues is a list of simple values. If N
argname was specified argvalues is a list of values. If N
argnames were specified, argvalues must be a list of N-tuples,
where each tuple-element specifies a value for its respective
argname.
:arg indirect: if True each argvalue corresponding to an argname will
:arg indirect: The list of argnames or boolean. A list of arguments'
names (subset of argnames). If True the list contains all names from
the argnames. Each argvalue corresponding to an argname in this list will
be passed as request.param to its respective argname fixture
function so that it can perform more expensive setups during the
setup phase of a test rather than at collection time.
@@ -843,13 +989,22 @@ class Metafunc(FuncargnamesCompatAttr):
if scope is None:
scope = "function"
scopenum = scopes.index(scope)
if not indirect:
#XXX should we also check for the opposite case?
for arg in argnames:
if arg not in self.fixturenames:
raise ValueError("%r uses no fixture %r" %(
valtypes = {}
for arg in argnames:
if arg not in self.fixturenames:
raise ValueError("%r uses no fixture %r" %(self.function, arg))
if indirect is True:
valtypes = dict.fromkeys(argnames, "params")
elif indirect is False:
valtypes = dict.fromkeys(argnames, "funcargs")
elif isinstance(indirect, (tuple, list)):
valtypes = dict.fromkeys(argnames, "funcargs")
for arg in indirect:
if arg not in argnames:
raise ValueError("indirect given to %r: fixture %r doesn't exist" %(
self.function, arg))
valtype = indirect and "params" or "funcargs"
valtypes[arg] = "params"
idfn = None
if callable(ids):
idfn = ids
@@ -864,7 +1019,7 @@ class Metafunc(FuncargnamesCompatAttr):
for param_index, valset in enumerate(argvalues):
assert len(valset) == len(argnames)
newcallspec = callspec.copy(self)
newcallspec.setmulti(valtype, argnames, valset, ids[param_index],
newcallspec.setmulti(valtypes, argnames, valset, ids[param_index],
newkeywords.get(param_index, {}), scopenum,
param_index)
newcalls.append(newcallspec)
@@ -907,6 +1062,40 @@ class Metafunc(FuncargnamesCompatAttr):
self._calls.append(cs)
if _PY3:
import codecs
def _escape_bytes(val):
"""
If val is pure ascii, returns it as a str(), otherwise escapes
into a sequence of escaped bytes:
b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
note:
the obvious "v.decode('unicode-escape')" will return
valid utf-8 unicode if it finds them in the string, but we
want to return escaped bytes for any byte, even if they match
a utf-8 string.
"""
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 ''
else:
def _escape_bytes(val):
"""
In py2 bytes and str are the same type, so return it unchanged if it
is a full ascii string, otherwise escape it into its binary form.
"""
try:
return val.decode('ascii')
except UnicodeDecodeError:
return val.encode('string-escape')
def _idval(val, argname, idx, idfn):
if idfn:
try:
@@ -915,8 +1104,25 @@ def _idval(val, argname, idx, idfn):
return s
except Exception:
pass
if isinstance(val, (float, int, str, bool, NoneType)):
if isinstance(val, bytes):
return _escape_bytes(val)
elif isinstance(val, (float, int, str, bool, NoneType)):
return str(val)
elif isinstance(val, REGEX_TYPE):
return val.pattern
elif enum is not None and isinstance(val, enum.Enum):
return str(val)
elif isclass(val) and hasattr(val, '__name__'):
return val.__name__
elif _PY2 and isinstance(val, unicode):
# special case for python 2: if a unicode string is
# convertible to ascii, return it as an str() object instead
try:
return str(val)
except UnicodeError:
# fallthrough
pass
return str(argname)+str(idx)
def _idvalset(idx, valset, argnames, idfn):
@@ -937,23 +1143,16 @@ def showfixtures(config):
return wrap_session(config, _showfixtures_main)
def _showfixtures_main(config, session):
import _pytest.config
session.perform_collect()
curdir = py.path.local()
if session.items:
nodeid = session.items[0].nodeid
else:
part = session._initialparts[0]
nodeid = "::".join(map(str, [curdir.bestrelpath(part[0])] + part[1:]))
nodeid.replace(session.fspath.sep, "/")
tw = py.io.TerminalWriter()
tw = _pytest.config.create_terminal_writer(config)
verbose = config.getvalue("verbose")
fm = session._fixturemanager
available = []
for argname in fm._arg2fixturedefs:
fixturedefs = fm.getfixturedefs(argname, nodeid)
for argname, fixturedefs in fm._arg2fixturedefs.items():
assert fixturedefs is not None
if not fixturedefs:
continue
@@ -998,8 +1197,8 @@ def getlocation(function, curdir):
# builtin pytest.raises helper
def raises(ExpectedException, *args, **kwargs):
""" assert that a code block/function call raises @ExpectedException
def raises(expected_exception, *args, **kwargs):
""" assert that a code block/function call raises @expected_exception
and raise a failure exception otherwise.
This helper produces a ``py.code.ExceptionInfo()`` object.
@@ -1010,6 +1209,28 @@ def raises(ExpectedException, *args, **kwargs):
>>> with raises(ZeroDivisionError):
... 1/0
.. note::
When using ``pytest.raises`` as a context manager, it's worthwhile to
note that normal context manager rules apply and that the exception
raised *must* be the final line in the scope of the context manager.
Lines of code after that, within the scope of the context manager will
not be executed. For example::
>>> with raises(OSError) as err:
assert 1 == 1 # this will execute as expected
raise OSError(errno.EEXISTS, 'directory exists')
assert err.errno == errno.EEXISTS # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
>>> with raises(OSError) as err:
assert 1 == 1 # this will execute as expected
raise OSError(errno.EEXISTS, 'directory exists')
assert err.errno == errno.EEXISTS # this will now execute
Or you can specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
@@ -1047,23 +1268,23 @@ def raises(ExpectedException, *args, **kwargs):
"""
__tracebackhide__ = True
if ExpectedException is AssertionError:
if expected_exception is AssertionError:
# we want to catch a AssertionError
# replace our subclass with the builtin one
# see https://bitbucket.org/pytest-dev/pytest/issue/176/pytestraises
# see https://github.com/pytest-dev/pytest/issues/176
from _pytest.assertion.util import BuiltinAssertionError \
as ExpectedException
as expected_exception
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
if isinstance(ExpectedException, tuple):
for exc in ExpectedException:
if not inspect.isclass(exc):
if isinstance(expected_exception, tuple):
for exc in expected_exception:
if not isclass(exc):
raise TypeError(msg % type(exc))
elif not inspect.isclass(ExpectedException):
raise TypeError(msg % type(ExpectedException))
elif not isclass(expected_exception):
raise TypeError(msg % type(expected_exception))
if not args:
return RaisesContext(ExpectedException)
return RaisesContext(expected_exception)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
@@ -1076,19 +1297,19 @@ def raises(ExpectedException, *args, **kwargs):
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except ExpectedException:
except expected_exception:
return py.code.ExceptionInfo()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except ExpectedException:
except expected_exception:
return py.code.ExceptionInfo()
pytest.fail("DID NOT RAISE")
class RaisesContext(object):
def __init__(self, ExpectedException):
self.ExpectedException = ExpectedException
def __init__(self, expected_exception):
self.expected_exception = expected_exception
self.excinfo = None
def __enter__(self):
@@ -1107,7 +1328,7 @@ class RaisesContext(object):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
return issubclass(self.excinfo.type, self.ExpectedException)
return issubclass(self.excinfo.type, self.expected_exception)
#
# the basic pytest Function item
@@ -1306,7 +1527,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
return self._pyfuncitem.session
def addfinalizer(self, finalizer):
"""add finalizer/teardown function to be called after the
""" add finalizer/teardown function to be called after the
last test within the requesting test context finished
execution. """
# XXX usually this method is shadowed by fixturedef specific ones
@@ -1470,7 +1691,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
factory = fixturedef.func
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = inspect.formatargspec(*inspect.getargspec(factory))
args = _format_args(factory)
lines.append("%s:%d: def %s%s" %(
p, lineno, factory.__name__, args))
return lines
@@ -1545,7 +1766,7 @@ class FixtureLookupError(LookupError):
for function in stack:
fspath, lineno = getfslineno(function)
try:
lines, _ = inspect.getsourcelines(function)
lines, _ = inspect.getsourcelines(get_real_func(function))
except IOError:
error_msg = "file %s, line %s: source code not available"
addline(error_msg % (fspath, lineno+1))
@@ -1628,7 +1849,6 @@ class FixtureManager:
self.session = session
self.config = session.config
self._arg2fixturedefs = {}
self._seenplugins = set()
self._holderobjseen = set()
self._arg2finish = {}
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
@@ -1653,11 +1873,7 @@ class FixtureManager:
node)
return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
### XXX this hook should be called for historic events like pytest_configure
### so that we don't have to do the below pytest_configure hook
def pytest_plugin_registered(self, plugin):
if plugin in self._seenplugins:
return
nodeid = None
try:
p = py.path.local(plugin.__file__)
@@ -1672,13 +1888,6 @@ class FixtureManager:
if p.sep != "/":
nodeid = nodeid.replace(p.sep, "/")
self.parsefactories(plugin, nodeid)
self._seenplugins.add(plugin)
@pytest.mark.tryfirst
def pytest_configure(self, config):
plugins = config.pluginmanager.getplugins()
for plugin in plugins:
self.pytest_plugin_registered(plugin)
def _getautousenames(self, nodeid):
""" return a tuple of fixture names to be used. """
@@ -1732,7 +1941,10 @@ class FixtureManager:
if fixturedef.params is not None:
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
# skip directly parametrized arguments
if argname not in func_params and argname not in func_params[0]:
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:
metafunc.parametrize(argname, fixturedef.params,
indirect=True, scope=fixturedef.scope,
ids=fixturedef.ids)
@@ -1755,14 +1967,14 @@ class FixtureManager:
autousenames = []
for name in dir(holderobj):
obj = getattr(holderobj, name, None)
if not callable(obj):
continue
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
# or are "@pytest.fixture" marked
marker = getfixturemarker(obj)
if marker is None:
if not name.startswith(self._argprefix):
continue
if not callable(obj):
continue
marker = defaultfuncargprefixmarker
name = name[len(self._argprefix):]
elif not isinstance(marker, FixtureFunctionMarker):
@@ -1898,10 +2110,13 @@ class FixtureDef:
self.finish()
assert not hasattr(self, "cached_result")
fixturefunc = self.func
if self.unittest:
result = self.func(request.instance, **kwargs)
if request.instance is not None:
# bind the unbound method to the TestCase instance
fixturefunc = self.func.__get__(request.instance)
else:
fixturefunc = self.func
# the fixture function needs to be bound to the actual
# request.instance so that code working with "self" behaves
# as expected.
@@ -1909,12 +2124,13 @@ class FixtureDef:
fixturefunc = getimfunc(self.func)
if fixturefunc != self.func:
fixturefunc = fixturefunc.__get__(request.instance)
try:
result = call_fixture_func(fixturefunc, request, kwargs,
self.yieldctx)
except Exception:
self.cached_result = (None, my_cache_key, sys.exc_info())
raise
try:
result = call_fixture_func(fixturefunc, request, kwargs,
self.yieldctx)
except Exception:
self.cached_result = (None, my_cache_key, sys.exc_info())
raise
self.cached_result = (result, my_cache_key, None)
return result
@@ -1936,7 +2152,7 @@ def num_mock_patch_args(function):
def getfuncargnames(function, startindex=None):
# XXX merge with main.py's varnames
#assert not inspect.isclass(function)
#assert not isclass(function)
realfunction = function
while hasattr(realfunction, "__wrapped__"):
realfunction = realfunction.__wrapped__
@@ -1945,7 +2161,15 @@ def getfuncargnames(function, startindex=None):
if realfunction != function:
startindex += num_mock_patch_args(function)
function = realfunction
argnames = inspect.getargs(py.code.getrawcode(function))[0]
if isinstance(function, functools.partial):
argnames = inspect.getargs(py.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(py.code.getrawcode(function))[0]
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)

View File

@@ -1,10 +1,14 @@
""" recording warnings during test function execution. """
import inspect
import py
import sys
import warnings
import pytest
def pytest_funcarg__recwarn(request):
@pytest.yield_fixture
def recwarn(request):
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
@@ -13,83 +17,174 @@ def pytest_funcarg__recwarn(request):
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
oldfilters = warnings.filters[:]
warnings.simplefilter('default')
def reset_filters():
warnings.filters[:] = oldfilters
request.addfinalizer(reset_filters)
wrec = WarningsRecorder()
request.addfinalizer(wrec.finalize)
return wrec
with wrec:
warnings.simplefilter('default')
yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call}
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)``
triggers a DeprecationWarning.
"""Assert that ``func(*args, **kwargs)`` triggers a DeprecationWarning.
"""
l = []
oldwarn_explicit = getattr(warnings, 'warn_explicit')
def warn_explicit(*args, **kwargs):
l.append(args)
oldwarn_explicit(*args, **kwargs)
oldwarn = getattr(warnings, 'warn')
def warn(*args, **kwargs):
l.append(args)
oldwarn(*args, **kwargs)
warnings.warn_explicit = warn_explicit
warnings.warn = warn
try:
wrec = WarningsRecorder()
with wrec:
warnings.simplefilter('always') # ensure all warnings are triggered
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = warn_explicit
warnings.warn = warn
if not l:
depwarnings = (DeprecationWarning, PendingDeprecationWarning)
if not any(r.category in depwarnings for r in wrec):
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
return ret
class RecordedWarning:
def __init__(self, message, category, filename, lineno, line):
def warns(expected_warning, *args, **kwargs):
"""Assert that code raises a particular class of warning.
Specifically, the input @expected_warning can be a warning class or
tuple of warning classes, and the code must return that warning
(if a single class) or one of those warnings (if a tuple).
This helper produces a list of ``warnings.WarningMessage`` objects,
one for each warning raised.
This function can be used as a context manager, or any of the other ways
``pytest.raises`` can be used::
>>> with warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
"""
wcheck = WarningsChecker(expected_warning)
if not args:
return wcheck
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
with wcheck:
code = py.code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
return func(*args[1:], **kwargs)
class RecordedWarning(object):
def __init__(self, message, category, filename, lineno, file, line):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.file = file
self.line = line
class WarningsRecorder:
def __init__(self):
self.list = []
def showwarning(message, category, filename, lineno, line=0):
self.list.append(RecordedWarning(
message, category, filename, lineno, line))
try:
self.old_showwarning(message, category,
filename, lineno, line=line)
except TypeError:
# < python2.6
self.old_showwarning(message, category, filename, lineno)
self.old_showwarning = warnings.showwarning
warnings.showwarning = showwarning
class WarningsRecorder(object):
"""A context manager to record raised warnings.
Adapted from `warnings.catch_warnings`.
"""
def __init__(self, module=None):
self._module = sys.modules['warnings'] if module is None else module
self._entered = False
self._list = []
@property
def list(self):
"""The list of recorded warnings."""
return self._list
def __getitem__(self, i):
"""Get a recorded warning by index."""
return self._list[i]
def __iter__(self):
"""Iterate through the recorded warnings."""
return iter(self._list)
def __len__(self):
"""The number of recorded warnings."""
return len(self._list)
def pop(self, cls=Warning):
""" pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self.list):
"""Pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self._list):
if issubclass(w.category, cls):
return self.list.pop(i)
return self._list.pop(i)
__tracebackhide__ = True
assert 0, "%r not found in %r" %(cls, self.list)
#def resetregistry(self):
# warnings.onceregistry.clear()
# warnings.__warningregistry__.clear()
raise AssertionError("%r not found in warning list" % cls)
def clear(self):
self.list[:] = []
"""Clear the list of recorded warnings."""
self._list[:] = []
def finalize(self):
warnings.showwarning = self.old_showwarning
def __enter__(self):
if self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
def showwarning(message, category, filename, lineno,
file=None, line=None):
self._list.append(RecordedWarning(
message, category, filename, lineno, file, line))
# still perform old showwarning functionality
self._showwarning(
message, category, filename, lineno, file=file, line=line)
self._module.showwarning = showwarning
# allow the same warning to be raised more than once
self._module.simplefilter('always', append=True)
return self
def __exit__(self, *exc_info):
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None):
super(WarningsChecker, self).__init__(module=module)
msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s")
if isinstance(expected_warning, tuple):
for exc in expected_warning:
if not inspect.isclass(exc):
raise TypeError(msg % type(exc))
elif inspect.isclass(expected_warning):
expected_warning = (expected_warning,)
elif expected_warning is not None:
raise TypeError(msg % type(expected_warning))
self.expected_warning = expected_warning
def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
# only check if we're not currently handling an exception
if all(a is None for a in exc_info):
if self.expected_warning is not None:
if not any(r.category in self.expected_warning for r in self):
__tracebackhide__ = True
pytest.fail("DID NOT WARN")

View File

@@ -226,7 +226,7 @@ def pytest_runtest_makereport(item, call):
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
for rwhen, key, content in item._report_sections:
sections.append(("Captured std%s %s" %(key, rwhen), content))
sections.append(("Captured %s %s" %(key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
sections, duration)
@@ -483,8 +483,6 @@ def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
Note that version comparison only works with simple version strings
like "1.2.3" but not "1.2.3.dev1" or others.
"""
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
@@ -496,9 +494,14 @@ def importorskip(modname, minversion=None):
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
def intver(verstring):
return [int(x) for x in verstring.split(".")]
if verattr is None or intver(verattr) < intver(minversion):
skip("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion))
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
skip("we have a required version for %r but can not import "
"no pkg_resources to parse version strings." %(modname,))
if verattr is None or pv(verattr) < pv(minversion):
skip("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion))
return mod

View File

@@ -98,24 +98,36 @@ class MarkEvaluator:
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder:
d = self._getglobals()
if self.holder.args:
self.result = False
for expr in self.holder.args:
self.expr = expr
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if self.get("reason") is None:
# XXX better be checked at collection time
pytest.fail("you need to specify reason=STRING "
"when using booleans as conditions.")
result = bool(expr)
if result:
self.result = True
# "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an
# _arglist attribute
if hasattr(self.holder, '_arglist'):
arglist = self.holder._arglist
else:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
for expr in args:
self.expr = expr
break
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
pytest.fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = kwargs.get('reason', None)
self.expr = expr
return self.result
else:
self.result = True
return getattr(self, 'result', False)
@@ -124,7 +136,7 @@ class MarkEvaluator:
return self.holder.kwargs.get(attr, default)
def getexplanation(self):
expl = self.get('reason', None)
expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""
@@ -133,7 +145,7 @@ class MarkEvaluator:
return expl
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
evalskip = MarkEvaluator(item, 'skipif')
if evalskip.istrue():
@@ -152,7 +164,7 @@ def check_xfail_no_run(item):
if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()

View File

@@ -68,6 +68,11 @@ class DictImporter(object):
return res
if __name__ == "__main__":
try:
import pkg_resources # noqa
except ImportError:
sys.stderr.write("ERROR: setuptools not installed\n")
sys.exit(2)
if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
@@ -80,6 +85,5 @@ if __name__ == "__main__":
importer = DictImporter(sources)
sys.meta_path.insert(0, importer)
entry = "@ENTRY@"
do_exec(entry, locals()) # noqa

View File

@@ -2,10 +2,15 @@
This is a good source for looking at the various reporting hooks.
"""
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import py
import sys
import time
import platform
import _pytest._pluggy as pluggy
def pytest_addoption(parser):
@@ -17,7 +22,7 @@ def pytest_addoption(parser):
group._addoption('-r',
action="store", dest="reportchars", default=None, metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed (w)warnings.")
"(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings (a)all.")
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
@@ -62,8 +67,10 @@ def getreportopt(config):
reportchars = config.option.reportchars
if reportchars:
for char in reportchars:
if char not in reportopts:
if char not in reportopts and char != 'a':
reportopts += char
elif char == 'a':
reportopts = 'fEsxXw'
return reportopts
def pytest_report_teststatus(report):
@@ -87,6 +94,7 @@ class WarningReport:
class TerminalReporter:
def __init__(self, config, file=None):
import _pytest.config
self.config = config
self.verbosity = self.config.option.verbose
self.showheader = self.verbosity >= 0
@@ -98,11 +106,8 @@ class TerminalReporter:
self.startdir = py.path.local()
if file is None:
file = sys.stdout
self._tw = self.writer = py.io.TerminalWriter(file)
if self.config.option.color == 'yes':
self._tw.hasmarkup = True
if self.config.option.color == 'no':
self._tw.hasmarkup = False
self._tw = self.writer = _pytest.config.create_terminal_writer(config,
file)
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
@@ -164,6 +169,8 @@ class TerminalReporter:
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
if isinstance(fslocation, tuple):
fslocation = "%s:%d" % fslocation
warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid)
warnings.append(warning)
@@ -265,18 +272,19 @@ class TerminalReporter:
def pytest_collection_modifyitems(self):
self.report_collect(True)
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session):
self._sessionstarttime = time.time()
if not self.showheader:
return
self.write_sep("=", "test session starts", bold=True)
verinfo = ".".join(map(str, sys.version_info[:3]))
verinfo = platform.python_version()
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
if hasattr(sys, 'pypy_version_info'):
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__)
msg += ", pytest-%s, py-%s, pluggy-%s" % (
pytest.__version__, py.__version__, pluggy.__version__)
if self.verbosity > 0 or self.config.option.debug or \
getattr(self.config.option, 'pastebin', None):
msg += " -- " + str(sys.executable)
@@ -292,15 +300,12 @@ class TerminalReporter:
if config.inifile:
inifile = config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
plugininfo = config.pluginmanager._plugin_distinfo
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
l = []
for dist, plugin in plugininfo:
name = dist.project_name
if name.startswith("pytest-"):
name = name[7:]
l.append(name)
lines.append("plugins: %s" % ", ".join(l))
lines.append(
"plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return lines
def pytest_collection_finish(self, session):
@@ -350,17 +355,20 @@ class TerminalReporter:
indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col))
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_sessionfinish(self, exitstatus):
outcome = yield
outcome.get_result()
self._tw.line("")
if exitstatus in (0, 1, 2, 4):
summary_exit_codes = (
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
EXIT_NOTESTSCOLLECTED)
if exitstatus in summary_exit_codes:
self.summary_errors()
self.summary_failures()
self.summary_warnings()
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
if exitstatus == EXIT_INTERRUPTED:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
@@ -433,7 +441,7 @@ class TerminalReporter:
warnings = self.stats.get("warnings")
if not warnings:
return
self.write_sep("=", "warning summary")
self.write_sep("=", "pytest-warning summary")
for w in warnings:
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message))
@@ -450,7 +458,8 @@ class TerminalReporter:
self.write_line(line)
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
markup = {'red': True, 'bold': True}
self.write_sep("_", msg, **markup)
self._outrep_summary(rep)
def summary_errors(self):
@@ -481,26 +490,9 @@ class TerminalReporter:
def summary_stats(self):
session_duration = time.time() - self._sessionstarttime
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings").split()
for key in self.stats.keys():
if key not in keys:
keys.append(key)
parts = []
for key in keys:
if key: # setup/teardown reports have an empty key, ignore them
val = self.stats.get(key, None)
if val:
parts.append("%d %s" % (len(val), key))
line = ", ".join(parts)
(line, color) = build_summary_stats_line(self.stats)
msg = "%s in %.2f seconds" % (line, session_duration)
markup = {'bold': True}
if 'failed' in self.stats or 'error' in self.stats:
markup = {'red': True, 'bold': True}
else:
markup = {'green': True, 'bold': True}
markup = {color: True, 'bold': True}
if self.verbosity >= 0:
self.write_sep("=", msg, **markup)
@@ -536,3 +528,46 @@ def flatten(l):
else:
yield x
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings error").split()
key_translation = {'warnings': 'pytest-warnings'}
unknown_key_seen = False
for key in stats.keys():
if key not in keys:
if key: # setup/teardown reports have an empty key, ignore them
keys.append(key)
unknown_key_seen = True
parts = []
for key in keys:
val = stats.get(key, None)
if val:
key_name = key_translation.get(key, key)
parts.append("%d %s" % (len(val), key_name))
line = ", ".join(parts)
if 'failed' in stats or 'error' in stats:
color = 'red'
elif 'warnings' in stats or unknown_key_seen:
color = 'yellow'
elif 'passed' in stats:
color = 'green'
else:
color = 'yellow'
return (line, color)
def _plugin_nameversions(plugininfo):
l = []
for plugin, dist in plugininfo:
# gets us name and version!
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
# questionable convenience, but it keeps things short
if name.startswith("pytest-"):
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

View File

@@ -6,7 +6,12 @@ import py
from _pytest.monkeypatch import monkeypatch
class TempdirHandler:
class TempdirFactory:
"""Factory for temporary directories under the common base temp directory.
The base directory can be configured using the ``--basetemp`` option.
"""
def __init__(self, config):
self.config = config
self.trace = config.trace.get("tmpdir")
@@ -22,6 +27,10 @@ class TempdirHandler:
return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True):
"""Create a subdirectory of the base temporary directory and return it.
If ``numbered``, ensure the directory is unique by adding a number
prefix greater than any existing one.
"""
basetemp = self.getbasetemp()
if not numbered:
p = basetemp.mkdir(basename)
@@ -43,7 +52,17 @@ class TempdirHandler:
basetemp.remove()
basetemp.mkdir()
else:
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
temproot = py.path.local.get_temproot()
user = get_user()
if user:
# use a sub-directory in the temproot to speed-up
# make_numbered_dir() call
rootdir = temproot.join('pytest-of-%s' % user)
else:
rootdir = temproot
rootdir.ensure(dir=1)
basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
rootdir=rootdir)
self._basetemp = t = basetemp.realpath()
self.trace("new basetemp", t)
return t
@@ -51,15 +70,44 @@ class TempdirHandler:
def finish(self):
self.trace("finish")
def get_user():
"""Return the current user name, or None if getuser() does not work
in the current environment (see #1010).
"""
import getpass
try:
return getpass.getuser()
except (ImportError, KeyError):
return None
# backward compatibility
TempdirHandler = TempdirFactory
def pytest_configure(config):
"""Create a TempdirFactory and attach it to the config object.
This is to comply with existing plugins which expect the handler to be
available at pytest_configure time, but ideally should be moved entirely
to the tmpdir_factory session fixture.
"""
mp = monkeypatch()
t = TempdirHandler(config)
t = TempdirFactory(config)
config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
@pytest.fixture(scope='session')
def tmpdir_factory(request):
"""Return a TempdirFactory instance for the test session.
"""
return request.config._tmpdirhandler
@pytest.fixture
def tmpdir(request):
def tmpdir(request, tmpdir_factory):
"""return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
@@ -71,5 +119,5 @@ def tmpdir(request):
MAXVAL = 30
if len(name) > MAXVAL:
name = name[:MAXVAL]
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
x = tmpdir_factory.mktemp(name, numbered=True)
return x

View File

@@ -69,12 +69,26 @@ class TestCaseFunction(pytest.Function):
def setup(self):
self._testcase = self.parent.obj(self.name)
self._fix_unittest_skip_decorator()
self._obj = getattr(self._testcase, self.name)
if hasattr(self._testcase, 'setup_method'):
self._testcase.setup_method(self._obj)
if hasattr(self, "_request"):
self._request._fillfixtures()
def _fix_unittest_skip_decorator(self):
"""
The @unittest.skip decorator calls functools.wraps(self._testcase)
The call to functools.wraps() fails unless self._testcase
has a __name__ attribute. This is usually automatically supplied
if the test is a function or method, but we need to add manually
here.
See issue #1169
"""
if sys.version_info[0] == 2:
setattr(self._testcase, "__name__", self.name)
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):
self._testcase.teardown_method(self._obj)
@@ -143,7 +157,7 @@ class TestCaseFunction(pytest.Function):
if traceback:
excinfo.traceback = traceback
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
if item._excinfo:
@@ -155,7 +169,7 @@ def pytest_runtest_makereport(item, call):
# twisted trial support
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules:

View File

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

View File

View File

@@ -0,0 +1,10 @@
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

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

View File

@@ -0,0 +1,8 @@
pluggy.py,sha256=v_RfWzyW6DPU1cJu_EFoL_OHq3t13qloVdR6UaMCXQA,29862
pluggy-0.3.1.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
pluggy-0.3.1.dist-info/pbr.json,sha256=xX3s6__wOcAyF-AZJX1sdZyW6PUXT-FkfBlM69EEUCg,47
pluggy-0.3.1.dist-info/RECORD,,
pluggy-0.3.1.dist-info/metadata.json,sha256=nLKltOT78dMV-00uXD6Aeemp4xNsz2q59j6ORSDeLjw,1027
pluggy-0.3.1.dist-info/METADATA,sha256=1b85Ho2u4iK30M099k7axMzcDDhLcIMb-A82JUJZnSo,1334
pluggy-0.3.1.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
pluggy-0.3.1.dist-info/DESCRIPTION.rst,sha256=P5Akh1EdIBR6CeqtV2P8ZwpGSpZiTKPw0NyS7jEiD-g,306

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
pluggy

View File

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

98
appveyor.yml Normal file
View File

@@ -0,0 +1,98 @@
environment:
matrix:
# Pre-installed Python versions, which Appveyor may upgrade to
# a later point release.
- PYTHON: "C:\\Python27"
PYTHON_VERSION: "2.7.x" # currently 2.7.9
PYTHON_ARCH: "32"
TESTENV: "py27"
- PYTHON: "C:\\Python27-x64"
PYTHON_VERSION: "2.7.x" # currently 2.7.9
PYTHON_ARCH: "64"
TESTENV: "py27"
- PYTHON: "C:\\Python33"
PYTHON_VERSION: "3.3.x" # currently 3.3.5
PYTHON_ARCH: "32"
TESTENV: "py33"
- PYTHON: "C:\\Python33-x64"
PYTHON_VERSION: "3.3.x" # currently 3.3.5
PYTHON_ARCH: "64"
TESTENV: "py33"
- PYTHON: "C:\\Python34"
PYTHON_VERSION: "3.4.x" # currently 3.4.3
PYTHON_ARCH: "32"
TESTENV: "py34"
- PYTHON: "C:\\Python34-x64"
PYTHON_VERSION: "3.4.x" # currently 3.4.3
PYTHON_ARCH: "64"
TESTENV: "py34"
- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.x" # currently 3.5.0
PYTHON_ARCH: "32"
TESTENV: "py35"
- PYTHON: "C:\\Python35-x64"
PYTHON_VERSION: "3.5.x" # currently 3.5.0
PYTHON_ARCH: "64"
TESTENV: "py35"
# Also test a Python version not pre-installed
# See: https://github.com/ogrisel/python-appveyor-demo/issues/10
- PYTHON: "C:\\Python266"
PYTHON_VERSION: "2.6.6"
PYTHON_ARCH: "32"
TESTENV: "py26"
# xdist testing
- PYTHON: "C:\\Python27"
PYTHON_VERSION: "2.7.x" # currently 2.7.9
PYTHON_ARCH: "32"
TESTENV: "py27-xdist"
- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.x" # currently 3.5.0
PYTHON_ARCH: "32"
TESTENV: "py35-xdist"
install:
- ECHO "Filesystem root:"
- ps: "ls \"C:/\""
- ECHO "Installed SDKs:"
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
# Install Python (from the official .msi of http://python.org) and pip when
# not already installed.
- ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 }
# Prepend newly installed Python to the PATH of this build (this cannot be
# done from inside the powershell script as it would require to restart
# the parent CMD process).
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
# Check that we have the expected version and architecture for Python
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
# Install the build dependencies of the project. If some dependencies contain
# compiled extensions and are not provided as pre-built wheel packages,
# pip will build them from source using the MSVC compiler matching the
# target Python version and architecture
- C:\Python27\python -m pip install tox
build: false # Not a C# project, build stuff at the test step instead.
test_script:
# Build the compiled extension and run the project tests
- C:\Python27\python -m tox -e %TESTENV%

180
appveyor/install.ps1 Normal file
View File

@@ -0,0 +1,180 @@
# Sample script to install Python and pip under Windows
# Authors: Olivier Grisel, Jonathan Helmus and Kyle Kastner
# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
$BASE_URL = "https://www.python.org/ftp/python/"
$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
$GET_PIP_PATH = "C:\get-pip.py"
function DownloadPython ($python_version, $platform_suffix) {
$webclient = New-Object System.Net.WebClient
$filename = "python-" + $python_version + $platform_suffix + ".msi"
$url = $BASE_URL + $python_version + "/" + $filename
$basedir = $pwd.Path + "\"
$filepath = $basedir + $filename
if (Test-Path $filename) {
Write-Host "Reusing" $filepath
return $filepath
}
# Download and retry up to 3 times in case of network transient errors.
Write-Host "Downloading" $filename "from" $url
$retry_attempts = 2
for($i=0; $i -lt $retry_attempts; $i++){
try {
$webclient.DownloadFile($url, $filepath)
break
}
Catch [Exception]{
Start-Sleep 1
}
}
if (Test-Path $filepath) {
Write-Host "File saved at" $filepath
} else {
# Retry once to get the error message if any at the last try
$webclient.DownloadFile($url, $filepath)
}
return $filepath
}
function InstallPython ($python_version, $architecture, $python_home) {
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
if (Test-Path $python_home) {
Write-Host $python_home "already exists, skipping."
return $false
}
if ($architecture -eq "32") {
$platform_suffix = ""
} else {
$platform_suffix = ".amd64"
}
$msipath = DownloadPython $python_version $platform_suffix
Write-Host "Installing" $msipath "to" $python_home
$install_log = $python_home + ".log"
$install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
$uninstall_args = "/qn /x $msipath"
RunCommand "msiexec.exe" $install_args
if (-not(Test-Path $python_home)) {
Write-Host "Python seems to be installed else-where, reinstalling."
RunCommand "msiexec.exe" $uninstall_args
RunCommand "msiexec.exe" $install_args
}
if (Test-Path $python_home) {
Write-Host "Python $python_version ($architecture) installation complete"
} else {
Write-Host "Failed to install Python in $python_home"
Get-Content -Path $install_log
Exit 1
}
}
function RunCommand ($command, $command_args) {
Write-Host $command $command_args
Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
}
function InstallPip ($python_home) {
$pip_path = $python_home + "\Scripts\pip.exe"
$python_path = $python_home + "\python.exe"
if (-not(Test-Path $pip_path)) {
Write-Host "Installing pip..."
$webclient = New-Object System.Net.WebClient
$webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
Write-Host "Executing:" $python_path $GET_PIP_PATH
Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
} else {
Write-Host "pip already installed."
}
}
function DownloadMiniconda ($python_version, $platform_suffix) {
$webclient = New-Object System.Net.WebClient
if ($python_version -eq "3.4") {
$filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe"
} else {
$filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
}
$url = $MINICONDA_URL + $filename
$basedir = $pwd.Path + "\"
$filepath = $basedir + $filename
if (Test-Path $filename) {
Write-Host "Reusing" $filepath
return $filepath
}
# Download and retry up to 3 times in case of network transient errors.
Write-Host "Downloading" $filename "from" $url
$retry_attempts = 2
for($i=0; $i -lt $retry_attempts; $i++){
try {
$webclient.DownloadFile($url, $filepath)
break
}
Catch [Exception]{
Start-Sleep 1
}
}
if (Test-Path $filepath) {
Write-Host "File saved at" $filepath
} else {
# Retry once to get the error message if any at the last try
$webclient.DownloadFile($url, $filepath)
}
return $filepath
}
function InstallMiniconda ($python_version, $architecture, $python_home) {
Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
if (Test-Path $python_home) {
Write-Host $python_home "already exists, skipping."
return $false
}
if ($architecture -eq "32") {
$platform_suffix = "x86"
} else {
$platform_suffix = "x86_64"
}
$filepath = DownloadMiniconda $python_version $platform_suffix
Write-Host "Installing" $filepath "to" $python_home
$install_log = $python_home + ".log"
$args = "/S /D=$python_home"
Write-Host $filepath $args
Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
if (Test-Path $python_home) {
Write-Host "Python $python_version ($architecture) installation complete"
} else {
Write-Host "Failed to install Python in $python_home"
Get-Content -Path $install_log
Exit 1
}
}
function InstallMinicondaPip ($python_home) {
$pip_path = $python_home + "\Scripts\pip.exe"
$conda_path = $python_home + "\Scripts\conda.exe"
if (-not(Test-Path $pip_path)) {
Write-Host "Installing pip..."
$args = "install --yes pip"
Write-Host $conda_path $args
Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
} else {
Write-Host "pip already installed."
}
}
function main () {
InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
InstallPip $env:PYTHON
}
main

View File

@@ -12,6 +12,13 @@ PAPEROPT_a4 = -D latex_paper_size=a4
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@" \
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
@@ -46,7 +53,7 @@ installall: clean install installpdf
@echo "done"
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@@ -6,7 +6,7 @@ def get_version_string():
fn = py.path.local(__file__).join("..", "..", "..",
"_pytest", "__init__.py")
for line in fn.readlines():
if "version" in line:
if "version" in line and not line.strip().startswith('#'):
return eval(line.split("=")[-1])
def get_minor_version_string():

View File

@@ -3,9 +3,9 @@
<li><a href="{{ pathto('index') }}">The pytest Website</a></li>
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
<li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
<li><a href="https://bitbucket.org/pytest-dev/pytest/">pytest @ Bitbucket</a></li>
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
<li><a href="http://pytest.org/latest/plugins_index/index.html">3rd party plugins</a></li>
<li><a href="https://bitbucket.org/pytest-dev/pytest/issues?status=new&status=open">Issue Tracker</a></li>
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
<li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
</ul>

View File

@@ -9,7 +9,7 @@ We will pair experienced pytest users with open source projects, for a month's e
In 2015 we are trying this for the first time. In February and March 2015 we will gather volunteers on both sides, in April we will do the work, and in May we will evaluate how it went. This effort is being coordinated by Brianna Laugher. If you have any questions or comments, you can raise them on the `@pytestdotorg twitter account <https://twitter.com/pytestdotorg>`_ the `issue tracker`_ or the `pytest-dev mailing list`_.
.. _`issue tracker`: https://bitbucket.org/pytest-dev/pytest/issue/676/adopt-pytest-month-2015
.. _`issue tracker`: https://github.com/pytest-dev/pytest/issues/676
.. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev

View File

@@ -5,6 +5,9 @@ Release announcements
.. toctree::
:maxdepth: 2
release-2.8.3
release-2.8.2
release-2.7.2
release-2.7.1
release-2.7.0
release-2.6.3

View File

@@ -0,0 +1,44 @@
pytest-2.8.2: bug fixes
=======================
pytest is a mature Python testing tool with more than a 1100 tests
against itself, passing on many different interpreters and platforms.
This release is supposed to be drop-in compatible to 2.8.1.
See below for the changes and see docs at:
http://pytest.org
As usual, you can upgrade from pypi via::
pip install -U pytest
Thanks to all who contributed to this release, among them:
Bruno Oliveira
Demian Brecht
Florian Bruhin
Ionel Cristian Mărieș
Raphael Pierzina
Ronny Pfannschmidt
holger krekel
Happy testing,
The py.test Development Team
2.8.2 (compared to 2.7.2)
-----------------------------
- fix #1085: proper handling of encoding errors when passing encoded byte
strings to pytest.parametrize in Python 2.
Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
- fix #1087: handling SystemError when passing empty byte strings to
pytest.parametrize in Python 3.
Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
- fix #995: fixed internal error when filtering tracebacks where one entry
was generated by an exec() statement.
Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
for contributing and Bruno Oliveira for the PR.

View File

@@ -0,0 +1,59 @@
pytest-2.8.3: bug fixes
=======================
pytest is a mature Python testing tool with more than a 1100 tests
against itself, passing on many different interpreters and platforms.
This release is supposed to be drop-in compatible to 2.8.2.
See below for the changes and see docs at:
http://pytest.org
As usual, you can upgrade from pypi via::
pip install -U pytest
Thanks to all who contributed to this release, among them:
Bruno Oliveira
Florian Bruhin
Gabe Hollombe
Gabriel Reis
Hartmut Goebel
John Vandenberg
Lee Kamentsky
Michael Birtwell
Raphael Pierzina
Ronny Pfannschmidt
William Martin Stewart
Happy testing,
The py.test Development Team
2.8.3 (compared to 2.8.2)
-----------------------------
- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
support the @unittest.skip decorator on functions and methods.
Thanks Lee Kamentsky for the PR.
- fix #1035: collecting tests if test module level obj has __getattr__().
Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
- fix #331: don't collect tests if their failure cannot be reported correctly
e.g. they are a callable instance of a class.
- fix #1133: fixed internal error when filtering tracebacks where one entry
belongs to a file which is no longer available.
Thanks Bruno Oliveira for the PR.
- enhancement made to highlight in red the name of the failing tests so
they stand out in the output.
Thanks Gabriel Reis for the PR.
- add more talks to the documentation
- extend documentation on the --ignore cli option
- use pytest-runner for setuptools integration
- minor fixes for interaction with OS X El Capitan system integrity protection (thanks Florian)

View File

@@ -1,27 +0,0 @@
.. _apiref:
pytest reference documentation
================================================
.. toctree::
:maxdepth: 2
builtin.txt
customize.txt
assert.txt
fixture.txt
yieldfixture.txt
parametrize.txt
xunit_setup.txt
capture.txt
monkeypatch.txt
xdist.txt
tmpdir.txt
mark.txt
skipping.txt
recwarn.txt
unittest.txt
nose.txt
doctest.txt

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::
$ py.test test_assert1.py
=========================== test session starts ============================
platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1
rootdir: /tmp/doc-exec-87, inifile:
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items
test_assert1.py F
================================= 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.01 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
@@ -114,6 +114,16 @@ like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.
.. _`assertwarns`:
Assertions about expected warnings
-----------------------------------------
.. versionadded:: 2.8
You can check that code raises a particular warning using
:ref:`pytest.warns <warns>`.
.. _newreport:
@@ -135,15 +145,15 @@ when it encounters comparisons. For example::
if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1
rootdir: /tmp/doc-exec-87, inifile:
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
======= FAILURES ========
_______ test_set_comparison ________
def test_set_comparison():
set1 = set("1308")
@@ -157,7 +167,7 @@ if you run this module::
E Use -v to get the full diff
test_assert2.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================
======= 1 failed in 0.12 seconds ========
Special comparisons are done for a number of cases:
@@ -182,15 +192,18 @@ provides an alternative explanation for ``Foo`` objects::
from test_foocompare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return ['Comparing Foo instances:',
' vals: %s != %s' % (left.val, right.val)]
return ['Comparing Foo instances:',
' vals: %s != %s' % (left.val, right.val)]
now, given this test module::
# content of test_foocompare.py
class Foo:
def __init__(self, val):
self.val = val
self.val = val
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
@@ -202,8 +215,8 @@ the conftest file::
$ py.test -q test_foocompare.py
F
================================= FAILURES =================================
_______________________________ test_compare _______________________________
======= FAILURES ========
_______ test_compare ________
def test_compare():
f1 = Foo(1)
@@ -212,8 +225,8 @@ the conftest file::
E assert Comparing Foo instances:
E vals: 1 != 2
test_foocompare.py:8: AssertionError
1 failed in 0.01 seconds
test_foocompare.py:11: AssertionError
1 failed in 0.12 seconds
.. _assert-details:
.. _`assert introspection`:
@@ -228,9 +241,7 @@ Reporting details about a failing assertion is achieved either by rewriting
assert statements before they are run or re-evaluating the assert expression and
recording the intermediate values. Which technique is used depends on the
location of the assert, ``pytest`` configuration, and Python version being used
to run ``pytest``. Note that for assert statements with a manually provided
message, i.e. ``assert expr, message``, no assertion introspection takes place
and the manually provided message will be rendered in tracebacks.
to run ``pytest``.
By default, if the Python version is greater than or equal to 2.6, ``pytest``
rewrites assert statements in test modules. Rewritten assert statements put

View File

@@ -73,6 +73,16 @@ You can ask for available builtin or project-custom
:ref:`fixtures <fixtures>` by typing::
$ py.test -q --fixtures
cache
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
cache.set(key, value)
Keys must be a ``/`` separated value, where the first part is usually the
name of your plugin or application to avoid clashes with other cache users.
Values can be any object handled by the json stdlib module.
capsys
enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
@@ -81,6 +91,10 @@ You can ask for available builtin or project-custom
enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capfd.readouterr()`` method calls
which return a ``(out, err)`` tuple.
record_xml_property
Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically
xml-encoded.
monkeypatch
The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
@@ -108,6 +122,8 @@ You can ask for available builtin or project-custom
See http://docs.python.org/library/warnings.html for information
on warning categories.
tmpdir_factory
Return a TempdirFactory instance for the test session.
tmpdir
return a temporary directory path object
which is unique to each test function invocation,
@@ -115,4 +131,4 @@ You can ask for available builtin or project-custom
directory. The returned object is a `py.path.local`_
path object.
in 0.00 seconds
in 0.12 seconds

278
doc/en/cache.rst Normal file
View File

@@ -0,0 +1,278 @@
Cache: working with cross-testrun state
=======================================
.. versionadded:: 2.8
.. warning::
The functionality of this core plugin was previosuly distributed
as a third party plugin named ``pytest-cache``. The core plugin
is compatible regarding command line options and API usage except that you
can only store/receive data between test runs that is json-serializable.
Usage
---------
The plugin provides two command line options to rerun failures from the
last ``py.test`` invocation:
* ``--lf`` (last failures) - to only re-run the failures.
* ``--ff`` (failures first) - to run the failures first and then the rest of
the tests.
For cleanup (usually not needed), a ``--cache-clear`` option allows to remove
all cross-session cache contents ahead of a test run.
Other plugins may access the `config.cache`_ object to set/get
**json encodable** values between ``py.test`` invocations.
.. note::
This plugin is enabled by default, but can be disabled if needed: see
:ref:`cmdunregister` (the internal name for this plugin is
``cacheprovider``).
Rerunning only failures or failures first
-----------------------------------------------
First, let's create 50 test invocation of which only 2 fail::
# content of test_50.py
import pytest
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
pytest.fail("bad luck")
If you run this for the first time you will see two failures::
$ py.test -q
.................F.......F........................
======= FAILURES ========
_______ test_num[17] ________
i = 17
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
i = 25
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
2 failed, 48 passed in 0.12 seconds
If you then run it with ``--lf``::
$ py.test --lf
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
run-last-failure: rerun last 2 failures
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
test_50.py FF
======= FAILURES ========
_______ test_num[17] ________
i = 17
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
i = 25
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
======= 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").
Now, if you run with the ``--ff`` option, all tests will be run but the first
previous failures will be executed first (as can be seen from the series
of ``FF`` and dots)::
$ py.test --ff
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
run-last-failure: rerun last 2 failures first
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
test_50.py FF................................................
======= FAILURES ========
_______ test_num[17] ________
i = 17
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
_______ test_num[25] ________
i = 25
@pytest.mark.parametrize("i", range(50))
def test_num(i):
if i in (17, 25):
> pytest.fail("bad luck")
E Failed: bad luck
test_50.py:6: Failed
======= 2 failed, 48 passed in 0.12 seconds ========
.. _`config.cache`:
The new config.cache object
--------------------------------
.. regendoc:wipe
Plugins or conftest.py support code can get a cached value using the
pytest ``config`` object. Here is a basic example plugin which
implements a :ref:`fixture` which re-uses previously created state
across py.test invocations::
# content of test_caching.py
import pytest
import time
@pytest.fixture
def mydata(request):
val = request.config.cache.get("example/value", None)
if val is None:
time.sleep(9*0.6) # expensive computation :)
val = 42
request.config.cache.set("example/value", val)
return val
def test_function(mydata):
assert mydata == 23
If you run this command once, it will take a while because
of the sleep::
$ py.test -q
F
======= FAILURES ========
_______ test_function ________
mydata = 42
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
test_caching.py:14: AssertionError
1 failed in 0.12 seconds
If you run it a second time the value will be retrieved from
the cache and this will be quick::
$ py.test -q
F
======= FAILURES ========
_______ test_function ________
mydata = 42
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
test_caching.py:14: AssertionError
1 failed in 0.12 seconds
See the `cache-api`_ for more details.
Inspecting Cache content
-------------------------------
You can always peek at the content of the cache using the
``--cache-clear`` command line option::
$ py.test --cache-clear
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items
test_caching.py F
======= FAILURES ========
_______ test_function ________
mydata = 42
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
test_caching.py:14: AssertionError
======= 1 failed in 0.12 seconds ========
Clearing Cache content
-------------------------------
You can instruct pytest to clear all cache files and values
by adding the ``--cache-clear`` option like this::
py.test --cache-clear
This is recommended for invocations from Continous Integration
servers where isolation and correctness is more important
than speed.
.. _`cache-api`:
config.cache API
------------------
The `config.cache`` object allows other plugins,
including ``conftest.py`` files,
to safely and flexibly store and retrieve values across
test runs because the ``config`` object is available
in many places.
Under the hood, the cache plugin uses the simple
dumps/loads API of the json stdlib module
.. currentmodule:: _pytest.cacheprovider
.. automethod:: Cache.get
.. automethod:: Cache.set
.. automethod:: Cache.makedir

View File

@@ -63,15 +63,15 @@ and running this module will show you precisely the output
of the failing function and hide the other one::
$ py.test
=========================== test session starts ============================
platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1
rootdir: /tmp/doc-exec-90, inifile:
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.3, py-1.4.30, pluggy-0.3.1
rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items
test_module.py .F
================================= FAILURES =================================
________________________________ test_func2 ________________________________
======= FAILURES ========
_______ test_func2 ________
def test_func2():
> assert False
@@ -79,15 +79,17 @@ of the failing function and hide the other one::
test_module.py:9: AssertionError
-------------------------- Captured stdout setup ---------------------------
setting up <function test_func2 at 0x7fa678d6eb70>
==================== 1 failed, 1 passed in 0.01 seconds ====================
setting up <function test_func2 at 0xdeadbeef>
======= 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::
that performs some output related checks:
.. code-block:: python
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")

View File

@@ -47,7 +47,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.txt'
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
@@ -73,13 +73,13 @@ copyright = u'2015, holger krekel and pytest-dev team'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
exclude_patterns = ['links.inc', '_build', 'naming20.rst', 'test/*',
"old_*",
'*attic*',
'*/attic*',
'funcargs.txt',
'setup.txt',
'example/remoteinterp.txt',
'funcargs.rst',
'setup.rst',
'example/remoteinterp.rst',
]

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