Compare commits

...

201 Commits
2.0.3 ... 2.1.3

Author SHA1 Message Date
holger krekel
448f1c0d9c add 2.1.3 release announce to index, add floris to thanks 2011-10-18 20:58:19 +02:00
holger krekel
346da57a8a bump to version 2.1.3, add release announce, regen docs (with make regen) 2011-10-18 20:07:45 +02:00
Floris Bruynooghe
9d92b19ed1 Mention issue #77 in changelog 2011-10-17 22:17:41 +01:00
Floris Bruynooghe
e2201fe3a9 Look up the pytest_assertrepr_compare hook for each test item
Before this was only done at the time the assertion plugin was loaded.
This lead to counter-intuitive behaviour where two subdirectories with
a pytest_assertrepr_compare hook in their conftest.py would not work,
only one would ever be used.

This defers assiging the _pytest.assertion.util._reprcompare function
until the item is loaded (pytest_runtest_setup) so that it can use the
hookrelay of the test item to find the appropriate
pytest_assertrepr_compare hook for the item.

This fixes issue #77.
2011-10-16 11:51:15 +01:00
Benjamin Peterson
45b98d6e70 cast boolean thing to int to make py3.3 happy 2011-10-14 18:08:10 -04:00
Benjamin Peterson
29b4082b00 put the explanation generating code in the conditional fail body (fixes #79) 2011-10-14 16:26:13 -04:00
Ronny Pfannschmidt
6ac638ba87 add a reportchar for reporting errors, fixes #76 2011-09-29 23:44:26 +02:00
Ronny Pfannschmidt
f2512017ea correctly handle zero length cmdline arguments 2011-09-25 23:26:49 +02:00
holger krekel
3bd3ba133f fix issue75 - test failure on jython.
also experimentally enable more tests in the jython test env.
2011-09-25 07:40:43 +02:00
holger krekel
be249dcfe5 correct base version number 2011-09-24 15:19:03 +02:00
holger krekel
45afb1b7d1 fix issue67 - junitxml now contains correct durations. thanks ronny. 2011-09-24 15:15:51 +02:00
holger krekel
922a283f99 bump version 2011-09-24 14:13:24 +02:00
holger krekel
7e857e9068 Added tag 2.1.2 for changeset 5864412c6f3c 2011-09-24 14:11:54 +02:00
holger krekel
172d46abd0 add release announcement for 2.1.2 2011-09-24 08:06:39 +02:00
Ronny Pfannschmidt
ac9192e4f8 make call durations part of the test report 2011-09-23 10:53:03 +02:00
holger krekel
b490047b1c make pip a bit more prominent now that it works on py3 2011-09-23 07:35:47 +02:00
holger krekel
ad785a476c going for 2.1.2 bug fix release 2011-09-23 07:30:44 +02:00
holger krekel
d37af98db3 try to make test suite pass on jython 2.5.1 again 2011-09-21 08:12:37 +02:00
holger krekel
4316cf2121 quick review of issues 2011-09-21 07:52:41 +02:00
holger krekel
fb6fc673b8 don't try assertion rewriting on jython for now 2011-09-21 06:45:40 +02:00
holger krekel
eaec527a60 relax error string matching 2011-09-21 06:21:48 +02:00
Benjamin Peterson
2bc4065a00 rewrite file newlines when the python parser is picky 2011-09-20 17:53:07 -04:00
holger krekel
5c32421f2e merge, bump version 2011-09-12 08:57:35 +02:00
Dinu Gherman
fab7615c8a Capitalised start of headlines, added -ing to a few headlines. 2011-09-06 11:43:42 +02:00
Florian Mayer
2315de8321 Add FIXME. 2011-09-05 22:01:50 +02:00
Florian Mayer
25711a0879 Add acceptance test for new --pyargs behavior. 2011-09-05 17:38:22 +02:00
Florian Mayer
0e05a4fbcf Improve --pyargs.
Don't evaluate modules and do nto show 'module not found' if ImportError is
thrown in the module.
2011-09-01 16:19:16 +02:00
Benjamin Peterson
8675cf640d every boolop operand must have it's own format context (fixes #69) 2011-08-30 10:34:21 -04:00
Benjamin Peterson
8b211983ff clear instead of deleting temporary assertion variables 2011-08-30 00:24:57 -04:00
Benjamin Peterson
661a8a4a92 only use the last part of the module name in the filename (fixes #68) 2011-08-30 00:12:07 -04:00
Benjamin Peterson
abe080c6b4 use different caches for optimized and unoptimized code (fixes #66) 2011-08-29 10:13:00 -04:00
Benjamin Peterson
574d230c22 add heading for next version 2011-08-29 10:10:00 -04:00
holger krekel
88c5299a94 fix announcement 2011-08-20 18:51:53 +02:00
holger krekel
09933b8b04 bump to 2.1.1, regen examples, add release announcement 2011-08-20 18:37:00 +02:00
holger krekel
fb1b1d9aae jython-2.5.2 has a core bug preventing pytest to run :( 2011-08-19 19:25:52 +02:00
holger krekel
68a08840e1 adding issue numbers to the CHANGELOG 2011-08-19 18:06:46 +02:00
holger krekel
41b8a03b05 merge 2011-08-19 18:07:39 +02:00
holger krekel
fba2079292 bump version number, refine goodpractises wrt to importing test modules 2011-08-19 07:58:50 +02:00
Benjamin Peterson
9675b0f65c factor out win32 checks 2011-08-18 18:15:30 -05:00
holger krekel
c426a67b0e make test skipping more precise to fix a py32 test failure 2011-08-18 22:52:02 +02:00
Benjamin Peterson
6ca3c980bf same as 6e94b1809f67: ENOTDIR is ENOENT on windows 2011-08-18 14:49:17 -05:00
Benjamin Peterson
5bd34f8ecc windows kicks up a ENOENT when a part of the path is not a dir 2011-08-18 14:39:57 -05:00
Ronny Pfannschmidt
7636dc76e0 support pytest.set_trace in collection 2011-08-01 10:53:37 +02:00
Benjamin Peterson
c5dee7b549 _make_rewritten_pyc doesn't need to return anything 2011-07-25 21:42:57 -05:00
Benjamin Peterson
643ab120f4 only try to create the __pycache__ dir (not a tree to it) fixes #60
Also, improve error handling surrounding __pycache__ creation.
2011-07-25 21:40:38 -05:00
Benjamin Peterson
f86c8469f5 now fix py3... 2011-07-19 22:56:34 -05:00
Benjamin Peterson
22335acd09 use binary mode 2011-07-19 22:45:27 -05:00
Benjamin Peterson
8b866aa065 add a newline for window's sake 2011-07-19 22:41:58 -05:00
Benjamin Peterson
2c4964d290 escape '%' in specialized comparison explanations (fixes #63) 2011-07-19 21:42:00 -05:00
holger krekel
a70293fdb7 add ability to build pytest man page with "cd doc ; make man". Is to be
found in doc/_build/man/pytest.1 afterwards.

also streamline PDF generation a bit.
2011-07-14 23:13:32 +02:00
holger krekel
43113f9a9d add some debugging tracing to assertion rewriting to understand where failures (specifically issue60) come from. 2011-07-14 19:17:17 +02:00
holger krekel
650c3bcfde enhance debug tracing: print trace tags at the end of message and forget about "prefix".
Always log to "pytestdebug.log" if "--debug" option is given.
also move related code to pytest_helpconfig plugin.
2011-07-14 19:11:50 +02:00
Benjamin Peterson
ade9b9aa8e add a test for vararg call 2011-07-14 11:46:32 -05:00
Benjamin Peterson
7576b3c7d0 fix assertion rewriting on calls with a double-star arg 2011-07-14 11:45:42 -05:00
Benjamin Peterson
85415135a4 merge heads 2011-07-13 13:34:24 -05:00
Benjamin Peterson
3cc8697744 respect sys.dont_write_bytecode and PYTHONDONTWRITEBYTECODE 2011-07-13 13:33:54 -05:00
holger krekel
703da22831 put systemout/systemerr to correct xml location 2011-07-13 18:47:27 +02:00
Benjamin Peterson
14ceaf2459 fix assertion rewriting in read-only directories (refs #60) 2011-07-12 17:09:14 -05:00
holger krekel
f3bc197afb fix #59: provide better Jenkins stdout and stderr sections 2011-07-12 23:09:03 +02:00
Benjamin Peterson
aafe6a8e34 add changelog note about fixing large boolops 2011-07-12 11:13:34 -05:00
holger krekel
46b1348b79 merge heads 2011-07-12 17:21:48 +02:00
holger krekel
709da3fe84 add benjamin's post to docs, up version, open changelog 2011-07-12 10:38:02 +02:00
Benjamin Peterson
a59c2c9e17 roll test_long_chain in with other boolop tests 2011-07-11 09:24:07 -05:00
Michał Bartoszkiewicz
6096aeca53 Fix a typo in assertion rewriting. 2011-07-11 11:57:47 +02:00
Benjamin Peterson
8cd68494bf update assertion option names 2011-07-10 21:02:36 -05:00
holger krekel
dd4b252749 add experimental "+1" button 2011-07-09 13:48:55 +02:00
holger krekel
59067684cd switching back to pytest.org self-hosting for docs - there is too much issues where i needed/need to poke people :/ 2011-07-09 13:23:58 +02:00
holger krekel
81f4a07548 Added tag 2.1.0 for changeset e5e1746a197f 2011-07-09 12:02:27 +02:00
holger krekel
50c8218501 mention pypi package name 2011-07-09 12:02:22 +02:00
holger krekel
814c6c70f1 try use distribute always, update version 2011-07-09 10:09:04 +02:00
holger krekel
85118e9019 needs pypi.testrun.org for now to pull py lib 2011-07-08 23:09:41 +02:00
holger krekel
c2cdc66eca only invoke distribute's use_setuptools when there is no setuptools installed 2011-07-08 22:58:22 +02:00
Benjamin Peterson
bc66cd85b1 customize pyc tag based on implementation 2011-07-08 13:53:23 -05:00
Benjamin Peterson
639f35bbc4 on windows, rename is not atomic, so utilize exclusive access to the file 2011-07-08 13:17:42 -05:00
holger krekel
8c683acad1 finalize 2.1.0 version numbering 2011-07-08 13:23:12 +02:00
holger krekel
dc8225afea adding release announcement 2011-07-08 13:16:32 +02:00
holger krekel
8713f4ba60 fix issue 35 - provide download link and improved PDF version 2011-07-08 12:42:26 +02:00
holger krekel
c40dc9f779 bump version 2011-07-07 23:21:01 +02:00
holger krekel
d1684e8052 report keyboardintterupt even if inteerrupted during sessionstartup 2011-07-07 21:24:09 +02:00
holger krekel
c25ea2cbe2 add a note about hook partial finalization issues 2011-07-07 21:15:35 +02:00
Benjamin Peterson
6a523b4f59 make test name shorter, so its testdir path isn't too long on windows 2011-07-07 09:43:39 -05:00
Benjamin Peterson
fb043c355e use py.builtin.exec_ 2011-07-07 09:27:40 -05:00
Benjamin Peterson
0ef23dd31f merge heads 2011-07-06 23:24:54 -05:00
Benjamin Peterson
c13fa886d9 simplify rewrite-on-import
Use load_module on the import hook to load the rewritten module. This allows the
removal of the complicated code related to copying pyc files in and out of the
cache location. It also plays more nicely with parallel py.test processes like
the ones found in xdist.
2011-07-06 23:24:04 -05:00
holger krekel
79ac8c6681 reshuffle start page as per gutworth feedback 2011-07-06 22:05:48 +02:00
holger krekel
418cd482b1 fix a doc link 2011-07-06 21:47:33 +02:00
holger krekel
491f58ab26 releax py requirement to allow readthedocs installing pytest dev 2011-07-06 21:44:57 +02:00
holger krekel
df85ddf0d2 don't import py 2011-07-06 20:25:54 +02:00
holger krekel
e7c8fc7db9 rearrange and streamline documentation navigation to better work
with readthedocs and also with PDF generation.
2011-07-06 20:21:59 +02:00
holger krekel
92f8eef836 show release level info for pypy 2011-07-06 10:18:11 +02:00
holger krekel
758b5e3511 fix issue53: nose-style setup now called with the correct ordering 2011-07-05 21:23:59 +02:00
holger krekel
e91dc7c895 up pytest version to 2.1.0.dev8, depend on py-1.4.4.dev2 2011-07-05 19:14:38 +02:00
Benjamin Peterson
4e8b9fab3c insure moving pyc files around is atomic 2011-07-05 12:02:53 -05:00
holger krekel
d105e75d87 fix pytest-xdist breakage 2011-07-05 18:01:31 +02:00
holger krekel
46950ef19a rename and simplify the assert option:
cmdline usage is now: --assert=rewrite/reinterp/plain
there is no conflict detection (don't think that's neccessary)
2011-07-05 17:29:53 +02:00
holger krekel
407ca5b120 fix python2.5 compatibility 2011-07-05 15:21:08 +02:00
Benjamin Peterson
a4fe63c08d test files are rewritten in a subprocess 2011-07-03 19:28:48 -05:00
Benjamin Peterson
fefdca5787 simplify 2011-06-29 14:00:13 -05:00
Benjamin Peterson
c7d120ec1c we want second resolution on mtime 2011-06-29 13:55:26 -05:00
Benjamin Peterson
ae8ee08ac0 adjust for new option 2011-06-29 13:28:04 -05:00
Benjamin Peterson
1707168b62 don't try to remove pycs twice 2011-06-29 12:16:47 -05:00
Benjamin Peterson
4fcd346838 update assert docs 2011-06-29 10:52:39 -05:00
Benjamin Peterson
aa7f7a1c71 rename --assertmode choices to be more explicit 2011-06-29 09:44:04 -05:00
Benjamin Peterson
48b76c7544 rewrite test modules on import 2011-06-28 21:13:12 -05:00
Benjamin Peterson
d52ff3e2b9 use a plain old list for queuing 2011-06-28 21:11:56 -05:00
Benjamin Peterson
f286a02582 rewrite with proper short-circuting on boolean operators (fixes #57) 2011-06-28 20:21:22 -05:00
Benjamin Peterson
c6e3606c6b fix the rewriter on relative imports (fixes #58) 2011-06-28 10:39:11 -05:00
holger krekel
f4eb15632d refine and streamline Floris example for an assert_reprcompare hook. 2011-06-20 18:12:48 +02:00
Floris Bruynooghe
d027f9d546 Add pytest_assertrepr_compare() docs 2011-06-20 14:18:12 +02:00
holger krekel
8de50347fb some fixes and clarifications to assert docs 2011-06-18 22:30:46 +02:00
Benjamin Peterson
4b4a2c8162 merge heads 2011-06-18 15:09:50 -05:00
Benjamin Peterson
29d58ffdf2 note condition for introspection happening 2011-06-18 15:07:36 -05:00
holger krekel
9ea58242d4 fix getting-started which claimed you need to avoid side effect in asserts 2011-06-15 07:50:34 +02:00
Benjamin Peterson
8772b8c928 fix name 2011-06-13 08:50:50 -05:00
Benjamin Peterson
8e81ed693a put explanation simplification in format_explanation so everyone can benefit 2011-06-12 22:41:58 -05:00
Benjamin Peterson
d853d9a9af treat local as a black box 2011-06-12 21:57:22 -05:00
Benjamin Peterson
57a3d4d6d8 some tweaks to allow pypy apptests to use newinterpret 2011-06-12 17:07:49 -05:00
Benjamin Peterson
8f6477f695 fix spacing 2011-06-12 16:39:38 -05:00
Benjamin Peterson
2618e3640f account for quotes in error messages 2011-06-03 22:11:00 -05:00
Benjamin Peterson
43de6c270f fix assertion introspection on python 3.2+ 2011-06-03 16:51:49 -05:00
holger krekel
ce1b456762 back out pytest_configure_funcargs hook for now 2011-06-01 15:08:54 +02:00
holger krekel
332bceeb7a add issue49 fix to CHANGELOG 2011-06-01 14:55:50 +02:00
holger krekel
e3b2792677 fix issue49 - avoid confusing errors when initialization goes wrong 2011-06-01 14:54:34 +02:00
holger krekel
67859158d4 fix issue48 - test and fix typo in MarkInfo repr 2011-06-01 08:03:06 +02:00
holger krekel
5dfce4a0ca update authors to reflect more current situation 2011-06-01 08:00:12 +02:00
holger krekel
6c90059342 - properly include _pytest.assertion in distribution
- import assertion only at import-test module time
2011-05-31 15:21:08 +02:00
holger krekel
5690beab5a merge Benjamin's assertion-rewrite branch: all assertion related code is now part of py.test core distribution - the builtin assertion plugin to be precise.
See doc/assert.txt for details on how what has been improved.
2011-05-31 14:11:53 +02:00
holger krekel
8bc9fdc8d3 fix a buffering issue that i think/hope only occurs during internal tests 2011-05-29 09:21:48 +02:00
Benjamin Peterson
00dee742b0 describe how assert rewriting interacts with cross test imports 2011-05-28 19:00:47 -05:00
Benjamin Peterson
5e31624315 return to the old scheme of rewriting test modules from _importtestmodule 2011-05-28 18:47:16 -05:00
holger krekel
5e311d3bfc fix timing float comparison 2011-05-29 00:45:31 +02:00
holger krekel
4b7293428b add Mozilla QA people to pytest users 2011-05-29 00:47:32 +02:00
Benjamin Peterson
6fdcecb864 typo 2011-05-28 16:04:36 -05:00
Benjamin Peterson
f63ff5267c s/debugging/introspection/ 2011-05-28 16:01:02 -05:00
Ronny Pfannschmidt
5498fe960f add another normpath in the junitxml tests 2011-05-28 19:00:23 +02:00
Ronny Pfannschmidt
4c885cf0d2 hopefully final win32 fix for the junitxml path expansion 2011-05-28 17:36:38 +02:00
Benjamin Peterson
326b63adf8 bump pylib required 2011-05-28 10:02:51 -05:00
Ronny Pfannschmidt
70dc7a976d dont wrap comparisation paths in py.path.local for the junitxml tests, since missing $HOME causes issues else 2011-05-28 16:52:05 +02:00
Ronny Pfannschmidt
89a98e3276 also apply normpath to junitxml file path 2011-05-28 16:21:57 +02:00
holger krekel
410438f187 fix issue43 - better tracebacks for unexpected exceptions in doctests 2011-05-28 14:38:15 +02:00
holger krekel
8dc4e732f0 fix issue47 - fix time-per-test timing output for junitxml 2011-05-28 14:03:10 +02:00
Benjamin Peterson
e98057130d a few more sentences 2011-05-27 12:30:27 -05:00
Ronny Pfannschmidt
70d22fbe9a update changelog 2011-05-27 12:58:22 +02:00
Ronny Pfannschmidt
56b40ebd75 use os.path.expanduser/expandvars on the junitxml path for convience, fixes #44 2011-05-27 07:54:03 +02:00
Benjamin Peterson
5f75c5851f can use non-underscored addoption 2011-05-26 23:15:33 -05:00
Benjamin Peterson
606ea870f0 versionadded and versionchanged for asserts 2011-05-26 23:13:39 -05:00
Benjamin Peterson
e56838cb6c write an explicit raise if the assertion fails 2011-05-26 21:15:40 -05:00
Benjamin Peterson
e22d3e03fe doc updates for new assertion debugging 2011-05-26 21:08:55 -05:00
Benjamin Peterson
d53feaf6f0 fix help for --assertmode 2011-05-26 20:59:43 -05:00
Benjamin Peterson
914f689ee8 beef up --assertmode help 2011-05-26 20:33:12 -05:00
Benjamin Peterson
971f34147a test that tests get rewritten 2011-05-26 20:06:11 -05:00
Benjamin Peterson
16b4f54545 remove module before/after import hooks 2011-05-26 20:00:29 -05:00
Benjamin Peterson
abb07fc732 new way to rewrite tests: do it all during fs collection
This should allow modules to be rewritten before some other test module loads
them.
2011-05-26 19:57:30 -05:00
Benjamin Peterson
cf6949c9a3 stuff contents of pytest_collection hook into perform_collect 2011-05-26 19:53:47 -05:00
Benjamin Peterson
2f984e0c23 remove after_initial_collect hook 2011-05-26 19:43:02 -05:00
Benjamin Peterson
0a7237b72f refactor common config/session protocol code for main() functions 2011-05-26 19:09:42 -05:00
Benjamin Peterson
f684a9ed56 expose Session on pytest namespace 2011-05-26 18:58:31 -05:00
Benjamin Peterson
196cece338 add a hook called after the inital fs collection 2011-05-26 18:57:37 -05:00
Benjamin Peterson
241ff0b43a add a hook called when a Module is successfully created 2011-05-26 18:56:45 -05:00
Benjamin Peterson
411e9b136b do configure hooks here, too 2011-05-26 18:37:04 -05:00
Benjamin Peterson
96521ada68 call configure hooks in reparseconfig 2011-05-26 18:11:12 -05:00
Benjamin Peterson
7cf8afef47 cause configure hooks to be called 2011-05-26 18:10:49 -05:00
Benjamin Peterson
657522b629 a less ugly way to detect if assert rewriting is enabled 2011-05-26 17:17:48 -05:00
Benjamin Peterson
dd199d255c move _setupstate into session 2011-05-26 17:08:56 -05:00
Benjamin Peterson
89d6defd68 correctly initialize and shutdown sessions 2011-05-26 17:08:44 -05:00
Benjamin Peterson
c4d761fe99 these tests should cause pytest_configure to be called 2011-05-26 16:50:04 -05:00
Benjamin Peterson
bf3d9f3737 correct attribute name 2011-05-26 16:18:18 -05:00
Benjamin Peterson
32a67f9622 add some tracing in the assert plugin 2011-05-26 16:08:25 -05:00
Benjamin Peterson
d438a0bd83 introduce --assertmode option 2011-05-26 14:34:27 -05:00
Benjamin Peterson
d3645758ea this comment was moved away 2011-05-26 13:17:39 -05:00
Benjamin Peterson
15b9e8ed7d forgot to util module 2011-05-26 13:17:26 -05:00
Benjamin Peterson
ee64da4bad fix grammar 2011-05-26 13:15:21 -05:00
Benjamin Peterson
4fe13e59a7 fix comment 2011-05-26 13:15:03 -05:00
Benjamin Peterson
250160b4b0 refactor explanation formatting things into their own module 2011-05-26 12:01:34 -05:00
Benjamin Peterson
f423ce9c01 import assertion code from pylib 2011-05-25 17:54:02 -05:00
Benjamin Peterson
491c05cea7 create the _pytest/assertion package 2011-05-25 16:18:45 -05:00
Benjamin Peterson
e02d22aa4f expand try/except/finally which py2.4 does't like 2011-05-25 15:55:57 -05:00
Benjamin Peterson
c0910abf2f account py3 range objects 2011-05-24 18:30:18 -05:00
Benjamin Peterson
b061e71da9 account for py3 dict.values 2011-05-24 18:28:20 -05:00
Benjamin Peterson
fa412675fc use py.builtin.exec_ 2011-05-24 18:28:05 -05:00
Benjamin Peterson
0bb84abca7 handle comparison results which raise when asked for their truth value 2011-05-24 18:15:08 -05:00
Benjamin Peterson
f5decc90ca test that python loads our fake pycs 2011-05-24 17:52:17 -05:00
Benjamin Peterson
7fc2f8786f refactor writing the fake pyc into its own function 2011-05-24 17:48:56 -05:00
Benjamin Peterson
76cede83c0 add a way to disable assertion rewriting for a module 2011-05-24 17:30:35 -05:00
Benjamin Peterson
993efe927b fix sentence 2011-05-24 17:28:20 -05:00
Benjamin Peterson
9c4f6791e5 give initial imports a reasonable lineno 2011-05-24 17:21:58 -05:00
Benjamin Peterson
7ba8fee3dc improve this test 2011-05-20 09:44:36 -05:00
Benjamin Peterson
265b7458cb in the common case, the and operation isn't needed 2011-05-19 22:11:18 -05:00
Benjamin Peterson
dc3e39e95c a less silly way to check comparison results 2011-05-19 21:57:27 -05:00
Benjamin Peterson
4f2166c997 use assertion rewriting on test files
This works by writing a fake pyc with the asserts rewritten.
2011-05-19 21:52:10 -05:00
Benjamin Peterson
e0c128beec unconditionally override lineno and col_offset on generated ast 2011-05-19 21:49:37 -05:00
Benjamin Peterson
bf039fea74 add hooks before and after a module is imported 2011-05-19 21:45:33 -05:00
Benjamin Peterson
78be3db9bb remove unneeded list 2011-05-19 19:15:20 -05:00
Benjamin Peterson
aae89cd021 correctly handle multiple asserts 2011-05-19 18:56:48 -05:00
Benjamin Peterson
9ac818fb5c small refactoring 2011-05-19 18:32:48 -05:00
Benjamin Peterson
9e6dfaefd9 place assertion imports after __future__ statements and docstrings 2011-05-19 16:53:13 -05:00
Benjamin Peterson
c742e47de0 new assertion debugger which rewrites asserts before they are run 2011-05-18 15:31:10 -05:00
holger krekel
95ddd5059f bumping version and adding changelog entry for configure funcargs 2011-05-13 09:57:35 +02:00
Ronny Pfannschmidt
b6815538c5 introduce the pytest_configure_funcargs hook for better control on funcarg instanciation/configuration 2011-05-12 23:47:05 +02:00
holger krekel
ea936213bc fix link to pypy tests 2011-05-12 13:52:14 +02:00
holger krekel
7e65f346f4 Added tag 2.0.3 for changeset 363e5a5a59c8 2011-05-11 11:54:36 +02:00
91 changed files with 3652 additions and 591 deletions

View File

@@ -39,3 +39,7 @@ e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
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

17
AUTHORS
View File

@@ -1,16 +1,17 @@
Holger Krekel, holger at merlinux eu
Benjamin Peterson, benjamin at python org
Ronny Pfannschmidt, Ronny.Pfannschmidt at gmx de
Guido Wesdorp, johnny at johnnydebris net
Samuele Pedroni, pedronis at openend se
Carl Friedrich Bolz, cfbolz at gmx de
Armin Rigo, arigo at tunes org
Maciek Fijalkowski, fijal at genesilico pl
Brian Dorsey, briandorsey at gmail com
merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Ronny Pfannschmidt
Benjamin Peterson
Floris Bruynooghe
Samuele Pedroni
Carl Friedrich Bolz
Armin Rigo
Maciek Fijalkowski
Guido Wesdorp
Brian Dorsey
Ross Lawley
Ralf Schmitt
Chris Lamb

View File

@@ -1,3 +1,55 @@
Changes between 2.1.2 and 2.1.3
----------------------------------------
- fix issue79: assertion rewriting failed on some comparisons in boolops
- correctly handle zero length arguments (a la pytest '')
- fix issue67 / junitxml now contains correct test durations, thanks ronny
- fix issue75 / skipping test failure on jython
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
Changes between 2.1.1 and 2.1.2
----------------------------------------
- fix assertion rewriting on files with windows newlines on some Python versions
- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
- fix issue69 / assertion rewriting fixed on some boolean operations
- fix issue68 / packages now work with assertion rewriting
- fix issue66: use different assertion rewriting caches when the -O option is passed
- don't try assertion rewriting on Jython, use reinterp
Changes between 2.1.0 and 2.1.1
----------------------------------------------
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
- fix issue60 / fix error conditions involving the creation of __pycache__
- fix issue63 / assertion rewriting on inserts involving strings containing '%'
- fix assertion rewriting on calls with a ** arg
- don't cache rewritten modules if bytecode generation is disabled
- fix assertion rewriting in read-only directories
- fix issue59: provide system-out/err tags for junitxml output
- fix issue61: assertion rewriting on boolean operations with 3 or more operands
- you can now build a man page with "cd doc ; make man"
Changes between 2.0.3 and 2.1.0.DEV
----------------------------------------------
- fix issue53 call nosestyle setup functions with correct ordering
- fix issue58 and issue59: new assertion code fixes
- merge Benjamin's assertionrewrite branch: now assertions
for test modules on python 2.6 and above are done by rewriting
the AST and saving the pyc file before the test module is imported.
see doc/assert.txt for more info.
- fix issue43: improve doctests with better traceback reporting on
unexpected exceptions
- fix issue47: timing output in junitxml for test cases is now correct
- fix issue48: typo in MarkInfo repr leading to exception
- fix issue49: avoid confusing error when initizaliation partially fails
- fix issue44: env/username expansion for junitxml file path
- show releaselevel information in test runs for pypy
- reworked doc pages for better navigation and PDF generation
- report KeyboardInterrupt even if interrupted during session startup
- fix issue 35 - provide PDF doc version and download link from index page
Changes between 2.0.2 and 2.0.3
----------------------------------------------

View File

@@ -7,17 +7,27 @@ tags: bug 2.4 core xdist
the protocol now - setup/teardown is called at module level.
consider making calling of setup/teardown configurable
profiling / hook call optimization
-------------------------------------
tags: enhancement 2.1
optimizations
---------------------------------------------------------------
tags: 2.4 core
bench/bench.py reveals that for very quick running
unit tests the hook architecture is a bit slow.
Profile and improve hook calls.
- look at ihook optimization such that all lookups for
hooks relating to the same fspath are cached.
fix start/finish partial finailization problem
---------------------------------------------------------------
tags: bug core
if a configure/runtest_setup/sessionstart/... hook invocation partially
fails the sessionfinishes is not called. Each hook implementation
should better be repsonsible for registering a cleanup/finalizer
appropriately to avoid this issue. Moreover/Alternatively, we could
record which implementations of a hook succeeded and only call their
teardown.
do early-teardown of test modules
-----------------------------------------
tags: feature 2.1
tags: feature 2.2
currently teardowns are called when the next tests is setup
except for the function/method level where interally
@@ -29,7 +39,7 @@ prints of teardown-code appear in the setup of the next test.
consider and document __init__ file usage in test directories
---------------------------------------------------------------
tags: bug 2.1 core
tags: bug 2.2 core
Currently, a test module is imported with its fully qualified
package path, determined by checking __init__ files upwards.
@@ -44,7 +54,7 @@ certain scenarios makes sense.
relax requirement to have tests/testing contain an __init__
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
A local test run of a "tests" directory may work
@@ -55,25 +65,24 @@ i.e. port the nose-logic of unloading a test module.
customize test function collection
-------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
- introduce py.test.mark.nocollect for not considering a function for
test collection at all. maybe also introduce a py.test.mark.test to
explicitely mark a function to become a tested one. Lookup JUnit ways
of tagging tests.
- allow an easy way to customize "test_", "Test" prefixes for file paths
and test function/class names. the current customizable Item requires
too much code/concepts to influence this collection matching.
maybe introduce pytest_pycollect_filters = {
'file': 'test*.py',
'function': 'test*',
'class': 'Test*',
}
introduce pytest.mark.importorskip
-------------------------------------------------------
tags: feature 2.2
in addition to the imperative pytest.importorskip also introduce
a pytest.mark.importorskip so that the test count is more correct.
introduce py.test.mark.platform
-------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
Introduce nice-to-spell platform-skipping, examples:
@@ -90,7 +99,7 @@ interpreter versions.
pytest.mark.xfail signature change
-------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
change to pytest.mark.xfail(reason, (optional)condition)
to better implement the word meaning. It also signals
@@ -100,7 +109,7 @@ Compatibility? Maybe rename to "pytest.mark.xfail"?
introduce py.test.mark registration
-----------------------------------------
tags: feature 2.1
tags: feature 2.2
introduce a hook that allows to register a named mark decorator
with documentation and add "py.test --marks" to get
@@ -109,7 +118,7 @@ definitions.
allow to non-intrusively apply skipfs/xfail/marks
---------------------------------------------------
tags: feature 2.1
tags: feature 2.2
use case: mark a module or directory structures
to be skipped on certain platforms (i.e. no import
@@ -120,14 +129,14 @@ from conftests or plugins.
explicit referencing of conftest.py files
-----------------------------------------
tags: feature 2.1
tags: feature 2.2
allow to name conftest.py files (in sub directories) that should
be imported early, as to include command line options.
improve central py.test ini file
----------------------------------
tags: feature 2.1
tags: feature 2.2
introduce more declarative configuration options:
- (to-be-collected test directories)
@@ -138,7 +147,7 @@ introduce more declarative configuration options:
new documentation
----------------------------------
tags: feature 2.1
tags: feature 2.2
- logo py.test
- examples for unittest or functional testing
@@ -149,7 +158,7 @@ tags: feature 2.1
generalize parametrized testing to generate combinations
-------------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
think about extending metafunc.addcall or add a new method to allow to
generate tests with combinations of all generated versions - what to do
@@ -164,7 +173,7 @@ of values for a given function argument.
have imported module mismatch honour relative paths
--------------------------------------------------------
tags: bug 2.1
tags: bug 2.2
With 1.1.1 py.test fails at least on windows if an import
is relative and compared against an absolute conftest.py
@@ -172,7 +181,7 @@ path. Normalize.
call termination with small timeout
-------------------------------------------------
tags: feature 2.1
tags: feature 2.2
test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node
Call gateway group termination with a small timeout if available.
@@ -180,7 +189,7 @@ Should make dist-testing less likely to leave lost processes.
consider globals: py.test.ensuretemp and config
--------------------------------------------------------------
tags: experimental-wish 2.1
tags: experimental-wish 2.2
consider deprecating py.test.ensuretemp and py.test.config
to further reduce py.test globality. Also consider
@@ -189,7 +198,7 @@ a plugin rather than being there from the start.
consider allowing funcargs for setup methods
--------------------------------------------------------------
tags: experimental-wish 2.1
tags: experimental-wish 2.2
Users have expressed the wish to have funcargs available to setup
functions. Experiment with allowing funcargs there - it might
@@ -205,7 +214,7 @@ setup_module -> request has no request.cls
consider pytest_addsyspath hook
-----------------------------------------
tags: 2.1
tags: 2.2
py.test could call a new pytest_addsyspath() in order to systematically
allow manipulation of sys.path and to inhibit it via --no-addsyspath
@@ -217,7 +226,7 @@ and pytest_configure.
show plugin information in test header
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
Now that external plugins are becoming more numerous
it would be useful to have external plugins along with
@@ -225,7 +234,7 @@ their versions displayed as a header line.
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
py.test.ensuretemp and py.test.config are probably the last
objects containing global state. Often using them is not
@@ -235,7 +244,7 @@ as others.
remove deprecated bits in collect.py
-------------------------------------------------------------------
tags: feature 2.1
tags: feature 2.2
In an effort to further simplify code, review and remove deprecated bits
in collect.py. Probably good:

View File

@@ -1,2 +1,2 @@
#
__version__ = '2.0.3'
__version__ = '2.1.3'

View File

@@ -0,0 +1,119 @@
"""
support for presenting detailed information in failing assertions.
"""
import py
import sys
import pytest
from _pytest.monkeypatch import monkeypatch
from _pytest.assertion import util
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption('--assert', action="store", dest="assertmode",
choices=("rewrite", "reinterp", "plain",),
default="rewrite", metavar="MODE",
help="""control assertion debugging tools.
'plain' performs no assertion debugging.
'reinterp' reinterprets assert statements after they failed to provide assertion expression information.
'rewrite' (the default) rewrites assert statements in test modules on import
to provide assert expression information. """)
group.addoption('--no-assert', action="store_true", default=False,
dest="noassert", help="DEPRECATED equivalent to --assert=plain")
group.addoption('--nomagic', action="store_true", default=False,
dest="nomagic", help="DEPRECATED equivalent to --assert=plain")
class AssertionState:
"""State for the assertion plugin."""
def __init__(self, config, mode):
self.mode = mode
self.trace = config.trace.root.get("assertion")
def pytest_configure(config):
mode = config.getvalue("assertmode")
if config.getvalue("noassert") or config.getvalue("nomagic"):
mode = "plain"
if mode == "rewrite":
try:
import ast
except ImportError:
mode = "reinterp"
else:
if sys.platform.startswith('java'):
mode = "reinterp"
if mode != "plain":
_load_modules(mode)
m = monkeypatch()
config._cleanup.append(m.undo)
m.setattr(py.builtin.builtins, 'AssertionError',
reinterpret.AssertionError)
hook = None
if mode == "rewrite":
hook = rewrite.AssertionRewritingHook()
sys.meta_path.append(hook)
warn_about_missing_assertion(mode)
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:
sys.meta_path.remove(hook)
def pytest_collection(session):
# this hook is only called when test modules are collected
# so for example not in the master process of pytest-xdist
# (which does not collect test modules)
hook = session.config._assertstate.hook
if hook is not None:
hook.set_session(session)
def pytest_runtest_setup(item):
def callbinrepr(op, left, right):
hook_result = item.ihook.pytest_assertrepr_compare(
config=item.config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
res = '\n~'.join(new_expl)
if item.config.getvalue("assertmode") == "rewrite":
# The result will be fed back a python % formatting
# operation, which will fail if there are extraneous
# '%'s in the string. Escape them here.
res = res.replace("%", "%%")
return res
util._reprcompare = callbinrepr
def pytest_runtest_teardown(item):
util._reprcompare = None
def pytest_sessionfinish(session):
hook = session.config._assertstate.hook
if hook is not None:
hook.session = None
def _load_modules(mode):
"""Lazily import assertion related code."""
global rewrite, reinterpret
from _pytest.assertion import reinterpret
if mode == "rewrite":
from _pytest.assertion import rewrite
def warn_about_missing_assertion(mode):
try:
assert False
except AssertionError:
pass
else:
if mode == "rewrite":
specifically = ("assertions which are not in test modules "
"will be ignored")
else:
specifically = "failing tests may report as passing"
sys.stderr.write("WARNING: " + specifically +
" because assert statements are not executed "
"by the underlying Python interpreter "
"(are you using python -O?)\n")
pytest_assertrepr_compare = util.assertrepr_compare

View File

@@ -0,0 +1,333 @@
"""
Find intermediate evalutation results in assert statements through builtin AST.
This should replace oldinterpret.py eventually.
"""
import sys
import ast
import py
from _pytest.assertion import util
from _pytest.assertion.reinterpret import BuiltinAssertionError
if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
# See http://bugs.jython.org/issue1497
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
"List", "Tuple")
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
_expr_nodes = set(getattr(ast, name) for name in _exprs)
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
def _is_ast_expr(node):
return node.__class__ in _expr_nodes
def _is_ast_stmt(node):
return node.__class__ in _stmt_nodes
else:
def _is_ast_expr(node):
return isinstance(node, ast.expr)
def _is_ast_stmt(node):
return isinstance(node, ast.stmt)
class Failure(Exception):
"""Error found while interpreting AST."""
def __init__(self, explanation=""):
self.cause = sys.exc_info()
self.explanation = explanation
def interpret(source, frame, should_fail=False):
mod = ast.parse(source)
visitor = DebugInterpreter(frame)
try:
visitor.visit(mod)
except Failure:
failure = sys.exc_info()[1]
return getfailure(failure)
if should_fail:
return ("(assertion failed, but when it was re-run for "
"printing intermediate values, it did not fail. Suggestions: "
"compute assert expression before the assert or use --assert=plain)")
def run(offending_line, frame=None):
if frame is None:
frame = py.code.Frame(sys._getframe(1))
return interpret(offending_line, frame)
def getfailure(e):
explanation = util.format_explanation(e.explanation)
value = e.cause[1]
if str(value):
lines = explanation.split('\n')
lines[0] += " << %s" % (value,)
explanation = '\n'.join(lines)
text = "%s: %s" % (e.cause[0].__name__, explanation)
if text.startswith('AssertionError: assert '):
text = text[16:]
return text
operator_map = {
ast.BitOr : "|",
ast.BitXor : "^",
ast.BitAnd : "&",
ast.LShift : "<<",
ast.RShift : ">>",
ast.Add : "+",
ast.Sub : "-",
ast.Mult : "*",
ast.Div : "/",
ast.FloorDiv : "//",
ast.Mod : "%",
ast.Eq : "==",
ast.NotEq : "!=",
ast.Lt : "<",
ast.LtE : "<=",
ast.Gt : ">",
ast.GtE : ">=",
ast.Pow : "**",
ast.Is : "is",
ast.IsNot : "is not",
ast.In : "in",
ast.NotIn : "not in"
}
unary_map = {
ast.Not : "not %s",
ast.Invert : "~%s",
ast.USub : "-%s",
ast.UAdd : "+%s"
}
class DebugInterpreter(ast.NodeVisitor):
"""Interpret AST nodes to gleam useful debugging information. """
def __init__(self, frame):
self.frame = frame
def generic_visit(self, node):
# Fallback when we don't have a special implementation.
if _is_ast_expr(node):
mod = ast.Expression(node)
co = self._compile(mod)
try:
result = self.frame.eval(co)
except Exception:
raise Failure()
explanation = self.frame.repr(result)
return explanation, result
elif _is_ast_stmt(node):
mod = ast.Module([node])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co)
except Exception:
raise Failure()
return None, None
else:
raise AssertionError("can't handle %s" %(node,))
def _compile(self, source, mode="eval"):
return compile(source, "<assertion interpretation>", mode)
def visit_Expr(self, expr):
return self.visit(expr.value)
def visit_Module(self, mod):
for stmt in mod.body:
self.visit(stmt)
def visit_Name(self, name):
explanation, result = self.generic_visit(name)
# See if the name is local.
source = "%r in locals() is not globals()" % (name.id,)
co = self._compile(source)
try:
local = self.frame.eval(co)
except Exception:
# have to assume it isn't
local = None
if local is None or not self.frame.is_true(local):
return name.id, result
return explanation, result
def visit_Compare(self, comp):
left = comp.left
left_explanation, left_result = self.visit(left)
for op, next_op in zip(comp.ops, comp.comparators):
next_explanation, next_result = self.visit(next_op)
op_symbol = operator_map[op.__class__]
explanation = "%s %s %s" % (left_explanation, op_symbol,
next_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=next_result)
except Exception:
raise Failure(explanation)
try:
if not self.frame.is_true(result):
break
except KeyboardInterrupt:
raise
except:
break
left_explanation, left_result = next_explanation, next_result
if util._reprcompare is not None:
res = util._reprcompare(op_symbol, left_result, next_result)
if res:
explanation = res
return explanation, result
def visit_BoolOp(self, boolop):
is_or = isinstance(boolop.op, ast.Or)
explanations = []
for operand in boolop.values:
explanation, result = self.visit(operand)
explanations.append(explanation)
if result == is_or:
break
name = is_or and " or " or " and "
explanation = "(" + name.join(explanations) + ")"
return explanation, result
def visit_UnaryOp(self, unary):
pattern = unary_map[unary.op.__class__]
operand_explanation, operand_result = self.visit(unary.operand)
explanation = pattern % (operand_explanation,)
co = self._compile(pattern % ("__exprinfo_expr",))
try:
result = self.frame.eval(co, __exprinfo_expr=operand_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_BinOp(self, binop):
left_explanation, left_result = self.visit(binop.left)
right_explanation, right_result = self.visit(binop.right)
symbol = operator_map[binop.op.__class__]
explanation = "(%s %s %s)" % (left_explanation, symbol,
right_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=right_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_Call(self, call):
func_explanation, func = self.visit(call.func)
arg_explanations = []
ns = {"__exprinfo_func" : func}
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)
for keyword in call.keywords:
arg_explanation, arg_result = self.visit(keyword.value)
arg_name = "__exprinfo_%s" % (len(ns),)
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:
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:
arg_explanation, arg_result = self.visit(call.kwargs)
arg_name = "__exprinfo_kwds"
ns[arg_name] = arg_result
arguments.append("**%s" % (arg_name,))
arg_explanations.append("**%s" % (arg_explanation,))
args_explained = ", ".join(arg_explanations)
explanation = "%s(%s)" % (func_explanation, args_explained)
args = ", ".join(arguments)
source = "__exprinfo_func(%s)" % (args,)
co = self._compile(source)
try:
result = self.frame.eval(co, **ns)
except Exception:
raise Failure(explanation)
pattern = "%s\n{%s = %s\n}"
rep = self.frame.repr(result)
explanation = pattern % (rep, rep, explanation)
return explanation, result
def _is_builtin_name(self, name):
pattern = "%r not in globals() and %r not in locals()"
source = pattern % (name.id, name.id)
co = self._compile(source)
try:
return self.frame.eval(co)
except Exception:
return False
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)
source_explanation, source_result = self.visit(attr.value)
explanation = "%s.%s" % (source_explanation, attr.attr)
source = "__exprinfo_expr.%s" % (attr.attr,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
raise Failure(explanation)
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
self.frame.repr(result),
source_explanation, attr.attr)
# Check if the attr is from an instance.
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
source = source % (attr.attr,)
co = self._compile(source)
try:
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
from_instance = None
if from_instance is None or self.frame.is_true(from_instance):
rep = self.frame.repr(result)
pattern = "%s\n{%s = %s\n}"
explanation = pattern % (rep, rep, explanation)
return explanation, result
def visit_Assert(self, assrt):
test_explanation, test_result = self.visit(assrt.test)
explanation = "assert %s" % (test_explanation,)
if not self.frame.is_true(test_result):
try:
raise BuiltinAssertionError
except Exception:
raise Failure(explanation)
return explanation, test_result
def visit_Assign(self, assign):
value_explanation, value_result = self.visit(assign.value)
explanation = "... = %s" % (value_explanation,)
name = ast.Name("__exprinfo_expr", ast.Load(),
lineno=assign.value.lineno,
col_offset=assign.value.col_offset)
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
col_offset=assign.col_offset)
mod = ast.Module([new_assign])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co, __exprinfo_expr=value_result)
except Exception:
raise Failure(explanation)
return explanation, value_result

View File

@@ -0,0 +1,552 @@
import py
import sys, inspect
from compiler import parse, ast, pycodegen
from _pytest.assertion.util import format_explanation
from _pytest.assertion.reinterpret import BuiltinAssertionError
passthroughex = py.builtin._sysex
class Failure:
def __init__(self, node):
self.exc, self.value, self.tb = sys.exc_info()
self.node = node
class View(object):
"""View base class.
If C is a subclass of View, then C(x) creates a proxy object around
the object x. The actual class of the proxy is not C in general,
but a *subclass* of C determined by the rules below. To avoid confusion
we call view class the class of the proxy (a subclass of C, so of View)
and object class the class of x.
Attributes and methods not found in the proxy are automatically read on x.
Other operations like setting attributes are performed on the proxy, as
determined by its view class. The object x is available from the proxy
as its __obj__ attribute.
The view class selection is determined by the __view__ tuples and the
optional __viewkey__ method. By default, the selected view class is the
most specific subclass of C whose __view__ mentions the class of x.
If no such subclass is found, the search proceeds with the parent
object classes. For example, C(True) will first look for a subclass
of C with __view__ = (..., bool, ...) and only if it doesn't find any
look for one with __view__ = (..., int, ...), and then ..., object,...
If everything fails the class C itself is considered to be the default.
Alternatively, the view class selection can be driven by another aspect
of the object x, instead of the class of x, by overriding __viewkey__.
See last example at the end of this module.
"""
_viewcache = {}
__view__ = ()
def __new__(rootclass, obj, *args, **kwds):
self = object.__new__(rootclass)
self.__obj__ = obj
self.__rootclass__ = rootclass
key = self.__viewkey__()
try:
self.__class__ = self._viewcache[key]
except KeyError:
self.__class__ = self._selectsubclass(key)
return self
def __getattr__(self, attr):
# attributes not found in the normal hierarchy rooted on View
# are looked up in the object's real class
return getattr(self.__obj__, attr)
def __viewkey__(self):
return self.__obj__.__class__
def __matchkey__(self, key, subclasses):
if inspect.isclass(key):
keys = inspect.getmro(key)
else:
keys = [key]
for key in keys:
result = [C for C in subclasses if key in C.__view__]
if result:
return result
return []
def _selectsubclass(self, key):
subclasses = list(enumsubclasses(self.__rootclass__))
for C in subclasses:
if not isinstance(C.__view__, tuple):
C.__view__ = (C.__view__,)
choices = self.__matchkey__(key, subclasses)
if not choices:
return self.__rootclass__
elif len(choices) == 1:
return choices[0]
else:
# combine the multiple choices
return type('?', tuple(choices), {})
def __repr__(self):
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
def enumsubclasses(cls):
for subcls in cls.__subclasses__():
for subsubclass in enumsubclasses(subcls):
yield subsubclass
yield cls
class Interpretable(View):
"""A parse tree node with a few extra methods."""
explanation = None
def is_builtin(self, frame):
return False
def eval(self, frame):
# fall-back for unknown expression nodes
try:
expr = ast.Expression(self.__obj__)
expr.filename = '<eval>'
self.__obj__.filename = '<eval>'
co = pycodegen.ExpressionCodeGenerator(expr).getCode()
result = frame.eval(co)
except passthroughex:
raise
except:
raise Failure(self)
self.result = result
self.explanation = self.explanation or frame.repr(self.result)
def run(self, frame):
# fall-back for unknown statement nodes
try:
expr = ast.Module(None, ast.Stmt([self.__obj__]))
expr.filename = '<run>'
co = pycodegen.ModuleCodeGenerator(expr).getCode()
frame.exec_(co)
except passthroughex:
raise
except:
raise Failure(self)
def nice_explanation(self):
return format_explanation(self.explanation)
class Name(Interpretable):
__view__ = ast.Name
def is_local(self, frame):
source = '%r in locals() is not globals()' % self.name
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def is_global(self, frame):
source = '%r in globals()' % self.name
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def is_builtin(self, frame):
source = '%r not in locals() and %r not in globals()' % (
self.name, self.name)
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def eval(self, frame):
super(Name, self).eval(frame)
if not self.is_local(frame):
self.explanation = self.name
class Compare(Interpretable):
__view__ = ast.Compare
def eval(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
for operation, expr2 in self.ops:
if hasattr(self, 'result'):
# shortcutting in chained expressions
if not frame.is_true(self.result):
break
expr2 = Interpretable(expr2)
expr2.eval(frame)
self.explanation = "%s %s %s" % (
expr.explanation, operation, expr2.explanation)
source = "__exprinfo_left %s __exprinfo_right" % operation
try:
self.result = frame.eval(source,
__exprinfo_left=expr.result,
__exprinfo_right=expr2.result)
except passthroughex:
raise
except:
raise Failure(self)
expr = expr2
class And(Interpretable):
__view__ = ast.And
def eval(self, frame):
explanations = []
for expr in self.nodes:
expr = Interpretable(expr)
expr.eval(frame)
explanations.append(expr.explanation)
self.result = expr.result
if not frame.is_true(expr.result):
break
self.explanation = '(' + ' and '.join(explanations) + ')'
class Or(Interpretable):
__view__ = ast.Or
def eval(self, frame):
explanations = []
for expr in self.nodes:
expr = Interpretable(expr)
expr.eval(frame)
explanations.append(expr.explanation)
self.result = expr.result
if frame.is_true(expr.result):
break
self.explanation = '(' + ' or '.join(explanations) + ')'
# == Unary operations ==
keepalive = []
for astclass, astpattern in {
ast.Not : 'not __exprinfo_expr',
ast.Invert : '(~__exprinfo_expr)',
}.items():
class UnaryArith(Interpretable):
__view__ = astclass
def eval(self, frame, astpattern=astpattern):
expr = Interpretable(self.expr)
expr.eval(frame)
self.explanation = astpattern.replace('__exprinfo_expr',
expr.explanation)
try:
self.result = frame.eval(astpattern,
__exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
keepalive.append(UnaryArith)
# == Binary operations ==
for astclass, astpattern in {
ast.Add : '(__exprinfo_left + __exprinfo_right)',
ast.Sub : '(__exprinfo_left - __exprinfo_right)',
ast.Mul : '(__exprinfo_left * __exprinfo_right)',
ast.Div : '(__exprinfo_left / __exprinfo_right)',
ast.Mod : '(__exprinfo_left % __exprinfo_right)',
ast.Power : '(__exprinfo_left ** __exprinfo_right)',
}.items():
class BinaryArith(Interpretable):
__view__ = astclass
def eval(self, frame, astpattern=astpattern):
left = Interpretable(self.left)
left.eval(frame)
right = Interpretable(self.right)
right.eval(frame)
self.explanation = (astpattern
.replace('__exprinfo_left', left .explanation)
.replace('__exprinfo_right', right.explanation))
try:
self.result = frame.eval(astpattern,
__exprinfo_left=left.result,
__exprinfo_right=right.result)
except passthroughex:
raise
except:
raise Failure(self)
keepalive.append(BinaryArith)
class CallFunc(Interpretable):
__view__ = ast.CallFunc
def is_bool(self, frame):
source = 'isinstance(__exprinfo_value, bool)'
try:
return frame.is_true(frame.eval(source,
__exprinfo_value=self.result))
except passthroughex:
raise
except:
return False
def eval(self, frame):
node = Interpretable(self.node)
node.eval(frame)
explanations = []
vars = {'__exprinfo_fn': node.result}
source = '__exprinfo_fn('
for a in self.args:
if isinstance(a, ast.Keyword):
keyword = a.name
a = a.expr
else:
keyword = None
a = Interpretable(a)
a.eval(frame)
argname = '__exprinfo_%d' % len(vars)
vars[argname] = a.result
if keyword is None:
source += argname + ','
explanations.append(a.explanation)
else:
source += '%s=%s,' % (keyword, argname)
explanations.append('%s=%s' % (keyword, a.explanation))
if self.star_args:
star_args = Interpretable(self.star_args)
star_args.eval(frame)
argname = '__exprinfo_star'
vars[argname] = star_args.result
source += '*' + argname + ','
explanations.append('*' + star_args.explanation)
if self.dstar_args:
dstar_args = Interpretable(self.dstar_args)
dstar_args.eval(frame)
argname = '__exprinfo_kwds'
vars[argname] = dstar_args.result
source += '**' + argname + ','
explanations.append('**' + dstar_args.explanation)
self.explanation = "%s(%s)" % (
node.explanation, ', '.join(explanations))
if source.endswith(','):
source = source[:-1]
source += ')'
try:
self.result = frame.eval(source, **vars)
except passthroughex:
raise
except:
raise Failure(self)
if not node.is_builtin(frame) or not self.is_bool(frame):
r = frame.repr(self.result)
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
class Getattr(Interpretable):
__view__ = ast.Getattr
def eval(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
source = '__exprinfo_expr.%s' % self.attrname
try:
self.result = frame.eval(source, __exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
self.explanation = '%s.%s' % (expr.explanation, self.attrname)
# if the attribute comes from the instance, its value is interesting
source = ('hasattr(__exprinfo_expr, "__dict__") and '
'%r in __exprinfo_expr.__dict__' % self.attrname)
try:
from_instance = frame.is_true(
frame.eval(source, __exprinfo_expr=expr.result))
except passthroughex:
raise
except:
from_instance = True
if from_instance:
r = frame.repr(self.result)
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
# == Re-interpretation of full statements ==
class Assert(Interpretable):
__view__ = ast.Assert
def run(self, frame):
test = Interpretable(self.test)
test.eval(frame)
# print the result as 'assert <explanation>'
self.result = test.result
self.explanation = 'assert ' + test.explanation
if not frame.is_true(test.result):
try:
raise BuiltinAssertionError
except passthroughex:
raise
except:
raise Failure(self)
class Assign(Interpretable):
__view__ = ast.Assign
def run(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
self.result = expr.result
self.explanation = '... = ' + expr.explanation
# fall-back-run the rest of the assignment
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
mod = ast.Module(None, ast.Stmt([ass]))
mod.filename = '<run>'
co = pycodegen.ModuleCodeGenerator(mod).getCode()
try:
frame.exec_(co, __exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
class Discard(Interpretable):
__view__ = ast.Discard
def run(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
self.result = expr.result
self.explanation = expr.explanation
class Stmt(Interpretable):
__view__ = ast.Stmt
def run(self, frame):
for stmt in self.nodes:
stmt = Interpretable(stmt)
stmt.run(frame)
def report_failure(e):
explanation = e.node.nice_explanation()
if explanation:
explanation = ", in: " + explanation
else:
explanation = ""
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
def check(s, frame=None):
if frame is None:
frame = sys._getframe(1)
frame = py.code.Frame(frame)
expr = parse(s, 'eval')
assert isinstance(expr, ast.Expression)
node = Interpretable(expr.node)
try:
node.eval(frame)
except passthroughex:
raise
except Failure:
e = sys.exc_info()[1]
report_failure(e)
else:
if not frame.is_true(node.result):
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
###########################################################
# API / Entry points
# #########################################################
def interpret(source, frame, should_fail=False):
module = Interpretable(parse(source, 'exec').node)
#print "got module", module
if isinstance(frame, py.std.types.FrameType):
frame = py.code.Frame(frame)
try:
module.run(frame)
except Failure:
e = sys.exc_info()[1]
return getfailure(e)
except passthroughex:
raise
except:
import traceback
traceback.print_exc()
if should_fail:
return ("(assertion failed, but when it was re-run for "
"printing intermediate values, it did not fail. Suggestions: "
"compute assert expression before the assert or use --assert=plain)")
else:
return None
def getmsg(excinfo):
if isinstance(excinfo, tuple):
excinfo = py.code.ExceptionInfo(excinfo)
#frame, line = gettbline(tb)
#frame = py.code.Frame(frame)
#return interpret(line, frame)
tb = excinfo.traceback[-1]
source = str(tb.statement).strip()
x = interpret(source, tb.frame, should_fail=True)
if not isinstance(x, str):
raise TypeError("interpret returned non-string %r" % (x,))
return x
def getfailure(e):
explanation = e.node.nice_explanation()
if str(e.value):
lines = explanation.split('\n')
lines[0] += " << %s" % (e.value,)
explanation = '\n'.join(lines)
text = "%s: %s" % (e.exc.__name__, explanation)
if text.startswith('AssertionError: assert '):
text = text[16:]
return text
def run(s, frame=None):
if frame is None:
frame = sys._getframe(1)
frame = py.code.Frame(frame)
module = Interpretable(parse(s, 'exec').node)
try:
module.run(frame)
except Failure:
e = sys.exc_info()[1]
report_failure(e)
if __name__ == '__main__':
# example:
def f():
return 5
def g():
return 3
def h(x):
return 'never'
check("f() * g() == 5")
check("not f()")
check("not (f() and g() or 0)")
check("f() == g()")
i = 4
check("i == f()")
check("len(f()) == 0")
check("isinstance(2+3+4, float)")
run("x = i")
check("x == 5")
run("assert not f(), 'oops'")
run("a, b, c = 1, 2")
run("a, b, c = f()")
check("max([f(),g()]) == 4")
check("'hello'[g()] == 'h'")
run("'guk%d' % h(f())")

View File

@@ -0,0 +1,48 @@
import sys
import py
BuiltinAssertionError = py.builtin.builtins.AssertionError
class AssertionError(BuiltinAssertionError):
def __init__(self, *args):
BuiltinAssertionError.__init__(self, *args)
if args:
try:
self.msg = str(args[0])
except py.builtin._sysex:
raise
except:
self.msg = "<[broken __repr__] %s at %0xd>" %(
args[0].__class__, id(args[0]))
else:
f = py.code.Frame(sys._getframe(1))
try:
source = f.code.fullsource
if source is not None:
try:
source = source.getstatement(f.lineno, assertion=True)
except IndexError:
source = None
else:
source = str(source.deindent()).strip()
except py.error.ENOENT:
source = None
# this can also occur during reinterpretation, when the
# co_filename is set to "<run>".
if source:
self.msg = reinterpret(source, f, should_fail=True)
else:
self.msg = "<could not determine information>"
if not self.args:
self.args = (self.msg,)
if sys.version_info > (3, 0):
AssertionError.__module__ = "builtins"
reinterpret_old = "old reinterpretation not available for py3"
else:
from _pytest.assertion.oldinterpret import interpret as reinterpret_old
if sys.version_info >= (2, 6) or (sys.platform.startswith("java")):
from _pytest.assertion.newinterpret import interpret as reinterpret
else:
reinterpret = reinterpret_old

View File

@@ -0,0 +1,598 @@
"""Rewrite assertion AST to produce nice error messages"""
import ast
import collections
import errno
import itertools
import imp
import marshal
import os
import struct
import sys
import types
import py
from _pytest.assertion import util
# Windows gives ENOENT in places *nix gives ENOTDIR.
if sys.platform.startswith("win"):
PATH_COMPONENT_NOT_DIR = errno.ENOENT
else:
PATH_COMPONENT_NOT_DIR = errno.ENOTDIR
# py.test caches rewritten pycs in __pycache__.
if hasattr(imp, "get_tag"):
PYTEST_TAG = imp.get_tag() + "-PYTEST"
else:
if hasattr(sys, "pypy_version_info"):
impl = "pypy"
elif sys.platform == "java":
impl = "jython"
else:
impl = "cpython"
ver = sys.version_info
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
del ver, impl
PYC_EXT = ".py" + "c" if __debug__ else "o"
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
class AssertionRewritingHook(object):
"""Import hook which rewrites asserts."""
def __init__(self):
self.session = None
self.modules = {}
def set_session(self, session):
self.fnpats = session.config.getini("python_files")
self.session = session
def find_module(self, name, path=None):
if self.session is None:
return None
sess = self.session
state = sess.config._assertstate
state.trace("find_module called for: %s" % name)
names = name.rsplit(".", 1)
lastname = names[-1]
pth = None
if path is not None and len(path) == 1:
pth = path[0]
if pth is None:
try:
fd, fn, desc = imp.find_module(lastname, path)
except ImportError:
return None
if fd is not None:
fd.close()
tp = desc[2]
if tp == imp.PY_COMPILED:
if hasattr(imp, "source_from_cache"):
fn = imp.source_from_cache(fn)
else:
fn = fn[:-1]
elif tp != imp.PY_SOURCE:
# Don't know what this is.
return None
else:
fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
fn_pypath = py.path.local(fn)
# Is this a test file?
if not sess.isinitpath(fn):
# We have to be very careful here because imports in this code can
# trigger a cycle.
self.session = None
try:
for pat in self.fnpats:
if fn_pypath.fnmatch(pat):
state.trace("matched test file %r" % (fn,))
break
else:
return None
finally:
self.session = sess
else:
state.trace("matched test file (was specified on cmdline): %r" % (fn,))
# The requested module looks like a test file, so rewrite it. This is
# the most magical part of the process: load the source, rewrite the
# asserts, and load the rewritten source. We also cache the rewritten
# module code in a special pyc. We must be aware of the possibility of
# concurrent py.test processes rewriting and loading pycs. To avoid
# tricky race conditions, we maintain the following invariant: The
# cached pyc is always a complete, valid pyc. Operations on it must be
# atomic. POSIX's atomic rename comes in handy.
write = not sys.dont_write_bytecode
cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
if write:
try:
os.mkdir(cache_dir)
except OSError:
e = sys.exc_info()[1].errno
if e == errno.EEXIST:
# Either the __pycache__ directory already exists (the
# common case) or it's blocked by a non-dir node. In the
# latter case, we'll ignore it in _write_pyc.
pass
elif e == PATH_COMPONENT_NOT_DIR:
# One of the path components was not a directory, likely
# because we're in a zip file.
write = False
elif e == errno.EACCES:
state.trace("read only directory: %r" % (fn_pypath.dirname,))
write = False
else:
raise
cache_name = fn_pypath.basename[:-3] + PYC_TAIL
pyc = os.path.join(cache_dir, cache_name)
# Notice that even if we're in a read-only directory, I'm going to check
# for a cached pyc. This may not be optimal...
co = _read_pyc(fn_pypath, pyc)
if co is None:
state.trace("rewriting %r" % (fn,))
co = _rewrite_test(state, fn_pypath)
if co is None:
# Probably a SyntaxError in the test.
return None
if write:
_make_rewritten_pyc(state, fn_pypath, pyc, co)
else:
state.trace("found cached rewritten pyc for %r" % (fn,))
self.modules[name] = co, pyc
return self
def load_module(self, name):
co, pyc = self.modules.pop(name)
# I wish I could just call imp.load_compiled here, but __file__ has to
# be set properly. In Python 3.2+, this all would be handled correctly
# by load_compiled.
mod = sys.modules[name] = imp.new_module(name)
try:
mod.__file__ = co.co_filename
# Normally, this attribute is 3.2+.
mod.__cached__ = pyc
py.builtin.exec_(co, mod.__dict__)
except:
del sys.modules[name]
raise
return sys.modules[name]
def _write_pyc(co, source_path, pyc):
# Technically, we don't have to have the same pyc format as (C)Python, since
# these "pycs" should never be seen by builtin import. However, there's
# little reason deviate, and I hope sometime to be able to use
# imp.load_compiled to load them. (See the comment in load_module above.)
mtime = int(source_path.mtime())
try:
fp = open(pyc, "wb")
except IOError:
err = sys.exc_info()[1].errno
if err == PATH_COMPONENT_NOT_DIR:
# This happens when we get a EEXIST in find_module creating the
# __pycache__ directory and __pycache__ is by some non-dir node.
return False
raise
try:
fp.write(imp.get_magic())
fp.write(struct.pack("<l", mtime))
marshal.dump(co, fp)
finally:
fp.close()
return True
RN = "\r\n".encode("utf-8")
N = "\n".encode("utf-8")
def _rewrite_test(state, fn):
"""Try to read and rewrite *fn* and return the code object."""
try:
source = fn.read("rb")
except EnvironmentError:
return None
# On Python versions which are not 2.7 and less than or equal to 3.1, the
# parser expects *nix newlines.
if REWRITE_NEWLINES:
source = source.replace(RN, N) + N
try:
tree = ast.parse(source)
except SyntaxError:
# Let this pop up again in the real import.
state.trace("failed to parse: %r" % (fn,))
return None
rewrite_asserts(tree)
try:
co = compile(tree, fn.strpath, "exec")
except SyntaxError:
# It's possible that this error is from some bug in the
# assertion rewriting, but I don't know of a fast way to tell.
state.trace("failed to compile: %r" % (fn,))
return None
return co
def _make_rewritten_pyc(state, fn, pyc, co):
"""Try to dump rewritten code to *pyc*."""
if sys.platform.startswith("win"):
# Windows grants exclusive access to open files and doesn't have atomic
# rename, so just write into the final file.
_write_pyc(co, fn, pyc)
else:
# When not on windows, assume rename is atomic. Dump the code object
# into a file specific to this process and atomically replace it.
proc_pyc = pyc + "." + str(os.getpid())
if _write_pyc(co, fn, proc_pyc):
os.rename(proc_pyc, pyc)
def _read_pyc(source, pyc):
"""Possibly read a py.test pyc containing rewritten code.
Return rewritten code if successful or None if not.
"""
try:
fp = open(pyc, "rb")
except IOError:
return None
try:
try:
mtime = int(source.mtime())
data = fp.read(8)
except EnvironmentError:
return None
# Check for invalid or out of date pyc file.
if (len(data) != 8 or
data[:4] != imp.get_magic() or
struct.unpack("<l", data[4:])[0] != mtime):
return None
co = marshal.load(fp)
if not isinstance(co, types.CodeType):
# That's interesting....
return None
return co
finally:
fp.close()
def rewrite_asserts(mod):
"""Rewrite the assert statements in mod."""
AssertionRewriter().run(mod)
_saferepr = py.io.saferepr
from _pytest.assertion.util import format_explanation as _format_explanation
def _format_boolop(explanations, is_or):
return "(" + (is_or and " or " or " and ").join(explanations) + ")"
def _call_reprcompare(ops, results, expls, each_obj):
for i, res, expl in zip(range(len(ops)), results, expls):
try:
done = not res
except Exception:
done = True
if done:
break
if util._reprcompare is not None:
custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
if custom is not None:
return custom
return expl
unary_map = {
ast.Not : "not %s",
ast.Invert : "~%s",
ast.USub : "-%s",
ast.UAdd : "+%s"
}
binop_map = {
ast.BitOr : "|",
ast.BitXor : "^",
ast.BitAnd : "&",
ast.LShift : "<<",
ast.RShift : ">>",
ast.Add : "+",
ast.Sub : "-",
ast.Mult : "*",
ast.Div : "/",
ast.FloorDiv : "//",
ast.Mod : "%",
ast.Eq : "==",
ast.NotEq : "!=",
ast.Lt : "<",
ast.LtE : "<=",
ast.Gt : ">",
ast.GtE : ">=",
ast.Pow : "**",
ast.Is : "is",
ast.IsNot : "is not",
ast.In : "in",
ast.NotIn : "not in"
}
def set_location(node, lineno, col_offset):
"""Set node location information recursively."""
def _fix(node, lineno, col_offset):
if "lineno" in node._attributes:
node.lineno = lineno
if "col_offset" in node._attributes:
node.col_offset = col_offset
for child in ast.iter_child_nodes(node):
_fix(child, lineno, col_offset)
_fix(node, lineno, col_offset)
return node
class AssertionRewriter(ast.NodeVisitor):
def run(self, mod):
"""Find all assert statements in *mod* and rewrite them."""
if not mod.body:
# Nothing to do.
return
# Insert some special imports at the top of the module but after any
# docstrings and __future__ imports.
aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
expect_docstring = True
pos = 0
lineno = 0
for item in mod.body:
if (expect_docstring and isinstance(item, ast.Expr) and
isinstance(item.value, ast.Str)):
doc = item.value.s
if "PYTEST_DONT_REWRITE" in doc:
# The module has disabled assertion rewriting.
return
lineno += len(doc) - 1
expect_docstring = False
elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
item.module != "__future__"):
lineno = item.lineno
break
pos += 1
imports = [ast.Import([alias], lineno=lineno, col_offset=0)
for alias in aliases]
mod.body[pos:pos] = imports
# Collect asserts.
nodes = [mod]
while nodes:
node = nodes.pop()
for name, field in ast.iter_fields(node):
if isinstance(field, list):
new = []
for i, child in enumerate(field):
if isinstance(child, ast.Assert):
# Transform assert.
new.extend(self.visit(child))
else:
new.append(child)
if isinstance(child, ast.AST):
nodes.append(child)
setattr(node, name, new)
elif (isinstance(field, ast.AST) and
# Don't recurse into expressions as they can't contain
# asserts.
not isinstance(field, ast.expr)):
nodes.append(field)
def variable(self):
"""Get a new variable."""
# Use a character invalid in python identifiers to avoid clashing.
name = "@py_assert" + str(next(self.variable_counter))
self.variables.append(name)
return name
def assign(self, expr):
"""Give *expr* a name."""
name = self.variable()
self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
return ast.Name(name, ast.Load())
def display(self, expr):
"""Call py.io.saferepr on the expression."""
return self.helper("saferepr", expr)
def helper(self, name, *args):
"""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)
def builtin(self, name):
"""Return the builtin called *name*."""
builtin_name = ast.Name("@py_builtins", ast.Load())
return ast.Attribute(builtin_name, name, ast.Load())
def explanation_param(self, expr):
specifier = "py" + str(next(self.variable_counter))
self.explanation_specifiers[specifier] = expr
return "%(" + specifier + ")s"
def push_format_context(self):
self.explanation_specifiers = {}
self.stack.append(self.explanation_specifiers)
def pop_format_context(self, expl_expr):
current = self.stack.pop()
if self.stack:
self.explanation_specifiers = self.stack[-1]
keys = [ast.Str(key) for key in current.keys()]
format_dict = ast.Dict(keys, list(current.values()))
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
name = "@py_format" + str(next(self.variable_counter))
self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
return ast.Name(name, ast.Load())
def generic_visit(self, node):
"""Handle expressions we don't have custom code for."""
assert isinstance(node, ast.expr)
res = self.assign(node)
return res, self.explanation_param(self.display(res))
def visit_Assert(self, assert_):
if assert_.msg:
# There's already a message. Don't mess with it.
return [assert_]
self.statements = []
self.cond_chain = ()
self.variables = []
self.variable_counter = itertools.count()
self.stack = []
self.on_failure = []
self.push_format_context()
# Rewrite assert into a bunch of statements.
top_condition, explanation = self.visit(assert_.test)
# Create failure message.
body = self.on_failure
negation = ast.UnaryOp(ast.Not(), top_condition)
self.statements.append(ast.If(negation, body, []))
explanation = "assert " + explanation
template = ast.Str(explanation)
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)
if sys.version_info[0] >= 3:
raise_ = ast.Raise(exc, None)
else:
raise_ = ast.Raise(exc, None, None)
body.append(raise_)
# Clear temporary variables by setting them to None.
if self.variables:
variables = [ast.Name(name, ast.Store()) for name in self.variables]
clear = ast.Assign(variables, ast.Name("None", ast.Load()))
self.statements.append(clear)
# Fix line numbers.
for stmt in self.statements:
set_location(stmt, assert_.lineno, assert_.col_offset)
return self.statements
def visit_Name(self, name):
# Check if the name is local or not.
locs = ast.Call(self.builtin("locals"), [], [], None, None)
globs = ast.Call(self.builtin("globals"), [], [], None, None)
ops = [ast.In(), ast.IsNot()]
test = ast.Compare(ast.Str(name.id), ops, [locs, globs])
expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
return name, self.explanation_param(expr)
def visit_BoolOp(self, boolop):
res_var = self.variable()
expl_list = self.assign(ast.List([], ast.Load()))
app = ast.Attribute(expl_list, "append", ast.Load())
is_or = int(isinstance(boolop.op, ast.Or))
body = save = self.statements
fail_save = self.on_failure
levels = len(boolop.values) - 1
self.push_format_context()
# Process each operand, short-circuting if needed.
for i, v in enumerate(boolop.values):
if i:
fail_inner = []
self.on_failure.append(ast.If(cond, fail_inner, []))
self.on_failure = fail_inner
self.push_format_context()
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)
self.on_failure.append(ast.Expr(call))
if i < levels:
cond = res
if is_or:
cond = ast.UnaryOp(ast.Not(), cond)
inner = []
self.statements.append(ast.If(cond, inner, []))
self.statements = body = inner
self.statements = save
self.on_failure = fail_save
expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
expl = self.pop_format_context(expl_template)
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
def visit_UnaryOp(self, unary):
pattern = unary_map[unary.op.__class__]
operand_res, operand_expl = self.visit(unary.operand)
res = self.assign(ast.UnaryOp(unary.op, operand_res))
return res, pattern % (operand_expl,)
def visit_BinOp(self, binop):
symbol = binop_map[binop.op.__class__]
left_expr, left_expl = self.visit(binop.left)
right_expr, right_expl = self.visit(binop.right)
explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation
def visit_Call(self, call):
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
new_kwargs = []
new_star = new_kwarg = None
for arg in call.args:
res, expl = self.visit(arg)
new_args.append(res)
arg_expls.append(expl)
for keyword in call.keywords:
res, expl = self.visit(keyword.value)
new_kwargs.append(ast.keyword(keyword.arg, res))
arg_expls.append(keyword.arg + "=" + expl)
if call.starargs:
new_star, expl = self.visit(call.starargs)
arg_expls.append("*" + expl)
if call.kwargs:
new_kwarg, expl = self.visit(call.kwargs)
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
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_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)
value, value_expl = self.visit(attr.value)
res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
res_expl = self.explanation_param(self.display(res))
pat = "%s\n{%s = %s.%s\n}"
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
return res, expl
def visit_Compare(self, comp):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
expls = []
syms = []
results = [left_res]
for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand)
results.append(next_res)
sym = binop_map[op.__class__]
syms.append(ast.Str(sym))
expl = "%s %s %s" % (left_expl, sym, next_expl)
expls.append(ast.Str(expl))
res_expr = ast.Compare(left_res, [op], [next_res])
self.statements.append(ast.Assign([store_names[i]], res_expr))
left_res, left_expl = next_res, next_expl
# Use py.code._reprcompare if that's available.
expl_call = self.helper("call_reprcompare",
ast.Tuple(syms, ast.Load()),
ast.Tuple(load_names, ast.Load()),
ast.Tuple(expls, ast.Load()),
ast.Tuple(results, ast.Load()))
if len(comp.ops) > 1:
res = ast.BoolOp(ast.And(), load_names)
else:
res = load_names[0]
return res, self.explanation_param(self.pop_format_context(expl_call))

View File

@@ -1,43 +1,79 @@
"""
support for presented detailed information in failing assertions.
"""
"""Utilities for assertion debugging"""
import py
import sys
from _pytest.monkeypatch import monkeypatch
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group._addoption('--no-assert', action="store_true", default=False,
dest="noassert",
help="disable python assert expression reinterpretation."),
def pytest_configure(config):
# The _reprcompare attribute on the py.code module is used by
# py._code._assertionnew to detect this plugin was loaded and in
# turn call the hooks defined here as part of the
# DebugInterpreter.
m = monkeypatch()
config._cleanup.append(m.undo)
warn_about_missing_assertion()
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
def callbinrepr(op, left, right):
hook_result = config.hook.pytest_assertrepr_compare(
config=config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
return '\n~'.join(new_expl)
m.setattr(py.builtin.builtins,
'AssertionError', py.code._AssertionError)
m.setattr(py.code, '_reprcompare', callbinrepr)
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
# loaded and in turn call the hooks defined here as part of the
# DebugInterpreter.
_reprcompare = None
def format_explanation(explanation):
"""This formats an explanation
Normally all embedded newlines are escaped, however there are
three exceptions: \n{, \n} and \n~. The first two are intended
cover nested explanations, see function and attribute explanations
for examples (.visit_Call(), visit_Attribute()). The last one is
for when one explanation needs to span multiple lines, e.g. when
displaying diffs.
"""
# simplify 'assert False where False = ...'
where = 0
while True:
start = where = explanation.find("False\n{False = ", where)
if where == -1:
break
level = 0
for i, c in enumerate(explanation[start:]):
if c == "{":
level += 1
elif c == "}":
level -= 1
if not level:
break
else:
raise AssertionError("unbalanced braces: %r" % (explanation,))
end = start + i
where = end
if explanation[end - 1] == '\n':
explanation = (explanation[:start] + explanation[start+15:end-1] +
explanation[end+1:])
where -= 17
raw_lines = (explanation or '').split('\n')
# escape newlines not followed by {, } and ~
lines = [raw_lines[0]]
for l in raw_lines[1:]:
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
lines.append(l)
else:
lines[-1] += '\\n' + l
result = lines[:1]
stack = [0]
stackcnt = [0]
for line in lines[1:]:
if line.startswith('{'):
if stackcnt[-1]:
s = 'and '
else:
s = 'where '
stack.append(len(result))
stackcnt[-1] += 1
stackcnt.append(0)
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
elif line.startswith('}'):
assert line.startswith('}')
stack.pop()
stackcnt.pop()
result[stack[-1]] += line[1:]
else:
assert line.startswith('~')
result.append(' '*len(stack) + line[1:])
assert len(stack) == 1
return '\n'.join(result)
def warn_about_missing_assertion():
try:
assert False
except AssertionError:
pass
else:
sys.stderr.write("WARNING: failing tests may report as passing because "
"assertions are turned off! (are you using python -O?)\n")
# Provide basestring in python3
try:
@@ -46,7 +82,7 @@ except NameError:
basestring = str
def pytest_assertrepr_compare(op, left, right):
def assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))

View File

@@ -12,12 +12,9 @@ def pytest_addoption(parser):
help="shortcut for --capture=no.")
def addouterr(rep, outerr):
repr = getattr(rep, 'longrepr', None)
if not hasattr(repr, 'addsection'):
return
for secname, content in zip(["out", "err"], outerr):
if content:
repr.addsection("Captured std%s" % secname, content.rstrip())
rep.sections.append(("Captured std%s" % secname, content))
def pytest_unconfigure(config):
# registered in config.py during early conftest.py loading

View File

@@ -8,8 +8,6 @@ import pytest
def pytest_cmdline_parse(pluginmanager, args):
config = Config(pluginmanager)
config.parse(args)
if config.option.debug:
config.trace.root.setwriter(sys.stderr.write)
return config
def pytest_unconfigure(config):
@@ -334,6 +332,7 @@ class Config(object):
# Note that this can only be called once per testing process.
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
self._origargs = args
self._preparse(args)
self._parser.hints.extend(self.pluginmanager._hints)
args = self._parser.parse_setoption(args, self.option)
@@ -422,7 +421,7 @@ class Config(object):
def getcfg(args, inibasenames):
args = [x for x in args if str(x)[0] != "-"]
args = [x for x in args if not str(x).startswith("-")]
if not args:
args = [py.path.local()]
for arg in args:

View File

@@ -16,11 +16,10 @@ default_plugins = (
"junitxml resultlog doctest").split()
class TagTracer:
def __init__(self, prefix="[pytest] "):
def __init__(self):
self._tag2proc = {}
self.writer = None
self.indent = 0
self.prefix = prefix
def get(self, name):
return TagTracerSub(self, (name,))
@@ -30,7 +29,7 @@ class TagTracer:
if args:
indent = " " * self.indent
content = " ".join(map(str, args))
self.writer("%s%s%s\n" %(self.prefix, indent, content))
self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags)))
try:
self._tag2proc[tags](tags, args)
except KeyError:

View File

@@ -59,7 +59,7 @@ class DoctestItem(pytest.Item):
inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
lines += py.std.traceback.format_exception(*excinfo.value.exc_info)
return ReprFailDoctest(reprlocation, lines)
else:
return super(DoctestItem, self).repr_failure(excinfo)

View File

@@ -1,7 +1,7 @@
""" version info, help messages, tracing configuration. """
import py
import pytest
import inspect, sys
import os, inspect, sys
from _pytest.core import varnames
def pytest_addoption(parser):
@@ -16,12 +16,31 @@ def pytest_addoption(parser):
group.addoption('--traceconfig',
action="store_true", dest="traceconfig", default=False,
help="trace considerations of conftest.py files."),
group._addoption('--nomagic',
action="store_true", dest="nomagic", default=False,
help="don't reinterpret asserts, no traceback cutting. ")
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="generate and show internal debugging information.")
help="store internal tracing debug information in 'pytestdebug.log'.")
def pytest_cmdline_parse(__multicall__):
config = __multicall__.execute()
if config.option.debug:
path = os.path.abspath("pytestdebug.log")
f = open(path, 'w')
config._debugfile = f
f.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.trace.root.setwriter(f.write)
sys.stderr.write("writing pytestdebug information to %s\n" % path)
return config
@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 pytest_cmdline_main(config):

View File

@@ -65,18 +65,18 @@ def pytest_unconfigure(config):
class LogXML(object):
def __init__(self, logfile, prefix):
self.logfile = logfile
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(logfile)
self.prefix = prefix
self.test_logs = []
self.passed = self.skipped = 0
self.failed = self.errors = 0
self._durations = {}
def _opentestcase(self, report):
names = report.nodeid.split("::")
names[0] = names[0].replace("/", '.')
names = tuple(names)
d = {'time': self._durations.pop(names, "0")}
d = {'time': getattr(report, 'duration', 0)}
names = [x.replace(".py", "") for x in names if x != "()"]
classnames = names[:-1]
if self.prefix:
@@ -113,8 +113,14 @@ class LogXML(object):
'<skipped message="xfail-marked test passes unexpectedly"/>')
self.skipped += 1
else:
sec = dict(report.sections)
self.appendlog('<failure message="test failure">%s</failure>',
report.longrepr)
for name in ('out', 'err'):
content = sec.get("Captured std%s" % name)
if content:
self.appendlog(
"<system-%s>%%s</system-%s>" % (name, name), content)
self.failed += 1
self._closetestcase()
@@ -169,14 +175,6 @@ class LogXML(object):
elif report.skipped:
self.append_skipped(report)
def pytest_runtest_call(self, item, __multicall__):
names = tuple(item.listnames())
start = time.time()
try:
return __multicall__.execute()
finally:
self._durations[names] = time.time() - start
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:

View File

@@ -2,7 +2,7 @@
import py
import pytest, _pytest
import os, sys
import os, sys, imp
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
# exitcodes for the command line
@@ -46,23 +46,25 @@ def pytest_addoption(parser):
def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector, File=File))
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
return dict(collect=collect)
def pytest_configure(config):
py.test.config = config # compatibiltiy
if config.option.exitfirst:
config.option.maxfail = 1
def pytest_cmdline_main(config):
""" default command line protocol for initialization, session,
running tests and reporting. """
def wrap_session(config, doit):
"""Skeleton command line program"""
session = Session(config)
session.exitstatus = EXIT_OK
initstate = 0
try:
config.pluginmanager.do_configure(config)
initstate = 1
config.hook.pytest_sessionstart(session=session)
config.hook.pytest_collection(session=session)
config.hook.pytest_runtestloop(session=session)
initstate = 2
doit(config, session)
except pytest.UsageError:
raise
except KeyboardInterrupt:
@@ -77,18 +79,24 @@ def pytest_cmdline_main(config):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
if not session.exitstatus and session._testsfailed:
session.exitstatus = EXIT_TESTSFAILED
config.hook.pytest_sessionfinish(session=session,
exitstatus=session.exitstatus)
config.pluginmanager.do_unconfigure(config)
if initstate >= 2:
config.hook.pytest_sessionfinish(session=session,
exitstatus=session.exitstatus)
if initstate >= 1:
config.pluginmanager.do_unconfigure(config)
return session.exitstatus
def pytest_cmdline_main(config):
return wrap_session(config, _main)
def _main(config, session):
""" default command line protocol for initialization, session,
running tests and reporting. """
config.hook.pytest_collection(session=session)
config.hook.pytest_runtestloop(session=session)
def pytest_collection(session):
session.perform_collect()
hook = session.config.hook
hook.pytest_collection_modifyitems(session=session,
config=session.config, items=session.items)
hook.pytest_collection_finish(session=session)
return True
return session.perform_collect()
def pytest_runtestloop(session):
if session.config.option.collectonly:
@@ -374,6 +382,16 @@ class Session(FSCollector):
return HookProxy(fspath, self.config)
def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
try:
items = self._perform_collect(args, genitems)
hook.pytest_collection_modifyitems(session=self,
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
return items
def _perform_collect(self, args, genitems):
if args is None:
args = self.config.args
self.trace("perform_collect", self, args)
@@ -451,16 +469,22 @@ class Session(FSCollector):
return True
def _tryconvertpyarg(self, x):
try:
mod = __import__(x, None, None, ['__doc__'])
except (ValueError, ImportError):
return x
p = py.path.local(mod.__file__)
if p.purebasename == "__init__":
p = p.dirpath()
else:
p = p.new(basename=p.purebasename+".py")
return str(p)
mod = None
path = [os.path.abspath('.')] + sys.path
for name in x.split('.'):
try:
fd, mod, type_ = imp.find_module(name, path)
except ImportError:
return x
else:
if fd is not None:
fd.close()
if type_[2] != imp.PKG_DIRECTORY:
path = [os.path.dirname(mod)]
else:
path = [mod]
return mod
def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """

View File

@@ -153,7 +153,7 @@ class MarkInfo:
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self._name, self.args, self.kwargs)
self.name, self.args, self.kwargs)
def pytest_itemcollected(item):
if not isinstance(item, pytest.Function):

View File

@@ -13,6 +13,7 @@ def pytest_runtest_makereport(__multicall__, item, call):
call.excinfo = call2.excinfo
@pytest.mark.trylast
def pytest_runtest_setup(item):
if isinstance(item, (pytest.Function)):
if isinstance(item.parent, pytest.Generator):

View File

@@ -19,11 +19,13 @@ def pytest_configure(config):
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
item = None
collector = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
frame = sys._getframe().f_back
item = getattr(self, 'item', None)
item = self.item or self.collector
if item is not None:
capman = item.config.pluginmanager.getplugin("capturemanager")
out, err = capman.suspendcapture()
@@ -38,6 +40,14 @@ def pdbitem(item):
pytestPDB.item = item
pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
@pytest.mark.tryfirst
def pytest_make_collect_report(__multicall__, collector):
try:
pytestPDB.collector = collector
return __multicall__.execute()
finally:
pytestPDB.collector = None
def pytest_runtest_makereport():
pytestPDB.item = None

View File

@@ -6,7 +6,7 @@ import re
import inspect
import time
from fnmatch import fnmatch
from _pytest.main import Session
from _pytest.main import Session, EXIT_OK
from py.builtin import print_
from _pytest.core import HookRelay
@@ -25,6 +25,7 @@ def pytest_configure(config):
_pytest_fullpath
except NameError:
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
def pytest_funcarg___pytest(request):
return PytestArg(request)
@@ -292,13 +293,19 @@ class TmpTestdir:
assert '::' not in str(arg)
p = py.path.local(arg)
x = session.fspath.bestrelpath(p)
return session.perform_collect([x], genitems=False)[0]
config.hook.pytest_sessionstart(session=session)
res = session.perform_collect([x], genitems=False)[0]
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
return res
def getpathnode(self, path):
config = self.parseconfig(path)
config = self.parseconfigure(path)
session = Session(config)
x = session.fspath.bestrelpath(path)
return session.perform_collect([x], genitems=False)[0]
config.hook.pytest_sessionstart(session=session)
res = session.perform_collect([x], genitems=False)[0]
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
return res
def genitems(self, colitems):
session = colitems[0].session
@@ -312,7 +319,9 @@ class TmpTestdir:
config = self.parseconfigure(*args)
rec = self.getreportrecorder(config)
session = Session(config)
config.hook.pytest_sessionstart(session=session)
session.perform_collect()
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
return session.items, rec
def runitem(self, source):
@@ -382,6 +391,8 @@ class TmpTestdir:
c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
keep=0, rootdir=self.tmpdir, lock_timeout=None)
c.parse(args)
c.pluginmanager.do_configure(c)
self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c))
return c
finally:
py.test.config = oldconfig

View File

@@ -374,7 +374,7 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
self.config._setupstate.prepare(self)
self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
l = []
@@ -721,7 +721,7 @@ class FuncargRequest:
def _addfinalizer(self, finalizer, scope):
colitem = self._getscopeitem(scope)
self.config._setupstate.addfinalizer(
self._pyfuncitem.session._setupstate.addfinalizer(
finalizer=finalizer, colitem=colitem)
def __repr__(self):
@@ -742,8 +742,10 @@ class FuncargRequest:
raise self.LookupError(msg)
def showfuncargs(config):
from _pytest.main import Session
session = Session(config)
from _pytest.main import wrap_session
return wrap_session(config, _showfuncargs_main)
def _showfuncargs_main(config, session):
session.perform_collect()
if session.items:
plugins = session.items[0].getplugins()

View File

@@ -1,6 +1,6 @@
""" basic collect and runtest protocol implementations """
import py, sys
import py, sys, time
from py._code.code import TerminalRepr
def pytest_namespace():
@@ -14,17 +14,15 @@ def pytest_namespace():
#
# pytest plugin hooks
# XXX move to pytest_sessionstart and fix py.test owns tests
def pytest_configure(config):
config._setupstate = SetupState()
def pytest_sessionstart(session):
session._setupstate = SetupState()
def pytest_sessionfinish(session, exitstatus):
if hasattr(session.config, '_setupstate'):
hook = session.config.hook
rep = hook.pytest__teardown_final(session=session)
if rep:
hook.pytest__teardown_final_logerror(session=session, report=rep)
session.exitstatus = 1
hook = session.config.hook
rep = hook.pytest__teardown_final(session=session)
if rep:
hook.pytest__teardown_final_logerror(session=session, report=rep)
session.exitstatus = 1
class NodeInfo:
def __init__(self, location):
@@ -46,16 +44,16 @@ def runtestprotocol(item, log=True):
return reports
def pytest_runtest_setup(item):
item.config._setupstate.prepare(item)
item.session._setupstate.prepare(item)
def pytest_runtest_call(item):
item.runtest()
def pytest_runtest_teardown(item):
item.config._setupstate.teardown_exact(item)
item.session._setupstate.teardown_exact(item)
def pytest__teardown_final(session):
call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
call = CallInfo(session._setupstate.teardown_all, when="teardown")
if call.excinfo:
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
call.excinfo.traceback = ntraceback.filter()
@@ -97,12 +95,16 @@ class CallInfo:
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
self.when = when
self.start = time.time()
try:
self.result = func()
except KeyboardInterrupt:
raise
except:
self.excinfo = py.code.ExceptionInfo()
try:
self.result = func()
except KeyboardInterrupt:
raise
except:
self.excinfo = py.code.ExceptionInfo()
finally:
self.stop = time.time()
def __repr__(self):
if self.excinfo:
@@ -141,6 +143,7 @@ class BaseReport(object):
def pytest_runtest_makereport(item, call):
when = call.when
duration = call.stop-call.start
keywords = dict([(x,1) for x in item.keywords])
excinfo = call.excinfo
if not call.excinfo:
@@ -162,14 +165,15 @@ def pytest_runtest_makereport(item, call):
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo)
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when)
keywords, outcome, longrepr, when,
duration=duration)
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
def __init__(self, nodeid, location,
keywords, outcome, longrepr, when):
keywords, outcome, longrepr, when, sections=(), duration=0):
#: normalized collection node id
self.nodeid = nodeid
@@ -191,6 +195,13 @@ class TestReport(BaseReport):
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
self.when = when
#: list of (secname, data) extra information which needs to
#: marshallable
self.sections = list(sections)
#: time it took to run just the test
self.duration = duration
def __repr__(self):
return "<TestReport %r when=%r outcome=%r>" % (
self.nodeid, self.when, self.outcome)
@@ -200,6 +211,7 @@ class TeardownErrorReport(BaseReport):
when = "teardown"
def __init__(self, longrepr):
self.longrepr = longrepr
self.sections = []
def pytest_make_collect_report(collector):
call = CallInfo(collector._memocollect, "memocollect")
@@ -221,11 +233,12 @@ def pytest_make_collect_report(collector):
getattr(call, 'result', None))
class CollectReport(BaseReport):
def __init__(self, nodeid, outcome, longrepr, result):
def __init__(self, nodeid, outcome, longrepr, result, sections=()):
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
self.result = result or []
self.sections = list(sections)
@property
def location(self):

View File

@@ -169,21 +169,23 @@ def pytest_terminal_summary(terminalreporter):
elif char == "X":
show_xpassed(terminalreporter, lines)
elif char in "fF":
show_failed(terminalreporter, lines)
show_simple(terminalreporter, lines, 'failed', "FAIL %s")
elif char in "sS":
show_skipped(terminalreporter, lines)
elif char == "E":
show_simple(terminalreporter, lines, 'error', "ERROR %s")
if lines:
tr._tw.sep("=", "short test summary info")
for line in lines:
tr._tw.line(line)
def show_failed(terminalreporter, lines):
def show_simple(terminalreporter, lines, stat, format):
tw = terminalreporter._tw
failed = terminalreporter.stats.get("failed")
failed = terminalreporter.stats.get(stat)
if failed:
for rep in failed:
pos = rep.nodeid
lines.append("FAIL %s" %(pos, ))
lines.append(format %(pos, ))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")

View File

@@ -15,7 +15,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, "
"(s)skipped, (x)failed, (X)passed.")
"(E)error, (s)skipped, (x)failed, (X)passed.")
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
@@ -259,7 +259,7 @@ class TerminalReporter:
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]" % verinfo
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
msg += " -- pytest-%s" % (py.test.__version__)
if self.verbosity > 0 or self.config.option.debug or \
getattr(self.config.option, 'pastebin', None):
@@ -318,12 +318,17 @@ class TerminalReporter:
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
self.summary_stats()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
def pytest_unconfigure(self):
if hasattr(self, '_keyboardinterrupt_memo'):
self._report_keyboardinterrupt()
def _report_keyboardinterrupt(self):
excrepr = self._keyboardinterrupt_memo
msg = excrepr.reprcrash.message
@@ -388,7 +393,7 @@ class TerminalReporter:
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
rep.toterminal(self._tw)
self._outrep_summary(rep)
def summary_errors(self):
if self.config.option.tbstyle != "no":
@@ -406,7 +411,15 @@ class TerminalReporter:
elif rep.when == "teardown":
msg = "ERROR at teardown of " + msg
self.write_sep("_", msg)
rep.toterminal(self._tw)
self._outrep_summary(rep)
def _outrep_summary(self, rep):
rep.toterminal(self._tw)
for secname, content in rep.sections:
self._tw.sep("-", secname)
if content[-1:] == "\n":
content = content[:-1]
self._tw.line(content)
def summary_stats(self):
session_duration = py.std.time.time() - self._sessionstarttime

View File

@@ -46,7 +46,7 @@ except ImportError:
args = [quote(arg) for arg in args]
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
DEFAULT_VERSION = "0.6.14"
DEFAULT_VERSION = "0.6.19"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"

View File

@@ -39,8 +39,14 @@ help:
clean:
-rm -rf $(BUILDDIR)/*
install: clean html
rsync -avz _build/html/ code:www-pytest/
install: html
@rsync -avz _build/html/ pytest.org:/www/pytest.org/latest
installpdf: latexpdf
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/latest
installall: clean install installpdf
@echo "done"
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@@ -48,7 +48,7 @@ div.body {
}
div.related {
font-size: 1em;
font-size: 0.8em;
}
div.related ul {

View File

@@ -10,7 +10,7 @@
<p><b><a href="{{ pathto('announce/index')}}">{{ release }} release</a></b>
[<a href="{{ pathto('changelog') }}">Changelog</a>]</p>
<p>
<a href="http://pypi.python.org/pypi/pytest">pytest on PyPI</a>
<a href="http://pypi.python.org/pypi/pytest">pytest/PyPI</a>
</p>
<pre>easy_install pytest</pre>
<pre>pip install pytest</pre>

View File

@@ -1,28 +1,8 @@
{% extends "!layout.html" %}
{% block relbar1 %}
{% endblock %}
{% block relbar2 %}
{% endblock %}
{% block rootrellink %}
{% endblock %}
{% block sidebarrel %}
{% endblock %}
{% block header %}
<div style="background-color: white; text-align: left; padding: 10px 10px 15px 15px">
<h1>pytest: rapid no-boilerplate testing with Python</h1>
<div style="text-align: left; font-size: 130%; vertical-align: middle;">
<a href="{{ pathto('index') }}">home</a>&nbsp;|&nbsp;
<a href="{{ pathto('contents') }}">all docs</a>&nbsp;|&nbsp;
<a href="{{ pathto('getting-started') }}">install</a>&nbsp;|&nbsp;
<a href="{{ pathto('example/index') }}">examples</a>&nbsp;|&nbsp;
<a href="{{ pathto('customize') }}">customize</a>&nbsp;|&nbsp;
<a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues</a>|&nbsp;
<a href="{{ pathto('contact') }}">contact</a>&nbsp;
</div>
</div>
{% block relbaritems %}
{{ super() }}
<g:plusone></g:plusone>
{% endblock %}
{% block footer %}
@@ -40,4 +20,5 @@
})();
</script>
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
{% endblock %}

37
doc/_templates/localtoc.html vendored Normal file
View File

@@ -0,0 +1,37 @@
{%- if pagename != "search" %}
<div id="searchbox" style="display: none">
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" size="18" />
<input type="submit" value="{{ _('Search') }}" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
{%- endif %}
<h3>quicklinks</h3>
<div style="text-align: left; font-size: 100%; vertical-align: middle;">
<table>
<tr>
<td>
<a href="{{ pathto('index') }}">home</a>
</td><td>
<a href="{{ pathto('contents') }}">TOC/contents</a>
</td></tr><tr><td>
<a href="{{ pathto('getting-started') }}">install</a>
</td><td>
<a href="{{ pathto('changelog') }}">changelog</a>
</td></tr><tr><td>
<a href="{{ pathto('example/index') }}">examples</a>
</td><td>
<a href="{{ pathto('customize') }}">customize</a>
</td></tr><tr><td>
<a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues[bb]</a>
</td><td>
<a href="{{ pathto('contact') }}">contact</a>
</td></tr></table>
</div>
{% extends "basic/localtoc.html" %}

0
doc/_templates/searchbox.html vendored Normal file
View File

View File

@@ -5,6 +5,10 @@ Release announcements
.. toctree::
:maxdepth: 2
release-2.1.3
release-2.1.2
release-2.1.1
release-2.1.0
release-2.0.3
release-2.0.2
release-2.0.1

View File

@@ -0,0 +1,48 @@
py.test 2.1.0: perfected assertions and bug fixes
===========================================================================
Welcome to the relase of pytest-2.1, a mature testing tool for Python,
supporting CPython 2.4-3.2, Jython and latest PyPy interpreters. See
the improved extensive docs (now also as PDF!) with tested examples here:
http://pytest.org/
The single biggest news about this release are **perfected assertions**
courtesy of Benjamin Peterson. You can now safely use ``assert``
statements in test modules without having to worry about side effects
or python optimization ("-OO") options. This is achieved by rewriting
assert statements in test modules upon import, using a PEP302 hook.
See http://pytest.org/assert.html#advanced-assertion-introspection for
detailed information. The work has been partly sponsored by my company,
merlinux GmbH.
For further details on bug fixes and smaller enhancements see below.
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
best,
holger krekel / http://merlinux.eu
Changes between 2.0.3 and 2.1.0
----------------------------------------------
- fix issue53 call nosestyle setup functions with correct ordering
- fix issue58 and issue59: new assertion code fixes
- merge Benjamin's assertionrewrite branch: now assertions
for test modules on python 2.6 and above are done by rewriting
the AST and saving the pyc file before the test module is imported.
see doc/assert.txt for more info.
- fix issue43: improve doctests with better traceback reporting on
unexpected exceptions
- fix issue47: timing output in junitxml for test cases is now correct
- fix issue48: typo in MarkInfo repr leading to exception
- fix issue49: avoid confusing error when initizaliation partially fails
- fix issue44: env/username expansion for junitxml file path
- show releaselevel information in test runs for pypy
- reworked doc pages for better navigation and PDF generation
- report KeyboardInterrupt even if interrupted during session startup
- fix issue 35 - provide PDF doc version and download link from index page

View File

@@ -0,0 +1,37 @@
py.test 2.1.1: assertion fixes and improved junitxml output
===========================================================================
pytest-2.1.1 is a backward compatible maintenance release of the
popular py.test testing tool. See extensive docs with examples here:
http://pytest.org/
Most bug fixes address remaining issues with the perfected assertions
introduced with 2.1.0 - many thanks to the bug reporters and to Benjamin
Peterson for helping to fix them. Also, junitxml output now produces
system-out/err tags which lead to better displays of tracebacks with Jenkins.
Also a quick note to package maintainers and others interested: there now
is a "pytest" man page which can be generated with "make man" in doc/.
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
best,
holger krekel / http://merlinux.eu
Changes between 2.1.0 and 2.1.1
----------------------------------------------
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
- fix issue60 / fix error conditions involving the creation of __pycache__
- fix issue63 / assertion rewriting on inserts involving strings containing '%'
- fix assertion rewriting on calls with a ** arg
- don't cache rewritten modules if bytecode generation is disabled
- fix assertion rewriting in read-only directories
- fix issue59: provide system-out/err tags for junitxml output
- fix issue61: assertion rewriting on boolean operations with 3 or more operands
- you can now build a man page with "cd doc ; make man"

View File

@@ -0,0 +1,33 @@
py.test 2.1.2: bug fixes and fixes for jython
===========================================================================
pytest-2.1.2 is a minor backward compatible maintenance release of the
popular py.test testing tool. pytest is commonly used for unit,
functional- and integration testing. See extensive docs with examples
here:
http://pytest.org/
Most bug fixes address remaining issues with the perfected assertions
introduced in the 2.1 series - many thanks to the bug reporters and to Benjamin
Peterson for helping to fix them. pytest should also work better with
Jython-2.5.1 (and Jython trunk).
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
best,
holger krekel / http://merlinux.eu
Changes between 2.1.1 and 2.1.2
----------------------------------------
- fix assertion rewriting on files with windows newlines on some Python versions
- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
- fix issue69 / assertion rewriting fixed on some boolean operations
- fix issue68 / packages now work with assertion rewriting
- fix issue66: use different assertion rewriting caches when the -O option is passed
- don't try assertion rewriting on Jython, use reinterp

View File

@@ -0,0 +1,32 @@
py.test 2.1.3: just some more fixes
===========================================================================
pytest-2.1.3 is a minor backward compatible maintenance release of the
popular py.test testing tool. It is commonly used for unit, functional-
and integration testing. See extensive docs with examples here:
http://pytest.org/
The release contains another fix to the perfected assertions introduced
with the 2.1 series as well as the new possibility to customize reporting
for assertion expressions on a per-directory level.
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
Thanks to the bug reporters and to Ronny Pfannschmidt, Benjamin Peterson
and Floris Bruynooghe who implemented the fixes.
best,
holger krekel
Changes between 2.1.2 and 2.1.3
----------------------------------------
- fix issue79: assertion rewriting failed on some comparisons in boolops,
- correctly handle zero length arguments (a la pytest '')
- fix issue67 / junitxml now contains correct test durations
- fix issue75 / skipping test failure on jython
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests

View File

@@ -4,7 +4,7 @@ The writing and reporting of assertions in tests
.. _`assert with the assert statement`:
assert with the ``assert`` statement
Asserting with the ``assert`` statement
---------------------------------------------------------
``py.test`` allows you to use the standard python ``assert`` for verifying
@@ -18,12 +18,12 @@ following::
def test_function():
assert f() == 4
to assert that your object returns a certain value. If this
assertion fails you will see the value of ``x``::
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 linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_assert1.py F
@@ -37,27 +37,24 @@ assertion fails you will see the value of ``x``::
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.03 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording the intermediate values.
py.test has support for showing the values of the most common subexpressions
including calls, attributes, comparisons, and binary and unary
operators. (See :ref:`tbreportdemo`). This allows you to use the
idiomatic python constructs without boilerplate code while not losing
introspection information.
Note: If evaluating the assert expression has side effects you may get a
warning that the intermediate values could not be determined safely. A
common example of this issue is an assertion which reads from a file::
However, if you specify a message with the assertion like this::
assert f.read() != '...'
assert a % 2 == 0, "value was odd, should be even"
If this assertion fails then the re-evaluation will probably succeed!
This is because ``f.read()`` will return an empty string when it is
called the second time during the re-evaluation. However, it is
easy to rewrite the assertion and avoid any trouble::
then no assertion introspection takes places at all and the message
will be simply shown in the traceback.
content = f.read()
assert content != '...'
See :ref:`assert-details` for more information on assertion introspection.
assertions about expected exceptions
Assertions about expected exceptions
------------------------------------------
In order to write assertions about raised exceptions, you can use
@@ -108,7 +105,7 @@ if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_assert2.py F
@@ -127,7 +124,7 @@ if you run this module::
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.03 seconds =========================
========================= 1 failed in 0.02 seconds =========================
Special comparisons are done for a number of cases:
@@ -137,8 +134,117 @@ Special comparisons are done for a number of cases:
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
..
Defining your own comparison
----------------------------------------------
Defining your own assertion comparison
----------------------------------------------
It is possible to add your own detailed explanations by implementing
the ``pytest_assertrepr_compare`` hook.
.. autofunction:: _pytest.hookspec.pytest_assertrepr_compare
As an example consider adding the following hook in a conftest.py which
provides an alternative explanation for ``Foo`` objects::
# content of conftest.py
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)]
now, given this test module::
# content of test_foocompare.py
class Foo:
def __init__(self, val):
self.val = val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
you can run the test module and get the custom output defined in
the conftest file::
$ py.test -q test_foocompare.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_compare _______________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert Comparing Foo instances:
E vals: 1 != 2
test_foocompare.py:8: AssertionError
1 failed in 0.02 seconds
.. _assert-details:
.. _`assert introspection`:
Advanced assertion introspection
----------------------------------
.. versionadded:: 2.1
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, py.test's configuration, and Python version being used
to run py.test. 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.
By default, if the Python version is greater than or equal to 2.6, py.test
rewrites assert statements in test modules. Rewritten assert statements put
introspection information into the assertion failure message. py.test only
rewrites test modules directly discovered by its test collection process, so
asserts in supporting modules which are not themselves test modules will not be
rewritten.
.. note::
py.test rewrites test modules on import. It does this by using an import hook
to write a new pyc files. Most of the time this works transparently. However,
if you are messing with import yourself, the import hook may interfere. If
this is the case, simply use ``--assert=reinterp`` or
``--assert=plain``. Additionally, rewriting will fail silently if it cannot
write new pycs, i.e. in a read-only filesystem or a zipfile.
If an assert statement has not been rewritten or the Python version is less than
2.6, py.test falls back on assert reinterpretation. In assert reinterpretation,
py.test walks the frame of the function containing the assert statement to
discover sub-expression results of the failing assert statement. You can force
py.test to always use assertion reinterpretation by passing the
``--assert=reinterp`` option.
Assert reinterpretation has a caveat not present with assert rewriting: If
evaluating the assert expression has side effects you may get a warning that the
intermediate values could not be determined safely. A common example of this
issue is an assertion which reads from a file::
assert f.read() != '...'
If this assertion fails then the re-evaluation will probably succeed!
This is because ``f.read()`` will return an empty string when it is
called the second time during the re-evaluation. However, it is
easy to rewrite the assertion and avoid any trouble::
content = f.read()
assert content != '...'
All assert introspection can be turned off by passing ``--assert=plain``.
For further information, Benjamin Peterson wrote up `Behind the scenes of py.test's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
.. versionadded:: 2.1
Add assert rewriting as an alternate introspection technique.
.. versionchanged:: 2.1
Introduce the ``--assert`` option. Deprecate ``--no-assert`` and
``--nomagic``.

View File

@@ -1,7 +1,7 @@
.. _`pytest helpers`:
pytest builtin helpers
Pytest builtin helpers
================================================
builtin pytest.* functions and helping objects
@@ -17,13 +17,16 @@ to get an overview on the globally available helpers.
.. automodule:: pytest
:members:
builtin function arguments
Builtin function arguments
-----------------------------------------------------
You can ask for available builtin or project-custom
:ref:`function arguments <funcargs>` by typing::
$ py.test --funcargs
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collected 0 items
pytestconfig
the pytest config object with access to command line opts.
capsys
@@ -69,3 +72,7 @@ You can ask for available builtin or project-custom
See http://docs.python.org/library/warnings.html for information
on warning categories.
cov
A pytest funcarg that provides access to the underlying coverage object.
============================= in 0.00 seconds =============================

View File

@@ -64,7 +64,7 @@ of the failing function and hide the other one::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
test_module.py .F
@@ -78,8 +78,8 @@ of the failing function and hide the other one::
test_module.py:9: AssertionError
----------------------------- Captured stdout ------------------------------
setting up <function test_func2 at 0x238c410>
==================== 1 failed, 1 passed in 0.02 seconds ====================
setting up <function test_func2 at 0x10130ccf8>
==================== 1 failed, 1 passed in 0.03 seconds ====================
Accessing captured output from a test function
---------------------------------------------------

View File

@@ -38,7 +38,7 @@ source_suffix = '.txt'
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = 'contents'
# General information about the project.
project = u'pytest'
@@ -48,13 +48,11 @@ copyright = u'2011, holger krekel et alii'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0'
# The full version, including alpha/beta/rc tags.
import py, pytest
import pytest
release = pytest.__version__
# The short X.Y version.
version = ".".join(release.split(".")[:2])
#assert py.path.local().relto(py.path.local(pytest.__file__).dirpath().dirpath())
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -68,7 +66,10 @@ version = ".".join(release.split(".")[:2])
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['links.inc', '_build', 'test', ] # XXX
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
'example/attic.txt',
]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
@@ -107,10 +108,10 @@ html_theme_options = {}
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
html_short_title = "pytest-%s" % release
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
@@ -136,7 +137,7 @@ html_static_path = ['_static']
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
html_sidebars = {'index': 'indexsidebar.html'}
#html_sidebars = {'index': 'indexsidebar.html'}
# Additional templates that should be rendered to pages, maps page names to
# template names.
@@ -145,16 +146,16 @@ html_sidebars = {'index': 'indexsidebar.html'}
# If false, no module index is generated.
#html_domain_indices = True
html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
html_use_index = False
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
@@ -185,7 +186,7 @@ htmlhelp_basename = 'pytestdoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'pytest.tex', u'pytest Documentation',
('contents', 'pytest.tex', u'pytest Documentation',
u'holger krekel et alii', 'manual'),
]
@@ -210,16 +211,15 @@ latex_documents = [
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
latex_domain_indices = False
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pytest', u'pytest Documentation',
[u'holger krekel et alii'], 1)
('usage', 'pytest', u'pytest usage',
[u'holger krekel at merlinux eu'], 1)
]
@@ -227,9 +227,9 @@ man_pages = [
# Bibliographic Dublin Core info.
epub_title = u'pytest'
epub_author = u'holger krekel et alii'
epub_publisher = u'holger krekel et alii'
epub_copyright = u'2010, holger krekel et alii'
epub_author = u'holger krekel at merlinux eu'
epub_publisher = u'holger krekel at merlinux eu'
epub_copyright = u'2011, holger krekel et alii'
# The language of the text. It defaults to the language option
# or en if the language is not set.

View File

@@ -1,19 +1,20 @@
.. _toc:
Table of Contents
Full pytest documenation
========================
.. note::
version 2.0 introduces :ref:`pytest as the main Python import name <naming20>`
`Download latest version as PDF <http://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
.. `Download latest version as EPUB <http://media.readthedocs.org/epub/pytest/latest/pytest.epub>`_
.. toctree::
:maxdepth: 2
overview
example/index
apiref
plugins
example/index
talks
develop
announce/index
@@ -22,12 +23,4 @@ Table of Contents
:hidden:
changelog.txt
naming20.txt
example/attic
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -1,4 +1,4 @@
basic test configuration
Basic test configuration
===================================
Command line options and configuration file settings
@@ -59,7 +59,7 @@ progress output, you can write it into a configuration file::
From now on, running ``py.test`` will add the specified options.
builtin configuration file options
Builtin configuration file options
----------------------------------------------
.. confval:: minversion

View File

@@ -1,5 +1,5 @@
doctest integration for modules and test files
Doctest integration for modules and test files
=========================================================
By default all files matching the ``test*.txt`` pattern will
@@ -44,9 +44,10 @@ then you can just invoke ``py.test`` without command line options::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
mymodule.py .
========================= 1 passed in 0.40 seconds =========================
========================= 1 passed in 0.06 seconds =========================
[?1034h

View File

@@ -7,6 +7,10 @@ Usages and Examples
Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
need more examples or have questions. Also take a look at the :ref:`comprehensive documentation <toc>` which contains many example snippets as well.
.. note::
see :doc:`../getting-started` for basic introductionary examples
.. toctree::
:maxdepth: 2

View File

@@ -3,7 +3,7 @@
.. _mysetup:
mysetup pattern: application specific test fixtures
Mysetup pattern: application specific test fixtures
==========================================================
Here is a basic useful step-by-step example for managing and interacting
@@ -12,8 +12,8 @@ where we have the glue and test support code for bootstrapping and
configuring application objects and allow test modules and test
functions to stay ignorant of involved details.
step1: implementing the test/app-specific ``mysetup`` pattern
-------------------------------------------------------------
Step 1: Implementing the test/app-specific ``mysetup`` pattern
--------------------------------------------------------------
Let's write a simple test function using a ``mysetup`` funcarg::
@@ -49,7 +49,7 @@ You can now run the test::
$ py.test test_sample.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_sample.py F
@@ -57,7 +57,7 @@ You can now run the test::
================================= FAILURES =================================
_______________________________ test_answer ________________________________
mysetup = <conftest.MySetup instance at 0x2c1b128>
mysetup = <conftest.MySetup instance at 0x1013145a8>
def test_answer(mysetup):
app = mysetup.myapp()
@@ -76,7 +76,7 @@ the concrete question or answers actually mean, please see here_.
.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
.. _`tut-cmdlineoption`:
step 2: checking a command line option and skipping tests
Step 2: Checking a command line option and skipping tests
-----------------------------------------------------------
To add a command line option we update the ``conftest.py`` of
@@ -122,14 +122,14 @@ Running it yields::
$ py.test test_ssh.py -rs
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_ssh.py s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-37/conftest.py:22: specify ssh host with --ssh
SKIP [1] /Users/hpk/tmp/doc-exec-167/conftest.py:22: specify ssh host with --ssh
======================== 1 skipped in 0.01 seconds =========================
======================== 1 skipped in 0.02 seconds =========================
If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.

View File

@@ -6,7 +6,7 @@ Working with non-python tests
.. _`yaml plugin`:
a basic example for specifying tests in Yaml files
A basic example for specifying tests in Yaml files
--------------------------------------------------------------
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
@@ -27,7 +27,7 @@ now execute the test specification::
nonpython $ py.test test_simple.yml
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
test_simple.yml .F
@@ -37,7 +37,7 @@ now execute the test specification::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.24 seconds ====================
==================== 1 failed, 1 passed in 0.09 seconds ====================
You get one dot for the passing ``sub1: sub1`` check and one failure.
Obviously in the above ``conftest.py`` you'll want to implement a more
@@ -56,7 +56,7 @@ reporting in ``verbose`` mode::
nonpython $ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_simple.yml:1: usecase: ok PASSED
@@ -67,17 +67,17 @@ reporting in ``verbose`` mode::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.07 seconds ====================
==================== 1 failed, 1 passed in 0.09 seconds ====================
While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
<YamlFile 'test_simple.yml'>
<YamlItem 'ok'>
<YamlItem 'hello'>
============================= in 0.07 seconds =============================
============================= in 0.08 seconds =============================

View File

@@ -1,14 +1,14 @@
.. _paramexamples:
parametrizing tests
Parametrizing tests
=================================================
py.test allows to easily implement your own custom
parametrization scheme for tests. Here we provide
some examples for inspiration and re-use.
generating parameters combinations, depending on command line
Generating parameters combinations, depending on command line
----------------------------------------------------------------------------
.. regendoc:wipe
@@ -44,7 +44,7 @@ This means that we only run 2 tests if we do not pass ``--all``::
$ py.test -q test_compute.py
collecting ... collected 2 items
..
2 passed in 0.01 seconds
2 passed in 0.02 seconds
We run only two computations, so we see two dots.
let's run the full monty::
@@ -114,7 +114,7 @@ Let's first see how it looks like at collection time::
$ py.test test_backends.py --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[0]'>
@@ -130,7 +130,7 @@ And then when we run the test::
================================= FAILURES =================================
__________________________ test_db_initialized[1] __________________________
db = <conftest.DB2 instance at 0x2bf7bd8>
db = <conftest.DB2 instance at 0x101316b90>
def test_db_initialized(db):
# a dummy test
@@ -139,7 +139,7 @@ And then when we run the test::
E Failed: deliberately failing for demo purposes
test_backends.py:6: Failed
1 failed, 1 passed in 0.03 seconds
1 failed, 1 passed in 0.02 seconds
Now you see that one invocation of the test passes and another fails,
as it to be expected.
@@ -184,7 +184,7 @@ the respective settings::
================================= FAILURES =================================
__________________________ test_db_initialized[1] __________________________
db = <conftest.DB2 instance at 0x19bcb90>
db = <conftest.DB2 instance at 0x10131c488>
def test_db_initialized(db):
# a dummy test
@@ -195,7 +195,7 @@ the respective settings::
test_backends.py:6: Failed
_________________________ TestClass.test_equals[0] _________________________
self = <test_parametrize.TestClass instance at 0x19ca8c0>, a = 1, b = 2
self = <test_parametrize.TestClass instance at 0x101322170>, a = 1, b = 2
def test_equals(self, a, b):
> assert a == b
@@ -204,7 +204,7 @@ the respective settings::
test_parametrize.py:17: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize.TestClass instance at 0x19cd4d0>, a = 3, b = 2
self = <test_parametrize.TestClass instance at 0x1013228c0>, a = 3, b = 2
def test_zerodivision(self, a, b):
> pytest.raises(ZeroDivisionError, "a/b")
@@ -252,7 +252,7 @@ Running it gives similar results as before::
================================= FAILURES =================================
_________________________ TestClass.test_equals[0] _________________________
self = <test_parametrize2.TestClass instance at 0x1cf1170>, a = 1, b = 2
self = <test_parametrize2.TestClass instance at 0x10130ac20>, a = 1, b = 2
@params([dict(a=1, b=2), dict(a=3, b=3), ])
def test_equals(self, a, b):
@@ -262,7 +262,7 @@ Running it gives similar results as before::
test_parametrize2.py:19: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize2.TestClass instance at 0x1d02170>, a = 3, b = 2
self = <test_parametrize2.TestClass instance at 0x10131c878>, a = 3, b = 2
@params([dict(a=1, b=0), dict(a=3, b=2)])
def test_zerodivision(self, a, b):
@@ -270,9 +270,9 @@ Running it gives similar results as before::
E Failed: DID NOT RAISE
test_parametrize2.py:23: Failed
2 failed, 2 passed in 0.03 seconds
2 failed, 2 passed in 0.04 seconds
checking serialization between Python interpreters
Checking serialization between Python interpreters
--------------------------------------------------------------
Here is a stripped down real-life example of using parametrized
@@ -290,5 +290,5 @@ Running it (with Python-2.4 through to Python2.7 installed)::
. $ py.test -q multipython.py
collecting ... collected 75 items
....s....s....s....ssssss....s....s....s....ssssss....s....s....s....ssssss
48 passed, 27 skipped in 2.04 seconds
ssssss...ss...ss...ssssssssssss...ss...ss...ssssssssssss...ss...ss...ssssss
27 passed, 48 skipped in 3.04 seconds

View File

@@ -1,7 +1,7 @@
Changing standard (Python) test discovery
===============================================
changing directory recursion
Changing directory recursion
-----------------------------------------------------
You can set the :confval:`norecursedirs` option in an ini-file, for example your ``setup.cfg`` in the project root directory::
@@ -14,7 +14,7 @@ This would tell py.test to not recurse into typical subversion or sphinx-build d
.. _`change naming conventions`:
change naming conventions
Changing naming conventions
-----------------------------------------------------
You can configure different naming conventions by setting
@@ -43,7 +43,7 @@ then the test collection looks like this::
$ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
<Module 'check_myapp.py'>
<Class 'CheckMyApp'>
@@ -51,9 +51,9 @@ then the test collection looks like this::
<Function 'check_simple'>
<Function 'check_complex'>
============================= in 0.01 seconds =============================
============================= in 0.02 seconds =============================
interpret cmdline arguments as Python packages
Interpreting cmdline arguments as Python packages
-----------------------------------------------------
You can use the ``--pyargs`` option to make py.test try
@@ -75,14 +75,14 @@ Now a simple invocation of ``py.test NAME`` will check
if NAME exists as an importable package/module and otherwise
treat it as a filesystem path.
finding out what is collected
Finding out what is collected
-----------------------------------------------
You can always peek at the collection tree without running tests like this::
. $ py.test --collectonly pythoncollection.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 3 items
<Module 'pythoncollection.py'>
<Function 'test_function'>

View File

@@ -13,7 +13,7 @@ get on the terminal - we are working on that):
assertion $ py.test failure_demo.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 39 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
@@ -30,7 +30,7 @@ get on the terminal - we are working on that):
failure_demo.py:15: AssertionError
_________________________ TestFailing.test_simple __________________________
self = <failure_demo.TestFailing object at 0x14b9890>
self = <failure_demo.TestFailing object at 0x10134bc50>
def test_simple(self):
def f():
@@ -40,13 +40,13 @@ get on the terminal - we are working on that):
> assert f() == g()
E assert 42 == 43
E + where 42 = <function f at 0x14a5e60>()
E + and 43 = <function g at 0x14bc1b8>()
E + where 42 = <function f at 0x101322320>()
E + and 43 = <function g at 0x101322398>()
failure_demo.py:28: AssertionError
____________________ TestFailing.test_simple_multiline _____________________
self = <failure_demo.TestFailing object at 0x14b9b50>
self = <failure_demo.TestFailing object at 0x10134b150>
def test_simple_multiline(self):
otherfunc_multi(
@@ -59,26 +59,26 @@ get on the terminal - we are working on that):
a = 42, b = 54
def otherfunc_multi(a,b):
assert (a ==
> b)
> assert (a ==
b)
E assert 42 == 54
failure_demo.py:12: AssertionError
failure_demo.py:11: AssertionError
___________________________ TestFailing.test_not ___________________________
self = <failure_demo.TestFailing object at 0x14b9790>
self = <failure_demo.TestFailing object at 0x10134b710>
def test_not(self):
def f():
return 42
> assert not f()
E assert not 42
E + where 42 = <function f at 0x14bc398>()
E + where 42 = <function f at 0x101322398>()
failure_demo.py:38: AssertionError
_________________ TestSpecialisedExplanations.test_eq_text _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x14aa810>
self = <failure_demo.TestSpecialisedExplanations object at 0x10134be10>
def test_eq_text(self):
> assert 'spam' == 'eggs'
@@ -89,7 +89,7 @@ get on the terminal - we are working on that):
failure_demo.py:42: AssertionError
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
self = <failure_demo.TestSpecialisedExplanations object at 0x1576190>
self = <failure_demo.TestSpecialisedExplanations object at 0x101347110>
def test_eq_similar_text(self):
> assert 'foo 1 bar' == 'foo 2 bar'
@@ -102,7 +102,7 @@ get on the terminal - we are working on that):
failure_demo.py:45: AssertionError
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
self = <failure_demo.TestSpecialisedExplanations object at 0x14a7450>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343d50>
def test_eq_multiline_text(self):
> assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
@@ -115,7 +115,7 @@ get on the terminal - we are working on that):
failure_demo.py:48: AssertionError
______________ TestSpecialisedExplanations.test_eq_long_text _______________
self = <failure_demo.TestSpecialisedExplanations object at 0x14b9350>
self = <failure_demo.TestSpecialisedExplanations object at 0x10134b210>
def test_eq_long_text(self):
a = '1'*100 + 'a' + '2'*100
@@ -132,7 +132,7 @@ get on the terminal - we are working on that):
failure_demo.py:53: AssertionError
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0x15764d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343d90>
def test_eq_long_text_multiline(self):
a = '1\n'*100 + 'a' + '2\n'*100
@@ -156,7 +156,7 @@ get on the terminal - we are working on that):
failure_demo.py:58: AssertionError
_________________ TestSpecialisedExplanations.test_eq_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x1576350>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343dd0>
def test_eq_list(self):
> assert [0, 1, 2] == [0, 1, 3]
@@ -166,7 +166,7 @@ get on the terminal - we are working on that):
failure_demo.py:61: AssertionError
______________ TestSpecialisedExplanations.test_eq_list_long _______________
self = <failure_demo.TestSpecialisedExplanations object at 0x1576f10>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343b90>
def test_eq_list_long(self):
a = [0]*100 + [1] + [3]*100
@@ -178,7 +178,7 @@ get on the terminal - we are working on that):
failure_demo.py:66: AssertionError
_________________ TestSpecialisedExplanations.test_eq_dict _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x1576390>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343210>
def test_eq_dict(self):
> assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
@@ -191,7 +191,7 @@ get on the terminal - we are working on that):
failure_demo.py:69: AssertionError
_________________ TestSpecialisedExplanations.test_eq_set __________________
self = <failure_demo.TestSpecialisedExplanations object at 0x14bd790>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343990>
def test_eq_set(self):
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
@@ -207,7 +207,7 @@ get on the terminal - we are working on that):
failure_demo.py:72: AssertionError
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
self = <failure_demo.TestSpecialisedExplanations object at 0x157a7d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343590>
def test_eq_longer_list(self):
> assert [1,2] == [1,2,3]
@@ -217,7 +217,7 @@ get on the terminal - we are working on that):
failure_demo.py:75: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x157ab50>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343e50>
def test_in_list(self):
> assert 1 in [0, 2, 3, 4, 5]
@@ -226,7 +226,7 @@ get on the terminal - we are working on that):
failure_demo.py:78: AssertionError
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0x157a090>
self = <failure_demo.TestSpecialisedExplanations object at 0x10133bb10>
def test_not_in_text_multiline(self):
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
@@ -244,7 +244,7 @@ get on the terminal - we are working on that):
failure_demo.py:82: AssertionError
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
self = <failure_demo.TestSpecialisedExplanations object at 0x14aaa50>
self = <failure_demo.TestSpecialisedExplanations object at 0x10133b990>
def test_not_in_text_single(self):
text = 'single foo line'
@@ -257,7 +257,7 @@ get on the terminal - we are working on that):
failure_demo.py:86: AssertionError
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
self = <failure_demo.TestSpecialisedExplanations object at 0x157ab90>
self = <failure_demo.TestSpecialisedExplanations object at 0x10133bbd0>
def test_not_in_text_single_long(self):
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
@@ -270,7 +270,7 @@ get on the terminal - we are working on that):
failure_demo.py:90: AssertionError
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
self = <failure_demo.TestSpecialisedExplanations object at 0x1576ed0>
self = <failure_demo.TestSpecialisedExplanations object at 0x101343510>
def test_not_in_text_single_long_term(self):
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
@@ -289,7 +289,7 @@ get on the terminal - we are working on that):
i = Foo()
> assert i.b == 2
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x157a910>.b
E + where 1 = <failure_demo.Foo object at 0x10133b390>.b
failure_demo.py:101: AssertionError
_________________________ test_attribute_instance __________________________
@@ -299,8 +299,8 @@ get on the terminal - we are working on that):
b = 1
> assert Foo().b == 2
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x1584610>.b
E + where <failure_demo.Foo object at 0x1584610> = <class 'failure_demo.Foo'>()
E + where 1 = <failure_demo.Foo object at 0x10133b250>.b
E + where <failure_demo.Foo object at 0x10133b250> = <class 'failure_demo.Foo'>()
failure_demo.py:107: AssertionError
__________________________ test_attribute_failure __________________________
@@ -316,7 +316,7 @@ get on the terminal - we are working on that):
failure_demo.py:116:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.Foo object at 0x157a3d0>
self = <failure_demo.Foo object at 0x10133bd50>
def _get_b(self):
> raise Exception('Failed to get attrib')
@@ -332,15 +332,15 @@ get on the terminal - we are working on that):
b = 2
> assert Foo().b == Bar().b
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x157a1d0>.b
E + where <failure_demo.Foo object at 0x157a1d0> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x157a9d0>.b
E + where <failure_demo.Bar object at 0x157a9d0> = <class 'failure_demo.Bar'>()
E + where 1 = <failure_demo.Foo object at 0x10133bb50>.b
E + where <failure_demo.Foo object at 0x10133bb50> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x10133b1d0>.b
E + where <failure_demo.Bar object at 0x10133b1d0> = <class 'failure_demo.Bar'>()
failure_demo.py:124: AssertionError
__________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises instance at 0x157d7e8>
self = <failure_demo.TestRaises instance at 0x1013697e8>
def test_raises(self):
s = 'qwe'
@@ -352,10 +352,10 @@ get on the terminal - we are working on that):
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen /home/hpk/p/pytest/_pytest/python.py:831>:1: ValueError
<0-codegen /Users/hpk/p/pytest/_pytest/python.py:833>:1: ValueError
______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises instance at 0x158ae60>
self = <failure_demo.TestRaises instance at 0x101372a70>
def test_raises_doesnt(self):
> raises(IOError, "int('3')")
@@ -364,7 +364,7 @@ get on the terminal - we are working on that):
failure_demo.py:136: Failed
__________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises instance at 0x158bb90>
self = <failure_demo.TestRaises instance at 0x10136a908>
def test_raise(self):
> raise ValueError("demo error")
@@ -373,7 +373,7 @@ get on the terminal - we are working on that):
failure_demo.py:139: ValueError
________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises instance at 0x157cd40>
self = <failure_demo.TestRaises instance at 0x10136c710>
def test_tupleerror(self):
> a,b = [1]
@@ -382,7 +382,7 @@ get on the terminal - we are working on that):
failure_demo.py:142: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises instance at 0x157d488>
self = <failure_demo.TestRaises instance at 0x101365488>
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
l = [1,2,3]
@@ -395,7 +395,7 @@ get on the terminal - we are working on that):
l is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________
self = <failure_demo.TestRaises instance at 0x158a7e8>
self = <failure_demo.TestRaises instance at 0x101367248>
def test_some_error(self):
> if namenotexi:
@@ -420,10 +420,10 @@ get on the terminal - we are working on that):
> assert 1 == 0
E assert 1 == 0
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/example/assertion/failure_demo.py:162>:2: AssertionError
<2-codegen 'abc-123' /Users/hpk/p/pytest/doc/example/assertion/failure_demo.py:162>:2: AssertionError
____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors instance at 0x158f8c0>
self = <failure_demo.TestMoreErrors instance at 0x101380f38>
def test_complex_error(self):
def f():
@@ -452,7 +452,7 @@ get on the terminal - we are working on that):
failure_demo.py:5: AssertionError
___________________ TestMoreErrors.test_z1_unpack_error ____________________
self = <failure_demo.TestMoreErrors instance at 0x158c998>
self = <failure_demo.TestMoreErrors instance at 0x101367f80>
def test_z1_unpack_error(self):
l = []
@@ -462,7 +462,7 @@ get on the terminal - we are working on that):
failure_demo.py:179: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors instance at 0x15854d0>
self = <failure_demo.TestMoreErrors instance at 0x101363dd0>
def test_z2_type_error(self):
l = 3
@@ -472,20 +472,19 @@ get on the terminal - we are working on that):
failure_demo.py:183: TypeError
______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors instance at 0x14b65a8>
self = <failure_demo.TestMoreErrors instance at 0x101364bd8>
def test_startswith(self):
s = "123"
g = "456"
> assert s.startswith(g)
E assert False
E + where False = <built-in method startswith of str object at 0x14902a0>('456')
E + where <built-in method startswith of str object at 0x14902a0> = '123'.startswith
E assert <built-in method startswith of str object at 0x1013524e0>('456')
E + where <built-in method startswith of str object at 0x1013524e0> = '123'.startswith
failure_demo.py:188: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors instance at 0x158d518>
self = <failure_demo.TestMoreErrors instance at 0x101363fc8>
def test_startswith_nested(self):
def f():
@@ -493,39 +492,36 @@ get on the terminal - we are working on that):
def g():
return "456"
> assert f().startswith(g())
E assert False
E + where False = <built-in method startswith of str object at 0x14902a0>('456')
E + where <built-in method startswith of str object at 0x14902a0> = '123'.startswith
E + where '123' = <function f at 0x15806e0>()
E + and '456' = <function g at 0x1580aa0>()
E assert <built-in method startswith of str object at 0x1013524e0>('456')
E + where <built-in method startswith of str object at 0x1013524e0> = '123'.startswith
E + where '123' = <function f at 0x10132c500>()
E + and '456' = <function g at 0x10132c8c0>()
failure_demo.py:195: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors instance at 0x1593440>
self = <failure_demo.TestMoreErrors instance at 0x1013696c8>
def test_global_func(self):
> assert isinstance(globf(42), float)
E assert False
E + where False = isinstance(43, float)
E + where 43 = globf(42)
E assert isinstance(43, float)
E + where 43 = globf(42)
failure_demo.py:198: AssertionError
_______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors instance at 0x15952d8>
self = <failure_demo.TestMoreErrors instance at 0x1013671b8>
def test_instance(self):
self.x = 6*7
> assert self.x != 42
E assert 42 != 42
E + where 42 = 42
E + where 42 = <failure_demo.TestMoreErrors instance at 0x15952d8>.x
E + where 42 = <failure_demo.TestMoreErrors instance at 0x1013671b8>.x
failure_demo.py:202: AssertionError
_______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors instance at 0x1593758>
self = <failure_demo.TestMoreErrors instance at 0x101366560>
def test_compare(self):
> assert globf(10) < 5
@@ -535,7 +531,7 @@ get on the terminal - we are working on that):
failure_demo.py:205: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors instance at 0x157cd88>
self = <failure_demo.TestMoreErrors instance at 0x1013613b0>
def test_try_finally(self):
x = 1
@@ -544,4 +540,4 @@ get on the terminal - we are working on that):
E assert 1 == 0
failure_demo.py:210: AssertionError
======================== 39 failed in 0.23 seconds =========================
======================== 39 failed in 0.39 seconds =========================

View File

@@ -1,10 +1,10 @@
.. highlightlang:: python
basic patterns and examples
Basic patterns and examples
==========================================================
pass different values to a test function, depending on command line options
Pass different values to a test function, depending on command line options
----------------------------------------------------------------------------
.. regendoc:wipe
@@ -53,7 +53,7 @@ Let's run this without supplying our new command line option::
test_sample.py:6: AssertionError
----------------------------- Captured stdout ------------------------------
first
1 failed in 0.03 seconds
1 failed in 0.02 seconds
And now with supplying a command line option::
@@ -85,7 +85,7 @@ next example or refer to :ref:`mysetup` for more information
on real-life examples.
dynamically adding command line options
Dynamically adding command line options
--------------------------------------------------------------
.. regendoc:wipe
@@ -109,17 +109,17 @@ directory with the above conftest.py::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
gw0 I / gw1 I / gw2 I / gw3 I
gw0 [0] / gw1 [0] / gw2 [0] / gw3 [0]
platform darwin -- Python 2.7.1 -- pytest-2.1.3
gw0 I
gw0 [0]
scheduling tests via LoadScheduling
============================= in 0.52 seconds =============================
============================= in 0.48 seconds =============================
.. _`excontrolskip`:
control skipping of tests according to command line option
Control skipping of tests according to command line option
--------------------------------------------------------------
.. regendoc:wipe
@@ -156,27 +156,27 @@ and when running it will see a skipped "slow" test::
$ py.test -rs # "-rs" means report details on the little 's'
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
test_module.py .s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-42/conftest.py:9: need --runslow option to run
SKIP [1] /Users/hpk/tmp/doc-exec-172/conftest.py:9: need --runslow option to run
=================== 1 passed, 1 skipped in 0.01 seconds ====================
=================== 1 passed, 1 skipped in 0.02 seconds ====================
Or run it including the ``slow`` marked test::
$ py.test --runslow
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 2 items
test_module.py ..
========================= 2 passed in 0.01 seconds =========================
========================= 2 passed in 0.02 seconds =========================
writing well integrated assertion helpers
Writing well integrated assertion helpers
--------------------------------------------------
.. regendoc:wipe
@@ -261,7 +261,7 @@ which will add the string to the test header accordingly::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
project deps: mylib-1.1
collecting ... collected 0 items
@@ -284,7 +284,7 @@ which will add info only when run with "--v"::
$ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
info1: did you know that ...
did you?
collecting ... collected 0 items
@@ -295,7 +295,7 @@ and nothing when run plainly::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 0 items
============================= in 0.00 seconds =============================

View File

@@ -47,19 +47,23 @@ customizable testing frameworks for Python. However,
``py.test`` still uses many metaprogramming techniques and
reading its source is thus likely not something for Python beginners.
A second "magic" issue arguably the assert statement re-intepreation:
When an ``assert`` statement fails, py.test re-interprets the expression
to show intermediate values if a test fails. If your expression
has side effects (better to avoid them anyway!) the intermediate values
may not be the same, obfuscating the initial error (this is also
explained at the command line if it happens).
``py.test --no-assert`` turns off assert re-interpretation.
A second "magic" issue arguably the assert statement debugging feature. When
loading test modules py.test rewrites the source code of assert statements. When
a rewritten assert statement fails, its error message has more information than
the original. py.test also has a second assert debugging technique. When an
``assert`` statement that was missed by the rewriter fails, py.test
re-interprets the expression to show intermediate values if a test fails. This
second technique suffers from caveat that the rewriting does not: If your
expression has side effects (better to avoid them anyway!) the intermediate
values may not be the same, confusing the reinterpreter and obfuscating the
initial error (this is also explained at the command line if it happens).
You can turn off all assertion debugging with ``py.test --assertmode=off``.
.. _`py namespaces`: index.html
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
function arguments, parametrized tests and setup
Function arguments, parametrized tests and setup
-------------------------------------------------------
.. _funcargs: test/funcargs.html

View File

@@ -61,7 +61,7 @@ Running the test looks like this::
$ py.test test_simplefactory.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_simplefactory.py F
@@ -167,7 +167,7 @@ Running this::
$ py.test test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 10 items
test_example.py .........F
@@ -182,7 +182,7 @@ Running this::
E assert 9 < 9
test_example.py:7: AssertionError
==================== 1 failed, 9 passed in 0.03 seconds ====================
==================== 1 failed, 9 passed in 0.04 seconds ====================
Note that the ``pytest_generate_tests(metafunc)`` hook is called during
the test collection phase which is separate from the actual test running.
@@ -190,7 +190,7 @@ Let's just look at what is collected::
$ py.test --collectonly test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 10 items
<Module 'test_example.py'>
<Function 'test_func[0]'>
@@ -204,19 +204,37 @@ Let's just look at what is collected::
<Function 'test_func[8]'>
<Function 'test_func[9]'>
============================= in 0.00 seconds =============================
============================= in 0.01 seconds =============================
If you want to select only the run with the value ``7`` you could do::
$ py.test -v -k 7 test_example.py # or -k test_func[7]
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
collecting ... collected 10 items
test_example.py:6: test_func[0] PASSED
test_example.py:6: test_func[1] PASSED
test_example.py:6: test_func[2] PASSED
test_example.py:6: test_func[3] PASSED
test_example.py:6: test_func[4] PASSED
test_example.py:6: test_func[5] PASSED
test_example.py:6: test_func[6] PASSED
test_example.py:6: test_func[7] PASSED
test_example.py:6: test_func[8] PASSED
test_example.py:6: test_func[9] FAILED
======================== 9 tests deselected by '7' =========================
================== 1 passed, 9 deselected in 0.01 seconds ==================
================================= FAILURES =================================
_______________________________ test_func[9] _______________________________
numiter = 9
def test_func(numiter):
> assert numiter < 9
E assert 9 < 9
test_example.py:7: AssertionError
==================== 1 failed, 9 passed in 0.05 seconds ====================
You might want to look at :ref:`more parametrization examples <paramexamples>`.

View File

@@ -1,7 +1,13 @@
Installation and Getting Started
===================================
**Compatibility**: Python 2.4-3.2, Jython, PyPy on Unix/Posix and Windows
**Pythons**: Python 2.4-3.2, Jython, PyPy
**Platforms**: Unix/Posix and Windows
**PyPI package name**: `pytest <http://pypi.python.org/pypi/pytest>`_
**documentation as PDF**: `download latest <http://pytest.org/latest/pytest.pdf>`_
.. _`getstarted`:
@@ -10,16 +16,16 @@ Installation
Installation options::
easy_install -U pytest # or
pip install -U pytest
pip install -U pytest # or
easy_install -U pytest
To check your installation has installed the correct version::
$ py.test --version
This is py.test version 2.0.3, imported from /home/hpk/p/pytest/pytest.pyc
This is py.test version 2.1.3, imported from /Users/hpk/p/pytest/pytest.pyc
setuptools registered plugins:
pytest-xdist-1.6.dev3 at /home/hpk/p/pytest-xdist/xdist/plugin.pyc
pytest-incremental-0.1.0 at /home/hpk/venv/0/lib/python2.6/site-packages/pytest_incremental.pyc
pytest-cov-1.4 at /Users/hpk/venv/0/lib/python2.7/site-packages/pytest_cov.pyc
pytest-xdist-1.6 at /Users/hpk/venv/0/lib/python2.7/site-packages/xdist/plugin.pyc
If you get an error checkout :ref:`installation issues`.
@@ -41,7 +47,7 @@ That's it. You can execute the test function now::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_sample.py F
@@ -61,22 +67,12 @@ py.test found the ``test_answer`` function by following :ref:`standard test disc
.. note::
You can simply use the ``assert`` statement for asserting
expectations because intermediate values will be presented to you.
This is arguably easier than learning all the `the JUnit legacy
methods`_.
You can simply use the ``assert`` statement for asserting test
expectations. pytest's :ref:`assert introspection` will intelligently
report intermediate values of the assert expression freeing
you from the need to learn the many names of `JUnit legacy methods`_.
However, there remains one caveat to using simple asserts: your
assertion expression should better be side-effect free. Because
after an assertion failed py.test will re-evaluate the expression
in order to present intermediate values. You will get a nice warning
and you can easily fix it: compute the value ahead of the assert and
then do the assertion. Or maybe just use the assert "explicit message"
syntax::
assert expr, "message" # show "message" if expr is not True
.. _`the JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
.. _`JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
.. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
@@ -131,13 +127,12 @@ run the module by passing its filename::
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass instance at 0x142c320>
self = <test_class.TestClass instance at 0x1013167a0>
def test_two(self):
x = "hello"
> assert hasattr(x, 'check')
E assert False
E + where False = hasattr('hello', 'check')
E assert hasattr('hello', 'check')
test_class.py:8: AssertionError
1 failed, 1 passed in 0.03 seconds
@@ -169,7 +164,7 @@ before performing the test function call. Let's just run it::
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmpdir = local('/tmp/pytest-10/test_needsfiles0')
tmpdir = local('/Users/hpk/tmp/pytest-93/test_needsfiles0')
def test_needsfiles(tmpdir):
print tmpdir
@@ -178,8 +173,8 @@ before performing the test function call. Let's just run it::
test_tmpdir.py:3: AssertionError
----------------------------- Captured stdout ------------------------------
/tmp/pytest-10/test_needsfiles0
1 failed in 0.13 seconds
/Users/hpk/tmp/pytest-93/test_needsfiles0
1 failed in 0.04 seconds
Before the test runs, a unique-per-test-invocation temporary directory
was created. More info at :ref:`tmpdir handling`.
@@ -188,7 +183,7 @@ You can find out what kind of builtin :ref:`funcargs` exist by typing::
py.test --funcargs # shows builtin and custom function arguments
where to go next
Where to go next
-------------------------------------
Here are a few suggestions where to go next:
@@ -206,10 +201,15 @@ Known Installation issues
easy_install or pip not found?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Consult `distribute docs`_ to install the ``easy_install``
tool on your machine. You may also use the older
`setuptools`_ project but it lacks bug fixes and does not
work on Python3. If you use Python2 you may also install pip_.
.. _`install pip`: http://www.pip-installer.org/en/latest/index.html
`Install pip`_ for a state of the art python package installer.
Or consult `distribute docs`_ to install the ``easy_install``
tool on your machine.
You may also use the older `setuptools`_ project but it lacks bug fixes
and does not work on Python3.
py.test not found on Windows despite installation?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

View File

@@ -53,7 +53,7 @@ You can tell people to download the script and then e.g. run it like this::
Integrating with distutils / ``python setup.py test``
--------------------------------------------------------
You can easily integrate test runs into your distutils or
You can integrate test runs into your distutils or
setuptools based project. Use the `genscript method`_
to generate a standalone py.test script::
@@ -106,9 +106,9 @@ Conventions for Python test discovery
* ``Test`` prefixed test classes (without an ``__init__`` method)
* ``test_`` prefixed test functions or methods are test items
For examples of how to cnd cusotmize your test discovery :doc:`example/pythoncollection`.
For examples of how to customize your test discovery :doc:`example/pythoncollection`.
py.test additionally discovers tests using the standard
Within Python modules, py.test also discovers tests using the standard
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
Choosing a test layout / import rules
@@ -138,7 +138,10 @@ py.test supports common test layouts:
test_app.py
...
You can always run your tests by pointing to it::
In both cases you usually need to make sure that ``mypkg`` is importable,
for example by using the setuptools ``python setup.py develop`` method.
You can run your tests by pointing to it::
py.test tests/test_app.py # for external test dirs
py.test mypkg/test/test_app.py # for inlined test dirs
@@ -150,18 +153,27 @@ You can always run your tests by pointing to it::
.. note::
Test modules are imported under their fully qualified name as follows:
If py.test finds a "a/b/test_module.py" test file while
recursing into the filesystem it determines the import name
as follows:
* find ``basedir`` -- this is the first "upward" (towards the root)
directory not containing an ``__init__.py``
directory not containing an ``__init__.py``. If both the ``a``
and ``b`` directories contain an ``__init__.py`` the basedir will
be the parent dir of ``a``.
* perform ``sys.path.insert(0, basedir)`` to make the fully
qualified test module path importable.
* perform ``sys.path.insert(0, basedir)`` to make the test module
importable under the fully qualified import name.
* ``import path.to.test_module`` where the path is determined
by converting path separators into "." files. This means
* ``import a.b.test_module`` where the path is determined
by converting path separators ``/`` into "." characters. This means
you must follow the convention of having directory and file
names map to the import names.
names map directly to the import names.
The reason for this somewhat evolved importing technique is
that in larger projects multiple test modules might import
from each other and thus deriving a canonical import name helps
to avoid surprises such as a test modules getting imported twice.
.. include:: links.inc

View File

@@ -1,17 +1,16 @@
Welcome to ``py.test``!
Welcome to pytest!
=============================================
- **a mature full-featured testing tool**
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython-2.5.1
- :ref:`comprehensive online <toc>` and `PDF documentation <pytest.pdf>`_
- continuously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
- used in :ref:`many projects and organisations <projects>`, in test
suites ranging from 10 to 10s of thousands of tests
- has :ref:`comprehensive documentation <toc>`
- comes with :ref:`tested examples <examples>`
- comes with many :ref:`tested examples <examples>`
- supports :ref:`good integration practises <goodpractises>`
- **provides no-boilerplate testing**
@@ -54,9 +53,4 @@ Welcome to ``py.test``!
.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
.. toctree::
:hidden:
contact.txt
contents.txt

View File

@@ -1,7 +1,7 @@
.. _mark:
mark test functions with attributes
Marking test functions with attributes
=================================================================
.. currentmodule:: _pytest.mark
@@ -88,35 +88,35 @@ You can use the ``-k`` command line option to select tests::
$ py.test -k webtest # running with the above defined examples yields
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 4 items
test_mark.py ..
test_mark_classlevel.py ..
========================= 4 passed in 0.01 seconds =========================
========================= 4 passed in 0.03 seconds =========================
And you can also run all tests except the ones that match the keyword::
$ py.test -k-webtest
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 4 items
===================== 4 tests deselected by '-webtest' =====================
======================= 4 deselected in 0.01 seconds =======================
======================= 4 deselected in 0.02 seconds =======================
Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 4 items
test_mark_classlevel.py ..
==================== 2 tests deselected by 'TestClass' =====================
================== 2 passed, 2 deselected in 0.01 seconds ==================
================== 2 passed, 2 deselected in 0.02 seconds ==================
API reference for mark related objects
------------------------------------------------

View File

@@ -1,5 +1,5 @@
monkeypatching/mocking modules and environments
Monkeypatching/mocking modules and environments
================================================================
.. currentmodule:: _pytest.monkeypatch
@@ -39,7 +39,7 @@ will be undone.
.. background check:
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 0 items
============================= in 0.00 seconds =============================

View File

@@ -1,4 +1,4 @@
Running test written for nose
Running tests written for nose
=======================================
.. include:: links.inc

View File

@@ -5,6 +5,7 @@ Getting started basics
.. toctree::
:maxdepth: 2
index.txt
getting-started.txt
usage.txt
goodpractises.txt

View File

@@ -203,7 +203,7 @@ and their names. It will also print local plugins aka
.. _`cmdunregister`:
deactivate / unregister a plugin by name
Deactivating / unregistering a plugin by name
----------------------------------------------------------------------------
You can prevent plugins from loading or unregister them::
@@ -253,7 +253,7 @@ in the `pytest repository <http://bitbucket.org/hpk42/pytest/>`_.
py.test hook reference
====================================
hook specification and validation
Hook specification and validation
-----------------------------------------
py.test calls hook functions to implement initialization, running,
@@ -264,7 +264,7 @@ specification. However, a hook function may accept *fewer* parameters
by simply not specifying them. If you mistype argument names or the
hook name itself you get an error showing the available arguments.
initialisation, command line and configuration hooks
Initialisation, command line and configuration hooks
--------------------------------------------------------------------
.. currentmodule:: _pytest.hookspec
@@ -277,7 +277,7 @@ initialisation, command line and configuration hooks
.. autofunction:: pytest_configure
.. autofunction:: pytest_unconfigure
generic "runtest" hooks
Generic "runtest" hooks
------------------------------
All all runtest related hooks receive a :py:class:`pytest.Item` object.
@@ -297,7 +297,7 @@ into interactive debugging when a test failure occurs.
The :py:mod:`_pytest.terminal` reported specifically uses
the reporting hook to print information about a test run.
collection hooks
Collection hooks
------------------------------
py.test calls the following hooks for collecting files and directories:
@@ -312,7 +312,7 @@ you can use the following hook:
.. autofunction:: pytest_pycollect_makeitem
reporting hooks
Reporting hooks
------------------------------
Session related reporting hooks:

View File

@@ -5,7 +5,8 @@ Project examples
Here are some examples of projects using py.test (please send notes via :ref:`contact`):
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over `16000 tests <http://test.pypy.org>`_
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over
`16000 tests <http://buildbot.pypy.org/summary?branch=%3Ctrunk%3E>`_
* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
* `tox <http://codespeak.net/tox>`_, virtualenv/Hudson integration tool
* `PIDA <http://pida.co.uk>`_ framework for integrated development
@@ -42,9 +43,10 @@ Some organisations using py.test
-----------------------------------
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
* `Tandberg <http://www.tandberg.com/>`_
* `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Düsseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `cellzome <http://www.cellzome.com/>`_
* `Open End, Gothenborg <http://www.openend.se>`_
* `Laboraratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_

View File

@@ -1,8 +1,8 @@
asserting deprecation and other warnings.
Asserting deprecation and other warnings.
=====================================================
recwarn function argument
The recwarn function argument
------------------------------------
You can use the ``recwarn`` funcarg to assert that code triggers
@@ -24,7 +24,7 @@ The ``recwarn`` function argument provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
ensuring a function triggers a deprecation warning
Ensuring a function triggers a deprecation warning
-------------------------------------------------------
You can also call a global helper for checking

View File

@@ -1,6 +1,6 @@
.. _`skip and xfail`:
skip and xfail: dealing with tests that can not succeed
Skip and xfail: dealing with tests that can not succeed
=====================================================================
If you have test functions that cannot be run on certain platforms
@@ -62,7 +62,7 @@ decorator at module level like this::
def test_function():
...
skip all test functions of a class
Skip all test functions of a class
--------------------------------------
As with all function :ref:`marking <mark>` you can skip test functions at the
@@ -92,7 +92,7 @@ Using multiple "skipif" decorators on a single function is generally fine - it m
.. _xfail:
mark a test function as expected to fail
Mark a test function as expected to fail
-------------------------------------------------------
You can use the ``xfail`` marker to indicate that you
@@ -130,7 +130,7 @@ Running it with the report-on-xfail option gives this output::
example $ py.test -rx xfail_demo.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 6 items
xfail_demo.py xxxxxx
@@ -147,11 +147,11 @@ Running it with the report-on-xfail option gives this output::
XFAIL xfail_demo.py::test_hello6
reason: reason
======================== 6 xfailed in 0.05 seconds =========================
======================== 6 xfailed in 0.11 seconds =========================
.. _`evaluation of skipif/xfail conditions`:
evaluation of skipif/xfail expressions
Evaluation of skipif/xfail expressions
----------------------------------------------------
.. versionadded:: 2.0.2
@@ -174,7 +174,7 @@ which you might have added::
...
imperative xfail from within a test or setup function
Imperative xfail from within a test or setup function
------------------------------------------------------
If you cannot declare xfail-conditions at import time
@@ -186,7 +186,7 @@ within test or setup code. Example::
pytest.xfail("unsupported configuration")
skipping on a missing import dependency
Skipping on a missing import dependency
--------------------------------------------------
You can use the following import helper at module level
@@ -202,7 +202,7 @@ version number of a library::
The version will be read from the specified module's ``__version__`` attribute.
imperative skip from within a test or setup function
Imperative skip from within a test or setup function
------------------------------------------------------
If for some reason you cannot declare skip-conditions

View File

@@ -4,35 +4,40 @@ Talks and Tutorials
.. _`funcargs`: funcargs.html
tutorial examples and blog postings
Tutorial examples and blog postings
---------------------------------------------
.. _`tutorial1 repository`: http://bitbucket.org/hpk42/pytest-tutorial1/
.. _`pycon 2010 tutorial PDF`: http://bitbucket.org/hpk42/pytest-tutorial1/raw/tip/pytest-basic.pdf
basic usage and funcargs:
Basic usage and funcargs:
- `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_
function arguments:
Function arguments:
- :ref:`mysetup`
- `application setup in test functions with funcargs`_
- `monkey patching done right`_ (blog post, consult `monkeypatch
plugin`_ for actual 1.0 API)
test parametrization:
Test parametrization:
- `generating parametrized tests with funcargs`_
- `test generators and cached setup`_
- `parametrizing tests, generalized`_ (blog post)
- `putting test-hooks into local or global plugins`_ (blog post)
distributed testing:
Assertion introspection:
- `(07/2011) Behind the scenes of py.test's new assertion rewriting
<http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_
Distributed testing:
- `simultaneously test your code on all platforms`_ (blog entry)
plugin specific examples:
Plugin specific examples:
- `skipping slow tests by default in py.test`_ (blog entry)
@@ -49,7 +54,7 @@ plugin specific examples:
.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
conference talks and tutorials
Conference talks and tutorials
----------------------------------------
- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):

View File

@@ -1,10 +1,10 @@
.. _`tmpdir handling`:
temporary directories and files
Temporary directories and files
================================================
the 'tmpdir' test function argument
The 'tmpdir' test function argument
-----------------------------------
You can use the ``tmpdir`` function argument which will
@@ -28,7 +28,7 @@ Running this would result in a passed test except for the last
$ py.test test_tmpdir.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_tmpdir.py F
@@ -36,7 +36,7 @@ Running this would result in a passed test except for the last
================================= FAILURES =================================
_____________________________ test_create_file _____________________________
tmpdir = local('/tmp/pytest-11/test_create_file0')
tmpdir = local('/Users/hpk/tmp/pytest-94/test_create_file0')
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
@@ -47,11 +47,11 @@ Running this would result in a passed test except for the last
E assert 0
test_tmpdir.py:7: AssertionError
========================= 1 failed in 0.06 seconds =========================
========================= 1 failed in 0.05 seconds =========================
.. _`base temporary directory`:
the default base temporary directory
The default base temporary directory
-----------------------------------------------
Temporary directories are by default created as sub-directories of

View File

@@ -1,7 +1,7 @@
.. _`unittest.TestCase`:
unittest.TestCase support
Support for unittest.TestCase
=====================================================================
py.test has limited support for running Python `unittest.py style`_ tests.
@@ -24,7 +24,7 @@ Running it yields::
$ py.test test_unittest.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
platform darwin -- Python 2.7.1 -- pytest-2.1.3
collecting ... collected 1 items
test_unittest.py F
@@ -42,7 +42,7 @@ Running it yields::
test_unittest.py:8: AssertionError
----------------------------- Captured stdout ------------------------------
hello
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.04 seconds =========================
.. _`unittest.py style`: http://docs.python.org/library/unittest.html

View File

@@ -7,7 +7,7 @@ Usage and Invocations
.. _cmdline:
calling pytest through ``python -m pytest``
Calling pytest through ``python -m pytest``
-----------------------------------------------------
.. versionadded:: 2.0
@@ -38,7 +38,7 @@ To stop the testing process after the first (N) failures::
py.test -x # stop after first failure
py.test -maxfail=2 # stop after two failures
specifying tests / selecting tests
Specifying tests / selecting tests
---------------------------------------------------
Several test run options::
@@ -98,7 +98,7 @@ can use a helper::
In previous versions you could only enter PDB tracing if
you disable capturing on the command line via ``py.test -s``.
creating JUnitXML format files
Creating JUnitXML format files
----------------------------------------------------
To create result files which can be read by Hudson_ or other Continuous
@@ -108,7 +108,7 @@ integration servers, use this invocation::
to create an XML file at ``path``.
creating resultlog format files
Creating resultlog format files
----------------------------------------------------
To create plain-text machine-readable result files you can issue::
@@ -121,7 +121,7 @@ by the `PyPy-test`_ web page to show test results over several revisions.
.. _`PyPy-test`: http://codespeak.net:8099/summary
send test report to pocoo pastebin service
Sending test report to pocoo pastebin service
-----------------------------------------------------
**Creating a URL for each test failure**::
@@ -138,7 +138,7 @@ for example ``-x`` if you only want to send one particular failure.
Currently only pasting to the http://paste.pocoo.org service is implemented.
calling pytest from Python code
Calling pytest from Python code
----------------------------------------------------
.. versionadded:: 2.0

View File

@@ -1,7 +1,7 @@
.. _xunitsetup:
====================================
extended xUnit style setup fixtures
Extended xUnit style setup fixtures
====================================
.. _`funcargs`: funcargs.html
@@ -16,7 +16,7 @@ before running a test function and ``teardown`` after it has finished.
handling by optionally calling per-module and per-class hooks.
module level setup/teardown
Module level setup/teardown
=============================================
If you have multiple test functions and test classes in a single
@@ -33,7 +33,7 @@ which will usually be called once for all the functions::
with a setup_module method.
"""
class level setup/teardown
Class level setup/teardown
=============================================
Similarly, the following methods are called at class level before
@@ -51,7 +51,7 @@ and after all test methods of the class are called::
with a call to setup_class.
"""
method and function level setup/teardown
Method and function level setup/teardown
=============================================
Similarly, the following methods are called around each method invocation::

View File

@@ -1,36 +1,39 @@
import os, sys
if sys.version_info >= (3,0):
try:
from setuptools import setup
except ImportError:
from distribute_setup import use_setuptools
use_setuptools()
from setuptools import setup
from setuptools import setup
long_description = """
cross-project testing tool for Python.
Platforms: Linux, Win32, OSX
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy-1.5
Bugs and issues: http://bitbucket.org/hpk42/pytest/issues/
Web page: http://pytest.org
(c) Holger Krekel and others, 2004-2010
(c) Holger Krekel and others, 2004-2011
"""
def main():
setup(
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.0.3',
version='2.1.3',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
author='Holger Krekel, Benjamin Peterson, Ronny Pfannschmidt, Floris Bruynooghe and others',
author_email='holger at merlinux.eu',
entry_points= make_entry_points(),
install_requires=['py>1.4.1'],
classifiers=['Development Status :: 5 - Production/Stable',
# the following should be enabled for release
install_requires=['py>=1.4.5'],
classifiers=['Development Status :: 6 - Mature',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: POSIX',
@@ -41,7 +44,7 @@ def main():
'Topic :: Utilities',
'Programming Language :: Python',
'Programming Language :: Python :: 3'],
packages=['_pytest', ],
packages=['_pytest', '_pytest.assertion'],
py_modules=['pytest'],
zip_safe=False,
)

View File

@@ -89,7 +89,7 @@ class TestGeneralUsage:
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
#XXX on jython this fails: "> import import_fails",
"E ImportError: No module named does_not_work",
"E ImportError: No module named *does_not_work*",
])
assert result.ret == 1
@@ -155,7 +155,7 @@ class TestGeneralUsage:
assert result.ret != 0
assert "should be seen" in result.stdout.str()
@pytest.mark.skipif("not hasattr(os, 'symlink')")
@pytest.mark.skipif("not hasattr(py.path.local, 'mksymlinkto')")
def test_chdir(self, testdir):
testdir.tmpdir.join("py").mksymlinkto(py._pydir)
p = testdir.tmpdir.join("main.py")
@@ -231,7 +231,7 @@ class TestGeneralUsage:
res = testdir.runpytest(p)
assert res.ret == 0
res.stdout.fnmatch_lines(["*1 skipped*"])
def test_direct_addressing_selects(self, testdir):
p = testdir.makepyfile("""
def pytest_generate_tests(metafunc):
@@ -259,6 +259,19 @@ class TestGeneralUsage:
if name.startswith("pytest_"):
assert value.__doc__, "no docstring for %s" % name
def test_initialization_error_issue49(self, testdir):
testdir.makeconftest("""
def pytest_configure():
x
""")
result = testdir.runpytest()
assert result.ret == 3 # internal error
result.stderr.fnmatch_lines([
"INTERNAL*pytest_configure*",
"INTERNAL*x*",
])
assert 'sessionstarttime' not in result.stderr.str()
class TestInvocationVariants:
def test_earlyinit(self, testdir):
p = testdir.makepyfile("""
@@ -268,6 +281,7 @@ class TestInvocationVariants:
result = testdir.runpython(p)
assert result.ret == 0
@pytest.mark.xfail("sys.platform.startswith('java')")
def test_pydoc(self, testdir):
for name in ('py.test', 'pytest'):
result = testdir.runpython_c("import %s;help(%s)" % (name, name))
@@ -351,7 +365,7 @@ class TestInvocationVariants:
retcode = testdir.pytestmain(testdir.tmpdir)
assert not retcode
out, err = capsys.readouterr()
def test_invoke_plugin_api(self, capsys):
class MyPlugin:
def pytest_addoption(self, parser):
@@ -361,6 +375,19 @@ class TestInvocationVariants:
out, err = capsys.readouterr()
assert "--myopt" in out
def test_pyargs_importerror(self, testdir, monkeypatch):
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
path = testdir.mkpydir("tpkg")
path.join("test_hello.py").write('raise ImportError')
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
assert result.ret != 0
# FIXME: It would be more natural to match NOT
# "ERROR*file*or*package*not*found*".
result.stdout.fnmatch_lines([
"*collected 0 items*"
])
def test_cmdline_python_package(self, testdir, monkeypatch):
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
path = testdir.mkpydir("tpkg")
@@ -385,7 +412,7 @@ class TestInvocationVariants:
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
assert result.ret != 0
result.stderr.fnmatch_lines([
"*file*not*found*test_hello*",
"*not*found*test_hello*",
])
def test_cmdline_python_package_not_exists(self, testdir):

View File

@@ -53,7 +53,7 @@ def pytest_generate_tests(metafunc):
metafunc.addcall(funcargs={name: val})
elif 'anypython' in metafunc.funcargnames:
for name in ('python2.4', 'python2.5', 'python2.6',
'python2.7', 'python3.1', 'pypy-c', 'jython'):
'python2.7', 'python3.1', 'pypy', 'jython'):
metafunc.addcall(id=name, param=name)
# XXX copied from execnet's conftest.py - needs to be merged
@@ -78,6 +78,8 @@ def getexecutable(name, cache={}):
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
@@ -91,5 +93,5 @@ def pytest_funcarg__anypython(request):
executable = py.path.local(executable)
if executable.check():
return executable
pytest.skip("no %s found" % (name,))
pytest.skip("no suitable %s found" % (name,))
return executable

View File

@@ -0,0 +1,327 @@
"PYTEST_DONT_REWRITE"
import pytest, py
from _pytest.assertion import util
def exvalue():
return py.std.sys.exc_info()[1]
def f():
return 2
def test_not_being_rewritten():
assert "@py_builtins" not in globals()
def test_assert():
try:
assert f() == 3
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_with_explicit_message():
try:
assert f() == 3, "hello"
except AssertionError:
e = exvalue()
assert e.msg == 'hello'
def test_assert_within_finally():
class A:
def f():
pass
excinfo = py.test.raises(TypeError, """
try:
A().f()
finally:
i = 42
""")
s = excinfo.exconly()
assert s.find("takes no argument") != -1
#def g():
# A.f()
#excinfo = getexcinfo(TypeError, g)
#msg = getmsg(excinfo)
#assert msg.find("must be called with A") != -1
def test_assert_multiline_1():
try:
assert (f() ==
3)
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith('assert 2 == 3\n')
def test_assert_multiline_2():
try:
assert (f() == (4,
3)[-1])
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith('assert 2 ==')
def test_in():
try:
assert "hi" in [1, 2]
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith("assert 'hi' in")
def test_is():
try:
assert 1 is 2
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith("assert 1 is 2")
@py.test.mark.skipif("sys.version_info < (2,6)")
def test_attrib():
class Foo(object):
b = 1
i = Foo()
try:
assert i.b == 2
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith("assert 1 == 2")
@py.test.mark.skipif("sys.version_info < (2,6)")
def test_attrib_inst():
class Foo(object):
b = 1
try:
assert Foo().b == 2
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith("assert 1 == 2")
def test_len():
l = list(range(42))
try:
assert len(l) == 100
except AssertionError:
e = exvalue()
s = str(e)
assert s.startswith("assert 42 == 100")
assert "where 42 = len([" in s
def test_assert_non_string_message():
class A:
def __str__(self):
return "hello"
try:
assert 0 == 1, A()
except AssertionError:
e = exvalue()
assert e.msg == "hello"
def test_assert_keyword_arg():
def f(x=3):
return False
try:
assert f(x=5)
except AssertionError:
e = exvalue()
assert "x=5" in e.msg
# These tests should both fail, but should fail nicely...
class WeirdRepr:
def __repr__(self):
return '<WeirdRepr\nsecond line>'
def bug_test_assert_repr():
v = WeirdRepr()
try:
assert v == 1
except AssertionError:
e = exvalue()
assert e.msg.find('WeirdRepr') != -1
assert e.msg.find('second line') != -1
assert 0
def test_assert_non_string():
try:
assert 0, ['list']
except AssertionError:
e = exvalue()
assert e.msg.find("list") != -1
def test_assert_implicit_multiline():
try:
x = [1,2,3]
assert x != [1,
2, 3]
except AssertionError:
e = exvalue()
assert e.msg.find('assert [1, 2, 3] !=') != -1
def test_assert_with_brokenrepr_arg():
class BrokenRepr:
def __repr__(self): 0 / 0
e = AssertionError(BrokenRepr())
if e.msg.find("broken __repr__") == -1:
py.test.fail("broken __repr__ not handle correctly")
def test_multiple_statements_per_line():
try:
a = 1; assert a == 2
except AssertionError:
e = exvalue()
assert "assert 1 == 2" in e.msg
def test_power():
try:
assert 2**3 == 7
except AssertionError:
e = exvalue()
assert "assert (2 ** 3) == 7" in e.msg
class TestView:
def setup_class(cls):
cls.View = pytest.importorskip("_pytest.assertion.oldinterpret").View
def test_class_dispatch(self):
### Use a custom class hierarchy with existing instances
class Picklable(self.View):
pass
class Simple(Picklable):
__view__ = object
def pickle(self):
return repr(self.__obj__)
class Seq(Picklable):
__view__ = list, tuple, dict
def pickle(self):
return ';'.join(
[Picklable(item).pickle() for item in self.__obj__])
class Dict(Seq):
__view__ = dict
def pickle(self):
return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
assert Picklable(123).pickle() == '123'
assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
assert Picklable({1:2}).pickle() == '1!2'
def test_viewtype_class_hierarchy(self):
# Use a custom class hierarchy based on attributes of existing instances
class Operation:
"Existing class that I don't want to change."
def __init__(self, opname, *args):
self.opname = opname
self.args = args
existing = [Operation('+', 4, 5),
Operation('getitem', '', 'join'),
Operation('setattr', 'x', 'y', 3),
Operation('-', 12, 1)]
class PyOp(self.View):
def __viewkey__(self):
return self.opname
def generate(self):
return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
class PyBinaryOp(PyOp):
__view__ = ('+', '-', '*', '/')
def generate(self):
return '%s %s %s' % (self.args[0], self.opname, self.args[1])
codelines = [PyOp(op).generate() for op in existing]
assert codelines == ["4 + 5", "getitem('', 'join')",
"setattr('x', 'y', 3)", "12 - 1"]
@py.test.mark.skipif("sys.version_info < (2,6)")
def test_assert_customizable_reprcompare(monkeypatch):
monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
try:
assert 3 == 4
except AssertionError:
e = exvalue()
s = str(e)
assert "hello" in s
def test_assert_long_source_1():
try:
assert len == [
(None, ['somet text', 'more text']),
]
except AssertionError:
e = exvalue()
s = str(e)
assert 're-run' not in s
assert 'somet text' in s
def test_assert_long_source_2():
try:
assert(len == [
(None, ['somet text', 'more text']),
])
except AssertionError:
e = exvalue()
s = str(e)
assert 're-run' not in s
assert 'somet text' in s
def test_assert_raise_alias(testdir):
testdir.makepyfile("""
"PYTEST_DONT_REWRITE"
import sys
EX = AssertionError
def test_hello():
raise EX("hello"
"multi"
"line")
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*def test_hello*",
"*raise EX*",
"*1 failed*",
])
@pytest.mark.skipif("sys.version_info < (2,5)")
def test_assert_raise_subclass():
class SomeEx(AssertionError):
def __init__(self, *args):
super(SomeEx, self).__init__()
try:
raise SomeEx("hello")
except AssertionError:
s = str(exvalue())
assert 're-run' not in s
assert 'could not determine' in s
def test_assert_raises_in_nonzero_of_object_pytest_issue10():
class A(object):
def __nonzero__(self):
raise ValueError(42)
def __lt__(self, other):
return A()
def __repr__(self):
return "<MY42 object>"
def myany(x):
return True
try:
assert not(myany(A() < 0))
except AssertionError:
e = exvalue()
s = str(e)
assert "<MY42 object> < 0" in s

View File

@@ -2,11 +2,12 @@ import sys
import py, pytest
import _pytest.assertion as plugin
from _pytest.assertion import reinterpret, util
needsnewassert = pytest.mark.skipif("sys.version_info < (2,6)")
def interpret(expr):
return py.code._reinterpret(expr, py.code.Frame(sys._getframe(1)))
return reinterpret.reinterpret(expr, py.code.Frame(sys._getframe(1)))
class TestBinReprIntegration:
pytestmark = needsnewassert
@@ -25,7 +26,7 @@ class TestBinReprIntegration:
self.right = right
mockhook = MockHook()
monkeypatch = request.getfuncargvalue("monkeypatch")
monkeypatch.setattr(py.code, '_reprcompare', mockhook)
monkeypatch.setattr(util, '_reprcompare', mockhook)
return mockhook
def test_pytest_assertrepr_compare_called(self, hook):
@@ -39,15 +40,6 @@ class TestBinReprIntegration:
assert hook.left == [0, 1]
assert hook.right == [0, 2]
def test_configure_unconfigure(self, testdir, hook):
assert hook == py.code._reprcompare
config = testdir.parseconfig()
plugin.pytest_configure(config)
assert hook != py.code._reprcompare
from _pytest.config import pytest_unconfigure
pytest_unconfigure(config)
assert hook == py.code._reprcompare
def callequal(left, right):
return plugin.pytest_assertrepr_compare('==', left, right)
@@ -119,6 +111,14 @@ class TestAssert_reprcompare:
expl = ' '.join(callequal('foo', 'bar'))
assert 'raised in repr()' not in expl
@needsnewassert
def test_rewritten(testdir):
testdir.makepyfile("""
def test_rewritten():
assert "@py_builtins" in globals()
""")
assert testdir.runpytest().ret == 0
def test_reprcompare_notin():
detail = plugin.pytest_assertrepr_compare('not in', 'foo', 'aaafoobbb')[1:]
assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++']
@@ -158,8 +158,30 @@ def test_sequence_comparison_uses_repr(testdir):
"*E*'y'*",
])
@needsnewassert
def test_assertrepr_loaded_per_dir(testdir):
testdir.makepyfile(test_base=['def test_base(): assert 1 == 2'])
a = testdir.mkdir('a')
a_test = a.join('test_a.py')
a_test.write('def test_a(): assert 1 == 2')
a_conftest = a.join('conftest.py')
a_conftest.write('def pytest_assertrepr_compare(): return ["summary a"]')
b = testdir.mkdir('b')
b_test = b.join('test_b.py')
b_test.write('def test_b(): assert 1 == 2')
b_conftest = b.join('conftest.py')
b_conftest.write('def pytest_assertrepr_compare(): return ["summary b"]')
result = testdir.runpytest()
result.stdout.fnmatch_lines([
'*def test_base():*',
'*E*assert 1 == 2*',
'*def test_a():*',
'*E*assert summary a*',
'*def test_b():*',
'*E*assert summary b*'])
def test_functional(testdir):
def test_assertion_options(testdir):
testdir.makepyfile("""
def test_hello():
x = 3
@@ -167,8 +189,24 @@ def test_functional(testdir):
""")
result = testdir.runpytest()
assert "3 == 4" in result.stdout.str()
result = testdir.runpytest("--no-assert")
assert "3 == 4" not in result.stdout.str()
off_options = (("--no-assert",),
("--nomagic",),
("--no-assert", "--nomagic"),
("--assert=plain",),
("--assert=plain", "--no-assert"),
("--assert=plain", "--nomagic"),
("--assert=plain", "--no-assert", "--nomagic"))
for opt in off_options:
result = testdir.runpytest(*opt)
assert "3 == 4" not in result.stdout.str()
def test_old_assert_mode(testdir):
testdir.makepyfile("""
def test_in_old_mode():
assert "@py_builtins" not in globals()
""")
result = testdir.runpytest("--assert=reinterp")
assert result.ret == 0
def test_triple_quoted_string_issue113(testdir):
testdir.makepyfile("""
@@ -210,14 +248,14 @@ def test_traceback_failure(testdir):
"*test_traceback_failure.py:4: AssertionError"
])
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names")
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" )
def test_warn_missing(testdir):
p1 = testdir.makepyfile("")
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
result.stderr.fnmatch_lines([
"*WARNING*assertion*",
"*WARNING*assert statements are not executed*",
])
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--no-assert")
result.stderr.fnmatch_lines([
"*WARNING*assertion*",
"*WARNING*assert statements are not executed*",
])

View File

@@ -0,0 +1,376 @@
import sys
import zipfile
import py
import pytest
ast = pytest.importorskip("ast")
if sys.platform.startswith("java"):
# XXX should be xfail
pytest.skip("assert rewrite does currently not work on jython")
from _pytest.assertion import util
from _pytest.assertion.rewrite import rewrite_asserts
def setup_module(mod):
mod._old_reprcompare = util._reprcompare
py.code._reprcompare = None
def teardown_module(mod):
util._reprcompare = mod._old_reprcompare
del mod._old_reprcompare
def rewrite(src):
tree = ast.parse(src)
rewrite_asserts(tree)
return tree
def getmsg(f, extra_ns=None, must_pass=False):
"""Rewrite the assertions in f, run it, and get the failure message."""
src = '\n'.join(py.code.Code(f).source().lines)
mod = rewrite(src)
code = compile(mod, "<test>", "exec")
ns = {}
if extra_ns is not None:
ns.update(extra_ns)
py.builtin.exec_(code, ns)
func = ns[f.__name__]
try:
func()
except AssertionError:
if must_pass:
pytest.fail("shouldn't have raised")
s = str(sys.exc_info()[1])
if not s.startswith("assert"):
return "AssertionError: " + s
return s
else:
if not must_pass:
pytest.fail("function didn't raise at all")
class TestAssertionRewrite:
def test_place_initial_imports(self):
s = """'Doc string'\nother = stuff"""
m = rewrite(s)
assert isinstance(m.body[0], ast.Expr)
assert isinstance(m.body[0].value, ast.Str)
for imp in m.body[1:3]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 2
assert imp.col_offset == 0
assert isinstance(m.body[3], ast.Assign)
s = """from __future__ import with_statement\nother_stuff"""
m = rewrite(s)
assert isinstance(m.body[0], ast.ImportFrom)
for imp in m.body[1:3]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 2
assert imp.col_offset == 0
assert isinstance(m.body[3], ast.Expr)
s = """'doc string'\nfrom __future__ import with_statement\nother"""
m = rewrite(s)
assert isinstance(m.body[0], ast.Expr)
assert isinstance(m.body[0].value, ast.Str)
assert isinstance(m.body[1], ast.ImportFrom)
for imp in m.body[2:4]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 3
assert imp.col_offset == 0
assert isinstance(m.body[4], ast.Expr)
s = """from . import relative\nother_stuff"""
m = rewrite(s)
for imp in m.body[0:2]:
assert isinstance(imp, ast.Import)
assert imp.lineno == 1
assert imp.col_offset == 0
assert isinstance(m.body[3], ast.Expr)
def test_dont_rewrite(self):
s = """'PYTEST_DONT_REWRITE'\nassert 14"""
m = rewrite(s)
assert len(m.body) == 2
assert isinstance(m.body[0].value, ast.Str)
assert isinstance(m.body[1], ast.Assert)
assert m.body[1].msg is None
def test_name(self):
def f():
assert False
assert getmsg(f) == "assert False"
def f():
f = False
assert f
assert getmsg(f) == "assert False"
def f():
assert a_global
assert getmsg(f, {"a_global" : False}) == "assert a_global"
def test_assert_already_has_message(self):
def f():
assert False, "something bad!"
assert getmsg(f) == "AssertionError: something bad!"
def test_boolop(self):
def f():
f = g = False
assert f and g
assert getmsg(f) == "assert (False)"
def f():
f = True
g = False
assert f and g
assert getmsg(f) == "assert (True and False)"
def f():
f = False
g = True
assert f and g
assert getmsg(f) == "assert (False)"
def f():
f = g = False
assert f or g
assert getmsg(f) == "assert (False or False)"
def f():
f = g = False
assert not f and not g
getmsg(f, must_pass=True)
def x():
return False
def f():
assert x() and x()
assert getmsg(f, {"x" : x}) == "assert (x())"
def f():
assert False or x()
assert getmsg(f, {"x" : x}) == "assert (False or x())"
def f():
assert 1 in {} and 2 in {}
assert getmsg(f) == "assert (1 in {})"
def f():
x = 1
y = 2
assert x in {1 : None} and y in {}
assert getmsg(f) == "assert (1 in {1: None} and 2 in {})"
def f():
f = True
g = False
assert f or g
getmsg(f, must_pass=True)
def f():
f = g = h = lambda: True
assert f() and g() and h()
getmsg(f, must_pass=True)
def test_short_circut_evaluation(self):
def f():
assert True or explode
getmsg(f, must_pass=True)
def f():
x = 1
assert x == 1 or x == 2
getmsg(f, must_pass=True)
def test_unary_op(self):
def f():
x = True
assert not x
assert getmsg(f) == "assert not True"
def f():
x = 0
assert ~x + 1
assert getmsg(f) == "assert (~0 + 1)"
def f():
x = 3
assert -x + x
assert getmsg(f) == "assert (-3 + 3)"
def f():
x = 0
assert +x + x
assert getmsg(f) == "assert (+0 + 0)"
def test_binary_op(self):
def f():
x = 1
y = -1
assert x + y
assert getmsg(f) == "assert (1 + -1)"
def test_call(self):
def g(a=42, *args, **kwargs):
return False
ns = {"g" : g}
def f():
assert g()
assert getmsg(f, ns) == """assert g()"""
def f():
assert g(1)
assert getmsg(f, ns) == """assert g(1)"""
def f():
assert g(1, 2)
assert getmsg(f, ns) == """assert g(1, 2)"""
def f():
assert g(1, g=42)
assert getmsg(f, ns) == """assert g(1, g=42)"""
def f():
assert g(1, 3, g=23)
assert getmsg(f, ns) == """assert g(1, 3, g=23)"""
def f():
seq = [1, 2, 3]
assert g(*seq)
assert getmsg(f, ns) == """assert g(*[1, 2, 3])"""
def f():
x = "a"
assert g(**{x : 2})
assert getmsg(f, ns) == """assert g(**{'a': 2})"""
def test_attribute(self):
class X(object):
g = 3
ns = {"X" : X, "x" : X()}
def f():
assert not x.g
assert getmsg(f, ns) == """assert not 3
+ where 3 = x.g"""
def f():
x.a = False
assert x.a
assert getmsg(f, ns) == """assert x.a"""
def test_comparisons(self):
def f():
a, b = range(2)
assert b < a
assert getmsg(f) == """assert 1 < 0"""
def f():
a, b, c = range(3)
assert a > b > c
assert getmsg(f) == """assert 0 > 1"""
def f():
a, b, c = range(3)
assert a < b > c
assert getmsg(f) == """assert 1 > 2"""
def f():
a, b, c = range(3)
assert a < b <= c
getmsg(f, must_pass=True)
def f():
a, b, c = range(3)
assert a < b
assert b < c
getmsg(f, must_pass=True)
def test_len(self):
def f():
l = list(range(10))
assert len(l) == 11
assert getmsg(f).startswith("""assert 10 == 11
+ where 10 = len([""")
def test_custom_reprcompare(self, monkeypatch):
def my_reprcompare(op, left, right):
return "42"
monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
def f():
assert 42 < 3
assert getmsg(f) == "assert 42"
def my_reprcompare(op, left, right):
return "%s %s %s" % (left, op, right)
monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
def f():
assert 1 < 3 < 5 <= 4 < 7
assert getmsg(f) == "assert 5 <= 4"
def test_assert_raising_nonzero_in_comparison(self):
def f():
class A(object):
def __nonzero__(self):
raise ValueError(42)
def __lt__(self, other):
return A()
def __repr__(self):
return "<MY42 object>"
def myany(x):
return False
assert myany(A() < 0)
assert "<MY42 object> < 0" in getmsg(f)
def test_formatchar(self):
def f():
assert "%test" == "test"
assert getmsg(f).startswith("assert '%test' == 'test'")
class TestRewriteOnImport:
def test_pycache_is_a_file(self, testdir):
testdir.tmpdir.join("__pycache__").write("Hello")
testdir.makepyfile("""
def test_rewritten():
assert "@py_builtins" in globals()""")
assert testdir.runpytest().ret == 0
def test_zipfile(self, testdir):
z = testdir.tmpdir.join("myzip.zip")
z_fn = str(z)
f = zipfile.ZipFile(z_fn, "w")
try:
f.writestr("test_gum/__init__.py", "")
f.writestr("test_gum/test_lizard.py", "")
finally:
f.close()
z.chmod(256)
testdir.makepyfile("""
import sys
sys.path.append(%r)
import test_gum.test_lizard""" % (z_fn,))
assert testdir.runpytest().ret == 0
def test_readonly(self, testdir):
sub = testdir.mkdir("testing")
sub.join("test_readonly.py").write(
py.builtin._totext("""
def test_rewritten():
assert "@py_builtins" in globals()
""").encode("utf-8"), "wb")
sub.chmod(320)
assert testdir.runpytest().ret == 0
def test_dont_write_bytecode(self, testdir, monkeypatch):
testdir.makepyfile("""
import os
def test_no_bytecode():
assert "__pycache__" in __cached__
assert not os.path.exists(__cached__)
assert not os.path.exists(os.path.dirname(__cached__))""")
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
assert testdir.runpytest().ret == 0
def test_pyc_vs_pyo(self, testdir, monkeypatch):
testdir.makepyfile("""
import pytest
def test_optimized():
"hello"
assert test_optimized.__doc__ is None""")
p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
rootdir=testdir.tmpdir)
tmp = "--basetemp=%s" % p
monkeypatch.setenv("PYTHONOPTIMIZE", "2")
assert testdir.runpybin("py.test", tmp).ret == 0
monkeypatch.undo()
assert testdir.runpybin("py.test", tmp).ret == 1
def test_package(self, testdir):
pkg = testdir.tmpdir.join("pkg")
pkg.mkdir()
pkg.join("__init__.py").ensure()
pkg.join("test_blah.py").write("""
def test_rewritten():
assert "@py_builtins" in globals()""")
assert testdir.runpytest().ret == 0
def test_translate_newlines(self, testdir):
content = "def test_rewritten():\r\n assert '@py_builtins' in globals()"
b = content.encode("utf-8")
testdir.tmpdir.join("test_newlines.py").write(b, "wb")
assert testdir.runpytest().ret == 0

View File

@@ -313,7 +313,7 @@ class TestSession:
def test_collect_topdir(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
id = "::".join([p.basename, "test_func"])
config = testdir.parseconfig(id)
config = testdir.parseconfigure(id)
topdir = testdir.tmpdir
rcol = Session(config)
assert topdir == rcol.fspath
@@ -328,7 +328,7 @@ class TestSession:
def test_collect_protocol_single_function(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
id = "::".join([p.basename, "test_func"])
config = testdir.parseconfig(id)
config = testdir.parseconfigure(id)
topdir = testdir.tmpdir
rcol = Session(config)
assert topdir == rcol.fspath
@@ -363,7 +363,7 @@ class TestSession:
p.basename + "::TestClass::()",
normid,
]:
config = testdir.parseconfig(id)
config = testdir.parseconfigure(id)
rcol = Session(config=config)
rcol.perform_collect()
items = rcol.items
@@ -388,7 +388,7 @@ class TestSession:
""" % p.basename)
id = p.basename
config = testdir.parseconfig(id)
config = testdir.parseconfigure(id)
rcol = Session(config)
hookrec = testdir.getreportrecorder(config)
rcol.perform_collect()
@@ -413,7 +413,7 @@ class TestSession:
aaa = testdir.mkpydir("aaa")
test_aaa = aaa.join("test_aaa.py")
p.move(test_aaa)
config = testdir.parseconfig()
config = testdir.parseconfigure()
rcol = Session(config)
hookrec = testdir.getreportrecorder(config)
rcol.perform_collect()
@@ -437,7 +437,7 @@ class TestSession:
p.move(test_bbb)
id = "."
config = testdir.parseconfig(id)
config = testdir.parseconfigure(id)
rcol = Session(config)
hookrec = testdir.getreportrecorder(config)
rcol.perform_collect()
@@ -455,7 +455,7 @@ class TestSession:
def test_serialization_byid(self, testdir):
p = testdir.makepyfile("def test_func(): pass")
config = testdir.parseconfig()
config = testdir.parseconfigure()
rcol = Session(config)
rcol.perform_collect()
items = rcol.items
@@ -476,7 +476,7 @@ class TestSession:
pass
""")
arg = p.basename + ("::TestClass::test_method")
config = testdir.parseconfig(arg)
config = testdir.parseconfigure(arg)
rcol = Session(config)
rcol.perform_collect()
items = rcol.items

View File

@@ -16,6 +16,9 @@ class TestParseIni:
config._preparse([sub])
assert config.inicfg['name'] == 'value'
def test_getcfg_empty_path(self, tmpdir):
cfg = getcfg([''], ['setup.cfg']) #happens on py.test ""
def test_append_parse_args(self, tmpdir):
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
@@ -88,7 +91,7 @@ class TestConfigAPI:
config.trace.root.setwriter(l.append)
config.trace("hello")
assert len(l) == 1
assert l[0] == "[pytest] hello\n"
assert l[0] == "hello [config]\n"
def test_config_getvalue_honours_conftest(self, testdir):
testdir.makepyfile(conftest="x=1")

View File

@@ -89,7 +89,7 @@ class TestConftestValueAccessGlobal:
assert value == 1.5
path = py.path.local(mod.__file__)
assert path.dirpath() == basedir.join("adir", "b")
assert path.purebasename == "conftest"
assert path.purebasename.startswith("conftest")
def test_conftest_in_nonpkg_with_init(tmpdir):
tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")

View File

@@ -586,17 +586,17 @@ class TestHookRelay:
class TestTracer:
def test_simple(self):
from _pytest.core import TagTracer
rootlogger = TagTracer("[my] ")
rootlogger = TagTracer()
log = rootlogger.get("pytest")
log("hello")
l = []
rootlogger.setwriter(l.append)
log("world")
assert len(l) == 1
assert l[0] == "[my] world\n"
assert l[0] == "world [pytest]\n"
sublog = log.get("collection")
sublog("hello")
assert l[1] == "[my] hello\n"
assert l[1] == "hello [pytest:collection]\n"
def test_indent(self):
from _pytest.core import TagTracer
@@ -616,7 +616,7 @@ class TestTracer:
log.root.indent -= 1
log("last")
assert len(l) == 7
names = [x.rstrip()[len(rootlogger.prefix):] for x in l]
names = [x[:x.rfind(' [')] for x in l]
assert names == ['hello', ' line1', ' line2',
' line3', ' line4', ' line5', 'last']

View File

@@ -59,6 +59,21 @@ class TestDoctests:
"*UNEXPECTED*ZeroDivision*",
])
def test_doctest_unex_importerror(self, testdir):
testdir.tmpdir.join("hello.py").write(py.code.Source("""
import asdalsdkjaslkdjasd
"""))
p = testdir.maketxtfile("""
>>> import hello
>>>
""")
result = testdir.runpytest("--doctest-modules")
result.stdout.fnmatch_lines([
"*>>> import hello",
"*UNEXPECTED*ImportError*",
"*import asdals*",
])
def test_doctestmodule(self, testdir):
p = testdir.makepyfile("""
'''
@@ -99,7 +114,7 @@ class TestDoctests:
>>> i + 1
2
""")
result = testdir.runpytest(p)
result = testdir.runpytest(p, "-s")
result.stdout.fnmatch_lines([
'001 >>> i = 0',
'002 >>> i + 1',

View File

@@ -62,3 +62,17 @@ def test_traceconfig(testdir):
"*using*pytest*py*",
"*active plugins*",
])
def test_debug(testdir, monkeypatch):
result = testdir.runpytest("--debug")
assert result.ret == 0
p = testdir.tmpdir.join("pytestdebug.log")
assert "pytest_sessionstart" in p.read()
def test_PYTEST_DEBUG(testdir, monkeypatch):
monkeypatch.setenv("PYTEST_DEBUG", "1")
result = testdir.runpytest()
assert result.ret == 0
result.stderr.fnmatch_lines([
"*registered*PluginManager*"
])

View File

@@ -1,6 +1,6 @@
from xml.dom import minidom
import py, sys
import py, sys, os
def runandparse(testdir, *args):
resultpath = testdir.tmpdir.join("junit.xml")
@@ -39,6 +39,18 @@ class TestPython:
node = dom.getElementsByTagName("testsuite")[0]
assert_attr(node, errors=0, failures=1, skips=3, tests=2)
def test_timing_function(self, testdir):
testdir.makepyfile("""
import time, pytest
def test_sleep():
time.sleep(0.01)
""")
result, dom = runandparse(testdir)
node = dom.getElementsByTagName("testsuite")[0]
tnode = node.getElementsByTagName("testcase")[0]
val = tnode.getAttributeNode("time").value
assert float(val) >= 0.001
def test_setup_error(self, testdir):
testdir.makepyfile("""
def pytest_funcarg__arg(request):
@@ -119,7 +131,14 @@ class TestPython:
assert "Division" in fnode.toxml()
def test_failure_function(self, testdir):
testdir.makepyfile("def test_fail(): raise ValueError(42)")
testdir.makepyfile("""
import sys
def test_fail():
print ("hello-stdout")
sys.stderr.write("hello-stderr\\n")
raise ValueError(42)
""")
result, dom = runandparse(testdir)
assert result.ret
node = dom.getElementsByTagName("testsuite")[0]
@@ -131,6 +150,12 @@ class TestPython:
fnode = tnode.getElementsByTagName("failure")[0]
assert_attr(fnode, message="test failure")
assert "ValueError" in fnode.toxml()
systemout = fnode.nextSibling
assert systemout.tagName == "system-out"
assert "hello-stdout" in systemout.toxml()
systemerr = systemout.nextSibling
assert systemerr.tagName == "system-err"
assert "hello-stderr" in systemerr.toxml()
def test_failure_escape(self, testdir):
testdir.makepyfile("""
@@ -351,3 +376,16 @@ def test_invalid_xml_escape(testdir):
assert '#x%04X' % i in text
for i in valid:
assert chr(i) in text
def test_logxml_path_expansion():
from _pytest.junitxml import LogXML
home_tilde = os.path.normpath(os.path.expanduser('~/test.xml'))
# this is here for when $HOME is not set correct
home_var = os.path.normpath(os.path.expandvars('$HOME/test.xml'))
xml_tilde = LogXML('~/test.xml', None)
assert xml_tilde.logfile == home_tilde
xml_var = LogXML('$HOME/test.xml', None)
assert xml_var.logfile == home_var

View File

@@ -2,6 +2,11 @@ import py, pytest
from _pytest.mark import MarkGenerator as Mark
class TestMark:
def test_markinfo_repr(self):
from _pytest.mark import MarkInfo
m = MarkInfo("hello", (1,2), {})
repr(m)
def test_pytest_exists_in_namespace_all(self):
assert 'mark' in py.test.__all__
assert 'mark' in pytest.__all__

View File

@@ -255,3 +255,19 @@ def test_nose_style_setup_teardown(testdir):
result.stdout.fnmatch_lines([
"*2 passed*",
])
def test_nose_setup_ordering(testdir):
testdir.makepyfile("""
def setup_module(mod):
mod.visited = True
class TestClass:
def setup(self):
assert visited
def test_first(self):
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 passed*",
])

View File

@@ -144,6 +144,19 @@ class TestPDB:
child.sendeof()
child.wait()
def test_pdb_used_in_generate_tests(self, testdir):
p1 = testdir.makepyfile("""
import pytest
def pytest_generate_tests(metafunc):
pytest.set_trace()
x = 5
def test_foo(a):
pass
""")
child = testdir.spawn_pytest(str(p1))
child.expect("x = 5")
child.sendeof()
child.wait()
def test_pdb_collection_failure_is_shown(self, testdir):
p1 = testdir.makepyfile("""xxx """)
result = testdir.runpytest("--pdb", p1)

View File

@@ -689,11 +689,11 @@ class TestRequest:
def test_func(something): pass
""")
req = funcargs.FuncargRequest(item)
req.config._setupstate.prepare(item) # XXX
req._pyfuncitem.session._setupstate.prepare(item) # XXX
req._fillfuncargs()
# successively check finalization calls
teardownlist = item.getparent(pytest.Module).obj.teardownlist
ss = item.config._setupstate
ss = item.session._setupstate
assert not teardownlist
ss.teardown_exact(item)
print(ss.stack)
@@ -818,11 +818,11 @@ class TestRequestCachedSetup:
ret1 = req1.cached_setup(setup, teardown, scope="function")
assert l == ['setup']
# artificial call of finalizer
req1.config._setupstate._callfinalizers(item1)
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
assert l == ["setup", "teardown"]
ret2 = req1.cached_setup(setup, teardown, scope="function")
assert l == ["setup", "teardown", "setup"]
req1.config._setupstate._callfinalizers(item1)
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
assert l == ["setup", "teardown", "setup", "teardown"]
def test_request_cached_setup_two_args(self, testdir):

View File

@@ -186,7 +186,7 @@ def test_setup_fails_again_on_all_tests(testdir):
])
assert "passed" not in result.stdout.str()
def test_setup_funcarg_setup_not_called_if_outer_scope_fails(testdir):
def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
p = testdir.makepyfile("""
import pytest
def setup_module(mod):

View File

@@ -1,4 +1,5 @@
import pytest
import sys
from _pytest.skipping import MarkEvaluator, folded_skips
from _pytest.skipping import pytest_runtest_setup
@@ -471,6 +472,21 @@ def test_reportchars(testdir):
"SKIP*four*",
])
def test_reportchars_error(testdir):
testdir.makepyfile(
conftest="""
def pytest_runtest_teardown():
assert 0
""",
test_simple="""
def test_foo():
pass
""")
result = testdir.runpytest('-rE')
result.stdout.fnmatch_lines([
'ERROR*test_foo*',
])
@pytest.mark.xfail("hasattr(sys, 'pypy_version_info')")
def test_errors_in_xfail_skip_expressions(testdir):
testdir.makepyfile("""
@@ -486,6 +502,10 @@ def test_errors_in_xfail_skip_expressions(testdir):
pass
""")
result = testdir.runpytest()
markline = " ^"
if sys.platform.startswith("java"):
# XXX report this to java
markline = "*" + markline[8:]
result.stdout.fnmatch_lines([
"*ERROR*test_nameerror*",
"*evaluating*skipif*expression*",
@@ -493,7 +513,7 @@ def test_errors_in_xfail_skip_expressions(testdir):
"*ERROR*test_syntax*",
"*evaluating*xfail*expression*",
" syntax error",
" ^",
markline,
"SyntaxError: invalid syntax",
"*1 pass*2 error*",
])

View File

@@ -167,6 +167,19 @@ class TestTerminal:
])
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
def test_keyboard_in_sessionstart(self, testdir):
testdir.makeconftest("""
def pytest_sessionstart():
raise KeyboardInterrupt
""")
p = testdir.makepyfile("""
def test_foobar():
pass
""")
result = testdir.runpytest()
assert result.ret == 2
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
class TestCollectonly:
@@ -245,10 +258,13 @@ class TestCollectonly:
def test_repr_python_version(monkeypatch):
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
assert repr_pythonversion() == "2.5.1-final-0"
py.std.sys.version_info = x = (2,3)
assert repr_pythonversion() == str(x)
try:
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
assert repr_pythonversion() == "2.5.1-final-0"
py.std.sys.version_info = x = (2,3)
assert repr_pythonversion() == str(x)
finally:
monkeypatch.undo() # do this early as pytest can get confused
class TestFixtureReporting:
def test_setup_fixture_error(self, testdir):
@@ -508,21 +524,6 @@ def test_traceconfig(testdir, monkeypatch):
])
assert result.ret == 0
def test_debug(testdir, monkeypatch):
result = testdir.runpytest("--debug")
result.stderr.fnmatch_lines([
"*pytest_sessionstart*session*",
])
assert result.ret == 0
def test_PYTEST_DEBUG(testdir, monkeypatch):
monkeypatch.setenv("PYTEST_DEBUG", "1")
result = testdir.runpytest()
assert result.ret == 0
result.stderr.fnmatch_lines([
"*registered*PluginManager*"
])
class TestGenericReporting:
""" this test class can be subclassed with a different option
@@ -533,7 +534,7 @@ class TestGenericReporting:
result = testdir.runpytest(*option.args)
result.stdout.fnmatch_lines([
"> import xyz",
"E ImportError: No module named xyz",
"E ImportError: No module named *xyz*",
"*1 error*",
])

View File

@@ -30,9 +30,11 @@ commands=
changedir=.
basepython=python2.6
deps=:pypi:twisted
:pypi:pexpect
py>=1.4.5.dev1
commands=
py.test -rsxf \
--junitxml={envlogdir}/junit-{envname}.xml [testing/test_unittest.py]
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
[testenv:doctest]
changedir=.
commands=py.test --doctest-modules _pytest
@@ -64,13 +66,13 @@ deps=py>=1.4.0
[testenv:jython]
changedir=testing
commands=
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
{envpython} {envbindir}/py.test-jython \
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml []
[pytest]
minversion=2.0
plugins=pytester
#addopts= -rxf --pyargs --doctest-modules --ignore=.tox
addopts= -rxs #--pyargs --doctest-modules --ignore=.tox
rsyncdirs=tox.ini pytest.py _pytest testing
python_files=test_*.py *_test.py
python_classes=Test Acceptance