Compare commits

...

392 Commits
2.0.0 ... 2.2.2

Author SHA1 Message Date
holger krekel
87b4cb283f regen docs / examples for 2.2.2 2012-02-05 18:33:04 -05:00
holger krekel
83505b790d preparing release 2.2.2 2012-02-05 17:32:01 -05:00
Ronny Pfannschmidt
2ca6d9f039 no longer check if indirect metafunc.parametrize params are funcarg names 2012-02-03 16:54:00 +01:00
Ronny Pfannschmidt
87b8769680 readd CHANGELOG, since i accidentially killed it with mq 2012-02-03 17:11:42 +01:00
Ronny Pfannschmidt
78e7d7aed0 more quit collectonly shows only files and the number of tests in them 2012-02-03 16:56:06 +01:00
Ronny Pfannschmidt
68b353be0d create session.items before preparse, so collectonly wont break on preparse fail, fixes #115 2012-02-03 16:33:32 +01:00
holger krekel
a756dc8106 fix issue118 - typo 2012-02-02 06:24:12 -05:00
holger krekel
604e27658c add CHANGELOG for last commit 2012-02-01 09:06:38 -05:00
holger krekel
dfa273dc25 fix issue177 - actually perform session scope finalization 2012-02-01 08:52:34 -05:00
Ronny Pfannschmidt
5263656df6 kwarg support for reports, so xdist can deserialized extended reports 2012-01-20 19:50:45 +01:00
holger krekel
d88fe07377 fix link to pdf from contents page 2012-01-19 20:53:21 +01:00
holger krekel
2e23057804 remove nonsennse part of commit related to "mp" shortcut.
I wonder if introducing "mp" as a shortcut to monkeypatch
is a good idea, actually :)
2012-01-06 20:40:14 +00:00
holger krekel
303f49a5ad bump version, mention "mp" also in the docs and changelog 2012-01-06 20:37:18 +00:00
Ralf Schmitt
adbbd164ff update documentation for the new monkeypatch.chdir method 2012-01-06 15:25:57 +01:00
Ralf Schmitt
93424b0f9c add monkeypatch.chdir method 2012-01-04 12:43:19 +01:00
Ralf Schmitt
fb7706d4c7 make sure calling undo a second time doesn't change sys.path 2012-01-04 12:42:23 +01:00
Ralf Schmitt
4131923c0f test that a second undo doesn't change sys.path
also use a 'mp' funcarg that restores sys.path and the current working
directory in preparation for the monkeypatch.chdir method.
2012-01-04 12:40:57 +01:00
holger krekel
7b95af2400 only run test_unittest.py for the twisted/trial config 2011-12-28 17:43:56 +00:00
holger krekel
eb6481c663 fix trial test failure and simplify todo->xfail conversion 2011-12-28 17:35:38 +00:00
holger krekel
c126cac98d fix unittest/marker integration 2011-12-28 16:47:08 +00:00
holger krekel
e3a8b1e062 bump version 2011-12-28 15:49:35 +00:00
holger krekel
fa6d5bd15b work around an apparent python2.4/python2.5 bug with subprocess.Popen,
causing jenkins failures.  Apparently "os.environ.popitem(name, None)"
is not the same as::

    try:
        del os.environ[name]
    except KeyError:
        pass
2011-12-28 15:49:13 +00:00
holger krekel
f2c8a837af fix issue106: allow parametrize to be applied per-class/per-module 2011-12-28 15:47:19 +00:00
holger krekel
ccc1b21ebd internally keep multiple applications of the same markers as separate
entities such that the new iter() API can iterate over pytest.mark
function attributes, getting all such applications.  See added example
for more info.
2011-12-28 15:47:18 +00:00
holger krekel
85f2a78005 fix wrongly committed line 2011-12-28 07:57:19 +00:00
holger krekel
e21202b730 fix Jenkins test failures 2011-12-27 21:03:15 +00:00
holger krekel
dc0535f7d5 fix typo, thanks jurko 2011-12-20 14:12:12 +00:00
holger krekel
f2791988f9 fix issue102: report more useful errors and hints for when a
test directory was renamed and some pyc/__pycache__ remain
2011-12-20 12:20:59 +00:00
Benjamin Peterson
8e83af1c33 fix spacing 2011-12-19 14:23:39 -05:00
Benjamin Peterson
268c051eba propogate current PYTHONPATH 2011-12-19 12:02:07 -05:00
Benjamin Peterson
03cb37b1eb use an exception more consistent across python versions 2011-12-19 11:56:22 -05:00
holger krekel
d5c3265763 fix issue101: wrong args to unittest.TestCase test function now
produce better output
2011-12-18 23:01:39 +00:00
holger krekel
13e0340350 use newer distribute_setup.py 2011-12-18 19:01:43 +00:00
holger krekel
5093d8b925 fix test to actually mean something useful (thanks Jurko) 2011-12-18 10:56:39 +00:00
holger krekel
40187ec9bb robustify monkeypatch 2011-12-16 22:41:23 +00:00
holger krekel
f5f8695587 add 2.2.1 announce to index 2011-12-16 12:12:23 +00:00
holger krekel
27f5213718 Added tag 2.2.1 for changeset 3da8cec6c532 2011-12-16 11:56:44 +00:00
holger krekel
b83a3bcc80 mention pytest-xdist-1.8 2011-12-16 11:43:05 +00:00
holger krekel
3a3f69372f regen docs, prepare release 2.2.1 2011-12-16 10:38:34 +00:00
holger krekel
4a08ee2b74 remove debugging-changes to tox.ini 2011-12-14 11:29:25 +00:00
holger krekel
82ba764bb6 fix unorderable types as reported by Ralf Schmitt 2011-12-14 10:56:51 +00:00
holger krekel
94e31e414a bump version depend on new pylib 2011-12-12 12:45:28 +00:00
holger krekel
a94a6b4282 fix issue99 - internalerror with --resultlog now produce better output.
the fix depends on another change in the py lib which unifies
the output for native and non-native traceback formatting styles
2011-12-10 08:49:21 +00:00
Jurko Gospodnetić
af0edf0d10 Documentation cleanup - corrected typos & minor stylistic changes. 2011-12-05 11:10:48 +01:00
holger krekel
8307270cec yay! now that we have perfect teardowns we don't need some ugly internal hooks anymore. 2011-12-02 21:00:21 +00:00
holger krekel
c4fe622b82 fix issue93 - avoid "delayed" teardowns for distributed testing by
simplifying handling of teardowns.
2011-12-02 21:00:19 +00:00
Ronny Pfannschmidt
b28977fbaf take the skip property of unittest cases and functions into account 2011-12-01 20:17:24 +01:00
Ronny Pfannschmidt
96cb1208d3 use py.xml for generating the junitxml files 2011-12-01 20:08:51 +01:00
Ronny Pfannschmidt
0c8e71faa5 simplify the loop in Node.listchain 2011-12-01 19:36:44 +01:00
holger krekel
d965101f6a add new "parametrize refinement" issue critical for 2.2 series
also cleanup issues a bit and make a note on xUnit/funcargs collaboration
2011-12-01 12:23:28 +00:00
holger krekel
d15ee2fb87 some updates, add fslayout idea 2011-12-01 10:51:43 +00:00
holger krekel
826d1e6153 fix docstring for setup.py 2011-11-25 21:34:05 +00:00
holger krekel
50c9e3f654 improve parametrize() docs 2011-11-19 23:45:05 +00:00
holger krekel
59b8ea1746 fix makeinstall 2011-11-18 21:28:14 +00:00
holger krekel
cf02fb60c1 isolate test example run 2011-11-18 21:26:38 +00:00
holger krekel
679d72eedf fix typo in alfredo's name 2011-11-18 19:16:00 +00:00
holger krekel
03b23e2587 Added tag 2.2.0 for changeset 152271036933 2011-11-18 18:48:44 +00:00
holger krekel
48e6823c7a small fix to release announcement 2011-11-18 18:45:15 +00:00
holger krekel
6b4e6eee09 improve release announcement, shift and fix examples a bit. Bump version to 2.2.0 2011-11-18 18:32:11 +00:00
holger krekel
f7648e11d8 another try to properly fix durations sorting (still producing sometimes failing tests, apparently when two durations of a test report are identical) 2011-11-18 17:59:52 +00:00
holger krekel
7bb7d1205c finally fixing a bug that resulted in sometimes-failing duration tests (doh) 2011-11-18 17:35:23 +00:00
holger krekel
a1d41c6811 remove a hack that isn't needed because runtestprotocol now memorizes pending teardowns and thus dist-testing has "exact" teardowns as well 2011-11-18 16:58:21 +00:00
holger krekel
58e0301f87 fix compat with testcases from trial-11.1.0 2011-11-18 16:34:46 +00:00
holger krekel
a5e7b2760d fix issue90 - perform teardown after its actual test function/item. This is implemented by modifying the runtestprotocol to remember "pending" teardowns and call them before the setup of the next item. 2011-11-18 16:01:29 +00:00
Alfredo Deza
efe438d3e8 add padding to durations in rep.when 2011-11-18 09:59:39 -05:00
holger krekel
ec0565fac5 introduce metafunc.parametrize() and @pytest.mark.parametrize with examples. deprecate metafunc.addcall() 2011-11-17 11:09:21 +00:00
holger krekel
48a6a504b6 add changelog entry for issue87 2011-11-15 13:36:02 +00:00
holger krekel
8f55425898 fix issue87 - pastebin option now works with python3 2011-11-15 13:35:06 +00:00
holger krekel
a51e52aee3 fix issue89 apply Daniel Nouri's patch to doctest/--pdb interaction. 2011-11-15 13:28:22 +00:00
Ronny Pfannschmidt
69dfc75572 test and fix pastebin xmlrpc import name missmatch, fixes #87 2011-11-14 17:51:12 +01:00
holger krekel
9d3e51af9f fix issue50 (add a reference to the already implemented -m) and improve release annoucnement and changelog. 2011-11-12 15:10:12 +00:00
holger krekel
f7c1b9087a fix test 2011-11-11 23:18:33 +00:00
holger krekel
36c42b5c15 introduce a new -m mark_expression option 2011-11-11 23:02:06 +00:00
holger krekel
bc8ee95e72 add ini-file "markers" option and a cmdline option "--markers" to show defined markers. Add "skipif", "xfail" etc. to the set of builtin markers shown with the --markers option. 2011-11-11 22:56:11 +00:00
holger krekel
979dfd20f2 add a method to the config object to dynamically add a value to an (line-type) ini-value 2011-11-11 22:56:08 +00:00
holger krekel
67fbd24ebf improve mark.txt document and add new regristration/markers features.
(welcome to documentation driven development)
2011-11-11 22:56:06 +00:00
holger krekel
7f7589afa9 skip pexpect tests on darwin 2011-11-11 21:33:45 +00:00
holger krekel
4f01cda2a7 fix formatting 2011-11-09 11:04:37 +00:00
holger krekel
bd296c796f try to avoid timing/race condition 2011-11-08 23:04:31 +00:00
holger krekel
7144cec580 avoid race condition in test, fix doc link 2011-11-08 22:06:57 +00:00
holger krekel
99a1188287 simplify durations output, no percentage, no "remaining" bits 2011-11-08 20:57:19 +00:00
holger krekel
0b18b6094e fix duration option in case of collection errors 2011-11-08 19:00:25 +00:00
holger krekel
ae53d04780 fix py3 compat 2011-11-08 18:37:08 +00:00
holger krekel
a324826dfd separate out the duration tests 2011-11-08 18:12:16 +00:00
holger krekel
29bf205f3a make --durations also show the execution times of setup/teardown calls. This requires a slight incompatibility - pytest_runtest_logreport now sees setup/teardown reports even if the tests passed. 2011-11-08 17:53:46 +00:00
holger krekel
3b9fd3abd8 introduce --durations=N showing slowest test executions 2011-11-08 17:20:56 +00:00
holger krekel
974e4e3a9d skip the symlink test on windows, win32/py32 does not support it without privs 2011-11-07 22:00:12 +00:00
holger krekel
369b7709f7 use os.symlink to make things work on windows/py32 2011-11-07 21:02:07 +00:00
holger krekel
78438db752 fix py3 failure 2011-11-07 18:28:30 +00:00
holger krekel
a2f4a11301 refine lsof/FD leakage testing and rework test setup and some of pytest own tests. Note that the actual diff to non-test code is small. Also remove some redundant tests (introduced by a copy-paste-error apparently in test_mark.py). 2011-11-07 18:08:41 +00:00
holger krekel
077c468589 don't remove symlinks from temporary directory path - should help with some standard OSX setups 2011-11-06 19:34:02 +00:00
holger krekel
d4fe273b2f fix FD leakage during pytest's own test run and add "--lsof" option to tox default test runs.
the leakage came down to a problematic bit of the stdlib logging module: it takes ownerships of stdout/stderr making it hard for pytest to implement clean capturing.  The current work around is to add some extra code in the setup machinery of pytest's own tests which actually closes sub-FDs.
2011-11-06 15:40:17 +00:00
Ronny Pfannschmidt
761a95e542 fix some of my typos, thanks Arfrever 2011-10-27 07:38:44 +02:00
Ronny Pfannschmidt
5ae04397bd proper tests for issue74, thanks Arfrever 2011-10-26 23:59:22 +02:00
Ronny Pfannschmidt
2c230f910d fix issue74 - propperly filter out missfit names in _tryconvertpyarg 2011-10-26 22:40:08 +02:00
holger krekel
ae54151467 fix issue83 - add a link to already generated list of funcargs 2011-10-21 15:45:56 +02:00
holger krekel
05af53d160 Added tag 2.1.3 for changeset 12a05d59249f 2011-10-18 21:01:37 +02:00
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
holger krekel
07e870dc14 unbump version to retag 2011-05-11 11:54:30 +02:00
holger krekel
ee53b1f591 bump version 2011-05-11 11:23:46 +02:00
holger krekel
63ccec90be regen examples to use 2.0.3 version number 2011-05-01 12:38:56 +02:00
holger krekel
c666aeabbb Added tag 2.0.3 for changeset 49f11dbff725 2011-04-17 23:16:16 +02:00
holger krekel
f8137390c2 add release announcement 2011-04-17 23:16:14 +02:00
holger krekel
2589aa183c Added tag 2.0.3 for changeset c777dcad1665 2011-04-17 23:15:51 +02:00
holger krekel
b316bc6723 Added tag 2.0.3 for changeset 2ef82d82daac 2011-04-17 23:09:33 +02:00
holger krekel
420bbfd9a9 bump version 2011-04-17 23:09:13 +02:00
holger krekel
942ae47cd1 tentatively use internal list for cleanups at unconfigure time - this helps reporting with partially executed pytest_configure() hooks 2011-04-17 12:20:13 +02:00
holger krekel
06ca7090f9 fix issue38 - nicer tracebacks on sessionstart/configure (and other internal/custom hook failures) 2011-04-17 12:20:11 +02:00
Floris Bruynooghe
bc4e4b38a9 Update changelog 2011-04-16 00:47:16 +01:00
Floris Bruynooghe
1c1918eb22 Prevent null-characters from appearing in junitxml's output
The Jenkins XML parser does not deal with null-characters inside the
XML.  This replaces any null character with nothing in the XML output,
which makes no visual difference.
2011-04-16 00:09:25 +01:00
Floris Bruynooghe
60ff2e8529 Allow unicode characters in testdir.makepyfile()
On python2.x text arguments where passed through str, which meant only
ascii-encodable strings could be used.  This uses
py.builting._totext() to keep unicode until it is written out to the
file, which was already UTF-8 encoded.
2011-04-11 23:15:56 +01:00
holger krekel
b7ba4d4e70 shift version string to _pytest directory 2011-03-19 20:13:04 +01:00
holger krekel
3a9788fc6f fix missing reason/name information for skipped tests 2011-03-19 17:59:07 +01:00
holger krekel
cf4e14baed add @classmethod although it's not strictly neccessary for basic cases. 2011-03-16 19:05:28 +01:00
holger krekel
1d40abadc4 offer a semi-internal method to create a config object in subprocesses
(helps pytest-xdist plugin to fix issue34)
2011-03-16 18:00:52 +01:00
holger krekel
ed6d2537bc fix issue33 - no collection error for classes prefixed "test" deriving from object 2011-03-16 16:36:18 +01:00
holger krekel
a9f1f26a39 don't import stuff at genscript import time but rather when it is used 2011-03-12 20:12:19 +01:00
holger krekel
a7131dc911 speed up skipping 2011-03-11 15:43:24 +01:00
holger krekel
6aaaaa8e67 fix issue link 2011-03-11 15:25:37 +01:00
holger krekel
6d06f55543 Added tag 2.0.2 for changeset 84e5c54b7244 2011-03-09 14:02:20 +01:00
holger krekel
527bc472a8 fix install location 2011-03-09 13:59:00 +01:00
holger krekel
007f0daeb9 bump to release version, regenerate docs 2011-03-09 10:58:36 +01:00
holger krekel
55657d6c51 simplify _locationline helper 2011-03-08 13:44:53 +01:00
holger krekel
1a7c6ecc42 fix slightly wrong verbose output for non subclasses on windows 2011-03-08 13:37:00 +01:00
holger krekel
f2670651b3 half the overhead for calling a test function by introducing some caching 2011-03-07 18:28:45 +01:00
holger krekel
5470cadbff fix issue25 --pdb and win32/python encodings cause a crash in certain situations.
The reason is not clear but avoiding a fresh copy of the terminal writer
helps, maybe because the underlying file object has some state?
2011-03-07 13:17:07 +01:00
holger krekel
f8e3fe8fbf mention that one might need to use the pypi.testrun.org repository 2011-03-07 11:53:14 +01:00
holger krekel
c552b58dc5 fix issue27 - --collectonly and -k keyword selection now work together.
internally, collectonly and terminal reporting has been unified.
2011-03-06 18:32:00 +01:00
holger krekel
18e784c9c9 re-introduce pytest._fillfuncargs - it's actually used by oejskit,
added a test documenting this.
2011-03-06 08:56:58 +01:00
holger krekel
5bef795ba7 add changelog entry about unittest change, bump version 2011-03-05 18:22:33 +01:00
Ronny Pfannschmidt
a6c518e68c unittest plugin: prune __unittest marked modules from traces 2011-03-05 17:49:51 +01:00
holger krekel
7e44c38570 avoid this test on pypy because syntax errors on pypy-1.4.1 are not precise it seems 2011-03-05 14:59:06 +01:00
holger krekel
9c952b3ce0 actually don't expose unused _fillfuncargs 2011-03-05 14:31:01 +01:00
holger krekel
bfe6e98abb don't expose _fillfuncargs (no clue why it ever was exposed) 2011-03-05 14:29:10 +01:00
holger krekel
07cee24122 avoid deprecation warnings for our internal accesses 2011-03-05 14:16:27 +01:00
holger krekel
22fac92ca0 improve and clarify skipping docs 2011-03-05 13:08:43 +01:00
holger krekel
318e8a404b fix and improve error reporting for parametrizing funcargs (originally reported by antlong) 2011-03-05 12:11:35 +01:00
holger krekel
fadd1a2313 incorporate typo/grammar fixes from Laura and respond to a number of issues she raised in comments.
Also fixed links and some other bits and pieces.
2011-03-03 23:40:38 +01:00
holger krekel
070c73ff2f fix issue30 (the second time)
put module globals into namespace for xfail and skipif expressions
2011-03-03 23:22:55 +01:00
holger krekel
682773e0cb fix issue30 - better handling and reporting of errors in xfail expressions 2011-03-03 12:19:17 +01:00
holger krekel
6f3b84da9f fix issue 28 - setup_method now works with pytest_generate_tests 2011-03-02 18:03:43 +01:00
holger krekel
f1b5dae1fb fix help string 2011-02-27 12:52:27 +01:00
holger krekel
8d62e4c71c add "issues" to layout of web page 2011-02-16 00:32:57 +01:00
Brianna Laugher
f6c1e49287 Fix mistakes in monkeypatch doc example 2011-02-18 16:52:18 +11:00
holger krekel
27577170e1 doc typo fixes from Victor Garcia, thanks! 2011-02-17 14:46:40 +01:00
Floris Bruynooghe
2f2586af72 Fix pytest_assertrepr_compare on python3 (issue24)
The maxsize argument must be an integer and the devision syntax changed
between python2 and python3.
2011-02-15 23:24:18 +00:00
holger krekel
70ceb946e4 fix typo, thanks antocuni 2011-02-14 18:49:16 +01:00
holger krekel
d2f9b41519 some doc fixes and improvements to parametrized test examples, thanks ccxCZ for review and suggestions. 2011-02-09 14:55:21 +01:00
holger krekel
2bd0c98801 up version, commit 2.0.1 annoucnement as sent out 2011-02-07 11:54:08 +01:00
holger krekel
5a5a618dcb Added tag 2.0.1 for changeset e4497c2aed35 2011-02-07 11:45:47 +01:00
holger krekel
98cd8edb71 regen docs with examples 2011-02-07 11:45:37 +01:00
holger krekel
e7b69a2ac0 doc fixes 2011-02-07 11:39:05 +01:00
holger krekel
74b9ebc1cd accept a left out "()" for ids on command line for better compatibility with pytest.vim 2011-02-07 11:09:42 +01:00
holger krekel
3004fe3915 fix the last committed laxation of a test 2011-02-04 23:20:27 +01:00
holger krekel
eb225456d7 laxer test for also passing it with pypy 2011-02-04 22:51:05 +01:00
holger krekel
b04f87b1a6 add release announcement 2011-02-03 15:58:22 +01:00
holger krekel
35b0b376f0 bumping version to pytest-2.0.1, regen docs and examples 2011-02-03 15:14:50 +01:00
holger krekel
762ea71f67 fix error reporting issue when a "pyc" file has no relating "py" 2011-01-27 21:11:21 +01:00
holger krekel
adacd3491d fix test related to "not in" 2011-01-27 11:36:12 +01:00
Floris Bruynooghe
709d5e3f2c Improve the "not in" assertion output
This cleans up the generic diff and tailors the output more to this
specific assertion (based on feedback from hpk).
2011-01-25 20:47:34 +00:00
holger krekel
d8d88ede65 refine and unify initial capturing - now works also if the logging module
is already used from an early-loaded conftest.py file (prior to option parsing)
2011-01-18 12:51:21 +01:00
holger krekel
b8f0d10f80 fix a pypy related regression - re-allow self.NAME style collection tree customization 2011-01-18 12:47:31 +01:00
holger krekel
aea4d1bd7a fix regression with yield-based tests (hopefully) 2011-01-14 13:30:36 +01:00
holger krekel
2b750074f4 fix typo (thanks derdon) 2011-01-13 23:50:10 +01:00
holger krekel
88cfaebbcb fix issue12 - show plugin versions with "--version" and "--traceconfig" and also document how to add extra information to reporting test header 2011-01-12 19:39:36 +01:00
holger krekel
426e056d2b fix issue10 - numpy arrays should now work better in assertion expressions
(or any other objects which have an exception-raising __nonzero__ method ...)
2011-01-12 19:17:54 +01:00
holger krekel
4445685285 pypy doesn't neccessarily honour -OO it seems, let's not test assertions there. 2011-01-12 18:57:40 +01:00
holger krekel
ae9b7a8bea use pypi.testrun.org so that py>1.4.0 gets picked up correctly 2011-01-12 18:03:55 +01:00
holger krekel
5daef51000 fix issue14 : it was actually issue14 instead of issue8 that was fixed with
the older https://bitbucket.org/hpk42/pytest/changeset/1c3eb86502b3

please try out with the usual "pip install -i http://pypi.testrun.org -U pytest"
2011-01-12 17:35:09 +01:00
holger krekel
647b56614a fix issue17 by requiring an update to pylib which helps to fix it 2011-01-12 17:21:11 +01:00
holger krekel
1b3fb3d229 fix issue15 - tests for python3/nose-1.0 combo work now 2011-01-11 17:27:34 +01:00
holger krekel
170c78cef9 remove same-conftest.py detection - does more harm than good
(see mail from Ralf Schmitt on py-dev)
2011-01-11 15:54:47 +01:00
Benjamin Peterson
8f5d837ef6 duplicate word 2010-12-23 14:56:38 -06:00
holger krekel
0ec5f3fd6c small improvements, add assertion improvement to CHANGELOG 2010-12-10 12:28:04 +01:00
Floris Bruynooghe
8631c1f57a Add "not in" to detailed explanations
This simply uses difflib to compare the text without the offending
string to the full text.

Also ensures the summary line uses all space available.  But the
terminal width is still hardcoded.
2010-12-10 01:03:26 +00:00
holger krekel
821f493378 check docstring at test time instead of runtime, improve and test warning on assertion turned off (thanks FND for reporting) 2010-12-09 11:00:31 +01:00
holger krekel
4086d46378 fix issue11 doc typo (thanks Eduardo) 2010-12-07 15:25:25 +01:00
holger krekel
a15983cb33 rather named the new hook cmdline_preparse 2010-12-07 12:34:18 +01:00
holger krekel
9ab256c296 make getvalueorskip() be hidden in skip-reporting. also bump version. 2010-12-07 12:18:24 +01:00
holger krekel
7db9e98b55 introduce a pytest_cmdline_processargs hook to modify/add dynamically to command line arguments. 2010-12-07 12:14:12 +01:00
holger krekel
e6541ed14e bump version and fix changelog issue reference 2010-12-06 19:01:50 +01:00
holger krekel
fc4f72cb1f fix issue7 - assert failure inside doctest doesn't prettyprint
unexpected exceptions are now reported within the doctest failure
representation context.
2010-12-06 19:00:30 +01:00
holger krekel
feea4ea3d5 fix hasplugin() method / test failures 2010-12-06 18:32:04 +01:00
holger krekel
513482f4f7 fix issue9 wrong XPass with failing setup/teardown function of xfail marked test
now when setup or teardown of a test item/function fails and the test
is marked "xfail" it will show up as an xfail-ed test.
2010-12-06 18:20:47 +01:00
holger krekel
2e80512bb8 fix issue8 : avoid errors caused by logging module wanting to close already closed streams.
The issue arose if logging was initialized while capturing was enabled
and then capturing streams were closed before process exit, leading
to the logging module to complain.
2010-12-06 16:56:12 +01:00
holger krekel
c7531705fc refine plugin registration, allow new "-p no:NAME" way to prevent/undo plugin registration 2010-12-06 16:54:42 +01:00
holger krekel
752965c298 add some docs and new projects 2010-12-06 10:41:20 +01:00
holger krekel
96a687b97c make pytest test suite pypy ready 2010-11-27 16:40:52 +01:00
holger krekel
d894bae281 bumping version to a dev version, run tests by using python PyPI by default 2010-11-26 13:37:00 +01:00
holger krekel
f1fc6e5eb6 regenerating examples 2010-11-26 13:26:56 +01:00
Benjamin Peterson
ca72c162c8 need double colon here 2010-11-25 20:55:32 -06:00
holger krekel
9bcb66d9a5 Added tag 2.0.0 for changeset e9e127acd6f0 2010-11-25 21:03:08 +01:00
134 changed files with 8130 additions and 2217 deletions

15
.hgtags
View File

@@ -31,3 +31,18 @@ c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
e9e127acd6f0497324ef7f40cfb997cad4c4cd17 2.0.0
e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
84e5c54b72448194a0f6f815da7e048ac8019d50 2.0.2
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
363e5a5a59c803e6bc176a6f9cc4bf1a1ca2dab0 2.0.3
e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
5864412c6f3c903384243bd315639d101d7ebc67 2.1.2
12a05d59249f80276e25fd8b96e8e545b1332b7a 2.1.3
1522710369337d96bf9568569d5f0ca9b38a74e0 2.2.0
3da8cec6c5326ed27c144c9b6d7a64a648370005 2.2.1

18
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
@@ -21,3 +22,4 @@ Jan Balster
Grig Gheorghiu
Bob Ippolito
Christian Tismer
Daniel Nuri

247
CHANGELOG
View File

@@ -1,4 +1,249 @@
Changes between 1.3.4 and 2.0.0dev0
Changes between 2.2.1 and 2.2.2
----------------------------------------
- fix issue101: wrong args to unittest.TestCase test function now
produce better output
- fix issue102: report more useful errors and hints for when a
test directory was renamed and some pyc/__pycache__ remain
- fix issue106: allow parametrize to be applied multiple times
e.g. from module, class and at function level.
- fix issue107: actually perform session scope finalization
- don't check in parametrize if indirect parameters are funcarg names
- add chdir method to monkeypatch funcarg
- fix crash resulting from calling monkeypatch undo a second time
- fix issue115: make --collectonly robust against early failure
(missing files/directories)
- "-qq --collectonly" now shows only files and the number of tests in them
- "-q --collectonly" now shows test ids
- allow adding of attributes to test reports such that it also works
with distributed testing (no upgrade of pytest-xdist needed)
Changes between 2.2.0 and 2.2.1
----------------------------------------
- fix issue99 (in pytest and py) internallerrors with resultlog now
produce better output - fixed by normalizing pytest_internalerror
input arguments.
- fix issue97 / traceback issues (in pytest and py) improve traceback output
in conjunction with jinja2 and cython which hack tracebacks
- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
the final test in a test node will now run its teardown directly
instead of waiting for the end of the session. Thanks Dave Hunt for
the good reporting and feedback. The pytest_runtest_protocol as well
as the pytest_runtest_teardown hooks now have "nextitem" available
which will be None indicating the end of the test run.
- fix collection crash due to unknown-source collected items, thanks
to Ralf Schmitt (fixed by depending on a more recent pylib)
Changes between 2.1.3 and 2.2.0
----------------------------------------
- fix issue90: introduce eager tearing down of test items so that
teardown function are called earlier.
- add an all-powerful metafunc.parametrize function which allows to
parametrize test function arguments in multiple steps and therefore
from indepdenent plugins and palces.
- add a @pytest.mark.parametrize helper which allows to easily
call a test function with different argument values
- Add examples to the "parametrize" example page, including a quick port
of Test scenarios and the new parametrize function and decorator.
- introduce registration for "pytest.mark.*" helpers via ini-files
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that "-m"
only matches complete markers and has more obvious rules for and/or
semantics.
- new feature to help optimizing the speed of your tests:
--durations=N option for displaying N slowest test calls
and setup/teardown methods.
- fix issue87: --pastebin now works with python3
- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly
- fix and cleanup pytest's own test suite to not leak FDs
- fix issue83: link to generated funcarg list
- fix issue74: pyarg module names are now checked against imp.find_module false positives
- fix compatibility with twisted/trial-11.1.0 use cases
- simplify Node.listchain
- simplify junitxml output code by relying on py.xml
- add support for skip properties on unittest classes and functions
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
----------------------------------------------
- fix issue38: nicer tracebacks on calls to hooks, particularly early
configure/sessionstart ones
- fix missing skip reason/meta information in junitxml files, reported
via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
- fix issue34: avoid collection failure with "test" prefixed classes
deriving from object.
- don't require zlib (and other libs) for genscript plugin without
--genscript actually being used.
- speed up skips (by not doing a full traceback represenation
internally)
- fix issue37: avoid invalid characters in junitxml's output
Changes between 2.0.1 and 2.0.2
----------------------------------------------
- tackle issue32 - speed up test runs of very quick test functions
by reducing the relative overhead
- fix issue30 - extended xfail/skipif handling and improved reporting.
If you have a syntax error in your skip/xfail
expressions you now get nice error reports.
Also you can now access module globals from xfail/skipif
expressions so that this for example works now::
import pytest
import mymodule
@pytest.mark.skipif("mymodule.__version__[0] == "1")
def test_function():
pass
This will not run the test function if the module's version string
does not start with a "1". Note that specifying a string instead
of a boolean expressions allows py.test to report meaningful information
when summarizing a test run as to what conditions lead to skipping
(or xfail-ing) tests.
- fix issue28 - setup_method and pytest_generate_tests work together
The setup_method fixture method now gets called also for
test function invocations generated from the pytest_generate_tests
hook.
- fix issue27 - collectonly and keyword-selection (-k) now work together
Also, if you do "py.test --collectonly -q" you now get a flat list
of test ids that you can use to paste to the py.test commandline
in order to execute a particular test.
- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
Starting with Python3.2 os.symlink may be supported. By requiring
a newer py lib version the py.path.local() implementation acknowledges
this.
- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
thanks to Laura Creighton who also revieved parts of the documentation.
- fix slighly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
Changes between 2.0.0 and 2.0.1
----------------------------------------------
- refine and unify initial capturing so that it works nicely
even if the logging module is used on an early-loaded conftest.py
file or plugin.
- allow to omit "()" in test ids to allow for uniform test ids
as produced by Alfredo's nice pytest.vim plugin.
- fix issue12 - show plugin versions with "--version" and
"--traceconfig" and also document how to add extra information
to reporting test header
- fix issue17 (import-* reporting issue on python3) by
requiring py>1.4.0 (1.4.1 is going to include it)
- fix issue10 (numpy arrays truth checking) by refining
assertion interpretation in py lib
- fix issue15: make nose compatibility tests compatible
with python3 (now that nose-1.0 supports python3)
- remove somewhat surprising "same-conftest" detection because
it ignores conftest.py when they appear in several subdirs.
- improve assertions ("not in"), thanks Floris Bruynooghe
- improve behaviour/warnings when running on top of "python -OO"
(assertions and docstrings are turned off, leading to potential
false positives)
- introduce a pytest_cmdline_processargs(args) hook
to allow dynamic computation of command line arguments.
This fixes a regression because py.test prior to 2.0
allowed to set command line options from conftest.py
files which so far pytest-2.0 only allowed from ini-files now.
- fix issue7: assert failures in doctest modules.
unexpected failures in doctests will not generally
show nicer, i.e. within the doctest failing context.
- fix issue9: setup/teardown functions for an xfail-marked
test will report as xfail if they fail but report as normally
passing (not xpassing) if they succeed. This only is true
for "direct" setup/teardown invocations because teardown_class/
teardown_module cannot closely relate to a single test.
- fix issue14: no logging errors at process exit
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not
setup as with pytest 1.3.4. Note, however, that
the recommended and much cleaner way to do test
parametraization remains the "pytest_generate_tests"
mechanism, see the docs.
Changes between 1.3.4 and 2.0.0
----------------------------------------------
- pytest-2.0 is now its own package and depends on pylib-2.0

View File

@@ -1,3 +1,24 @@
refine parametrize API in 2.2 series
-------------------------------------------------------------
tags: critical feature 2.2
extend metafunc.parametrize to better support indirection
by specifying a setupfunc(request, val) which will _substitute_
the funcarg factory. Here is an example:
def setupdb(request, val):
# setup "resource" based on test request and the values passed
# in to parametrize. setupfunc is called for each such value.
# you may use request.addfinalizer() or request.cached_setup ...
return db
@pytest.mark.parametrize("db", ["pg", "mysql"], setupfunc=setupdb)
def test_heavy_functional_test(db):
...
There would be no need to write funcarg factories for this example, only
to explain the attributes and functionality of "request".
checks / deprecations for next release
---------------------------------------------------------------
tags: bug 2.4 core xdist
@@ -7,17 +28,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.3
currently teardowns are called when the next tests is setup
except for the function/method level where interally
@@ -29,7 +60,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.3 core
Currently, a test module is imported with its fully qualified
package path, determined by checking __init__ files upwards.
@@ -44,7 +75,7 @@ certain scenarios makes sense.
relax requirement to have tests/testing contain an __init__
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.3
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
A local test run of a "tests" directory may work
@@ -55,25 +86,24 @@ i.e. port the nose-logic of unloading a test module.
customize test function collection
-------------------------------------------------------
tags: feature 2.1
tags: feature 2.3
- 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.3
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.3
Introduce nice-to-spell platform-skipping, examples:
@@ -88,9 +118,19 @@ etc. Idea is to allow Python expressions which can operate
on common spellings for operating systems and python
interpreter versions.
pytest.mark.xfail signature change
-------------------------------------------------------
tags: feature 2.3
change to pytest.mark.xfail(reason, (optional)condition)
to better implement the word meaning. It also signals
better that we always have some kind of an implementation
reason that can be formualated.
Compatibility? how to introduce a new name/keep compat?
introduce py.test.mark registration
-----------------------------------------
tags: feature 2.1
tags: feature 2.3
introduce a hook that allows to register a named mark decorator
with documentation and add "py.test --marks" to get
@@ -99,7 +139,7 @@ definitions.
allow to non-intrusively apply skipfs/xfail/marks
---------------------------------------------------
tags: feature 2.1
tags: feature 2.3
use case: mark a module or directory structures
to be skipped on certain platforms (i.e. no import
@@ -110,14 +150,14 @@ from conftests or plugins.
explicit referencing of conftest.py files
-----------------------------------------
tags: feature 2.1
tags: feature 2.3
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.3
introduce more declarative configuration options:
- (to-be-collected test directories)
@@ -128,33 +168,16 @@ introduce more declarative configuration options:
new documentation
----------------------------------
tags: feature 2.1
tags: feature 2.3
- logo py.test
- examples for unittest or functional testing
- resource management for functional testing
- patterns: page object
- parametrized testing
- better / more integrated plugin docs
generalize parametrized testing to generate combinations
-------------------------------------------------------------
tags: feature 2.1
think about extending metafunc.addcall or add a new method to allow to
generate tests with combinations of all generated versions - what to do
about "id" and "param" in such combinations though?
introduce py.test.mark.multi
-----------------------------------------
tags: feature 1.3
introduce py.test.mark.multi to specify a number
of values for a given function argument.
have imported module mismatch honour relative paths
--------------------------------------------------------
tags: bug 2.1
tags: bug 2.3
With 1.1.1 py.test fails at least on windows if an import
is relative and compared against an absolute conftest.py
@@ -162,7 +185,7 @@ path. Normalize.
call termination with small timeout
-------------------------------------------------
tags: feature 2.1
tags: feature 2.3
test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node
Call gateway group termination with a small timeout if available.
@@ -170,7 +193,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.3
consider deprecating py.test.ensuretemp and py.test.config
to further reduce py.test globality. Also consider
@@ -179,7 +202,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.3
Users have expressed the wish to have funcargs available to setup
functions. Experiment with allowing funcargs there - it might
@@ -189,13 +212,20 @@ factories with a request object that not have a cls/function
attributes. However, how to handle parametrized test functions
and funcargs?
setup_function -> request can be like it is now
setup_class -> request has no request.function
setup_module -> request has no request.cls
maybe introduce a setup method like:
setup_invocation(self, request)
which has full access to the test invocation through "request"
through which you can get funcargvalues, use cached_setup etc.
Therefore, the access to funcargs would be indirect but it
could be consistently implemented. setup_invocation() would
be a "glue" function for bringing together the xUnit and funcargs
world.
consider pytest_addsyspath hook
-----------------------------------------
tags: 2.1
tags: 2.3
py.test could call a new pytest_addsyspath() in order to systematically
allow manipulation of sys.path and to inhibit it via --no-addsyspath
@@ -207,7 +237,7 @@ and pytest_configure.
show plugin information in test header
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.3
Now that external plugins are becoming more numerous
it would be useful to have external plugins along with
@@ -215,7 +245,7 @@ their versions displayed as a header line.
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 2.1
tags: feature 2.3
py.test.ensuretemp and py.test.config are probably the last
objects containing global state. Often using them is not
@@ -225,9 +255,32 @@ as others.
remove deprecated bits in collect.py
-------------------------------------------------------------------
tags: feature 2.1
tags: feature 2.3
In an effort to further simplify code, review and remove deprecated bits
in collect.py. Probably good:
- inline consider_file/dir methods, no need to have them
subclass-overridable because of hooks
implement fslayout decorator
---------------------------------
tags: feature 2.3
Improve the way how tests can work with pre-made examples,
keeping the layout close to the test function:
@pytest.mark.fslayout("""
conftest.py:
# empty
tests/
test_%(NAME)s: # becomes test_run1.py
def test_function(self):
pass
""")
def test_run(pytester, fslayout):
p = fslayout.find("test_*.py")
result = pytester.runpytest(p)
assert result.ret == 0
assert result.passed == 1

View File

@@ -1 +1,2 @@
#
__version__ = '2.2.2'

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,45 +1,78 @@
"""
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 _pytesthook attribute on the AssertionError 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.
config._monkeypatch = m = monkeypatch()
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
warn_about_missing_assertion()
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 pytest_unconfigure(config):
config._monkeypatch.undo()
def format_explanation(explanation):
"""This formats an explanation
def warn_about_missing_assertion():
try:
assert False
except AssertionError:
pass
else:
py.std.warnings.warn("Assertions are turned off!"
" (are you using python -O?)")
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)
# Provide basestring in python3
@@ -49,10 +82,11 @@ except NameError:
basestring = str
def pytest_assertrepr_compare(op, left, right):
def assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
left_repr = py.io.saferepr(left, maxsize=30)
right_repr = py.io.saferepr(right, maxsize=30)
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
summary = '%s %s %s' % (left_repr, op, right_repr)
issequence = lambda x: isinstance(x, (list, tuple))
@@ -72,6 +106,9 @@ def pytest_assertrepr_compare(op, left, right):
elif isdict(left) and isdict(right):
explanation = _diff_text(py.std.pprint.pformat(left),
py.std.pprint.pformat(right))
elif op == 'not in':
if istext(left) and istext(right):
explanation = _notin_text(left, right)
except py.builtin._sysex:
raise
except:
@@ -155,3 +192,22 @@ def _compare_eq_set(left, right):
for item in diff_right:
explanation.append(py.io.saferepr(item))
return explanation
def _notin_text(term, text):
index = text.find(term)
head = text[:index]
tail = text[index+len(term):]
correct_text = head + tail
diff = _diff_text(correct_text, text)
newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)]
for line in diff:
if line.startswith('Skipping'):
continue
if line.startswith('- '):
continue
if line.startswith('+ '):
newdiff.append(' ' + line[2:])
else:
newdiff.append(line)
return newdiff

View File

@@ -11,22 +11,22 @@ def pytest_addoption(parser):
group._addoption('-s', action="store_const", const="no", dest="capture",
help="shortcut for --capture=no.")
@pytest.mark.tryfirst
def pytest_cmdline_parse(pluginmanager, args):
# we want to perform capturing already for plugin/conftest loading
if '-s' in args or "--capture=no" in args:
method = "no"
elif hasattr(os, 'dup') and '--capture=sys' not in args:
method = "fd"
else:
method = "sys"
capman = CaptureManager(method)
pluginmanager.register(capman, "capturemanager")
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())
def pytest_configure(config):
config.pluginmanager.register(CaptureManager(), 'capturemanager')
def pytest_unconfigure(config):
capman = config.pluginmanager.getplugin('capturemanager')
while capman._method2capture:
name, cap = capman._method2capture.popitem()
cap.reset()
rep.sections.append(("Captured std%s" % secname, content))
class NoCapture:
def startall(self):
@@ -39,8 +39,9 @@ class NoCapture:
return "", ""
class CaptureManager:
def __init__(self):
def __init__(self, defaultmethod=None):
self._method2capture = {}
self._defaultmethod = defaultmethod
def _maketempfile(self):
f = py.std.tempfile.TemporaryFile()
@@ -77,16 +78,22 @@ class CaptureManager:
method = "sys"
return method
def reset_capturings(self):
for name, cap in self._method2capture.items():
cap.reset()
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
if not hasattr(item, 'outerr'):
item.outerr = ('', '') # we accumulate outerr on the item
return self.resumecapture(method)
def resumecapture(self, method):
def resumecapture(self, method=None):
if hasattr(self, '_capturing'):
raise ValueError("cannot resume, already capturing with %r" %
(self._capturing,))
if method is None:
method = self._defaultmethod
cap = self._method2capture.get(method)
self._capturing = method
if cap is None:
@@ -156,17 +163,6 @@ class CaptureManager:
def pytest_runtest_teardown(self, item):
self.resumecapture_item(item)
def pytest__teardown_final(self, __multicall__, session):
method = self._getmethod(session.config, None)
self.resumecapture(method)
try:
rep = __multicall__.execute()
finally:
outerr = self.suspendcapture()
if rep:
addouterr(rep, outerr)
return rep
def pytest_keyboard_interrupt(self, excinfo):
if hasattr(self, '_capturing'):
self.suspendcapture()
@@ -184,18 +180,16 @@ class CaptureManager:
return rep
def pytest_funcarg__capsys(request):
"""captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
"""enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
return CaptureFuncarg(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
"""enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")

View File

@@ -8,10 +8,16 @@ 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):
while 1:
try:
fin = config._cleanup.pop()
except IndexError:
break
fin()
class Parser:
""" Parser for command line arguments. """
@@ -34,7 +40,7 @@ class Parser:
def getgroup(self, name, description="", after=None):
""" get (or create) a named option Group.
:name: unique name of the option group.
:description: long description for --help output.
:after: name of other group, used for ordering --help output.
@@ -77,6 +83,7 @@ class Parser:
self._inidict[name] = (help, type, default)
self._ininames.append(name)
class OptionGroup:
def __init__(self, name, description="", parser=None):
self.name = name
@@ -125,7 +132,6 @@ class Conftest(object):
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = confcutdir
self._md5cache = {}
def setinitial(self, args):
""" try to find a first anchor path for looking up global values
@@ -179,14 +185,7 @@ class Conftest(object):
else:
conftestpath = path.join("conftest.py")
if conftestpath.check(file=1):
key = conftestpath.computehash()
# XXX logging about conftest loading
if key not in self._md5cache:
clist.append(self.importconftest(conftestpath))
self._md5cache[key] = conftestpath
else:
# use some kind of logging
print ("WARN: not loading %s" % conftestpath)
clist.append(self.importconftest(conftestpath))
clist[:0] = self.getconftestmodules(dp)
self._path2confmods[path] = clist
# be defensive: avoid changes from caller side to
@@ -259,6 +258,20 @@ class Config(object):
self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook
self._inicache = {}
self._cleanup = []
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
config = cls()
# XXX slightly crude way to initialize capturing
import _pytest.capture
_pytest.capture.pytest_cmdline_parse(config.pluginmanager, args)
config._preparse(args, addopts=False)
config.option.__dict__.update(option_dict)
for x in config.option.plugins:
config.pluginmanager.consider_pluginarg(x)
return config
def _onimportconftest(self, conftestmodule):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
@@ -278,13 +291,15 @@ class Config(object):
def _setinitialconftest(self, args):
# capture output during conftest init (#issue93)
name = hasattr(os, 'dup') and 'StdCaptureFD' or 'StdCapture'
cap = getattr(py.io, name)()
# XXX introduce load_conftest hook to avoid needing to know
# about capturing plugin here
capman = self.pluginmanager.getplugin("capturemanager")
capman.resumecapture()
try:
try:
self._conftest.setinitial(args)
finally:
out, err = cap.reset()
out, err = capman.suspendcapture() # logging might have got it
except:
sys.stdout.write(out)
sys.stderr.write(err)
@@ -300,11 +315,13 @@ class Config(object):
if addopts:
args[:] = self.getini("addopts") + args
self._checkversion()
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.pluginmanager.consider_preparse(args)
self._setinitialconftest(args)
self.pluginmanager.do_addoption(self._parser)
if addopts:
self.hook.pytest_cmdline_preparse(config=self, args=args)
def _checkversion(self):
minver = self.inicfg.get('minversion', None)
@@ -322,6 +339,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)
@@ -329,6 +347,14 @@ class Config(object):
args.append(py.std.os.getcwd())
self.args = args
def addinivalue_line(self, name, line):
""" add a line to an ini-file option. The option must have been
declared but might not yet be set in which case the line becomes the
the first line in its value. """
x = self.getini(name)
assert isinstance(x, list)
x.append(line) # modifies the cached list inline
def getini(self, name):
""" return configuration value from an ini file. If the
specified name hasn't been registered through a prior ``parse.addini``
@@ -397,7 +423,9 @@ class Config(object):
return self._getconftest(name, path, check=False)
def getvalueorskip(self, name, path=None):
""" (deprecated) return getvalue(name) or call py.test.skip if no value exists. """
""" (deprecated) return getvalue(name) or call
py.test.skip if no value exists. """
__tracebackhide__ = True
try:
val = self.getvalue(name, path)
if val is None:
@@ -408,7 +436,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:
@@ -421,7 +449,7 @@ def getcfg(args, inibasenames):
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
def findupwards(current, basename):
current = py.path.local(current)
while 1:

View File

@@ -11,18 +11,15 @@ assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
default_plugins = (
"config mark session terminal runner python pdb unittest capture skipping "
"config mark main terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml doctest").split()
IMPORTPREFIX = "pytest_"
"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,))
@@ -32,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:
@@ -62,9 +59,11 @@ class TagTracerSub:
class PluginManager(object):
def __init__(self, load=False):
self._name2plugin = {}
self._listattrcache = {}
self._plugins = []
self._hints = []
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
encoding = getattr(err, 'encoding', 'utf8')
@@ -79,20 +78,12 @@ class PluginManager(object):
for spec in default_plugins:
self.import_plugin(spec)
def _getpluginname(self, plugin, name):
if name is None:
if hasattr(plugin, '__name__'):
name = plugin.__name__.split(".")[-1]
else:
name = id(plugin)
return name
def register(self, plugin, name=None, prepend=False):
assert not self.isregistered(plugin), plugin
assert not self.isregistered(plugin), plugin
name = self._getpluginname(plugin, name)
name = name or getattr(plugin, '__name__', str(id(plugin)))
if name in self._name2plugin:
return False
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
@@ -112,7 +103,7 @@ class PluginManager(object):
del self._name2plugin[name]
def isregistered(self, plugin, name=None):
if self._getpluginname(plugin, name) in self._name2plugin:
if self.getplugin(name) is not None:
return True
for val in self._name2plugin.values():
if plugin == val:
@@ -129,18 +120,15 @@ class PluginManager(object):
py.test.skip("plugin %r is missing" % name)
def hasplugin(self, name):
try:
self.getplugin(name)
return True
except KeyError:
return False
return bool(self.getplugin(name))
def getplugin(self, name):
if name is None:
return None
try:
return self._name2plugin[name]
except KeyError:
impname = canonical_importname(name)
return self._name2plugin[impname]
return self._name2plugin.get("_pytest." + name, None)
# API for bootstrapping
#
@@ -160,19 +148,32 @@ class PluginManager(object):
except ImportError:
return # XXX issue a warning
for ep in iter_entry_points('pytest11'):
name = canonical_importname(ep.name)
if name in self._name2plugin:
name = ep.name
if name.startswith("pytest_"):
name = name[7:]
if ep.name in self._name2plugin or name in self._name2plugin:
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self._plugin_distinfo.append((ep.dist, plugin))
self.register(plugin, name=name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.import_plugin(opt2)
self.consider_pluginarg(opt2)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
name = arg[3:]
if self.getplugin(name) is not None:
self.unregister(None, name=name)
self._name2plugin[name] = -1
else:
if self.getplugin(arg) is None:
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
@@ -186,15 +187,19 @@ class PluginManager(object):
for spec in attr:
self.import_plugin(spec)
def import_plugin(self, spec):
assert isinstance(spec, str)
modname = canonical_importname(spec)
if modname in self._name2plugin:
def import_plugin(self, modname):
assert isinstance(modname, str)
if self.getplugin(modname) is not None:
return
try:
#self.trace("importing", modname)
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except ImportError:
if modname.startswith("pytest_"):
return self.import_plugin(modname[7:])
raise
except:
e = py.std.sys.exc_info()[1]
if not hasattr(py.test, 'skip'):
@@ -206,6 +211,14 @@ class PluginManager(object):
self.register(mod, modname)
self.consider_module(mod)
def pytest_configure(self, config):
config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
def pytest_plugin_registered(self, plugin):
import pytest
dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
@@ -259,8 +272,15 @@ class PluginManager(object):
config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self)
def notify_exception(self, excinfo):
excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
def notify_exception(self, excinfo, option=None):
if option and option.fulltrace:
style = "long"
else:
style = "native"
excrepr = excinfo.getrepr(funcargs=True,
showlocals=getattr(option, 'showlocals', False),
style=style,
)
res = self.hook.pytest_internalerror(excrepr=excrepr)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
@@ -270,6 +290,11 @@ class PluginManager(object):
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
key = (attrname,) + tuple(plugins)
try:
return list(self._listattrcache[key])
except KeyError:
pass
l = []
last = []
for plugin in plugins:
@@ -284,40 +309,25 @@ class PluginManager(object):
except AttributeError:
continue
l.extend(last)
self._listattrcache[key] = list(l)
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def canonical_importname(name):
if '.' in name:
return name
name = name.lower()
if not name.startswith(IMPORTPREFIX):
name = IMPORTPREFIX + name
return name
def importplugin(importspec):
#print "importing", importspec
name = importspec
try:
return __import__(importspec, None, None, '__doc__')
mod = "_pytest." + name
return __import__(mod, None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(importspec) == -1:
raise
name = importspec
try:
if name.startswith("pytest_"):
name = importspec[7:]
return __import__("_pytest.%s" %(name), None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(name) == -1:
raise
# show the original exception, not the failing internal one
return __import__(importspec, None, None, '__doc__')
#e = py.std.sys.exc_info()[1]
#if str(e).find(name) == -1:
# raise
pass #
return __import__(importspec, None, None, '__doc__')
class MultiCall:
""" execute a call into multiple python functions/methods. """
@@ -354,14 +364,20 @@ class MultiCall:
return kwargs
def varnames(func):
try:
return func._varnames
except AttributeError:
pass
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func)
rawcode = py.code.getrawcode(func)
try:
return rawcode.co_varnames[ismethod:rawcode.co_argcount]
x = rawcode.co_varnames[ismethod:rawcode.co_argcount]
except AttributeError:
return ()
x = ()
py.builtin._getfuncdict(func)['_varnames'] = x
return x
class HookRelay:
def __init__(self, hookspecs, pm, prefix="pytest_"):
@@ -378,9 +394,6 @@ class HookRelay:
added = False
for name, method in vars(hookspecs).items():
if name.startswith(prefix):
if not method.__doc__:
raise ValueError("docstring required for hook %r, in %r"
% (method, hookspecs))
firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult)
setattr(self, name, hc)
@@ -426,10 +439,7 @@ _preinit = []
def _preloadplugins():
_preinit.append(PluginManager(load=True))
def main(args=None, plugins=None):
""" returned exit code integer, after an in-process testing run
with the given command line arguments, preloading an optional list
of passed in plugin objects. """
def _prepareconfig(args=None, plugins=None):
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
@@ -443,13 +453,19 @@ def main(args=None, plugins=None):
else: # subsequent calls to main will create a fresh instance
_pluginmanager = PluginManager(load=True)
hook = _pluginmanager.hook
if plugins:
for plugin in plugins:
_pluginmanager.register(plugin)
return hook.pytest_cmdline_parse(
pluginmanager=_pluginmanager, args=args)
def main(args=None, plugins=None):
""" returned exit code integer, after an in-process testing run
with the given command line arguments, preloading an optional list
of passed in plugin objects. """
try:
if plugins:
for plugin in plugins:
_pluginmanager.register(plugin)
config = hook.pytest_cmdline_parse(
pluginmanager=_pluginmanager, args=args)
exitstatus = hook.pytest_cmdline_main(config=config)
config = _prepareconfig(args, plugins)
exitstatus = config.hook.pytest_cmdline_main(config=config)
except UsageError:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],))

View File

@@ -34,7 +34,9 @@ class ReprFailDoctest(TerminalRepr):
class DoctestItem(pytest.Item):
def repr_failure(self, excinfo):
if excinfo.errisinstance(py.std.doctest.DocTestFailure):
doctest = py.std.doctest
if excinfo.errisinstance((doctest.DocTestFailure,
doctest.UnexpectedException)):
doctestfailure = excinfo.value
example = doctestfailure.example
test = doctestfailure.test
@@ -50,12 +52,15 @@ class DoctestItem(pytest.Item):
for line in filelines[i:lineno]:
lines.append("%03d %s" % (i+1, line))
i += 1
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
if excinfo.errisinstance(doctest.DocTestFailure):
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
else:
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)
elif excinfo.errisinstance(py.std.doctest.UnexpectedException):
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
return super(DoctestItem, self).repr_failure(excinfo)
else:
return super(DoctestItem, self).repr_failure(excinfo)

View File

@@ -1,8 +1,5 @@
""" generate a single-file self-contained version of py.test """
import py
import pickle
import zlib
import base64
def find_toplevel(name):
for syspath in py.std.sys.path:
@@ -31,9 +28,9 @@ def pkg_to_mapping(name):
return name2src
def compress_mapping(mapping):
data = pickle.dumps(mapping, 2)
data = zlib.compress(data, 9)
data = base64.encodestring(data)
data = py.std.pickle.dumps(mapping, 2)
data = py.std.zlib.compress(data, 9)
data = py.std.base64.encodestring(data)
data = data.decode('ascii')
return data
@@ -44,7 +41,6 @@ def compress_packages(names):
mapping.update(pkg_to_mapping(name))
return compress_mapping(mapping)
def generate_script(entry, packages):
data = compress_packages(packages)
tmpl = py.path.local(__file__).dirpath().join('standalonetemplate.py')

View File

@@ -1,7 +1,8 @@
""" 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):
group = parser.getgroup('debugconfig')
@@ -15,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):
@@ -28,10 +48,15 @@ def pytest_cmdline_main(config):
p = py.path.local(pytest.__file__)
sys.stderr.write("This is py.test version %s, imported from %s\n" %
(pytest.__version__, p))
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
config.pluginmanager.do_configure(config)
showhelp(config)
config.pluginmanager.do_unconfigure(config)
return 0
def showhelp(config):
@@ -69,18 +94,37 @@ conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
]
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager._plugin_distinfo
if plugininfo:
lines.append("setuptools registered plugins:")
for dist, plugin in plugininfo:
loc = getattr(plugin, '__file__', repr(plugin))
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
lines.append(" " + content)
return lines
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
verinfo = getpluginversioninfo(config)
if verinfo:
lines.extend(verinfo)
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []
items = config.pluginmanager._name2plugin.items()
for name, plugin in items:
lines.append(" %-20s: %s" %(name, repr(plugin)))
if hasattr(plugin, '__file__'):
r = plugin.__file__
else:
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
return lines
@@ -112,12 +156,11 @@ def pytest_plugin_registered(manager, plugin):
fail = True
else:
#print "checking", method
method_args = getargs(method)
#print "method_args", method_args
method_args = list(varnames(method))
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = getargs(hook)
hookargs = varnames(hook)
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
@@ -139,11 +182,6 @@ def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def getargs(func):
args = inspect.getargs(py.code.getrawcode(func))[0]
startindex = inspect.ismethod(func) and 1 or 0
return args[startindex:]
def collectattr(obj):
methods = {}
for apiname in dir(obj):

View File

@@ -19,6 +19,9 @@ def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
pytest_cmdline_parse.firstresult = True
def pytest_cmdline_preparse(config, args):
"""modify command line arguments before option parsing. """
def pytest_addoption(parser):
"""add optparse-style options and ini-style config values via calls
to ``parser.addoption`` and ``parser.addini(...)``.
@@ -118,16 +121,23 @@ def pytest_generate_tests(metafunc):
def pytest_itemstart(item, node=None):
""" (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item):
""" implements the standard runtest_setup/call/teardown protocol including
capturing exceptions and calling reporting hooks on the results accordingly.
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
the given test item, including capturing exceptions and calling
reporting hooks.
:arg item: test item for which the runtest protocol is performed.
:arg nexitem: the scheduled-to-be-next test item (or None if this
is the end my friend). This argument is passed on to
:py:func:`pytest_runtest_teardown`.
:return boolean: True if no further hook implementations should be invoked.
"""
pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location):
""" signal the start of a test run. """
""" signal the start of running a single test item. """
def pytest_runtest_setup(item):
""" called before ``pytest_runtest_call(item)``. """
@@ -135,8 +145,14 @@ def pytest_runtest_setup(item):
def pytest_runtest_call(item):
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item):
""" called after ``pytest_runtest_call``. """
def pytest_runtest_teardown(item, nextitem):
""" called after ``pytest_runtest_call``.
:arg nexitem: the scheduled-to-be-next test item (None if no further
test item is scheduled). This argument can be used to
perform exact teardowns, i.e. calling just enough finalizers
so that nextitem only needs to call setup-functions.
"""
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
@@ -146,15 +162,8 @@ def pytest_runtest_makereport(item, call):
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report):
""" process item test report. """
# special handling for final teardown - somewhat internal for now
def pytest__teardown_final(session):
""" called before test session finishes. """
pytest__teardown_final.firstresult = True
def pytest__teardown_final_logerror(report, session):
""" called if runtest_teardown_final failed. """
""" process a test setup/call/teardown report relating to
the respective phase of executing a test. """
# -------------------------------------------------------------------------
# test session related hooks
@@ -202,7 +211,6 @@ def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------

View File

@@ -5,8 +5,54 @@ Based on initial code from Ross Lawley.
import py
import os
import re
import sys
import time
# Python 2.X and 3.X compatibility
try:
unichr(65)
except NameError:
unichr = chr
try:
unicode('A')
except NameError:
unicode = str
try:
long(1)
except NameError:
long = int
class Junit(py.xml.Namespace):
pass
# We need to get the subset of the invalid unicode ranges according to
# XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
# | [#x10000-#x10FFFF]
_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x19),
(0xD800, 0xDFFF), (0xFDD0, 0xFFFF)]
_illegal_ranges = [unicode("%s-%s") % (unichr(low), unichr(high))
for (low, high) in _illegal_unichrs
if low < sys.maxunicode]
illegal_xml_re = re.compile(unicode('[%s]') %
unicode('').join(_illegal_ranges))
del _illegal_unichrs
del _illegal_ranges
def bin_xml_escape(arg):
def repl(matchobj):
i = ord(matchobj.group())
if i <= 0xFF:
return unicode('#x%02X') % i
else:
return unicode('#x%04X') % i
return illegal_xml_re.sub(repl, py.xml.escape(arg))
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', action="store", dest="xmlpath",
@@ -28,109 +74,103 @@ def pytest_unconfigure(config):
del config._xml
config.pluginmanager.unregister(xml)
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.tests = []
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")}
names = [x.replace(".py", "") for x in names if x != "()"]
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
d['classname'] = ".".join(classnames)
d['name'] = py.xml.escape(names[-1])
attrs = ['%s="%s"' % item for item in sorted(d.items())]
self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
self.tests.append(Junit.testcase(
classname=".".join(classnames),
name=names[-1],
time=getattr(report, 'duration', 0)
))
def _closetestcase(self):
self.test_logs.append("</testcase>")
def appendlog(self, fmt, *args):
args = tuple([py.xml.escape(arg) for arg in args])
self.test_logs.append(fmt % args)
def append(self, obj):
self.tests[-1].append(obj)
def append_pass(self, report):
self.passed += 1
self._opentestcase(report)
self._closetestcase()
def append_failure(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
if "xfail" in report.keywords:
self.appendlog(
'<skipped message="xfail-marked test passes unexpectedly"/>')
self.append(
Junit.skipped(message="xfail-marked test passes unexpectedly"))
self.skipped += 1
else:
self.appendlog('<failure message="test failure">%s</failure>',
report.longrepr)
sec = dict(report.sections)
fail = Junit.failure(message="test failure")
fail.append(str(report.longrepr))
self.append(fail)
for name in ('out', 'err'):
content = sec.get("Captured std%s" % name)
if content:
tag = getattr(Junit, 'system-'+name)
self.append(tag(bin_xml_escape(content)))
self.failed += 1
self._closetestcase()
def append_collect_failure(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
self.appendlog('<failure message="collection failure">%s</failure>',
report.longrepr)
self._closetestcase()
self.append(Junit.failure(str(report.longrepr),
message="collection failure"))
self.errors += 1
def append_collect_skipped(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
self.appendlog('<skipped message="collection skipped">%s</skipped>',
report.longrepr)
self._closetestcase()
self.append(Junit.skipped(str(report.longrepr),
message="collection skipped"))
self.skipped += 1
def append_error(self, report):
self._opentestcase(report)
self.appendlog('<error message="test setup failure">%s</error>',
report.longrepr)
self._closetestcase()
self.append(Junit.error(str(report.longrepr),
message="test setup failure"))
self.errors += 1
def append_skipped(self, report):
self._opentestcase(report)
if "xfail" in report.keywords:
self.appendlog(
'<skipped message="expected test failure">%s</skipped>',
report.keywords['xfail'])
self.append(Junit.skipped(str(report.keywords['xfail']),
message="expected test failure"))
else:
self.appendlog("<skipped/>")
self._closetestcase()
filename, lineno, skipreason = report.longrepr
if skipreason.startswith("Skipped: "):
skipreason = skipreason[9:]
self.append(
Junit.skipped("%s:%s: %s" % report.longrepr,
type="pytest.skip",
message=skipreason
))
self.skipped += 1
def pytest_runtest_logreport(self, report):
if report.passed:
self.append_pass(report)
if report.when == "call": # ignore setup/teardown
self._opentestcase(report)
self.append_pass(report)
elif report.failed:
self._opentestcase(report)
if report.when != "call":
self.append_error(report)
else:
self.append_failure(report)
elif report.skipped:
self._opentestcase(report)
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:
self._opentestcase(report)
if report.failed:
self.append_collect_failure(report)
else:
@@ -139,10 +179,11 @@ class LogXML(object):
def pytest_internalerror(self, excrepr):
self.errors += 1
data = py.xml.escape(excrepr)
self.test_logs.append(
'\n<testcase classname="pytest" name="internal">'
' <error message="internal error">'
'%s</error></testcase>' % data)
self.tests.append(
Junit.testcase(
Junit.error(data, message="internal error"),
classname="pytest",
name="internal"))
def pytest_sessionstart(self, session):
self.suite_start_time = time.time()
@@ -156,17 +197,17 @@ class LogXML(object):
suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time
numtests = self.passed + self.failed
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write('<testsuite ')
logfile.write('name="" ')
logfile.write('errors="%i" ' % self.errors)
logfile.write('failures="%i" ' % self.failed)
logfile.write('skips="%i" ' % self.skipped)
logfile.write('tests="%i" ' % numtests)
logfile.write('time="%.3f"' % suite_time_delta)
logfile.write(' >')
logfile.writelines(self.test_logs)
logfile.write('</testsuite>')
logfile.write(Junit.testsuite(
self.tests,
name="",
errors=self.errors,
failures=self.failed,
skips=self.skipped,
tests=numtests,
time="%.3f" % suite_time_delta,
).unicode(indent=0))
logfile.close()
def pytest_terminal_summary(self, terminalreporter):

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
@@ -11,6 +11,8 @@ EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
name_re = py.std.re.compile("^[a-zA-Z_]\w*$")
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=('.*', 'CVS', '_darcs', '{arch}'))
@@ -27,6 +29,9 @@ def pytest_addoption(parser):
action="store", type="int", dest="maxfail", default=0,
help="exit after first num failures or errors.")
group._addoption('--strict', action="store_true",
help="run pytest in strict mode, warnings become errors.")
group = parser.getgroup("collect", "collection")
group.addoption('--collectonly',
action="store_true", dest="collectonly",
@@ -40,29 +45,31 @@ def pytest_addoption(parser):
help="only load conftest.py's relative to specified dir.")
group = parser.getgroup("debugconfig",
"test process debugging and configuration")
"test session debugging and configuration")
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
def pytest_namespace():
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:
@@ -71,30 +78,40 @@ def pytest_cmdline_main(config):
session.exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
config.pluginmanager.notify_exception(excinfo)
config.pluginmanager.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
if initstate >= 2:
config.hook.pytest_sessionfinish(session=session,
exitstatus=session.exitstatus or (session._testsfailed and 1))
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 >= 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:
return True
for item in session.session.items:
item.config.hook.pytest_runtest_protocol(item=item)
for i, item in enumerate(session.items):
try:
nextitem = session.items[i+1]
except IndexError:
nextitem = None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
@@ -121,13 +138,10 @@ class HookProxy:
def compatproperty(name):
def fget(self):
#print "retrieving %r property from %s" %(name, self.fspath)
py.log._apiwarn("2.0", "use pytest.%s for "
"test collection and item classes" % name)
return getattr(pytest, name)
return property(fget, None, None,
"deprecated attribute %r, use pytest.%s" % (name,name))
class Node(object):
""" base class for all Nodes in the collection tree.
Collector subclasses have children, Items are terminal nodes."""
@@ -138,13 +152,13 @@ class Node(object):
#: the parent collector node.
self.parent = parent
#: the test config object
self.config = config or parent.config
#: the collection this node is part of
self.session = session or parent.session
#: filesystem path where this node was collected from
self.fspath = getattr(parent, 'fspath', None)
self.ihook = self.session.gethookproxy(self.fspath)
@@ -152,10 +166,19 @@ class Node(object):
Module = compatproperty("Module")
Class = compatproperty("Class")
Instance = compatproperty("Instance")
Function = compatproperty("Function")
File = compatproperty("File")
Item = compatproperty("Item")
def _getcustomclass(self, name):
cls = getattr(self, name)
if cls != getattr(pytest, name):
py.log._apiwarn("2.0", "use of node.%s is deprecated, "
"use pytest_pycollect_makeitem(...) to create custom "
"collection nodes" % name)
return cls
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
@@ -210,13 +233,13 @@ class Node(object):
def listchain(self):
""" return list of all parent collectors up to self,
starting from root of collection tree. """
l = [self]
while 1:
x = l[0]
if x.parent is not None: # and x.parent.parent is not None:
l.insert(0, x.parent)
else:
return l
chain = []
item = self
while item is not None:
chain.append(item)
item = item.parent
chain.reverse()
return chain
def listnames(self):
return [x.name for x in self.listchain()]
@@ -311,6 +334,8 @@ class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
"""
nextitem = None
def reportinfo(self):
return self.fspath, None, ""
@@ -320,7 +345,13 @@ class Item(Node):
return self._location
except AttributeError:
location = self.reportinfo()
fspath = self.session.fspath.bestrelpath(location[0])
# bestrelpath is a quite slow function
cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
try:
fspath = cache[location[0]]
except KeyError:
fspath = self.session.fspath.bestrelpath(location[0])
cache[location[0]] = fspath
location = (fspath, location[1], str(location[2]))
self._location = location
return location
@@ -336,7 +367,7 @@ class Session(FSCollector):
def __init__(self, config):
super(Session, self).__init__(py.path.local(), parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
assert self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
@@ -362,6 +393,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)
@@ -369,6 +410,7 @@ class Session(FSCollector):
self._notfound = []
self._initialpaths = set()
self._initialparts = []
self.items = items = []
for arg in args:
parts = self._parsearg(arg)
self._initialparts.append(parts)
@@ -384,7 +426,6 @@ class Session(FSCollector):
if not genitems:
return rep.result
else:
self.items = items = []
if rep.passed:
for node in rep.result:
self.items.extend(self.genitems(node))
@@ -439,16 +480,29 @@ 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 p
mod = None
path = [os.path.abspath('.')] + sys.path
for name in x.split('.'):
# ignore anything that's not a proper name here
# else something like --pyargs will mess up '.'
# since imp.find_module will actually sometimes work for it
# but it's supposed to be considered a filesystem path
# not a package
if name_re.match(name) is None:
return x
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. """
@@ -466,7 +520,7 @@ class Session(FSCollector):
raise pytest.UsageError(msg + arg)
parts[0] = path
return parts
def matchnodes(self, matching, names):
self.trace("matchnodes", matching, names)
self.trace.root.indent += 1
@@ -494,9 +548,15 @@ class Session(FSCollector):
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
has_matched = False
for x in rep.result:
if x.name == name:
resultnodes.extend(self.matchnodes([x], nextnames))
has_matched = True
# XXX accept IDs that don't have "()" for class instances
if not has_matched and len(rep.result) == 1 and x.name == "()":
nextnames.insert(0, name)
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
return resultnodes

View File

@@ -14,12 +14,37 @@ def pytest_addoption(parser):
"Terminate expression with ':' to make the first match match "
"all subsequent tests (usually file-order). ")
group._addoption("-m",
action="store", dest="markexpr", default="", metavar="MARKEXPR",
help="only run tests matching given mark expression. "
"example: -m 'mark1 and not mark2'."
)
group.addoption("--markers", action="store_true", help=
"show markers (builtin, plugin and per-project ones).")
parser.addini("markers", "markers for test functions", 'linelist')
def pytest_cmdline_main(config):
if config.option.markers:
config.pluginmanager.do_configure(config)
tw = py.io.TerminalWriter()
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
config.pluginmanager.do_unconfigure(config)
return 0
pytest_cmdline_main.tryfirst = True
def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword
if not keywordexpr:
matchexpr = config.option.markexpr
if not keywordexpr and not matchexpr:
return
selectuntil = False
if keywordexpr[-1] == ":":
if keywordexpr[-1:] == ":":
selectuntil = True
keywordexpr = keywordexpr[:-1]
@@ -29,21 +54,38 @@ def pytest_collection_modifyitems(items, config):
if keywordexpr and skipbykeyword(colitem, keywordexpr):
deselected.append(colitem)
else:
remaining.append(colitem)
if selectuntil:
keywordexpr = None
if matchexpr:
if not matchmark(colitem, matchexpr):
deselected.append(colitem)
continue
remaining.append(colitem)
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
class BoolDict:
def __init__(self, mydict):
self._mydict = mydict
def __getitem__(self, name):
return name in self._mydict
def matchmark(colitem, matchexpr):
return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__))
def pytest_configure(config):
if config.option.strict:
pytest.mark._config = config
def skipbykeyword(colitem, keywordexpr):
""" return True if they given keyword expression means to
skip this collector/item.
"""
if not keywordexpr:
return
itemkeywords = getkeywords(colitem)
for key in filter(None, keywordexpr.split()):
eor = key[:1] == '-'
@@ -77,20 +119,36 @@ class MarkGenerator:
@py.test.mark.slowtest
def test_function():
pass
will set a 'slowtest' :class:`MarkInfo` object
on the ``test_function`` object. """
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError(name)
if hasattr(self, '_config'):
self._check(name)
return MarkDecorator(name)
def _check(self, name):
try:
if name in self._markers:
return
except AttributeError:
pass
self._markers = l = set()
for line in self._config.getini("markers"):
beginning = line.split(":", 1)
x = beginning[0].split("(", 1)[0]
l.add(x)
if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,))
class MarkDecorator:
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
:ref:`retrieved by hooks as item keywords` MarkDecorator instances
are usually created by writing::
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
MarkDecorator instances are often created like this::
mark1 = py.test.mark.NAME # simple MarkDecorator
mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator
@@ -133,8 +191,7 @@ class MarkDecorator:
holder = MarkInfo(self.markname, self.args, self.kwargs)
setattr(func, self.markname, holder)
else:
holder.kwargs.update(self.kwargs)
holder.args += self.args
holder.add(self.args, self.kwargs)
return func
kw = self.kwargs.copy()
kw.update(kwargs)
@@ -150,27 +207,20 @@ class MarkInfo:
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs
self._arglist = [(args, kwargs.copy())]
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self._name, self.args, self.kwargs)
self.name, self.args, self.kwargs)
def add(self, args, kwargs):
""" add a MarkInfo with the given args and kwargs. """
self._arglist.append((args, kwargs))
self.args += args
self.kwargs.update(kwargs)
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist:
yield MarkInfo(self.name, args, kwargs)
def pytest_itemcollected(item):
if not isinstance(item, pytest.Function):
return
try:
func = item.obj.__func__
except AttributeError:
func = getattr(item.obj, 'im_func', item.obj)
pyclasses = (pytest.Class, pytest.Module)
for node in item.listchain():
if isinstance(node, pyclasses):
marker = getattr(node.obj, 'pytestmark', None)
if marker is not None:
if isinstance(marker, list):
for mark in marker:
mark(func)
else:
marker(func)
node = node.parent
item.keywords.update(py.builtin._getfuncdict(func))

View File

@@ -13,9 +13,10 @@ def pytest_funcarg__monkeypatch(request):
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
All modifications will be undone after the requesting
test function has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
"""
@@ -30,6 +31,7 @@ class monkeypatch:
def __init__(self):
self._setattr = []
self._setitem = []
self._cwd = None
def setattr(self, obj, name, value, raising=True):
""" set attribute ``name`` on ``obj`` to ``value``, by default
@@ -83,6 +85,17 @@ class monkeypatch:
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
def chdir(self, path):
""" change the current working directory to the specified path
path can be a string or a py.path.local object
"""
if self._cwd is None:
self._cwd = os.getcwd()
if hasattr(path, "chdir"):
path.chdir()
else:
os.chdir(path)
def undo(self):
""" undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
@@ -95,9 +108,17 @@ class monkeypatch:
self._setattr[:] = []
for dictionary, name, value in self._setitem:
if value is notset:
del dictionary[name]
try:
del dictionary[name]
except KeyError:
pass # was already deleted, so we have the desired state
else:
dictionary[name] = value
self._setitem[:] = []
if hasattr(self, '_savesyspath'):
sys.path[:] = self._savesyspath
del self._savesyspath
if self._cwd is not None:
os.chdir(self._cwd)
self._cwd = None

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

@@ -38,7 +38,11 @@ def pytest_unconfigure(config):
del tr._tw.__dict__['write']
def getproxy():
return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
if sys.version_info < (3, 0):
from xmlrpclib import ServerProxy
else:
from xmlrpc.client import ServerProxy
return ServerProxy(url.xmlrpc).pastes
def pytest_terminal_summary(terminalreporter):
if terminalreporter.config.option.pastebin != "failed":

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
@@ -52,12 +62,21 @@ class PdbInvoke:
if "xfail" in rep.keywords:
return rep
# we assume that the above execute() suspended capturing
tw = py.io.TerminalWriter()
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
# for not completely clear reasons.
tw = item.config.pluginmanager.getplugin("terminalreporter")._tw
tw.line()
tw.sep(">", "traceback")
rep.toterminal(tw)
tw.sep(">", "entering PDB")
post_mortem(call.excinfo._excinfo[2])
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:
if isinstance(call.excinfo.value, py.std.doctest.UnexpectedException):
tb = call.excinfo.value.exc_info[2]
else:
tb = call.excinfo._excinfo[2]
post_mortem(tb)
rep._pdbshown = True
return rep

View File

@@ -6,7 +6,7 @@ import re
import inspect
import time
from fnmatch import fnmatch
from _pytest.session 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)
@@ -236,13 +237,14 @@ class TmpTestdir:
def _makefile(self, ext, args, kwargs):
items = list(kwargs.items())
if args:
source = "\n".join(map(str, args)) + "\n"
source = py.builtin._totext("\n").join(
map(py.builtin._totext, args)) + py.builtin._totext("\n")
basename = self.request.function.__name__
items.insert(0, (basename, source))
ret = None
for name, value in items:
p = self.tmpdir.join(name).new(ext=ext)
source = str(py.code.Source(value)).lstrip()
source = py.builtin._totext(py.code.Source(value)).lstrip()
p.write(source.encode("utf-8"), "wb")
if ret is None:
ret = p
@@ -291,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
@@ -306,14 +314,6 @@ class TmpTestdir:
result.extend(session.genitems(colitem))
return result
def inline_genitems(self, *args):
#config = self.parseconfig(*args)
config = self.parseconfigure(*args)
rec = self.getreportrecorder(config)
session = Session(config)
session.perform_collect()
return session.items, rec
def runitem(self, source):
# used from runner functional tests
item = self.getitem(source)
@@ -334,62 +334,57 @@ class TmpTestdir:
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 1, reports
return reports[0]
assert len(reports) == 3, reports # setup/call/teardown
return reports[1]
def inline_genitems(self, *args):
return self.inprocess_run(list(args) + ['--collectonly'])
def inline_run(self, *args):
args = ("-s", ) + args # otherwise FD leakage
config = self.parseconfig(*args)
reprec = self.getreportrecorder(config)
#config.pluginmanager.do_configure(config)
config.hook.pytest_cmdline_main(config=config)
#config.pluginmanager.do_unconfigure(config)
return reprec
items, rec = self.inprocess_run(args)
return rec
def config_preparse(self):
config = self.Config()
for plugin in self.plugins:
if isinstance(plugin, str):
config.pluginmanager.import_plugin(plugin)
else:
if isinstance(plugin, dict):
plugin = PseudoPlugin(plugin)
if not config.pluginmanager.isregistered(plugin):
config.pluginmanager.register(plugin)
return config
def inprocess_run(self, args, plugins=None):
rec = []
items = []
class Collect:
def pytest_configure(x, config):
rec.append(self.getreportrecorder(config))
def pytest_itemcollected(self, item):
items.append(item)
if not plugins:
plugins = []
plugins.append(Collect())
ret = self.pytestmain(list(args), plugins=[Collect()])
reprec = rec[0]
reprec.ret = ret
assert len(rec) == 1
return items, reprec
def parseconfig(self, *args):
if not args:
args = (self.tmpdir,)
config = self.config_preparse()
args = list(args)
args = [str(x) for x in args]
for x in args:
if str(x).startswith('--basetemp'):
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
config.parse(args)
import _pytest.core
config = _pytest.core._prepareconfig(args, self.plugins)
# the in-process pytest invocation needs to avoid leaking FDs
# so we register a "reset_capturings" callmon the capturing manager
# and make sure it gets called
config._cleanup.append(
config.pluginmanager.getplugin("capturemanager").reset_capturings)
import _pytest.config
self.request.addfinalizer(
lambda: _pytest.config.pytest_unconfigure(config))
return config
def reparseconfig(self, args=None):
""" this is used from tests that want to re-invoke parse(). """
if not args:
args = [self.tmpdir]
oldconfig = getattr(py.test, 'config', None)
try:
c = py.test.config = self.Config()
c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
keep=0, rootdir=self.tmpdir, lock_timeout=None)
c.parse(args)
return c
finally:
py.test.config = oldconfig
def parseconfigure(self, *args):
config = self.parseconfig(*args)
config.pluginmanager.do_configure(config)
self.request.addfinalizer(lambda:
config.pluginmanager.do_unconfigure(config))
config.pluginmanager.do_unconfigure(config))
return config
def getitem(self, source, funcname="test_func"):
@@ -409,7 +404,6 @@ class TmpTestdir:
self.makepyfile(__init__ = "#")
self.config = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path)
#config.pluginmanager.do_unconfigure(config)
return node
def collect_by_name(self, modcol, name):
@@ -426,9 +420,16 @@ class TmpTestdir:
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
def pytestmain(self, *args, **kwargs):
ret = pytest.main(*args, **kwargs)
if ret == 2:
raise KeyboardInterrupt()
class ResetCapturing:
@pytest.mark.trylast
def pytest_unconfigure(self, config):
capman = config.pluginmanager.getplugin("capturemanager")
capman.reset_capturings()
plugins = kwargs.setdefault("plugins", [])
rc = ResetCapturing()
plugins.append(rc)
return pytest.main(*args, **kwargs)
def run(self, *cmdargs):
return self._run(*cmdargs)
@@ -515,6 +516,10 @@ class TmpTestdir:
def spawn(self, cmd, expect_timeout=10.0):
pexpect = py.test.importorskip("pexpect", "2.4")
if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine():
pytest.skip("pypy-64 bit not supported")
if sys.platform == "darwin":
pytest.xfail("pexpect does not work reliably on darwin?!")
logfile = self.tmpdir.join("spawn.out")
child = pexpect.spawn(cmd, logfile=logfile.open("w"))
child.timeout = expect_timeout
@@ -527,10 +532,6 @@ def getdecoded(out):
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
class PseudoPlugin:
def __init__(self, vars):
self.__dict__.update(vars)
class ReportRecorder(object):
def __init__(self, hook):
self.hook = hook
@@ -552,10 +553,17 @@ class ReportRecorder(object):
def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
return [x.report for x in self.getcalls(names)]
def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None):
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
for rep in self.getreports(names=names):
try:
if not when and rep.when != "call" and rep.passed:
# setup/teardown passing reports - let's ignore those
continue
except AttributeError:
pass
if when and getattr(rep, 'when', None) != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):

View File

@@ -4,6 +4,7 @@ import inspect
import sys
import pytest
from py._code.code import TerminalRepr
from _pytest.monkeypatch import monkeypatch
import _pytest
cutdir = py.path.local(_pytest.__file__).dirpath()
@@ -26,6 +27,24 @@ def pytest_cmdline_main(config):
showfuncargs(config)
return 0
def pytest_generate_tests(metafunc):
try:
param = metafunc.function.parametrize
except AttributeError:
return
for p in param:
metafunc.parametrize(*p.args, **p.kwargs)
def pytest_configure(config):
config.addinivalue_line("markers",
"parametrize(argnames, argvalues): call a test function multiple "
"times passing in multiple different argument value sets. Example: "
"@parametrize('arg1', [1,2]) would lead to two calls of the decorated "
"test function, one with arg1=1 and another with arg1=2."
)
@pytest.mark.trylast
def pytest_namespace():
raises.Exception = pytest.fail.Exception
@@ -70,10 +89,13 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
res = __multicall__.execute()
if res is not None:
return res
if collector._istestclasscandidate(name, obj):
if inspect.isclass(obj):
#if hasattr(collector.obj, 'unittest'):
# return # we assume it's a mixin class for a TestCase derived one
return Class(name, parent=collector)
if collector.classnamefilter(name):
if not hasinit(obj):
Class = collector._getcustomclass("Class")
return Class(name, parent=collector)
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
if is_generator(obj):
return Generator(name, parent=collector)
@@ -135,6 +157,7 @@ class PyobjMixin(object):
obj = obj.place_as
self._fslineno = py.code.getfslineno(obj)
assert isinstance(self._fslineno[1], int), obj
return self._fslineno
def reportinfo(self):
@@ -152,6 +175,7 @@ class PyobjMixin(object):
else:
fspath, lineno = self._getfslineno()
modpath = self.getmodpath()
assert isinstance(lineno, int)
return fspath, lineno, modpath
class PyCollectorMixin(PyobjMixin, pytest.Collector):
@@ -160,7 +184,7 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
for prefix in self.config.getini("python_functions"):
if name.startswith(prefix):
return True
def classnamefilter(self, name):
for prefix in self.config.getini("python_classes"):
if name.startswith(prefix):
@@ -193,18 +217,11 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
return self.ihook.pytest_pycollect_makeitem(
collector=self, name=name, obj=obj)
def _istestclasscandidate(self, name, obj):
if self.classnamefilter(name) and \
inspect.isclass(obj):
if hasinit(obj):
# XXX WARN
return False
return True
def _genfunctions(self, name, funcobj):
module = self.getparent(Module).obj
clscol = self.getparent(Class)
cls = clscol and clscol.obj or None
transfer_markers(funcobj, cls, module)
metafunc = Metafunc(funcobj, config=self.config,
cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests
@@ -213,6 +230,7 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
extra.append(cls())
plugins = self.getplugins() + extra
gentesthook.pcall(plugins, metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls:
return Function(name, parent=self)
l = []
@@ -223,6 +241,20 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
l.append(function)
return l
def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark
# plugin should merge with the python plugin.
for holder in (cls, mod):
try:
pytestmark = holder.pytestmark
except AttributeError:
continue
if isinstance(pytestmark, list):
for mark in pytestmark:
mark(funcobj)
else:
pytestmark(funcobj)
class Module(pytest.File, PyCollectorMixin):
def _getobj(self):
return self._memoizedcall('_obj', self._importtestmodule)
@@ -242,7 +274,8 @@ class Module(pytest.File, PyCollectorMixin):
" %s\n"
"which is not the same as the test file we want to collect:\n"
" %s\n"
"HINT: use a unique basename for your test file modules"
"HINT: remove __pycache__ / .pyc files and/or use a "
"unique basename for your test file modules"
% e.args
)
#print "imported test module", mod
@@ -272,7 +305,7 @@ class Module(pytest.File, PyCollectorMixin):
class Class(PyCollectorMixin, pytest.Collector):
def collect(self):
return [Instance(name="()", parent=self)]
return [self._getcustomclass("Instance")(name="()", parent=self)]
def setup(self):
setup_class = getattr(self.obj, 'setup_class', None)
@@ -297,18 +330,19 @@ class Instance(PyCollectorMixin, pytest.Collector):
class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator.
"""
def setup(self):
""" perform setup for this test function. """
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
if isinstance(self.parent, Instance):
if hasattr(self, '_preservedparent'):
obj = self._preservedparent
elif isinstance(self.parent, Instance):
obj = self.parent.newinstance()
self.obj = self._getobj()
else:
obj = self.parent.obj
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
setup_func_or_method = getattr(obj, name, None)
if setup_func_or_method is not None:
setup_func_or_method(self.obj)
@@ -371,13 +405,15 @@ class FuncargLookupErrorRepr(TerminalRepr):
tw.line()
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
def collect(self):
# 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 = []
seen = {}
for i, x in enumerate(self.obj()):
@@ -391,7 +427,7 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
if name in seen:
raise ValueError("%r generated tests with non-unique name %r" %(self, name))
seen[name] = True
l.append(Function(name, self, args=args, callobj=call))
l.append(self.Function(name, self, args=args, callobj=call))
return l
def getcallargs(self, obj):
@@ -426,6 +462,7 @@ class Function(FunctionMixin, pytest.Item):
"yielded functions (deprecated) cannot have funcargs")
else:
if callspec is not None:
self.callspec = callspec
self.funcargs = callspec.funcargs or {}
self._genid = callspec.id
if hasattr(callspec, "param"):
@@ -484,10 +521,11 @@ def hasinit(obj):
return True
def getfuncargnames(function):
def getfuncargnames(function, startindex=None):
# XXX merge with main.py's varnames
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
startindex = py.std.inspect.ismethod(function) and 1 or 0
if startindex is None:
startindex = py.std.inspect.ismethod(function) and 1 or 0
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)
@@ -501,47 +539,137 @@ def fillfuncargs(function):
request._fillfuncargs()
_notexists = object()
class CallSpec:
def __init__(self, funcargs, id, param):
self.funcargs = funcargs
self.id = id
class CallSpec2(object):
def __init__(self, metafunc):
self.metafunc = metafunc
self.funcargs = {}
self._idlist = []
self.params = {}
self._globalid = _notexists
self._globalid_args = set()
self._globalparam = _notexists
def copy(self, metafunc):
cs = CallSpec2(self.metafunc)
cs.funcargs.update(self.funcargs)
cs.params.update(self.params)
cs._idlist = list(self._idlist)
cs._globalid = self._globalid
cs._globalid_args = self._globalid_args
cs._globalparam = self._globalparam
return cs
def _checkargnotcontained(self, arg):
if arg in self.params or arg in self.funcargs:
raise ValueError("duplicate %r" %(arg,))
def getparam(self, name):
try:
return self.params[name]
except KeyError:
if self._globalparam is _notexists:
raise ValueError(name)
return self._globalparam
@property
def id(self):
return "-".join(map(str, filter(None, self._idlist)))
def setmulti(self, valtype, argnames, valset, id):
for arg,val in zip(argnames, valset):
self._checkargnotcontained(arg)
getattr(self, valtype)[arg] = val
self._idlist.append(id)
def setall(self, funcargs, id, param):
for x in funcargs:
self._checkargnotcontained(x)
self.funcargs.update(funcargs)
if id is not _notexists:
self._idlist.append(id)
if param is not _notexists:
self.param = param
def __repr__(self):
return "<CallSpec id=%r param=%r funcargs=%r>" %(
self.id, getattr(self, 'param', '?'), self.funcargs)
assert self._globalparam is _notexists
self._globalparam = param
class Metafunc:
def __init__(self, function, config=None, cls=None, module=None):
self.config = config
self.module = module
self.function = function
self.funcargnames = getfuncargnames(function)
self.funcargnames = getfuncargnames(function,
startindex=int(cls is not None))
self.cls = cls
self.module = module
self._calls = []
self._ids = py.builtin.set()
def parametrize(self, argnames, argvalues, indirect=False, ids=None):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
during the collection phase. If you need to setup expensive resources
you may pass indirect=True and implement a funcarg factory which can
perform the expensive setup just before a test is actually run.
:arg argnames: an argument name or a list of argument names
:arg argvalues: a list of values for the argname or a list of tuples of
values for the list of argument names.
:arg indirect: if True each argvalue corresponding to an argument will
be passed as request.param to its respective funcarg factory so
that it can perform more expensive setups during the setup phase of
a test rather than at collection time.
:arg ids: list of string ids each corresponding to the argvalues so
that they are part of the test id. If no ids are provided they will
be generated automatically from the argvalues.
"""
if not isinstance(argnames, (tuple, list)):
argnames = (argnames,)
argvalues = [(val,) for val in argvalues]
if not indirect:
#XXX should we also check for the opposite case?
for arg in argnames:
if arg not in self.funcargnames:
raise ValueError("%r has no argument %r" %(self.function, arg))
valtype = indirect and "params" or "funcargs"
if not ids:
idmaker = IDMaker()
ids = list(map(idmaker, argvalues))
newcalls = []
for callspec in self._calls or [CallSpec2(self)]:
for i, valset in enumerate(argvalues):
assert len(valset) == len(argnames)
newcallspec = callspec.copy(self)
newcallspec.setmulti(valtype, argnames, valset, ids[i])
newcalls.append(newcallspec)
self._calls = newcalls
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
""" add a new call to the underlying test function during the
collection phase of a test run.
""" (deprecated, use parametrize) Add a new call to the underlying
test function during the collection phase of a test run. Note that
request.addcall() is called during the test collection phase prior and
independently to actual test execution. You should only use addcall()
if you need to specify multiple arguments of a test function.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` the length of the currently
list of calls to the test function will be used.
:arg param: will be exposed to a later funcarg factory invocation
through the ``request.param`` attribute. Setting it (instead of
directly providing a ``funcargs`` ditionary) is called
*indirect parametrization*. Indirect parametrization is
preferable if test values are expensive to setup or can
only be created after certain fixtures or test-run related
initialization code has been run.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` an automatic unique id will be generated.
:arg param: a parameter which will be exposed to a later funcarg factory
invocation through the ``request.param`` attribute.
"""
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
if name not in self.funcargnames:
pytest.fail("funcarg %r not used in this function." % name)
else:
funcargs = {}
if id is None:
raise ValueError("id=None not allowed")
if id is _notexists:
@@ -550,16 +678,37 @@ class Metafunc:
if id in self._ids:
raise ValueError("duplicate id %r" % id)
self._ids.add(id)
self._calls.append(CallSpec(funcargs, id, param))
cs = CallSpec2(self)
cs.setall(funcargs, id, param)
self._calls.append(cs)
class IDMaker:
def __init__(self):
self.counter = 0
def __call__(self, valset):
l = []
for val in valset:
if not isinstance(val, (int, str)):
val = "."+str(self.counter)
self.counter += 1
l.append(str(val))
return "-".join(l)
class FuncargRequest:
""" A request for function arguments from a test function. """
""" A request for function arguments from a test function.
Note that there is an optional ``param`` attribute in case
there was an invocation to metafunc.addcall(param=...).
If no such call was done in a ``pytest_generate_tests``
hook, the attribute will not be present.
"""
_argprefix = "pytest_funcarg__"
_argname = None
class LookupError(LookupError):
""" error on performing funcarg request. """
def __init__(self, pyfuncitem):
self._pyfuncitem = pyfuncitem
if hasattr(pyfuncitem, '_requestparam'):
@@ -587,7 +736,7 @@ class FuncargRequest:
def module(self):
""" module where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
@property
def cls(self):
""" class (can be None) where the test function was collected. """
@@ -603,7 +752,7 @@ class FuncargRequest:
def config(self):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@property
def fspath(self):
""" the file system path of the test module which collected this test. """
@@ -620,7 +769,7 @@ class FuncargRequest:
def applymarker(self, marker):
""" apply a marker to a single test function invocation.
""" Apply a marker to a single test function invocation.
This method is useful if you don't want to have a keyword/marker
on all function invocations.
@@ -632,7 +781,7 @@ class FuncargRequest:
self._pyfuncitem.keywords[marker.markname] = marker
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" return a testing resource managed by ``setup`` &
""" Return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource.
@@ -681,11 +830,18 @@ class FuncargRequest:
self._raiselookupfailed(argname)
funcargfactory = self._name2factory[argname].pop()
oldarg = self._currentarg
self._currentarg = argname
mp = monkeypatch()
mp.setattr(self, '_currentarg', argname)
try:
param = self._pyfuncitem.callspec.getparam(argname)
except (AttributeError, ValueError):
pass
else:
mp.setattr(self, 'param', param, raising=False)
try:
self._funcargs[argname] = res = funcargfactory(request=self)
finally:
self._currentarg = oldarg
mp.undo()
return res
def _getscopeitem(self, scope):
@@ -709,7 +865,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):
@@ -730,8 +886,10 @@ class FuncargRequest:
raise self.LookupError(msg)
def showfuncargs(config):
from _pytest.session 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()
@@ -777,7 +935,7 @@ def getlocation(function, curdir):
def raises(ExpectedException, *args, **kwargs):
""" assert that a code block/function call raises @ExpectedException
and raise a failure exception otherwise.
If using Python 2.5 or above, you may use this function as a
context manager::
@@ -798,9 +956,8 @@ def raises(ExpectedException, *args, **kwargs):
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
A third possibility is to use a string which which will
be executed::
A third possibility is to use a string to be executed::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
"""
@@ -849,3 +1006,4 @@ class RaisesContext(object):
pytest.fail("DID NOT RAISE")
self.excinfo.__init__(tp)
return issubclass(self.excinfo.type, self.ExpectedException)

View File

@@ -8,6 +8,9 @@ def pytest_funcarg__recwarn(request):
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
import warnings

View File

@@ -1,12 +1,12 @@
""" (disabled by default) create result information in a plain text file. """
import py
from py.builtin import print_
def pytest_addoption(parser):
group = parser.getgroup("resultlog", "resultlog plugin options")
group.addoption('--resultlog', action="store", dest="resultlog", metavar="path", default=None,
help="path for machine-readable result log.")
group = parser.getgroup("terminal reporting", "resultlog plugin options")
group.addoption('--resultlog', action="store", dest="resultlog",
metavar="path", default=None,
help="path for machine-readable result log.")
def pytest_configure(config):
resultlog = config.option.resultlog
@@ -52,9 +52,9 @@ class ResultLog(object):
self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, lettercode, longrepr):
print_("%s %s" % (lettercode, testpath), file=self.logfile)
py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
for line in longrepr.splitlines():
print_(" %s" % line, file=self.logfile)
py.builtin.print_(" %s" % line, file=self.logfile)
def log_outcome(self, report, lettercode, longrepr):
testpath = getattr(report, 'nodeid', None)
@@ -63,6 +63,8 @@ class ResultLog(object):
self.write_log_entry(testpath, lettercode, longrepr)
def pytest_runtest_logreport(self, report):
if report.when != "call" and report.passed:
return
res = self.config.hook.pytest_report_teststatus(report=report)
code = res[1]
if code == 'x':
@@ -89,5 +91,8 @@ class ResultLog(object):
self.log_outcome(report, code, longrepr)
def pytest_internalerror(self, excrepr):
path = excrepr.reprcrash.path
reprcrash = getattr(excrepr, 'reprcrash', None)
path = getattr(reprcrash, "path", None)
if path is None:
path = "cwd:%s" % py.path.local()
self.write_log_entry(path, '!', str(excrepr))

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,53 +14,70 @@ 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_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group.addoption('--durations',
action="store", type="int", default=None, metavar="N",
help="show N slowest setup/test durations (N=0 for all)."),
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
def pytest_terminal_summary(terminalreporter):
durations = terminalreporter.config.option.durations
if durations is None:
return
tr = terminalreporter
dlist = []
for replist in tr.stats.values():
for rep in replist:
if hasattr(rep, 'duration'):
dlist.append(rep)
if not dlist:
return
dlist.sort(key=lambda x: x.duration)
dlist.reverse()
if not durations:
tr.write_sep("=", "slowest test durations")
else:
tr.write_sep("=", "slowest %s test durations" % durations)
dlist = dlist[:durations]
for rep in dlist:
nodeid = rep.nodeid.replace("::()::", "::")
tr.write_line("%02.2fs %-8s %s" %
(rep.duration, rep.when, nodeid))
def pytest_sessionstart(session):
session._setupstate = SetupState()
def pytest_sessionfinish(session):
session._setupstate.teardown_all()
class NodeInfo:
def __init__(self, location):
self.location = location
def pytest_runtest_protocol(item):
def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(
nodeid=item.nodeid, location=item.location,
)
runtestprotocol(item)
runtestprotocol(item, nextitem=nextitem)
return True
def runtestprotocol(item, log=True):
def runtestprotocol(item, log=True, nextitem=None):
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log))
reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem))
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)
def pytest__teardown_final(session):
call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
if call.excinfo:
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
call.excinfo.traceback = ntraceback.filter()
longrepr = call.excinfo.getrepr(funcargs=True)
return TeardownErrorReport(longrepr)
def pytest_runtest_teardown(item, nextitem):
item.session._setupstate.teardown_exact(item, nextitem)
def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"):
@@ -76,18 +93,18 @@ def pytest_report_teststatus(report):
#
# Implementation
def call_and_report(item, when, log=True):
call = call_runtest_hook(item, when)
def call_and_report(item, when, log=True, **kwds):
call = call_runtest_hook(item, when, **kwds)
hook = item.ihook
report = hook.pytest_runtest_makereport(item=item, call=call)
if log and (when == "call" or not report.passed):
if log:
hook.pytest_runtest_logreport(report=report)
return report
def call_runtest_hook(item, when):
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(lambda: ihook(item=item), when=when)
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
class CallInfo:
""" Result/Exception info a function invocation. """
@@ -97,12 +114,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:
@@ -122,6 +143,10 @@ def getslaveinfoline(node):
return s
class BaseReport(object):
def __init__(self, **kw):
self.__dict__.update(kw)
def toterminal(self, out):
longrepr = self.longrepr
if hasattr(self, 'node'):
@@ -141,6 +166,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:
@@ -153,7 +179,7 @@ def pytest_runtest_makereport(item, call):
longrepr = excinfo
elif excinfo.errisinstance(py.test.skip.Exception):
outcome = "skipped"
r = item._repr_failure_py(excinfo, "line").reprcrash
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
@@ -162,14 +188,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, **extra):
#: normalized collection node id
self.nodeid = nodeid
@@ -181,16 +208,25 @@ class TestReport(BaseReport):
#: a name -> value dictionary containing all keywords and
#: markers associated with a test invocation.
self.keywords = keywords
#: test outcome, always one of "passed", "failed", "skipped".
self.outcome = outcome
#: None or a failure representation.
self.longrepr = longrepr
#: 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
self.__dict__.update(extra)
def __repr__(self):
return "<TestReport %r when=%r outcome=%r>" % (
self.nodeid, self.when, self.outcome)
@@ -198,8 +234,10 @@ class TestReport(BaseReport):
class TeardownErrorReport(BaseReport):
outcome = "failed"
when = "teardown"
def __init__(self, longrepr):
def __init__(self, longrepr, **extra):
self.longrepr = longrepr
self.sections = []
self.__dict__.update(extra)
def pytest_make_collect_report(collector):
call = CallInfo(collector._memocollect, "memocollect")
@@ -221,11 +259,13 @@ 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=(), **extra):
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
self.result = result or []
self.sections = list(sections)
self.__dict__.update(extra)
@property
def location(self):
@@ -279,20 +319,22 @@ class SetupState(object):
self._teardown_with_finalization(None)
assert not self._finalizers
def teardown_exact(self, item):
if self.stack and item == self.stack[-1]:
def teardown_exact(self, item, nextitem):
needed_collectors = nextitem and nextitem.listchain() or []
self._teardown_towards(needed_collectors)
def _teardown_towards(self, needed_collectors):
while self.stack:
if self.stack == needed_collectors[:len(self.stack)]:
break
self._pop_and_teardown()
else:
self._callfinalizers(item)
def prepare(self, colitem):
""" setup objects along the collector chain to the test-method
and teardown previously setup objects."""
needed_collectors = colitem.listchain()
while self.stack:
if self.stack == needed_collectors[:len(self.stack)]:
break
self._pop_and_teardown()
self._teardown_towards(needed_collectors)
# check if the last collection node has raised an error
for col in self.stack:
if hasattr(col, '_prepare_exc'):

View File

@@ -1,6 +1,7 @@
""" support for skip/xfail functions and markers. """
import py, pytest
import sys
def pytest_addoption(parser):
group = parser.getgroup("general")
@@ -8,6 +9,21 @@ def pytest_addoption(parser):
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
def pytest_configure(config):
config.addinivalue_line("markers",
"skipif(*conditions): skip the given test function if evaluation "
"of all conditions has a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. "
)
config.addinivalue_line("markers",
"xfail(*conditions, reason=None, run=True): mark the the test function "
"as an expected failure. Optionally specify a reason and run=False "
"if you don't even want to execute the test function. Any positional "
"condition strings will be evaluated (like with skipif) and if one is "
"False the marker will not be applied."
)
def pytest_namespace():
return dict(xfail=xfail)
@@ -32,9 +48,39 @@ class MarkEvaluator:
return bool(self.holder)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
def istrue(self):
try:
return self._istrue()
except KeyboardInterrupt:
raise
except:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",]
msg.append("SyntaxError: invalid syntax")
else:
msg = py.std.traceback.format_exception_only(*self.exc[:2])
pytest.fail("Error evaluating %r expression\n"
" %s\n"
"%s"
%(self.name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
func = self.item.obj
try:
d.update(func.__globals__)
except AttributeError:
d.update(func.func_globals)
return d
def _istrue(self):
if self.holder:
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
d = self._getglobals()
if self.holder.args:
self.result = False
for expr in self.holder.args:
@@ -42,7 +88,7 @@ class MarkEvaluator:
if isinstance(expr, str):
result = cached_eval(self.item.config, expr, d)
else:
result = expr
pytest.fail("expression is not a string")
if result:
self.result = True
self.expr = expr
@@ -60,7 +106,7 @@ class MarkEvaluator:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + self.expr
return "condition: " + str(self.expr)
return expl
@@ -97,19 +143,20 @@ def pytest_runtest_makereport(__multicall__, item, call):
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
return rep
if call.when == "call":
rep = __multicall__.execute()
evalxfail = getattr(item, '_evalxfail')
if not item.config.getvalue("runxfail") and evalxfail.istrue():
rep = __multicall__.execute()
evalxfail = item._evalxfail
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
else:
rep.keywords['xfail'] = evalxfail.getexplanation()
elif call.when == "call":
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
else:
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
rep.keywords['xfail'] = evalxfail.getexplanation()
return rep
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
@@ -137,21 +184,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")
@@ -179,7 +228,8 @@ def cached_eval(config, expr, d):
except KeyError:
#import sys
#print >>sys.stderr, ("cache-miss: %r" % expr)
config._evalcache[expr] = x = eval(expr, d)
exprcode = py.code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x

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).")
@@ -25,29 +25,27 @@ def pytest_addoption(parser):
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/no).")
help="traceback print mode (long/short/line/native/no).")
group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut).")
def pytest_configure(config):
config.option.verbose -= config.option.quiet
if config.option.collectonly:
reporter = CollectonlyReporter(config)
else:
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
reporter = TerminalReporter(config, stdout)
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._cleanup.append(lambda: stdout.close())
reporter = TerminalReporter(config, stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
@@ -55,11 +53,6 @@ def pytest_configure(config):
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
def pytest_unconfigure(config):
if hasattr(config, '_toclose'):
#print "closing", config._toclose, config._toclose.fileno()
config._toclose.close()
def getreportopt(config):
reportopts = ""
optvalue = config.option.report
@@ -136,6 +129,9 @@ class TerminalReporter:
self._tw.line()
self.currentfspath = None
def write(self, content, **markup):
self._tw.write(content, **markup)
def write_line(self, line, **markup):
line = str(line)
self.ensure_newline()
@@ -165,9 +161,6 @@ class TerminalReporter:
def pytest_deselected(self, items):
self.stats.setdefault('deselected', []).extend(items)
def pytest__teardown_final_logerror(self, report):
self.stats.setdefault("error", []).append(report)
def pytest_runtest_logstart(self, nodeid, location):
# ensure that the path is printed before the
# 1st test of a module starts running
@@ -215,7 +208,7 @@ class TerminalReporter:
def pytest_collection(self):
if not self.hasmarkup:
self.write_line("collecting ...", bold=True)
self.write("collecting ... ", bold=True)
def pytest_collectreport(self, report):
if report.failed:
@@ -259,7 +252,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):
@@ -270,12 +263,53 @@ class TerminalReporter:
for line in flatten(lines):
self.write_line(line)
def pytest_collection_finish(self):
def pytest_collection_finish(self, session):
if self.config.option.collectonly:
self._printcollecteditems(session.items)
if self.stats.get('failed'):
self._tw.sep("!", "collection failures")
for rep in self.stats.get('failed'):
rep.toterminal(self._tw)
return 1
return 0
if not self.showheader:
return
#for i, testarg in enumerate(self.config.args):
# self.write_line("test path %d: %s" %(i+1, testarg))
def _printcollecteditems(self, items):
# to print out items and their parent collectors
# we take care to leave out Instances aka ()
# because later versions are going to get rid of them anyway
if self.config.option.verbose < 0:
if self.config.option.verbose < -1:
counts = {}
for item in items:
name = item.nodeid.split('::', 1)[0]
counts[name] = counts.get(name, 0) + 1
for name, count in sorted(counts.items()):
self._tw.line("%s: %d" % (name, count))
else:
for item in items:
nodeid = item.nodeid
nodeid = nodeid.replace("::()::", "::")
self._tw.line(nodeid)
return
stack = []
indent = ""
for item in items:
needed_collectors = item.listchain()[1:] # strip root node
while stack:
if stack == needed_collectors[:len(stack)]:
break
stack.pop()
for col in needed_collectors[len(stack):]:
stack.append(col)
#if col.name == "()":
# continue
indent = (len(stack)-1) * " "
self._tw.line("%s%s" %(indent, col))
def pytest_sessionfinish(self, exitstatus, __multicall__):
__multicall__.execute()
self._tw.line("")
@@ -285,12 +319,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
@@ -302,19 +341,19 @@ class TerminalReporter:
excrepr.reprcrash.toterminal(self._tw)
def _locationline(self, collect_fspath, fspath, lineno, domain):
if fspath and fspath != collect_fspath:
# collect_fspath comes from testid which has a "/"-normalized path
if fspath and fspath.replace("\\", "/") != collect_fspath:
fspath = "%s <- %s" % (collect_fspath, fspath)
if lineno is not None:
lineno += 1
if fspath and lineno and domain:
line = "%(fspath)s:%(lineno)s: %(domain)s"
elif fspath and domain:
line = "%(fspath)s: %(domain)s"
elif fspath and lineno:
line = "%(fspath)s:%(lineno)s %(extrapath)s"
if fspath:
line = str(fspath)
if lineno is not None:
lineno += 1
line += ":" + str(lineno)
if domain:
line += ": " + str(domain)
else:
line = "[nolocation]"
return line % locals() + " "
line = "[location]"
return line + " "
def _getfailureheadline(self, rep):
if hasattr(rep, 'location'):
@@ -355,7 +394,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":
@@ -373,7 +412,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
@@ -384,9 +431,10 @@ class TerminalReporter:
keys.append(key)
parts = []
for key in keys:
val = self.stats.get(key, None)
if val:
parts.append("%d %s" %(len(val), key))
if key: # setup/teardown reports have an empty key, ignore them
val = self.stats.get(key, None)
if val:
parts.append("%d %s" %(len(val), key))
line = ", ".join(parts)
# XXX coloring
msg = "%s in %.2f seconds" %(line, session_duration)
@@ -397,54 +445,15 @@ class TerminalReporter:
def summary_deselected(self):
if 'deselected' in self.stats:
l = []
k = self.config.option.keyword
if k:
l.append("-k%s" % k)
m = self.config.option.markexpr
if m:
l.append("-m %r" % m)
self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True)
class CollectonlyReporter:
INDENT = " "
def __init__(self, config, out=None):
self.config = config
if out is None:
out = py.std.sys.stdout
self._tw = py.io.TerminalWriter(out)
self.indent = ""
self._failed = []
def outindent(self, line):
self._tw.line(self.indent + str(line))
def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"):
self._tw.line("INTERNALERROR> " + line)
def pytest_collectstart(self, collector):
if collector.session != collector:
self.outindent(collector)
self.indent += self.INDENT
def pytest_itemcollected(self, item):
self.outindent(item)
def pytest_collectreport(self, report):
if not report.passed:
if hasattr(report.longrepr, 'reprcrash'):
msg = report.longrepr.reprcrash.message
else:
# XXX unify (we have CollectErrorRepr here)
msg = str(report.longrepr[2])
self.outindent("!!! %s !!!" % msg)
#self.outindent("!!! error !!!")
self._failed.append(report)
self.indent = self.indent[:-len(self.INDENT)]
def pytest_collection_finish(self):
if self._failed:
self._tw.sep("!", "collection failures")
for rep in self._failed:
rep.toterminal(self._tw)
return self._failed and 1 or 0
len(self.stats['deselected']), " ".join(l)), bold=True)
def repr_pythonversion(v=None):
if v is None:

View File

@@ -46,20 +46,17 @@ class TempdirHandler:
def finish(self):
self.trace("finish")
def pytest_configure(config):
config._mp = mp = monkeypatch()
mp = monkeypatch()
t = TempdirHandler(config)
config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
def pytest_unconfigure(config):
config._tmpdirhandler.finish()
config._mp.undo()
def pytest_funcarg__tmpdir(request):
"""return a temporary directory path object
unique to each test function invocation,
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
@@ -67,5 +64,5 @@ def pytest_funcarg__tmpdir(request):
name = request._pyfuncitem.name
name = py.std.re.sub("[\W]", "_", name)
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
return x.realpath()
return x

View File

@@ -2,6 +2,9 @@
import pytest, py
import sys, pdb
# for transfering markers
from _pytest.python import transfer_markers
def pytest_pycollect_makeitem(collector, name, obj):
unittest = sys.modules.get('unittest')
if unittest is None:
@@ -19,7 +22,14 @@ def pytest_pycollect_makeitem(collector, name, obj):
class UnitTestCase(pytest.Class):
def collect(self):
loader = py.std.unittest.TestLoader()
module = self.getparent(pytest.Module).obj
cls = self.obj
for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name)
funcobj = getattr(x, 'im_func', x)
transfer_markers(funcobj, cls, module)
if hasattr(funcobj, 'todo'):
pytest.mark.xfail(reason=str(funcobj.todo))(funcobj)
yield TestCaseFunction(name, parent=self)
def setup(self):
@@ -37,15 +47,13 @@ class UnitTestCase(pytest.Class):
class TestCaseFunction(pytest.Function):
_excinfo = None
def __init__(self, name, parent):
super(TestCaseFunction, self).__init__(name, parent)
if hasattr(self._obj, 'todo'):
getattr(self._obj, 'im_func', self._obj).xfail = \
pytest.mark.xfail(reason=str(self._obj.todo))
def setup(self):
self._testcase = self.parent.obj(self.name)
self._obj = getattr(self._testcase, self.name)
if hasattr(self._testcase, 'skip'):
pytest.skip(self._testcase.skip)
if hasattr(self._obj, 'skip'):
pytest.skip(self._obj.skip)
if hasattr(self._testcase, 'setup_method'):
self._testcase.setup_method(self._obj)
@@ -102,6 +110,13 @@ class TestCaseFunction(pytest.Function):
def runtest(self):
self._testcase(result=self)
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter(
lambda x:not x.frame.f_globals.get('__unittest'))
if traceback:
excinfo.traceback = traceback
@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
@@ -116,14 +131,19 @@ def pytest_runtest_protocol(item, __multicall__):
ut = sys.modules['twisted.python.failure']
Failure__init__ = ut.Failure.__init__.im_func
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None):
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
if exc_value is None:
self._rawexcinfo = sys.exc_info()
else:
if exc_type is None:
exc_type = type(exc_value)
self._rawexcinfo = (exc_type, exc_value, exc_tb)
Failure__init__(self, exc_value, exc_type, exc_tb)
try:
Failure__init__(self, exc_value, exc_type, exc_tb,
captureVars=captureVars)
except TypeError:
Failure__init__(self, exc_value, exc_type, exc_tb)
ut.Failure.__init__ = excstore
try:
return __multicall__.execute()

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.24"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"

View File

@@ -15,7 +15,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
regen:
COLUMNS=76 regendoc --update *.txt */*.txt
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
help:
@echo "Please use \`make <target>' where <target> is one of"
@@ -39,8 +39,14 @@ help:
clean:
-rm -rf $(BUILDDIR)/*
install: clean html
rsync -avz _build/html/ code:www-pytest/2.0.0
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

@@ -7,10 +7,10 @@
released versions in the <a href="http://pypi.python.org/pypi/pytest">Python
Package Index</a>.</p>
{% else %}
<p><b><a href="{{ pathto('announce/index')}}">{{ version }} release</a></b>
<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,27 +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="{{ pathto('contact') }}">contact</a>&nbsp;
</div>
</div>
{% block relbaritems %}
{{ super() }}
<g:plusone></g:plusone>
{% endblock %}
{% block footer %}
@@ -39,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,5 +5,15 @@ Release announcements
.. toctree::
:maxdepth: 2
release-2.2.2
release-2.2.1
release-2.2.0
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
release-2.0.0

View File

@@ -117,7 +117,7 @@ Important Notes
- py.test.collect.Directory does not exist anymore and it
is not possible to provide an own "Directory" object.
If you have used this and don't know what to do, get
in contact. We'll figure someting out.
in contact. We'll figure something out.
Note that pytest_collect_directory() is still called but
any return value will be ignored. This allows to keep

View File

@@ -0,0 +1,67 @@
py.test 2.0.1: bug fixes
===========================================================================
Welcome to pytest-2.0.1, a maintenance and bug fix release of pytest,
a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
and latest PyPy interpreters. See extensive docs with tested examples here:
http://pytest.org/
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
Many thanks to all issue reporters and people asking questions or
complaining. Particular thanks to Floris Bruynooghe and Ronny Pfannschmidt
for their great coding contributions and many others for feedback and help.
best,
holger krekel
Changes between 2.0.0 and 2.0.1
----------------------------------------------
- refine and unify initial capturing so that it works nicely
even if the logging module is used on an early-loaded conftest.py
file or plugin.
- fix issue12 - show plugin versions with "--version" and
"--traceconfig" and also document how to add extra information
to reporting test header
- fix issue17 (import-* reporting issue on python3) by
requiring py>1.4.0 (1.4.1 is going to include it)
- fix issue10 (numpy arrays truth checking) by refining
assertion interpretation in py lib
- fix issue15: make nose compatibility tests compatible
with python3 (now that nose-1.0 supports python3)
- remove somewhat surprising "same-conftest" detection because
it ignores conftest.py when they appear in several subdirs.
- improve assertions ("not in"), thanks Floris Bruynooghe
- improve behaviour/warnings when running on top of "python -OO"
(assertions and docstrings are turned off, leading to potential
false positives)
- introduce a pytest_cmdline_processargs(args) hook
to allow dynamic computation of command line arguments.
This fixes a regression because py.test prior to 2.0
allowed to set command line options from conftest.py
files which so far pytest-2.0 only allowed from ini-files now.
- fix issue7: assert failures in doctest modules.
unexpected failures in doctests will not generally
show nicer, i.e. within the doctest failing context.
- fix issue9: setup/teardown functions for an xfail-marked
test will report as xfail if they fail but report as normally
passing (not xpassing) if they succeed. This only is true
for "direct" setup/teardown invocations because teardown_class/
teardown_module cannot closely relate to a single test.
- fix issue14: no logging errors at process exit
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not
setup as with pytest 1.3.4. Note, however, that
the recommended and much cleaner way to do test
parametrization remains the "pytest_generate_tests"
mechanism, see the docs.

View File

@@ -0,0 +1,73 @@
py.test 2.0.2: bug fixes, improved xfail/skip expressions, speed ups
===========================================================================
Welcome to pytest-2.0.2, a maintenance and bug fix release of pytest,
a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
and latest PyPy interpreters. See the extensive docs with tested examples here:
http://pytest.org/
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
Many thanks to all issue reporters and people asking questions
or complaining, particularly Jurko for his insistence,
Laura, Victor and Brianna for helping with improving
and Ronny for his general advise.
best,
holger krekel
Changes between 2.0.1 and 2.0.2
----------------------------------------------
- tackle issue32 - speed up test runs of very quick test functions
by reducing the relative overhead
- fix issue30 - extended xfail/skipif handling and improved reporting.
If you have a syntax error in your skip/xfail
expressions you now get nice error reports.
Also you can now access module globals from xfail/skipif
expressions so that this for example works now::
import pytest
import mymodule
@pytest.mark.skipif("mymodule.__version__[0] == "1")
def test_function():
pass
This will not run the test function if the module's version string
does not start with a "1". Note that specifying a string instead
of a boolean expressions allows py.test to report meaningful information
when summarizing a test run as to what conditions lead to skipping
(or xfail-ing) tests.
- fix issue28 - setup_method and pytest_generate_tests work together
The setup_method fixture method now gets called also for
test function invocations generated from the pytest_generate_tests
hook.
- fix issue27 - collectonly and keyword-selection (-k) now work together
Also, if you do "py.test --collectonly -q" you now get a flat list
of test ids that you can use to paste to the py.test commandline
in order to execute a particular test.
- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
Starting with Python3.2 os.symlink may be supported. By requiring
a newer py lib version the py.path.local() implementation acknowledges
this.
- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
thanks to Laura Creighton who also revieved parts of the documentation.
- fix slighly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)

View File

@@ -0,0 +1,40 @@
py.test 2.0.3: bug fixes and speed ups
===========================================================================
Welcome to pytest-2.0.3, a maintenance and bug fix release of pytest,
a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
and latest PyPy interpreters. See the extensive docs with tested examples here:
http://pytest.org/
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
There also is a bugfix release 1.6 of pytest-xdist, the plugin
that enables seemless distributed and "looponfail" testing for Python.
best,
holger krekel
Changes between 2.0.2 and 2.0.3
----------------------------------------------
- fix issue38: nicer tracebacks on calls to hooks, particularly early
configure/sessionstart ones
- fix missing skip reason/meta information in junitxml files, reported
via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
- fix issue34: avoid collection failure with "test" prefixed classes
deriving from object.
- don't require zlib (and other libs) for genscript plugin without
--genscript actually being used.
- speed up skips (by not doing a full traceback represenation
internally)
- fix issue37: avoid invalid characters in junitxml's output

View File

@@ -0,0 +1,47 @@
py.test 2.1.0: perfected assertions and bug fixes
===========================================================================
Welcome to the release 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 initialization 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

@@ -0,0 +1,95 @@
py.test 2.2.0: test marking++, parametrization++ and duration profiling
===========================================================================
pytest-2.2.0 is a test-suite compatible release of the popular
py.test testing tool. Plugins might need upgrades. It comes
with these improvements:
* easier and more powerful parametrization of tests:
- new @pytest.mark.parametrize decorator to run tests with different arguments
- new metafunc.parametrize() API for parametrizing arguments independently
- see examples at http://pytest.org/latest/example/parametrize.html
- NOTE that parametrize() related APIs are still a bit experimental
and might change in future releases.
* improved handling of test markers and refined marking mechanism:
- "-m markexpr" option for selecting tests according to their mark
- a new "markers" ini-variable for registering test markers for your project
- the new "--strict" bails out with an error if using unregistered markers.
- see examples at http://pytest.org/latest/example/markers.html
* duration profiling: new "--duration=N" option showing the N slowest test
execution or setup/teardown calls. This is most useful if you want to
find out where your slowest test code is.
* also 2.2.0 performs more eager calling of teardown/finalizers functions
resulting in better and more accurate reporting when they fail
Besides there is the usual set of bug fixes along with a cleanup of
pytest's own test suite allowing it to run on a wider range of environments.
For general information, see extensive docs with examples here:
http://pytest.org/
If you want to install or upgrade pytest you might just type::
pip install -U pytest # or
easy_install -U pytest
Thanks to Ronny Pfannschmidt, David Burns, Jeff Donner, Daniel Nouri, Alfredo Deza and all who gave feedback or sent bug reports.
best,
holger krekel
notes on incompatibility
------------------------------
While test suites should work unchanged you might need to upgrade plugins:
* You need a new version of the pytest-xdist plugin (1.7) for distributing
test runs.
* Other plugins might need an upgrade if they implement
the ``pytest_runtest_logreport`` hook which now is called unconditionally
for the setup/teardown fixture phases of a test. You may choose to
ignore setup/teardown failures by inserting "if rep.when != 'call': return"
or something similar. Note that most code probably "just" works because
the hook was already called for failing setup/teardown phases of a test
so a plugin should have been ready to grok such reports already.
Changes between 2.1.3 and 2.2.0
----------------------------------------
- fix issue90: introduce eager tearing down of test items so that
teardown function are called earlier.
- add an all-powerful metafunc.parametrize function which allows to
parametrize test function arguments in multiple steps and therefore
from independent plugins and places.
- add a @pytest.mark.parametrize helper which allows to easily
call a test function with different argument values.
- Add examples to the "parametrize" example page, including a quick port
of Test scenarios and the new parametrize function and decorator.
- introduce registration for "pytest.mark.*" helpers via ini-files
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See examples at http://pytest.org/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of "-k" in that "-m"
only matches complete markers and has more obvious rules for and/or
semantics.
- new feature to help optimizing the speed of your tests:
--durations=N option for displaying N slowest test calls
and setup/teardown methods.
- fix issue87: --pastebin now works with python3
- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly
- fix and cleanup pytest's own test suite to not leak FDs
- fix issue83: link to generated funcarg list
- fix issue74: pyarg module names are now checked against imp.find_module false positives
- fix compatibility with twisted/trial-11.1.0 use cases

View File

@@ -0,0 +1,41 @@
pytest-2.2.1: bug fixes, perfect teardowns
===========================================================================
pytest-2.2.1 is a minor backward-compatible release of the the py.test
testing tool. It contains bug fixes and little improvements, including
documentation fixes. If you are using the distributed testing
pluginmake sure to upgrade it to pytest-xdist-1.8.
For general information see here:
http://pytest.org/
To install or upgrade pytest:
pip install -U pytest # or
easy_install -U pytest
Special thanks for helping on this release to Ronny Pfannschmidt, Jurko
Gospodnetic and Ralf Schmitt.
best,
holger krekel
Changes between 2.2.0 and 2.2.1
----------------------------------------
- fix issue99 (in pytest and py) internallerrors with resultlog now
produce better output - fixed by normalizing pytest_internalerror
input arguments.
- fix issue97 / traceback issues (in pytest and py) improve traceback output
in conjunction with jinja2 and cython which hack tracebacks
- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
the final test in a test node will now run its teardown directly
instead of waiting for the end of the session. Thanks Dave Hunt for
the good reporting and feedback. The pytest_runtest_protocol as well
as the pytest_runtest_teardown hooks now have "nextitem" available
which will be None indicating the end of the test run.
- fix collection crash due to unknown-source collected items, thanks
to Ralf Schmitt (fixed by depending on a more recent pylib)

View File

@@ -0,0 +1,42 @@
pytest-2.2.2: bug fixes
===========================================================================
pytest-2.2.2 is a minor backward-compatible release of the versatile py.test
testing tool. It contains bug fixes and a few refinements particularly
to reporting with "--collectonly", see below for betails.
For general information see here:
http://pytest.org/
To install or upgrade pytest:
pip install -U pytest # or
easy_install -U pytest
Special thanks for helping on this release to Ronny Pfannschmidt
and Ralf Schmitt and the contributors of issues.
best,
holger krekel
Changes between 2.2.1 and 2.2.2
----------------------------------------
- fix issue101: wrong args to unittest.TestCase test function now
produce better output
- fix issue102: report more useful errors and hints for when a
test directory was renamed and some pyc/__pycache__ remain
- fix issue106: allow parametrize to be applied multiple times
e.g. from module, class and at function level.
- fix issue107: actually perform session scope finalization
- don't check in parametrize if indirect parameters are funcarg names
- add chdir method to monkeypatch funcarg
- fix crash resulting from calling monkeypatch undo a second time
- fix issue115: make --collectonly robust against early failure
(missing files/directories)
- "-qq --collectonly" now shows only files and the number of tests in them
- "-q --collectonly" now shows test ids
- allow adding of attributes to test reports such that it also works
with distributed testing (no upgrade of pytest-xdist needed)

View File

@@ -1,15 +1,15 @@
Writing and reporting of assertions in tests
============================================
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 to use the standard python ``assert`` for verifying
``py.test`` allows you to use the standard python ``assert`` for verifying
expectations and values in Python tests. For example, you can write the
following in your tests::
following::
# content of test_assert1.py
def f():
@@ -18,49 +18,49 @@ following in your tests::
def test_function():
assert f() == 4
to state that your object has a certain ``attribute``. In case 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.5 -- pytest-2.0.0.dev30
test path 1: test_assert1.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_assert1.py F
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.03 seconds =========================
========================= 1 failed in 0.02 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording 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 for this issue is reading from a file and comparing in one
line::
However, if you specify a message with the assertion like this::
assert f.read() != '...'
assert a % 2 == 0, "value was odd, should be even"
This might fail but when re-interpretation comes along it might pass.
You can rewrite this (and any other expression with side effects) easily, though:
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
``pytest.raises`` as a context manager like this::
import pytest
with pytest.raises(ZeroDivisionError):
1 / 0
@@ -73,7 +73,7 @@ and if you need to have access to the actual exception info you may use::
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
If you want to write test code that works on Python2.4 as well,
If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception::
pytest.raises(ExpectedException, func, *args, **kwargs)
@@ -91,7 +91,7 @@ Making use of context-sensitive comparisons
.. versionadded:: 2.0
py.test has rich support for providing context-sensitive informations
py.test has rich support for providing context-sensitive information
when it encounters comparisons. For example::
# content of test_assert2.py
@@ -105,14 +105,14 @@ if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_assert2.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
@@ -122,9 +122,9 @@ if you run this module::
E '1'
E Extra items in the right set:
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.03 seconds =========================
Special comparisons are done for a number of cases:
@@ -134,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
@@ -12,43 +12,47 @@ You can always use an interactive Python prompt and type::
import pytest
help(pytest)
to get an overview on available globally available helpers.
to get an overview on the globally available helpers.
.. automodule:: pytest
:members:
builtin function arguments
.. _builtinfuncargs:
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.2.2
collected 0 items
pytestconfig
the pytest config object with access to command line opts.
capsys
captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
capfd
captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
tmpdir
return a temporary directory path object
unique to each test function invocation,
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
monkeypatch
The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
@@ -56,15 +60,23 @@ You can ask for available builtin or project-custom
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
monkeypatch.chdir(path)
All modifications will be undone after the requesting
test function has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
recwarn
Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
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.01 seconds =============================

View File

@@ -1,31 +1,36 @@
.. _`captures`:
Capturing of stdout/stderr output
Capturing of the stdout/stderr output
=========================================================
By default ``stdout`` and ``stderr`` output is captured separately for
setup and test execution code. If a test or a setup method fails its
according output will usually be shown along with the failure traceback.
In addition, ``stdin`` is set to a "null" object which will fail all
attempts to read from it. This is important if some code paths in
test otherwise might lead to waiting for input - which is usually
not desired when running automated tests.
Default stdout/stderr/stdin capturing behaviour
---------------------------------------------------------
During test execution any output sent to ``stdout`` and ``stderr`` is
captured. If a test or a setup method fails its according captured
output will usually be shown along with the failure traceback.
In addition, ``stdin`` is set to a "null" object which will
fail on attempts to read from it because it is rarely desired
to wait for interactive input when running automated tests.
By default capturing is done by intercepting writes to low level
file descriptors. This allows to capture output from simple
print statements as well as output from a subprocess started by
a test.
Setting capturing methods or disabling capturing
-------------------------------------------------
There are two ways in which ``py.test`` can perform capturing:
* ``fd`` level capturing (default): All writes going to the operating
system file descriptors 1 and 2 will be captured, for example writes such
as ``os.write(1, 'hello')``. Capturing on ``fd``-level also includes
**output from subprocesses**.
* file descriptor (FD) level capturing (default): All writes going to the
operating system file descriptors 1 and 2 will be captured.
* ``sys`` level capturing: The ``sys.stdout`` and ``sys.stderr`` will
will be replaced with in-memory files and the ``print`` builtin or
output from code like ``sys.stderr.write(...)`` will be captured with
this method.
* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
and ``sys.stderr`` will be captured. No capturing of writes to
filedescriptors is performed.
.. _`disable capturing`:
@@ -35,14 +40,46 @@ You can influence output capturing mechanisms from the command line::
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
py.test --capture=fd # also point filedescriptors 1 and 2 to temp file
If you set capturing values in a conftest file like this::
.. _printdebugging:
# conftest.py
option_capture = 'fd'
Using print statements for debugging
---------------------------------------------------
then all tests in that directory will execute with "fd" style capturing.
One primary benefit of the default capturing of stdout/stderr output
is that you can use print statements for debugging::
_ `printdebugging`:
# content of test_module.py
def setup_function(function):
print ("setting up %s" % function)
def test_func1():
assert True
def test_func2():
assert False
and running this module will show you precisely the output
of the failing function and hide the other one::
$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
test_module.py .F
================================= FAILURES =================================
________________________________ test_func2 ________________________________
def test_func2():
> assert False
E assert False
test_module.py:9: AssertionError
----------------------------- Captured stdout ------------------------------
setting up <function test_func2 at 0x1013230c8>
==================== 1 failed, 1 passed in 0.03 seconds ====================
Accessing captured output from a test function
---------------------------------------------------

View File

@@ -38,22 +38,21 @@ 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'
copyright = u'2010, holger krekel et aliter'
copyright = u'2011, holger krekel et alii'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0.0'
# The full version, including alpha/beta/rc tags.
import py, pytest
assert py.path.local().relto(py.path.local(pytest.__file__).dirpath().dirpath())
import pytest
release = pytest.__version__
# The short X.Y version.
version = ".".join(release.split(".")[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -67,7 +66,10 @@ release = pytest.__version__
# 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
@@ -106,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.
@@ -135,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.
@@ -144,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
@@ -184,8 +186,8 @@ 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',
u'holger krekel et aliter', 'manual'),
('contents', 'pytest.tex', u'pytest Documentation',
u'holger krekel et alii', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -209,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 aliter'], 1)
('usage', 'pytest', u'pytest usage',
[u'holger krekel at merlinux eu'], 1)
]
@@ -226,9 +227,9 @@ man_pages = [
# Bibliographic Dublin Core info.
epub_title = u'pytest'
epub_author = u'holger krekel et aliter'
epub_publisher = u'holger krekel et aliter'
epub_copyright = u'2010, holger krekel et aliter'
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

@@ -15,7 +15,7 @@ Contact channels
- #pylib on irc.freenode.net IRC channel for random questions.
- private mail to Holger.Krekel at gmail com if you have sensitive issues to communicate
- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
- `commit mailing list`_

View File

@@ -1,19 +1,20 @@
.. _toc:
Table of Contents
========================
Full pytest documentation
===========================
.. note::
version 2.0 introduces :ref:`pytest as the main Python import name <naming20>`
`Download latest version as PDF <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,23 +1,23 @@
basic test configuration
Basic test configuration
===================================
Command line options and configuration file settings
-----------------------------------------------------------------
You can get help on options and ini-config values by running::
You can get help on command line options and values in INI-style
configurations files by using the general help option::
py.test -h # prints options _and_ config file settings
This will display command line and configuration file settings
which were registered by installed plugins.
How test configuration is read from configuration INI-files
-------------------------------------------------------------
how test configuration is read from setup/tox ini-files
--------------------------------------------------------
py.test searched for the first matching ini-style configuration file
py.test searches for the first matching ini-style configuration file
in the directories of command line argument and the directories above.
It looks for filenames in this order::
It looks for file basenames in this order::
pytest.ini
tox.ini
@@ -44,35 +44,33 @@ is used to start the search.
.. _`how to change command line options defaults`:
.. _`adding default options`:
how to change command line options defaults
How to change command line options defaults
------------------------------------------------
py.test provides a simple way to set some default
command line options. For example, if you want
to always see detailed info on skipped and xfailed
tests, as well as have terser "dot progress output",
you can add this to your root directory::
It can be tedious to type the same series of command line options
every time you use py.test . For example, if you always want to see
detailed info on skipped and xfailed tests, as well as have terser "dot"
progress output, you can write it into a configuration file::
# content of pytest.ini
# (or tox.ini or setup.cfg)
[pytest]
addopts = -rsxX -q
From now on, running ``py.test`` will implicitely add
the specified options.
From now on, running ``py.test`` will add the specified options.
builtin configuration file options
Builtin configuration file options
----------------------------------------------
.. confval:: minversion
specifies a minimal pytest version needed for running tests.
Specifies a minimal pytest version required for running tests.
minversion = 2.1 # will fail if we run with pytest-2.0
.. confval:: addopts
add the specified ``OPTS`` to the set of command line arguments as if they
Add the specified ``OPTS`` to the set of command line arguments as if they
had been specified by the user. Example: if you have this ini file content::
[pytest]
@@ -96,15 +94,15 @@ builtin configuration file options
[seq] matches any character in seq
[!seq] matches any char not in seq
Default patterns are ``.* _* CVS {args}``. Setting a ``norecurse``
replaces the default. Here is a customizing example for avoiding
a different set of directories::
Default patterns are ``.* _* CVS {args}``. Setting a ``norecursedir``
replaces the default. Here is an example of how to avoid
certain directories::
# content of setup.cfg
[pytest]
norecursedirs = .svn _build tmp*
This would tell py.test to not recurse into typical subversion or
This would tell py.test to not look into typical subversion or
sphinx-build directories or into any ``tmp`` prefixed directory.
.. confval:: python_files
@@ -123,4 +121,3 @@ builtin configuration file options
and methods are considered as test modules.
See :ref:`change naming conventions` for examples.

View File

@@ -22,7 +22,7 @@ download and unpack a TAR file::
http://pypi.python.org/pypi/pytest/
activating a checkout with setuptools
Activating a checkout with setuptools
--------------------------------------------
With a working Distribute_ or setuptools_ installation you can type::
@@ -31,4 +31,10 @@ With a working Distribute_ or setuptools_ installation you can type::
in order to work inline with the tools and the lib of your checkout.
If this command complains that it could not find the required version
of "py" then you need to use the development pypi repository::
python setup.py develop -i http://pypi.testrun.org
.. include:: links.inc

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
@@ -15,11 +15,11 @@ python test modules)::
py.test --doctest-modules
You can make these changes permanent in your project by
putting them into a conftest.py file like this::
putting them into a pytest.ini file like this::
# content of conftest.py
option_doctestmodules = True
option_doctestglob = "*.rst"
# content of pytest.ini
[pytest]
addopts = --doctest-modules
If you then have a text file like this::
@@ -35,7 +35,7 @@ and another like this::
# content of mymodule.py
def something():
""" a doctest in a docstring
>>> something()
>>> something()
42
"""
return 42
@@ -44,7 +44,10 @@ then you can just invoke ``py.test`` without command line options::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-66
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
============================= in 0.00 seconds =============================
mymodule.py .
========================= 1 passed in 0.51 seconds =========================
[?1034h

View File

@@ -77,6 +77,22 @@ class TestSpecialisedExplanations(object):
def test_in_list(self):
assert 1 in [0, 2, 3, 4, 5]
def test_not_in_text_multiline(self):
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
assert 'foo' not in text
def test_not_in_text_single(self):
text = 'single foo line'
assert 'foo' not in text
def test_not_in_text_single_long(self):
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
assert 'foo' not in text
def test_not_in_text_single_long_term(self):
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
assert 'f'*70 not in text
def test_attribute():
class Foo(object):

View File

@@ -9,6 +9,6 @@ def test_failure_demo_fails_properly(testdir):
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target)
result.stdout.fnmatch_lines([
"*35 failed*"
"*39 failed*"
])
assert result.ret != 0

View File

@@ -48,7 +48,7 @@ the output.
example: decorating a funcarg in a test module
--------------------------------------------------------------
For larger scale setups it's sometimes useful to decorare
For larger scale setups it's sometimes useful to decorate
a funcarg just for a particular test module. We can
extend the `accept example`_ by putting this in our test module:

View File

@@ -7,12 +7,17 @@ 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 introductory examples
.. toctree::
:maxdepth: 2
reportingdemo.txt
simple.txt
pythoncollection.txt
mysetup.txt
parametrize.txt
markers.txt
pythoncollection.txt
nonpython.txt

297
doc/example/markers.txt Normal file
View File

@@ -0,0 +1,297 @@
.. _`mark examples`:
Working with custom markers
=================================================
Here are some example using the :ref:`mark` mechanism.
Marking test functions and selecting them for a run
----------------------------------------------------
You can "mark" a test function with custom metadata like this::
# content of test_server.py
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
def test_something_quick():
pass
.. versionadded:: 2.2
You can then restrict a test run to only run tests marked with ``webtest``::
$ py.test -v -m webtest
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_server.py:3: test_send_http PASSED
=================== 1 tests deselected by "-m 'webtest'" ===================
================== 1 passed, 1 deselected in 0.01 seconds ==================
Or the inverse, running all tests except the webtest ones::
$ py.test -v -m "not webtest"
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_server.py:6: test_something_quick PASSED
================= 1 tests deselected by "-m 'not webtest'" =================
================== 1 passed, 1 deselected in 0.02 seconds ==================
Registering markers
-------------------------------------
.. versionadded:: 2.2
.. ini-syntax for custom markers:
Registering markers for your test suite is simple::
# content of pytest.ini
[pytest]
markers =
webtest: mark a test as a webtest.
You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers::
$ py.test --markers
@pytest.mark.webtest: mark a test as a webtest.
@pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform.
@pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
For an example on how to add and work with markers from a plugin, see
:ref:`adding a custom marker from a plugin`.
.. note::
It is recommended to explicitely register markers so that:
* there is one place in your test suite defining your markers
* asking for existing markers via ``py.test --markers`` gives good output
* typos in function markers are treated as an error if you use
the ``--strict`` option. Later versions of py.test are probably
going to treat non-registered markers as an error.
.. _`scoped-marking`:
Marking whole classes or modules
----------------------------------------------------
If you are programming with Python 2.6 or later you may use ``pytest.mark``
decorators with classes to apply markers to all of its test methods::
# content of test_mark_classlevel.py
import pytest
@pytest.mark.webtest
class TestClass:
def test_startup(self):
pass
def test_startup_and_more(self):
pass
This is equivalent to directly applying the decorator to the
two test functions.
To remain backward-compatible with Python 2.4 you can also set a
``pytestmark`` attribute on a TestClass like this::
import pytest
class TestClass:
pytestmark = pytest.mark.webtest
or if you need to use multiple markers you can use a list::
import pytest
class TestClass:
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
You can also set a module level marker::
import pytest
pytestmark = pytest.mark.webtest
in which case it will be applied to all functions and
methods defined in the module.
Using ``-k TEXT`` to select tests
----------------------------------------------------
You can use the ``-k`` command line option to only run tests with names matching
the given argument::
$ py.test -k send_http # running with the above defined examples
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 4 items
test_server.py .
=================== 3 tests deselected by '-ksend_http' ====================
================== 1 passed, 3 deselected in 0.02 seconds ==================
And you can also run all tests except the ones that match the keyword::
$ py.test -k-send_http
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 4 items
test_mark_classlevel.py ..
test_server.py .
=================== 1 tests deselected by '-k-send_http' ===================
================== 3 passed, 1 deselected in 0.03 seconds ==================
Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 4 items
test_mark_classlevel.py ..
=================== 2 tests deselected by '-kTestClass' ====================
================== 2 passed, 2 deselected in 0.03 seconds ==================
.. _`adding a custom marker from a plugin`:
Custom marker and command line option to control test runs
----------------------------------------------------------
.. regendoc:wipe
Plugins can provide custom markers and implement specific behaviour
based on it. This is a self-contained example which adds a command
line option and a parametrized test function marker to run tests
specifies via named environments::
# content of conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption("-E", dest="env", action="store", metavar="NAME",
help="only run tests matching the environment NAME.")
def pytest_configure(config):
# register an additional marker
config.addinivalue_line("markers",
"env(name): mark test to run only on named environment")
def pytest_runtest_setup(item):
if not isinstance(item, item.Function):
return
if hasattr(item.obj, 'env'):
envmarker = getattr(item.obj, 'env')
envname = envmarker.args[0]
if envname != item.config.option.env:
pytest.skip("test requires env %r" % envname)
A test file using this local plugin::
# content of test_someenv.py
import pytest
@pytest.mark.env("stage1")
def test_basic_db_operation():
pass
and an example invocations specifying a different environment than what
the test needs::
$ py.test -E stage2
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_someenv.py s
======================== 1 skipped in 0.02 seconds =========================
and here is one that specifies exactly the environment needed::
$ py.test -E stage1
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_someenv.py .
========================= 1 passed in 0.02 seconds =========================
The ``--markers`` option always gives you a list of available markers::
$ py.test --markers
@pytest.mark.env(name): mark test to run only on named environment
@pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform.
@pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
@pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in multiple different argument value sets. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
Reading markers which were set from multiple places
----------------------------------------------------
.. versionadded: 2.2.2
If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin
code you can read over all such settings. Example::
# content of test_mark_three_times.py
import pytest
pytestmark = pytest.mark.glob("module", x=1)
@pytest.mark.glob("class", x=2)
class TestClass:
@pytest.mark.glob("function", x=3)
def test_something(self):
pass
Here we have the marker "glob" applied three times to the same
test function. From a conftest file we can read it like this::
# content of conftest.py
def pytest_runtest_setup(item):
g = getattr(item.obj, 'glob', None)
if g is not None:
for info in g:
print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
Let's run this without capturing output and see what we get::
$ py.test -q -s
collecting ... collected 2 items
..
2 passed in 0.02 seconds
glob args=('function',) kwargs={'x': 3}
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}

View File

@@ -2,20 +2,20 @@
module containing a parametrized tests testing cross-python
serialization via the pickle module.
"""
import py
import py, pytest
pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
def pytest_generate_tests(metafunc):
if 'python1' in metafunc.funcargnames:
assert 'python2' in metafunc.funcargnames
for obj in metafunc.function.multiarg.kwargs['obj']:
for py1 in pythonlist:
for py2 in pythonlist:
metafunc.addcall(id="%s-%s-%s" % (py1, py2, obj),
param=(py1, py2, obj))
# we parametrize all "python1" and "python2" arguments to iterate
# over the python interpreters of our list above - the actual
# setup and lookup of interpreters in the python1/python2 factories
# respectively.
for arg in metafunc.funcargnames:
if arg in ("python1", "python2"):
metafunc.parametrize(arg, pythonlist, indirect=True)
@py.test.mark.multiarg(obj=[42, {}, {1:3},])
@pytest.mark.parametrize("obj", [42, {}, {1:3},])
def test_basic_objects(python1, python2, obj):
python1.dumps(obj)
python2.load_and_is_true("obj == %s" % obj)
@@ -23,14 +23,11 @@ def test_basic_objects(python1, python2, obj):
def pytest_funcarg__python1(request):
tmpdir = request.getfuncargvalue("tmpdir")
picklefile = tmpdir.join("data.pickle")
return Python(request.param[0], picklefile)
return Python(request.param, picklefile)
def pytest_funcarg__python2(request):
python1 = request.getfuncargvalue("python1")
return Python(request.param[1], python1.picklefile)
def pytest_funcarg__obj(request):
return request.param[2]
return Python(request.param, python1.picklefile)
class Python:
def __init__(self, version, picklefile):

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::
@@ -26,7 +26,7 @@ Let's write a simple test function using a ``mysetup`` funcarg::
To run this test py.test needs to find and call a factory to
obtain the required ``mysetup`` function argument. To make
an according factory findable we write down a specifically named factory
method in a :ref:`local plugin`::
method in a :ref:`local plugin <localplugin>` ::
# content of conftest.py
from myapp import MyApp
@@ -38,7 +38,7 @@ method in a :ref:`local plugin`::
def myapp(self):
return MyApp()
To run the example we stub out a a simple ``MyApp`` application object::
To run the example we stub out a simple ``MyApp`` application object::
# content of myapp.py
class MyApp:
@@ -49,15 +49,15 @@ You can now run the test::
$ py.test test_sample.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_sample.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_sample.py F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
mysetup = <conftest.MySetup instance at 0x16f5998>
mysetup = <conftest.MySetup instance at 0x101322fc8>
def test_answer(mysetup):
app = mysetup.myapp()
@@ -66,7 +66,7 @@ You can now run the test::
E assert 54 == 42
test_sample.py:4: AssertionError
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.72 seconds =========================
This means that our ``mysetup`` object was successfully instantiated
and ``mysetup.app()`` returned an initialized ``MyApp`` instance.
@@ -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,12 +122,12 @@ Running it yields::
$ py.test test_ssh.py -rs
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_ssh.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_ssh.py s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-107/conftest.py:22: specify ssh host with --ssh
SKIP [1] /Users/hpk/tmp/doc-exec-153/conftest.py:22: specify ssh host with --ssh
======================== 1 skipped in 0.02 seconds =========================

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,8 +27,8 @@ now execute the test specification::
nonpython $ py.test test_simple.yml
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_simple.yml
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
test_simple.yml .F
@@ -37,9 +37,7 @@ now execute the test specification::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
==================== 1 failed, 1 passed in 0.48 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
@@ -53,13 +51,13 @@ your own domain specific testing language this way.
representation string of your choice. It
will be reported as a (red) string.
``reportinfo()`` is used for representing the test location and is also consulted for
reporting in ``verbose`` mode::
``reportinfo()`` is used for representing the test location and is also
consulted when reporting in ``verbose`` mode::
nonpython $ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30 -- /home/hpk/venv/0/bin/python
test path 1: /home/hpk/p/pytest/doc/example/nonpython
platform darwin -- Python 2.7.1 -- pytest-2.2.2 -- /Users/hpk/venv/0/bin/python
collecting ... collected 2 items
test_simple.yml:1: usecase: ok PASSED
test_simple.yml:1: usecase: hello FAILED
@@ -69,14 +67,17 @@ reporting in ``verbose`` mode::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
==================== 1 failed, 1 passed in 0.10 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 darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
<YamlFile 'test_simple.yml'>
<YamlItem 'ok'>
<YamlItem 'hello'>
============================= in 0.18 seconds =============================

View File

@@ -1,34 +1,283 @@
parametrizing tests
.. _paramexamples:
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.
.. currentmodule:: _pytest.python
py.test allows to easily parametrize test functions.
In the following we provide some examples using
the builtin mechanisms.
.. _parametrizemark:
Simple "decorator" parametrization of a test function
----------------------------------------------------------------------------
.. versionadded:: 2.2
The builtin ``pytest.mark.parametrize`` decorator directly enables
parametrization of arguments for a test function. Here is an example
of a test function that wants to compare that processing some input
results in expected output::
# content of test_expectation.py
import pytest
@pytest.mark.parametrize(("input", "expected"), [
("3+5", 8),
("2+4", 6),
("6*9", 42),
])
def test_eval(input, expected):
assert eval(input) == expected
we parametrize two arguments of the test function so that the test
function is called three times. Let's run it::
$ py.test -q
collecting ... collected 3 items
..F
================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________
input = '6*9', expected = 42
@pytest.mark.parametrize(("input", "expected"), [
("3+5", 8),
("2+4", 6),
("6*9", 42),
])
def test_eval(input, expected):
> assert eval(input) == expected
E assert 54 == 42
E + where 54 = eval('6*9')
test_expectation.py:8: AssertionError
1 failed, 2 passed in 0.03 seconds
As expected only one pair of input/output values fails the simple test function.
Note that there are various ways how you can mark groups of functions,
see :ref:`mark`.
Generating parameters combinations, depending on command line
----------------------------------------------------------------------------
.. regendoc:wipe
Let's say we want to execute a test with different computation
parameters and the parameter range shall be determined by a command
line argument. Let's first write a simple (do-nothing) computation test::
# content of test_compute.py
def test_compute(param1):
assert param1 < 4
Now we add a test configuration like this::
# content of conftest.py
def pytest_addoption(parser):
parser.addoption("--all", action="store_true",
help="run all combinations")
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.funcargnames:
if metafunc.config.option.all:
end = 5
else:
end = 2
metafunc.parametrize("param1", range(end))
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.03 seconds
We run only two computations, so we see two dots.
let's run the full monty::
$ py.test -q --all
collecting ... collected 5 items
....F
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
1 failed, 4 passed in 0.05 seconds
As expected when running the full range of ``param1`` values
we'll get an error on the last one.
A quick port of "testscenarios"
------------------------------------
.. _`test scenarios`: http://bazaar.launchpad.net/~lifeless/testscenarios/trunk/annotate/head%3A/doc/example.py
Here is a quick port to run tests configured with `test scenarios`_,
an add-on from Robert Collins for the standard unittest framework. We
only have to work a bit to construct the correct arguments for pytest's
:py:func:`Metafunc.parametrize`::
# content of test_scenarios.py
def pytest_generate_tests(metafunc):
idlist = []
argvalues = []
for scenario in metafunc.cls.scenarios:
idlist.append(scenario[0])
items = scenario[1].items()
argnames = [x[0] for x in items]
argvalues.append(([x[1] for x in items]))
metafunc.parametrize(argnames, argvalues, ids=idlist)
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'})
class TestSampleWithScenarios:
scenarios = [scenario1, scenario2]
def test_demo(self, attribute):
assert isinstance(attribute, str)
this is a fully self-contained example which you can run with::
$ py.test test_scenarios.py
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
test_scenarios.py ..
========================= 2 passed in 0.02 seconds =========================
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function::
$ py.test --collectonly test_scenarios.py
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
<Module 'test_scenarios.py'>
<Class 'TestSampleWithScenarios'>
<Instance '()'>
<Function 'test_demo[basic]'>
<Function 'test_demo[advanced]'>
============================= in 0.05 seconds =============================
Deferring the setup of parametrized resources
---------------------------------------------------
.. regendoc:wipe
The parametrization of test functions happens at collection
time. It is a good idea to setup expensive resources like DB
connections or subprocess only when the actual test is run.
Here is a simple example how you can achieve that, first
the actual test requiring a ``db`` object::
# content of test_backends.py
import pytest
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
pytest.fail("deliberately failing for demo purposes")
We can now add a test configuration that generates two invocations of
the ``test_db_initialized`` function and also implements a factory that
creates a database object for the actual test invocations::
# content of conftest.py
def pytest_generate_tests(metafunc):
if 'db' in metafunc.funcargnames:
metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
class DB1:
"one database object"
class DB2:
"alternative database object"
def pytest_funcarg__db(request):
if request.param == "d1":
return DB1()
elif request.param == "d2":
return DB2()
else:
raise ValueError("invalid internal test config")
Let's first see how it looks like at collection time::
$ py.test test_backends.py --collectonly
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[d1]'>
<Function 'test_db_initialized[d2]'>
============================= in 0.01 seconds =============================
And then when we run the test::
$ py.test -q test_backends.py
collecting ... collected 2 items
.F
================================= FAILURES =================================
_________________________ test_db_initialized[d2] __________________________
db = <conftest.DB2 instance at 0x101323710>
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
> pytest.fail("deliberately failing for demo purposes")
E Failed: deliberately failing for demo purposes
test_backends.py:6: Failed
1 failed, 1 passed in 0.03 seconds
The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``pytest_funcarg__db`` factory has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
.. regendoc:wipe
Parametrizing test methods through per-class configuration
--------------------------------------------------------------
.. _`unittest parameterizer`: http://code.google.com/p/unittest-ext/source/browse/trunk/params.py
Here is an example ``pytest_generate_function`` function implementing a
parametrization scheme similar to Michael Foords `unittest
parameterizer`_ in a lot less code::
parametrization scheme similar to Michael Foord's `unittest
parameterizer`_ but in a lot less code::
# content of ./test_parametrize.py
import pytest
def pytest_generate_tests(metafunc):
# called once per each test function
for funcargs in metafunc.cls.params[metafunc.function.__name__]:
# schedule a new test function run with applied **funcargs
metafunc.addcall(funcargs=funcargs)
funcarglist = metafunc.cls.params[metafunc.function.__name__]
argnames = list(funcarglist[0])
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
for funcargs in funcarglist])
class TestClass:
# a map specifying multiple argument sets for a test method
params = {
'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
'test_zerodivision': [dict(a=1, b=0), dict(a=3, b=2)],
'test_zerodivision': [dict(a=1, b=0), ],
}
def test_equals(self, a, b):
@@ -37,106 +286,44 @@ parameterizer`_ in a lot less code::
def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b")
Running it means we are two tests for each test functions, using
the respective settings::
Our test generator looks up a class-level definition which specifies which
argument sets to use for each test function. Let's run it::
$ py.test -q
F..F
collecting ... collected 3 items
F..
================================= FAILURES =================================
_________________________ TestClass.test_equals[0] _________________________
________________________ TestClass.test_equals[1-2] ________________________
self = <test_parametrize.TestClass instance at 0x128a638>, a = 1, b = 2
self = <test_parametrize.TestClass instance at 0x101326368>, a = 1, b = 2
def test_equals(self, a, b):
> assert a == b
E assert 1 == 2
test_parametrize.py:17: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize.TestClass instance at 0x1296440>, a = 3, b = 2
def test_zerodivision(self, a, b):
> pytest.raises(ZeroDivisionError, "a/b")
E Failed: DID NOT RAISE
test_parametrize.py:20: Failed
2 failed, 2 passed in 0.03 seconds
test_parametrize.py:18: AssertionError
1 failed, 2 passed in 0.03 seconds
Parametrizing test methods through a decorator
--------------------------------------------------------------
Modifying the previous example we can also allow decorators
for parametrizing test methods::
# content of test_parametrize2.py
import pytest
# test support code
def params(funcarglist):
def wrapper(function):
function.funcarglist = funcarglist
return function
return wrapper
def pytest_generate_tests(metafunc):
for funcargs in getattr(metafunc.function, 'funcarglist', ()):
metafunc.addcall(funcargs=funcargs)
# actual test code
class TestClass:
@params([dict(a=1, b=2), dict(a=3, b=3), ])
def test_equals(self, a, b):
assert a == b
@params([dict(a=1, b=0), dict(a=3, b=2)])
def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b")
Running it gives similar results as before::
$ py.test -q test_parametrize2.py
F..F
================================= FAILURES =================================
_________________________ TestClass.test_equals[0] _________________________
self = <test_parametrize2.TestClass instance at 0x1dbcc68>, a = 1, b = 2
@params([dict(a=1, b=2), dict(a=3, b=3), ])
def test_equals(self, a, b):
> assert a == b
E assert 1 == 2
test_parametrize2.py:19: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize2.TestClass instance at 0x1dd0488>, a = 3, b = 2
@params([dict(a=1, b=0), dict(a=3, b=2)])
def test_zerodivision(self, a, b):
> pytest.raises(ZeroDivisionError, "a/b")
E Failed: DID NOT RAISE
test_parametrize2.py:23: Failed
2 failed, 2 passed in 0.03 seconds
checking serialization between Python interpreters
Indirect parametrization with multiple resources
--------------------------------------------------------------
Here is a stripped down real-life example of using parametrized
testing for testing serialization betwee different interpreters.
testing for testing serialization, invoking different python interpreters.
We define a ``test_basic_objects`` function which is to be run
with different sets of arguments for its three arguments::
with different sets of arguments for its three arguments:
* ``python1``: first python interpreter
* ``python2``: second python interpreter
* ``obj``: object to be dumped from first interpreter and loaded into second interpreter
* ``python1``: first python interpreter, run to pickle-dump an object to a file
* ``python2``: second interpreter, run to pickle-load an object from a file
* ``obj``: object to be dumped/loaded
.. literalinclude:: multipython.py
Running it (with Python-2.4 through to Python2.7 installed)::
Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize)::
. $ py.test -q multipython.py
....s....s....s....ssssss....s....s....s....ssssss....s....s....s....ssssss
48 passed, 27 skipped in 2.55 seconds
. $ py.test -rs -q multipython.py
collecting ... collected 75 items
ssssssssssssssssss.........ssssss.........ssssss.........ssssssssssssssssss
========================= short test summary info ==========================
SKIP [24] /Users/hpk/p/pytest/doc/example/multipython.py:36: 'python2.8' not found
SKIP [24] /Users/hpk/p/pytest/doc/example/multipython.py:36: 'python2.4' not found
27 passed, 48 skipped in 7.76 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
@@ -42,13 +42,18 @@ in functions and classes. For example, if we have::
then the test collection looks like this::
$ py.test --collectonly
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
<Module 'check_myapp.py'>
<Class 'CheckMyApp'>
<Instance '()'>
<Function 'check_simple'>
<Function 'check_complex'>
============================= in 0.01 seconds =============================
interpret cmdline arguments as Python packages
Interpreting cmdline arguments as Python packages
-----------------------------------------------------
You can use the ``--pyargs`` option to make py.test try
@@ -70,15 +75,20 @@ 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 darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 3 items
<Module 'pythoncollection.py'>
<Function 'test_function'>
<Class 'TestClass'>
<Instance '()'>
<Function 'test_method'>
<Function 'test_anothermethod'>
============================= in 0.01 seconds =============================

View File

@@ -13,11 +13,10 @@ get on the terminal - we are working on that):
assertion $ py.test failure_demo.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev38
collecting ...
collected 35 items
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 39 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
================================= FAILURES =================================
____________________________ test_generative[0] ____________________________
@@ -31,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 0x2c9da90>
self = <failure_demo.TestFailing object at 0x101490690>
def test_simple(self):
def f():
@@ -41,45 +40,45 @@ get on the terminal - we are working on that):
> assert f() == g()
E assert 42 == 43
E + where 42 = <function f at 0x2c447d0>()
E + and 43 = <function g at 0x2c44cf8>()
E + where 42 = <function f at 0x101462b90>()
E + and 43 = <function g at 0x101462c08>()
failure_demo.py:28: AssertionError
____________________ TestFailing.test_simple_multiline _____________________
self = <failure_demo.TestFailing object at 0x2c9dc90>
self = <failure_demo.TestFailing object at 0x101490b10>
def test_simple_multiline(self):
otherfunc_multi(
42,
> 6*9)
failure_demo.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
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 0x2c93f10>
self = <failure_demo.TestFailing object at 0x101490210>
def test_not(self):
def f():
return 42
> assert not f()
E assert not 42
E + where 42 = <function f at 0x2ca1050>()
E + where 42 = <function f at 0x101462aa0>()
failure_demo.py:38: AssertionError
_________________ TestSpecialisedExplanations.test_eq_text _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9d9d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x101490a10>
def test_eq_text(self):
> assert 'spam' == 'eggs'
@@ -90,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 0x2a04e90>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148d9d0>
def test_eq_similar_text(self):
> assert 'foo 1 bar' == 'foo 2 bar'
@@ -103,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 0x2c9d710>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148d590>
def test_eq_multiline_text(self):
> assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
@@ -116,13 +115,13 @@ 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 0x2c9db10>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148dc90>
def test_eq_long_text(self):
a = '1'*100 + 'a' + '2'*100
b = '1'*100 + 'b' + '2'*100
> assert a == b
E assert '111111111111...2222222222222' == '111111111111...2222222222222'
E assert '111111111111...2222222222222' == '1111111111111...2222222222222'
E Skipping 90 identical leading characters in diff
E Skipping 91 identical trailing characters in diff
E - 1111111111a222222222
@@ -133,13 +132,13 @@ 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 0x2caf950>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148d910>
def test_eq_long_text_multiline(self):
a = '1\n'*100 + 'a' + '2\n'*100
b = '1\n'*100 + 'b' + '2\n'*100
> assert a == b
E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
E Skipping 190 identical leading characters in diff
E Skipping 191 identical trailing characters in diff
E 1
@@ -157,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 0x2caf590>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148b9d0>
def test_eq_list(self):
> assert [0, 1, 2] == [0, 1, 3]
@@ -167,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 0x2c9e310>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148b750>
def test_eq_list_long(self):
a = [0]*100 + [1] + [3]*100
@@ -179,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 0x2c9dc50>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148bdd0>
def test_eq_dict(self):
> assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
@@ -192,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 0x2cafc10>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148b1d0>
def test_eq_set(self):
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
@@ -208,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 0x2cba890>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148bf10>
def test_eq_longer_list(self):
> assert [1,2] == [1,2,3]
@@ -218,13 +217,70 @@ get on the terminal - we are working on that):
failure_demo.py:75: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2cba6d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x10148b390>
def test_in_list(self):
> assert 1 in [0, 2, 3, 4, 5]
E assert 1 in [0, 2, 3, 4, 5]
failure_demo.py:78: AssertionError
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0x101483e50>
def test_not_in_text_multiline(self):
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
> assert 'foo' not in text
E assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail'
E 'foo' is contained here:
E some multiline
E text
E which
E includes foo
E ? +++
E and a
E tail
failure_demo.py:82: AssertionError
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
self = <failure_demo.TestSpecialisedExplanations object at 0x101483c10>
def test_not_in_text_single(self):
text = 'single foo line'
> assert 'foo' not in text
E assert 'foo' not in 'single foo line'
E 'foo' is contained here:
E single foo line
E ? +++
failure_demo.py:86: AssertionError
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
self = <failure_demo.TestSpecialisedExplanations object at 0x101483ed0>
def test_not_in_text_single_long(self):
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
> assert 'foo' not in text
E assert 'foo' not in 'head head head head hea...ail tail tail tail tail '
E 'foo' is contained here:
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? +++
failure_demo.py:90: AssertionError
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
self = <failure_demo.TestSpecialisedExplanations object at 0x101483310>
def test_not_in_text_single_long_term(self):
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
> assert 'f'*70 not in text
E assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail '
E 'ffffffffffffffffff...fffffffffffffffffff' is contained here:
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:94: AssertionError
______________________________ test_attribute ______________________________
def test_attribute():
@@ -233,9 +289,9 @@ 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 0x2c9d750>.b
E + where 1 = <failure_demo.Foo object at 0x101483f50>.b
failure_demo.py:85: AssertionError
failure_demo.py:101: AssertionError
_________________________ test_attribute_instance __________________________
def test_attribute_instance():
@@ -243,10 +299,10 @@ 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 0x2cafdd0>.b
E + where <failure_demo.Foo object at 0x2cafdd0> = <class 'failure_demo.Foo'>()
E + where 1 = <failure_demo.Foo object at 0x101483210>.b
E + where <failure_demo.Foo object at 0x101483210> = <class 'failure_demo.Foo'>()
failure_demo.py:91: AssertionError
failure_demo.py:107: AssertionError
__________________________ test_attribute_failure __________________________
def test_attribute_failure():
@@ -257,16 +313,16 @@ get on the terminal - we are working on that):
i = Foo()
> assert i.b == 2
failure_demo.py:100:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:116:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.Foo object at 0x2cba790>
self = <failure_demo.Foo object at 0x101483450>
def _get_b(self):
> raise Exception('Failed to get attrib')
E Exception: Failed to get attrib
failure_demo.py:97: Exception
failure_demo.py:113: Exception
_________________________ test_attribute_multiple __________________________
def test_attribute_multiple():
@@ -276,57 +332,57 @@ 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 0x2cba210>.b
E + where <failure_demo.Foo object at 0x2cba210> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x2cba850>.b
E + where <failure_demo.Bar object at 0x2cba850> = <class 'failure_demo.Bar'>()
E + where 1 = <failure_demo.Foo object at 0x101483150>.b
E + where <failure_demo.Foo object at 0x101483150> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x101483350>.b
E + where <failure_demo.Bar object at 0x101483350> = <class 'failure_demo.Bar'>()
failure_demo.py:108: AssertionError
failure_demo.py:124: AssertionError
__________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises instance at 0x2cc2560>
self = <failure_demo.TestRaises instance at 0x1014a6758>
def test_raises(self):
s = 'qwe'
> raises(TypeError, "int(s)")
failure_demo.py:117:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:133:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen /home/hpk/p/pytest/_pytest/python.py:819>:1: ValueError
<0-codegen /Users/hpk/p/pytest/_pytest/python.py:976>:1: ValueError
______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises instance at 0x2cb6bd8>
self = <failure_demo.TestRaises instance at 0x1014b03f8>
def test_raises_doesnt(self):
> raises(IOError, "int('3')")
E Failed: DID NOT RAISE
failure_demo.py:120: Failed
failure_demo.py:136: Failed
__________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises instance at 0x2cc4830>
self = <failure_demo.TestRaises instance at 0x1014a8998>
def test_raise(self):
> raise ValueError("demo error")
E ValueError: demo error
failure_demo.py:123: ValueError
failure_demo.py:139: ValueError
________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises instance at 0x2cc5560>
self = <failure_demo.TestRaises instance at 0x1014a27a0>
def test_tupleerror(self):
> a,b = [1]
E ValueError: need more than 1 value to unpack
failure_demo.py:126: ValueError
failure_demo.py:142: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises instance at 0x2cc6248>
self = <failure_demo.TestRaises instance at 0x1014a5518>
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
l = [1,2,3]
@@ -334,18 +390,18 @@ get on the terminal - we are working on that):
> a,b = l.pop()
E TypeError: 'int' object is not iterable
failure_demo.py:131: TypeError
failure_demo.py:147: TypeError
----------------------------- Captured stdout ------------------------------
l is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________
self = <failure_demo.TestRaises instance at 0x2cc6f38>
self = <failure_demo.TestRaises instance at 0x1014a1320>
def test_some_error(self):
> if namenotexi:
E NameError: global name 'namenotexi' is not defined
failure_demo.py:134: NameError
failure_demo.py:150: NameError
____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely():
@@ -357,17 +413,17 @@ get on the terminal - we are working on that):
py.std.sys.modules[name] = module
> module.foo()
failure_demo.py:149:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:165:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def foo():
> assert 1 == 0
E assert 1 == 0
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/example/assertion/failure_demo.py:146>: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 0x2cc4050>
self = <failure_demo.TestMoreErrors instance at 0x1014a6638>
def test_complex_error(self):
def f():
@@ -376,16 +432,16 @@ get on the terminal - we are working on that):
return 43
> somefunc(f(), g())
failure_demo.py:159:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:175:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
x = 44, y = 43
def somefunc(x,y):
> otherfunc(x,y)
failure_demo.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = 44, b = 43
@@ -396,39 +452,39 @@ 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 0x2cc7ab8>
self = <failure_demo.TestMoreErrors instance at 0x1014a42d8>
def test_z1_unpack_error(self):
l = []
> a,b = l
E ValueError: need more than 0 values to unpack
failure_demo.py:163: ValueError
failure_demo.py:179: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors instance at 0x2ccb8c0>
self = <failure_demo.TestMoreErrors instance at 0x1014a0128>
def test_z2_type_error(self):
l = 3
> a,b = l
E TypeError: 'int' object is not iterable
failure_demo.py:167: TypeError
failure_demo.py:183: TypeError
______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors instance at 0x2ccd5f0>
self = <failure_demo.TestMoreErrors instance at 0x1014a0ef0>
def test_startswith(self):
s = "123"
g = "456"
> assert s.startswith(g)
E assert <built-in method startswith of str object at 0x2c321b0>('456')
E + where <built-in method startswith of str object at 0x2c321b0> = '123'.startswith
E assert <built-in method startswith of str object at 0x1014951c0>('456')
E + where <built-in method startswith of str object at 0x1014951c0> = '123'.startswith
failure_demo.py:172: AssertionError
failure_demo.py:188: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors instance at 0x2ccbc20>
self = <failure_demo.TestMoreErrors instance at 0x1014a4170>
def test_startswith_nested(self):
def f():
@@ -436,47 +492,46 @@ get on the terminal - we are working on that):
def g():
return "456"
> assert f().startswith(g())
E assert <built-in method startswith of str object at 0x2c321b0>('456')
E + where <built-in method startswith of str object at 0x2c321b0> = '123'.startswith
E + where '123' = <function f at 0x2c2d140>()
E + and '456' = <function g at 0x2cb00c8>()
E assert <built-in method startswith of str object at 0x1014951c0>('456')
E + where <built-in method startswith of str object at 0x1014951c0> = '123'.startswith
E + where '123' = <function f at 0x1014aea28>()
E + and '456' = <function g at 0x101477c80>()
failure_demo.py:179: AssertionError
failure_demo.py:195: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors instance at 0x2cb69e0>
self = <failure_demo.TestMoreErrors instance at 0x1014b3ab8>
def test_global_func(self):
> assert isinstance(globf(42), float)
E assert isinstance(43, float)
E + where 43 = globf(42)
failure_demo.py:182: AssertionError
failure_demo.py:198: AssertionError
_______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors instance at 0x2cc6440>
self = <failure_demo.TestMoreErrors instance at 0x1014a2878>
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 0x2cc6440>.x
E + where 42 = <failure_demo.TestMoreErrors instance at 0x1014a2878>.x
failure_demo.py:186: AssertionError
failure_demo.py:202: AssertionError
_______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors instance at 0x2dcc200>
self = <failure_demo.TestMoreErrors instance at 0x10149da70>
def test_compare(self):
> assert globf(10) < 5
E assert 11 < 5
E + where 11 = globf(10)
failure_demo.py:189: AssertionError
failure_demo.py:205: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors instance at 0x2dce0e0>
self = <failure_demo.TestMoreErrors instance at 0x101493908>
def test_try_finally(self):
x = 1
@@ -484,5 +539,5 @@ get on the terminal - we are working on that):
> assert x == 0
E assert 1 == 0
failure_demo.py:194: AssertionError
======================== 35 failed in 0.19 seconds =========================
failure_demo.py:210: AssertionError
======================== 39 failed in 1.05 seconds =========================

View File

@@ -1,12 +1,14 @@
.. 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
Suppose we want to write a test that depends on a command line option.
Here is a basic pattern how to achieve this::
@@ -32,7 +34,8 @@ provide the ``cmdopt`` through a :ref:`function argument <funcarg>` factory::
Let's run this without supplying our new command line option::
$ py.test -q
$ py.test -q test_sample.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
@@ -50,11 +53,12 @@ Let's run this without supplying our new command line option::
test_sample.py:6: AssertionError
----------------------------- Captured stdout ------------------------------
first
1 failed in 0.02 seconds
1 failed in 0.50 seconds
And now with supplying a command line option::
$ py.test -q --cmdopt=type2
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
@@ -80,67 +84,46 @@ rather pass in different or more complex objects. See the
next example or refer to :ref:`mysetup` for more information
on real-life examples.
generating parameters combinations, depending on command line
----------------------------------------------------------------------------
Let's say we want to execute a test with different parameters
and the parameter range shall be determined by a command
line argument. Let's first write a simple computation test::
Dynamically adding command line options
--------------------------------------------------------------
# content of test_compute.py
.. regendoc:wipe
def test_compute(param1):
assert param1 < 4
Now we add a test configuration like this::
Through :confval:`addopts` you can statically add command line
options for your project. You can also dynamically modify
the command line arguments before they get processed::
# content of conftest.py
import sys
def pytest_cmdline_preparse(args):
if 'xdist' in sys.modules: # pytest-xdist plugin
import multiprocessing
num = max(multiprocessing.cpu_count() / 2, 1)
args[:] = ["-n", str(num)] + args
def pytest_addoption(parser):
parser.addoption("--all", action="store_true",
help="run all combinations")
If you have the :ref:`xdist plugin <xdist>` installed
you will now always perform test runs using a number
of subprocesses close to your CPU. Running in an empty
directory with the above conftest.py::
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.funcargnames:
if metafunc.config.option.all:
end = 5
else:
end = 2
for i in range(end):
metafunc.addcall(funcargs={'param1': i})
This means that we only run 2 tests if we do not pass ``--all``::
$ py.test -q test_compute.py
..
2 passed in 0.01 seconds
We run only two computations, so we see two dots.
let's run the full monty::
$ py.test -q --all test_compute.py
....F
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
gw0 I
gw0 [0]
param1 = 4
scheduling tests via LoadScheduling
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
1 failed, 4 passed in 0.03 seconds
============================= in 5.12 seconds =============================
As expected when running the full range of ``param1`` values
we'll get an error on the last one.
.. _`excontrolskip`:
.. _`retrieved by hooks as item keywords`:
control skipping of tests according to command line option
Control skipping of tests according to command line option
--------------------------------------------------------------
.. regendoc:wipe
Here is a ``conftest.py`` file adding a ``--runslow`` command
line option to control skipping of ``slow`` marked tests::
@@ -171,32 +154,33 @@ We can now write a test module like this::
and when running it will see a skipped "slow" test::
$ py.test test_module.py -rs # "-rs" means report details on the little 's'
$ py.test -rs # "-rs" means report details on the little 's'
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_module.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 2 items
test_module.py .s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-104/conftest.py:9: need --runslow option to run
SKIP [1] /Users/hpk/tmp/doc-exec-158/conftest.py:9: need --runslow option to run
=================== 1 passed, 1 skipped in 0.02 seconds ====================
=================== 1 passed, 1 skipped in 0.09 seconds ====================
Or run it including the ``slow`` marked test::
$ py.test test_module.py --runslow
$ py.test --runslow
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_module.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
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
If you have a test helper function called from a test you can
use the ``pytest.fail`` marker to fail a test with a certain message.
The test support function will not show up in the traceback if you
@@ -218,7 +202,8 @@ of tracebacks: the ``checkconfig`` function will not be shown
unless the ``--fulltrace`` command line option is specified.
Let's run our little function::
$ py.test -q
$ py.test -q test_checkconfig.py
collecting ... collected 1 items
F
================================= FAILURES =================================
______________________________ test_something ______________________________
@@ -228,18 +213,19 @@ Let's run our little function::
E Failed: not configured: 42
test_checkconfig.py:8: Failed
1 failed in 0.02 seconds
1 failed in 0.07 seconds
Detect if running from within a py.test run
--------------------------------------------------------------
.. regendoc:wipe
Usually it is a bad idea to make application code
behave differently if called from a test. But if you
absolutely must find out if your application code is
running from a test you can do something like this::
# content of conftest.py in your testing directory
# content of conftest.py
def pytest_configure(config):
import sys
@@ -254,8 +240,100 @@ and then check for the ``sys._called_from_test`` flag::
# called from within a test run
else:
# called "normally"
accordingly in your application. It's also a good idea
to rather use your own application module rather than ``sys``
to use your own application module rather than ``sys``
for handling flag.
Adding info to test report header
--------------------------------------------------------------
.. regendoc:wipe
It's easy to present extra information in a py.test run::
# content of conftest.py
def pytest_report_header(config):
return "project deps: mylib-1.1"
which will add the string to the test header accordingly::
$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
project deps: mylib-1.1
collecting ... collected 0 items
============================= in 0.01 seconds =============================
.. regendoc:wipe
You can also return a list of strings which will be considered as several
lines of information. You can of course also make the amount of reporting
information on e.g. the value of ``config.option.verbose`` so that
you present more information appropriately::
# content of conftest.py
def pytest_report_header(config):
if config.option.verbose > 0:
return ["info1: did you know that ...", "did you?"]
which will add info only when run with "--v"::
$ py.test -v
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2 -- /Users/hpk/venv/0/bin/python
info1: did you know that ...
did you?
collecting ... collected 0 items
============================= in 0.03 seconds =============================
and nothing when run plainly::
$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 0 items
============================= in 0.01 seconds =============================
profiling test duration
--------------------------
.. regendoc:wipe
.. versionadded: 2.2
If you have a slow running large test suite you might want to find
out which tests are the slowest. Let's make an artifical test suite::
# content of test_some_are_slow.py
import time
def test_funcfast():
pass
def test_funcslow1():
time.sleep(0.1)
def test_funcslow2():
time.sleep(0.2)
Now we can profile which test functions execute the slowest::
$ py.test --durations=3
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 3 items
test_some_are_slow.py ...
========================= slowest 3 test durations =========================
0.20s call test_some_are_slow.py::test_funcslow2
0.10s call test_some_are_slow.py::test_funcslow1
0.00s call test_some_are_slow.py::test_funcfast
========================= 3 passed in 0.33 seconds =========================

View File

@@ -17,5 +17,9 @@ def test_hello3():
def test_hello4():
assert 0
@xfail('pytest.__version__[0] != "17"')
def test_hello5():
assert 0
def test_hello6():
pytest.xfail("reason")

View File

@@ -1,32 +0,0 @@
changing Python test discovery patterns
--------------------------------------------------
You can influence python test file, function and class prefixes through
the :confval:`python_patterns` configuration valueto determine which
files are checked and which test functions are found. Example for using
a scheme that builds on ``check`` rather than on ``test`` prefixes::
# content of setup.cfg
[pytest]
python_patterns =
files: check_*.py
functions: check_
classes: Check
See
:confval:`python_funcprefixes` and :confval:`python_classprefixes`
changing test file discovery
-----------------------------------------------------
You can specify patterns where python tests are found::
python_testfilepatterns =
testing/**/{purebasename}.py
testing/*.py
.. note::
conftest.py files are never considered for test discovery

View File

@@ -12,25 +12,26 @@ On naming, nosetests, licensing and magic
Why a ``py.test`` instead of a ``pytest`` command?
++++++++++++++++++++++++++++++++++++++++++++++++++
Some historic, some practical reasons: ``py.test`` used to be part of
the ``py`` package which provided several developer utitilities,
all starting with ``py.<TAB>``, providing nice TAB-completion. If
Some of the reasons are historic, others are practical. ``py.test``
used to be part of the ``py`` package which provided several developer
utilities, all starting with ``py.<TAB>``, thus providing nice
TAB-completion. If
you install ``pip install pycmd`` you get these tools from a separate
package. These days the command line tool could be called ``pytest``
but then again many people have gotten used to the old name and there
is another tool named "pytest" so we just decided to stick with
but since many people have gotten used to the old name and there
is another tool named "pytest" we just decided to stick with
``py.test``.
What's the relation to nose and unittest?
How does py.test relate to nose and unittest?
+++++++++++++++++++++++++++++++++++++++++++++++++
py.test and nose_ share basic philosophy when it comes
to running Python tests. In fact, you can run many tests
written nose with py.test. nose_ was originally created
to running and writing Python tests. In fact, you can run many tests
written for nose with py.test. nose_ was originally created
as a clone of ``py.test`` when py.test was in the ``0.8`` release
cycle. As of version 2.0 support for running unittest test
suites is majorly improved and you should be able to run
many Django and Twisted test suites.
cycle. Note that starting with pytest-2.0 support for running unittest
test suites is majorly improved and you should be able to run
many Django and Twisted test suites without modification.
.. _features: test/features.html
@@ -39,28 +40,30 @@ What's this "magic" with py.test?
++++++++++++++++++++++++++++++++++++++++++
Around 2007 (version ``0.8``) some people claimed that py.test
was using too much "magic". It has been refactored a lot. Thrown
out old code. Deprecated unused approaches and code. And it is today
probably one of the smallest, most universally runnable and most
customizable testing frameworks for Python. It's true that
``py.test`` uses metaprogramming techniques, i.e. it views
test code similar to how compilers view programs, using a
somewhat abstract internal model.
was using too much "magic". Partly this has been fixed by removing
unused, deprecated or complicated code. It is today probably one
of the smallest, most universally runnable and most
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.
It's also true that the no-boilerplate testing is implemented by making
use of the Python assert statement through "re-interpretation":
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 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-intepretation.
Sidenote: it is good practise to avoid asserts with side effects.
A second "magic" issue is 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 a 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
@@ -69,7 +72,7 @@ Is using funcarg- versus xUnit setup a style question?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
For simple applications and for people experienced with nose_ or
unittest-style test setup using `xUnit style setup`_ often
unittest-style test setup using `xUnit style setup`_ probably
feels natural. For larger test suites, parametrized testing
or setup of complex test resources using funcargs_ may feel more natural.
Moreover, funcargs are ideal for writing advanced test support
@@ -86,13 +89,11 @@ in a managed class/module/function scope.
Why the ``pytest_funcarg__*`` name for funcarg factories?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
We alternatively implemented an explicit registration mechanism for
function argument factories. But lacking a good use case for this
indirection and flexibility we decided to go for `Convention over
Configuration`_ and rather have factories specified by convention.
Besides removing the need for an registration indirection it allows to
"grep" for ``pytest_funcarg__MYARG`` and will safely find all factory
functions for the ``MYARG`` function argument.
We like `Convention over Configuration`_ and didn't see much point
in allowing a more flexible or abstract mechanism. Moreover,
it is nice to be able to search for ``pytest_funcarg__MYARG`` in
source code and safely find all factory functions for
the ``MYARG`` function argument.
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
@@ -125,8 +126,8 @@ Issues with py.test, multiprocess and setuptools?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
On windows the multiprocess package will instantiate sub processes
by pickling and thus implicitely re-import a lot of local modules.
Unfortuantely, setuptools-0.6.11 does not ``if __name__=='__main__'``
by pickling and thus implicitly re-import a lot of local modules.
Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'``
protect its generated command line script. This leads to infinite
recursion when running a test that instantiates Processes.

View File

@@ -1,5 +1,5 @@
==============================================================
creating and managing test function arguments
Injecting objects into test functions (funcargs)
==============================================================
.. currentmodule:: _pytest.python
@@ -11,16 +11,26 @@ creating and managing test function arguments
Dependency injection through function arguments
=================================================
py.test allows to inject values into test functions through the *funcarg
mechanism*: For each argument name in a test function signature a factory is
looked up and called to create the value. The factory can live in the
same test class, test module, in a per-directory ``confest.py`` file or
in an external plugin. It has full access to the requesting test
function, can register finalizers and invoke lifecycle-caching
helpers. As can be expected from a systematic dependency
injection mechanism, this allows full de-coupling of resource and
fixture setup from test code, enabling more maintainable and
easy-to-modify test suites.
py.test lets you inject objects into test functions and precisely
control their life cycle in relation to the test execution. It is
also possible to run a test function multiple times with different objects.
The basic mechanism for injecting objects is also called the
*funcarg mechanism* because objects are ultimately injected
by calling a test function with it as an argument. Unlike the
classical xUnit approach *funcargs* relate more to `Dependency Injection`_
because they help to de-couple test code from objects required for
them to execute.
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
To create a value with which to call a test function a factory function
is called which gets full access to the test function context and can
register finalizers or invoke lifecycle-caching helpers. The factory
can be implemented in same test class or test module, or in a
per-directory ``conftest.py`` file or even in an external plugin. This
allows full de-coupling of test code and objects needed for test
execution.
A test function may be invoked multiple times in which case we
speak of :ref:`parametrized testing <parametrizing-tests>`. This can be
@@ -28,11 +38,14 @@ very useful if you want to test e.g. against different database backends
or with multiple numerical arguments sets and want to reuse the same set
of test functions.
Basic funcarg example
-----------------------
py.test comes with :ref:`builtinfuncargs` and there are some refined usages in the examples section.
Let's look at a simple self-contained example that you can put
into a test module::
.. _funcarg:
Basic injection example
--------------------------------
Let's look at a simple self-contained test module::
# content of ./test_simplefactory.py
def pytest_funcarg__myfuncarg(request):
@@ -41,12 +54,16 @@ into a test module::
def test_function(myfuncarg):
assert myfuncarg == 17
This test function needs an injected object named ``myfuncarg``.
py.test will discover and call the factory named
``pytest_funcarg__myfuncarg`` within the same module in this case.
Running the test looks like this::
$ py.test test_simplefactory.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_simplefactory.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_simplefactory.py F
@@ -60,10 +77,10 @@ Running the test looks like this::
E assert 42 == 17
test_simplefactory.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.03 seconds =========================
This means that the test function was called with a ``myfuncarg`` value
of ``42`` and the assert fails accordingly. Here is how py.test
This means that indeed the test function was called with a ``myfuncarg``
argument value of ``42`` and the assert fails. Here is how py.test
comes to call the test function this way:
1. py.test :ref:`finds <test discovery>` the ``test_function`` because
@@ -74,14 +91,15 @@ comes to call the test function this way:
2. ``pytest_funcarg__myfuncarg(request)`` is called and
returns the value for ``myfuncarg``.
3. the test function can now be called: ``test_function(42)``
and results in the above exception because of the assertion
3. the test function can now be called: ``test_function(42)``.
This results in the above exception because of the assertion
mismatch.
Note that if you misspell a function argument or want
to use one that isn't available, you'll see an error
with a list of available function arguments. You can
also issue::
with a list of available function arguments.
You can always issue::
py.test --funcargs test_simplefactory.py
@@ -101,9 +119,9 @@ think of as "resources").
The funcarg **request** object
=============================================
Each funcarg factory receives a **request** object which is tied to a
specific test function call. A request object is passed to a funcarg
factory and provides access to test configuration and context:
Each funcarg factory receives a **request** object tied to a specific test
function call. A request object is passed to a funcarg factory and provides
access to test configuration and context:
.. autoclass:: _pytest.python.FuncargRequest()
:members: function,cls,module,keywords,config
@@ -140,18 +158,17 @@ hook to generate several calls to the same test function::
# content of test_example.py
def pytest_generate_tests(metafunc):
if "numiter" in metafunc.funcargnames:
for i in range(10):
metafunc.addcall(funcargs=dict(numiter=i))
metafunc.parametrize("numiter", range(10))
def test_func(numiter):
assert numiter < 9
Running this::
Running this will generate ten invocations of ``test_func`` passing in each of the items in the list of ``range(10)``::
$ py.test test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_example.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 10 items
test_example.py .........F
@@ -164,14 +181,17 @@ Running this::
> assert numiter < 9
E assert 9 < 9
test_example.py:7: AssertionError
==================== 1 failed, 9 passed in 0.03 seconds ====================
test_example.py:6: AssertionError
==================== 1 failed, 9 passed in 0.07 seconds ====================
Note that the ``pytest_generate_tests(metafunc)`` hook is called during
Obviously, only when ``numiter`` has the value of ``9`` does the test fail. Note that the ``pytest_generate_tests(metafunc)`` hook is called during
the test collection phase which is separate from the actual test running.
Let's just look at what is collected::
$ py.test --collectonly test_example.py
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 10 items
<Module 'test_example.py'>
<Function 'test_func[0]'>
<Function 'test_func[1]'>
@@ -183,19 +203,23 @@ Let's just look at what is collected::
<Function 'test_func[7]'>
<Function 'test_func[8]'>
<Function 'test_func[9]'>
============================= 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.5 -- pytest-2.0.0.dev30 -- /home/hpk/venv/0/bin/python
test path 1: test_example.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2 -- /Users/hpk/venv/0/bin/python
collecting ... collected 10 items
test_example.py:6: test_func[7] PASSED
test_example.py:5: test_func[7] PASSED
======================== 9 tests deselected by '7' =========================
======================= 9 tests deselected by '-k7' ========================
================== 1 passed, 9 deselected in 0.01 seconds ==================
You might want to look at :ref:`more parametrization examples <paramexamples>`.
.. _`metafunc object`:
The **metafunc** object
@@ -216,4 +240,5 @@ in the class or module where a test function is defined:
``metafunc.config``: access to command line opts and general config
.. automethod:: Metafunc.addcall(funcargs=None, id=_notexists, param=_notexists)
.. automethod:: Metafunc.parametrize
.. automethod:: Metafunc.addcall(funcargs=None,id=_notexists,param=_notexists)

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,13 +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.0.dev30, imported from /home/hpk/p/pytest/pytest.py
This is py.test version 2.2.2, imported from /Users/hpk/p/pytest/pytest.pyc
setuptools registered plugins:
pytest-xdist-1.8 at /Users/hpk/p/pytest-xdist/xdist/plugin.pyc
pytest-cov-1.4 at /Users/hpk/venv/0/lib/python2.7/site-packages/pytest_cov.pyc
If you get an error checkout :ref:`installation issues`.
@@ -38,48 +47,39 @@ That's it. You can execute the test function now::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-70
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_sample.py F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
py.test found the ``test_answer`` function by following :ref:`standard test discovery rules <test discovery>`, basically detecting the ``test_`` prefixes. We got a failure report because our little ``func(3)`` call did not return ``5``. The report is formatted using the :ref:`standard traceback reporting`.
py.test found the ``test_answer`` function by following :ref:`standard test discovery rules <test discovery>`, basically detecting the ``test_`` prefixes. We got a failure report because our little ``func(3)`` call did not return ``5``.
.. note::
You can simply use the ``assert`` statement for coding 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
Asserting a certain exception is raised
Asserting that a certain exception is raised
--------------------------------------------------------------
If you want to assert some code raises an exception you can
If you want to assert that some code raises an exception you can
use the ``raises`` helper::
# content of test_sysexit.py
@@ -94,17 +94,18 @@ use the ``raises`` helper::
Running it with, this time in "quiet" reporting mode::
$ py.test -q test_sysexit.py
collecting ... collected 1 items
.
1 passed in 0.00 seconds
1 passed in 0.01 seconds
.. todo:: For further ways to assert exceptions see the `raises`
Grouping multiple tests in a class
--------------------------------------------------------------
If you start to have more than a few tests it often makes sense
to group tests logically, in classes and modules. Let's put two
tests in a class like this::
Once you start to have more than a few tests it often makes sense
to group tests logically, in classes and modules. Let's write a class
containing two tests::
# content of test_class.py
class TestClass:
@@ -121,19 +122,20 @@ There is no need to subclass anything. We can simply
run the module by passing its filename::
$ py.test -q test_class.py
collecting ... collected 2 items
.F
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass instance at 0x288fc20>
self = <test_class.TestClass instance at 0x1013225a8>
def test_two(self):
x = "hello"
> assert hasattr(x, 'check')
E assert hasattr('hello', 'check')
test_class.py:8: AssertionError
1 failed, 1 passed in 0.02 seconds
1 failed, 1 passed in 0.03 seconds
The first test passed, the second failed. Again we can easily see
the intermediate values used in the assertion, helping us to
@@ -157,21 +159,22 @@ py.test will lookup and call a factory to create the resource
before performing the test function call. Let's just run it::
$ py.test -q test_tmpdir.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmpdir = local('/tmp/pytest-122/test_needsfiles0')
tmpdir = local('/Users/hpk/tmp/pytest-20/test_needsfiles0')
def test_needsfiles(tmpdir):
print tmpdir
> assert 0
E assert 0
test_tmpdir.py:3: AssertionError
----------------------------- Captured stdout ------------------------------
/tmp/pytest-122/test_needsfiles0
1 failed in 0.05 seconds
/Users/hpk/tmp/pytest-20/test_needsfiles0
1 failed in 0.11 seconds
Before the test runs, a unique-per-test-invocation temporary directory
was created. More info at :ref:`tmpdir handling`.
@@ -180,13 +183,13 @@ 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:
* :ref:`cmdline` for command line invocation examples
* :ref:`good practises` for virtualenv, test layout, genscript support
* :ref:`good practises <goodpractises>` for virtualenv, test layout, genscript support
* :ref:`apiref` for documentation and examples on using py.test
* :ref:`plugins` managing and writing plugins
@@ -198,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?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -220,7 +228,7 @@ py.test not found on Windows despite installation?
- **Jython2.5.1 on Windows XP**: `Jython does not create command line launchers`_
so ``py.test`` will not work correctly. You may install py.test on
CPython and type ``py.test --genscript=mytest`` and then use
``jython mytest`` to run py.test for your tests to run in Jython.
``jython mytest`` to run py.test for your tests to run with Jython.
:ref:`examples` for more complex examples

View File

@@ -8,31 +8,29 @@ Good Integration Practises
Work with virtual environments
-----------------------------------------------------------
We recommend to work with virtualenv_ environments and use easy_install_
We recommend to use virtualenv_ environments and use easy_install_
(or pip_) for installing your application dependencies as well as
the ``pytest`` package itself. This way you get a much more reproducible
the ``pytest`` package itself. This way you will get a much more reproducible
environment. A good tool to help you automate test runs against multiple
dependency configurations or Python interpreters is `tox`_,
independently created by the main py.test author. The latter
is also useful for integration with the continous integration
server Hudson_.
dependency configurations or Python interpreters is `tox`_.
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
.. _`buildout`: http://www.buildout.org/
.. _pip: http://pypi.python.org/pypi/pip
Use tox and Continous Integration servers
Use tox and Continuous Integration servers
-------------------------------------------------
If you are (often) releasing code to the public you
If you frequently release code to the public you
may want to look into `tox`_, the virtualenv test automation
tool and its `pytest support <http://codespeak.net/tox/example/pytest.html>`_.
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continous integration server like Hudson_ pick it up.
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up
and generate reports.
.. _standalone:
.. _`genscript method`:
Create a py.test standalone Script
Create a py.test standalone script
-------------------------------------------
If you are a maintainer or application developer and want others
@@ -55,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::
@@ -90,7 +88,7 @@ If you now type::
this will execute your tests using ``runtest.py``. As this is a
standalone version of ``py.test`` no prior installation whatsoever is
required for calling the test command. You can also pass additional
arguments to the subprocess-calls like your test directory or other
arguments to the subprocess-calls such as your test directory or other
options.
.. _`test discovery`:
@@ -101,16 +99,16 @@ Conventions for Python test discovery
``py.test`` implements the following standard test discovery:
* collection starts from initial command line arguments
* collection starts from the initial command line arguments
which may be directories, filenames or test ids.
* recurse into directories, unless they match :confval:`norecursedirs`
* ``test_*.py`` or ``*_test.py`` files, imported by their `package name`_.
* ``Test`` prefixed test classes (without an ``__init__`` method)
* ``test_`` prefixed test functions or methods are test items
For changing and customization example, see :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
@@ -140,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
@@ -152,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" directory not
containing an ``__init__.py``
* find ``basedir`` -- this is the first "upward" (towards the root)
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,36 +1,39 @@
Welcome to ``py.test``!
Welcome to pytest!
=============================================
- **a mature full-featured testing tool**
- **a mature fully featured testing tool**
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
- continously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
- used in :ref:`many projects <projects>`, ranging from 10 to 10000 tests
- has :ref:`comprehensive documentation <toc>`
- comes with :ref:`tested examples <examples>`
- 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
- comes with many :ref:`tested examples <examples>`
- supports :ref:`good integration practises <goodpractises>`
- **provides no-boilerplate testing**
- makes it :ref:`easy to get started <getstarted>`, refined :ref:`usage options <usage>`
- makes it :ref:`easy to get started <getstarted>`,
- refined :ref:`usage options <usage>`
- :ref:`assert with the assert statement`
- helpful :ref:`traceback and failing assertion reporting <tbreportdemo>`
- allows `print debugging <printdebugging>`_ and `generic output capturing <captures>`_
- supports :pep:`8` compliant coding style in tests
- allows :ref:`print debugging <printdebugging>` and :ref:`the
capturing of standard output during test execution <captures>`
- supports :pep:`8` compliant coding styles in tests
- **supports functional testing and complex test setups**
- (new in 2.2) :ref:`durations`
- (much improved in 2.2) :ref:`marking and test selection <mark>`
- (improved in 2.2) :ref:`parametrized test functions <parametrized test functions>`
- advanced :ref:`skip and xfail`
- generic :ref:`marking and test selection <mark>`
- unique :ref:`dependency injection through funcargs <funcargs>`
- can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
- can :ref:`continously re-run failing tests <looponfailing>`
- can :ref:`continuously re-run failing tests <looponfailing>`
- many :ref:`builtin helpers <pytest helpers>`
- flexible :ref:`Python test discovery`
- unique :ref:`dependency injection through funcargs <funcargs>`
- :ref:`parametrized test functions <parametrized test functions>`
- **integrates many common testing methods**
@@ -38,8 +41,8 @@ Welcome to ``py.test``!
tests, including running testcases made for Django and trial
- supports extended :ref:`xUnit style setup <xunitsetup>`
- supports domain-specific :ref:`non-python tests`
- supports generating testing coverage reports
- `Javasript unit- and functional testing`_
- supports the generation of testing coverage reports
- `Javascript unit- and functional testing`_
- **extensive plugin and customization system**
@@ -47,13 +50,8 @@ Welcome to ``py.test``!
- customizations can be per-directory, per-project or per PyPI released plugins
- it is easy to add command line options or do other kind of add-ons and customizations.
.. _`Javasript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
.. _`Javascript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
.. toctree::
:hidden:
contact.txt
contents.txt

View File

@@ -10,11 +10,11 @@
.. _pytest: http://pypi.python.org/pypi/pytest
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
.. _`easy_install`:
.. _`distribute docs`:
.. _`distribute`: http://pypi.python.org/pypi/distribute
.. _`pip`: http://pypi.python.org/pypi/pip
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
.. _hudson: http://hudson-ci.org/
.. _jenkins: http://jenkins-ci.org/
.. _tox: http://codespeak.net/tox

View File

@@ -1,122 +1,25 @@
.. _mark:
mark test functions with attributes
Marking test functions with attributes
=================================================================
.. currentmodule:: _pytest.mark
By using the ``pytest.mark`` helper you can instantiate
decorators that will set named meta data on test functions.
By using the ``pytest.mark`` helper you can easily set
metadata on your test functions. There are
some builtin markers, for example:
Marking a single function
----------------------------------------------------
* :ref:`skipif <skipif>` - skip a test function if a certain condition is met
* :ref:`xfail <xfail>` - produce an "expected failure" outcome if a certain
condition is met
* :ref:`parametrize <parametrizemark>` to perform multiple calls
to the same test function.
You can "mark" a test function with meta data like this::
It's easy to create custom markers or to apply markers
to whole test classes or modules. See :ref:`mark examples` for examples
which also serve as documentation.
import pytest
@pytest.mark.webtest
def test_send_http():
...
This will set the function attribute ``webtest`` to a :py:class:`MarkInfo`
instance. You can also specify parametrized meta data like this::
# content of test_mark.py
import pytest
@pytest.mark.webtest(firefox=30)
def test_receive():
pass
@pytest.mark.webtest("functional", firefox=30)
def test_run_and_look():
pass
and access it from other places like this::
test_receive.webtest.kwargs['firefox'] == 30
test_run_and_look.webtest.args[0] == "functional"
.. _`scoped-marking`:
Marking whole classes or modules
----------------------------------------------------
If you are programming with Python2.6 you may use ``pytest.mark`` decorators
with classes to apply markers to all its test methods::
# content of test_mark_classlevel.py
import pytest
@pytest.mark.webtest
class TestClass:
def test_startup(self):
pass
def test_startup_and_more(self):
pass
This is equivalent to directly applying the decorator to the
two test functions.
To remain compatible with Python2.5 you can also set a
``pytestmark`` attribute on a TestClass like this::
import pytest
class TestClass:
pytestmark = pytest.mark.webtest
or if you need to use multiple markers you can use a list::
import pytest
class TestClass:
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
You can also set a module level marker::
import pytest
pytestmark = pytest.mark.webtest
in which case it will be applied to all functions and
methods defined in the module.
Using ``-k TEXT`` to select tests
----------------------------------------------------
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.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
test_mark.py ..
test_mark_classlevel.py ..
========================= 4 passed in 0.01 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.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
===================== 4 tests deselected by '-webtest' =====================
======================= 4 deselected in 0.01 seconds =======================
Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
test_mark_classlevel.py ..
==================== 2 tests deselected by 'TestClass' =====================
================== 2 passed, 2 deselected in 0.01 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
@@ -9,46 +9,39 @@ on global settings or which invokes code which cannot be easily
tested such as network access. The ``monkeypatch`` function argument
helps you to safely set/delete an attribute, dictionary item or
environment variable or to modify ``sys.path`` for importing.
See the `monkeypatch blog post`_ one some introduction material
and motivation.
See the `monkeypatch blog post`_ for some introduction material
and a discussion of its motivation.
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
Simple example: patching ``os.path.expanduser``
Simple example: monkeypatching functions
---------------------------------------------------
If you e.g. want to pretend that ``os.expanduser`` returns a certain
If you want to pretend that ``os.expanduser`` returns a certain
directory, you can use the :py:meth:`monkeypatch.setattr` method to
patch this function before calling into a function which uses it::
# content of test_module.py
import os.path
def getssh(): # pseudo application code
return os.path.join(os.expanduser("~admin"), '.ssh')
return os.path.join(os.path.expanduser("~admin"), '.ssh')
def test_mytest(monkeypatch):
def mockreturn(path):
return '/abc'
monkeypatch.setattr(os.path, 'expanduser', mockreturn)
x = getssh()
assert x == '/abc'
assert x == '/abc/.ssh'
After the test function finishes the ``os.path.expanduser`` modification
will be undone.
.. background check:
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-75
============================= in 0.00 seconds =============================
Here our test function monkeypatches ``os.path.expanduser`` and
then calls into an function that calls it. After the test function
finishes the ``os.path.expanduser`` modification will be undone.
Method reference of the monkeypatch function argument
-----------------------------------------------------
.. autoclass:: monkeypatch
:members: setattr, delattr, setitem, delitem, setenv, delenv, syspath_prepend, undo
:members: setattr, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo
``monkeypatch.setattr/delattr/delitem/delenv()`` all
by default raise an Exception if the target does not exist.

View File

@@ -16,5 +16,5 @@ these renaming rules::
py.test.cmdline.main -> pytest.main
The old ``py.test.*`` ways to access functionality remain
valid but you are encouraged to do global renames according
valid but you are encouraged to do global renaming according
to the above rules in your test code.

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

@@ -1,7 +1,7 @@
Writing, managing and understanding plugins
=============================================
.. _plugins:
.. _`local plugin`:
Working with plugins and conftest files
=============================================
py.test implements all aspects of configuration, collection, running and reporting by calling `well specified hooks`_. Virtually any Python module can be registered as a plugin. It can implement any number of hook functions (usually two or three) which all have a ``pytest_`` prefix, making hook functions easy to distinguish and find. There are three basic locations types:
@@ -11,14 +11,18 @@ py.test implements all aspects of configuration, collection, running and reporti
.. _`pytest/plugin`: http://bitbucket.org/hpk42/pytest/src/tip/pytest/plugin/
.. _`conftest.py plugins`:
.. _`conftest.py`:
.. _`localplugin`:
.. _`conftest`:
conftest.py: local per-directory plugins
--------------------------------------------------------------
local ``conftest.py`` plugins contain directory-specific hook
implementations. Session and test running activities will
invoke all hooks defined in "higher up" ``conftest.py`` files.
Example: Assume the following layout and content of files::
invoke all hooks defined in ``conftest.py`` files closer to the
root of the filesystem. Example: Assume the following layout
and content of files::
a/conftest.py:
def pytest_runtest_setup(item):
@@ -38,21 +42,17 @@ Here is how you might run it::
py.test test_flat.py # will not show "setting up"
py.test a/test_sub.py # will show "setting up"
A note on ordering: ``py.test`` loads all ``conftest.py`` files upwards
from the command line file arguments. It usually performs look up
right-to-left, i.e. the hooks in "closer" conftest files will be called
earlier than further away ones.
.. Note::
If you have ``conftest.py`` files which do not reside in a
python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" can be ambigous because there might be other
"import conftest" can be ambiguous because there might be other
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
It is thus good practise for projects to either put ``conftest.py``
under a package scope or to never import anything from a
conftest.py file.
.. _`external plugins`:
.. _`extplugins`:
Installing External Plugins / Searching
------------------------------------------------------
@@ -64,9 +64,26 @@ tool, for example::
pip uninstall pytest-NAME
If a plugin is installed, py.test automatically finds and integrates it,
there is no need to activate it. If you don't need a plugin anymore simply
de-install it. You can find a list of available plugins through a
`pytest- pypi.python.org search`_.
there is no need to activate it. Here is a list of known plugins:
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
to capture and assert about messages from the logging module
* `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_:
to distribute tests to CPUs and remote hosts, looponfailing mode,
see also :ref:`xdist`
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
coverage reporting, compatible with distributed testing
* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
a ``--pep8`` option to enable PEP8 compliance checking.
* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
a plugin to run javascript unittests in life browsers
(**version 0.8.9 not compatible with pytest-2.0**)
You may discover more plugins through a `pytest- pypi.python.org search`_.
.. _`available installable plugins`:
.. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search
@@ -93,12 +110,12 @@ Making your plugin installable by others
-----------------------------------------------
If you want to make your plugin externally available, you
may define a so called entry point for your distribution so
may define a so-called entry point for your distribution so
that ``py.test`` finds your plugin module. Entry points are
a feature that is provided by `setuptools`_ or `Distribute`_.
The concrete entry point is ``pytest11``. To make your plugin
available you can insert the following lines in your
setuptools/distribute-based setup-invocation:
py.test looks up the ``pytest11`` entrypoint to discover its
plugins and you can thus make your plugin available by definig
it in your setuptools/distribute-based setup-invocation:
.. sourcecode:: python
@@ -118,8 +135,8 @@ setuptools/distribute-based setup-invocation:
)
If a package is installed this way, py.test will load
``myproject.pluginmodule`` and accordingly call functions
if they match the `well specified hooks`_.
``myproject.pluginmodule`` as a plugin which can define
`well specified hooks`_.
Plugin discovery order at tool startup
--------------------------------------------
@@ -170,12 +187,42 @@ the plugin manager like this:
If you want to look at the names of existing plugins, use
the ``--traceconfig`` option.
.. _`findpluginname`:
Finding out which plugins are active
----------------------------------------------------------------------------
If you want to find out which plugins are active in your
environment you can type::
py.test --traceconfig
and will get an extended test header which shows activated plugins
and their names. It will also print local plugins aka
:ref:`conftest.py <conftest>` files when they are loaded.
.. _`cmdunregister`:
Deactivating / unregistering a plugin by name
----------------------------------------------------------------------------
You can prevent plugins from loading or unregister them::
py.test -p no:NAME
This means that any subsequent try to activate/load the named
plugin will it already existing. See :ref:`findpluginname` for
how to obtain the name of a plugin.
.. _`builtin plugins`:
py.test default plugin reference
====================================
You can find the source code for the following plugins
in the `pytest repository <http://bitbucket.org/hpk42/pytest/>`_.
.. autosummary::
_pytest.assertion
@@ -195,7 +242,7 @@ py.test default plugin reference
_pytest.recwarn
_pytest.resultlog
_pytest.runner
_pytest.session
_pytest.main
_pytest.skipping
_pytest.terminal
_pytest.tmpdir
@@ -206,22 +253,23 @@ py.test default plugin reference
py.test hook reference
====================================
hook specification and validation
Hook specification and validation
-----------------------------------------
py.test calls hook functions to implement initialization, running,
test execution and reporting. When py.test loads a plugin it validates
that all hook functions conform to their respective hook specification.
that each hook function conforms to its respective hook specification.
Each hook function name and its argument names need to match a hook
specification exactly but it is allowed for a hook function to accept
*less* parameters than specified. If you mistype argument names or the
hook name itself you get useful errors.
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
Initialization, command line and configuration hooks
--------------------------------------------------------------------
.. currentmodule:: _pytest.hookspec
.. autofunction:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_parse
.. autofunction:: pytest_namespace
.. autofunction:: pytest_addoption
@@ -229,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.
@@ -242,13 +290,14 @@ All all runtest related hooks receive a :py:class:`pytest.Item` object.
For deeper understanding you may look at the default implementation of
these hooks in :py:mod:`_pytest.runner` and maybe also
in :py:mod:`_pytest.pdb` which intercepts creation
of reports in order to drop to interactive debugging.
in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture`
and its input/output capturing in order to immediately drop
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:
@@ -263,7 +312,7 @@ you can use the following hook:
.. autofunction:: pytest_pycollect_makeitem
reporting hooks
Reporting hooks
------------------------------
Session related reporting hooks:
@@ -278,7 +327,6 @@ test execution:
.. autofunction: pytest_runtest_logreport
Reference of important objects involved in hooks
===========================================================
@@ -288,14 +336,14 @@ Reference of important objects involved in hooks
.. autoclass:: _pytest.config.Parser
:members:
.. autoclass:: _pytest.session.Node(name, parent)
.. autoclass:: _pytest.main.Node(name, parent)
:members:
..
.. autoclass:: _pytest.session.File(fspath, parent)
.. autoclass:: _pytest.main.File(fspath, parent)
:members:
.. autoclass:: _pytest.session.Item(name, parent)
.. autoclass:: _pytest.main.Item(name, parent)
:members:
.. autoclass:: _pytest.python.Module(name, parent)
@@ -313,4 +361,3 @@ Reference of important objects involved in hooks
.. autoclass:: _pytest.runner.TestReport
:members:

View File

@@ -3,21 +3,28 @@
Project examples
==========================
Here are some examples of projects using py.test:
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
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
* `six <http://pypi.python.org/pypi/six/>`_ Python 2 and 3 compatibility utilities
* `pediapress <http://code.pediapress.com/wiki/wiki>`_ MediaWiki articles
* `mwlib <http://pypi.python.org/pypi/mwlib>`_ mediawiki parser and utility library
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
* `pylib <http://pylib.org>`_ cross-platform path, IO, dynamic code library
* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_ create standalone executables from Python scripts
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
* `waskr <http://pacha.cafepais.com/>`_ WSGI Stats Middleware
* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
* `Circuits <http://pypi.python.org/pypi/circuits>`_ lightweight Event Driven Framework
* `pygtk-helpers <http://bitbucket.org/aafshar/pygtkhelpers-main/>`_ easy interaction with PyGTK
* `QuantumCore <http://quantumcore.org/>`_ statusmessage and repoze openid plugin
@@ -31,15 +38,17 @@ Here are some examples of projects using py.test:
* `bu <http://packages.python.org/bu/>`_ a microscopic build system
* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
* many more ... (please send notes via the :ref:`contact`)
Some organisations using py.test
-----------------------------------
* `Square Kilometre Array <http://ska.ac.za/>`_
* `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/>`_
* `Stups department of Heinrich Heine University <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `Open End <http://openend.se>`_
* `Laboraratory of Bioinformatics <http://genesilico.pl/>`_
* `merlinux <http://merlinux.eu>`_
* `Shootq <http://web.shootq.com/>`_
* `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/>`_
* `merlinux, Germany <http://merlinux.eu>`_
* many more ... (please be so kind to send a note via :ref:`contact`)

View File

@@ -1,2 +1,2 @@
[pytest]
# just defined to prevent the root level tox.ini to kick in
# just defined to prevent the root level tox.ini from kicking in

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
@@ -35,4 +35,3 @@ warning::
def test_global():
pytest.deprecated_call(myfunction, 17)

View File

@@ -1,49 +1,60 @@
.. _`skip and xfail`:
skip and xfail mechanisms
Skip and xfail: dealing with tests that can not succeed
=====================================================================
You can skip or "xfail" test functions, either by marking functions
through a decorator or by calling the ``pytest.skip|xfail`` helpers.
A *skip* means that you expect your test to pass unless a certain configuration or condition (e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that you expect your test to fail because there is an
implementation problem. py.test counts and lists *xfailing* tests separately
and you can provide info such as a bug number or a URL to provide a
human readable problem context.
If you have test functions that cannot be run on certain platforms
or that you expect to fail you can mark them accordingly or you
may call helper functions during execution of setup or test functions.
Usually detailed information about skipped/xfailed tests is not shown
to avoid cluttering the output. You can use the ``-r`` option to
see details corresponding to the "short" letters shown in the
test progress::
A *skip* means that you expect your test to pass unless a certain
configuration or condition (e.g. wrong Python interpreter, missing
dependency) prevents it to run. And *xfail* means that your test
can run but you expect it to fail because there is an implementation problem.
py.test -rxs # show extra info on skips and xfail tests
py.test counts and lists *skip* and *xfail* tests separately. However,
detailed information about skipped/xfailed tests is not shown by default
to avoid cluttering the output. You can use the ``-r`` option to see
details corresponding to the "short" letters shown in the test
progress::
py.test -rxs # show extra info on skips and xfails
(See :ref:`how to change command line options defaults`)
.. _skipif:
Skipping a single function
Marking a test function to be skipped
-------------------------------------------
Here is an example for marking a test function to be skipped
Here is an example of marking a test function to be skipped
when run on a Python3 interpreter::
import sys
@pytest.mark.skipif("sys.version_info >= (3,0)")
def test_function():
...
During test function setup the skipif condition is
evaluated by calling ``eval(expr, namespace)``. The namespace
contains the ``sys`` and ``os`` modules and the test
``config`` object. The latter allows you to skip based
on a test configuration value e.g. like this::
evaluated by calling ``eval('sys.version_info >= (3,0)', namespace)``.
(*New in version 2.0.2*) The namespace contains all the module globals of the test function so that
you can for example check for versions of a module you are using::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
import mymodule
@pytest.mark.skipif("mymodule.__version__ < '1.2'")
def test_function():
...
The test function will not be run ("skipped") if
``mymodule`` is below the specified version. The reason
for specifying the condition as a string is mainly that
py.test can report a summary of skip conditions.
For information on the construction of the ``namespace``
see `evaluation of skipif/xfail conditions`_.
Create a shortcut for your conditional skip decorator
at module level like this::
You can of course create a shortcut for your conditional skip
decorator at module level like this::
win32only = pytest.mark.skipif("sys.platform != 'win32'")
@@ -51,13 +62,12 @@ at module level like this::
def test_function():
...
skip test functions of a class
Skip all test functions of a class
--------------------------------------
As with all function :ref:`marking` you can do it at
As with all function :ref:`marking <mark>` you can skip test functions at the
`whole class- or module level`_. Here is an example
for skipping all methods of a test class based on platform::
for skipping all methods of a test class based on the platform::
class TestPosixCalls:
pytestmark = pytest.mark.skipif("sys.platform == 'win32'")
@@ -65,9 +75,10 @@ for skipping all methods of a test class based on platform::
def test_function(self):
"will not be setup or run under 'win32' platform"
The ``pytestmark`` decorator will be applied to each test function.
If your code targets python2.6 or above you can equivalently use
the skipif decorator on classes::
The ``pytestmark`` special name tells py.test to apply it to each test
function in the class. If your code targets python2.6 or above you can
more naturally use the skipif decorator (and any other marker) on
classes::
@pytest.mark.skipif("sys.platform == 'win32'")
class TestPosixCalls:
@@ -75,15 +86,13 @@ the skipif decorator on classes::
def test_function(self):
"will not be setup or run under 'win32' platform"
It is fine in general to apply multiple "skipif" decorators
on a single function - this means that if any of the conditions
apply the function will be skipped.
Using multiple "skipif" decorators on a single function is generally fine - it means that if any of the conditions apply the function execution will be skipped.
.. _`whole class- or module level`: mark.html#scoped-marking
.. _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
@@ -104,16 +113,16 @@ By specifying on the commandline::
you can force the running and reporting of an ``xfail`` marked test
as if it weren't marked at all.
Same as with skipif_ you can also selectively expect a failure
depending on platform::
As with skipif_ you can also mark your expectation of a failure
on a particular platform::
@pytest.mark.xfail("sys.version_info >= (3,0)")
def test_function():
...
You can also avoid running an "xfail" test at all or
You can furthermore prevent the running of an "xfail" test or
specify a reason such as a bug ID or similar. Here is
a simple test file with usages:
a simple test file with the several usages:
.. literalinclude:: example/xfail_demo.py
@@ -121,24 +130,51 @@ 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.5 -- pytest-2.0.0.dev31
test path 1: xfail_demo.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 6 items
xfail_demo.py xxxxx
xfail_demo.py xxxxxx
========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN]
reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
bug 110
XFAIL xfail_demo.py::test_hello5
condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
reason: reason
======================== 5 xfailed in 0.04 seconds =========================
======================== 6 xfailed in 0.16 seconds =========================
imperative xfail from within a test or setup function
.. _`evaluation of skipif/xfail conditions`:
Evaluation of skipif/xfail expressions
----------------------------------------------------
.. versionadded:: 2.0.2
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
dictionary which is constructed as follows:
* the namespace is initialized by putting the ``sys`` and ``os`` modules
and the pytest ``config`` object into it.
* updated with the module globals of the test function for which the
expression is applied.
The pytest ``config`` object allows you to skip based on a test configuration value
which you might have added::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
...
Imperative xfail from within a test or setup function
------------------------------------------------------
If you cannot declare xfail-conditions at import time
@@ -147,10 +183,10 @@ within test or setup code. Example::
def test_function():
if not valid_config():
pytest.xfail("unsuppored configuration")
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
@@ -159,21 +195,21 @@ or within a test or test setup function::
docutils = pytest.importorskip("docutils")
If ``docutils`` cannot be imported here, this will lead to a
skip outcome of the test. You can also skip dependeing if
if a library does not come with a high enough version::
skip outcome of the test. You can also skip based on the
version number of a library::
docutils = pytest.importorskip("docutils", minversion="0.3")
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
you can also imperatively produce a Skip-outcome from
you can also imperatively produce a skip-outcome from
within test or setup code. Example::
def test_function():
if not valid_config():
pytest.skip("unsuppored configuration")
pytest.skip("unsupported configuration")

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`_
- `generating parametrized tests with funcargs`_ (uses deprecated ``addcall()`` API.
- `test generators and cached setup`_
- `parametrizing tests, generalized`_ (blog post)
- `putting test-hooks into local or global plugins`_ (blog post)
distributed testing:
Assertion introspection:
- `simultanously test your code on all platforms`_ (blog entry)
- `(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>`_
plugin specific examples:
Distributed testing:
- `simultaneously test your code on all platforms`_ (blog entry)
Plugin specific examples:
- `skipping slow tests by default in py.test`_ (blog entry)
@@ -42,14 +47,14 @@ plugin specific examples:
.. _`many examples in the docs for plugins`: plugin/index.html
.. _`monkeypatch plugin`: plugin/monkeypatch.html
.. _`application setup in test functions with funcargs`: funcargs.html#appsetup
.. _`simultanously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
.. _`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

@@ -17,7 +17,7 @@ customize_: configuration, customization, extensions
changelog_: history of changes covering last releases
**Continous Integration of py.test's own tests and plugins with Hudson**:
**Continuous Integration of py.test's own tests and plugins with Hudson**:
`http://hudson.testrun.org/view/pytest`_

View File

@@ -5,7 +5,7 @@ Mission
py.test strives to make testing a fun and no-boilerplate effort.
The tool is distributed as part of the `py` package which contains supporting APIs that
are also useable independently. The project independent ``py.test`` command line tool helps you to:
are also usable independently. The project independent ``py.test`` command line tool helps you to:
* rapidly collect and run tests
* run unit- or doctests, functional or integration tests

View File

@@ -35,10 +35,11 @@ However easy_install does not provide an uninstall facility.
.. IMPORTANT::
Ensure that you manually delete the init_covmain.pth file in your site-packages directory.
Ensure that you manually delete the init_covmain.pth file in your
site-packages directory.
This file starts coverage collection of subprocesses if appropriate during site initialisation
at python startup.
This file starts coverage collection of subprocesses if appropriate during
site initialization at python startup.
Usage
@@ -198,11 +199,11 @@ slave.
For subprocess measurement environment variables must make it from the main process to the
subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must
do normal site initialisation so that the environment variables can be detected and coverage
do normal site initialization so that the environment variables can be detected and coverage
started.
Acknowledgements
Acknowledgments
----------------
Holger Krekel for pytest with its distributed testing support.

View File

@@ -1,7 +1,7 @@
pytest_oejskit plugin (EXTERNAL)
==========================================
The `oejskit`_ offers a py.test plugin for running Javascript tests in life browers. Running inside the browsers comes with some speed cost, on the other hand it means for example the code is tested against the real-word DOM implementations.
The `oejskit`_ offers a py.test plugin for running Javascript tests in life browsers. Running inside the browsers comes with some speed cost, on the other hand it means for example the code is tested against the real-word DOM implementations.
The approach enables to write integration tests such that the JavaScript code is tested against server-side Python code mocked as necessary. Any server-side framework that can already be exposed through WSGI (or for which a subset of WSGI can be written to accommodate the jskit own needs) can play along.
For more info and download please visit the `oejskit PyPI`_ page.

View File

@@ -129,7 +129,7 @@ put options values in a ``conftest.py`` file like this::
option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5']
option_dist = True
Any commandline ``--tx`` specifictions will add to the list of
Any commandline ``--tx`` specifications will add to the list of
available execution environments.
Specifying "rsync" dirs in a conftest.py

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,15 +28,15 @@ 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.5 -- pytest-2.0.0.dev30
test path 1: test_tmpdir.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_tmpdir.py F
================================= FAILURES =================================
_____________________________ test_create_file _____________________________
tmpdir = local('/tmp/pytest-123/test_create_file0')
tmpdir = local('/Users/hpk/tmp/pytest-21/test_create_file0')
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
@@ -47,16 +47,16 @@ Running this would result in a passed test except for the last
E assert 0
test_tmpdir.py:7: AssertionError
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.07 seconds =========================
.. _`base temporary directory`:
the default base temporary directory
The default base temporary directory
-----------------------------------------------
Temporary directories are by default created as sub directories of
Temporary directories are by default created as sub-directories of
the system temporary directory. The base name will be ``pytest-NUM`` where
``NUM`` will be incremenated with each test run. Moreover, entries older
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
You can override the default temporary directory setting like this::
@@ -64,8 +64,8 @@ You can override the default temporary directory setting like this::
py.test --basetemp=mydir
When distributing tests on the local machine, ``py.test`` takes care to
configure a basetemp directory for the sub processes such that all
temporary data lands below below a single per-test run basetemp directory.
configure a basetemp directory for the sub processes such that all temporary
data lands below a single per-test run basetemp directory.
.. _`py.path.local`: http://pylib.org/path.html

View File

@@ -1,14 +1,14 @@
.. _`unittest.TestCase`:
unittest.TestCase support
Support for unittest.TestCase
=====================================================================
py.test has limited support for running Python `unittest.py style`_ tests.
It will automatically collect ``unittest.TestCase`` subclasses
and their ``test`` methods in test files. It will invoke
``setUp/tearDown`` methods but also perform py.test's standard ways
of treating tests like e.g. IO capturing::
of treating tests such as IO capturing::
# content of test_unittest.py
@@ -24,8 +24,8 @@ Running it yields::
$ py.test test_unittest.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_unittest.py
platform darwin -- Python 2.7.1 -- pytest-2.2.2
collecting ... collected 1 items
test_unittest.py F
@@ -37,26 +37,12 @@ Running it yields::
def test_method(self):
x = 1
> self.assertEquals(x, 3)
E AssertionError: 1 != 3
test_unittest.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <test_unittest.MyTest testMethod=test_method>, first = 1, second = 3
msg = None
def failUnlessEqual(self, first, second, msg=None):
"""Fail if the two objects are unequal as determined by the '=='
operator.
"""
if not first == second:
raise self.failureException, \
> (msg or '%r != %r' % (first, second))
E AssertionError: 1 != 3
/usr/lib/python2.6/unittest.py:350: AssertionError
test_unittest.py:8: AssertionError
----------------------------- Captured stdout ------------------------------
hello
========================= 1 failed in 0.02 seconds =========================
========================= 1 failed in 0.15 seconds =========================
.. _`unittest.py style`: http://docs.python.org/library/unittest.html

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