Compare commits

..

550 Commits
2.0.2 ... 2.3.2

Author SHA1 Message Date
holger krekel
6ad16936bb bump version to 2.3.2, regen docs and changelog 2012-10-25 13:48:31 +02:00
holger krekel
bcb8dc71d2 fix issue208 and fix issue29 - avoid long pauses in traceback printing
by using the new getstatementrange() code of the py lib which uses
AST-parsing rather than the previous heuristic which had O(n^2) complexity
(with n = len(sourcelines))

- require new (in-dev) py version to
2012-10-25 12:08:11 +02:00
holger krekel
b8277bfed8 fix issue127 - improve pytest_addoption and related documentation 2012-10-25 11:07:07 +02:00
holger krekel
2637326782 improve support for trial a bit more: don't run trial's empty TestCase.runTest() method 2012-10-22 19:22:01 +02:00
holger krekel
aa79c0a4b9 fix unittest emulation: TestCase.runTest is now ignored
if there are test* methods.
2012-10-22 16:25:09 +02:00
holger krekel
05c86aeb28 make sure ihook uses a node's fspath - important for hooks
e.g. during a Module's collect to pick up conftest.py files
residing in the same dir
2012-10-22 16:12:22 +02:00
holger krekel
f28f073c7c fix teardown-ordering for parametrized setups/teardowns 2012-10-22 12:16:54 +02:00
holger krekel
036557ac18 fix issue206 - unset PYTHONDONTWRITEBYTECODE in assertrewrite test 2012-10-22 11:14:18 +02:00
holger krekel
1b61fbc8ed - fix test_nose.py by being more tolerant about the error message
(differs between py32 and py33, thanks Arfrever)
- use pypi again now that py is released
2012-10-22 10:55:59 +02:00
holger krekel
97f03edcd6 fix issue205 - nested conftest to pickup pycollect_makemodule - relates to the two
reports of a failing doc/en/example/py2py3.
2012-10-22 10:17:50 +02:00
holger krekel
7e5efa0005 mention twisted with external plugins 2012-10-22 09:32:41 +02:00
holger krekel
d55fc611c4 properly handle non-existent PYTHONPATH 2012-10-20 17:39:15 +02:00
holger krekel
720fe3405b allow to run self-tests with "python setup.py test" for pytest tests itself 2012-10-20 17:32:03 +02:00
holger krekel
c894b2b459 add tox.ini to distribution 2012-10-20 17:08:02 +02:00
holger krekel
6d5bf4b908 Added tag 2.3.1 for changeset acf0e1477fb1 2012-10-20 14:33:23 +02:00
holger krekel
d4d213f83d some more fixes 2012-10-20 14:10:12 +02:00
holger krekel
289ee1c6ea prepare a 2.3.1 2012-10-20 14:05:33 +02:00
holger krekel
f41f7fda68 improve --markers output 2012-10-20 13:56:53 +02:00
holger krekel
9ed127b5da fix issue203 - fixture functions with a scope=function should have a "self" that points to the actual instance with which the test functions run. 2012-10-20 09:59:20 +02:00
holger krekel
525b08bc5c some doc refinements 2012-10-20 09:52:03 +02:00
holger krekel
fae34ca5e3 proper version number (2.3.1.dev*) 2012-10-19 16:00:29 +02:00
holger krekel
0852e84d9f skip pexpect using tests on freebsd 2012-10-19 15:59:29 +02:00
holger krekel
76db624639 start new dev cycle 2012-10-19 15:01:29 +02:00
holger krekel
1e6ec9941c Added tag 2.3.0 for changeset c27a60097767 2012-10-19 15:01:15 +02:00
holger krekel
a5ce481022 final touches 2012-10-19 11:12:13 +02:00
holger krekel
dca5fa2241 fixing links for 2.3 release, and fixing a windows32 failure on py3 2012-10-19 10:53:28 +02:00
holger krekel
586befb945 make usefixtures appear in py.test --markers output 2012-10-19 10:17:13 +02:00
holger krekel
b0b6695538 improve automatic id generation for parametrized tests 2012-10-19 10:07:13 +02:00
holger krekel
024df6e00b some more finalization of docs 2012-10-19 10:07:11 +02:00
holger krekel
5e28f461c8 avoid recursing into "ja" japanese examples 2012-10-18 15:32:30 +02:00
holger krekel
64544bee1a fix trial tests 2012-10-18 15:09:20 +02:00
holger krekel
7c8755cc89 refine docs, fix a marker/keywords bit, and add a test that request.keywords points to node.keywords. 2012-10-18 15:06:55 +02:00
holger krekel
7d747a1cde remove .markers attribute which was added in development and after 2.2.4
so never released.  Rather extend keywords to also exist on nodes. Assigning
to node.keywords will make the value appear on all subchildren's
keywords.
2012-10-18 13:52:32 +02:00
holger krekel
dbaedbacde many doc improvements and fixes 2012-10-18 12:24:50 +02:00
holger@merlinux.eu
cf17f1d628 fixing the fix of the last commit 2012-10-17 13:45:03 +02:00
holger krekel
67de2c53ac fix issue198 - detection of fixtures from conftest.py files in deeper nested dir structures with certain invocations 2012-10-17 13:42:40 +02:00
holger krekel
26ab80c4cd fix and test --fixtures location information 2012-10-17 13:12:33 +02:00
holger krekel
20849a44f5 improve --fixtures output with per-plugin grouping and hiding underscore names in non-verbose mode, re-introduce --funcargs for compatibiliy 2012-10-17 12:57:05 +02:00
holger krekel
51644a116c remove unused code 2012-10-17 11:50:32 +02:00
holger krekel
98513b995a simplify/integrate fixturemapper into FixtureManager
also fix jstests test failures
2012-10-17 11:20:45 +02:00
holger krekel
dc4e205876 typographic fixes, little simplification 2012-10-17 09:21:04 +02:00
holger krekel
2855a2f6cb remove outdated IMPL.txt and move up-to-date doc bits to FixtureMapper class. 2012-10-16 16:27:51 +02:00
holger krekel
cc2337af3a refine parsefactories interface, fix two_classes test originally reported by Alex Okrushko, also add a few more tests to make sure autouse-fixtures are properly distinguished 2012-10-16 16:13:12 +02:00
holger krekel
ab4183d400 strike another use of getfuncargnames() and rename FixtureDef.fixturenames to "argnames" because it's really just the fixture function arguments 2012-10-16 14:19:38 +02:00
holger krekel
37965657d0 make factorydeflist immutable by using an index 2012-10-16 13:59:12 +02:00
holger krekel
ccaa1af534 use FixtureInfo from FixtureRequest 2012-10-16 13:48:00 +02:00
holger krekel
2f3bbdafda use fixturemapper/fixtureinfo from Function objects 2012-10-16 13:48:00 +02:00
holger krekel
021c087701 implement fixture information stored on the parentnode of functions
to be reused by metafunc mechanics and Function setup
2012-10-16 13:47:59 +02:00
holger krekel
4541456a96 add plan for better fixture implementation, an xfailing test
and a slight refactoring of Metafunc tests/creation
2012-10-16 13:47:59 +02:00
holger krekel
f5d796b093 improve docs further, refine unittest docs, rename `autoactive to autouse`
to better match ``@pytest.mark.usefixtures`` naming.
2012-10-12 14:52:36 +02:00
holger krekel
6eec2f5893 make tmpdir fixture always return a realpath()ed tmpdir and make a note
about it in the changed test.  Currently, i don't see a reason why this
is a bad idea (tm)
2012-10-11 13:05:16 +02:00
holger krekel
0594265adc fix output of --fixtures for @pytest.fixture defined functions. 2012-10-09 16:49:04 +02:00
holger krekel
fb3af07ef4 try to move docs to a more releasable state, also refine
release announce and a few coding bits
2012-10-09 14:35:17 +02:00
holger@merlinux.eu
39b8a19cf7 Fix test for windows 2012-10-08 13:42:31 +02:00
holger krekel
916c1c170e somewhat simplify pytest_generate_tests example 2012-10-08 13:19:31 +02:00
holger krekel
df643f65f0 remove support for @pytest.fixture on classes, to be reserved for future use:
Fixture-classes could offer setup/teardown/addoption/configure methods
and provide higher level support.  Preliminary allowing it to work on classes
may make introducing it harder.
2012-10-08 11:22:31 +02:00
holger krekel
d630d02c5b remove pytest.setup usage 2012-10-08 08:34:21 +02:00
holger krekel
30b10a6950 - fix doc references, refactor fixtures docs to more quickly start
with examples instead of big text blobgs
- also silence -q and -qq reporting some more
2012-10-07 13:06:17 +02:00
holger krekel
cda84fb566 - allow to use fixtures directly, i.e. without ()
- also allow scope to be determined by a dynamic function
2012-10-06 21:03:55 +02:00
holger krekel
d3893dd5d1 allow metafunc.parametrize(scope=...) calls to override the scope of a Fixture function definition. This is useful for cases where you want to dynamically
set scope and parametrization for a fixture instead of statically declaring
it on the fixture function.
2012-10-06 21:01:13 +02:00
holger krekel
55a8bfd174 fix issue197 - in case a function is parametrized with zero arguments,
skip it during setup
2012-10-06 11:34:06 +02:00
Floris Bruynooghe
f588eae4f5 Use updated names 2012-10-05 22:44:18 +01:00
holger krekel
d8c365ef2c implement pytest.mark.usefixtures and ini-file usefixtures setting
and also refine fixture docs a bit - fixtures.txt should now mostly
reflect the current state of the implementation
2012-10-05 19:20:40 +02:00
holger krekel
4cbb2ab3b3 bump version 2012-10-05 14:35:16 +02:00
holger krekel
d1a3f5c3a6 make the default non-error pass simpler and faster, refine error reporting by presenting "fixture" tracebacks 2012-10-05 14:24:45 +02:00
holger krekel
bb07ba7807 rename a number of internal and externally visible variables to use the fixture name
rather than funcargs.  Introduce .funcargnames compatibility attribute for backward compat.
2012-10-05 14:24:44 +02:00
holger krekel
8282efbb40 internally unify setup and fixture code, making setup a shortcut to fixture(autoactive=True) 2012-10-05 10:21:35 +02:00
holger krekel
9251e747af rename pytest.factory usages into pytest.fixture ones 2012-10-05 10:21:35 +02:00
holger krekel
439cc1238f merge factories/funcargs and setup functions into the new "fixture" document 2012-10-05 10:21:35 +02:00
holger krekel
3049af618c avoid pyc file issues by parametrizing the test instead of rewriting conftest.py files 2012-10-04 11:51:14 +02:00
holger krekel
7bc7a9b702 add py33 to tox.ini, report pypy-1.9 as working as well 2012-10-01 10:31:04 +02:00
holger krekel
5173647b4d fixes to against python3.3 2012-10-01 10:14:54 +02:00
holger krekel
57a832812b remove unneccessary internal __request__ funcarg. 2012-10-01 09:23:39 +02:00
Ronny Pfannschmidt
bee7543716 move Item.applymarker to Node, and defer to it from Funcargrequest.applymarker 2012-09-30 22:17:33 +02:00
holger krekel
b9767fd74c remove print, pass python32 2012-09-27 13:27:22 +02:00
holger krekel
dbe66f468a ensure proper calling of finalizers in case of parametrization on classes 2012-09-26 12:24:04 +02:00
Ronny Pfannschmidt
35cbb5791d fixes issue 156: monkeypatch class level descriptors 2012-09-25 18:15:13 +02:00
holger krekel
a18fd61a20 back out accidental changes introduced by last patch 2012-09-25 15:13:58 +02:00
holger krekel
a1c3d60747 add an xfail-ed test for a potential future "enabled" parameter to setup functions 2012-09-25 15:04:30 +02:00
holger krekel
fe4ccdff0e avoid double-instantiation of PluginManager in case of the "python pytest.py" or -m pytest invocation 2012-09-25 11:58:41 +02:00
holger krekel
cd1ead4f7b - make request.funcargnames carry the closure of all used funcargs
- make metafunc.funcargnames carry the closure of used funcargs
2012-09-24 17:04:34 +02:00
Ronny Pfannschmidt
9568ff3b23 backout, the _memoizedcall change worked only due to a local effect 2012-09-24 11:36:24 +02:00
Ronny Pfannschmidt
6e5f491a42 get rid of _memoizedcall - we dont really need it anymore 2012-09-24 11:26:38 +02:00
holger krekel
7768972ec5 make sure setups are called ahead of the funcarg factories of the test function 2012-09-24 10:36:22 +02:00
holger krekel
754fab9b55 merge 2012-09-22 20:26:13 +02:00
Ronny Pfannschmidt
253a87b2dc fix issue 191 - add support for runTest method of unittest.TestCase subclasses 2012-09-22 18:24:53 +02:00
holger krekel
81082ed3d3 extend --help to tell about --markers and --funcargs 2012-09-22 11:44:56 +02:00
holger krekel
465cfff6f9 don't call nose' setup methods if they are marked with pytest.setup 2012-09-22 00:23:36 +02:00
holger krekel
738f14a48a improve the parametrization scenario example to sort by id, rather than by file-order, see also: http://stackoverflow.com/questions/12521924/pytest-running-scenarios-in-the-correct-order-in-the-class 2012-09-21 09:39:54 +02:00
holger krekel
22dc47d9f9 refine internal test support for unicode-related bits (used by a test in pytest-pep8) 2012-09-20 10:57:23 +02:00
holger krekel
6cb3281ddd allow factory/setup-markers on classes, using their respective __init__ methods which can use the funcarg mechanism 2012-09-18 14:00:47 +02:00
holger krekel
a5e7e441d3 fix bug introduced with last checkin 2012-09-18 13:46:24 +02:00
holger krekel
a7c6688bd6 implement full @pytest.setup function unittest.TestCase interaction 2012-09-18 10:54:12 +02:00
holger krekel
d9c24552fc remove distinction of new versus old funcarg factories 2012-09-18 10:53:42 +02:00
holger krekel
631d311e89 - add request.node which maps to the collection node as specified by the scope.
- remove request.markers which is now available via request.node.markers
2012-09-17 20:43:37 +02:00
holger krekel
c2480f5c54 fix @funcarg to @factory 2012-09-17 17:36:08 +02:00
holger krekel
a94bb0a8bb introduce a new "markers" attribute to nodes and the request object. It is
a dynamic class making holdin
2012-09-17 17:32:23 +02:00
holger krekel
646c2c6001 drops special testcontext object in favour of "old" request object, simplifying communication and code for the 2.2-2.3 transition. also modify docs and examples. 2012-09-17 16:36:10 +02:00
holger krekel
f6b555f5ad merge 2012-09-17 08:41:04 +02:00
Ronny Pfannschmidt
bf5b226474 fix issue 188 - ensure sys.exc_info on py2 is clear before calling into a test 2012-09-15 15:20:49 +02:00
holger krekel
084c617b67 modify detection of factories located in plugins, allowing pytest's own test functions to access plugin defined funcargs even if they use internal machinery instead of a full test run 2012-09-12 12:51:45 +02:00
Ronny Pfannschmidt
bfaf8e50b6 fix issue 182: testdir.inprocess_run now considers passed plugins 2012-09-03 10:12:30 +02:00
Ronny Pfannschmidt
848c749d1a adapt the junit xml escaping test to my escaping changes 2012-09-03 09:54:02 +02:00
holger krekel
41ad7dbae1 fix issue185 monkeypatching time.time does not cause pytest to fail 2012-09-01 09:58:10 +02:00
holger krekel
93eac240a0 merge 2012-09-01 09:59:11 +02:00
Benjamin Peterson
a6060dfb6d use py3 compatible print syntax 2012-08-28 16:37:43 -04:00
Benjamin Peterson
7f36649763 remove usage of exception module, which is gone in py3.3 2012-08-28 16:35:06 -04:00
holger krekel
f07ebc6615 add talk from brianna and me from 2012 2012-08-26 16:30:01 +02:00
Ronny Pfannschmidt
e876ad9abd fix issue 179 - propperly show the dependency chain of factories on setup failure 2012-08-22 21:43:42 +02:00
Ronny Pfannschmidt
503addbf09 correctly have the test for issue #[C179 actually fail 2012-08-22 21:20:18 +02:00
Ronny Pfannschmidt
1318df4f5b add xfailing test for issue 179 2012-08-22 19:49:50 +02:00
Ronny Pfannschmidt
45693c2847 exchange the rawcode factory marker check with a more robust and specific instance check as advised by holger 2012-08-19 14:57:07 +02:00
Ronny Pfannschmidt
0e8cd9297a fix issue 176: raises(AssertionError) now catches builtin AssertionError as well 2012-08-19 13:45:26 +02:00
Ronny Pfannschmidt
0cca20bef9 ignore magic callables with no sane code in factory/setup discovery 2012-08-19 12:36:49 +02:00
Ronny Pfannschmidt
1446b4b4e6 fix issue #178 and extend the failure escape test 2012-08-17 16:08:08 +02:00
holger krekel
aa84359bd9 Merged in pfctdayelise/pytest (pull request #17) 2012-08-16 13:23:42 +02:00
Brianna Laugher
f275830ca7 expand list of projects based on URLs from holger 2012-08-16 19:31:21 +10:00
holger krekel
627e068516 fix issue172 so that @pytest.setup marked setup_module/function... functions
are not called twice.  Also fix ordering to that broader scoped setup
functions are executed first.
2012-08-13 13:37:14 +02:00
holger krekel
f472f21406 fix/update some docs to work with @pytest.factory instead of pytest_funcarg__ naming. 2012-08-13 12:58:08 +02:00
holger krekel
f4963270c6 fix typos 2012-08-11 20:02:34 +02:00
Brianna Laugher
08c3b1b80f Fix URL for waskr project 2012-08-10 15:44:58 +10:00
holger krekel
935761f098 also improve missing funcarg error for setup functions 2012-08-08 14:53:47 +02:00
holger krekel
dd268c1b2b improve error representation for missing factory definitions
in recursive funcarg reconstruction
2012-08-08 11:48:53 +02:00
holger krekel
172505f703 fix/consolidate --junitxml=path construction with relative pathes 2012-08-04 10:33:43 +02:00
holger krekel
6746a00cb8 majorly refine funcargs docs and rename "resources.txt" to "funcargs.txt" so that existing web links will eventually land at this new page when pytest is released. Also integrated the detailed reasoning and update setup function docs
to reflect latest discussions and feedback gathered on py-dev mailing list.
2012-08-03 19:08:27 +02:00
holger krekel
46dc7eeacb move pytest.mark.factory/setup to pytest.factory/setup, as per flub 's suggestion 2012-08-02 12:41:46 +02:00
holger krekel
ae241a5071 refine documentation, move setup to own "setup" page and provide
some more examples. move old setup_module/... to xunit_old page.
2012-08-02 12:07:54 +02:00
holger krekel
5fd84c35dd reshuffle docs, try to get a bit closer to release-relevant documentation 2012-08-01 14:52:51 +02:00
holger krekel
535d892f27 - rename @funcarg to @factory
- introduce a "testcontext" object for new-style funcargs and setup methods
- New-style funcargs and setup methods cannot use the "request" object anymore.
2012-08-01 13:57:09 +02:00
holger krekel
cb2eb9ba33 reorder internal layout so that funcarg-related functionality is in python.py 2012-08-01 09:23:39 +02:00
holger krekel
4f94ab4e42 mark a test as xfailing on python2.5 2012-08-01 09:10:40 +02:00
holger krekel
449b55cc70 - enhance ordering of tests using parametrized resources
- introduce a refined way to perform finalization for setup functions
  which does not use cached_setup() anymore
2012-08-01 09:07:32 +02:00
holger krekel
9dc79fd187 introduce a funcargcall object, holding meta information 2012-07-30 12:39:45 +02:00
holger krekel
b57fb9fd47 introduce a SetupCall, holding meta information and setup calling state 2012-07-30 11:51:50 +02:00
holger krekel
d68c65b493 minimize active parametrized non-function scoped resources by
- re-ordering at collection time
- modifying setup/teardown
2012-07-30 10:46:03 +02:00
holger krekel
fa61927c6b introduce @pytest.mark.setup decorated function,
extend newexamples.txt and draft a V4 resources API doc.
2012-07-24 12:10:04 +02:00
holger krekel
d4a487c725 allow funcarg factories to receive funcargs 2012-07-23 10:55:09 +02:00
holger krekel
76584b53a1 clarify and add to sort-by-session-scoped parametrized resources example 2012-07-23 10:54:57 +02:00
holger krekel
6b0f0adf5b implement a scope/parametrized examples using the so-far new features
also fix a bug with scoping/parametrization
2012-07-20 14:16:50 +02:00
holger krekel
396045e53f allow registration of "funcarg" marked factories 2012-07-20 14:16:49 +02:00
holger krekel
80db25822c implement funcarg factory scope marker and ScopeMismatch detection 2012-07-20 14:16:46 +02:00
holger krekel
f358fe7154 extend Metafunc and write a pytest_generate_tests hook on the funcarg manager
which discovers factories
2012-07-20 14:16:46 +02:00
holger krekel
e14459d45c discover funcarg factories independently from request/Function items 2012-07-20 14:16:28 +02:00
holger krekel
4e4b507472 move funcarg factory to a new FuncargManager object at session level 2012-07-19 09:20:14 +02:00
holger krekel
c7ee6e71ab re-introduce the old 2.2.4 FuncargRequest implementation as it is a better
base for implementing the new funcarg/setup api. Also Un-optimize
funcargnames discovery for now.
2012-07-18 19:49:14 +02:00
holger krekel
4766497515 V3 draft of resource api 2012-07-16 11:11:26 +02:00
holger krekel
38b18c44e9 ci 2012-07-18 19:48:43 +02:00
Wes Turner
a73c27da13 DOC: typo in doc/en/goodpractices.txt ("pytest" -> "PyTest") 2012-07-18 01:01:37 -05:00
holger krekel
dbaf7ee9d0 v2 of resources API draft 2012-07-16 10:47:41 +02:00
holger krekel
7a90bed19b V1 of the resources API draft 2012-07-16 10:47:00 +02:00
holger krekel
8adac2878f put automatic funcarg_ API to Py*objects only, refine internal subclassing and initialisation logic 2012-07-16 10:46:44 +02:00
holger krekel
66ed2d123a add a little example on how to group test execution by parametrized resource 2012-07-14 12:06:58 +02:00
Johannes
b902c36bfc Fix typo in terminal help text 2012-07-12 17:00:48 +01:00
Benjamin Peterson
099ac1e1f4 cleanup test a bit 2012-07-07 08:01:44 -07:00
Floris Bruynooghe
1aca6c9d7c Fix extension of of cached re-written file
With PYTHONOPTIMIZE set this had the extension of "o" instead of ".pyo".
Fixes issue #168.
2012-07-07 16:09:53 +02:00
Ronny Pfannschmidt
fe24e01a03 merge 2012-07-07 13:21:45 +02:00
holger krekel
838e758cf7 exit with errno instead of always signalling success, thanks John Anderson 2012-07-07 07:40:51 +02:00
holger krekel
ddd4467fdd merge 2012-07-02 13:23:41 +02:00
holger krekel
5574e45749 fix issue165 - fix broken links in documentation, also point to stackoverflow from FAQ and contact page 2012-07-02 13:13:48 +02:00
Ronny Pfannschmidt
74e55493d1 test and implement showing verbose assert repr for py.test -vv 2012-06-27 17:26:55 +02:00
holger krekel
ecec653e98 fix issue151 - heuristcally lookup conftest files on all command line arguments, not just the first existing dir/file
you can install the corresponding pytest-2.3.dev2 via
pip install -i http:/pypi.testrun.org -U pytest
2012-06-26 21:56:03 +02:00
holger krekel
0ba0f91720 remove unused code 2012-06-26 20:28:09 +02:00
holger krekel
ea49993459 fix issue139 - make it possible to access funcargs from pytest_runtest_setup 2012-06-25 18:08:12 +02:00
holger krekel
b4b86159cd better name for the oejskit-compatibility-class. 2012-06-25 17:49:13 +02:00
holger krekel
91b6f2bda8 mid-scale refactoring to make request API available directly on items.
This commit was slightly tricky because i want to backward
compatibility especially for the oejskit plugin which
uses Funcarg-filling for non-Function objects.
2012-06-25 17:35:33 +02:00
holger krekel
227d847216 fix problem with unicode in writing failure representations to terminal, thanks ThomasWaldmann 2012-06-24 16:42:31 +02:00
holger krekel
6e0c30d67d fix skip/xfail confusion, reported and discussed on
http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
2012-06-23 11:32:32 +02:00
holger krekel
65cbf591d8 ignore .cache 2012-06-22 10:53:28 +02:00
holger krekel
e79a312b92 fix internal test setup failure 2012-06-21 11:30:10 +02:00
holger krekel
42d44bfd43 fix some pep8 issues, more to go ... is there a tool that helps with pep8-ifying? 2012-06-21 11:20:29 +02:00
holger krekel
ccc04b9fc4 some refinements to reporting and hook order 2012-06-21 11:07:22 +02:00
holger krekel
18306a4644 add header info: always report 3rd party plugins in test runs 2012-06-20 00:16:47 +02:00
holger krekel
1bbe1d086c fix issue160 a failing setup of an xfail-marked tests should
be reported as xfail (not xpass)
2012-06-19 23:48:39 +02:00
holger krekel
672919a8e2 fix faq once more to get rid of the strange "missed" bit. 2012-06-18 11:47:57 +02:00
holger krekel
f176ee3a1c (correction-commit for wrong previous changelog message)
fix issue159 -- improve http://pytest.org/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
2012-06-17 11:01:14 +02:00
holger krekel
474b177da8 fix issue129 - improve http://pytest.org/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
2012-06-17 10:59:30 +02:00
holger krekel
b2e87ce027 change pluginmanager.register API to raise ValueError if the plugin object or the name is already registered 2012-06-16 21:29:04 +02:00
holger krekel
2e163e4aae mention pep302 in docstring 2012-06-16 10:14:52 +02:00
holger krekel
63eacd9dd5 fix comment handling 2012-06-12 13:41:29 +02:00
holger krekel
b008e489ba fix ReST errors, increment doc-version and push to pytest.org 2012-06-11 16:24:42 +02:00
Floris Bruynooghe
d5078001c9 Don't use deprecated API in example 2012-06-11 13:24:30 +01:00
holger krekel
8b3ac3b03a also set release 2012-06-07 19:26:18 +02:00
holger krekel
6af20a5290 use doc-versions that can increment separately from the pytest version 2012-06-07 19:18:50 +02:00
holger krekel
eb1b1005ae added an example on how to do python2/python3 customized test collection 2012-06-07 12:39:53 +02:00
holger krekel
6fd57ec786 extend marker section with a platform example 2012-06-06 16:34:13 +02:00
t2y
03a814a859 added Japanese translation for 2.2.4 (79a5b776a6f3) 2012-06-06 08:58:02 +09:00
t2y
b4c2161e35 fixed to find the CHANGELOG's path 2012-06-06 08:54:35 +09:00
t2y
9198069739 added "docs/en" directory and moved 2012-06-06 08:52:53 +09:00
holger krekel
4d77653bb0 simplify activate_funcargs 2012-06-03 21:06:43 +02:00
holger krekel
3f17784386 fix issue128 - show captured output when capsys/capfd are in use 2012-06-03 21:01:27 +02:00
holger krekel
971f96468c fix py2py3 example tests 2012-06-03 16:10:10 +02:00
holger krekel
c11202b549 remove pyc file 2012-06-01 20:13:18 +02:00
holger krekel
42d63832b7 draft example for skipping py2 and py3 only tests on
a per-module level.
2012-05-23 23:40:41 +02:00
holger krekel
f5f3fe54d5 update examples with 2.2.4 version, ReST fixes 2012-05-22 18:30:34 +02:00
holger krekel
76ec623b22 Added tag 2.2.4 for changeset ad9fe504a371 2012-05-22 18:27:49 +02:00
holger krekel
69fc6987ad upgrade inlined distribute_setup.py 2012-05-22 17:24:43 +02:00
holger krekel
0790f7a75f fix issue 144 - wrong classname in junitxml 2012-05-22 17:18:04 +02:00
Ronny Pfannschmidt
db8fbe7661 skip test for pyo on pypy since pypy doesnt do pyo 2012-05-22 16:20:58 +02:00
Jonathan Peirce
91c41cd6b3 minor fix to docs/usage 2012-05-22 12:24:53 +01:00
holger krekel
1bf1cfd07a fix help string for --paste 2012-05-19 10:54:12 +02:00
holger krekel
51d94a4a6e use higher difference on timing 2012-05-18 13:56:49 +02:00
holger krekel
e18abfd013 fix issue143 - call unconfigure/sessionfinish always when
configure/sessionstart where called

use exitcode 4 (instead of 3 which signaled an internal error)
when an initial directory/file was not found
2012-05-17 23:11:23 +02:00
holger krekel
6c7ea8191f fix wrong release version 2012-05-17 15:44:18 +02:00
holger krekel
329dca42a7 add release announcement 2012-05-17 15:25:58 +02:00
holger krekel
0362aaba5a require py-1.4.8 2012-05-17 08:47:50 +02:00
holger krekel
948dea8bb4 bump version to next release 2012-05-17 08:46:49 +02:00
Ronny Pfannschmidt
6155e9139d hande the trial todo class by using repr 2012-05-10 01:38:13 +02:00
holger krekel
6dd8405aed bump version 2012-05-10 00:34:47 +02:00
Ronny Pfannschmidt
c076f4e789 switch pastebin to bpaste.net, fixes #141 2012-05-08 16:13:25 +02:00
Ronny Pfannschmidt
d32a132b51 add the fix for issue 140 to CHANGELOG 2012-05-08 14:15:23 +02:00
Ronny Pfannschmidt
0e3779b14f strip bound wrappers of class setup/tardown, fixes #140
on python3 im_func is replaced by __func__
2012-05-06 23:03:16 +02:00
Benjamin Peterson
fe1c35f8d0 prepend the assertion rewriting hook, so as not to break when builtin import is explicitly on sys.meta_path 2012-05-05 17:31:05 -04:00
Benjamin Peterson
b4588f1798 escape the % operator in string formatting 2012-05-03 13:49:30 -04:00
Benjamin Peterson
64c7c1be15 use non-hacky dynamic package import method 2012-04-27 17:51:50 -04:00
Benjamin Peterson
1c817aa7bd don't use octal syntax, since its not py2/py3 compatible 2012-04-18 11:26:44 -04:00
Ronny Pfannschmidt
d02eaa8881 fix a import strange loop that affects pypy test appsupport on python2.5 2012-04-13 12:41:02 +02:00
holger krekel
b92176024c write down some thoughts on parametrization - still not completely clear what the next stage should look like ... 2012-03-31 10:01:03 -07:00
holger krekel
1c746e0819 merge 2012-03-31 10:12:11 -07:00
Ronny Pfannschmidt
166aae4418 word change in the docs fixes #130 2012-03-22 08:26:37 +01:00
holger krekel
58933aac2a try to better handle @unittest.expectedFailure decorator 2012-03-19 22:53:52 -07:00
Benjamin Peterson
45aa4e5229 remove unused import 2012-03-19 20:04:55 -04:00
holger krekel
e643e99586 bump version number to dev version 2012-03-19 08:53:08 -07:00
holger krekel
9f6d6f630d walk through issues 2012-03-19 06:56:35 -07:00
Ronny Pfannschmidt
812ba87f37 add acomment so highlighting won\'t fail 2012-03-15 15:22:13 +01:00
Ronny Pfannschmidt
2b0887fa5f document integration with setuptools/distribute test command and tests_require 2012-03-15 15:15:21 +01:00
Ronny Pfannschmidt
ee8d2f9950 junitxml: use a exclusive match on the legal ranges of xml for binary escaping, fixes issue 126 2012-03-09 13:12:18 +01:00
holger krekel
51d29cf4c6 mention 2.2.3 in 2.2.2 release announce 2012-02-05 23:54:20 -05:00
holger krekel
e378496b24 Added tag 2.2.3 for changeset 3c11c5c9776f 2012-02-05 23:39:00 -05:00
holger krekel
4d21274a29 release 2.2.3 to fix package contents (2.2.2 contained too many files) 2012-02-05 23:38:31 -05:00
holger krekel
705442cf4e Added tag 2.2.2 for changeset 92b916483c1e 2012-02-05 18:37:31 -05:00
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
288 changed files with 23464 additions and 3375 deletions

View File

@@ -15,7 +15,7 @@ syntax:glob
*.orig
*~
doc/_build
doc/*/_build
build/
dist/
*.egg-info
@@ -23,3 +23,5 @@ issue/
env/
3rdparty/
.tox
.cache
.coverage

18
.hgtags
View File

@@ -33,3 +33,21 @@ c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
e9e127acd6f0497324ef7f40cfb997cad4c4cd17 2.0.0
e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
84e5c54b72448194a0f6f815da7e048ac8019d50 2.0.2
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
363e5a5a59c803e6bc176a6f9cc4bf1a1ca2dab0 2.0.3
e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
5864412c6f3c903384243bd315639d101d7ebc67 2.1.2
12a05d59249f80276e25fd8b96e8e545b1332b7a 2.1.3
1522710369337d96bf9568569d5f0ca9b38a74e0 2.2.0
3da8cec6c5326ed27c144c9b6d7a64a648370005 2.2.1
92b916483c1e65a80dc80e3f7816b39e84b36a4d 2.2.2
3c11c5c9776f3c678719161e96cc0a08169c1cb8 2.2.3
ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1

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

291
CHANGELOG
View File

@@ -1,3 +1,294 @@
Changes between 2.3.1 and 2.3.2
-----------------------------------
- fix issue208 and fix issue29 use new py version to avoid long pauses
when printing tracebacks in long modules
- fix issue205 - conftests in subdirs customizing
pytest_pycollect_makemodule and pytest_pycollect_makeitem
now work properly
- fix teardown-ordering for parametrized setups
- fix issue127 - better documentation for pytest_addoption
and related objects.
- fix unittest behaviour: TestCase.runtest only called if there are
test methods defined
- improve trial support: don't collect its empty
unittest.TestCase.runTest() method
- "python setup.py test" now works with pytest itself
- fix/improve internal/packaging related bits:
- exception message check of test_nose.py now passes on python33 as well
- issue206 - fix test_assertrewrite.py to work when a global
PYTHONDONTWRITEBYTECODE=1 is present
- add tox.ini to pytest distribution so that ignore-dirs and others config
bits are properly distributed for maintainers who run pytest-own tests
Changes between 2.3.0 and 2.3.1
-----------------------------------
- fix issue202 - fix regression: using "self" from fixture functions now
works as expected (it's the same "self" instance that a test method
which uses the fixture sees)
- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
due to pexpect not supporting it properly (hanging)
- link to web pages from --markers output which provides help for
pytest.mark.* usage.
Changes between 2.2.4 and 2.3.0
-----------------------------------
- fix issue202 - better automatic names for parametrized test functions
- fix issue139 - introduce @pytest.fixture which allows direct scoping
and parametrization of funcarg factories.
- fix issue198 - conftest fixtures were not found on windows32 in some
circumstances with nested directory structures due to path manipulation issues
- fix issue193 skip test functions with were parametrized with empty
parameter sets
- fix python3.3 compat, mostly reporting bits that previously depended
on dict ordering
- introduce re-ordering of tests by resource and parametrization setup
which takes precedence to the usual file-ordering
- fix issue185 monkeypatching time.time does not cause pytest to fail
- fix issue172 duplicate call of pytest.fixture decoratored setup_module
functions
- fix junitxml=path construction so that if tests change the
current working directory and the path is a relative path
it is constructed correctly from the original current working dir.
- fix "python setup.py test" example to cause a proper "errno" return
- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
- catch unicode-issues when writing failure representations
to terminal to prevent the whole session from crashing
- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
will now take precedence before xfail-markers because we
can't determine xfail/xpass status in case of a skip. see also:
http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
- always report installed 3rd party plugins in the header of a test run
- fix issue160: a failing setup of an xfail-marked tests should
be reported as xfail (not xpass)
- fix issue128: show captured output when capsys/capfd are used
- fix issue179: propperly show the dependency chain of factories
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
- make request.keywords and node.keywords writable. All descendant
collection nodes will see keyword values. Keywords are dictionaries
containing markers and other info.
- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
- fix issue 176: correctly catch the builtin AssertionError
even when we replaced AssertionError with a subclass on the
python level
- factory discovery no longer fails with magic global callables
that provide no sane __code__ object (mock.call for example)
- fix issue 182: testdir.inprocess_run now considers passed plugins
- fix issue 188: ensure sys.exc_info is clear on python2
before calling into a test
- fix issue 191: add unittest TestCase runTest method support
- fix issue 156: monkeypatch correctly handles class level descriptors
- reporting refinements:
- pytest_report_header now receives a "startdir" so that
you can use startdir.bestrelpath(yourpath) to show
nice relative path
- allow plugins to implement both pytest_report_header and
pytest_sessionstart (sessionstart is invoked first).
- don't show deselected reason line if there is none
- py.test -vv will show all of assert comparisations instead of truncating
Changes between 2.2.3 and 2.2.4
-----------------------------------
- fix error message for rewritten assertions involving the % operator
- fix issue 126: correctly match all invalid xml characters for junitxml
binary escape
- fix issue with unittest: now @unittest.expectedFailure markers should
be processed correctly (you can also use @pytest.mark markers)
- document integration with the extended distribute/setuptools test commands
- fix issue 140: propperly get the real functions
of bound classmethods for setup/teardown_class
- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
- fix issue #143: call unconfigure/sessionfinish always when
configure/sessionstart where called
- fix issue #144: better mangle test ids to junitxml classnames
- upgrade distribute_setup.py to 0.6.27
Changes between 2.2.2 and 2.2.3
----------------------------------------
- fix uploaded package to only include neccesary files
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
----------------------------------------------

View File

@@ -1,3 +1,125 @@
improve / add to dependency/test resource injection
-------------------------------------------------------------
tags: wish feature docs
write up better examples showing the connection between
the two.
refine parametrize API
-------------------------------------------------------------
tags: critical feature
extend metafunc.parametrize to directly support indirection, example:
def setupdb(request, config):
# 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 dynamic_setup_database(val)
@pytest.mark.parametrize("db", ["pg", "mysql"], setupfunc=setupdb)
def test_heavy_functional_test(db):
...
There would be no need to write or explain funcarg factories and
their special __ syntax.
The examples and improvements should also show how to put the parametrize
decorator to a class, to a module or even to a directory. For the directory
part a conftest.py content like this::
pytestmark = [
@pytest.mark.parametrize_setup("db", ...),
]
probably makes sense in order to keep the declarative nature. This mirrors
the marker-mechanism with respect to a test module but puts it to a directory
scale.
When doing larger scoped parametrization it probably becomes neccessary
to allow parametrization to be ignored if the according parameter is not
used (currently any parametrized argument that is not present in a function will cause a ValueError). Example:
@pytest.mark.parametrize("db", ..., mustmatch=False)
means to not raise an error but simply ignore the parametrization
if the signature of a decorated function does not match. XXX is it
not sufficient to always allow non-matches?
unify item/request classes, generalize items
---------------------------------------------------------------
tags: 2.4 wish
in lieu of extended parametrization and the new way to specify resource
factories in terms of the parametrize decorator, consider unification
of the item and request class. This also is connected with allowing
funcargs in setup functions. Example of new item API:
item.getresource("db") # alias for request.getfuncargvalue
item.addfinalizer(...)
item.cached_setup(...)
item.applymarker(...)
test classes/modules could then use this api via::
def pytest_runtest_setup(item):
use item API ...
introduction of this new method needs to be _fully_ backward compatible -
and the documentation needs to change along to mention this new way of
doing things.
impl note: probably Request._fillfixtures would be called from the
python plugins own pytest_runtest_setup(item) and would call
item.getresource(X) for all X in the funcargs of a function.
XXX is it possible to even put the above item API to Nodes, i.e. also
to Directorty/module/file/class collectors? Problem is that current
funcarg factories presume they are called with a per-function (even
per-funcarg-per-function) scope. Could there be small tweaks to the new
API that lift this restriction?
consider::
def setup_class(cls, tmpdir):
# would get a per-class tmpdir because tmpdir parametrization
# would know that it is called with a class scope
#
#
#
this looks very difficult because those setup functions are also used
by nose etc. Rather consider introduction of a new setup hook:
def setup_test(self, item):
self.db = item.cached_setup(..., scope='class')
self.tmpdir = item.getresource("tmpdir")
this should be compatible to unittest/nose and provide much of what
"testresources" provide. XXX This would not allow full parametrization
such that test function could be run multiple times with different
values. See "parametrized attributes" issue.
allow parametrized attributes on classes
--------------------------------------------------
tags: wish 2.4
example:
@pytest.mark.parametrize_attr("db", setupfunc, [1,2,3], scope="class")
@pytest.mark.parametrize_attr("tmp", setupfunc, scope="...")
class TestMe:
def test_hello(self):
access self.db ...
this would run the test_hello() function three times with three
different values for self.db. This could also work with unittest/nose
style tests, i.e. it leverages existing test suites without needing
to rewrite them. Together with the previously mentioned setup_test()
maybe the setupfunc could be ommitted?
checks / deprecations for next release
---------------------------------------------------------------
tags: bug 2.4 core xdist
@@ -7,29 +129,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.
do early-teardown of test modules
-----------------------------------------
tags: feature 2.1
fix start/finish partial finailization problem
---------------------------------------------------------------
tags: bug core
currently teardowns are called when the next tests is setup
except for the function/method level where interally
"teardown_exact" tears down immediately. Generalize
this to perform the "neccessary" teardown compared to
the "next" test item during teardown - this should
get rid of some irritations because otherwise e.g.
prints of teardown-code appear in the setup of the next test.
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.
consider and document __init__ file usage in test directories
---------------------------------------------------------------
tags: bug 2.1 core
tags: bug core
Currently, a test module is imported with its fully qualified
package path, determined by checking __init__ files upwards.
@@ -44,7 +164,7 @@ certain scenarios makes sense.
relax requirement to have tests/testing contain an __init__
----------------------------------------------------------------
tags: feature 2.1
tags: feature
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
A local test run of a "tests" directory may work
@@ -55,25 +175,24 @@ i.e. port the nose-logic of unloading a test module.
customize test function collection
-------------------------------------------------------
tags: feature 2.1
tags: feature
- 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
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
Introduce nice-to-spell platform-skipping, examples:
@@ -90,44 +209,36 @@ interpreter versions.
pytest.mark.xfail signature change
-------------------------------------------------------
tags: feature 2.1
tags: feature
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? Maybe rename to "pytest.mark.xfail"?
introduce py.test.mark registration
-----------------------------------------
tags: feature 2.1
introduce a hook that allows to register a named mark decorator
with documentation and add "py.test --marks" to get
a list of available marks. Deprecate "dynamic" mark
definitions.
Compatibility? how to introduce a new name/keep compat?
allow to non-intrusively apply skipfs/xfail/marks
---------------------------------------------------
tags: feature 2.1
tags: feature
use case: mark a module or directory structures
to be skipped on certain platforms (i.e. no import
attempt will be made).
consider introducing a hook/mechanism that allows to apply marks
from conftests or plugins.
from conftests or plugins. (See extended parametrization)
explicit referencing of conftest.py files
-----------------------------------------
tags: feature 2.1
tags: feature
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
introduce more declarative configuration options:
- (to-be-collected test directories)
@@ -138,49 +249,24 @@ introduce more declarative configuration options:
new documentation
----------------------------------
tags: feature 2.1
tags: feature
- 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
With 1.1.1 py.test fails at least on windows if an import
is relative and compared against an absolute conftest.py
path. Normalize.
call termination with small timeout
-------------------------------------------------
tags: feature 2.1
test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node
Call gateway group termination with a small timeout if available.
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
consider deprecating py.test.ensuretemp and py.test.config
to further reduce py.test globality. Also consider
@@ -189,7 +275,7 @@ a plugin rather than being there from the start.
consider allowing funcargs for setup methods
--------------------------------------------------------------
tags: experimental-wish 2.1
tags: experimental-wish
Users have expressed the wish to have funcargs available to setup
functions. Experiment with allowing funcargs there - it might
@@ -199,13 +285,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:
py.test could call a new pytest_addsyspath() in order to systematically
allow manipulation of sys.path and to inhibit it via --no-addsyspath
@@ -217,7 +310,7 @@ and pytest_configure.
show plugin information in test header
----------------------------------------------------------------
tags: feature 2.1
tags: feature
Now that external plugins are becoming more numerous
it would be useful to have external plugins along with
@@ -225,7 +318,7 @@ their versions displayed as a header line.
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 2.1
tags: feature
py.test.ensuretemp and py.test.config are probably the last
objects containing global state. Often using them is not
@@ -235,9 +328,58 @@ as others.
remove deprecated bits in collect.py
-------------------------------------------------------------------
tags: feature 2.1
tags: feature
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
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.findone("test_*.py")
result = pytester.runpytest(p)
assert result.ret == 0
assert result.passed == 1
Another idea is to allow to define a full scenario including the run
in one content string::
runscenario("""
test_{TESTNAME}.py:
import pytest
@pytest.mark.xfail
def test_that_fails():
assert 0
@pytest.mark.skipif("True")
def test_hello():
pass
conftest.py:
import pytest
def pytest_runsetup_setup(item):
pytest.skip("abc")
runpytest -rsxX
*SKIP*{TESTNAME}*
*1 skipped*
""")
This could be run with at least three different ways to invoke pytest:
through the shell, through "python -m pytest" and inlined. As inlined
would be the fastest it could be run first (or "--fast" mode).

View File

@@ -2,6 +2,7 @@ include CHANGELOG
include README.txt
include setup.py
include distribute_setup.py
include tox.ini
include LICENSE
graft doc
graft testing

View File

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

View File

@@ -0,0 +1,123 @@
"""
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.insert(0, 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:
# Don't include pageloads of data unless we are very verbose (-vv)
if len(''.join(new_expl[1:])) > 80*8 and item.config.option.verbose < 2:
new_expl[1:] = ['Detailed information too verbose, truncated']
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,554 @@
import py
import sys, inspect
from compiler import parse, ast, pycodegen
from _pytest.assertion.util import format_explanation, 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,46 @@
import sys
import py
from _pytest.assertion.util import BuiltinAssertionError
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,600 @@
"""Rewrite assertion AST to produce nice error messages"""
import ast
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):
"""PEP302 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: "%%", # escaped for string formatting
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,80 @@
"""
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."),
BuiltinAssertionError = py.builtin.builtins.AssertionError
def pytest_configure(config):
# The _reprcompare attribute on the py.code module is used by
# py._code._assertionnew to detect this plugin was loaded and in
# turn call the hooks defined here as part of the
# DebugInterpreter.
config._monkeypatch = m = monkeypatch()
warn_about_missing_assertion()
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
def callbinrepr(op, left, right):
hook_result = config.hook.pytest_assertrepr_compare(
config=config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
return '\n~'.join(new_expl)
m.setattr(py.builtin.builtins,
'AssertionError', py.code._AssertionError)
m.setattr(py.code, '_reprcompare', callbinrepr)
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
# loaded and in turn call the hooks defined here as part of the
# DebugInterpreter.
_reprcompare = None
def pytest_unconfigure(config):
config._monkeypatch.undo()
def format_explanation(explanation):
"""This formats an explanation
Normally all embedded newlines are escaped, however there are
three exceptions: \n{, \n} and \n~. The first two are intended
cover nested explanations, see function and attribute explanations
for examples (.visit_Call(), visit_Attribute()). The last one is
for when one explanation needs to span multiple lines, e.g. when
displaying diffs.
"""
# simplify 'assert False where False = ...'
where = 0
while True:
start = where = explanation.find("False\n{False = ", where)
if where == -1:
break
level = 0
for i, c in enumerate(explanation[start:]):
if c == "{":
level += 1
elif c == "}":
level -= 1
if not level:
break
else:
raise AssertionError("unbalanced braces: %r" % (explanation,))
end = start + i
where = end
if explanation[end - 1] == '\n':
explanation = (explanation[:start] + explanation[start+15:end-1] +
explanation[end+1:])
where -= 17
raw_lines = (explanation or '').split('\n')
# escape newlines not followed by {, } and ~
lines = [raw_lines[0]]
for l in raw_lines[1:]:
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
lines.append(l)
else:
lines[-1] += '\\n' + l
result = lines[:1]
stack = [0]
stackcnt = [0]
for line in lines[1:]:
if line.startswith('{'):
if stackcnt[-1]:
s = 'and '
else:
s = 'where '
stack.append(len(result))
stackcnt[-1] += 1
stackcnt.append(0)
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
elif line.startswith('}'):
assert line.startswith('}')
stack.pop()
stackcnt.pop()
result[stack[-1]] += line[1:]
else:
assert line.startswith('~')
result.append(' '*len(stack) + line[1:])
assert len(stack) == 1
return '\n'.join(result)
def warn_about_missing_assertion():
try:
assert False
except AssertionError:
pass
else:
sys.stderr.write("WARNING: failing tests may report as passing because "
"assertions are turned off! (are you using python -O?)\n")
# Provide basestring in python3
try:
@@ -48,7 +83,7 @@ except NameError:
basestring = str
def pytest_assertrepr_compare(op, left, right):
def assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
@@ -81,16 +116,11 @@ def pytest_assertrepr_compare(op, left, right):
excinfo = py.code.ExceptionInfo()
explanation = ['(pytest_assertion plugin: representation of '
'details failed. Probably an object has a faulty __repr__.)',
str(excinfo)
]
str(excinfo)]
if not explanation:
return None
# Don't include pageloads of data, should be configurable
if len(''.join(explanation)) > 80*8:
explanation = ['Detailed information too verbose, truncated']
return [summary] + explanation

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_unconfigure(config):
# registered in config.py during early conftest.py loading
capman = config.pluginmanager.getplugin('capturemanager')
while capman._method2capture:
name, cap = capman._method2capture.popitem()
# XXX logging module may wants to close it itself on process exit
# otherwise we could do finalization here and call "reset()".
cap.suspend()
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()
@@ -65,14 +66,6 @@ class CaptureManager:
else:
raise ValueError("unknown capturing method: %r" % method)
def _getmethod_preoptionparse(self, args):
if '-s' in args or "--capture=no" in args:
return "no"
elif hasattr(os, 'dup') and '--capture=sys' not in args:
return "fd"
else:
return "sys"
def _getmethod(self, config, fspath):
if config.option.capture:
method = config.option.capture
@@ -85,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:
@@ -120,22 +119,20 @@ class CaptureManager:
return "", ""
def activate_funcargs(self, pyfuncitem):
if not hasattr(pyfuncitem, 'funcargs'):
return
assert not hasattr(self, '_capturing_funcargs')
self._capturing_funcargs = capturing_funcargs = []
for name, capfuncarg in pyfuncitem.funcargs.items():
if name in ('capsys', 'capfd'):
capturing_funcargs.append(capfuncarg)
capfuncarg._start()
funcargs = getattr(pyfuncitem, "funcargs", None)
if funcargs is not None:
for name, capfuncarg in funcargs.items():
if name in ('capsys', 'capfd'):
assert not hasattr(self, '_capturing_funcarg')
self._capturing_funcarg = capfuncarg
capfuncarg._start()
def deactivate_funcargs(self):
capturing_funcargs = getattr(self, '_capturing_funcargs', None)
if capturing_funcargs is not None:
while capturing_funcargs:
capfuncarg = capturing_funcargs.pop()
capfuncarg._finalize()
del self._capturing_funcargs
capturing_funcarg = getattr(self, '_capturing_funcarg', None)
if capturing_funcarg:
outerr = capturing_funcarg._finalize()
del self._capturing_funcarg
return outerr
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
@@ -164,26 +161,18 @@ 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()
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, __multicall__, item, call):
self.deactivate_funcargs()
funcarg_outerr = self.deactivate_funcargs()
rep = __multicall__.execute()
outerr = self.suspendcapture(item)
if funcarg_outerr is not None:
outerr = (outerr[0] + funcarg_outerr[0],
outerr[1] + funcarg_outerr[1])
if not rep.passed:
addouterr(rep, outerr)
if not rep.passed or rep.when == "teardown":
@@ -191,23 +180,29 @@ class CaptureManager:
item.outerr = outerr
return rep
error_capsysfderror = "cannot use capsys and capfd at the same time"
def pytest_funcarg__capsys(request):
"""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)
if "capfd" in request._funcargs:
raise request.raiseerror(error_capsysfderror)
return CaptureFixture(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""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 "capsys" in request._funcargs:
request.raiseerror(error_capsysfderror)
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")
return CaptureFuncarg(py.io.StdCaptureFD)
pytest.skip("capfd funcarg needs os.dup")
return CaptureFixture(py.io.StdCaptureFD)
class CaptureFuncarg:
class CaptureFixture:
def __init__(self, captureclass):
self.capture = captureclass(now=False)
@@ -216,8 +211,9 @@ class CaptureFuncarg:
def _finalize(self):
if hasattr(self, 'capture'):
self.capture.reset()
outerr = self.capture.reset()
del self.capture
return outerr
def readouterr(self):
return self.capture.readouterr()

View File

@@ -8,12 +8,18 @@ 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. """
""" Parser for command line arguments and ini-file values. """
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
@@ -29,15 +35,17 @@ class Parser:
if option.dest:
self._processopt(option)
def addnote(self, note):
self._notes.append(note)
def getgroup(self, name, description="", after=None):
""" get (or create) a named option Group.
:name: unique name of the option group.
:name: name of the option group.
:description: long description for --help output.
:after: name of other group, used for ordering --help output.
The returned group object has an ``addoption`` method with the same
signature as :py:func:`parser.addoption
<_pytest.config.Parser.addoption>` but will be shown in the
respective group in the output of ``pytest. --help``.
"""
for group in self._groups:
if group.name == name:
@@ -51,7 +59,19 @@ class Parser:
return group
def addoption(self, *opts, **attrs):
""" add an optparse-style option. """
""" register a command line option.
:opts: option names, can be short or long options.
:attrs: same attributes which the ``add_option()`` function of the
`optparse library
<http://docs.python.org/library/optparse.html#module-optparse>`_
accepts.
After command line parsing options are available on the pytest config
object via ``config.option.NAME`` where ``NAME`` is usually set
by passing a ``dest`` attribute, for example
``addoption("--long", dest="NAME", ...)``.
"""
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
@@ -72,11 +92,20 @@ class Parser:
return args
def addini(self, name, help, type=None, default=None):
""" add an ini-file option with the given name and description. """
""" register an ini-file option.
:name: name of the ini-variable
:type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
:default: default value if no ini-file option exists but is queried.
The value of ini-variables can be retrieved via a call to
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
"""
assert type in (None, "pathlist", "args", "linelist")
self._inidict[name] = (help, type, default)
self._ininames.append(name)
class OptionGroup:
def __init__(self, name, description="", parser=None):
self.name = name
@@ -147,20 +176,24 @@ class Conftest(object):
p = current.join(opt1[len(opt)+1:], abs=1)
self._confcutdir = p
break
for arg in args + [current]:
foundanchor = False
for arg in args:
if hasattr(arg, 'startswith') and arg.startswith("--"):
continue
anchor = current.join(arg, abs=1)
if anchor.check(): # we found some file object
self._path2confmods[None] = self.getconftestmodules(anchor)
# let's also consider test* dirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self.getconftestmodules(x)
break
else:
assert 0, "no root of filesystem?"
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self._path2confmods[None] = self.getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self.getconftestmodules(x)
def getconftestmodules(self, path):
""" return a list of imported conftest modules for the given path. """
@@ -238,8 +271,9 @@ class CmdOptions(object):
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
def __init__(self, pluginmanager=None):
#: command line option values, usually added via parser.addoption(...)
#: or parser.getgroup(...).addoption(...) calls
#: command line option values, which must have been previously added
#: via calls like ``parser.addoption(...)`` or
#: ``parser.getgroup(groupname).addoption(...)``
self.option = CmdOptions()
self._parser = Parser(
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
@@ -251,6 +285,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,))
@@ -270,11 +318,10 @@ class Config(object):
def _setinitialconftest(self, args):
# capture output during conftest init (#issue93)
from _pytest.capture import CaptureManager
capman = CaptureManager()
self.pluginmanager.register(capman, 'capturemanager')
# will be unregistered in capture.py's unconfigure()
capman.resumecapture(capman._getmethod_preoptionparse(args))
# 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)
@@ -319,6 +366,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)
@@ -326,6 +374,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``
@@ -407,7 +463,7 @@ class Config(object):
def getcfg(args, inibasenames):
args = [x for x in args if str(x)[0] != "-"]
args = [x for x in args if not str(x).startswith("-")]
if not args:
args = [py.path.local()]
for arg in args:

View File

@@ -16,11 +16,10 @@ default_plugins = (
"junitxml resultlog doctest").split()
class TagTracer:
def __init__(self, prefix="[pytest] "):
def __init__(self):
self._tag2proc = {}
self.writer = None
self.indent = 0
self.prefix = prefix
def get(self, name):
return TagTracerSub(self, (name,))
@@ -30,7 +29,7 @@ class TagTracer:
if args:
indent = " " * self.indent
content = " ".join(map(str, args))
self.writer("%s%s%s\n" %(self.prefix, indent, content))
self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags)))
try:
self._tag2proc[tags](tags, args)
except KeyError:
@@ -80,10 +79,11 @@ class PluginManager(object):
self.import_plugin(spec)
def register(self, plugin, name=None, prepend=False):
assert not self.isregistered(plugin), plugin
if self._name2plugin.get(name, None) == -1:
return
name = name or getattr(plugin, '__name__', str(id(plugin)))
if name in self._name2plugin:
return False
if self.isregistered(plugin, name):
raise ValueError("Plugin already registered: %s=%s" %(name, plugin))
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
@@ -164,14 +164,17 @@ class PluginManager(object):
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
if opt2.startswith("no:"):
name = opt2[3:]
if self.getplugin(name) is not None:
self.unregister(None, name=name)
self._name2plugin[name] = -1
else:
if self.getplugin(opt2) is None:
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__):
@@ -209,6 +212,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 {}
@@ -262,8 +273,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"):
@@ -304,13 +322,15 @@ def importplugin(importspec):
name = importspec
try:
mod = "_pytest." + name
return __import__(mod, None, None, '__doc__')
__import__(mod)
return sys.modules[mod]
except ImportError:
#e = py.std.sys.exc_info()[1]
#if str(e).find(name) == -1:
# raise
pass #
return __import__(importspec, None, None, '__doc__')
__import__(importspec)
return sys.modules[importspec]
class MultiCall:
""" execute a call into multiple python functions/methods. """
@@ -422,10 +442,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):
@@ -439,17 +456,22 @@ def main(args=None, plugins=None):
else: # subsequent calls to main will create a fresh instance
_pluginmanager = PluginManager(load=True)
hook = _pluginmanager.hook
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)
except UsageError:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
exitstatus = 3
if plugins:
for plugin in plugins:
_pluginmanager.register(plugin)
return hook.pytest_cmdline_parse(
pluginmanager=_pluginmanager, args=args)
def main(args=None, plugins=None):
""" return exit code, after performing an in-process test run.
:arg args: list of command line arguments.
:arg plugins: list of plugin objects to be auto-registered during
initialization.
"""
config = _prepareconfig(args, plugins)
exitstatus = config.hook.pytest_cmdline_main(config=config)
return exitstatus
class UsageError(Exception):

View File

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

View File

@@ -1,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,7 @@
""" version info, help messages, tracing configuration. """
import py
import pytest
import inspect, sys
import os, inspect, sys
from _pytest.core import varnames
def pytest_addoption(parser):
@@ -16,12 +16,31 @@ def pytest_addoption(parser):
group.addoption('--traceconfig',
action="store_true", dest="traceconfig", default=False,
help="trace considerations of conftest.py files."),
group._addoption('--nomagic',
action="store_true", dest="nomagic", default=False,
help="don't reinterpret asserts, no traceback cutting. ")
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="generate and show internal debugging information.")
help="store internal tracing debug information in 'pytestdebug.log'.")
def pytest_cmdline_parse(__multicall__):
config = __multicall__.execute()
if config.option.debug:
path = os.path.abspath("pytestdebug.log")
f = open(path, 'w')
config._debugfile = f
f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
config.trace.root.setwriter(f.write)
sys.stderr.write("writing pytestdebug information to %s\n" % path)
return config
@pytest.mark.trylast
def pytest_unconfigure(config):
if hasattr(config, '_debugfile'):
config._debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
config._debugfile.name)
config.trace.root.setwriter(None)
def pytest_cmdline_main(config):
@@ -37,6 +56,7 @@ def pytest_cmdline_main(config):
elif config.option.help:
config.pluginmanager.do_configure(config)
showhelp(config)
config.pluginmanager.do_unconfigure(config)
return 0
def showhelp(config):
@@ -59,6 +79,8 @@ def showhelp(config):
tw.line() ; tw.line()
#tw.sep("=")
tw.line("to see available markers type: py.test --markers")
tw.line("to see available fixtures type: py.test --fixtures")
return
tw.line("conftest.py options:")
@@ -94,7 +116,7 @@ def pytest_report_header(config):
verinfo = getpluginversioninfo(config)
if verinfo:
lines.extend(verinfo)
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []

View File

@@ -23,8 +23,10 @@ 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(...)``.
"""use the parser to add optparse-style options and ini-style
config values via calls, see :py:func:`parser.addoption(...)
<_pytest.config.Parser.addoption>`
and :py:func:`parser.addini(...) <_pytest.config.Parser.addini>`.
"""
def pytest_cmdline_main(config):
@@ -121,16 +123,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)``. """
@@ -138,8 +147,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
@@ -149,15 +164,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
@@ -187,7 +195,7 @@ def pytest_assertrepr_compare(config, op, left, right):
# hooks for influencing reporting (invoked from _pytest_terminal)
# -------------------------------------------------------------------------
def pytest_report_header(config):
def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting."""
def pytest_report_teststatus(report):

254
_pytest/impl Normal file
View File

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

View File

@@ -5,8 +5,60 @@ 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]
_legal_chars = (0x09, 0x0A, 0x0d)
_legal_ranges = (
(0x20, 0xD7FF),
(0xE000, 0xFFFD),
(0x10000, 0x10FFFF),
)
_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high))
for (low, high) in _legal_ranges
if low < sys.maxunicode]
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(unicode('[^%s]') %
unicode('').join(_legal_xml_re))
del _legal_chars
del _legal_ranges
del _legal_xml_re
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 py.xml.raw(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 +80,106 @@ def pytest_unconfigure(config):
del config._xml
config.pluginmanager.unregister(xml)
def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()']
names[0] = names[0].replace("/", '.')
return names
class LogXML(object):
def __init__(self, logfile, prefix):
self.logfile = logfile
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(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 != "()"]
names = mangle_testnames(report.nodeid.split("::"))
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"/>')
if hasattr(report, "wasxfail"):
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'])
if hasattr(report, "wasxfail"):
self.append(Junit.skipped(str(report.wasxfail),
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 +188,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 +206,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,15 @@
import py
import pytest, _pytest
import os, sys
import inspect
import os, sys, imp
try:
from collections import MutableMapping as MappingMixin
except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.mark import MarkInfo
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
# exitcodes for the command line
@@ -10,6 +18,9 @@ EXIT_OK = 0
EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4
name_re = py.std.re.compile("^[a-zA-Z_]\w*$")
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
@@ -26,6 +37,8 @@ def pytest_addoption(parser):
group._addoption('--maxfail', metavar="num",
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',
@@ -46,55 +59,78 @@ def pytest_addoption(parser):
def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector, File=File))
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
return dict(collect=collect)
def pytest_configure(config):
py.test.config = config # compatibiltiy
if config.option.exitfirst:
config.option.maxfail = 1
def pytest_cmdline_main(config):
""" default command line protocol for initialization, session,
running tests and reporting. """
def wrap_session(config, doit):
"""Skeleton command line program"""
session = Session(config)
session.exitstatus = EXIT_OK
initstate = 0
try:
config.pluginmanager.do_configure(config)
config.hook.pytest_sessionstart(session=session)
config.hook.pytest_collection(session=session)
config.hook.pytest_runtestloop(session=session)
except pytest.UsageError:
raise
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
config.pluginmanager.notify_exception(excinfo)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
if not session.exitstatus and session._testsfailed:
session.exitstatus = EXIT_TESTSFAILED
config.hook.pytest_sessionfinish(session=session,
exitstatus=session.exitstatus)
config.pluginmanager.do_unconfigure(config)
try:
config.pluginmanager.do_configure(config)
initstate = 1
config.hook.pytest_sessionstart(session=session)
initstate = 2
doit(config, session)
except pytest.UsageError:
msg = sys.exc_info()[1].args[0]
sys.stderr.write("ERROR: %s\n" %(msg,))
session.exitstatus = EXIT_USAGEERROR
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
config.pluginmanager.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
finally:
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
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)
def getnextitem(i):
# this is a function to avoid python2
# keeping sys.exc_info set when calling into a test
# python2 keeps sys.exc_info till the frame is left
try:
return session.items[i+1]
except IndexError:
return None
for i, item in enumerate(session.items):
nextitem = getnextitem(i)
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
@@ -112,8 +148,10 @@ class HookProxy:
def __init__(self, fspath, config):
self.fspath = fspath
self.config = config
def __getattr__(self, name):
hookmethod = getattr(self.config.hook, name)
def call_matching_hooks(**kwargs):
plugins = self.config._getmatchingplugins(self.fspath)
return hookmethod.pcall(plugins, **kwargs)
@@ -121,31 +159,71 @@ class HookProxy:
def compatproperty(name):
def fget(self):
# deprecated - use pytest.name
return getattr(pytest, name)
return property(fget, None, None,
"deprecated attribute %r, use pytest.%s" % (name,name))
return property(fget)
class NodeKeywords(MappingMixin):
def __init__(self, node):
parent = node.parent
bases = parent and (parent.keywords._markers,) or ()
self._markers = type("dynmarker", bases, {node.name: True})
def __getitem__(self, key):
try:
return getattr(self._markers, key)
except AttributeError:
raise KeyError(key)
def __setitem__(self, key, value):
setattr(self._markers, key, value)
def __delitem__(self, key):
delattr(self._markers, key)
def __iter__(self):
return iter(self.keys())
def __len__(self):
return len(self.keys())
def keys(self):
return dir(self._markers)
class Node(object):
""" base class for all Nodes in the collection tree.
""" base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes."""
def __init__(self, name, parent=None, config=None, session=None):
#: a unique name with the scope of the parent
#: a unique name within the scope of the parent node
self.name = name
#: the parent collector node.
self.parent = parent
#: the test config object
#: the pytest config object
self.config = config or parent.config
#: the collection this node is part of
#: the session this node is part of
self.session = session or parent.session
#: filesystem path where this node was collected from
#: filesystem path where this node was collected from (can be None)
self.fspath = getattr(parent, 'fspath', None)
self.ihook = self.session.gethookproxy(self.fspath)
self.keywords = {self.name: True}
#: keywords/markers collected from all scopes
self.keywords = NodeKeywords(self)
#self.extrainit()
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
#def extrainit(self):
# """"extra initialization after Node is initialized. Implemented
# by some subclasses. """
Module = compatproperty("Module")
Class = compatproperty("Class")
@@ -163,25 +241,28 @@ class Node(object):
return cls
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
return "<%s %r>" %(self.__class__.__name__,
getattr(self, 'name', None))
# methods for ordering nodes
@property
def nodeid(self):
""" a ::-separated string denoting its collection tree address. """
try:
return self._nodeid
except AttributeError:
self._nodeid = x = self._makeid()
return x
def _makeid(self):
return self.parent.nodeid + "::" + self.name
def __eq__(self, other):
if not isinstance(other, Node):
return False
return self.__class__ == other.__class__ and \
self.name == other.name and self.parent == other.parent
return (self.__class__ == other.__class__ and
self.name == other.name and self.parent == other.parent)
def __ne__(self, other):
return not self == other
@@ -216,13 +297,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()]
@@ -240,6 +321,9 @@ class Node(object):
pass
def _repr_failure_py(self, excinfo, style=None):
fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError):
return excinfo.value.formatrepr()
if self.config.option.fulltrace:
style="long"
else:
@@ -317,6 +401,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, ""
@@ -346,9 +432,9 @@ class Session(FSCollector):
__module__ = 'builtins' # for py3
def __init__(self, config):
super(Session, self).__init__(py.path.local(), parent=None,
config=config, session=self)
assert self.config.pluginmanager.register(self, name="session", prepend=True)
FSCollector.__init__(self, py.path.local(), parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
@@ -359,7 +445,7 @@ class Session(FSCollector):
raise self.Interrupted(self.shouldstop)
def pytest_runtest_logreport(self, report):
if report.failed and 'xfail' not in getattr(report, 'keywords', []):
if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
@@ -374,6 +460,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)
@@ -381,6 +477,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)
@@ -396,7 +493,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))
@@ -424,7 +520,7 @@ class Session(FSCollector):
if path.check(dir=1):
assert not names, "invalid arg %r" %(arg,)
for path in path.visit(fil=lambda x: x.check(file=1),
rec=self._recurse, bf=True, sort=True):
rec=self._recurse, bf=True, sort=True):
for x in self._collectfile(path):
yield x
else:
@@ -436,13 +532,13 @@ class Session(FSCollector):
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
return ()
return ()
return ihook.pytest_collect_file(path=path, parent=self)
def _recurse(self, path):
ihook = self.gethookproxy(path.dirpath())
if ihook.pytest_ignore_collect(path=path, config=self.config):
return
return
for pat in self._norecursepatterns:
if path.check(fnmatch=pat):
return False
@@ -451,16 +547,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 str(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. """
@@ -478,7 +587,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
@@ -532,3 +641,12 @@ class Session(FSCollector):
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)
def getfslineno(obj):
# xxx let decorators etc specify a sane ordering
if hasattr(obj, 'place_as'):
obj = obj.place_as
fslineno = py.code.getfslineno(obj)
assert isinstance(fslineno[1], int), obj
return fslineno

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,22 +54,39 @@ 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)
itemkeywords = colitem.keywords
for key in filter(None, keywordexpr.split()):
eor = key[:1] == '-'
if eor:
@@ -52,14 +94,6 @@ def skipbykeyword(colitem, keywordexpr):
if not (eor ^ matchonekeyword(key, itemkeywords)):
return True
def getkeywords(node):
keywords = {}
while node is not None:
keywords.update(node.keywords)
node = node.parent
return keywords
def matchonekeyword(key, itemkeywords):
for elem in key.split("."):
for kw in itemkeywords:
@@ -77,15 +111,31 @@ 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
@@ -133,8 +183,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 +199,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

@@ -1,6 +1,6 @@
""" monkeypatching and mocking functionality. """
import os, sys
import os, sys, inspect
def pytest_funcarg__monkeypatch(request):
"""The returned ``monkeypatch`` funcarg provides these
@@ -13,6 +13,7 @@ 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 after the requesting
test function has finished. The ``raising``
@@ -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
@@ -37,6 +39,10 @@ class monkeypatch:
oldval = getattr(obj, name, notset)
if raising and oldval is notset:
raise AttributeError("%r has no attribute %r" %(obj, name))
# avoid class descriptors like staticmethod/classmethod
if inspect.isclass(obj):
oldval = obj.__dict__.get(name, notset)
self._setattr.insert(0, (obj, name, oldval))
setattr(obj, name, value)
@@ -83,6 +89,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 +112,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):
@@ -40,7 +41,7 @@ def pytest_make_collect_report(collector):
def call_optional(obj, name):
method = getattr(obj, name, None)
if method:
if method is not None and not hasattr(method, "_pytestfixturefunction"):
# If there's any problems allow the exception to raise rather than
# silently ignoring them
method()

View File

@@ -2,7 +2,7 @@
import py, sys
class url:
base = "http://paste.pocoo.org"
base = "http://bpaste.net"
xmlrpc = base + "/xmlrpc/"
show = base + "/show/"
@@ -11,7 +11,7 @@ def pytest_addoption(parser):
group._addoption('--pastebin', metavar="mode",
action='store', dest="pastebin", default=None,
type="choice", choices=['failed', 'all'],
help="send failed|all info to Pocoo pastebin service.")
help="send failed|all info to bpaste.net pastebin service.")
def pytest_configure(__multicall__, config):
import tempfile
@@ -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,9 +40,17 @@ 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
class PdbInvoke:
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, item, call, __multicall__):
@@ -49,7 +59,7 @@ class PdbInvoke:
call.excinfo.errisinstance(pytest.skip.Exception) or \
call.excinfo.errisinstance(py.std.bdb.BdbQuit):
return rep
if "xfail" in rep.keywords:
if hasattr(rep, "wasxfail"):
return rep
# we assume that the above execute() suspended capturing
# XXX we re-use the TerminalReporter's terminalwriter
@@ -60,7 +70,13 @@ class PdbInvoke:
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

@@ -2,11 +2,12 @@
import py, pytest
import sys, os
import codecs
import re
import inspect
import time
from fnmatch import fnmatch
from _pytest.main import Session
from _pytest.main import Session, EXIT_OK
from py.builtin import print_
from _pytest.core import HookRelay
@@ -25,6 +26,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,14 +238,17 @@ 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()
p.write(source.encode("utf-8"), "wb")
source = py.builtin._totext(py.code.Source(value)).strip()
content = source.encode("utf-8") # + "\n"
#content = content.rstrip() + "\n"
p.write(content, "wb")
if ret is None:
ret = p
return ret
@@ -291,13 +296,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,19 +317,11 @@ 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)
# the test class where we are called from wants to provide the runner
testclassinstance = py.builtin._getimself(self.request.function)
testclassinstance = self.request.instance
runner = testclassinstance.getrunner()
return runner(item)
@@ -334,69 +337,66 @@ 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=plugins)
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"):
for item in self.getitems(source):
items = self.getitems(source)
for item in items:
if item.name == funcname:
return item
assert 0, "%r item not found in module:\n%s" %(funcname, source)
assert 0, "%r item not found in module:\n%s\nitems: %s" %(
funcname, source, items)
def getitems(self, source):
modcol = self.getmodulecol(source)
@@ -409,7 +409,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 +425,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)
@@ -437,28 +443,35 @@ class TmpTestdir:
p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr")
print_("running", cmdargs, "curdir=", py.path.local())
f1 = p1.open("wb")
f2 = p2.open("wb")
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
ret = popen.wait()
f1.close()
f2.close()
out = p1.read("rb")
out = getdecoded(out).splitlines()
err = p2.read("rb")
err = getdecoded(err).splitlines()
def dump_lines(lines, fp):
try:
for line in lines:
py.builtin.print_(line, file=fp)
except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,))
dump_lines(out, sys.stdout)
dump_lines(err, sys.stderr)
f1 = codecs.open(str(p1), "w", encoding="utf8")
f2 = codecs.open(str(p2), "w", encoding="utf8")
try:
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
ret = popen.wait()
finally:
f1.close()
f2.close()
f1 = codecs.open(str(p1), "r", encoding="utf8")
f2 = codecs.open(str(p2), "r", encoding="utf8")
try:
out = f1.read().splitlines()
err = f2.read().splitlines()
finally:
f1.close()
f2.close()
self._dump_lines(out, sys.stdout)
self._dump_lines(err, sys.stderr)
return RunResult(ret, out, err, time.time()-now)
def _dump_lines(self, lines, fp):
try:
for line in lines:
py.builtin.print_(line, file=fp)
except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,))
def runpybin(self, scriptname, *args):
fullargs = self._getpybinargs(scriptname) + args
return self.run(*fullargs)
@@ -517,6 +530,10 @@ class TmpTestdir:
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?!")
if sys.platform.startswith("freebsd"):
pytest.xfail("pexpect does not work reliably on freebsd")
logfile = self.tmpdir.join("spawn.out")
child = pexpect.spawn(cmd, logfile=logfile.open("w"))
child.timeout = expect_timeout
@@ -529,10 +546,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
@@ -554,10 +567,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("::"):

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
""" (disabled by default) create result information in a plain text file. """
""" log machine-parseable test session result information in a plain
text file.
"""
import py
@@ -63,6 +65,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 +93,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,7 @@
""" basic collect and runtest protocol implementations """
import py, sys
from time import time
from py._code.code import TerminalRepr
def pytest_namespace():
@@ -14,53 +15,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 +94,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 +115,16 @@ class CallInfo:
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
self.when = when
self.start = 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()
def __repr__(self):
if self.excinfo:
@@ -122,6 +144,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'):
@@ -129,7 +155,10 @@ class BaseReport(object):
if hasattr(longrepr, 'toterminal'):
longrepr.toterminal(out)
else:
out.line(str(longrepr))
try:
out.line(longrepr)
except UnicodeEncodeError:
out.line("<unprintable longrepr>")
passed = property(lambda x: x.outcome == "passed")
failed = property(lambda x: x.outcome == "failed")
@@ -141,6 +170,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 +183,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 +192,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 +212,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 +238,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 +263,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):
@@ -239,7 +283,7 @@ class CollectErrorRepr(TerminalRepr):
def __init__(self, msg):
self.longrepr = msg
def toterminal(self, out):
out.line(str(self.longrepr), red=True)
out.line(self.longrepr, red=True)
class SetupState(object):
""" shared state for setting up/tearing down test items or collectors. """
@@ -251,6 +295,8 @@ class SetupState(object):
""" attach a finalizer to the given colitem.
if colitem is None, this will add a finalizer that
is called at the end of teardown_all().
if colitem is a tuple, it will be used as a key
and needs an explicit call to _callfinalizers(key) later on.
"""
assert hasattr(finalizer, '__call__')
#assert colitem in self.stack
@@ -268,31 +314,35 @@ class SetupState(object):
def _teardown_with_finalization(self, colitem):
self._callfinalizers(colitem)
if colitem:
if hasattr(colitem, "teardown"):
colitem.teardown()
for colitem in self._finalizers:
assert colitem is None or colitem in self.stack
assert colitem is None or colitem in self.stack \
or isinstance(colitem, tuple)
def teardown_all(self):
while self.stack:
self._pop_and_teardown()
self._teardown_with_finalization(None)
for key in list(self._finalizers):
self._teardown_with_finalization(key)
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'):
@@ -359,7 +409,9 @@ skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitely fail an currently-executing test with the given Message.
if @pytrace is not True the msg represents the full failure information.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
@@ -374,9 +426,10 @@ def importorskip(modname, minversion=None):
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
try:
mod = __import__(modname, None, None, ['__doc__'])
__import__(modname)
except ImportError:
py.test.skip("could not import %r" %(modname,))
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)

View File

@@ -9,6 +9,22 @@ 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(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html"
)
config.addinivalue_line("markers",
"xfail(condition, reason=None, run=True): mark the the test function "
"as an expected failure if eval(condition) has a True value. "
"Optionally specify a reason for better reporting and run=False if "
"you don't even want to execute the test function. See "
"http://pytest.org/latest/skipping.html"
)
def pytest_namespace():
return dict(xfail=xfail)
@@ -95,6 +111,7 @@ class MarkEvaluator:
return expl
@pytest.mark.tryfirst
def pytest_runtest_setup(item):
if not isinstance(item, pytest.Function):
return
@@ -117,6 +134,14 @@ def check_xfail_no_run(item):
def pytest_runtest_makereport(__multicall__, item, call):
if not isinstance(item, pytest.Function):
return
# unitttest special case, see setting of _unexpectedsuccess
if hasattr(item, '_unexpectedsuccess'):
rep = __multicall__.execute()
if rep.when == "call":
# we need to translate into how py.test encodes xpass
rep.wasxfail = "reason: " + repr(item._unexpectedsuccess)
rep.outcome = "failed"
return rep
if not (call.excinfo and
call.excinfo.errisinstance(py.test.xfail.Exception)):
evalxfail = getattr(item, '_evalxfail', None)
@@ -125,27 +150,27 @@ def pytest_runtest_makereport(__multicall__, item, call):
if call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception):
if not item.config.getvalue("runxfail"):
rep = __multicall__.execute()
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
return rep
rep = __multicall__.execute()
evalxfail = item._evalxfail
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
rep.keywords['xfail'] = evalxfail.getexplanation()
elif call.when == "call":
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
return rep
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
if not rep.skipped:
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
elif call.when == "call":
rep.outcome = "failed"
else:
return rep
rep.wasxfail = evalxfail.getexplanation()
return rep
return rep
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
if 'xfail' in report.keywords:
if hasattr(report, "wasxfail"):
if report.skipped:
return "xfailed", "x", "xfail"
elif report.failed:
@@ -169,28 +194,30 @@ 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")
if xfailed:
for rep in xfailed:
pos = rep.nodeid
reason = rep.keywords['xfail']
reason = rep.wasxfail
lines.append("XFAIL %s" % (pos,))
if reason:
lines.append(" " + str(reason))
@@ -200,7 +227,7 @@ def show_xpassed(terminalreporter, lines):
if xpassed:
for rep in xpassed:
pos = rep.nodeid
reason = rep.keywords['xfail']
reason = rep.wasxfail
lines.append("XPASS %s %s" %(pos, reason))
def cached_eval(config, expr, d):

View File

@@ -2,7 +2,8 @@
This is a good source for looking at the various reporting hooks.
"""
import pytest, py
import pytest
import py
import sys
import os
@@ -11,11 +12,11 @@ def pytest_addoption(parser):
group._addoption('-v', '--verbose', action="count",
dest="verbose", default=0, help="increase verbosity."),
group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decreate verbosity."),
dest="quiet", default=0, help="decrease verbosity."),
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).")
@@ -37,13 +38,15 @@ def pytest_configure(config):
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
newstdout = py.io.dupfile(stdout, buffering=1,
encoding=stdout.encoding)
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
config._cleanup.append(lambda: newstdout.close())
assert stdout.encoding == newstdout.encoding
stdout = newstdout
reporter = TerminalReporter(config, stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
@@ -52,11 +55,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
@@ -98,7 +96,7 @@ class TerminalReporter:
self._numcollected = 0
self.stats = {}
self.curdir = py.path.local()
self.startdir = self.curdir = py.path.local()
if file is None:
file = py.std.sys.stdout
self._tw = py.io.TerminalWriter(file)
@@ -113,9 +111,9 @@ class TerminalReporter:
def write_fspath_result(self, fspath, res):
if fspath != self.currentfspath:
self.currentfspath = fspath
#fspath = self.curdir.bestrelpath(fspath)
#fspath = self.startdir.bestrelpath(fspath)
self._tw.line()
#relpath = self.curdir.bestrelpath(fspath)
#relpath = self.startdir.bestrelpath(fspath)
self._tw.write(fspath + " ")
self._tw.write(res)
@@ -165,9 +163,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
@@ -214,7 +209,7 @@ class TerminalReporter:
self.currentfspath = -2
def pytest_collection(self):
if not self.hasmarkup:
if not self.hasmarkup and self.config.option.verbose >=1:
self.write("collecting ... ", bold=True)
def pytest_collectreport(self, report):
@@ -229,6 +224,9 @@ class TerminalReporter:
self.report_collect()
def report_collect(self, final=False):
if self.config.option.verbose < 0:
return
errors = len(self.stats.get('error', []))
skipped = len(self.stats.get('skipped', []))
if final:
@@ -250,6 +248,7 @@ class TerminalReporter:
def pytest_collection_modifyitems(self):
self.report_collect(True)
@pytest.mark.trylast
def pytest_sessionstart(self, session):
self._sessionstarttime = py.std.time.time()
if not self.showheader:
@@ -259,17 +258,29 @@ 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):
msg += " -- " + str(sys.executable)
self.write_line(msg)
lines = self.config.hook.pytest_report_header(config=self.config)
lines = self.config.hook.pytest_report_header(
config=self.config, startdir=self.startdir)
lines.reverse()
for line in flatten(lines):
self.write_line(line)
def pytest_report_header(self, config):
plugininfo = config.pluginmanager._plugin_distinfo
if plugininfo:
l = []
for dist, plugin in plugininfo:
name = dist.project_name
if name.startswith("pytest-"):
name = name[7:]
l.append(name)
return "plugins: %s" % ", ".join(l)
def pytest_collection_finish(self, session):
if self.config.option.collectonly:
self._printcollecteditems(session.items)
@@ -289,10 +300,18 @@ class TerminalReporter:
# 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:
for item in items:
nodeid = item.nodeid
nodeid = nodeid.replace("::()::", "::")
self._tw.line(nodeid)
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 = ""
@@ -318,12 +337,17 @@ class TerminalReporter:
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
self.summary_stats()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
def pytest_unconfigure(self):
if hasattr(self, '_keyboardinterrupt_memo'):
self._report_keyboardinterrupt()
def _report_keyboardinterrupt(self):
excrepr = self._keyboardinterrupt_memo
msg = excrepr.reprcrash.message
@@ -388,7 +412,7 @@ class TerminalReporter:
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
rep.toterminal(self._tw)
self._outrep_summary(rep)
def summary_errors(self):
if self.config.option.tbstyle != "no":
@@ -406,32 +430,49 @@ 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
keys = "failed passed skipped deselected".split()
keys = "failed passed skipped deselected xfailed xpassed".split()
for key in self.stats.keys():
if key not in keys:
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)
if self.verbosity >= 0:
self.write_sep("=", msg, bold=True)
else:
self.write_line(msg, bold=True)
#else:
# self.write_line(msg, bold=True)
def summary_deselected(self):
if 'deselected' in self.stats:
self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True)
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)
if l:
self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), " ".join(l)), bold=True)
def repr_pythonversion(v=None):
if v is None:

View File

@@ -40,23 +40,20 @@ class TempdirHandler:
basetemp.mkdir()
else:
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
self._basetemp = t = basetemp
self._basetemp = t = basetemp.realpath()
self.trace("new basetemp", t)
return t
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
which is unique to each test function invocation,
@@ -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:
@@ -17,10 +20,30 @@ def pytest_pycollect_makeitem(collector, name, obj):
return UnitTestCase(name, parent=collector)
class UnitTestCase(pytest.Class):
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs
def collect(self):
self.session._fixturemanager.parsefactories(self, unittest=True)
loader = py.std.unittest.TestLoader()
module = self.getparent(pytest.Module).obj
cls = self.obj
foundsomething = False
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)
foundsomething = True
if not foundsomething:
runtest = getattr(self.obj, 'runTest', None)
if runtest is not None:
ut = sys.modules.get("twisted.trial.unittest", None)
if ut is None or runtest != ut.TestCase.runTest:
yield TestCaseFunction('runTest', parent=self)
def setup(self):
meth = getattr(self.obj, 'setUpClass', None)
@@ -37,17 +60,17 @@ 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)
if hasattr(self, "_request"):
self._request._fillfixtures()
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):
@@ -83,28 +106,37 @@ class TestCaseFunction(pytest.Function):
self._addexcinfo(rawexcinfo)
def addFailure(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo)
def addSkip(self, testcase, reason):
try:
pytest.skip(reason)
except pytest.skip.Exception:
self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason):
def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
try:
pytest.xfail(str(reason))
except pytest.xfail.Exception:
self._addexcinfo(sys.exc_info())
def addUnexpectedSuccess(self, testcase, reason):
pass
def addUnexpectedSuccess(self, testcase, reason=""):
self._unexpectedsuccess = reason
def addSuccess(self, testcase):
pass
def stopTest(self, testcase):
pass
def runtest(self):
self._testcase(result=self)
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest'))
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):
@@ -120,14 +152,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.27"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"
@@ -63,7 +63,7 @@ Description: xxx
""" % SETUPTOOLS_FAKED_VERSION
def _install(tarball):
def _install(tarball, install_args=()):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
@@ -81,7 +81,7 @@ def _install(tarball):
# installing
log.warn('Installing Distribute')
if not _python_cmd('setup.py', 'install'):
if not _python_cmd('setup.py', 'install', *install_args):
log.warn('Something went wrong during the installation.')
log.warn('See the error message above.')
finally:
@@ -306,6 +306,9 @@ def _create_fake_setuptools_pkg_info(placeholder):
log.warn('%s already exists', pkg_info)
return
if not os.access(pkg_info, os.W_OK):
log.warn("Don't have permissions to write %s, skipping", pkg_info)
log.warn('Creating %s', pkg_info)
f = open(pkg_info, 'w')
try:
@@ -474,11 +477,20 @@ def _extractall(self, path=".", members=None):
else:
self._dbg(1, "tarfile: %s" % e)
def _build_install_args(argv):
install_args = []
user_install = '--user' in argv
if user_install and sys.version_info < (2,6):
log.warn("--user requires Python 2.6 or later")
raise SystemExit(1)
if user_install:
install_args.append('--user')
return install_args
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
tarball = download_setuptools()
_install(tarball)
_install(tarball, _build_install_args(argv))
if __name__ == '__main__':

View File

@@ -1,43 +0,0 @@
{% 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="http://https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues</a>|&nbsp;
<a href="{{ pathto('contact') }}">contact</a>&nbsp;
</div>
</div>
{% endblock %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-7597274-13']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
{% endblock %}

View File

@@ -1,11 +0,0 @@
Release announcements
===========================================
.. toctree::
:maxdepth: 2
release-2.0.2
release-2.0.1
release-2.0.0

View File

@@ -1,144 +0,0 @@
The writing and reporting of assertions in tests
==================================================
.. _`assert with the assert statement`:
assert with the ``assert`` statement
---------------------------------------------------------
``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::
# content of test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
to assert that your object returns a certain value. If this
assertion fails you will see the value of ``x``::
$ py.test test_assert1.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.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.07 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording the intermediate values.
Note: If evaluating the assert expression has side effects you may get a
warning that the intermediate values could not be determined safely. A
common example of this issue is an assertion which reads from a file::
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 != '...'
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
and if you need to have access to the actual exception info you may use::
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
If you want to write test code that works on Python2.4 as well,
you may also use two other ways to test for an expected exception::
pytest.raises(ExpectedException, func, *args, **kwargs)
pytest.raises(ExpectedException, "func(*args, **kwargs)")
both of which execute the specified function with args and kwargs and
asserts that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.
.. _newreport:
Making use of context-sensitive comparisons
-------------------------------------------------
.. versionadded:: 2.0
py.test has rich support for providing context-sensitive information
when it encounters comparisons. For example::
# content of test_assert2.py
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
> assert set1 == set2
E assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
Special comparisons are done for a number of cases:
* comparing long strings: a context diff is shown
* comparing long sequences: first failing indices
* comparing dicts: different entries
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
..
Defining your own comparison
----------------------------------------------

View File

@@ -1,33 +0,0 @@
.. _toc:
Table of Contents
========================
.. note::
version 2.0 introduces :ref:`pytest as the main Python import name <naming20>`
.. toctree::
:maxdepth: 2
overview
apiref
plugins
example/index
talks
develop
announce/index
.. toctree::
:hidden:
changelog.txt
naming20.txt
example/attic
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

144
doc/en/Makefile Normal file
View File

@@ -0,0 +1,144 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
SITETARGET=latest
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
install: html
rsync -avz _build/html/ pytest.org:/www/pytest.org/$(SITETARGET)
installpdf: latexpdf
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/$(SITETARGET)
installall: clean install installpdf
@echo "done"
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pytest.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/pytest"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
make -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
{% extends "!layout.html" %}
{% block relbaritems %}
{{ super() }}
<g:plusone></g:plusone>
{% endblock %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-7597274-13']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
{% endblock %}

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" %}

23
doc/en/announce/index.txt Normal file
View File

@@ -0,0 +1,23 @@
Release announcements
===========================================
.. toctree::
:maxdepth: 2
release-2.3.2
release-2.3.1
release-2.3.0
release-2.2.4
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

@@ -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/latest/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

@@ -1,4 +1,4 @@
py.test 2.0.2: bug fixes, improved xfail/skip expressions, speedups
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,
@@ -32,17 +32,17 @@ Changes between 2.0.1 and 2.0.2
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
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
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
@@ -65,7 +65,7 @@ Changes between 2.0.1 and 2.0.2
- 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
- fix slighly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses

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,43 @@
pytest-2.2.2: bug fixes
===========================================================================
pytest-2.2.2 (updated to 2.2.3 to fix packaging issues) 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

@@ -0,0 +1,39 @@
pytest-2.2.4: bug fixes, better junitxml/unittest/python3 compat
===========================================================================
pytest-2.2.4 is a minor backward-compatible release of the versatile
py.test testing tool. It contains bug fixes and a few refinements
to junitxml reporting, better unittest- and python3 compatibility.
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 Benjamin Peterson and the contributors of issues.
best,
holger krekel
Changes between 2.2.3 and 2.2.4
-----------------------------------
- fix error message for rewritten assertions involving the % operator
- fix issue 126: correctly match all invalid xml characters for junitxml
binary escape
- fix issue with unittest: now @unittest.expectedFailure markers should
be processed correctly (you can also use @pytest.mark markers)
- document integration with the extended distribute/setuptools test commands
- fix issue 140: propperly get the real functions
of bound classmethods for setup/teardown_class
- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
- fix issue #143: call unconfigure/sessionfinish always when
configure/sessionstart where called
- fix issue #144: better mangle test ids to junitxml classnames
- upgrade distribute_setup.py to 0.6.27

View File

@@ -0,0 +1,134 @@
pytest-2.3: improved fixtures / better unittest integration
=============================================================================
pytest-2.3 comes with many major improvements for fixture/funcarg management
and parametrized testing in Python. It is now easier, more efficient and
more predicatable to re-run the same tests with different fixture
instances. Also, you can directly declare the caching "scope" of
fixtures so that dependent tests throughout your whole test suite can
re-use database or other expensive fixture objects with ease. Lastly,
it's possible for fixture functions (formerly known as funcarg
factories) to use other fixtures, allowing for a completely modular and
re-useable fixture design.
For detailed info and tutorial-style examples, see:
http://pytest.org/latest/fixture.html
Moreover, there is now support for using pytest fixtures/funcargs with
unittest-style suites, see here for examples:
http://pytest.org/latest/unittest.html
Besides, more unittest-test suites are now expected to "simply work"
with pytest.
All changes are backward compatible and you should be able to continue
to run your test suites and 3rd party plugins that worked with
pytest-2.2.4.
If you are interested in the precise reasoning (including examples) of the
pytest-2.3 fixture evolution, please consult
http://pytest.org/latest/funcarg_compare.html
For general info on installation and getting started:
http://pytest.org/latest/getting-started.html
Docs and PDF access as usual at:
http://pytest.org
and more details for those already in the knowing of pytest can be found
in the CHANGELOG below.
Particular thanks for this release go to Floris Bruynooghe, Alex Okrushko
Carl Meyer, Ronny Pfannschmidt, Benjamin Peterson and Alex Gaynor for helping
to get the new features right and well integrated. Ronny and Floris
also helped to fix a number of bugs and yet more people helped by
providing bug reports.
have fun,
holger krekel
Changes between 2.2.4 and 2.3.0
-----------------------------------
- fix issue202 - better automatic names for parametrized test functions
- fix issue139 - introduce @pytest.fixture which allows direct scoping
and parametrization of funcarg factories. Introduce new @pytest.setup
marker to allow the writing of setup functions which accept funcargs.
- fix issue198 - conftest fixtures were not found on windows32 in some
circumstances with nested directory structures due to path manipulation issues
- fix issue193 skip test functions with were parametrized with empty
parameter sets
- fix python3.3 compat, mostly reporting bits that previously depended
on dict ordering
- introduce re-ordering of tests by resource and parametrization setup
which takes precedence to the usual file-ordering
- fix issue185 monkeypatching time.time does not cause pytest to fail
- fix issue172 duplicate call of pytest.setup-decoratored setup_module
functions
- fix junitxml=path construction so that if tests change the
current working directory and the path is a relative path
it is constructed correctly from the original current working dir.
- fix "python setup.py test" example to cause a proper "errno" return
- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
- catch unicode-issues when writing failure representations
to terminal to prevent the whole session from crashing
- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
will now take precedence before xfail-markers because we
can't determine xfail/xpass status in case of a skip. see also:
http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
- always report installed 3rd party plugins in the header of a test run
- fix issue160: a failing setup of an xfail-marked tests should
be reported as xfail (not xpass)
- fix issue128: show captured output when capsys/capfd are used
- fix issue179: propperly show the dependency chain of factories
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
- make request.keywords and node.keywords writable. All descendant
collection nodes will see keyword values. Keywords are dictionaries
containing markers and other info.
- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
- fix issue 176: correctly catch the builtin AssertionError
even when we replaced AssertionError with a subclass on the
python level
- factory discovery no longer fails with magic global callables
that provide no sane __code__ object (mock.call for example)
- fix issue 182: testdir.inprocess_run now considers passed plugins
- fix issue 188: ensure sys.exc_info is clear on python2
before calling into a test
- fix issue 191: add unittest TestCase runTest method support
- fix issue 156: monkeypatch correctly handles class level descriptors
- reporting refinements:
- pytest_report_header now receives a "startdir" so that
you can use startdir.bestrelpath(yourpath) to show
nice relative path
- allow plugins to implement both pytest_report_header and
pytest_sessionstart (sessionstart is invoked first).
- don't show deselected reason line if there is none
- py.test -vv will show all of assert comparisations instead of truncating

View File

@@ -0,0 +1,39 @@
pytest-2.3.1: fix regression with factory functions
===========================================================================
pytest-2.3.1 is a quick follow-up release:
- fix issue202 - regression with fixture functions/funcarg factories:
using "self" is now safe again and works as in 2.2.4. Thanks
to Eduard Schettino for the quick bug report.
- disable pexpect pytest self tests on Freebsd - thanks Koob for the
quick reporting
- fix/improve interactive docs with --markers
See
http://pytest.org/
for general information. To install or upgrade pytest:
pip install -U pytest # or
easy_install -U pytest
best,
holger krekel
Changes between 2.3.0 and 2.3.1
-----------------------------------
- fix issue202 - fix regression: using "self" from fixture functions now
works as expected (it's the same "self" instance that a test method
which uses the fixture sees)
- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
due to pexpect not supporting it properly (hanging)
- link to web pages from --markers output which provides help for
pytest.mark.* usage.

View File

@@ -10,14 +10,15 @@ py.test reference documentation
builtin.txt
customize.txt
assert.txt
funcargs.txt
fixture.txt
parametrize.txt
xunit_setup.txt
capture.txt
monkeypatch.txt
xdist.txt
tmpdir.txt
skipping.txt
mark.txt
skipping.txt
recwarn.txt
unittest.txt
nose.txt

253
doc/en/assert.txt Normal file
View File

@@ -0,0 +1,253 @@
The writing and reporting of assertions in tests
==================================================
.. _`assertfeedback`:
.. _`assert with the assert statement`:
.. _`assert`:
Asserting with the ``assert`` statement
---------------------------------------------------------
``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::
# content of test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
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.7.3 -- pytest-2.3.2
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.01 seconds =========================
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.
However, if you specify a message with the assertion like this::
assert a % 2 == 0, "value was odd, should be even"
then no assertion introspection takes places at all and the message
will be simply shown in the traceback.
See :ref:`assert-details` for more information on assertion introspection.
.. _`assertraises`:
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
and if you need to have access to the actual exception info you may use::
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
# do checks related to excinfo.type, excinfo.value, excinfo.traceback
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)
pytest.raises(ExpectedException, "func(*args, **kwargs)")
both of which execute the specified function with args and kwargs and
asserts that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.
.. _newreport:
Making use of context-sensitive comparisons
-------------------------------------------------
.. versionadded:: 2.0
py.test has rich support for providing context-sensitive information
when it encounters comparisons. For example::
# content of test_assert2.py
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
> assert set1 == set2
E assert set(['0', '1', '3', '8']) == set(['0', '3', '5', '8'])
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.01 seconds =========================
Special comparisons are done for a number of cases:
* comparing long strings: a context diff is shown
* comparing long sequences: first failing indices
* comparing dicts: different entries
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
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
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
.. _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``.

186
doc/en/attic_fixtures.txt Normal file
View File

@@ -0,0 +1,186 @@
**Test classes, modules or whole projects can make use of
one or more fixtures**. All required fixture functions will execute
before a test from the specifying context executes. As You can use this
to make tests operate from a pre-initialized directory or with
certain environment variables or with pre-configured global application
settings.
For example, the Django_ project requires database
initialization to be able to import from and use its model objects.
For that, the `pytest-django`_ plugin provides fixtures which your
project can then easily depend or extend on, simply by referencing the
name of the particular fixture.
Fixture functions have limited visilibity which depends on where they
are defined. If they are defined on a test class, only its test methods
may use it. A fixture defined in a module can only be used
from that test module. A fixture defined in a conftest.py file
can only be used by the tests below the directory of that file.
Lastly, plugins can define fixtures which are available across all
projects.
Python, Java and many other languages support a so called xUnit_ style
for providing a fixed state, `test fixtures`_, for running tests. It
typically involves calling a autouse function ahead and a teardown
function after test execute. In 2005 pytest introduced a scope-specific
model of automatically detecting and calling autouse and teardown
functions on a per-module, class or function basis. The Python unittest
package and nose have subsequently incorporated them. This model
remains supported by pytest as :ref:`classic xunit`.
One property of xunit fixture functions is that they work implicitely
by preparing global state or setting attributes on TestCase objects.
By contrast, pytest provides :ref:`funcargs` which allow to
dependency-inject application test state into test functions or
methods as function arguments. If your application is sufficiently modular
or if you are creating a new project, we recommend you now rather head over to
:ref:`funcargs` instead because many pytest users agree that using this
paradigm leads to better application and test organisation.
However, not all programs and frameworks work and can be tested in
a fully modular way. They rather require preparation of global state
like database autouse on which further fixtures like preparing application
specific tables or wrapping tests in transactions can take place. For those
needs, pytest-2.3 now supports new **fixture functions** which come with
a ton of improvements over classic xunit fixture writing. Fixture functions:
- allow to separate different autouse concerns into multiple modular functions
- can receive and fully interoperate with :ref:`funcargs <resources>`,
- are called multiple times if its funcargs are parametrized,
- don't need to be defined directly in your test classes or modules,
they can also be defined in a plugin or :ref:`conftest.py <conftest.py>` files and get called
- are called on a per-session, per-module, per-class or per-function basis
by means of a simple "scope" declaration.
- can access the :ref:`request <request>` object which allows to
introspect and interact with the (scoped) testcontext.
- can add cleanup functions which will be invoked when the last test
of the fixture test context has finished executing.
All of these features are now demonstrated by little examples.
test modules accessing a global resource
-------------------------------------------------------
.. note::
Relying on `global state is considered bad programming practise <http://en.wikipedia.org/wiki/Global_variable>`_ but when you work with an application
that relies on it you often have no choice.
If you want test modules to access a global resource,
you can stick the resource to the module globals in
a per-module autouse function. We use a :ref:`resource factory
<@pytest.fixture>` to create our global resource::
# content of conftest.py
import pytest
class GlobalResource:
def __init__(self):
pass
@pytest.fixture(scope="session")
def globresource():
return GlobalResource()
@pytest.fixture(scope="module")
def setresource(request, globresource):
request.module.globresource = globresource
Now any test module can access ``globresource`` as a module global::
# content of test_glob.py
def test_1():
print ("test_1 %s" % globresource)
def test_2():
print ("test_2 %s" % globresource)
Let's run this module without output-capturing::
$ py.test -qs test_glob.py
FF
================================= FAILURES =================================
__________________________________ test_1 __________________________________
def test_1():
> print ("test_1 %s" % globresource)
E NameError: global name 'globresource' is not defined
test_glob.py:3: NameError
__________________________________ test_2 __________________________________
def test_2():
> print ("test_2 %s" % globresource)
E NameError: global name 'globresource' is not defined
test_glob.py:5: NameError
The two tests see the same global ``globresource`` object.
Parametrizing the global resource
+++++++++++++++++++++++++++++++++++++++++++++++++
We extend the previous example and add parametrization to the globresource
factory and also add a finalizer::
# content of conftest.py
import pytest
class GlobalResource:
def __init__(self, param):
self.param = param
@pytest.fixture(scope="session", params=[1,2])
def globresource(request):
g = GlobalResource(request.param)
def fin():
print "finalizing", g
request.addfinalizer(fin)
return g
@pytest.fixture(scope="module")
def setresource(request, globresource):
request.module.globresource = globresource
And then re-run our test module::
$ py.test -qs test_glob.py
FF
================================= FAILURES =================================
__________________________________ test_1 __________________________________
def test_1():
> print ("test_1 %s" % globresource)
E NameError: global name 'globresource' is not defined
test_glob.py:3: NameError
__________________________________ test_2 __________________________________
def test_2():
> print ("test_2 %s" % globresource)
E NameError: global name 'globresource' is not defined
test_glob.py:5: NameError
We are now running the two tests twice with two different global resource
instances. Note that the tests are ordered such that only
one instance is active at any given time: the finalizer of
the first globresource instance is called before the second
instance is created and sent to the autouse functions.

View File

@@ -1,31 +1,78 @@
.. _`pytest helpers`:
pytest builtin helpers
Pytest API and builtin fixtures
================================================
builtin pytest.* functions and helping objects
-----------------------------------------------------
This is a list of ``pytest.*`` API functions and fixtures.
You can always use an interactive Python prompt and type::
For information on plugin hooks and objects, see :ref:`plugins`.
For information on the ``pytest.mark`` mechanism, see :ref:`mark`.
For the below objects, you can also interactively ask for help, e.g. by
typing on the Python interactive prompt something like::
import pytest
help(pytest)
to get an overview on the globally available helpers.
.. currentmodule:: pytest
.. automodule:: pytest
:members:
Invoking pytest interactively
---------------------------------------------------
builtin function arguments
.. autofunction:: main
More examples at :ref:`pytest.main-usage`
Helpers for assertions about Exceptions/Warnings
--------------------------------------------------------
.. autofunction:: raises
Examples at :ref:`assertraises`.
.. autofunction:: deprecated_call
Raising a specific test outcome
--------------------------------------
You can use the following functions in your test, fixture or setup
functions to force a certain test outcome. Note that most often
you can rather use declarative marks, see :ref:`skipping`.
.. autofunction:: _pytest.runner.fail
.. autofunction:: _pytest.runner.skip
.. autofunction:: _pytest.runner.importorskip
.. autofunction:: _pytest.skipping.xfail
.. autofunction:: _pytest.runner.exit
fixtures and requests
-----------------------------------------------------
You can ask for available builtin or project-custom
:ref:`function arguments <funcargs>` by typing::
To mark a fixture function:
$ py.test --funcargs
pytestconfig
the pytest config object with access to command line opts.
.. autofunction:: _pytest.python.fixture
Tutorial at :ref:`fixtures`.
The ``request`` object that can be used from fixture functions.
.. autoclass:: _pytest.python.FixtureRequest()
:members:
.. _builtinfixtures:
.. _builtinfuncargs:
Builtin fixtures/function arguments
-----------------------------------------
You can ask for available builtin or project-custom
:ref:`fixtures <fixtures>` by typing::
$ py.test -q --fixtures
capsys
enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
@@ -36,13 +83,6 @@ You can ask for available builtin or project-custom
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
tmpdir
return a temporary directory path object
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::
@@ -54,12 +94,15 @@ 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)
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.
pytestconfig
the pytest config object with access to command line opts.
recwarn
Return a WarningsRecorder instance that provides these methods:
@@ -69,3 +112,11 @@ You can ask for available builtin or project-custom
See http://docs.python.org/library/warnings.html for information
on warning categories.
tmpdir
return a temporary directory path object
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.

View File

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

View File

@@ -4,4 +4,4 @@
Changelog history
=================================
.. include:: ../CHANGELOG
.. include:: ../../CHANGELOG

287
doc/en/conf.py Normal file
View File

@@ -0,0 +1,287 @@
# -*- coding: utf-8 -*-
#
# pytest documentation build configuration file, created by
# sphinx-quickstart on Fri Oct 8 17:54:28 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# 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 full version, including alpha/beta/rc tags.
# The short X.Y version.
version = release = "2.4.2"
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
autodoc_member_order = "bysource"
todo_include_todos = 1
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.txt'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'contents'
# General information about the project.
project = u'pytest'
copyright = u'2012, holger krekel'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
"old_*",
'*attic*',
'*/attic*',
'funcargs.txt',
'setup.txt',
'example/remoteinterp.txt',
]
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = False
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'sphinxdoc'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
html_short_title = "pytest-%s" % release
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
#html_sidebars = {'index': 'indexsidebar.html'}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
#html_additional_pages = {'index': 'index.html'}
# If false, no module index is generated.
html_domain_indices = True
# If false, no index is generated.
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 = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'pytestdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('contents', 'pytest.tex', u'pytest Documentation',
u'holger krekel, http://merlinux.eu', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
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 = [
('usage', 'pytest', u'pytest usage',
[u'holger krekel at merlinux eu'], 1)
]
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'pytest'
epub_author = u'holger krekel at merlinux eu'
epub_publisher = u'holger krekel at merlinux eu'
epub_copyright = u'2012, holger krekel et alii'
# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
#epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
# 'lib': ("http://docs.python.org/2.7library/", None),
}
def setup(app):
#from sphinx.ext.autodoc import cut_lines
#app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
app.add_description_unit('confval', 'confval',
objname='configuration value',
indextemplate='pair: %s; configuration value')

View File

@@ -9,6 +9,10 @@ Contact channels
2.0 and above). You may also peek at the `old issue tracker`_ but please
don't submit bugs there anymore.
- `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_
to post questions with the tag ``pytest``. New Questions will usually
be seen by pytest users or developers.
- `Testing In Python`_: a mailing list for Python testing tools and discussion.
- `py-dev developers list`_ pytest specific announcements and discussions.

27
doc/en/contents.txt Normal file
View File

@@ -0,0 +1,27 @@
.. _toc:
Full pytest documentation
===========================
`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
apiref
plugins
example/index
talks
develop
funcarg_compare.txt
announce/index
.. toctree::
:hidden:
changelog.txt

View File

@@ -1,4 +1,4 @@
basic test configuration
Basic test configuration
===================================
Command line options and configuration file settings
@@ -23,8 +23,9 @@ It looks for file basenames in this order::
tox.ini
setup.cfg
Searching stops when the first ``[pytest]`` section is found.
There is no merging of configuration values from multiple files. Example::
Searching stops when the first ``[pytest]`` section is found in any of
these files. There is no merging of configuration values from multiple
files. Example::
py.test path/to/testdir
@@ -59,18 +60,18 @@ progress output, you can write it into a configuration file::
From now on, running ``py.test`` will add the specified options.
builtin configuration file options
Builtin configuration file options
----------------------------------------------
.. confval:: minversion
specifies a minimal pytest version required 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]
@@ -94,7 +95,7 @@ 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``
Default patterns are ``.* _* CVS {args}``. Setting a ``norecursedir``
replaces the default. Here is an example of how to avoid
certain directories::
@@ -121,4 +122,3 @@ builtin configuration file options
and methods are considered as test modules.
See :ref:`change naming conventions` for examples.

View File

@@ -1,5 +1,5 @@
doctest integration for modules and test files
Doctest integration for modules and test files
=========================================================
By default all files matching the ``test*.txt`` pattern will
@@ -44,9 +44,9 @@ then you can just invoke ``py.test`` without command line options::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 1 items
mymodule.py .
========================= 1 passed in 0.01 seconds =========================
========================= 1 passed in 0.02 seconds =========================

View File

@@ -15,7 +15,7 @@ def test_generative(param1, param2):
assert param1 * 2 < param2
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.funcargnames:
if 'param1' in metafunc.fixturenames:
metafunc.addcall(funcargs=dict(param1=3, param2=6))
class TestFailing(object):

View File

@@ -13,9 +13,9 @@ example: specifying and selecting acceptance tests
help="run (slow) acceptance tests")
def pytest_funcarg__accept(request):
return AcceptFuncarg(request)
return AcceptFixture(request)
class AcceptFuncarg:
class AcceptFixture:
def __init__(self, request):
if not request.config.option.acceptance:
pytest.skip("specify -A to run acceptance tests")
@@ -39,7 +39,7 @@ and the actual test function example:
If you run this test without specifying a command line option
the test will get skipped with an appropriate message. Otherwise
you can start to add convenience and test support methods
to your AcceptFuncarg and drive running of tools or
to your AcceptFixture and drive running of tools or
applications and provide ways to do assertions about
the output.

View File

@@ -0,0 +1,18 @@
import pytest
@pytest.fixture("session")
def setup(request):
setup = CostlySetup()
request.addfinalizer(setup.finalize)
return setup
class CostlySetup:
def __init__(self):
import time
print ("performing costly setup")
time.sleep(5)
self.timecostly = 1
def finalize(self):
del self.timecostly

33
doc/en/example/index.txt Normal file
View File

@@ -0,0 +1,33 @@
.. _examples:
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. Also, `pytest on stackoverflow.com
<http://stackoverflow.com/search?q=pytest>`_ often comes with example
answers.
For basic examples, see
- :doc:`../getting-started` for basic introductory examples
- :ref:`assert` for basic assertion examples
- :ref:`fixtures` for basic fixture/setup examples
- :ref:`parametrize` for basic test function parametrization
- :doc:`../unittest` for basic unittest integration
- :doc:`../nose` for basic nosetests integration
The following examples aim at various use cases you might encounter.
.. toctree::
:maxdepth: 2
reportingdemo.txt
simple.txt
parametrize.txt
markers.txt
pythoncollection.txt
nonpython.txt

375
doc/en/example/markers.txt Normal file
View File

@@ -0,0 +1,375 @@
.. _`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 linux2 -- Python 2.7.3 -- pytest-2.3.2 -- /home/hpk/p/pytest/.tox/regen/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 linux2 -- Python 2.7.3 -- pytest-2.3.2 -- /home/hpk/p/pytest/.tox/regen/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.01 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(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html
@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. see http://pytest.org/latest/parametrize.html for more info and examples.
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@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 linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_server.py .
=================== 3 tests deselected by '-ksend_http' ====================
================== 1 passed, 3 deselected in 0.01 seconds ==================
And you can also run all tests except the ones that match the keyword::
$ py.test -k-send_http
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_mark_classlevel.py ..
test_server.py .
=================== 1 tests deselected by '-k-send_http' ===================
================== 3 passed, 1 deselected in 0.01 seconds ==================
Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_mark_classlevel.py ..
=================== 2 tests deselected by '-kTestClass' ====================
================== 2 passed, 2 deselected in 0.01 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):
envmarker = item.keywords.get("env", None)
if envmarker is not None:
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 linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 1 items
test_someenv.py s
======================== 1 skipped in 0.01 seconds =========================
and here is one that specifies exactly the environment needed::
$ py.test -E stage1
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 1 items
test_someenv.py .
========================= 1 passed in 0.01 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(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html
@pytest.mark.xfail(condition, reason=None, run=True): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. See http://pytest.org/latest/skipping.html
@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. see http://pytest.org/latest/parametrize.html for more info and examples.
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures
@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
.. regendoc:wipe
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
import sys
def pytest_runtest_setup(item):
g = item.keywords.get("glob", None)
if g is not None:
for info in g:
print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
sys.stdout.flush()
Let's run this without capturing output and see what we get::
$ py.test -q -s
glob args=('function',) kwargs={'x': 3}
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}
.
marking platform specific tests with pytest
--------------------------------------------------------------
.. regendoc:wipe
Consider you have a test suite which marks tests for particular platforms,
namely ``pytest.mark.osx``, ``pytest.mark.win32`` etc. and you
also have tests that run on all platforms and have no specific
marker. If you now want to have a way to only run the tests
for your particular platform, you could use the following plugin::
# content of conftest.py
#
import sys
import pytest
ALL = set("osx linux2 win32".split())
def pytest_runtest_setup(item):
if isinstance(item, item.Function):
plat = sys.platform
if plat not in item.keywords:
if ALL.intersection(item.keywords):
pytest.skip("cannot run on platform %s" %(plat))
then tests will be skipped if they were specified for a different platform.
Let's do a little test file to show how this looks like::
# content of test_plat.py
import pytest
@pytest.mark.osx
def test_if_apple_is_evil():
pass
@pytest.mark.linux2
def test_if_linux_works():
pass
@pytest.mark.win32
def test_if_win32_crashes():
pass
def test_runs_everywhere():
pass
then you will see two test skipped and two executed tests as expected::
$ py.test -rs # this option reports skip reasons
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_plat.py s.s.
========================= short test summary info ==========================
SKIP [2] /tmp/doc-exec-99/conftest.py:12: cannot run on platform linux2
=================== 2 passed, 2 skipped in 0.01 seconds ====================
Note that if you specify a platform via the marker-command line option like this::
$ py.test -m linux2
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_plat.py .
=================== 3 tests deselected by "-m 'linux2'" ====================
================== 1 passed, 3 deselected in 0.01 seconds ==================
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.

View File

@@ -0,0 +1,50 @@
"""
module containing a parametrized tests testing cross-python
serialization via the pickle module.
"""
import py, pytest
pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
@pytest.fixture(params=pythonlist)
def python1(request, tmpdir):
picklefile = tmpdir.join("data.pickle")
return Python(request.param, picklefile)
@pytest.fixture(params=pythonlist)
def python2(request, python1):
return Python(request.param, python1.picklefile)
class Python:
def __init__(self, version, picklefile):
self.pythonpath = py.path.local.sysfind(version)
if not self.pythonpath:
py.test.skip("%r not found" %(version,))
self.picklefile = picklefile
def dumps(self, obj):
dumpfile = self.picklefile.dirpath("dump.py")
dumpfile.write(py.code.Source("""
import pickle
f = open(%r, 'wb')
s = pickle.dump(%r, f)
f.close()
""" % (str(self.picklefile), obj)))
py.process.cmdexec("%s %s" %(self.pythonpath, dumpfile))
def load_and_is_true(self, expression):
loadfile = self.picklefile.dirpath("load.py")
loadfile.write(py.code.Source("""
import pickle
f = open(%r, 'rb')
obj = pickle.load(f)
f.close()
res = eval(%r)
if not res:
raise SystemExit(1)
""" % (str(self.picklefile), expression)))
print (loadfile)
py.process.cmdexec("%s %s" %(self.pythonpath, loadfile))
@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)

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.6 -- pytest-2.0.2
collecting ... collected 2 items
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 2 items
test_simple.yml .F
@@ -37,7 +37,7 @@ now execute the test specification::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.51 seconds ====================
==================== 1 failed, 1 passed in 0.03 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
@@ -51,12 +51,12 @@ 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.6 -- pytest-2.0.2 -- /home/hpk/venv/0/bin/python
platform linux2 -- Python 2.7.3 -- pytest-2.3.2 -- /home/hpk/p/pytest/.tox/regen/bin/python
collecting ... collected 2 items
test_simple.yml:1: usecase: ok PASSED
@@ -67,17 +67,17 @@ reporting in ``verbose`` mode::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
==================== 1 failed, 1 passed in 0.06 seconds ====================
==================== 1 failed, 1 passed in 0.03 seconds ====================
While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
platform linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 2 items
<YamlFile 'test_simple.yml'>
<YamlItem 'ok'>
<YamlItem 'hello'>
============================= in 0.06 seconds =============================
============================= in 0.02 seconds =============================

View File

View File

@@ -0,0 +1,280 @@
.. _paramexamples:
Parametrizing tests
=================================================
.. currentmodule:: _pytest.python
py.test allows to easily parametrize test functions.
For basic docs, see :ref:`parametrize-basics`.
In the following we provide some examples using
the builtin mechanisms.
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.fixturenames:
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
..
We run only two computations, so we see two dots.
let's run the full monty::
$ py.test -q --all
....F
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
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://pypi.python.org/pypi/testscenarios/
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, scope="class")
scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'})
class TestSampleWithScenarios:
scenarios = [scenario1, scenario2]
def test_demo1(self, attribute):
assert isinstance(attribute, str)
def test_demo2(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 linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
test_scenarios.py ....
========================= 4 passed in 0.01 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 linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 4 items
<Module 'test_scenarios.py'>
<Class 'TestSampleWithScenarios'>
<Instance '()'>
<Function 'test_demo1[basic]'>
<Function 'test_demo2[basic]'>
<Function 'test_demo1[advanced]'>
<Function 'test_demo2[advanced]'>
============================= in 0.01 seconds =============================
Note that we told ``metafunc.parametrize()`` that your scenario values
should be considered class-scoped. With pytest-2.3 this leads to a
resource-based ordering.
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
import pytest
def pytest_generate_tests(metafunc):
if 'db' in metafunc.fixturenames:
metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
class DB1:
"one database object"
class DB2:
"alternative database object"
@pytest.fixture
def 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 linux2 -- Python 2.7.3 -- pytest-2.3.2
collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[d1]'>
<Function 'test_db_initialized[d2]'>
============================= in 0.00 seconds =============================
And then when we run the test::
$ py.test -q test_backends.py
.F
================================= FAILURES =================================
_________________________ test_db_initialized[d2] __________________________
db = <conftest.DB2 instance at 0x216ccb0>
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
The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``db`` fixture function 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 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
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), ],
}
def test_equals(self, a, b):
assert a == b
def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b")
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..
================================= FAILURES =================================
________________________ TestClass.test_equals[1-2] ________________________
self = <test_parametrize.TestClass instance at 0x216e8c0>, a = 1, b = 2
def test_equals(self, a, b):
> assert a == b
E assert 1 == 2
test_parametrize.py:18: AssertionError
Indirect parametrization with multiple fixtures
--------------------------------------------------------------
Here is a stripped down real-life example of using parametrized
testing for testing serialization of objects between different python
interpreters. We define a ``test_basic_objects`` function which
is to be run with different sets of arguments for its three arguments:
* ``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 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 -rs -q multipython.py
............sss............sss............sss............ssssssssssssssssss
========================= short test summary info ==========================
SKIP [27] /home/hpk/p/pytest/doc/en/example/multipython.py:21: 'python2.8' not found

View File

@@ -0,0 +1,16 @@
import sys
import pytest
py3 = sys.version_info[0] >= 3
class DummyCollector(pytest.collect.File):
def collect(self):
return []
def pytest_pycollect_makemodule(path, parent):
bn = path.basename
if "py3" in bn and not py3 or ("py2" in bn and py3):
return DummyCollector(path, parent=parent)

View File

@@ -0,0 +1,7 @@
def test_exception_syntax():
try:
0/0
except ZeroDivisionError, e:
pass

View File

@@ -0,0 +1,7 @@
def test_exception_syntax():
try:
0/0
except ZeroDivisionError as e:
pass

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