Compare commits

...

267 Commits
1.3.2 ... 2.0.0

Author SHA1 Message Date
holger krekel
f741d6a01e fix trove classifier 2010-11-25 21:02:09 +01:00
holger krekel
b622c85bbf last changes, preparing 2.0.0 2010-11-25 20:06:42 +01:00
holger krekel
9e7ef58cfd some small pre-release updates 2010-11-25 16:36:25 +01:00
holger krekel
0efa6e5aea adding anto's projects 2010-11-25 13:00:01 +01:00
holger krekel
f6894ce550 fix some more trial/unittest related bits, particularly allow todo/skip items,
now we can run a large fraction of twisted's own test suite, mostly not those
that depend on the exact Failure() semantics (e.g. frame objects not being around
after gc.collect() but py.test kills them only slightly later anyway)
2010-11-25 15:48:59 +01:00
holger krekel
1df0eaa387 tons and tons of refinements and additions to docs 2010-11-25 12:11:10 +01:00
Benjamin Peterson
8d4c9ec343 remove invalid comment 2010-11-24 21:35:36 -06:00
Benjamin Peterson
8a527b95f2 don't try to load conf.py 2010-11-24 21:27:10 -06:00
Benjamin Peterson
b28438171b express filter as a listcomp 2010-11-24 19:18:42 -06:00
holger krekel
ab08cb2176 simplify pluginlist computation 2010-11-24 22:22:52 +01:00
holger krekel
4cb2c74159 introduce new discovery mechanism
XXX experiment with using it before introducing it or wait
for feature request
2010-11-24 22:01:04 +01:00
holger krekel
03ee8b7fe0 [mq]: doc 2010-11-24 19:02:08 +01:00
holger krekel
539f828cdd also accept non-pytrace pytest.fail() call in setup/teardown methods 2010-11-24 16:43:55 +01:00
holger krekel
c36b20b137 allow setup_method/teardown_method to be mixed into unittest cases, reshuffle tests a bit 2010-11-24 16:17:49 +01:00
holger krekel
10d4544267 teach trial support code to throw separate errors/failures for setup/call/teardown 2010-11-24 14:35:04 +01:00
Maciej Fijalkowski
ff27d299cc Finish the test 2010-11-24 15:06:40 +02:00
Maciej Fijalkowski
233baecd2d A test for trial 2010-11-24 14:54:56 +02:00
holger krekel
9be1cd8007 fix #6 : allow skip/xfail/pdb with trial by hacking the raw exception info out from trial 2010-11-24 11:48:55 +01:00
Benjamin Peterson
b40a0c18b1 python3 fixes 2010-11-23 20:32:07 -06:00
Benjamin Peterson
ac5992f9a1 some cajoling to get pytest.py to be found when it's not on path 2010-11-23 20:27:12 -06:00
Benjamin Peterson
e2068927f9 tw is unused here 2010-11-23 20:05:40 -06:00
holger krekel
840eed28be allow setup_class in unittest test cases 2010-11-24 00:23:39 +01:00
holger krekel
6ebd5f2900 improve docs 2010-11-24 00:23:22 +01:00
holger krekel
4accc4aa68 fix the py version check 2010-11-23 19:11:21 +01:00
holger krekel
11e8e5570e depend on py, not pylib distro 2010-11-23 17:21:34 +01:00
holger krekel
4fa7a2e8ce fix #128 show tracebacks for all failures and errors that haven't beed PDB-debugged 2010-11-23 16:10:47 +01:00
holger krekel
695bffc83d refine unittest support to also work with twisted trial test cases better by
introducing a slightly hackish way to report a failure upstream
2010-11-23 15:42:23 +01:00
holger krekel
6e6b0ab5d9 nice-fy error reporting of self-tests 2010-11-22 15:20:18 +01:00
holger krekel
2458c139e4 fix bug on windows 2010-11-22 12:42:48 +01:00
holger krekel
0357d3afda refine initialization and collection reporting, introduce a progress bar 2010-11-22 11:59:56 +01:00
holger krekel
bc42cf8ffb add a way to mark hooks as "tryfirst" or "trylast" to influence its position in a hook chain.
Use 'tryfirst' for capturing hooks so they can start capturing as early as possible,
including when conftests add output in runtest_setup hooks.
2010-11-21 23:17:59 +01:00
holger krekel
f456e376b9 refine tmpdir handling and docs
- clear tmpdir specified with --basetemp
- remove config.mktmp and config.getbasetemp methods
2010-11-21 17:43:18 +01:00
holger krekel
158e160823 merging and refining examples, also refining skipping documentation. 2010-11-20 21:35:55 +01:00
holger krekel
bd5a9ba392 fix: mark.* objects are now immutable as long as they are not an attribute on a function, enables usage like this::
xfail = pytest.mark.xfail

    @xfail
    def test_func1():
        pass

    @xfail(reason="123")
    def test_func2():
        pass

where previously test_func1 and test_func2 would wrongly share the same reason
because the xfail object was modified in place.
2010-11-20 20:17:38 +01:00
holger krekel
9a21a81740 add ability to use scope="class" in request.cached_setup() calls 2010-11-20 18:03:18 +01:00
holger krekel
093bef0a08 refine release announcement 2010-11-18 18:42:33 +01:00
holger krekel
eaf68c1ffd better deal with importing conftest.py with --doctest-modules and
re-enable default of "--doctest-modules" even if issued at root level
2010-11-18 15:31:58 +01:00
holger krekel
5a2295ada5 fix bare "py.test" runs without a directory by not defaulting to --doctest-modules which will virtually import everything 2010-11-18 15:19:20 +01:00
holger krekel
0325441099 add some missing files 2010-11-18 15:04:50 +01:00
holger krekel
582486d531 refine docs and docstrings, fix some small bits here and there while doing that. 2010-11-18 14:56:16 +01:00
holger krekel
a698465487 streamline docs, especially use "import pytest" and "pytest.*" in python code examples instead of "import py" and "py.test.*". 2010-11-17 22:12:16 +01:00
holger krekel
93a436542c bump version number 2010-11-17 18:27:07 +01:00
holger krekel
2a825169b2 fix doctest IDs, also fix tree traversal and remove dead code 2010-11-17 18:24:28 +01:00
holger krekel
acd286f82f run doctests in .txt/.rst files directly specified on command line irrespective of "test*.txt" pattern. 2010-11-17 14:33:21 +01:00
holger krekel
fb102a2ddb bump version and comment out ignore-testclass-if-unittest-module-feature 2010-11-17 12:21:24 +01:00
holger krekel
a298cf753d some pep8 fixes 2010-11-13 23:33:50 +01:00
holger krekel
0323c5247f perform represenation of short paths at test execution site 2010-11-13 23:33:38 +01:00
holger krekel
82ba645a2e fix skip reporting over distributed testing. if we have a "skip" report
rep.longrepr will now be a 3-tuple (path, lineno, message)
2010-11-13 21:03:28 +01:00
holger krekel
1bc444d5c8 some fixes to make cross linux/windows remote testing work again 2010-11-13 19:46:28 +01:00
holger krekel
868848a9a6 revert benjamin's change: script could be py.test.exe so we cannot
just return "python,script".  When was the actual problem occuring?
2010-11-13 11:44:58 +01:00
holger krekel
076e03e90f also un-nest test directory 2010-11-13 11:30:40 +01:00
holger krekel
929291775e flat is better than nested (cont'd):
- pytest.py is new module, making "python -m pytest" work always
- _pytest/*.py now contains core.py, hookspec and the plugins, no sub packages
2010-11-13 11:10:45 +01:00
holger krekel
2e4e9eb745 internally use pytest.* instead of `py.test.*` in many places.
make sub namespace names 'collect' and 'cmdline' available on pytest directly
2010-11-13 09:05:11 +01:00
Benjamin Peterson
323dd8a25a run subprocess py.test scripts with the python version we're testing on 2010-11-08 17:25:02 -06:00
Benjamin Peterson
d44ff035d0 add coding for py3 2010-11-08 16:48:15 -06:00
holger krekel
5bec71edc4 adapt to simplified tox indexserver definition 2010-11-08 21:13:24 +01:00
holger krekel
51fa358d8a adapt to new tox indexserver syntax 2010-11-08 17:36:45 +01:00
holger krekel
07b67d36c4 install dependency from pytest distribution, not prior. 2010-11-08 09:22:14 +01:00
holger krekel
3845ea821f avoid parsing of path objects when pytest.main(path) is called. 2010-11-07 17:37:40 +01:00
holger krekel
3a53d86988 bump version 2010-11-07 16:26:44 +01:00
holger krekel
55dff651f4 refine initilization: read config also from a "pytest.ini" file if exists
and revert earlier commandline option and group ordering change.
2010-11-07 16:10:22 +01:00
holger krekel
fefac66079 remove duplicate code, normalize relative path names to fix windows running tests 2010-11-07 12:05:32 +01:00
holger krekel
6461295ab4 probably the last major internal cleanup action: rename collection to
session which now is the root collection node.  This means that
session, collection and config objects have a more defined
relationship (previously there was no way to get from a collection
node or even from a runtest hook to the session object which
was strange).
2010-11-07 10:19:58 +01:00
holger krekel
722e20c7d6 bump version 2010-11-07 09:05:54 +01:00
holger krekel
582a2100b1 fix test, bump version 2010-11-07 07:14:50 +01:00
holger krekel
9bed4bb31c fix bug showing up on windows 2010-11-07 01:13:40 +01:00
holger krekel
d9ad2e7cce some python3 related fixes 2010-11-07 01:10:15 +01:00
Benjamin Peterson
8716b391c7 PYTHONDONTWRITEBYTECODE might not be set 2010-11-06 18:36:24 -05:00
Benjamin Peterson
107b04d462 replace with list comp 2010-11-06 18:34:00 -05:00
holger krekel
885c7ce281 some fixes for --pyargs situations and the docs, remove wrongly added test 2010-11-07 00:22:16 +01:00
holger krekel
d0ac4135a2 introduce an option that avoids discovery of classes other than unittest.TestCase in modules
importing unittest.
2010-11-06 23:45:48 +01:00
holger krekel
707775dcfa introduce new --testpkg importpath option, add more meat to draft release announcement 2010-11-06 22:17:33 +01:00
holger krekel
1a7f2e77e8 show return values of hooks more explicitely 2010-11-06 20:12:45 +01:00
holger krekel
b3628daa62 test and fix tracing indentation in case of exceptions 2010-11-06 20:06:32 +01:00
holger krekel
1899443744 show traces for all hook invocations not just "path/node" based ones 2010-11-06 19:46:24 +01:00
holger krekel
fcebf4f557 some more improvements and updates to docs, add release announcements 2010-11-06 11:38:53 +01:00
holger krekel
6dac77433e majorly refactor collection process
- get rid of py.test.collect.Directory alltogether.
- introduce direct node.nodeid attribute
- remove now superflous attributes on collect and test reports
2010-11-06 09:58:04 +01:00
holger krekel
f181c70d8e add indent facility to tracing 2010-11-06 09:05:17 +01:00
holger krekel
d108235095 implement and document new invocation mechanisms, see doc/usage.txt
also rename pytest._core to pytest.main for convenience.
2010-11-05 23:37:31 +01:00
holger krekel
6a734efe44 introduce a minimal tag-based tracer, to be extended if needed, strike pytest_trace hook. 2010-11-05 23:37:31 +01:00
holger krekel
132eeeeade update issues 2010-11-05 23:37:31 +01:00
holger krekel
99dfb8ad65 reverse options ordering in Parser class instead of on PluginManager 2010-11-05 23:37:31 +01:00
holger krekel
bb732a4e75 add "linelist" type for ini-files 2010-11-05 23:37:31 +01:00
holger krekel
b1e4301457 document and refine py.test.fail helper and strike superflous ExceptionFailure class
refine builtin organisation and start a new doc
2010-11-05 23:37:31 +01:00
holger krekel
49319ba729 some more refinements to docs 2010-11-05 23:37:25 +01:00
holger krekel
fed8f19156 introduce norecursedirs config option, remove recfilter() 2010-11-04 23:21:26 +01:00
holger krekel
5251653fc3 remove pytest_report_iteminfo hook, i strongly guess nobody needs or uses it. 2010-11-04 23:21:23 +01:00
holger krekel
28d51e26a0 remove imperative xfail, this test passes 2010-11-03 08:09:13 +01:00
holger krekel
c18cca9d54 massive documentation refinements 2010-11-02 00:53:53 +01:00
holger krekel
7d495cc250 majorly changing the unittest compatibility code, calling TestCase(name)(result) 2010-11-01 23:08:16 +01:00
holger krekel
53d1cfc3a1 allow unregistration by name 2010-11-01 09:20:58 +01:00
holger krekel
32ac7a7c6e rename addargs to addopts, make adding of opts configurable 2010-11-01 08:55:14 +01:00
holger krekel
85c24b7fa1 some test fixes and refinements 2010-11-01 08:16:10 +01:00
holger krekel
cf8dd64703 slightly simplify collection node init 2010-11-01 01:01:31 +01:00
holger krekel
c3ec2718a2 fix tests by using less likely existing import names 2010-11-01 00:38:44 +01:00
holger krekel
209140fea0 simplify session object and rename some more hooks, not exposed/released yet 2010-11-01 00:27:12 +01:00
holger krekel
5616874823 streamline some hook docs and option handling, remove cruft bits, fix doc links 2010-10-31 23:28:31 +01:00
holger krekel
14b2b128c2 update issues 2010-10-31 21:47:26 +01:00
holger krekel
f1e3dde2ec bump version, add a bit to changelog 2010-10-31 19:53:11 +01:00
holger krekel
8871ca5bfa introduce "-q" option which decreases verbosity and basically leads to a unittest/nosetest-style "." output
add it in an ini file like this:

    [pytest]
    addargs=-q

and you get that by default.
2010-10-31 19:51:16 +01:00
holger krekel
bb50ec89a9 remove restdoc plugin which now lives as pytest-restdoc on bitbucket,
and be easily included in a project now (like PyPy which still needs it)
2010-10-31 19:04:22 +01:00
holger krekel
868670b5f2 fix --help output for ini-options 2010-10-31 19:02:38 +01:00
holger krekel
23f8d8bce7 allow modules/conftest files specify dotted import paths for loading plugins 2010-10-31 19:01:46 +01:00
holger krekel
03924d205d show pytest.__version__ not pylib 2010-10-31 18:57:44 +01:00
holger krekel
35969e13ae remove feature deprecated prior even to 1.0 2010-10-31 18:46:10 +01:00
holger krekel
c92365f8dd change status 2010-10-31 18:18:24 +01:00
holger krekel
f73ab23003 merge trunk 2010-10-31 18:17:08 +01:00
holger krekel
7138df5b51 bump version
--HG--
branch : trunk
2010-10-31 18:06:11 +01:00
holger krekel
bc574f4d94 remove superflous collect_by_name, and improve some docs
--HG--
branch : trunk
2010-10-31 18:01:33 +01:00
holger krekel
b6ec5a575d get option settings from ini-file. make getting configuration options from conftest.py only an internal feature.
--HG--
branch : trunk
2010-10-31 17:41:58 +01:00
holger krekel
1280041f0c add and document new parser.addini(name, description) method to describe
ini-values. Also document the parser object with its public methods.

--HG--
branch : trunk
2010-10-30 19:23:50 +02:00
holger krekel
2d8bcbdf55 document "setup.py test" to use genscript'ed version.
--HG--
branch : trunk
2010-10-28 09:29:56 +02:00
holger krekel
c9e629c870 remove old ways to set option defaults, relying on global setup.cfg or tox.ini files now.
revamp py.test --help-config

--HG--
branch : trunk
2010-10-27 22:29:01 +02:00
holger krekel
b86b1628bb introduce reading of setup.cfg / ini-style configuration files
rename internal config.Error to pytest.UsageError

--HG--
branch : trunk
2010-10-27 19:35:27 +02:00
holger krekel
f7b4f70a16 make examples less nested for now
--HG--
branch : trunk
2010-10-27 17:36:27 +02:00
holger krekel
65675d5a95 link to PyYAML parser
--HG--
branch : trunk
2010-10-27 15:14:12 +02:00
holger krekel
33951d48f1 a new example for doing non-python extensions
--HG--
branch : trunk
2010-10-27 14:52:28 +02:00
holger krekel
3ea082f63f close branch "trunk"
--HG--
branch : trunk
2010-10-26 10:33:31 +02:00
holger krekel
8248b5e31d changing default branch name to "default" instead of historic "trunk" 2010-10-26 10:32:36 +02:00
holger krekel
29222dffc9 add a genscript target
--HG--
branch : trunk
2010-10-26 10:09:41 +02:00
holger krekel
90c1084a88 add --lsof self-testing option
--HG--
branch : trunk
2010-10-26 09:11:53 +02:00
holger krekel
34c5c5d878 fix capturing: properly finalize self-testing of py.test, reducing number of open files during the
test run.

--HG--
branch : trunk
2010-10-25 23:09:24 +02:00
holger krekel
5fc87acf9b re-introduce compatibility attributes on collection nodes to keep compatible with code like::
def pytest_collect_file(path, parent):
        ... parent.Module(...)

--HG--
branch : trunk
2010-10-25 23:09:21 +02:00
holger krekel
4480401119 allow unittest test functions to work with the "pytestmark" mechanism
by refactoring mark/keyword handling and initialization

--HG--
branch : trunk
2010-10-25 23:08:56 +02:00
holger krekel
a6f10a6d80 unify collection for finding items and for finding initial nodes.
--HG--
branch : trunk
2010-10-25 23:08:41 +02:00
holger krekel
603ff3a64f also check for stderr, add changelog entry
--HG--
branch : trunk
2010-10-24 23:43:35 +02:00
holger krekel
b4210f3ae0 fix issue93 - hide output of code in early-loaded conftest files
--HG--
branch : trunk
2010-10-24 23:26:14 +02:00
holger krekel
f466d35771 add example for catching exceptions, simplify install doc
--HG--
branch : trunk
2010-10-24 21:55:27 +02:00
holger krekel
6cddd7e793 bump version
--HG--
branch : trunk
2010-10-23 15:42:53 +02:00
holger krekel
c2c8471f3d fix broken tests / last checkin
--HG--
branch : trunk
2010-10-22 12:04:50 +02:00
holger krekel
1999180dfd xpass tests don't cause non-zero exit codes
--HG--
branch : trunk
2010-10-22 12:00:17 +02:00
holger krekel
76a1bf391e fix some doc bits
--HG--
branch : trunk
2010-10-22 10:09:26 +02:00
holger krekel
47e56e0dee streamline tox ini
--HG--
branch : trunk
2010-10-21 16:10:46 +02:00
holger krekel
56afcfc9f3 make safer filenames
--HG--
branch : trunk
2010-10-21 16:10:37 +02:00
holger krekel
dc5e2f5ed3 bump version, fix readme
--HG--
branch : trunk
2010-10-21 12:18:10 +02:00
holger krekel
e3f48a81c5 skip tests that want to invoke py.test without proper installation
--HG--
branch : trunk
2010-10-20 22:10:35 +02:00
holger krekel
5701ffa8d6 also fix py31 tox.ini entry
--HG--
branch : trunk
2010-10-20 21:08:21 +02:00
holger krekel
87e9cb9bec reconfig tox.ini to care use testrun.org for installation of sdist
--HG--
branch : trunk
2010-10-20 21:01:01 +02:00
holger krekel
6411fa69bc fix example good -> bad (thanks frank)
--HG--
branch : trunk
2010-10-17 11:11:32 +02:00
holger krekel
c229e5459f fix CHANGELOG
--HG--
branch : trunk
2010-10-17 00:30:24 +02:00
Ronny Pfannschmidt
039037701a use pyfuncitem name for tmpdir in order to take generative test id into account
--HG--
branch : trunk
2010-10-17 00:24:59 +02:00
holger krekel
80778eb3ae add an example for testing from an app if a test run is ongoing
--HG--
branch : trunk
2010-10-17 00:10:22 +02:00
holger krekel
170346654c turn most funcargrequest attributes into properties and document them.
--HG--
branch : trunk
2010-10-16 23:59:38 +02:00
Ronny Pfannschmidt
5d798feaf0 fix genscript by copying the new implementation from the genscript package
--HG--
branch : trunk
2010-10-16 03:10:14 +02:00
Ronny Pfannschmidt
2a579217b8 alias function keywords to funcarg request keywords
--HG--
branch : trunk
2010-10-16 02:00:05 +02:00
holger krekel
976549cc88 fixing jython specs for testing
--HG--
branch : trunk
2010-10-15 21:12:06 +02:00
holger krekel
bf1cd25831 use testrun indexserver, remove hudson artifact handling
--HG--
branch : trunk
2010-10-15 20:35:21 +02:00
holger krekel
5a0ef7355e adding a small bench script to see where time is spend in the hook architecture
--HG--
branch : trunk
2010-10-15 16:36:25 +02:00
holger krekel
1b7d2b07ab some fixes to packaging and urls
--HG--
branch : trunk
2010-10-15 00:54:25 +02:00
holger krekel
8853c5bdef merge install and basic usage into getting-started.txt
add an assert1.txt

--HG--
branch : trunk
2010-10-14 01:25:09 +02:00
holger krekel
2e4391d28e major refinements to documentation and examples
--HG--
branch : trunk
2010-10-13 19:30:00 +02:00
holger krekel
9925ac883e refine and document conftest loading and handling.
--HG--
branch : trunk
2010-10-13 18:45:07 +02:00
holger krekel
f3fb91e296 remove all deprecated functionality and tests
--HG--
branch : trunk
2010-10-13 18:41:53 +02:00
holger krekel
17719b99a1 select tests by call-id, add and refine documentation around it
--HG--
branch : trunk
2010-10-13 12:26:14 +02:00
holger krekel
3a5d28f3fe removed unnccessary indirections in the PluginManager,
also fixed a bug in _core.varnames(), which probably considerably
speeds up hook calls.

--HG--
branch : trunk
2010-10-13 11:12:27 +02:00
holger krekel
270deb015e assert py.__version__ to be 2.0 at least to avoid other weird errors
--HG--
branch : trunk
2010-10-12 21:59:15 +02:00
holger krekel
22282eedb9 remove superflous directory
--HG--
branch : trunk
2010-10-12 21:39:37 +02:00
holger krekel
04c41cb672 shift config initialization to own "config" plugin
--HG--
branch : trunk
2010-10-12 15:34:32 +02:00
holger krekel
7453fc107c merge _pytest into pytester self-testing plugin
--HG--
branch : trunk
2010-10-12 13:10:39 +02:00
holger krekel
07c835fdf3 merge keyword into mark plugin
--HG--
branch : trunk
2010-10-12 13:05:29 +02:00
holger krekel
9555d427ae remove non-working pylint plugin
--HG--
branch : trunk
2010-10-12 13:02:48 +02:00
holger krekel
6631447161 merge config, pluginmanager, main into one file
--HG--
branch : trunk
2010-10-12 12:54:32 +02:00
holger krekel
6efc6dcb62 move pytest/collect.py to pytest/plugin/session.py - approaching
total py.test pluginizations ...

--HG--
branch : trunk
2010-10-12 12:19:53 +02:00
holger krekel
9b4cca2bf4 avoid early import of doctest
--HG--
branch : trunk
2010-10-12 12:19:00 +02:00
holger krekel
251fb0ab1c various documentation related refinements
--HG--
branch : trunk
2010-10-12 10:59:04 +02:00
holger krekel
a82a6bb058 regen docs, add a specific test_collectonly.py example
--HG--
branch : trunk
2010-10-11 13:38:16 +02:00
holger krekel
b5b8e5f0c2 advance customization docs, enhance docstrings, add more reference object docs.
--HG--
branch : trunk
2010-10-11 12:54:28 +02:00
holger krekel
431a582132 regen and extend examples a bit with regendoc.py
--HG--
branch : trunk
2010-10-11 10:07:14 +02:00
holger krekel
aa70d9073c rename last test files
--HG--
branch : trunk
2010-10-11 08:10:55 +02:00
holger krekel
eee0e14334 internally switch to pytest.plugin.NAME instead of pytest.plugin.pytest_NAME
--HG--
branch : trunk
2010-10-11 01:14:40 +02:00
holger krekel
ce3b260b0b start documenting hooks, improve hookspec docstrings a bit
--HG--
branch : trunk
2010-10-11 00:49:54 +02:00
holger krekel
c614adcf48 move and consolidate some more plugin docs
--HG--
branch : trunk
2010-10-11 00:14:32 +02:00
holger krekel
d89d0e8b26 fix format of examples so that ronny's regendoc detects it.
--HG--
branch : trunk
2010-10-10 23:54:00 +02:00
holger krekel
4ee3831ac9 start reorganizing docs, write more docs, shift plugin docs, to proper documentation,
use sphinx, remove old docs ... work in progress.

--HG--
branch : trunk
2010-10-10 23:45:45 +02:00
holger krekel
854f6a98ae remove some more cruft
--HG--
branch : trunk
2010-10-10 15:52:13 +02:00
holger krekel
7a461a2f3b fix dep
--HG--
branch : trunk
2010-10-10 14:46:57 +02:00
holger krekel
652d0ca636 fix tox.ini and dependencies, various fixes all around, tests pass.
--HG--
branch : trunk
2010-10-10 13:48:49 +02:00
holger krekel
32fce34825 move config to _config
--HG--
branch : trunk
2010-10-10 13:48:49 +02:00
holger krekel
51bb0f53c5 move session.py and collect.py to a unified pytest_session.py plugin.
--HG--
branch : trunk
2010-10-10 13:48:48 +02:00
holger krekel
d1aff902d5 remove pylib things and move things to new pytest namespace
--HG--
branch : trunk
2010-10-07 11:59:00 +02:00
holger krekel
f488da5cc8 merge parseopt into config module
--HG--
branch : trunk
2010-10-07 13:26:07 +02:00
holger krekel
98bdf022d3 merge conftesthandle into config.py
--HG--
branch : trunk
2010-10-07 11:51:58 +02:00
Ronny Pfannschmidt
09a9ce1da1 fix and test a unbound local in _diff_text of the assertion plugin
--HG--
branch : trunk
2010-10-09 07:35:28 +02:00
holger krekel
6b0db18eca two fixes for Jython
--HG--
branch : trunk
2010-10-07 08:55:44 +02:00
holger krekel
253c173a88 skip attribute tests on <(2,6)
--HG--
branch : trunk
2010-10-06 19:57:14 +02:00
holger krekel
7e3ff100f6 add to assertion related changelog
--HG--
branch : trunk
2010-10-06 19:46:31 +02:00
Floris Bruynooghe
ec5ea5c05e Show final value first when explaining an attribute
Then show the expansion as a "where" part of the explanation.

--HG--
branch : trunk
2010-10-06 18:20:09 +01:00
holger krekel
c62ed0cd93 fix changelog
--HG--
branch : trunk
2010-10-06 16:26:55 +02:00
holger krekel
eccc2a868c fix issue126 : introduce py.test.set_trace() to allow dropping to
interactive debugging even when py.test is configured to capture output.
If you like you can override pdb.set_trace by default like this:

    # content of conftest.py
    def pytest_configure():
        import py, pdb
        pdb.set_trace = py.test.set_trace

--HG--
branch : trunk
2010-10-06 14:48:24 +02:00
holger krekel
60a9b60634 remove unccessary code from pdb plugin
--HG--
branch : trunk
2010-10-06 11:55:12 +02:00
antocuni
94c2fd4033 fix the annoying interaction between "pdb.set_trace()" and --pdb. The problem
is that pdb raises BdbQuit on exit, which is then caught by --pdb, showing an
unwanted pdb prompt.  Fix it by making --pdb to ignore BdbQuit

--HG--
branch : trunk
2010-10-06 14:28:06 +02:00
holger krekel
fe54762b93 fix tests to avoid pyc-caching and skip python2.4 which doesn't support "python -m" on packages.
--HG--
branch : trunk
2010-10-06 09:40:14 +02:00
holger krekel
89c53de084 remove unused args
--HG--
branch : trunk
2010-10-05 17:56:37 +02:00
holger krekel
eead8f9ab4 fix issue123 - new "python -m py.test" invocation.
--HG--
branch : trunk
2010-10-05 17:52:32 +02:00
holger krekel
7c6e47f715 fix issue124 - make test reporting more resilient against tests changing FD 1
--HG--
branch : trunk
2010-10-05 17:21:50 +02:00
holger krekel
cebcdb83cf refine printing of exceptions via the pluginmanager.
if there is no pytest_internalerror() hook acknowledging
receival we print the exception to sys.stderr.  This helps
to see issues when there are failures in TerminalReporter
initialization.

--HG--
branch : trunk
2010-10-05 17:21:41 +02:00
holger krekel
a054b63bac introduce py.builtin.any
--HG--
branch : trunk
2010-10-05 17:21:27 +02:00
holger krekel
6892dc47a3 use repr() to print extra / differing values in assertion comparison failures
and guard against failures in detail-representations

--HG--
branch : trunk
2010-10-04 18:49:30 +02:00
holger krekel
f6da7ea0a5 remove config.getinitialnodes() method that was only used for testing method after the refactoring.
--HG--
branch : trunk
2010-10-04 16:55:03 +02:00
holger krekel
29051458fc fix issue 109 - sibling conftest.py files shall not be loaded.
also simplify / refine tests a bit.

--HG--
branch : trunk
2010-10-04 16:19:01 +02:00
holger krekel
4eb45dab08 small simplification and shuffling of python tests, no content change
--HG--
branch : trunk
2010-10-04 11:04:15 +02:00
holger krekel
939a53c436 fix a problem and make a note about pytest_nose calling setup/teardown functions
--HG--
branch : trunk
2010-10-03 11:17:37 +02:00
holger krekel
a6003ac332 some fixes after the merge
--HG--
branch : trunk
2010-10-02 20:49:24 +02:00
holger krekel
63bb9efd29 merge heads
--HG--
branch : trunk
2010-10-02 19:36:15 +02:00
holger krekel
77cacb99ee to better match the naming of the corresponding AST (and in case
we want to add more customizations later)
rename pytest_assert_binrepr -> pytest_assertrepr_compare
rename binrepr -> reprcompare

--HG--
branch : trunk
2010-10-02 19:00:47 +02:00
holger krekel
1ff173baee refactor assert interpretation to invoke a simple callable
and let the assertion plugin handle the hook invocation
and its multi-results and also pass in an (optional) test config
object to the hook. Add and refactor also a few tests.

--HG--
branch : trunk
2010-10-02 18:47:39 +02:00
holger krekel
b56d3c223d merge Floris branch and skip interpret-tests on python2.4
--HG--
branch : trunk
2010-10-02 16:15:02 +02:00
Floris Bruynooghe
cd5676adc4 Truncate the text passed to difflib where possible
This stops difflib from printing many lines which had no change in
them anyway.  It also avoids a bug in difflib which fails or hangs
when there are many trailing lines which are all identical.

--HG--
branch : trunk
2010-09-30 23:15:41 +01:00
holger krekel
e2c11f1ddb fix python3 issues, add py32 environment
--HG--
branch : trunk
2010-09-28 17:37:20 +02:00
holger krekel
81ec29a597 fix python3 bugs
--HG--
branch : trunk
2010-09-28 16:38:46 +02:00
holger krekel
88915aa57d fix tox.ini invocation
--HG--
branch : trunk
2010-09-28 15:58:23 +02:00
holger krekel
e2e01a8585 refine reporting a bit, show only "dots" for distributed testing
--HG--
branch : trunk
2010-09-28 15:53:10 +02:00
holger krekel
a60e470573 fix a collection bug where a::b::c could not be resolved properly if
there are multiple 'b' nodes.

--HG--
branch : trunk
2010-09-28 15:24:36 +02:00
holger krekel
f779d3f863 rework session instantiation and exitstatus handling
--HG--
branch : trunk
2010-09-28 12:59:48 +02:00
holger krekel
2718fccfa0 make "tools-on-path" the default and add new random fnmatch-matching method
--HG--
branch : trunk
2010-09-27 20:48:30 +02:00
holger krekel
a2fe6714f8 implement pytest_runtest_logstart(nodeid, location) hook
factor out a NodeInfo helper, and streamline terminal printing a bit

--HG--
branch : trunk
2010-09-26 16:23:45 +02:00
holger krekel
1c020c3d32 shift reporting info generation away from terminal reporting time, simplify code.
also get rid of redundant 'shortrepr' on collect/test reports
and rename reportinfo to "location" in some places

--HG--
branch : trunk
2010-09-26 16:23:44 +02:00
holger krekel
7d1585215d clean up and simplify startup test protocols and objects
introduce some new experimental hooks pytest_runtest_mainloop
to better integrate distributed testing

--HG--
branch : trunk
2010-09-26 16:23:43 +02:00
holger krekel
2cf22e3124 shift all python related testing functioanlity to a dedicated
pytest_python

plugin which incorporates pytest's logic of python function testing (including funcargs).

--HG--
branch : trunk
2010-09-25 18:23:26 +02:00
Floris Bruynooghe
c3166ee84a Fix bug when the right list was longer then the left
Thanks to Holger for finding this.

--HG--
branch : trunk
2010-09-22 18:52:07 +01:00
Floris Bruynooghe
56b955dfb5 Make pytest_assert_binrepr work on python3 too
--HG--
branch : trunk
2010-09-22 18:42:04 +01:00
Floris Bruynooghe
4b2cb3acbe Merge tip from py-trunk.
--HG--
branch : trunk
2010-09-22 18:14:59 +01:00
Floris Bruynooghe
ca84a5e8e0 Rename pytest_assert_compare to pytest_assert_binrepr
Holger prefers to only have one hook and it also turns out that "in"
is actually a ast.Compare node as well too.

This also modifies the pytest_assert_binrepr hook slightly so that
it's more accomodating to other operators then just compare (i.e.
don't bail out as soon as the types of the operands differ).

--HG--
branch : trunk
2010-09-22 00:56:39 +01:00
Floris Bruynooghe
b86207a6c1 Don't load py.test.config inside py._code._assertionnew
Loading py.test.config triggers py.test initialisation while py.code
should stay independent of py.test.  By adding the hook as an
attribute to py.test AssertionError py.code can get access to the
hooks only when py.test is loaded already.

--HG--
branch : trunk
2010-09-22 00:26:12 +01:00
Floris Bruynooghe
abab8f6f63 Move all tests to test_pytest_assertion
The py.code code is independent of any py.test specifics so we should
avoid creating dependencies on py.test in those parts.

--HG--
branch : trunk
2010-09-18 13:03:28 +01:00
Floris Bruynooghe
0af90e0962 Add specialised explanations to the demo
This currently breaks the test_failuers.py example as that file counts
the number of failures in the demo.  But this demo isn't fixed yet so
we'll leave it for now.

--HG--
branch : trunk
2010-09-16 01:07:53 +01:00
Floris Bruynooghe
58169edc8e Add set comparison
Also add a (too) simple mechanism too truncate too long explanations.

--HG--
branch : trunk
2010-09-16 01:06:07 +01:00
holger krekel
e2683f4538 refactor all collection related logic
- drop all pickling support (for now)
- perform collection completely ahead of test running (no iterativity)
- introduce new collection related hooks
- shift all keyword-selection code to pytest_keyword plugin
- simplify session object
- besides: fix issue88

--HG--
branch : trunk
2010-09-15 10:30:50 +02:00
holger krekel
350ebbd9ad Added tag 1.3.4 for changeset 90fffd35373e
--HG--
branch : trunk
2010-09-14 17:35:17 +02:00
holger krekel
bb6e9848b3 recreated plugin docs
--HG--
branch : trunk
2010-09-14 17:35:01 +02:00
holger krekel
489faf26f2 Added tag 1.3.4 for changeset 79ef63777051
--HG--
branch : trunk
2010-09-14 16:54:41 +02:00
holger krekel
9ca7ed647b finalize release announce and changelog
--HG--
branch : trunk
2010-09-14 16:36:34 +02:00
holger krekel
79734420df some small doc fixes
--HG--
branch : trunk
2010-09-14 16:18:06 +02:00
Ronny Pfannschmidt
b81e48507c introduce py.builtin._sysex as alias for the special exceptions, fixes #115
--HG--
branch : trunk
2010-09-14 16:12:50 +02:00
holger krekel
04b3b9a3da preliminary release announcement
--HG--
branch : trunk
2010-09-14 15:46:30 +02:00
holger krekel
af412d993c simplify and fix installation instructions particularly for windows (fixes #111)
and bump version to 1.3.4

--HG--
branch : trunk
2010-09-14 15:43:00 +02:00
Floris Bruynooghe
6fb56443a9 Split the tests between the core and plugin
The tests for _assertionnew are much better, the ones for
pytest_assert_compare() are still not great.

--HG--
branch : trunk
2010-09-08 22:21:52 +01:00
holger krekel
6f40441ef8 fixing test for python2.4 (thanks ronny)
--HG--
branch : trunk
2010-09-08 18:29:26 +02:00
holger krekel
7903fbb8ce applied ronny's patch, fixes #116
--HG--
branch : trunk
2010-09-08 16:51:46 +02:00
Ronny Pfannschmidt
2b59200786 implement and naively test the native traceback style
--HG--
branch : trunk
2010-09-08 12:00:36 +02:00
Floris Bruynooghe
2b3ac35780 Merge py-trunk tip
--HG--
branch : trunk
2010-09-07 22:45:19 +01:00
holger krekel
c17bb32f70 patch from flub to allow callable objects as hook implementations
--HG--
branch : trunk
2010-09-07 10:03:11 +02:00
Floris Bruynooghe
f194b16a09 Don't import difflib and pprint up-front
Builtin plugins need to keep their import time to a minimum.
Therefore it's better to delay importing till you really need it, i.e.
use py.std.* in this case.

--HG--
branch : trunk
2010-09-06 19:46:58 +01:00
Floris Bruynooghe
cd013746cf Initial patch as sent to py-dev
With a small but disasterous typo fixed though.

--HG--
branch : trunk
2010-09-06 19:35:17 +01:00
holger krekel
95bafbccd1 fix issue116 : --doctestmodules also works in the presence of __init__.py files, done by fixing the underlyingly used path.pyimport()
--HG--
branch : trunk
2010-09-04 09:21:35 +02:00
Ed Singleton
a2f9fbb178 Added a test and fix for nose compatible setup/teardown functions so that even less errors are ignored
--HG--
branch : trunk
2010-09-03 11:32:12 +01:00
Ed Singleton
f814cb5346 Added a test and fix for nose compatible setup/teardown functions that are partials, and so errors are not ignored
--HG--
branch : trunk
2010-09-03 11:27:47 +01:00
Ed Singleton
b690290c3f Whitespace normalisation inside funcs in test_pytest_nose.py
--HG--
branch : trunk
2010-09-03 10:09:41 +01:00
Ed Singleton
c542806396 Whitespace normalisation between funcs in test_pytest_nose.py
--HG--
branch : trunk
2010-09-03 10:07:17 +01:00
Ed Singleton
faf0fe8887 Added a test and fix for nose compatible setup/teardown functions that contain a variable
--HG--
branch : trunk
2010-09-03 10:04:45 +01:00
holger krekel
d8fcc96563 committed a xfailing test for sibling conftests
--HG--
branch : trunk
2010-08-02 16:39:36 +02:00
holger krekel
31c91796c6 bumping version number to 1.3.4a1
--HG--
branch : trunk
2010-08-01 20:44:11 +02:00
holger krekel
8f2b0d0889 test and fix for apipkg (also available in apipkg default branch)
--HG--
branch : trunk
2010-08-01 20:43:02 +02:00
holger krekel
ba1f6336af add 1.3.3 release "announcement"
--HG--
branch : trunk
2010-08-01 15:21:59 +02:00
holger krekel
782430c614 Added tag 1.3.3 for changeset c59d3fa8681a
--HG--
branch : trunk
2010-07-31 01:07:12 +02:00
holger krekel
3654592959 bump version, prepare 1.3.3
--HG--
branch : trunk
2010-07-30 15:06:50 +02:00
holger krekel
6aab9bcfb9 another whitespace-correction commit
--HG--
branch : trunk
2010-07-30 15:05:24 +02:00
holger krekel
efeae72509 fixes issue113 - assertion represenation issue
--HG--
branch : trunk
2010-07-29 12:55:39 +02:00
holger krekel
92bfb58798 fix bug on writing out objects with terminalwriter on windows
--HG--
branch : trunk
2010-07-29 11:32:24 +02:00
holger krekel
74523a9d09 avoid loading conftest files which are exactly the same content as a previously loaded conftest file
--HG--
branch : trunk
2010-07-29 11:22:16 +02:00
holger krekel
e5d09b771a fix windows32 terminal coloring
--HG--
branch : trunk
2010-07-28 17:33:26 +02:00
holger krekel
677f7c0a6a remove trailing whitespace everywhere
--HG--
branch : trunk
2010-07-26 21:15:15 +02:00
holger krekel
1693b9c407 update issues
--HG--
branch : trunk
2010-07-26 19:54:34 +01:00
holger krekel
b14f8505d0 fix test on python2.4
--HG--
branch : trunk
2010-07-26 13:34:59 +02:00
holger krekel
ed8e24312c fix terminal dimension detection to work with stdout
--HG--
branch : trunk
2010-07-26 13:13:10 +02:00
holger krekel
71cb42d263 updating some feature descriptions
--HG--
branch : trunk
2010-07-08 20:13:39 +02:00
holger krekel
74d3acea02 Added tag 1.3.2 for changeset 3bff44b188a7
--HG--
branch : trunk
2010-07-08 17:20:55 +02:00
376 changed files with 15373 additions and 32981 deletions

View File

@@ -15,9 +15,10 @@ syntax:glob
*.orig
*~
doc/_build
build/
dist/
py.egg-info
*.egg-info
issue/
env/
3rdparty/

View File

@@ -26,3 +26,8 @@ eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4

537
CHANGELOG
View File

@@ -1,12 +1,81 @@
Changes between 1.3.1 and 1.3.2
==================================================
Changes between 1.3.4 and 2.0.0dev0
----------------------------------------------
New features
- pytest-2.0 is now its own package and depends on pylib-2.0
- new ability: python -m pytest / python -m pytest.main ability
- new python invcation: pytest.main(args, plugins) to load
some custom plugins early.
- try harder to run unittest test suites in a more compatible manner
by deferring setup/teardown semantics to the unittest package.
also work harder to run twisted/trial and Django tests which
should now basically work by default.
- introduce a new way to set config options via ini-style files,
by default setup.cfg and tox.ini files are searched. The old
ways (certain environment variables, dynamic conftest.py reading
is removed).
- add a new "-q" option which decreases verbosity and prints a more
nose/unittest-style "dot" output.
- fix issue135 - marks now work with unittest test cases as well
- fix issue126 - introduce py.test.set_trace() to trace execution via
PDB during the running of tests even if capturing is ongoing.
- fix issue123 - new "python -m py.test" invocation for py.test
(requires Python 2.5 or above)
- fix issue124 - make reporting more resilient against tests opening
files on filedescriptor 1 (stdout).
- fix issue109 - sibling conftest.py files will not be loaded.
(and Directory collectors cannot be customized anymore from a Directory's
conftest.py - this needs to happen at least one level up).
- introduce (customizable) assertion failure representations and enhance
output on assertion failures for comparisons and other cases (Floris Bruynooghe)
- nose-plugin: pass through type-signature failures in setup/teardown
functions instead of not calling them (Ed Singleton)
- remove py.test.collect.Directory (follows from a major refactoring
and simplification of the collection process)
- majorly reduce py.test core code, shift function/python testing to own plugin
- fix issue88 (finding custom test nodes from command line arg)
- refine 'tmpdir' creation, will now create basenames better associated
with test names (thanks Ronny)
- "xpass" (unexpected pass) tests don't cause exitcode!=0
- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages
- fix issue93 stdout/stderr is captured while importing conftest.py
- fix bug: unittest collected functions now also can have "pytestmark"
applied at class/module level
- add ability to use "class" level for cached_setup helper
- fix strangeness: mark.* objects are now immutable, create new instances
Changes between 1.3.3 and 1.3.4
----------------------------------------------
- fix issue111: improve install documentation for windows
- fix issue119: fix custom collectability of __init__.py as a module
- fix issue116: --doctestmodules work with __init__.py files as well
- fix issue115: unify internal exception passthrough/catching/GeneratorExit
- fix issue118: new --tb=native for presenting cpython-standard exceptions
Changes between 1.3.2 and 1.3.3
----------------------------------------------
- fix issue113: assertion representation problem with triple-quoted strings
(and possibly other cases)
- make conftest loading detect that a conftest file with the same
content was already loaded, avoids surprises in nested directory structures
which can be produced e.g. by Hudson. It probably removes the need to use
--confcutdir in most cases.
- fix terminal coloring for win32
(thanks Michael Foord for reporting)
- fix weirdness: make terminal width detection work on stdout instead of stdin
(thanks Armin Ronacher for reporting)
- remove trailing whitespace in all py/text distribution files
Changes between 1.3.1 and 1.3.2
----------------------------------------------
New features
++++++++++++++++++
- fix issue103: introduce py.test.raises as context manager, examples::
with py.test.raises(ZeroDivisionError):
with py.test.raises(ZeroDivisionError):
x = 0
1 / x
@@ -15,10 +84,10 @@ New features
# you may do extra checks on excinfo.value|type|traceback here
(thanks Ronny Pfannschmidt)
(thanks Ronny Pfannschmidt)
- Funcarg factories can now dynamically apply a marker to a
test invocation. This is for example useful if a factory
- Funcarg factories can now dynamically apply a marker to a
test invocation. This is for example useful if a factory
provides parameters to a test which are expected-to-fail::
def pytest_funcarg__arg(request):
@@ -28,75 +97,75 @@ New features
def test_function(arg):
...
- improved error reporting on collection and import errors. This makes
- improved error reporting on collection and import errors. This makes
use of a more general mechanism, namely that for custom test item/collect
nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
override it to return a string error representation of your choice
which is going to be reported as a (red) string.
nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
override it to return a string error representation of your choice
which is going to be reported as a (red) string.
- introduce '--junitprefix=STR' option to prepend a prefix
to all reports in the junitxml file.
- introduce '--junitprefix=STR' option to prepend a prefix
to all reports in the junitxml file.
Bug fixes / Maintenance
++++++++++++++++++++++++++
- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
you can properly check for their existence in a cross-python manner).
- refine --pdb: ignore xfailed tests, unify its TB-reporting and
- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
you can properly check for their existence in a cross-python manner).
- refine --pdb: ignore xfailed tests, unify its TB-reporting and
don't display failures again at the end.
- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
- fix py.code.compile(source) to generate unique filenames
- fix assertion re-interp problems on PyPy, by defering code
- fix py.code.compile(source) to generate unique filenames
- fix assertion re-interp problems on PyPy, by defering code
compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
- fix py.path.local.pyimport() to work with directories
- streamline py.path.local.mkdtemp implementation and usage
- don't print empty lines when showing junitxml-filename
- add optional boolean ignore_errors parameter to py.path.local.remove
- fix terminal writing on win32/python2.4
- py.process.cmdexec() now tries harder to return properly encoded unicode objects
- fix terminal writing on win32/python2.4
- py.process.cmdexec() now tries harder to return properly encoded unicode objects
on all python versions
- install plain py.test/py.which scripts also for Jython, this helps to
get canonical script paths in virtualenv situations
- make path.bestrelpath(path) return ".", note that when calling
X.bestrelpath the assumption is that X is a directory.
- make initial conftest discovery ignore "--" prefixed arguments
- fix resultlog plugin when used in an multicpu/multihost xdist situation
(thanks Jakub Gustak)
- perform distributed testing related reporting in the xdist-plugin
rather than having dist-related code in the generic py.test
- make path.bestrelpath(path) return ".", note that when calling
X.bestrelpath the assumption is that X is a directory.
- make initial conftest discovery ignore "--" prefixed arguments
- fix resultlog plugin when used in an multicpu/multihost xdist situation
(thanks Jakub Gustak)
- perform distributed testing related reporting in the xdist-plugin
rather than having dist-related code in the generic py.test
distribution
- fix homedir detection on Windows
- fix homedir detection on Windows
- ship distribute_setup.py version 0.6.13
Changes between 1.3.0 and 1.3.1
==================================================
---------------------------------------------
New features
New features
++++++++++++++++++
- issue91: introduce new py.test.xfail(reason) helper
to imperatively mark a test as expected to fail. Can
- issue91: introduce new py.test.xfail(reason) helper
to imperatively mark a test as expected to fail. Can
be used from within setup and test functions. This is
useful especially for parametrized tests when certain
useful especially for parametrized tests when certain
configurations are expected-to-fail. In this case the
declarative approach with the @py.test.mark.xfail cannot
be used as it would mark all configurations as xfail.
be used as it would mark all configurations as xfail.
- issue102: introduce new --maxfail=NUM option to stop
test runs after NUM failures. This is a generalization
of the '-x' or '--exitfirst' option which is now equivalent
to '--maxfail=1'. Both '-x' and '--maxfail' will
now also print a line near the end indicating the Interruption.
to '--maxfail=1'. Both '-x' and '--maxfail' will
now also print a line near the end indicating the Interruption.
- issue89: allow py.test.mark decorators to be used on classes
(class decorators were introduced with python2.6) and
(class decorators were introduced with python2.6) and
also allow to have multiple markers applied at class/module level
by specifying a list.
by specifying a list.
- improve and refine letter reporting in the progress bar:
. pass
@@ -106,19 +175,19 @@ New features
X xpassed test (test that was expected to fail but passed)
You can use any combination of 'fsxX' with the '-r' extended
reporting option. The xfail/xpass results will show up as
skipped tests in the junitxml output - which also fixes
reporting option. The xfail/xpass results will show up as
skipped tests in the junitxml output - which also fixes
issue99.
- make py.test.cmdline.main() return the exitstatus instead of raising
- make py.test.cmdline.main() return the exitstatus instead of raising
SystemExit and also allow it to be called multiple times. This of
course requires that your application and tests are properly teared
down and don't have global state.
course requires that your application and tests are properly teared
down and don't have global state.
Fixes / Maintenance
Fixes / Maintenance
++++++++++++++++++++++
- improved traceback presentation:
- improved traceback presentation:
- improved and unified reporting for "--tb=short" option
- Errors during test module imports are much shorter, (using --tb=short style)
- raises shows shorter more relevant tracebacks
@@ -126,65 +195,65 @@ Fixes / Maintenance
- improve support for raises and other dynamically compiled code by
manipulating python's linecache.cache instead of the previous
rather hacky way of creating custom code objects. This makes
rather hacky way of creating custom code objects. This makes
it seemlessly work on Jython and PyPy where it previously didn't.
- fix issue96: make capturing more resilient against Control-C
- fix issue96: make capturing more resilient against Control-C
interruptions (involved somewhat substantial refactoring
to the underlying capturing functionality to avoid race
to the underlying capturing functionality to avoid race
conditions).
- fix chaining of conditional skipif/xfail decorators - so it works now
- fix chaining of conditional skipif/xfail decorators - so it works now
as expected to use multiple @py.test.mark.skipif(condition) decorators,
including specific reporting which of the conditions lead to skipping.
including specific reporting which of the conditions lead to skipping.
- fix issue95: late-import zlib so that it's not required
for general py.test startup.
- fix issue95: late-import zlib so that it's not required
for general py.test startup.
- fix issue94: make reporting more robust against bogus source code
(and internally be more careful when presenting unexpected byte sequences)
Changes between 1.2.1 and 1.3.0
==================================================
---------------------------------------------
- deprecate --report option in favour of a new shorter and easier to
remember -r option: it takes a string argument consisting of any
combination of 'xfsX' characters. They relate to the single chars
you see during the dotted progress printing and will print an extra line
you see during the dotted progress printing and will print an extra line
per test at the end of the test run. This extra line indicates the exact
position or test ID that you directly paste to the py.test cmdline in order
to re-run a particular test.
to re-run a particular test.
- allow external plugins to register new hooks via the new
- allow external plugins to register new hooks via the new
pytest_addhooks(pluginmanager) hook. The new release of
the pytest-xdist plugin for distributed and looponfailing
testing requires this feature.
the pytest-xdist plugin for distributed and looponfailing
testing requires this feature.
- add a new pytest_ignore_collect(path, config) hook to allow projects and
plugins to define exclusion behaviour for their directory structure -
plugins to define exclusion behaviour for their directory structure -
for example you may define in a conftest.py this method::
def pytest_ignore_collect(path):
return path.check(link=1)
to prevent even a collection try of any tests in symlinked dirs.
to prevent even a collection try of any tests in symlinked dirs.
- new pytest_pycollect_makemodule(path, parent) hook for
allowing customization of the Module collection object for a
matching test module.
allowing customization of the Module collection object for a
matching test module.
- extend and refine xfail mechanism:
- extend and refine xfail mechanism:
``@py.test.mark.xfail(run=False)`` do not run the decorated test
``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
specifiying ``--runxfail`` on command line virtually ignores xfail markers
- expose (previously internal) commonly useful methods:
- expose (previously internal) commonly useful methods:
py.io.get_terminal_with() -> return terminal width
py.io.ansi_print(...) -> print colored/bold text on linux/win32
py.io.saferepr(obj) -> return limited representation string
- expose test outcome related exceptions as py.test.skip.Exception,
- expose test outcome related exceptions as py.test.skip.Exception,
py.test.raises.Exception etc., useful mostly for plugins
doing special outcome interpretation/tweaking
@@ -192,105 +261,105 @@ Changes between 1.2.1 and 1.3.0
- fix/refine python3 compatibility (thanks Benjamin Peterson)
- fixes for making the jython/win32 combination work, note however:
- fixes for making the jython/win32 combination work, note however:
jython2.5.1/win32 does not provide a command line launcher, see
http://bugs.jython.org/issue1491 . See pylib install documentation
for how to work around.
for how to work around.
- fixes for handling of unicode exception values and unprintable objects
- (issue87) fix unboundlocal error in assertionold code
- (issue87) fix unboundlocal error in assertionold code
- (issue86) improve documentation for looponfailing
- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
- ship distribute_setup.py version 0.6.10
- ship distribute_setup.py version 0.6.10
- added links to the new capturelog and coverage plugins
- added links to the new capturelog and coverage plugins
Changes between 1.2.1 and 1.2.0
=====================================
---------------------------------------------
- refined usage and options for "py.cleanup"::
py.cleanup # remove "*.pyc" and "*$py.class" (jython) files
py.cleanup -e .swp -e .cache # also remove files with these extensions
py.cleanup -s # remove "build" and "dist" directory next to setup.py files
py.cleanup -d # also remove empty directories
py.cleanup -d # also remove empty directories
py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'"
py.cleanup -n # dry run, only show what would be removed
- add a new option "py.test --funcargs" which shows available funcargs
and their help strings (docstrings on their respective factory function)
- add a new option "py.test --funcargs" which shows available funcargs
and their help strings (docstrings on their respective factory function)
for a given test path
- display a short and concise traceback if a funcarg lookup fails
- display a short and concise traceback if a funcarg lookup fails
- early-load "conftest.py" files in non-dot first-level sub directories.
allows to conveniently keep and access test-related options in a ``test``
subdir and still add command line options.
- early-load "conftest.py" files in non-dot first-level sub directories.
allows to conveniently keep and access test-related options in a ``test``
subdir and still add command line options.
- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
- fix issue78: always call python-level teardown functions even if the
according setup failed. This includes refinements for calling setup_module/class functions
according setup failed. This includes refinements for calling setup_module/class functions
which will now only be called once instead of the previous behaviour where they'd be called
multiple times if they raise an exception (including a Skipped exception). Any exception
will be re-corded and associated with all tests in the according module/class scope.
- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
- fix pdb debugging to be in the correct frame on raises-related errors
- fix pdb debugging to be in the correct frame on raises-related errors
- update apipkg.py to fix an issue where recursive imports might
unnecessarily break importing
unnecessarily break importing
- fix plugin links
- fix plugin links
Changes between 1.2 and 1.1.1
=====================================
---------------------------------------------
- moved dist/looponfailing from py.test core into a new
- moved dist/looponfailing from py.test core into a new
separately released pytest-xdist plugin.
- new junitxml plugin: --junitxml=path will generate a junit style xml file
which is processable e.g. by the Hudson CI system.
which is processable e.g. by the Hudson CI system.
- new option: --genscript=path will generate a standalone py.test script
which will not need any libraries installed. thanks to Ralf Schmitt.
which will not need any libraries installed. thanks to Ralf Schmitt.
- new option: --ignore will prevent specified path from collection.
Can be specified multiple times.
- new option: --ignore will prevent specified path from collection.
Can be specified multiple times.
- new option: --confcutdir=dir will make py.test only consider conftest
files that are relative to the specified dir.
- new option: --confcutdir=dir will make py.test only consider conftest
files that are relative to the specified dir.
- new funcarg: "pytestconfig" is the pytest config object for access
to command line args and can now be easily used in a test.
to command line args and can now be easily used in a test.
- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
- new "pytestconfig" funcarg allows access to test config object
- new "pytest_report_header" hook can return additional lines
to be displayed at the header of a test run.
- new "pytest_report_header" hook can return additional lines
to be displayed at the header of a test run.
- (experimental) allow "py.test path::name1::name2::..." for pointing
to a test within a test collection directly. This might eventually
evolve as a full substitute to "-k" specifications.
evolve as a full substitute to "-k" specifications.
- streamlined plugin loading: order is now as documented in
customize.html: setuptools, ENV, commandline, conftest.
customize.html: setuptools, ENV, commandline, conftest.
also setuptools entry point names are turned to canonical namees ("pytest_*")
- automatically skip tests that need 'capfd' but have no os.dup
- automatically skip tests that need 'capfd' but have no os.dup
- allow pytest_generate_tests to be defined in classes as well
- allow pytest_generate_tests to be defined in classes as well
- deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate definition of Directory, Module, Class and Function nodes
in conftest.py files. Use pytest collect hooks instead.
@@ -305,28 +374,28 @@ Changes between 1.2 and 1.1.1
change its long command line options to be a bit shorter (see py.test -h).
- change: pytest doctest plugin is now enabled by default and has a
new option --doctest-glob to set a pattern for file matches.
new option --doctest-glob to set a pattern for file matches.
- change: remove internal py._* helper vars, only keep py._pydir
- change: remove internal py._* helper vars, only keep py._pydir
- robustify capturing to survive if custom pytest_runtest_setup
code failed and prevented the capturing setup code from running.
- robustify capturing to survive if custom pytest_runtest_setup
code failed and prevented the capturing setup code from running.
- make py.test.* helpers provided by default plugins visible early -
works transparently both for pydoc and for interactive sessions
which will regularly see e.g. py.test.mark and py.test.importorskip.
which will regularly see e.g. py.test.mark and py.test.importorskip.
- simplify internal plugin manager machinery
- simplify internal plugin manager machinery
- simplify internal collection tree by introducing a RootCollector node
- fix assert reinterpreation that sees a call containing "keyword=..."
- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
hooks on slaves during dist-testing, report module/session teardown
- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
hooks on slaves during dist-testing, report module/session teardown
hooks correctly.
- fix issue65: properly handle dist-testing if no
execnet/py lib installed remotely.
- fix issue65: properly handle dist-testing if no
execnet/py lib installed remotely.
- skip some install-tests if no execnet is available
@@ -334,17 +403,17 @@ Changes between 1.2 and 1.1.1
Changes between 1.1.1 and 1.1.0
=====================================
---------------------------------------------
- introduce automatic plugin registration via 'pytest11'
- introduce automatic plugin registration via 'pytest11'
entrypoints via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility
- re-introduce py.test.cmdline.main() for better backward compatibility
- svn paths: fix a bug with path.check(versioned=True) for svn paths,
allow '%' in svn paths, make svnwc.update() default to interactive mode
allow '%' in svn paths, make svnwc.update() default to interactive mode
like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
- refine distributed tarball to contain test and no pyc files
@@ -353,22 +422,22 @@ Changes between 1.1.1 and 1.1.0
report a correct location
Changes between 1.1.0 and 1.0.2
=====================================
---------------------------------------------
* adjust and improve docs
* remove py.rest tool and internal namespace - it was
never really advertised and can still be used with
the old release if needed. If there is interest
the old release if needed. If there is interest
it could be revived into its own tool i guess.
* fix issue48 and issue59: raise an Error if the module
from an imported test file does not seem to come from
from an imported test file does not seem to come from
the filepath - avoids "same-name" confusion that has
been reported repeatedly
* merged Ronny's nose-compatibility hacks: now
nose-style setup_module() and setup() functions are
nose-style setup_module() and setup() functions are
supported
* introduce generalized py.test.mark function marking
@@ -377,116 +446,116 @@ Changes between 1.1.0 and 1.0.2
* deprecate parser.addgroup in favour of getgroup which creates option group
* add --report command line option that allows to control showing of skipped/xfailed sections
* add --report command line option that allows to control showing of skipped/xfailed sections
* generalized skipping: a new way to mark python functions with skipif or xfail
at function, class and modules level based on platform or sys-module attributes.
* generalized skipping: a new way to mark python functions with skipif or xfail
at function, class and modules level based on platform or sys-module attributes.
* extend py.test.mark decorator to allow for positional args
* introduce and test "py.cleanup -d" to remove empty directories
* introduce and test "py.cleanup -d" to remove empty directories
* fix issue #59 - robustify unittest test collection
* make bpython/help interaction work by adding an __all__ attribute
* make bpython/help interaction work by adding an __all__ attribute
to ApiModule, cleanup initpkg
* use MIT license for pylib, add some contributors
* remove py.execnet code and substitute all usages with 'execnet' proper
* fix issue50 - cached_setup now caches more to expectations
for test functions with multiple arguments.
* fix issue50 - cached_setup now caches more to expectations
for test functions with multiple arguments.
* merge Jarko's fixes, issue #45 and #46
* add the ability to specify a path for py.lookup to search in
* fix a funcarg cached_setup bug probably only occuring
in distributed testing and "module" scope with teardown.
* fix a funcarg cached_setup bug probably only occuring
in distributed testing and "module" scope with teardown.
* many fixes and changes for making the code base python3 compatible,
many thanks to Benjamin Peterson for helping with this.
many thanks to Benjamin Peterson for helping with this.
* consolidate builtins implementation to be compatible with >=2.3,
* consolidate builtins implementation to be compatible with >=2.3,
add helpers to ease keeping 2 and 3k compatible code
* deprecate py.compat.doctest|subprocess|textwrap|optparse
* deprecate py.magic.autopath, remove py/magic directory
* deprecate py.magic.autopath, remove py/magic directory
* move pytest assertion handling to py/code and a pytest_assertion
plugin, add "--no-assert" option, deprecate py.magic namespaces
in favour of (less) py.code ones.
plugin, add "--no-assert" option, deprecate py.magic namespaces
in favour of (less) py.code ones.
* consolidate and cleanup py/code classes and files
* consolidate and cleanup py/code classes and files
* cleanup py/misc, move tests to bin-for-dist
* cleanup py/misc, move tests to bin-for-dist
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
* consolidate py.log implementation, remove old approach.
* consolidate py.log implementation, remove old approach.
* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
text/unicode and byte-streams (uses underlying standard lib io.*
if available)
text/unicode and byte-streams (uses underlying standard lib io.*
if available)
* make py.unittest_convert helper script available which converts "unittest.py"
* make py.unittest_convert helper script available which converts "unittest.py"
style files into the simpler assert/direct-test-classes py.test/nosetests
style. The script was written by Laura Creighton.
* simplified internal localpath implementation
style. The script was written by Laura Creighton.
* simplified internal localpath implementation
Changes between 1.0.1 and 1.0.2
=====================================
-------------------------------------------
* fixing packaging issues, triggered by fedora redhat packaging,
also added doc, examples and contrib dirs to the tarball.
* fixing packaging issues, triggered by fedora redhat packaging,
also added doc, examples and contrib dirs to the tarball.
* added a documentation link to the new django plugin.
* added a documentation link to the new django plugin.
Changes between 1.0.0 and 1.0.1
=====================================
-------------------------------------------
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* capturing of unicode writes or encoded strings to sys.stdout/err
work better, also terminalwriting was adapted and somewhat
unified between windows and linux.
* capturing of unicode writes or encoded strings to sys.stdout/err
work better, also terminalwriting was adapted and somewhat
unified between windows and linux.
* improved documentation layout and content a lot
* added a "--help-config" option to show conftest.py / ENV-var names for
all longopt cmdline options, and some special conftest.py variables.
renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
all longopt cmdline options, and some special conftest.py variables.
renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
* fix issue #27: better reporting on non-collectable items given on commandline
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* fix issue #32: adding support for "incomplete" paths to wcpath.status()
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* monkeypatch setenv() now accepts a "prepend" parameter
* improved reporting of collection error tracebacks
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames
Changes between 1.0.0b9 and 1.0.0
=====================================
-------------------------------------------
* more terse reporting try to show filesystem path relatively to current dir
* more terse reporting try to show filesystem path relatively to current dir
* improve xfail output a bit
Changes between 1.0.0b8 and 1.0.0b9
=====================================
-------------------------------------------
* cleanly handle and report final teardown of test setup
@@ -495,160 +564,160 @@ Changes between 1.0.0b8 and 1.0.0b9
* setup/teardown or collection problems now show as ERRORs
or with big "E"'s in the progress lines. they are reported
and counted separately.
* dist-testing: properly handle test items that get locally
collected but cannot be collected on the remote side - often
and counted separately.
* dist-testing: properly handle test items that get locally
collected but cannot be collected on the remote side - often
due to platform/dependency reasons
* simplified py.test.mark API - see keyword plugin documentation
* integrate better with logging: capturing now by default captures
test functions and their immediate setup/teardown in a single stream
test functions and their immediate setup/teardown in a single stream
* capsys and capfd funcargs now have a readouterr() and a close() method
(underlyingly py.io.StdCapture/FD objects are used which grew a
(underlyingly py.io.StdCapture/FD objects are used which grew a
readouterr() method as well to return snapshots of captured out/err)
* make assert-reinterpretation work better with comparisons not
* make assert-reinterpretation work better with comparisons not
returning bools (reported with numpy from thanks maciej fijalkowski)
* reworked per-test output capturing into the pytest_iocapture.py plugin
and thus removed capturing code from config object
* reworked per-test output capturing into the pytest_iocapture.py plugin
and thus removed capturing code from config object
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
Changes between 1.0.0b7 and 1.0.0b8
=====================================
-------------------------------------------
* pytest_unittest-plugin is now enabled by default
* introduced pytest_keyboardinterrupt hook and
refined pytest_sessionfinish hooked, added tests.
* introduced pytest_keyboardinterrupt hook and
refined pytest_sessionfinish hooked, added tests.
* workaround a buggy logging module interaction ("closing already closed
files"). Thanks to Sridhar Ratnakumar for triggering.
files"). Thanks to Sridhar Ratnakumar for triggering.
* if plugins use "py.test.importorskip" for importing
a dependency only a warning will be issued instead
of exiting the testing process.
* if plugins use "py.test.importorskip" for importing
a dependency only a warning will be issued instead
of exiting the testing process.
* many improvements to docs:
* many improvements to docs:
- refined funcargs doc , use the term "factory" instead of "provider"
- added a new talk/tutorial doc page
- added a new talk/tutorial doc page
- better download page
- better plugin docstrings
- added new plugins page and automatic doc generation script
* fixed teardown problem related to partially failing funcarg setups
(thanks MrTopf for reporting), "pytest_runtest_teardown" is now
always invoked even if the "pytest_runtest_setup" failed.
* fixed teardown problem related to partially failing funcarg setups
(thanks MrTopf for reporting), "pytest_runtest_teardown" is now
always invoked even if the "pytest_runtest_setup" failed.
* tweaked doctest output for docstrings in py modules,
thanks Radomir.
* tweaked doctest output for docstrings in py modules,
thanks Radomir.
Changes between 1.0.0b3 and 1.0.0b7
=============================================
-------------------------------------------
* renamed py.test.xfail back to py.test.mark.xfail to avoid
* renamed py.test.xfail back to py.test.mark.xfail to avoid
two ways to decorate for xfail
* re-added py.test.mark decorator for setting keywords on functions
(it was actually documented so removing it was not nice)
* re-added py.test.mark decorator for setting keywords on functions
(it was actually documented so removing it was not nice)
* remove scope-argument from request.addfinalizer() because
request.cached_setup has the scope arg. TOOWTDI.
* remove scope-argument from request.addfinalizer() because
request.cached_setup has the scope arg. TOOWTDI.
* perform setup finalization before reporting failures
* apply modified patches from Andreas Kloeckner to allow
test functions to have no func_code (#22) and to make
"-k" and function keywords work (#20)
* apply modified patches from Andreas Kloeckner to allow
test functions to have no func_code (#22) and to make
"-k" and function keywords work (#20)
* apply patch from Daniel Peolzleithner (issue #23)
* apply patch from Daniel Peolzleithner (issue #23)
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* make __name__ == "__channelexec__" for remote_exec code
Changes between 1.0.0b1 and 1.0.0b3
=============================================
-------------------------------------------
* plugin classes are removed: one now defines
hooks directly in conftest.py or global pytest_*.py
files.
* plugin classes are removed: one now defines
hooks directly in conftest.py or global pytest_*.py
files.
* added new pytest_namespace(config) hook that allows
to inject helpers directly to the py.test.* namespace.
* added new pytest_namespace(config) hook that allows
to inject helpers directly to the py.test.* namespace.
* documented and refined many hooks
* documented and refined many hooks
* added new style of generative tests via
pytest_generate_tests hook that integrates
well with function arguments.
* added new style of generative tests via
pytest_generate_tests hook that integrates
well with function arguments.
Changes between 0.9.2 and 1.0.0b1
=============================================
-------------------------------------------
* introduced new "funcarg" setup method,
see doc/test/funcarg.txt
* introduced new "funcarg" setup method,
see doc/test/funcarg.txt
* introduced plugin architecuture and many
new py.test plugins, see
* introduced plugin architecuture and many
new py.test plugins, see
doc/test/plugins.txt
* teardown_method is now guaranteed to get
called after a test method has run.
* teardown_method is now guaranteed to get
called after a test method has run.
* new method: py.test.importorskip(mod,minversion)
will either import or call py.test.skip()
* completely revised internal py.test architecture
* new py.process.ForkedFunc object allowing to
* new py.process.ForkedFunc object allowing to
fork execution of a function to a sub process
and getting a result back.
and getting a result back.
XXX lots of things missing here XXX
Changes between 0.9.1 and 0.9.2
===============================
-------------------------------------------
* refined installation and metadata, created new setup.py,
now based on setuptools/ez_setup (thanks to Ralf Schmitt
* refined installation and metadata, created new setup.py,
now based on setuptools/ez_setup (thanks to Ralf Schmitt
for his support).
* improved the way of making py.* scripts available in
windows environments, they are now added to the
Scripts directory as ".cmd" files.
* improved the way of making py.* scripts available in
windows environments, they are now added to the
Scripts directory as ".cmd" files.
* py.path.svnwc.status() now is more complete and
* py.path.svnwc.status() now is more complete and
uses xml output from the 'svn' command if available
(Guido Wesdorp)
* fix for py.path.svn* to work with svn 1.5
(Chris Lamb)
* fix path.relto(otherpath) method on windows to
* fix path.relto(otherpath) method on windows to
use normcase for checking if a path is relative.
* py.test's traceback is better parseable from editors
* py.test's traceback is better parseable from editors
(follows the filenames:LINENO: MSG convention)
(thanks to Osmo Salomaa)
* fix to javascript-generation, "py.test --runbrowser"
* fix to javascript-generation, "py.test --runbrowser"
should work more reliably now
* removed previously accidentally added
py.test.broken and py.test.notimplemented helpers.
* removed previously accidentally added
py.test.broken and py.test.notimplemented helpers.
* there now is a py.__version__ attribute
Changes between 0.9.0 and 0.9.1
===============================
-------------------------------------------
This is a fairly complete list of changes between 0.9 and 0.9.1, which can
serve as a reference for developers.

View File

@@ -1,31 +1,81 @@
refine session initialization / fix custom collect crash
checks / deprecations for next release
---------------------------------------------------------------
tags: bug 1.4 core xdist
tags: bug 2.4 core xdist
When calling "py.test path/X" py.test can crash if the collection
of that directory is skipped. Calling "py.test path" will give
proper output. The reason is that for the very first colitems
getinitialnodes() and a collection is done before the fully
controlled session and pytest_make_collect_report protocol takes over.
Try to remove the redundant getinitialnodes related logic and amend
the session collect logic to care for this "initial" case as well.
Apart from simplification a side effect the dsession's session
and the core session probably converge some more.
* check oejskit plugin compatibility
* move pytest_nose out of pylib because it implicitely extends
the protocol now - setup/teardown is called at module level.
consider making calling of setup/teardown configurable
introduce py.test.mark.nocollect
profiling / hook call optimization
-------------------------------------
tags: enhancement 2.1
bench/bench.py reveals that for very quick running
unit tests the hook architecture is a bit slow.
Profile and improve hook calls.
do early-teardown of test modules
-----------------------------------------
tags: feature 2.1
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.
consider and document __init__ file usage in test directories
---------------------------------------------------------------
tags: bug 2.1 core
Currently, a test module is imported with its fully qualified
package path, determined by checking __init__ files upwards.
This has the side effect that a source package at the root
of the test dir could be imported as well. This is somewhat
convenient but complicates the picture for running tests against
different versions of a package. Also, implicit sys.path
manipulations are problematic per-se. Maybe factorting out
a pytest_addsyspath hook which can be disabled from the command line
makes sense. In any case documentation/recommendations for
certain scenarios makes sense.
relax requirement to have tests/testing contain an __init__
----------------------------------------------------------------
tags: feature 2.1
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
A local test run of a "tests" directory may work
but a remote one fail because the tests directory
does not contain an "__init__.py". Either give
an error or make it work without the __init__.py
i.e. port the nose-logic of unloading a test module.
customize test function collection
-------------------------------------------------------
tags: feature 1.4
tags: feature 2.1
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.
- 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.
introduce py.test.mark.platform
- 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 py.test.mark.platform
-------------------------------------------------------
tags: feature 1.4
tags: feature 2.1
Introduce nice-to-spell platform-skipping, examples:
Introduce nice-to-spell platform-skipping, examples:
@py.test.mark.platform("python3")
@py.test.mark.platform("not python3")
@@ -35,159 +85,147 @@ Introduce nice-to-spell platform-skipping, examples:
@py.test.mark.platform("not (jython and win32)", xfail=True)
etc. Idea is to allow Python expressions which can operate
on common spellings for operating systems and python
interpreter versions.
on common spellings for operating systems and python
interpreter versions.
introduce py.test.mark registration
-----------------------------------------
tags: feature 1.3
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.
definitions.
do early-teardown of test modules
allow to non-intrusively apply skipfs/xfail/marks
---------------------------------------------------
tags: feature 2.1
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.
explicit referencing of conftest.py files
-----------------------------------------
tags: feature 1.3
tags: feature 2.1
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.
allow to name conftest.py files (in sub directories) that should
be imported early, as to include command line options.
do recursive walk of conftest.py files?
-----------------------------------------
tags: feature 1.3
improve central py.test ini file
----------------------------------
tags: feature 2.1
it maybe makes sense to generally do a recursive search of conftest.py
files before command line parsing - this would help to offer the
full list of options as applicable to a given test project.
introduce more declarative configuration options:
- (to-be-collected test directories)
- required plugins
- test func/class/file matching patterns
- skip/xfail (non-intrusive)
- pytest.ini and tox.ini and setup.cfg configuration in the same file
consider introducing py.test.mark.skip_[not]win32/jython/pyXY
-------------------------------------------------------------
tags: feature 1.3
new documentation
----------------------------------
tags: feature 2.1
conveniently introduce markers for platforms to
have a shorter form for skipping.
- 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 1.3
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?
about "id" and "param" in such combinations though?
introduce py.test.mark.multi
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.
of values for a given function argument.
have imported module mismatch honour relative paths
--------------------------------------------------------
tags: bug 1.4
tags: bug 2.1
With 1.1.1 py.test fails at least on windows if an import
is relative and compared against an absolute conftest.py
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.
make node._checkcollectable more robust
-------------------------------------------------
tags: bug 1.4
currently node._checkcollectable() can raise
exceptions for all kinds of reasons ('conftest.py' loading
problems, missing rsync-dirs, platform-skip-at-import-level
issues, ...). It should just return True/False and cause
a good error message.
call termination with small timeout
-------------------------------------------------
tags: feature 1.4
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.
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
consider globals: py.test.ensuretemp and config
--------------------------------------------------------------
tags: experimental-wish 1.4
tags: experimental-wish 2.1
consider deprecating py.test.ensuretemp and py.test.config
to further reduce py.test globality. Also consider
consider deprecating py.test.ensuretemp and py.test.config
to further reduce py.test globality. Also consider
having py.test.config and ensuretemp coming from
a plugin rather than being there from the start.
consider allowing funcargs to setup methods
consider allowing funcargs for setup methods
--------------------------------------------------------------
tags: experimental-wish 1.4
tags: experimental-wish 2.1
Users have expressed the wish to have funcargs available to setup
Users have expressed the wish to have funcargs available to setup
functions. Experiment with allowing funcargs there - it might
also help to make the py.test.ensuretemp and config deprecation.
For filling funcargs for setup methods, we could call funcarg
factories with a request object that not have a cls/function
For filling funcargs for setup methods, we could call funcarg
factories with a request object that not have a cls/function
attributes. However, how to handle parametrized test functions
and funcargs?
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
consider pytest_addsyspath hook
-----------------------------------------
tags: 1.4
tags: 2.1
py.test could call a new pytest_addsyspath() in order to systematically
allow manipulation of sys.path and to inhibit it via --no-addsyspath
in order to more easily run against installed packages.
in order to more easily run against installed packages.
Alternatively it could also be done via the config object
and pytest_configure.
Alternatively it could also be done via the config object
and pytest_configure.
relax requirement to have tests/testing contain an __init__
show plugin information in test header
----------------------------------------------------------------
tags: feature 1.4
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
tags: feature 2.1
A local test run of a "tests" directory may work
but a remote one fail because the tests directory
does not contain an "__init__.py". Either give
an error or make it work without the __init__.py
show plugin information in test header
----------------------------------------------------------------
tags: feature 1.4
Now that external plugins are becoming more numerous
Now that external plugins are becoming more numerous
it would be useful to have external plugins along with
their versions displayed as a header line.
their versions displayed as a header line.
generate/refine plugin doc generation
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 1.4
tags: feature 2.1
review and prepare docs for 1.4.0 release. Probably
have docs living with the plugin and require them to
be available on doc generation time, at least when
the target is the website? Or rather go for interactive help?
py.test.ensuretemp and py.test.config are probably the last
objects containing global state. Often using them is not
neccessary. This is about trying to get rid of them, i.e.
deprecating them and checking with PyPy's usages as well
as others.
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 1.4
py.test.ensuretemp and py.test.config are probably the last
objects containing global state. Often using them is not
neccessary. This is about trying to get rid of them, i.e.
deprecating them and checking with PyPy's usages as well
as others.
remove deprecated bits in collect.py
remove deprecated bits in collect.py
-------------------------------------------------------------------
tags: feature 1.4
tags: feature 2.1
In an effort to further simplify code, review and remove deprecated bits
in collect.py. Probably good:

View File

@@ -2,18 +2,6 @@ include CHANGELOG
include README.txt
include setup.py
include distribute_setup.py
include LICENSE
include conftest.py
include LICENSE
graft doc
graft contrib
graft bin
graft testing
#exclude *.orig
#exclude *.orig
exclude *.rej
exclude .hginore
exclude *.pyc
#recursive-exclude testing *.pyc *.orig *.rej *$py.class
#prune .pyc
#prune .svn
#prune .hg

View File

@@ -1,8 +1,4 @@
The py lib is a Python development support library featuring
the following tools and modules:
py.test is a simple and popular testing tool for Python.
* py.test: tool for distributed automated testing
* py.code: dynamic code generation and introspection
* py.path: uniform local and svn path objects
See http://pytest.org for more documentation.
For questions and more information please visit http://pylib.org

157
_pytest/assertion.py Normal file
View File

@@ -0,0 +1,157 @@
"""
support for presented detailed information in failing assertions.
"""
import py
import sys
from _pytest.monkeypatch import monkeypatch
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group._addoption('--no-assert', action="store_true", default=False,
dest="noassert",
help="disable python assert expression reinterpretation."),
def pytest_configure(config):
# The _pytesthook attribute on the AssertionError is used by
# py._code._assertionnew to detect this plugin was loaded and in
# turn call the hooks defined here as part of the
# DebugInterpreter.
config._monkeypatch = m = monkeypatch()
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
warn_about_missing_assertion()
def callbinrepr(op, left, right):
hook_result = config.hook.pytest_assertrepr_compare(
config=config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
return '\n~'.join(new_expl)
m.setattr(py.builtin.builtins,
'AssertionError', py.code._AssertionError)
m.setattr(py.code, '_reprcompare', callbinrepr)
def pytest_unconfigure(config):
config._monkeypatch.undo()
def warn_about_missing_assertion():
try:
assert False
except AssertionError:
pass
else:
py.std.warnings.warn("Assertions are turned off!"
" (are you using python -O?)")
# Provide basestring in python3
try:
basestring = basestring
except NameError:
basestring = str
def pytest_assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
left_repr = py.io.saferepr(left, maxsize=30)
right_repr = py.io.saferepr(right, maxsize=30)
summary = '%s %s %s' % (left_repr, op, right_repr)
issequence = lambda x: isinstance(x, (list, tuple))
istext = lambda x: isinstance(x, basestring)
isdict = lambda x: isinstance(x, dict)
isset = lambda x: isinstance(x, set)
explanation = None
try:
if op == '==':
if istext(left) and istext(right):
explanation = _diff_text(left, right)
elif issequence(left) and issequence(right):
explanation = _compare_eq_sequence(left, right)
elif isset(left) and isset(right):
explanation = _compare_eq_set(left, right)
elif isdict(left) and isdict(right):
explanation = _diff_text(py.std.pprint.pformat(left),
py.std.pprint.pformat(right))
except py.builtin._sysex:
raise
except:
excinfo = py.code.ExceptionInfo()
explanation = ['(pytest_assertion plugin: representation of '
'details failed. Probably an object has a faulty __repr__.)',
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
def _diff_text(left, right):
"""Return the explanation for the diff between text
This will skip leading and trailing characters which are
identical to keep the diff minimal.
"""
explanation = []
i = 0 # just in case left or right has zero length
for i in range(min(len(left), len(right))):
if left[i] != right[i]:
break
if i > 42:
i -= 10 # Provide some context
explanation = ['Skipping %s identical '
'leading characters in diff' % i]
left = left[i:]
right = right[i:]
if len(left) == len(right):
for i in range(len(left)):
if left[-i] != right[-i]:
break
if i > 42:
i -= 10 # Provide some context
explanation += ['Skipping %s identical '
'trailing characters in diff' % i]
left = left[:-i]
right = right[:-i]
explanation += [line.strip('\n')
for line in py.std.difflib.ndiff(left.splitlines(),
right.splitlines())]
return explanation
def _compare_eq_sequence(left, right):
explanation = []
for i in range(min(len(left), len(right))):
if left[i] != right[i]:
explanation += ['At index %s diff: %r != %r' %
(i, left[i], right[i])]
break
if len(left) > len(right):
explanation += ['Left contains more items, '
'first extra item: %s' % py.io.saferepr(left[len(right)],)]
elif len(left) < len(right):
explanation += ['Right contains more items, '
'first extra item: %s' % py.io.saferepr(right[len(left)],)]
return explanation # + _diff_text(py.std.pprint.pformat(left),
# py.std.pprint.pformat(right))
def _compare_eq_set(left, right):
explanation = []
diff_left = left - right
diff_right = right - left
if diff_left:
explanation.append('Extra items in the left set:')
for item in diff_left:
explanation.append(py.io.saferepr(item))
if diff_right:
explanation.append('Extra items in the right set:')
for item in diff_right:
explanation.append(py.io.saferepr(item))
return explanation

View File

@@ -1,90 +1,6 @@
"""
configurable per-test stdout/stderr capturing mechanisms.
""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments. """
This plugin captures stdout/stderr output for each test separately.
In case of test failures this captured output is shown grouped
togtther with the test.
The plugin also provides test function arguments that help to
assert stdout/stderr output from within your tests, see the
`funcarg example`_.
Capturing of input/output streams during tests
---------------------------------------------------
By default ``sys.stdout`` and ``sys.stderr`` are substituted with
temporary streams during the execution of tests and setup/teardown code.
During the whole testing process it will re-use the same temporary
streams allowing to play well with the logging module which easily
takes ownership on these streams.
Also, 'sys.stdin' is substituted with a file-like "null" object that
does not return any values. This is to immediately error out
on tests that wait on reading something from stdin.
You can influence output capturing mechanisms from the command line::
py.test -s # disable all capturing
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
py.test --capture=fd # point filedescriptors 1 and 2 to temp file
If you set capturing values in a conftest file like this::
# conftest.py
option_capture = 'fd'
then all tests in that directory will execute with "fd" style capturing.
sys-level capturing
------------------------------------------
Capturing on 'sys' level means that ``sys.stdout`` and ``sys.stderr``
will be replaced with in-memory files (``py.io.TextIO`` to be precise)
that capture writes and decode non-unicode strings to a unicode object
(using a default, usually, UTF-8, encoding).
FD-level capturing and subprocesses
------------------------------------------
The ``fd`` based method means that writes going to system level files
based on the standard file descriptors will be captured, for example
writes such as ``os.write(1, 'hello')`` will be captured properly.
Capturing on fd-level will include output generated from
any subprocesses created during a test.
.. _`funcarg example`:
Example Usage of the capturing Function arguments
---------------------------------------------------
You can use the `capsys funcarg`_ and `capfd funcarg`_ to
capture writes to stdout and stderr streams. Using the
funcargs frees your test from having to care about setting/resetting
the old streams and also interacts well with py.test's own
per-test capturing. Here is an example test function:
.. sourcecode:: python
def test_myoutput(capsys):
print ("hello")
sys.stderr.write("world\\n")
out, err = capsys.readouterr()
assert out == "hello\\n"
assert err == "world\\n"
print "next"
out, err = capsys.readouterr()
assert out == "next\\n"
The ``readouterr()`` call snapshots the output so far -
and capturing will be continued. After the test
function finishes the original streams will
be restored. If you want to capture on
the filedescriptor level you can use the ``capfd`` function
argument which offers the same interface.
"""
import py
import pytest, py
import os
def pytest_addoption(parser):
@@ -92,7 +8,7 @@ def pytest_addoption(parser):
group._addoption('--capture', action="store", default=None,
metavar="method", type="choice", choices=['fd', 'sys', 'no'],
help="per-test capturing method: one of fd (default)|sys|no.")
group._addoption('-s', action="store_const", const="no", dest="capture",
group._addoption('-s', action="store_const", const="no", dest="capture",
help="shortcut for --capture=no.")
def addouterr(rep, outerr):
@@ -106,11 +22,19 @@ def addouterr(rep, outerr):
def pytest_configure(config):
config.pluginmanager.register(CaptureManager(), 'capturemanager')
def pytest_unconfigure(config):
capman = config.pluginmanager.getplugin('capturemanager')
while capman._method2capture:
name, cap = capman._method2capture.popitem()
cap.reset()
class NoCapture:
def startall(self):
pass
def resume(self):
pass
def reset(self):
pass
def suspend(self):
return "", ""
@@ -120,14 +44,15 @@ class CaptureManager:
def _maketempfile(self):
f = py.std.tempfile.TemporaryFile()
newf = py.io.dupfile(f, encoding="UTF-8")
newf = py.io.dupfile(f, encoding="UTF-8")
f.close()
return newf
def _makestringio(self):
return py.io.TextIO()
return py.io.TextIO()
def _getcapture(self, method):
if method == "fd":
if method == "fd":
return py.io.StdCaptureFD(now=False,
out=self._maketempfile(), err=self._maketempfile()
)
@@ -144,12 +69,12 @@ class CaptureManager:
if config.option.capture:
method = config.option.capture
else:
try:
try:
method = config._conftest.rget("option_capture", path=fspath)
except KeyError:
method = "fd"
if method == "fd" and not hasattr(os, 'dup'): # e.g. jython
method = "sys"
if method == "fd" and not hasattr(os, 'dup'): # e.g. jython
method = "sys"
return method
def resumecapture_item(self, item):
@@ -160,10 +85,10 @@ class CaptureManager:
def resumecapture(self, method):
if hasattr(self, '_capturing'):
raise ValueError("cannot resume, already capturing with %r" %
raise ValueError("cannot resume, already capturing with %r" %
(self._capturing,))
cap = self._method2capture.get(method)
self._capturing = method
self._capturing = method
if cap is None:
self._method2capture[method] = cap = self._getcapture(method)
cap.startall()
@@ -179,9 +104,11 @@ class CaptureManager:
outerr = cap.suspend()
del self._capturing
if item:
outerr = (item.outerr[0] + outerr[0],
outerr = (item.outerr[0] + outerr[0],
item.outerr[1] + outerr[1])
return outerr
return outerr
if hasattr(item, 'outerr'):
return item.outerr
return "", ""
def activate_funcargs(self, pyfuncitem):
@@ -204,7 +131,11 @@ class CaptureManager:
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
self.resumecapture(method)
try:
self.resumecapture(method)
except ValueError:
return # recursive collect, XXX refactor capturing
# to allow for more lightweight recursive capturing
try:
rep = __multicall__.execute()
finally:
@@ -212,13 +143,16 @@ class CaptureManager:
addouterr(rep, outerr)
return rep
@pytest.mark.tryfirst
def pytest_runtest_setup(self, item):
self.resumecapture_item(item)
@pytest.mark.tryfirst
def pytest_runtest_call(self, item):
self.resumecapture_item(item)
self.activate_funcargs(item)
@pytest.mark.tryfirst
def pytest_runtest_teardown(self, item):
self.resumecapture_item(item)
@@ -237,6 +171,7 @@ class CaptureManager:
if hasattr(self, '_capturing'):
self.suspendcapture()
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, __multicall__, item, call):
self.deactivate_funcargs()
rep = __multicall__.execute()
@@ -245,33 +180,30 @@ class CaptureManager:
addouterr(rep, outerr)
if not rep.passed or rep.when == "teardown":
outerr = ('', '')
item.outerr = outerr
item.outerr = outerr
return rep
def pytest_funcarg__capsys(request):
"""captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
"""
return CaptureFuncarg(request, py.io.StdCapture)
"""captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
"""
return CaptureFuncarg(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
"""
this funcarg will automatically skip.
"""
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")
return CaptureFuncarg(request, py.io.StdCaptureFD)
return CaptureFuncarg(py.io.StdCaptureFD)
class CaptureFuncarg:
def __init__(self, request, captureclass):
self._cclass = captureclass
self.capture = self._cclass(now=False)
#request.addfinalizer(self._finalize)
def __init__(self, captureclass):
self.capture = captureclass(now=False)
def _start(self):
self.capture.startall()
@@ -279,7 +211,7 @@ class CaptureFuncarg:
def _finalize(self):
if hasattr(self, 'capture'):
self.capture.reset()
del self.capture
del self.capture
def readouterr(self):
return self.capture.readouterr()

435
_pytest/config.py Normal file
View File

@@ -0,0 +1,435 @@
""" command line options, ini-file and conftest.py processing. """
import py
import sys, os
from _pytest.core import PluginManager
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
class Parser:
""" Parser for command line arguments. """
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
self._groups = []
self._processopt = processopt
self._usage = usage
self._inidict = {}
self._ininames = []
self.hints = []
def processoption(self, option):
if self._processopt:
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.
:description: long description for --help output.
:after: name of other group, used for ordering --help output.
"""
for group in self._groups:
if group.name == name:
return group
group = OptionGroup(name, description, parser=self)
i = 0
for i, grp in enumerate(self._groups):
if grp.name == after:
break
self._groups.insert(i+1, group)
return group
def addoption(self, *opts, **attrs):
""" add an optparse-style option. """
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
self.optparser = optparser = MyOptionParser(self)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
desc = group.description or group.name
optgroup = py.std.optparse.OptionGroup(optparser, desc)
optgroup.add_options(group.options)
optparser.add_option_group(optgroup)
return self.optparser.parse_args([str(x) for x in args])
def parse_setoption(self, args, option):
parsedoption, args = self.parse(args)
for name, value in parsedoption.__dict__.items():
setattr(option, name, value)
return args
def addini(self, name, help, type=None, default=None):
""" add an ini-file option with the given name and description. """
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
self.description = description
self.options = []
self.parser = parser
def addoption(self, *optnames, **attrs):
""" add an option to this group. """
option = py.std.optparse.Option(*optnames, **attrs)
self._addoption_instance(option, shortupper=False)
def _addoption(self, *optnames, **attrs):
option = py.std.optparse.Option(*optnames, **attrs)
self._addoption_instance(option, shortupper=True)
def _addoption_instance(self, option, shortupper=False):
if not shortupper:
for opt in option._short_opts:
if opt[0] == '-' and opt[1].islower():
raise ValueError("lowercase shortoptions reserved")
if self.parser:
self.parser.processoption(option)
self.options.append(option)
class MyOptionParser(py.std.optparse.OptionParser):
def __init__(self, parser):
self._parser = parser
py.std.optparse.OptionParser.__init__(self, usage=parser._usage,
add_help_option=False)
def format_epilog(self, formatter):
hints = self._parser.hints
if hints:
s = "\n".join(["hint: " + x for x in hints]) + "\n"
s = "\n" + s + "\n"
return s
return ""
class Conftest(object):
""" the single place for accessing values and interacting
towards conftest modules from py.test objects.
"""
def __init__(self, onimport=None, confcutdir=None):
self._path2confmods = {}
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = confcutdir
self._md5cache = {}
def setinitial(self, args):
""" try to find a first anchor path for looking up global values
from conftests. This function is usually called _before_
argument parsing. conftest files may add command line options
and we thus have no completely safe way of determining
which parts of the arguments are actually related to options
and which are file system paths. We just try here to get
bootstrapped ...
"""
current = py.path.local()
opt = '--confcutdir'
for i in range(len(args)):
opt1 = str(args[i])
if opt1.startswith(opt):
if opt1 == opt:
if len(args) > i:
p = current.join(args[i+1], abs=True)
elif opt1.startswith(opt + "="):
p = current.join(opt1[len(opt)+1:], abs=1)
self._confcutdir = p
break
for arg in args + [current]:
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?"
def getconftestmodules(self, path):
""" return a list of imported conftest modules for the given path. """
try:
clist = self._path2confmods[path]
except KeyError:
if path is None:
raise ValueError("missing default confest.")
dp = path.dirpath()
clist = []
if dp != path:
cutdir = self._confcutdir
if cutdir and path != cutdir and not path.relto(cutdir):
pass
else:
conftestpath = path.join("conftest.py")
if conftestpath.check(file=1):
key = conftestpath.computehash()
# XXX logging about conftest loading
if key not in self._md5cache:
clist.append(self.importconftest(conftestpath))
self._md5cache[key] = conftestpath
else:
# use some kind of logging
print ("WARN: not loading %s" % conftestpath)
clist[:0] = self.getconftestmodules(dp)
self._path2confmods[path] = clist
# be defensive: avoid changes from caller side to
# affect us by always returning a copy of the actual list
return clist[:]
def rget(self, name, path=None):
mod, value = self.rget_with_confmod(name, path)
return value
def rget_with_confmod(self, name, path=None):
modules = self.getconftestmodules(path)
modules.reverse()
for mod in modules:
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def importconftest(self, conftestpath):
assert conftestpath.check(), conftestpath
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport()
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self._postimport(mod)
return mod
def _postimport(self, mod):
if self._onimport:
self._onimport(mod)
return mod
def _ensure_removed_sysmodule(modname):
try:
del sys.modules[modname]
except KeyError:
pass
class CmdOptions(object):
""" holds cmdline options as attributes."""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __repr__(self):
return "<CmdOptions %r>" %(self.__dict__,)
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
self.option = CmdOptions()
self._parser = Parser(
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
processopt=self._processopt,
)
#: a pluginmanager instance
self.pluginmanager = pluginmanager or PluginManager(load=True)
self.trace = self.pluginmanager.trace.root.get("config")
self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook
self._inicache = {}
def _onimportconftest(self, conftestmodule):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
self.pluginmanager.consider_conftest(conftestmodule)
def _processopt(self, opt):
if hasattr(opt, 'default') and opt.dest:
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath):
allconftests = self._conftest._conftestpath2mod.values()
plugins = [x for x in self.pluginmanager.getplugins()
if x not in allconftests]
plugins += self._conftest.getconftestmodules(fspath)
return plugins
def _setinitialconftest(self, args):
# capture output during conftest init (#issue93)
name = hasattr(os, 'dup') and 'StdCaptureFD' or 'StdCapture'
cap = getattr(py.io, name)()
try:
try:
self._conftest.setinitial(args)
finally:
out, err = cap.reset()
except:
sys.stdout.write(out)
sys.stderr.write(err)
raise
def _initini(self, args):
self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
def _preparse(self, args, addopts=True):
self._initini(args)
if addopts:
args[:] = self.getini("addopts") + args
self._checkversion()
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.pluginmanager.consider_preparse(args)
self._setinitialconftest(args)
self.pluginmanager.do_addoption(self._parser)
def _checkversion(self):
minver = self.inicfg.get('minversion', None)
if minver:
ver = minver.split(".")
myver = pytest.__version__.split(".")
if myver < ver:
raise pytest.UsageError(
"%s:%d: requires pytest-%s, actual pytest-%s'" %(
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args):
# parse given cmdline arguments into this 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._preparse(args)
self._parser.hints.extend(self.pluginmanager._hints)
args = self._parser.parse_setoption(args, self.option)
if not args:
args.append(py.std.os.getcwd())
self.args = args
def getini(self, name):
""" return configuration value from an ini file. If the
specified name hasn't been registered through a prior ``parse.addini``
call (usually from a plugin), a ValueError is raised. """
try:
return self._inicache[name]
except KeyError:
self._inicache[name] = val = self._getini(name)
return val
def _getini(self, name):
try:
description, type, default = self._parser._inidict[name]
except KeyError:
raise ValueError("unknown configuration value: %r" %(name,))
try:
value = self.inicfg[name]
except KeyError:
if default is not None:
return default
if type is None:
return ''
return []
if type == "pathlist":
dp = py.path.local(self.inicfg.config.path).dirpath()
l = []
for relpath in py.std.shlex.split(value):
l.append(dp.join(relpath, abs=True))
return l
elif type == "args":
return py.std.shlex.split(value)
elif type == "linelist":
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
else:
assert type is None
return value
def _getconftest_pathlist(self, name, path=None):
try:
mod, relroots = self._conftest.rget_with_confmod(name, path)
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
l = []
for relroot in relroots:
if not isinstance(relroot, py.path.local):
relroot = relroot.replace("/", py.path.local.sep)
relroot = modpath.join(relroot, abs=True)
l.append(relroot)
return l
def _getconftest(self, name, path=None, check=False):
if check:
self._checkconftest(name)
return self._conftest.rget(name, path)
def getvalue(self, name, path=None):
""" return ``name`` value looked set from command line options.
(deprecated) if we can't find the option also lookup
the name in a matching conftest file.
"""
try:
return getattr(self.option, name)
except AttributeError:
return self._getconftest(name, path, check=False)
def getvalueorskip(self, name, path=None):
""" (deprecated) return getvalue(name) or call py.test.skip if no value exists. """
try:
val = self.getvalue(name, path)
if val is None:
raise KeyError(name)
return val
except KeyError:
py.test.skip("no %r value found" %(name,))
def getcfg(args, inibasenames):
args = [x for x in args if str(x)[0] != "-"]
if not args:
args = [py.path.local()]
for arg in args:
arg = py.path.local(arg)
for base in arg.parts(reverse=True):
for inibasename in inibasenames:
p = base.join(inibasename)
if p.check():
iniconfig = py.iniconfig.IniConfig(p)
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
def findupwards(current, basename):
current = py.path.local(current)
while 1:
p = current.join(basename)
if p.check():
return p
p = current.dirpath()
if p == current:
return
current = p

461
_pytest/core.py Normal file
View File

@@ -0,0 +1,461 @@
"""
pytest PluginManager, basic initialization and tracing.
(c) Holger Krekel 2004-2010
"""
import sys, os
import inspect
import py
from _pytest import hookspec # the extension point definitions
assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
default_plugins = (
"config mark session terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml doctest").split()
IMPORTPREFIX = "pytest_"
class TagTracer:
def __init__(self, prefix="[pytest] "):
self._tag2proc = {}
self.writer = None
self.indent = 0
self.prefix = prefix
def get(self, name):
return TagTracerSub(self, (name,))
def processmessage(self, tags, args):
if self.writer is not None:
if args:
indent = " " * self.indent
content = " ".join(map(str, args))
self.writer("%s%s%s\n" %(self.prefix, indent, content))
try:
self._tag2proc[tags](tags, args)
except KeyError:
pass
def setwriter(self, writer):
self.writer = writer
def setprocessor(self, tags, processor):
if isinstance(tags, str):
tags = tuple(tags.split(":"))
else:
assert isinstance(tags, tuple)
self._tag2proc[tags] = processor
class TagTracerSub:
def __init__(self, root, tags):
self.root = root
self.tags = tags
def __call__(self, *args):
self.root.processmessage(self.tags, args)
def setmyprocessor(self, processor):
self.root.setprocessor(self.tags, processor)
def get(self, name):
return self.__class__(self.root, self.tags + (name,))
class PluginManager(object):
def __init__(self, load=False):
self._name2plugin = {}
self._plugins = []
self._hints = []
self.trace = TagTracer().get("pluginmanage")
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
encoding = getattr(err, 'encoding', 'utf8')
try:
err = py.io.dupfile(err, encoding=encoding)
except Exception:
pass
self.trace.root.setwriter(err.write)
self.hook = HookRelay([hookspec], pm=self)
self.register(self)
if load:
for spec in default_plugins:
self.import_plugin(spec)
def _getpluginname(self, plugin, name):
if name is None:
if hasattr(plugin, '__name__'):
name = plugin.__name__.split(".")[-1]
else:
name = id(plugin)
return name
def register(self, plugin, name=None, prepend=False):
assert not self.isregistered(plugin), plugin
assert not self.isregistered(plugin), plugin
name = self._getpluginname(plugin, name)
if name in self._name2plugin:
return False
self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
return True
def unregister(self, plugin=None, name=None):
if plugin is None:
plugin = self.getplugin(name=name)
self._plugins.remove(plugin)
self.hook.pytest_plugin_unregistered(plugin=plugin)
for name, value in list(self._name2plugin.items()):
if value == plugin:
del self._name2plugin[name]
def isregistered(self, plugin, name=None):
if self._getpluginname(plugin, name) in self._name2plugin:
return True
for val in self._name2plugin.values():
if plugin == val:
return True
def addhooks(self, spec):
self.hook._addhooks(spec, prefix="pytest_")
def getplugins(self):
return list(self._plugins)
def skipifmissing(self, name):
if not self.hasplugin(name):
py.test.skip("plugin %r is missing" % name)
def hasplugin(self, name):
try:
self.getplugin(name)
return True
except KeyError:
return False
def getplugin(self, name):
try:
return self._name2plugin[name]
except KeyError:
impname = canonical_importname(name)
return self._name2plugin[impname]
# API for bootstrapping
#
def _envlist(self, varname):
val = py.std.os.environ.get(varname, None)
if val is not None:
return val.split(',')
return ()
def consider_env(self):
for spec in self._envlist("PYTEST_PLUGINS"):
self.import_plugin(spec)
def consider_setuptools_entrypoints(self):
try:
from pkg_resources import iter_entry_points, DistributionNotFound
except ImportError:
return # XXX issue a warning
for ep in iter_entry_points('pytest11'):
name = canonical_importname(ep.name)
if name in self._name2plugin:
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self.register(plugin, name=name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.import_plugin(opt2)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
self.consider_module(conftestmodule)
def consider_module(self, mod):
attr = getattr(mod, "pytest_plugins", ())
if attr:
if not isinstance(attr, (list, tuple)):
attr = (attr,)
for spec in attr:
self.import_plugin(spec)
def import_plugin(self, spec):
assert isinstance(spec, str)
modname = canonical_importname(spec)
if modname in self._name2plugin:
return
try:
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except:
e = py.std.sys.exc_info()[1]
if not hasattr(py.test, 'skip'):
raise
elif not isinstance(e, py.test.skip.Exception):
raise
self._hints.append("skipped plugin %r: %s" %((modname, e.msg)))
else:
self.register(mod, modname)
self.consider_module(mod)
def pytest_plugin_registered(self, plugin):
import pytest
dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
if dic:
self._setns(pytest, dic)
if hasattr(self, '_config'):
self.call_plugin(plugin, "pytest_addoption",
{'parser': self._config._parser})
self.call_plugin(plugin, "pytest_configure",
{'config': self._config})
def _setns(self, obj, dic):
import pytest
for name, value in dic.items():
if isinstance(value, dict):
mod = getattr(obj, name, None)
if mod is None:
modname = "pytest.%s" % name
mod = py.std.types.ModuleType(modname)
sys.modules[modname] = mod
mod.__all__ = []
setattr(obj, name, mod)
obj.__all__.append(name)
self._setns(mod, value)
else:
setattr(obj, name, value)
obj.__all__.append(name)
#if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)
def pytest_terminal_summary(self, terminalreporter):
tw = terminalreporter._tw
if terminalreporter.config.option.traceconfig:
for hint in self._hints:
tw.line("hint: %s" % hint)
def do_addoption(self, parser):
mname = "pytest_addoption"
methods = reversed(self.listattr(mname))
MultiCall(methods, {'parser': parser}).execute()
def do_configure(self, config):
assert not hasattr(self, '_config')
self._config = config
config.hook.pytest_configure(config=self._config)
def do_unconfigure(self, config):
config = self._config
del self._config
config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self)
def notify_exception(self, excinfo):
excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
res = self.hook.pytest_internalerror(excrepr=excrepr)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.flush()
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
l = []
last = []
for plugin in plugins:
try:
meth = getattr(plugin, attrname)
if hasattr(meth, 'tryfirst'):
last.append(meth)
elif hasattr(meth, 'trylast'):
l.insert(0, meth)
else:
l.append(meth)
except AttributeError:
continue
l.extend(last)
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def canonical_importname(name):
if '.' in name:
return name
name = name.lower()
if not name.startswith(IMPORTPREFIX):
name = IMPORTPREFIX + name
return name
def importplugin(importspec):
#print "importing", importspec
try:
return __import__(importspec, None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(importspec) == -1:
raise
name = importspec
try:
if name.startswith("pytest_"):
name = importspec[7:]
return __import__("_pytest.%s" %(name), None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(name) == -1:
raise
# show the original exception, not the failing internal one
return __import__(importspec, None, None, '__doc__')
class MultiCall:
""" execute a call into multiple python functions/methods. """
def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods)
self.kwargs = kwargs
self.results = []
self.firstresult = firstresult
def __repr__(self):
status = "%d results, %d meths" % (len(self.results), len(self.methods))
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self):
while self.methods:
method = self.methods.pop()
kwargs = self.getkwargs(method)
res = method(**kwargs)
if res is not None:
self.results.append(res)
if self.firstresult:
return res
if not self.firstresult:
return self.results
def getkwargs(self, method):
kwargs = {}
for argname in varnames(method):
try:
kwargs[argname] = self.kwargs[argname]
except KeyError:
if argname == "__multicall__":
kwargs[argname] = self
return kwargs
def varnames(func):
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func)
rawcode = py.code.getrawcode(func)
try:
return rawcode.co_varnames[ismethod:rawcode.co_argcount]
except AttributeError:
return ()
class HookRelay:
def __init__(self, hookspecs, pm, prefix="pytest_"):
if not isinstance(hookspecs, list):
hookspecs = [hookspecs]
self._hookspecs = []
self._pm = pm
self.trace = pm.trace.root.get("hook")
for hookspec in hookspecs:
self._addhooks(hookspec, prefix)
def _addhooks(self, hookspecs, prefix):
self._hookspecs.append(hookspecs)
added = False
for name, method in vars(hookspecs).items():
if name.startswith(prefix):
if not method.__doc__:
raise ValueError("docstring required for hook %r, in %r"
% (method, hookspecs))
firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult)
setattr(self, name, hc)
added = True
#print ("setting new hook", name)
if not added:
raise ValueError("did not find new %r hooks in %r" %(
prefix, hookspecs,))
class HookCaller:
def __init__(self, hookrelay, name, firstresult):
self.hookrelay = hookrelay
self.name = name
self.firstresult = firstresult
self.trace = self.hookrelay.trace
def __repr__(self):
return "<HookCaller %r>" %(self.name,)
def __call__(self, **kwargs):
methods = self.hookrelay._pm.listattr(self.name)
return self._docall(methods, kwargs)
def pcall(self, plugins, **kwargs):
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
return self._docall(methods, kwargs)
def _docall(self, methods, kwargs):
self.trace(self.name, kwargs)
self.trace.root.indent += 1
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
try:
res = mc.execute()
if res:
self.trace("finish", self.name, "-->", res)
finally:
self.trace.root.indent -= 1
return res
_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. """
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
args = [str(args)]
elif not isinstance(args, (tuple, list)):
if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,))
args = py.std.shlex.split(args)
if _preinit:
_pluginmanager = _preinit.pop(0)
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
return exitstatus
class UsageError(Exception):
""" error in py.test usage or invocation"""

82
_pytest/doctest.py Normal file
View File

@@ -0,0 +1,82 @@
""" discover and run doctests in modules and test files."""
import pytest, py
from py._code.code import TerminalRepr, ReprFileLocation
def pytest_addoption(parser):
group = parser.getgroup("collect")
group.addoption("--doctest-modules",
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
group.addoption("--doctest-glob",
action="store", default="test*.txt", metavar="pat",
help="doctests file matching pattern, default: test*.txt",
dest="doctestglob")
def pytest_collect_file(path, parent):
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules:
return DoctestModule(path, parent)
elif (path.ext in ('.txt', '.rst') and parent.session.isinitpath(path)) or \
path.check(fnmatch=config.getvalue("doctestglob")):
return DoctestTextfile(path, parent)
class ReprFailDoctest(TerminalRepr):
def __init__(self, reprlocation, lines):
self.reprlocation = reprlocation
self.lines = lines
def toterminal(self, tw):
for line in self.lines:
tw.line(line)
self.reprlocation.toterminal(tw)
class DoctestItem(pytest.Item):
def repr_failure(self, excinfo):
if excinfo.errisinstance(py.std.doctest.DocTestFailure):
doctestfailure = excinfo.value
example = doctestfailure.example
test = doctestfailure.test
filename = test.filename
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = py.std.doctest.OutputChecker()
REPORT_UDIFF = py.std.doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
i = max(test.lineno, max(0, lineno - 10)) # XXX?
lines = []
for line in filelines[i:lineno]:
lines.append("%03d %s" % (i+1, line))
i += 1
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
return ReprFailDoctest(reprlocation, lines)
elif excinfo.errisinstance(py.std.doctest.UnexpectedException):
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
return super(DoctestItem, self).repr_failure(excinfo)
else:
return super(DoctestItem, self).repr_failure(excinfo)
def reportinfo(self):
return self.fspath, None, "[doctest]"
class DoctestTextfile(DoctestItem, pytest.File):
def runtest(self):
doctest = py.std.doctest
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=doctest.ELLIPSIS,
raise_on_error=True, verbose=0)
class DoctestModule(DoctestItem, pytest.File):
def runtest(self):
doctest = py.std.doctest
if self.fspath.basename == "conftest.py":
module = self.config._conftest.importconftest(self.fspath)
else:
module = self.fspath.pyimport()
failed, tot = doctest.testmod(
module, raise_on_error=True, verbose=0,
optionflags=doctest.ELLIPSIS)

73
_pytest/genscript.py Executable file
View File

@@ -0,0 +1,73 @@
""" 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:
base = py.path.local(syspath)
lib = base/name
if lib.check(dir=1):
return lib
mod = base.join("%s.py" % name)
if mod.check(file=1):
return mod
raise LookupError(name)
def pkgname(toplevel, rootpath, path):
parts = path.parts()[len(rootpath.parts()):]
return '.'.join([toplevel] + [x.purebasename for x in parts])
def pkg_to_mapping(name):
toplevel = find_toplevel(name)
name2src = {}
if toplevel.check(file=1): # module
name2src[toplevel.purebasename] = toplevel.read()
else: # package
for pyfile in toplevel.visit('*.py'):
pkg = pkgname(name, toplevel, pyfile)
name2src[pkg] = pyfile.read()
return name2src
def compress_mapping(mapping):
data = pickle.dumps(mapping, 2)
data = zlib.compress(data, 9)
data = base64.encodestring(data)
data = data.decode('ascii')
return data
def compress_packages(names):
mapping = {}
for name in 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')
exe = tmpl.read()
exe = exe.replace('@SOURCES@', data)
exe = exe.replace('@ENTRY@', entry)
return exe
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption("--genscript", action="store", default=None,
dest="genscript", metavar="path",
help="create standalone py.test script at given target path.")
def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
script = generate_script(
'import py; raise SystemExit(py.test.cmdline.main())',
['py', '_pytest', 'pytest'],
)
genscript = py.path.local(genscript)
genscript.write(script)
return 0

View File

@@ -1,14 +1,16 @@
""" provide version info, conftest/environment config names.
"""
""" version info, help messages, tracing configuration. """
import py
import pytest
import inspect, sys
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
group.addoption('--version', action="store_true",
help="display py lib version and import information.")
group.addoption('--version', action="store_true",
help="display pytest lib version and import information.")
group._addoption("-h", "--help", action="store_true", dest="help",
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default = [],
metavar="name",
metavar="name",
help="early-load given plugin (multi-allowed).")
group.addoption('--traceconfig',
action="store_true", dest="traceconfig", default=False,
@@ -19,66 +21,60 @@ def pytest_addoption(parser):
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="generate and show internal debugging information.")
group.addoption("--help-config", action="store_true", dest="helpconfig",
help="show available conftest.py and ENV-variable names.")
def pytest_configure(__multicall__, config):
def pytest_cmdline_main(config):
if config.option.version:
p = py.path.local(py.__file__).dirpath()
sys.stderr.write("This is py.test version %s, imported from %s\n" %
(py.__version__, p))
sys.exit(0)
if not config.option.helpconfig:
return
__multicall__.execute()
options = []
for group in config._parser._groups:
options.extend(group.options)
widths = [0] * 10
p = py.path.local(pytest.__file__)
sys.stderr.write("This is py.test version %s, imported from %s\n" %
(pytest.__version__, p))
return 0
elif config.option.help:
config.pluginmanager.do_configure(config)
showhelp(config)
return 0
def showhelp(config):
tw = py.io.TerminalWriter()
tw.sep("-")
tw.line("%-13s | %-18s | %-25s | %s" %(
"cmdline name", "conftest.py name", "ENV-variable name", "help"))
tw.sep("-")
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
#tw.sep( "=", "config file settings")
tw.line("[pytest] ini-options in the next "
"pytest.ini|tox.ini|setup.cfg file:")
tw.line()
options = [opt for opt in options if opt._long_opts]
options.sort(key=lambda x: x._long_opts)
for opt in options:
if not opt._long_opts or not opt.dest:
continue
optstrings = list(opt._long_opts) # + list(opt._short_opts)
optstrings = filter(None, optstrings)
optstring = "|".join(optstrings)
line = "%-13s | %-18s | %-25s | %s" %(
optstring,
"option_%s" % opt.dest,
"PYTEST_OPTION_%s" % opt.dest.upper(),
opt.help and opt.help or "",
)
for name in config._parser._ininames:
help, type, default = config._parser._inidict[name]
if type is None:
type = "string"
spec = "%s (%s)" % (name, type)
line = " %-24s %s" %(spec, help)
tw.line(line[:tw.fullwidth])
for name, help in conftest_options:
line = "%-13s | %-18s | %-25s | %s" %(
"",
name,
"",
help,
)
tw.line(line[:tw.fullwidth])
tw.sep("-")
sys.exit(0)
conftest_options = (
tw.line() ; tw.line()
#tw.sep("=")
return
tw.line("conftest.py options:")
tw.line()
conftestitems = sorted(config._parser._conftestdict.items())
for name, help in conftest_options + conftestitems:
line = " %-15s %s" %(name, help)
tw.line(line[:tw.fullwidth])
tw.line()
#tw.sep( "=")
conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
('collect_ignore', '(relative) paths ignored during collection'),
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
)
]
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using py lib: %s" % (py.path.local(py.__file__).dirpath()))
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []
@@ -89,7 +85,7 @@ def pytest_report_header(config):
# =====================================================
# validate plugin syntax and hooks
# validate plugin syntax and hooks
# =====================================================
def pytest_plugin_registered(manager, plugin):
@@ -97,7 +93,7 @@ def pytest_plugin_registered(manager, plugin):
hooks = {}
for hookspec in manager.hook._hookspecs:
hooks.update(collectattr(hookspec))
stringio = py.io.TextIO()
def Print(*args):
if args:
@@ -110,7 +106,7 @@ def pytest_plugin_registered(manager, plugin):
#print "checking", name
if isgenerichook(name):
continue
if name not in hooks:
if name not in hooks:
if not getattr(method, 'optionalhook', False):
Print("found unknown hook:", name)
fail = True
@@ -126,15 +122,15 @@ def pytest_plugin_registered(manager, plugin):
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
Print("actual definition: %s" %(formatdef(method)))
Print("available hook arguments: %s" %
Print("available hook arguments: %s" %
", ".join(hookargs))
fail = True
break
break
#if not fail:
# print "matching hook:", formatdef(method)
if fail:
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("%s:\n%s" %(name, stringio.getvalue()))
raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
class PluginValidationError(Exception):
""" plugin failed validation. """
@@ -148,17 +144,16 @@ def getargs(func):
startindex = inspect.ismethod(func) and 1 or 0
return args[startindex:]
def collectattr(obj, prefixes=("pytest_",)):
def collectattr(obj):
methods = {}
for apiname in dir(obj):
for prefix in prefixes:
if apiname.startswith(prefix):
methods[apiname] = getattr(obj, apiname)
return methods
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):
return "%s%s" %(
func.__name__,
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@@ -1,49 +1,84 @@
"""
hook specifications for py.test plugins
"""
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
# -------------------------------------------------------------------------
# Command line and configuration
# Initialization
# -------------------------------------------------------------------------
def pytest_namespace():
"return dict of name->object which will get stored at py.test. namespace"
def pytest_addoption(parser):
"add optparse-style options via parser.addoption."
def pytest_addhooks(pluginmanager):
"add hooks via pluginmanager.registerhooks(module)"
"""called at plugin load time to allow adding new hooks via a call to
pluginmanager.registerhooks(module)."""
def pytest_namespace():
"""return dict of name->object to be made globally available in
the py.test/pytest namespace. This hook is called before command
line options are parsed.
"""
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
pytest_cmdline_parse.firstresult = True
def pytest_addoption(parser):
"""add optparse-style options and ini-style config values via calls
to ``parser.addoption`` and ``parser.addini(...)``.
"""
def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
implementation will invoke the configure hooks and runtest_mainloop. """
pytest_cmdline_main.firstresult = True
def pytest_configure(config):
""" called after command line options have been parsed.
and all plugins and initial conftest files been loaded.
""" called after command line options have been parsed.
and all plugins and initial conftest files been loaded.
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
pytest_runtestloop.firstresult = True
# -------------------------------------------------------------------------
# collection hooks
# -------------------------------------------------------------------------
def pytest_collection(session):
""" perform the collection protocol for the given session. """
pytest_collection.firstresult = True
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
def pytest_ignore_collect(path, config):
""" return true value to prevent considering this path for collection.
This hook is consulted for all files and directories prior to considering
collection hooks.
""" return True to prevent considering this path for collection.
This hook is consulted for all files and directories prior to calling
more specific hooks.
"""
pytest_ignore_collect.firstresult = True
def pytest_collect_directory(path, parent):
""" return Collection node or None for the given path. """
""" called before traversing a directory for collection files. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent):
""" return Collection node or None for the given path. """
""" return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
# logging hooks for collection
def pytest_collectstart(collector):
""" collector starts collecting. """
def pytest_itemcollected(item):
""" we just collected a test item. """
def pytest_collectreport(report):
""" collector finished collecting. """
@@ -51,21 +86,17 @@ def pytest_deselected(items):
""" called for test items deselected by keyword. """
def pytest_make_collect_report(collector):
""" perform a collection and return a collection. """
""" perform ``collector.collect()`` and return a CollectReport. """
pytest_make_collect_report.firstresult = True
# XXX rename to item_collected()? meaning in distribution context?
def pytest_itemstart(item, node=None):
""" test item gets collected. """
# -------------------------------------------------------------------------
# Python test function related hooks
# -------------------------------------------------------------------------
def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path.
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
""" return a Module collector or None for the given path.
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
create test modules for files that do not match as a test module.
"""
pytest_pycollect_makemodule.firstresult = True
@@ -82,39 +113,51 @@ def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
# -------------------------------------------------------------------------
# generic runtest related hooks
# generic runtest related hooks
# -------------------------------------------------------------------------
def pytest_itemstart(item, node=None):
""" (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item):
""" implement fixture, run and report about the given test item. """
""" implements the standard runtest_setup/call/teardown protocol including
capturing exceptions and calling reporting hooks on the results accordingly.
: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. """
def pytest_runtest_setup(item):
""" called before pytest_runtest_call(). """
""" called before ``pytest_runtest_call(item)``. """
def pytest_runtest_call(item):
""" execute test item. """
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item):
""" called after pytest_runtest_call(). """
""" called after ``pytest_runtest_call``. """
def pytest_runtest_makereport(item, call):
""" make a test report for the given item and call outcome. """
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item` and
:py:class:`_pytest.runner.CallInfo`.
"""
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report):
""" process item test 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):
""" called if runtest_teardown_final failed. """
def pytest__teardown_final_logerror(report, session):
""" called if runtest_teardown_final failed. """
# -------------------------------------------------------------------------
# test session related hooks
# test session related hooks
# -------------------------------------------------------------------------
def pytest_sessionstart(session):
@@ -123,8 +166,22 @@ def pytest_sessionstart(session):
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
# -------------------------------------------------------------------------
# hooks for influencing reporting (invoked from pytest_terminal)
# hooks for customising the assert methods
# -------------------------------------------------------------------------
def pytest_assertrepr_compare(config, op, left, right):
"""return explanation for comparisons in failing assert expressions.
Return None for no custom explanation, otherwise return a list
of strings. The strings will be joined by newlines but any newlines
*in* a string will be escaped. Note that all but the first line will
be indented sligthly, the intention is for the first line to be a summary.
"""
# -------------------------------------------------------------------------
# hooks for influencing reporting (invoked from _pytest_terminal)
# -------------------------------------------------------------------------
def pytest_report_header(config):
@@ -137,14 +194,8 @@ pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """
def pytest_report_iteminfo(item):
""" return (fspath, lineno, name) for the item.
the information is used for result display and to sort tests
"""
pytest_report_iteminfo.firstresult = True
# -------------------------------------------------------------------------
# doctest hooks
# doctest hooks
# -------------------------------------------------------------------------
def pytest_doctest_prepare_content(content):
@@ -153,7 +204,7 @@ pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_plugin_registered(plugin, manager):
@@ -167,6 +218,3 @@ def pytest_internalerror(excrepr):
def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """
def pytest_trace(category, msg):
""" called for debug info. """

View File

@@ -1,17 +1,18 @@
"""
logging of test results in JUnit-XML format, for use with Hudson
and build integration servers. Based on initial code from Ross Lawley.
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
Based on initial code from Ross Lawley.
"""
import py
import os
import time
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', action="store", dest="xmlpath",
group.addoption('--junitxml', action="store", dest="xmlpath",
metavar="path", default=None,
help="create junit-xml style report file at given path.")
group.addoption('--junitprefix', action="store", dest="junitprefix",
group.addoption('--junitprefix', action="store", dest="junitprefix",
metavar="str", default=None,
help="prepend prefix to classnames in junit-xml output")
@@ -24,7 +25,7 @@ def pytest_configure(config):
def pytest_unconfigure(config):
xml = getattr(config, '_xml', None)
if xml:
del config._xml
del config._xml
config.pluginmanager.unregister(xml)
class LogXML(object):
@@ -35,14 +36,13 @@ class LogXML(object):
self.passed = self.skipped = 0
self.failed = self.errors = 0
self._durations = {}
def _opentestcase(self, report):
if hasattr(report, 'item'):
node = report.item
else:
node = report.collector
d = {'time': self._durations.pop(node, "0")}
names = [x.replace(".py", "") for x in node.listnames() if x != "()"]
names = report.nodeid.split("::")
names[0] = names[0].replace("/", '.')
names = tuple(names)
d = {'time': self._durations.pop(names, "0")}
names = [x.replace(".py", "") for x in names if x != "()"]
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
@@ -57,7 +57,7 @@ class LogXML(object):
def appendlog(self, fmt, *args):
args = tuple([py.xml.escape(arg) for arg in args])
self.test_logs.append(fmt % args)
def append_pass(self, report):
self.passed += 1
self._opentestcase(report)
@@ -71,7 +71,7 @@ class LogXML(object):
'<skipped message="xfail-marked test passes unexpectedly"/>')
self.skipped += 1
else:
self.appendlog('<failure message="test failure">%s</failure>',
self.appendlog('<failure message="test failure">%s</failure>',
report.longrepr)
self.failed += 1
self._closetestcase()
@@ -79,7 +79,7 @@ class LogXML(object):
def append_collect_failure(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
self.appendlog('<failure message="collection failure">%s</failure>',
self.appendlog('<failure message="collection failure">%s</failure>',
report.longrepr)
self._closetestcase()
self.errors += 1
@@ -94,7 +94,7 @@ class LogXML(object):
def append_error(self, report):
self._opentestcase(report)
self.appendlog('<error message="test setup failure">%s</error>',
self.appendlog('<error message="test setup failure">%s</error>',
report.longrepr)
self._closetestcase()
self.errors += 1
@@ -103,7 +103,7 @@ class LogXML(object):
self._opentestcase(report)
if "xfail" in report.keywords:
self.appendlog(
'<skipped message="expected test failure">%s</skipped>',
'<skipped message="expected test failure">%s</skipped>',
report.keywords['xfail'])
else:
self.appendlog("<skipped/>")
@@ -120,14 +120,15 @@ class LogXML(object):
self.append_failure(report)
elif report.skipped:
self.append_skipped(report)
def pytest_runtest_call(self, item, __multicall__):
names = tuple(item.listnames())
start = time.time()
try:
return __multicall__.execute()
finally:
self._durations[item] = time.time() - start
self._durations[names] = time.time() - start
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:
@@ -151,7 +152,7 @@ class LogXML(object):
logfile = py.std.codecs.open(self.logfile, 'w', encoding='utf-8')
else:
logfile = open(self.logfile, 'w', encoding='utf-8')
suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time
numtests = self.passed + self.failed
@@ -169,5 +170,4 @@ class LogXML(object):
logfile.close()
def pytest_terminal_summary(self, terminalreporter):
tw = terminalreporter._tw
terminalreporter.write_sep("-", "generated xml file: %s" %(self.logfile))
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))

176
_pytest/mark.py Normal file
View File

@@ -0,0 +1,176 @@
""" generic mechanism for marking and selecting python functions. """
import pytest, py
def pytest_namespace():
return {'mark': MarkGenerator()}
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption('-k',
action="store", dest="keyword", default='', metavar="KEYWORDEXPR",
help="only run tests which match given keyword expression. "
"An expression consists of space-separated terms. "
"Each term must match. Precede a term with '-' to negate. "
"Terminate expression with ':' to make the first match match "
"all subsequent tests (usually file-order). ")
def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword
if not keywordexpr:
return
selectuntil = False
if keywordexpr[-1] == ":":
selectuntil = True
keywordexpr = keywordexpr[:-1]
remaining = []
deselected = []
for colitem in items:
if keywordexpr and skipbykeyword(colitem, keywordexpr):
deselected.append(colitem)
else:
remaining.append(colitem)
if selectuntil:
keywordexpr = None
if deselected:
config.hook.pytest_deselected(items=deselected)
items[:] = remaining
def skipbykeyword(colitem, keywordexpr):
""" return True if they given keyword expression means to
skip this collector/item.
"""
if not keywordexpr:
return
itemkeywords = getkeywords(colitem)
for key in filter(None, keywordexpr.split()):
eor = key[:1] == '-'
if eor:
key = key[1:]
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:
if elem in kw:
break
else:
return False
return True
class MarkGenerator:
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``py.test.mark`` singleton instance. Example::
import py
@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)
return MarkDecorator(name)
class MarkDecorator:
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
:ref:`retrieved by hooks as item keywords` MarkDecorator instances
are usually created by writing::
mark1 = py.test.mark.NAME # simple MarkDecorator
mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator
and can then be applied as decorators to test functions::
@mark2
def test_function():
pass
"""
def __init__(self, name, args=None, kwargs=None):
self.markname = name
self.args = args or ()
self.kwargs = kwargs or {}
def __repr__(self):
d = self.__dict__.copy()
name = d.pop('markname')
return "<MarkDecorator %r %r>" %(name, d)
def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
otherwise add *args/**kwargs in-place to mark information. """
if args:
func = args[0]
if len(args) == 1 and hasattr(func, '__call__') or \
hasattr(func, '__bases__'):
if hasattr(func, '__bases__'):
if hasattr(func, 'pytestmark'):
l = func.pytestmark
if not isinstance(l, list):
func.pytestmark = [l, self]
else:
l.append(self)
else:
func.pytestmark = [self]
else:
holder = getattr(func, self.markname, None)
if holder is None:
holder = MarkInfo(self.markname, self.args, self.kwargs)
setattr(func, self.markname, holder)
else:
holder.kwargs.update(self.kwargs)
holder.args += self.args
return func
kw = self.kwargs.copy()
kw.update(kwargs)
args = self.args + args
return self.__class__(self.markname, args=args, kwargs=kw)
class MarkInfo:
""" Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, name, args, kwargs):
#: name of attribute
self.name = name
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs
def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % (
self._name, self.args, self.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))

103
_pytest/monkeypatch.py Normal file
View File

@@ -0,0 +1,103 @@
""" monkeypatching and mocking functionality. """
import os, sys
def pytest_funcarg__monkeypatch(request):
"""The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
"""
mpatch = monkeypatch()
request.addfinalizer(mpatch.undo)
return mpatch
notset = object()
class monkeypatch:
""" object keeping a record of setattr/item/env/syspath changes. """
def __init__(self):
self._setattr = []
self._setitem = []
def setattr(self, obj, name, value, raising=True):
""" set attribute ``name`` on ``obj`` to ``value``, by default
raise AttributeEror if the attribute did not exist. """
oldval = getattr(obj, name, notset)
if raising and oldval is notset:
raise AttributeError("%r has no attribute %r" %(obj, name))
self._setattr.insert(0, (obj, name, oldval))
setattr(obj, name, value)
def delattr(self, obj, name, raising=True):
""" delete attribute ``name`` from ``obj``, by default raise
AttributeError it the attribute did not previously exist. """
if not hasattr(obj, name):
if raising:
raise AttributeError(name)
else:
self._setattr.insert(0, (obj, name, getattr(obj, name, notset)))
delattr(obj, name)
def setitem(self, dic, name, value):
""" set dictionary entry ``name`` to value. """
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
dic[name] = value
def delitem(self, dic, name, raising=True):
""" delete ``name`` from dict, raise KeyError if it doesn't exist."""
if name not in dic:
if raising:
raise KeyError(name)
else:
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
del dic[name]
def setenv(self, name, value, prepend=None):
""" set environment variable ``name`` to ``value``. if ``prepend``
is a character, read the current environment variable value
and prepend the ``value`` adjoined with the ``prepend`` character."""
value = str(value)
if prepend and name in os.environ:
value = value + prepend + os.environ[name]
self.setitem(os.environ, name, value)
def delenv(self, name, raising=True):
""" delete ``name`` from environment, raise KeyError it not exists."""
self.delitem(os.environ, name, raising=raising)
def syspath_prepend(self, path):
""" prepend ``path`` to ``sys.path`` list of import locations. """
if not hasattr(self, '_savesyspath'):
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
def undo(self):
""" undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
you do more monkeypatching after the undo call."""
for obj, name, value in self._setattr:
if value is not notset:
setattr(obj, name, value)
else:
delattr(obj, name)
self._setattr[:] = []
for dictionary, name, value in self._setitem:
if value is notset:
del dictionary[name]
else:
dictionary[name] = value
self._setitem[:] = []
if hasattr(self, '_savesyspath'):
sys.path[:] = self._savesyspath

47
_pytest/nose.py Normal file
View File

@@ -0,0 +1,47 @@
""" run test suites written for nose. """
import pytest, py
import inspect
import sys
def pytest_runtest_makereport(__multicall__, item, call):
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
if SkipTest:
if call.excinfo and call.excinfo.errisinstance(SkipTest):
# let's substitute the excinfo with a py.test.skip one
call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo
def pytest_runtest_setup(item):
if isinstance(item, (pytest.Function)):
if isinstance(item.parent, pytest.Generator):
gen = item.parent
if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup')
if isinstance(gen.parent, pytest.Instance):
call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True
if not call_optional(item.obj, 'setup'):
# call module level setup if there is no object level one
call_optional(item.parent.obj, 'setup')
def pytest_runtest_teardown(item):
if isinstance(item, pytest.Function):
if not call_optional(item.obj, 'teardown'):
call_optional(item.parent.obj, 'teardown')
#if hasattr(item.parent, '_nosegensetup'):
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, pytest.Generator):
call_optional(collector.obj, 'setup')
def call_optional(obj, name):
method = getattr(obj, name, None)
if method:
# If there's any problems allow the exception to raise rather than
# silently ignoring them
method()
return True

View File

@@ -1,24 +1,4 @@
"""
submit failure or test session information to a pastebin service.
Usage
----------
**Creating a URL for each test failure**::
py.test --pastebin=failed
This will submit test run information to a remote Paste service and
provide a URL for each failure. You may select tests as usual or add
for example ``-x`` if you only want to send one particular failure.
**Creating a URL for a whole test session log**::
py.test --pastebin=all
Currently only pasting to the http://paste.pocoo.org service is implemented.
"""
""" submit failure or test session information to a pastebin service. """
import py, sys
class url:
@@ -29,8 +9,8 @@ class url:
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group._addoption('--pastebin', metavar="mode",
action='store', dest="pastebin", default=None,
type="choice", choices=['failed', 'all'],
action='store', dest="pastebin", default=None,
type="choice", choices=['failed', 'all'],
help="send failed|all info to Pocoo pastebin service.")
def pytest_configure(__multicall__, config):
@@ -39,13 +19,13 @@ def pytest_configure(__multicall__, config):
if config.option.pastebin == "all":
config._pastebinfile = tempfile.TemporaryFile('w+')
tr = config.pluginmanager.getplugin('terminalreporter')
oldwrite = tr._tw.write
oldwrite = tr._tw.write
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
config._pastebinfile.write(str(s))
tr._tw.write = tee_write
tr._tw.write = tee_write
def pytest_unconfigure(config):
def pytest_unconfigure(config):
if hasattr(config, '_pastebinfile'):
config._pastebinfile.seek(0)
sessionlog = config._pastebinfile.read()
@@ -56,7 +36,7 @@ def pytest_unconfigure(config):
sys.stderr.write("pastebin session-log: %s\n" % pastebinurl)
tr = config.pluginmanager.getplugin('terminalreporter')
del tr._tw.__dict__['write']
def getproxy():
return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes

76
_pytest/pdb.py Normal file
View File

@@ -0,0 +1,76 @@
""" interactive debugging with PDB, the Python Debugger. """
import pytest, py
import sys
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption('--pdb',
action="store_true", dest="usepdb", default=False,
help="start the interactive Python debugger on errors.")
def pytest_namespace():
return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config):
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
item = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
frame = sys._getframe().f_back
item = getattr(self, 'item', None)
if item is not None:
capman = item.config.pluginmanager.getplugin("capturemanager")
out, err = capman.suspendcapture()
if hasattr(item, 'outerr'):
item.outerr = (item.outerr[0] + out, item.outerr[1] + err)
tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
py.std.pdb.Pdb().set_trace(frame)
def pdbitem(item):
pytestPDB.item = item
pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
def pytest_runtest_makereport():
pytestPDB.item = None
class PdbInvoke:
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, item, call, __multicall__):
rep = __multicall__.execute()
if not call.excinfo or \
call.excinfo.errisinstance(pytest.skip.Exception) or \
call.excinfo.errisinstance(py.std.bdb.BdbQuit):
return rep
if "xfail" in rep.keywords:
return rep
# we assume that the above execute() suspended capturing
tw = py.io.TerminalWriter()
tw.line()
tw.sep(">", "traceback")
rep.toterminal(tw)
tw.sep(">", "entering PDB")
post_mortem(call.excinfo._excinfo[2])
rep._pdbshown = True
return rep
def post_mortem(t):
pdb = py.std.pdb
class Pdb(pdb.Pdb):
def get_stack(self, f, t):
stack, i = pdb.Pdb.get_stack(self, f, t)
if f is None:
i = max(0, len(stack) - 1)
while i and stack[i][0].f_locals.get("__tracebackhide__", False):
i-=1
return stack, i
p = Pdb()
p.reset()
p.interaction(None, t)

View File

@@ -1,23 +1,153 @@
"""
funcargs and support code for testing py.test's own functionality.
"""
""" (disabled by default) support for testing py.test and py.test plugins. """
import py
import py, pytest
import sys, os
import re
import inspect
import time
from py._test.config import Config as pytestConfig
from fnmatch import fnmatch
from _pytest.session import Session
from py.builtin import print_
from _pytest.core import HookRelay
def pytest_addoption(parser):
group = parser.getgroup("pylib")
group.addoption('--tools-on-path',
action="store_true", dest="toolsonpath", default=False,
group.addoption('--no-tools-on-path',
action="store_true", dest="notoolsonpath", default=False,
help=("discover tools on PATH instead of going through py.cmdline.")
)
pytest_plugins = '_pytest'
def pytest_configure(config):
# This might be called multiple times. Only take the first.
global _pytest_fullpath
import pytest
try:
_pytest_fullpath
except NameError:
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
def pytest_funcarg___pytest(request):
return PytestArg(request)
class PytestArg:
def __init__(self, request):
self.request = request
def gethookrecorder(self, hook):
hookrecorder = HookRecorder(hook._pm)
hookrecorder.start_recording(hook._hookspecs)
self.request.addfinalizer(hookrecorder.finish_recording)
return hookrecorder
class ParsedCall:
def __init__(self, name, locals):
assert '_name' not in locals
self.__dict__.update(locals)
self.__dict__.pop('self')
self._name = name
def __repr__(self):
d = self.__dict__.copy()
del d['_name']
return "<ParsedCall %r(**%r)>" %(self._name, d)
class HookRecorder:
def __init__(self, pluginmanager):
self._pluginmanager = pluginmanager
self.calls = []
self._recorders = {}
def start_recording(self, hookspecs):
if not isinstance(hookspecs, (list, tuple)):
hookspecs = [hookspecs]
for hookspec in hookspecs:
assert hookspec not in self._recorders
class RecordCalls:
_recorder = self
for name, method in vars(hookspec).items():
if name[0] != "_":
setattr(RecordCalls, name, self._makecallparser(method))
recorder = RecordCalls()
self._recorders[hookspec] = recorder
self._pluginmanager.register(recorder)
self.hook = HookRelay(hookspecs, pm=self._pluginmanager,
prefix="pytest_")
def finish_recording(self):
for recorder in self._recorders.values():
self._pluginmanager.unregister(recorder)
self._recorders.clear()
def _makecallparser(self, method):
name = method.__name__
args, varargs, varkw, default = py.std.inspect.getargspec(method)
if not args or args[0] != "self":
args.insert(0, 'self')
fspec = py.std.inspect.formatargspec(args, varargs, varkw, default)
# we use exec because we want to have early type
# errors on wrong input arguments, using
# *args/**kwargs delays this and gives errors
# elsewhere
exec (py.code.compile("""
def %(name)s%(fspec)s:
self._recorder.calls.append(
ParsedCall(%(name)r, locals()))
""" % locals()))
return locals()[name]
def getcalls(self, names):
if isinstance(names, str):
names = names.split()
for name in names:
for cls in self._recorders:
if name in vars(cls):
break
else:
raise ValueError("callname %r not found in %r" %(
name, self._recorders.keys()))
l = []
for call in self.calls:
if call._name in names:
l.append(call)
return l
def contains(self, entries):
__tracebackhide__ = True
from py.builtin import print_
i = 0
entries = list(entries)
backlocals = py.std.sys._getframe(1).f_locals
while entries:
name, check = entries.pop(0)
for ind, call in enumerate(self.calls[i:]):
if call._name == name:
print_("NAMEMATCH", name, call)
if eval(check, backlocals, call.__dict__):
print_("CHECKERMATCH", repr(check), "->", call)
else:
print_("NOCHECKERMATCH", repr(check), "-", call)
continue
i += ind + 1
break
print_("NONAMEMATCH", name, "with", call)
else:
py.test.fail("could not find %r check %r" % (name, check))
def popcall(self, name):
__tracebackhide__ = True
for i, call in enumerate(self.calls):
if call._name == name:
del self.calls[i]
return call
lines = ["could not find call %r, in:" % (name,)]
lines.extend([" %s" % str(x) for x in self.calls])
py.test.fail("\n".join(lines))
def getcall(self, name):
l = self.getcalls(name)
assert len(l) == 1, (name, l)
return l[0]
def pytest_funcarg__linecomp(request):
return LineComp()
@@ -52,9 +182,10 @@ class RunResult:
class TmpTestdir:
def __init__(self, request):
self.request = request
self.Config = request.config.__class__
self._pytest = request.getfuncargvalue("_pytest")
# XXX remove duplication with tmpdir plugin
basetmp = request.config.ensuretemp("testdir")
# XXX remove duplication with tmpdir plugin
basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
name = request.function.__name__
for i in range(100):
try:
@@ -74,11 +205,6 @@ class TmpTestdir:
def __repr__(self):
return "<TmpTestdir %r>" % (self.tmpdir,)
def Config(self, topdir=None):
if topdir is None:
topdir = self.tmpdir.dirpath()
return pytestConfig(topdir=topdir)
def finalize(self):
for p in self._syspathremove:
py.std.sys.path.remove(p)
@@ -105,7 +231,7 @@ class TmpTestdir:
def chdir(self):
old = self.tmpdir.chdir()
if not hasattr(self, '_olddir'):
self._olddir = old
self._olddir = old
def _makefile(self, ext, args, kwargs):
items = list(kwargs.items())
@@ -120,15 +246,25 @@ class TmpTestdir:
p.write(source.encode("utf-8"), "wb")
if ret is None:
ret = p
return ret
return ret
def makefile(self, ext, *args, **kwargs):
return self._makefile(ext, args, kwargs)
def makeini(self, source):
return self.makefile('cfg', setup=source)
def makeconftest(self, source):
return self.makepyfile(conftest=source)
def makeini(self, source):
return self.makefile('.ini', tox=source)
def getinicfg(self, source):
p = self.makeini(source)
return py.iniconfig.IniConfig(p)['pytest']
def makepyfile(self, *args, **kwargs):
return self._makefile('.py', args, kwargs)
@@ -140,7 +276,7 @@ class TmpTestdir:
path = self.tmpdir
py.std.sys.path.insert(0, str(path))
self._syspathremove.append(str(path))
def mkdir(self, name):
return self.tmpdir.mkdir(name)
@@ -149,22 +285,39 @@ class TmpTestdir:
p.ensure("__init__.py")
return p
Session = Session
def getnode(self, config, arg):
session = Session(config)
assert '::' not in str(arg)
p = py.path.local(arg)
x = session.fspath.bestrelpath(p)
return session.perform_collect([x], genitems=False)[0]
def getpathnode(self, path):
config = self.parseconfig(path)
session = Session(config)
x = session.fspath.bestrelpath(path)
return session.perform_collect([x], genitems=False)[0]
def genitems(self, colitems):
return list(self.session.genitems(colitems))
session = colitems[0].session
result = []
for colitem in colitems:
result.extend(session.genitems(colitem))
return result
def inline_genitems(self, *args):
#config = self.parseconfig(*args)
config = self.parseconfig(*args)
session = config.initsession()
config = self.parseconfigure(*args)
rec = self.getreportrecorder(config)
colitems = [config.getnode(arg) for arg in config.args]
items = list(session.genitems(colitems))
return items, rec
session = Session(config)
session.perform_collect()
return session.items, rec
def runitem(self, source):
# used from runner functional tests
# used from runner functional tests
item = self.getitem(source)
# the test class where we are called from wants to provide the runner
# the test class where we are called from wants to provide the runner
testclassinstance = py.builtin._getimself(self.request.function)
runner = testclassinstance.getrunner()
return runner(item)
@@ -181,19 +334,17 @@ class TmpTestdir:
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 1, reports
assert len(reports) == 1, reports
return reports[0]
def inline_run(self, *args):
args = ("-s", ) + args # otherwise FD leakage
config = self.parseconfig(*args)
config.pluginmanager.do_configure(config)
session = config.initsession()
reprec = self.getreportrecorder(config)
colitems = config.getinitialnodes()
session.main(colitems)
config.pluginmanager.do_unconfigure(config)
return reprec
#config.pluginmanager.do_configure(config)
config.hook.pytest_cmdline_main(config=config)
#config.pluginmanager.do_unconfigure(config)
return reprec
def config_preparse(self):
config = self.Config()
@@ -202,7 +353,7 @@ class TmpTestdir:
config.pluginmanager.import_plugin(plugin)
else:
if isinstance(plugin, dict):
plugin = PseudoPlugin(plugin)
plugin = PseudoPlugin(plugin)
if not config.pluginmanager.isregistered(plugin):
config.pluginmanager.register(plugin)
return config
@@ -211,74 +362,73 @@ class TmpTestdir:
if not args:
args = (self.tmpdir,)
config = self.config_preparse()
args = list(args) + ["--basetemp=%s" % self.tmpdir.dirpath('basetemp')]
args = list(args)
for x in args:
if str(x).startswith('--basetemp'):
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
config.parse(args)
return 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]
from py._test import config
oldconfig = config.config_per_process # py.test.config
oldconfig = getattr(py.test, 'config', None)
try:
c = config.config_per_process = py.test.config = pytestConfig()
c.basetemp = oldconfig.mktemp("reparse", numbered=True)
c.parse(args)
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:
config.config_per_process = py.test.config = oldconfig
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))
return config
def getitem(self, source, funcname="test_func"):
modcol = self.getmodulecol(source)
moditems = modcol.collect()
for item in modcol.collect():
for item in self.getitems(source):
if item.name == funcname:
return item
else:
assert 0, "%r item not found in module:\n%s" %(funcname, source)
return item
assert 0, "%r item not found in module:\n%s" %(funcname, source)
def getitems(self, source):
modcol = self.getmodulecol(source)
return list(modcol.config.initsession().genitems([modcol]))
#assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
#return item
def getfscol(self, path, configargs=()):
self.config = self.parseconfig(path, *configargs)
self.session = self.config.initsession()
return self.config.getnode(path)
return self.genitems([modcol])
def getmodulecol(self, source, configargs=(), withinit=False):
kw = {self.request.function.__name__: py.code.Source(source).strip()}
path = self.makepyfile(**kw)
if withinit:
self.makepyfile(__init__ = "#")
self.config = self.parseconfig(path, *configargs)
self.session = self.config.initsession()
#self.config.pluginmanager.do_configure(config=self.config)
# XXX
self.config.pluginmanager.import_plugin("runner")
plugin = self.config.pluginmanager.getplugin("runner")
plugin.pytest_configure(config=self.config)
self.config = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path)
#config.pluginmanager.do_unconfigure(config)
return node
return self.config.getnode(path)
def collect_by_name(self, modcol, name):
for colitem in modcol._memocollect():
if colitem.name == name:
return colitem
def popen(self, cmdargs, stdout, stderr, **kw):
if not hasattr(py.std, 'subprocess'):
py.test.skip("no subprocess module")
env = os.environ.copy()
env['PYTHONPATH'] = ":".join(filter(None, [
env['PYTHONPATH'] = os.pathsep.join(filter(None, [
str(os.getcwd()), env.get('PYTHONPATH', '')]))
kw['env'] = env
#print "env", env
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()
def run(self, *cmdargs):
return self._run(*cmdargs)
@@ -290,7 +440,7 @@ class TmpTestdir:
f1 = p1.open("wb")
f2 = p2.open("wb")
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
ret = popen.wait()
f1.close()
@@ -314,26 +464,23 @@ class TmpTestdir:
return self.run(*fullargs)
def _getpybinargs(self, scriptname):
if self.request.config.getvalue("toolsonpath"):
script = py.path.local.sysfind(scriptname)
assert script, "script %r not found" % scriptname
return (script,)
if not self.request.config.getvalue("notoolsonpath"):
# XXX we rely on script refering to the correct environment
# we cannot use "(py.std.sys.executable,script)"
# becaue on windows the script is e.g. a py.test.exe
return (py.std.sys.executable, _pytest_fullpath,)
else:
cmdlinename = scriptname.replace(".", "")
assert hasattr(py.cmdline, cmdlinename), cmdlinename
source = ("import sys;sys.path.insert(0,%r);"
"import py;py.cmdline.%s()" %
(str(py._pydir.dirpath()), cmdlinename))
return (sys.executable, "-c", source,)
py.test.skip("cannot run %r with --no-tools-on-path" % scriptname)
def runpython(self, script):
s = self._getsysprepend()
if s:
script.write(s + "\n" + script.read())
def runpython(self, script, prepend=True):
if prepend:
s = self._getsysprepend()
if s:
script.write(s + "\n" + script.read())
return self.run(sys.executable, script)
def _getsysprepend(self):
if not self.request.config.getvalue("toolsonpath"):
if self.request.config.getvalue("notoolsonpath"):
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
else:
s = ""
@@ -344,27 +491,32 @@ class TmpTestdir:
return self.run(py.std.sys.executable, "-c", command)
def runpytest(self, *args):
p = py.path.local.make_numbered_dir(prefix="runpytest-",
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
for x in args:
if '--confcutdir' in str(x):
break
else:
args = ('--confcutdir=.',) + args
args = ('--basetemp=%s' % p, ) + args
#for x in args:
# if '--confcutdir' in str(x):
# break
#else:
# pass
# args = ('--confcutdir=.',) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
if plugins:
args = ('-p', plugins[0]) + args
return self.runpybin("py.test", *args)
def spawn_pytest(self, string, expect_timeout=10.0):
pexpect = py.test.importorskip("pexpect", "2.4")
if not self.request.config.getvalue("toolsonpath"):
py.test.skip("need --tools-on-path to run py.test script")
if self.request.config.getvalue("notoolsonpath"):
py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
basetemp = self.tmpdir.mkdir("pexpect")
invoke = self._getpybinargs("py.test")[0]
invoke = " ".join(map(str, self._getpybinargs("py.test")))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
child = pexpect.spawn(cmd, logfile=basetemp.join("spawn.out").open("w"))
return self.spawn(cmd, expect_timeout=expect_timeout)
def spawn(self, cmd, expect_timeout=10.0):
pexpect = py.test.importorskip("pexpect", "2.4")
logfile = self.tmpdir.join("spawn.out")
child = pexpect.spawn(cmd, logfile=logfile.open("w"))
child.timeout = expect_timeout
return child
@@ -377,13 +529,13 @@ def getdecoded(out):
class PseudoPlugin:
def __init__(self, vars):
self.__dict__.update(vars)
self.__dict__.update(vars)
class ReportRecorder(object):
def __init__(self, hook):
self.hook = hook
self.registry = hook._registry
self.registry.register(self)
self.pluginmanager = hook._pm
self.pluginmanager.register(self)
def getcall(self, name):
return self.hookrecorder.getcall(name)
@@ -395,17 +547,18 @@ class ReportRecorder(object):
""" return list of ParsedCall instances matching the given eventname. """
return self.hookrecorder.getcalls(names)
# functionality for test reports
# functionality for test reports
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"):
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):
colitem = rep.getnode()
if not inamepart or inamepart in colitem.listnames():
if when and getattr(rep, 'when', None) != when:
continue
if not inamepart or inamepart in rep.nodeid.split("::"):
l.append(rep)
if not l:
raise ValueError("could not find test report matching %r: no test reports at all!" %
@@ -426,14 +579,14 @@ class ReportRecorder(object):
skipped = []
failed = []
for rep in self.getreports("pytest_runtest_logreport"):
if rep.passed:
if rep.when == "call":
passed.append(rep)
elif rep.skipped:
skipped.append(rep)
if rep.passed:
if rep.when == "call":
passed.append(rep)
elif rep.skipped:
skipped.append(rep)
elif rep.failed:
failed.append(rep)
return passed, skipped, failed
failed.append(rep)
return passed, skipped, failed
def countoutcomes(self):
return [len(x) for x in self.listoutcomes()]
@@ -448,7 +601,7 @@ class ReportRecorder(object):
self.hookrecorder.calls[:] = []
def unregister(self):
self.registry.unregister(self)
self.pluginmanager.unregister(self)
self.hookrecorder.finish_recording()
class LineComp:
@@ -456,7 +609,7 @@ class LineComp:
self.stringio = py.io.TextIO()
def assert_contains_lines(self, lines2):
""" assert that lines2 are contained (linearly) in lines1.
""" assert that lines2 are contained (linearly) in lines1.
return a list of extralines found.
"""
__tracebackhide__ = True
@@ -465,7 +618,7 @@ class LineComp:
self.stringio.seek(0)
lines1 = val.split("\n")
return LineMatcher(lines1).fnmatch_lines(lines2)
class LineMatcher:
def __init__(self, lines):
self.lines = lines
@@ -473,13 +626,27 @@ class LineMatcher:
def str(self):
return "\n".join(self.lines)
def fnmatch_lines(self, lines2):
def _getlines(self, lines2):
if isinstance(lines2, str):
lines2 = py.code.Source(lines2)
if isinstance(lines2, py.code.Source):
lines2 = lines2.strip().lines
return lines2
from fnmatch import fnmatch
def fnmatch_lines_random(self, lines2):
lines2 = self._getlines(lines2)
for line in lines2:
for x in self.lines:
if line == x or fnmatch(x, line):
print_("matched: ", repr(line))
break
else:
raise ValueError("line %r not found in output" % line)
def fnmatch_lines(self, lines2):
def show(arg1, arg2):
py.builtin.print_(arg1, arg2, file=py.std.sys.stderr)
lines2 = self._getlines(lines2)
lines1 = self.lines[:]
nextline = None
extralines = []
@@ -489,17 +656,17 @@ class LineMatcher:
while lines1:
nextline = lines1.pop(0)
if line == nextline:
print_("exact match:", repr(line))
break
show("exact match:", repr(line))
break
elif fnmatch(nextline, line):
print_("fnmatch:", repr(line))
print_(" with:", repr(nextline))
show("fnmatch:", repr(line))
show(" with:", repr(nextline))
break
else:
if not nomatchprinted:
print_("nomatch:", repr(line))
show("nomatch:", repr(line))
nomatchprinted = True
print_(" and:", repr(nextline))
show(" and:", repr(nextline))
extralines.append(nextline)
else:
assert line == nextline
py.test.fail("remains unmatched: %r, see stderr" % (line,))

851
_pytest/python.py Normal file
View File

@@ -0,0 +1,851 @@
""" Python test discovery, setup and run of test functions. """
import py
import inspect
import sys
import pytest
from py._code.code import TerminalRepr
import _pytest
cutdir = py.path.local(_pytest.__file__).dirpath()
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption('--funcargs',
action="store_true", dest="showfuncargs", default=False,
help="show available function arguments, sorted by plugin")
parser.addini("python_files", type="args",
default=('test_*.py', '*_test.py'),
help="glob-style file patterns for Python test module discovery")
parser.addini("python_classes", type="args", default=("Test",),
help="prefixes for Python test class discovery")
parser.addini("python_functions", type="args", default=("test",),
help="prefixes for Python test function and method discovery")
def pytest_cmdline_main(config):
if config.option.showfuncargs:
showfuncargs(config)
return 0
@pytest.mark.trylast
def pytest_namespace():
raises.Exception = pytest.fail.Exception
return {
'raises' : raises,
'collect': {
'Module': Module, 'Class': Class, 'Instance': Instance,
'Function': Function, 'Generator': Generator,
'_fillfuncargs': fillfuncargs}
}
def pytest_funcarg__pytestconfig(request):
""" the pytest config object with access to command line opts."""
return request.config
def pytest_pyfunc_call(__multicall__, pyfuncitem):
if not __multicall__.execute():
testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction():
testfunction(*pyfuncitem._args)
else:
funcargs = pyfuncitem.funcargs
testfunction(**funcargs)
def pytest_collect_file(path, parent):
ext = path.ext
pb = path.purebasename
if ext == ".py":
if not parent.session.isinitpath(path):
for pat in parent.config.getini('python_files'):
if path.fnmatch(pat):
break
else:
return
return parent.ihook.pytest_pycollect_makemodule(
path=path, parent=parent)
def pytest_pycollect_makemodule(path, parent):
return Module(path, parent)
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
res = __multicall__.execute()
if res is not None:
return res
if collector._istestclasscandidate(name, obj):
#if hasattr(collector.obj, 'unittest'):
# return # we assume it's a mixin class for a TestCase derived one
return Class(name, parent=collector)
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
if is_generator(obj):
return Generator(name, parent=collector)
else:
return collector._genfunctions(name, obj)
def is_generator(func):
try:
return py.code.getrawcode(func).co_flags & 32 # generator function
except AttributeError: # builtin functions have no bytecode
# assume them to not be generators
return False
class PyobjMixin(object):
def obj():
def fget(self):
try:
return self._obj
except AttributeError:
self._obj = obj = self._getobj()
return obj
def fset(self, value):
self._obj = value
return property(fget, fset, None, "underlying python object")
obj = obj()
def _getobj(self):
return getattr(self.parent.obj, self.name)
def getmodpath(self, stopatmodule=True, includemodule=False):
""" return python path relative to the containing module. """
chain = self.listchain()
chain.reverse()
parts = []
for node in chain:
if isinstance(node, Instance):
continue
name = node.name
if isinstance(node, Module):
assert name.endswith(".py")
name = name[:-3]
if stopatmodule:
if includemodule:
parts.append(name)
break
parts.append(name)
parts.reverse()
s = ".".join(parts)
return s.replace(".[", "[")
def _getfslineno(self):
try:
return self._fslineno
except AttributeError:
pass
obj = self.obj
# xxx let decorators etc specify a sane ordering
if hasattr(obj, 'place_as'):
obj = obj.place_as
self._fslineno = py.code.getfslineno(obj)
return self._fslineno
def reportinfo(self):
# XXX caching?
obj = self.obj
if hasattr(obj, 'compat_co_firstlineno'):
# nose compatibility
fspath = sys.modules[obj.__module__].__file__
if fspath.endswith(".pyc"):
fspath = fspath[:-1]
#assert 0
#fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
lineno = obj.compat_co_firstlineno
modpath = obj.__module__
else:
fspath, lineno = self._getfslineno()
modpath = self.getmodpath()
return fspath, lineno, modpath
class PyCollectorMixin(PyobjMixin, pytest.Collector):
def funcnamefilter(self, name):
for prefix in self.config.getini("python_functions"):
if name.startswith(prefix):
return True
def classnamefilter(self, name):
for prefix in self.config.getini("python_classes"):
if name.startswith(prefix):
return True
def collect(self):
# NB. we avoid random getattrs and peek in the __dict__ instead
# (XXX originally introduced from a PyPy need, still true?)
dicts = [getattr(self.obj, '__dict__', {})]
for basecls in inspect.getmro(self.obj.__class__):
dicts.append(basecls.__dict__)
seen = {}
l = []
for dic in dicts:
for name, obj in dic.items():
if name in seen:
continue
seen[name] = True
if name[0] != "_":
res = self.makeitem(name, obj)
if res is None:
continue
if not isinstance(res, list):
res = [res]
l.extend(res)
l.sort(key=lambda item: item.reportinfo()[:2])
return l
def makeitem(self, name, obj):
return self.ihook.pytest_pycollect_makeitem(
collector=self, name=name, obj=obj)
def _istestclasscandidate(self, name, obj):
if self.classnamefilter(name) and \
inspect.isclass(obj):
if hasinit(obj):
# XXX WARN
return False
return True
def _genfunctions(self, name, funcobj):
module = self.getparent(Module).obj
clscol = self.getparent(Class)
cls = clscol and clscol.obj or None
metafunc = Metafunc(funcobj, config=self.config,
cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests
extra = [module]
if cls is not None:
extra.append(cls())
plugins = self.getplugins() + extra
gentesthook.pcall(plugins, metafunc=metafunc)
if not metafunc._calls:
return Function(name, parent=self)
l = []
for callspec in metafunc._calls:
subname = "%s[%s]" %(name, callspec.id)
function = Function(name=subname, parent=self,
callspec=callspec, callobj=funcobj, keywords={callspec.id:True})
l.append(function)
return l
class Module(pytest.File, PyCollectorMixin):
def _getobj(self):
return self._memoizedcall('_obj', self._importtestmodule)
def _importtestmodule(self):
# we assume we are only called once per module
try:
mod = self.fspath.pyimport(ensuresyspath=True)
except SyntaxError:
excinfo = py.code.ExceptionInfo()
raise self.CollectError(excinfo.getrepr(style="short"))
except self.fspath.ImportMismatchError:
e = sys.exc_info()[1]
raise self.CollectError(
"import file mismatch:\n"
"imported module %r has this __file__ attribute:\n"
" %s\n"
"which is not the same as the test file we want to collect:\n"
" %s\n"
"HINT: use a unique basename for your test file modules"
% e.args
)
#print "imported test module", mod
self.config.pluginmanager.consider_module(mod)
return mod
def setup(self):
if hasattr(self.obj, 'setup_module'):
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, its probably a pytest style one
# so we pass the current module object
if inspect.getargspec(self.obj.setup_module)[0]:
self.obj.setup_module(self.obj)
else:
self.obj.setup_module()
def teardown(self):
if hasattr(self.obj, 'teardown_module'):
#XXX: nose compat hack, move to nose plugin
# if it takes a positional arg, its probably a py.test style one
# so we pass the current module object
if inspect.getargspec(self.obj.teardown_module)[0]:
self.obj.teardown_module(self.obj)
else:
self.obj.teardown_module()
class Class(PyCollectorMixin, pytest.Collector):
def collect(self):
return [Instance(name="()", parent=self)]
def setup(self):
setup_class = getattr(self.obj, 'setup_class', None)
if setup_class is not None:
setup_class = getattr(setup_class, 'im_func', setup_class)
setup_class(self.obj)
def teardown(self):
teardown_class = getattr(self.obj, 'teardown_class', None)
if teardown_class is not None:
teardown_class = getattr(teardown_class, 'im_func', teardown_class)
teardown_class(self.obj)
class Instance(PyCollectorMixin, pytest.Collector):
def _getobj(self):
return self.parent.obj()
def newinstance(self):
self.obj = self._getobj()
return self.obj
class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator.
"""
def setup(self):
""" perform setup for this test function. """
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
if isinstance(self.parent, Instance):
obj = self.parent.newinstance()
self.obj = self._getobj()
else:
obj = self.parent.obj
setup_func_or_method = getattr(obj, name, None)
if setup_func_or_method is not None:
setup_func_or_method(self.obj)
def teardown(self):
""" perform teardown for this test function. """
if inspect.ismethod(self.obj):
name = 'teardown_method'
else:
name = 'teardown_function'
obj = self.parent.obj
teardown_func_or_meth = getattr(obj, name, None)
if teardown_func_or_meth is not None:
teardown_func_or_meth(self.obj)
def _prunetraceback(self, excinfo):
if hasattr(self, '_obj') and not self.config.option.fulltrace:
code = py.code.Code(self.obj)
path, firstlineno = code.path, code.firstlineno
traceback = excinfo.traceback
ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
if ntraceback == traceback:
ntraceback = ntraceback.cut(path=path)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=cutdir)
excinfo.traceback = ntraceback.filter()
def _repr_failure_py(self, excinfo, style="long"):
if excinfo.errisinstance(FuncargRequest.LookupError):
fspath, lineno, msg = self.reportinfo()
lines, _ = inspect.getsourcelines(self.obj)
for i, line in enumerate(lines):
if line.strip().startswith('def'):
return FuncargLookupErrorRepr(fspath, lineno,
lines[:i+1], str(excinfo.value))
if excinfo.errisinstance(pytest.fail.Exception):
if not excinfo.value.pytrace:
return str(excinfo.value)
return super(FunctionMixin, self)._repr_failure_py(excinfo,
style=style)
def repr_failure(self, excinfo, outerr=None):
assert outerr is None, "XXX outerr usage is deprecated"
return self._repr_failure_py(excinfo,
style=self.config.option.tbstyle)
class FuncargLookupErrorRepr(TerminalRepr):
def __init__(self, filename, firstlineno, deflines, errorstring):
self.deflines = deflines
self.errorstring = errorstring
self.filename = filename
self.firstlineno = firstlineno
def toterminal(self, tw):
tw.line()
for line in self.deflines:
tw.line(" " + line.strip())
for line in self.errorstring.split("\n"):
tw.line(" " + line.strip(), red=True)
tw.line()
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
def collect(self):
# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
self.config._setupstate.prepare(self)
l = []
seen = {}
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not py.builtin.callable(call):
raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
if name is None:
name = "[%d]" % i
else:
name = "['%s']" % name
if name in seen:
raise ValueError("%r generated tests with non-unique name %r" %(self, name))
seen[name] = True
l.append(Function(name, self, args=args, callobj=call))
return l
def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
# explict naming
if isinstance(obj[0], py.builtin._basestring):
name = obj[0]
obj = obj[1:]
else:
name = None
call, args = obj[0], obj[1:]
return name, call, args
#
# Test Items
#
_dummy = object()
class Function(FunctionMixin, pytest.Item):
""" a Function Item is responsible for setting up
and executing a Python callable test object.
"""
_genid = None
def __init__(self, name, parent=None, args=None, config=None,
callspec=None, callobj=_dummy, keywords=None, session=None):
super(Function, self).__init__(name, parent,
config=config, session=session)
self._args = args
if self._isyieldedfunction():
assert not callspec, (
"yielded functions (deprecated) cannot have funcargs")
else:
if callspec is not None:
self.funcargs = callspec.funcargs or {}
self._genid = callspec.id
if hasattr(callspec, "param"):
self._requestparam = callspec.param
else:
self.funcargs = {}
if callobj is not _dummy:
self._obj = callobj
self.function = getattr(self.obj, 'im_func', self.obj)
self.keywords.update(py.builtin._getfuncdict(self.obj) or {})
if keywords:
self.keywords.update(keywords)
def _getobj(self):
name = self.name
i = name.find("[") # parametrization
if i != -1:
name = name[:i]
return getattr(self.parent.obj, name)
def _isyieldedfunction(self):
return self._args is not None
def runtest(self):
""" execute the underlying test function. """
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
def setup(self):
super(Function, self).setup()
if hasattr(self, 'funcargs'):
fillfuncargs(self)
def __eq__(self, other):
try:
return (self.name == other.name and
self._args == other._args and
self.parent == other.parent and
self.obj == other.obj and
getattr(self, '_genid', None) ==
getattr(other, '_genid', None)
)
except AttributeError:
pass
return False
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash((self.parent, self.name))
def hasinit(obj):
init = getattr(obj, '__init__', None)
if init:
if init != object.__init__:
return True
def getfuncargnames(function):
# XXX merge with main.py's varnames
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
startindex = py.std.inspect.ismethod(function) and 1 or 0
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)
if numdefaults:
return argnames[startindex:-numdefaults]
return argnames[startindex:]
def fillfuncargs(function):
""" fill missing funcargs. """
request = FuncargRequest(pyfuncitem=function)
request._fillfuncargs()
_notexists = object()
class CallSpec:
def __init__(self, funcargs, id, param):
self.funcargs = funcargs
self.id = id
if param is not _notexists:
self.param = param
def __repr__(self):
return "<CallSpec id=%r param=%r funcargs=%r>" %(
self.id, getattr(self, 'param', '?'), self.funcargs)
class Metafunc:
def __init__(self, function, config=None, cls=None, module=None):
self.config = config
self.module = module
self.function = function
self.funcargnames = getfuncargnames(function)
self.cls = cls
self.module = module
self._calls = []
self._ids = py.builtin.set()
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
""" add a new call to the underlying test function during the
collection phase of a test run.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` the length of the currently
list of calls to the test function will be used.
:arg param: will be exposed to a later funcarg factory invocation
through the ``request.param`` attribute. Setting it (instead of
directly providing a ``funcargs`` ditionary) is called
*indirect parametrization*. Indirect parametrization is
preferable if test values are expensive to setup or can
only be created after certain fixtures or test-run related
initialization code has been run.
"""
assert funcargs is None or isinstance(funcargs, dict)
if id is None:
raise ValueError("id=None not allowed")
if id is _notexists:
id = len(self._calls)
id = str(id)
if id in self._ids:
raise ValueError("duplicate id %r" % id)
self._ids.add(id)
self._calls.append(CallSpec(funcargs, id, param))
class FuncargRequest:
""" A request for function arguments from a test function. """
_argprefix = "pytest_funcarg__"
_argname = None
class LookupError(LookupError):
""" error on performing funcarg request. """
def __init__(self, pyfuncitem):
self._pyfuncitem = pyfuncitem
if hasattr(pyfuncitem, '_requestparam'):
self.param = pyfuncitem._requestparam
extra = [obj for obj in (self.module, self.instance) if obj]
self._plugins = pyfuncitem.getplugins() + extra
self._funcargs = self._pyfuncitem.funcargs.copy()
self._name2factory = {}
self._currentarg = None
@property
def function(self):
""" function object of the test invocation. """
return self._pyfuncitem.obj
@property
def keywords(self):
""" keywords of the test function item.
.. versionadded:: 2.0
"""
return self._pyfuncitem.keywords
@property
def module(self):
""" module where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
@property
def cls(self):
""" class (can be None) where the test function was collected. """
clscol = self._pyfuncitem.getparent(pytest.Class)
if clscol:
return clscol.obj
@property
def instance(self):
""" instance (can be None) on which test function was collected. """
return py.builtin._getimself(self.function)
@property
def config(self):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@property
def fspath(self):
""" the file system path of the test module which collected this test. """
return self._pyfuncitem.fspath
def _fillfuncargs(self):
argnames = getfuncargnames(self.function)
if argnames:
assert not getattr(self._pyfuncitem, '_args', None), (
"yielded functions cannot have funcargs")
for argname in argnames:
if argname not in self._pyfuncitem.funcargs:
self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
def applymarker(self, marker):
""" apply a marker to a single test function invocation.
This method is useful if you don't want to have a keyword/marker
on all function invocations.
:arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
created by a call to ``py.test.mark.NAME(...)``.
"""
if not isinstance(marker, py.test.mark.XYZ.__class__):
raise ValueError("%r is not a py.test.mark.* object")
self._pyfuncitem.keywords[marker.markname] = marker
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource.
:arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource.
:arg scope: a string value out of ``function``, ``class``, ``module``
or ``session`` indicating the caching lifecycle of the resource.
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {} # XXX weakref?
cachekey = (self._currentarg, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
val = setup()
cache[cachekey] = val
if teardown is not None:
def finalizer():
del cache[cachekey]
teardown(val)
self._addfinalizer(finalizer, scope=scope)
return val
def getfuncargvalue(self, argname):
""" Retrieve a function argument by name for this test
function invocation. This allows one function argument factory
to call another function argument factory. If there are two
funcarg factories for the same test function argument the first
factory may use ``getfuncargvalue`` to call the second one and
do something additional with the resource.
"""
try:
return self._funcargs[argname]
except KeyError:
pass
if argname not in self._name2factory:
self._name2factory[argname] = self.config.pluginmanager.listattr(
plugins=self._plugins,
attrname=self._argprefix + str(argname)
)
#else: we are called recursively
if not self._name2factory[argname]:
self._raiselookupfailed(argname)
funcargfactory = self._name2factory[argname].pop()
oldarg = self._currentarg
self._currentarg = argname
try:
self._funcargs[argname] = res = funcargfactory(request=self)
finally:
self._currentarg = oldarg
return res
def _getscopeitem(self, scope):
if scope == "function":
return self._pyfuncitem
elif scope == "session":
return None
elif scope == "class":
x = self._pyfuncitem.getparent(pytest.Class)
if x is not None:
return x
scope = "module"
if scope == "module":
return self._pyfuncitem.getparent(pytest.Module)
raise ValueError("unknown finalization scope %r" %(scope,))
def addfinalizer(self, finalizer):
"""add finalizer function to be called after test function
finished execution. """
self._addfinalizer(finalizer, scope="function")
def _addfinalizer(self, finalizer, scope):
colitem = self._getscopeitem(scope)
self.config._setupstate.addfinalizer(
finalizer=finalizer, colitem=colitem)
def __repr__(self):
return "<FuncargRequest for %r>" %(self._pyfuncitem)
def _raiselookupfailed(self, argname):
available = []
for plugin in self._plugins:
for name in vars(plugin):
if name.startswith(self._argprefix):
name = name[len(self._argprefix):]
if name not in available:
available.append(name)
fspath, lineno, msg = self._pyfuncitem.reportinfo()
msg = "LookupError: no factory found for function argument %r" % (argname,)
msg += "\n available funcargs: %s" %(", ".join(available),)
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
raise self.LookupError(msg)
def showfuncargs(config):
from _pytest.session import Session
session = Session(config)
session.perform_collect()
if session.items:
plugins = session.items[0].getplugins()
else:
plugins = session.getplugins()
curdir = py.path.local()
tw = py.io.TerminalWriter()
verbose = config.getvalue("verbose")
for plugin in plugins:
available = []
for name, factory in vars(plugin).items():
if name.startswith(FuncargRequest._argprefix):
name = name[len(FuncargRequest._argprefix):]
if name not in available:
available.append([name, factory])
if available:
pluginname = plugin.__name__
for name, factory in available:
loc = getlocation(factory, curdir)
if verbose:
funcargspec = "%s -- %s" %(name, loc,)
else:
funcargspec = name
tw.line(funcargspec, green=True)
doc = factory.__doc__ or ""
if doc:
for line in doc.split("\n"):
tw.line(" " + line.strip())
else:
tw.line(" %s: no docstring available" %(loc,),
red=True)
def getlocation(function, curdir):
import inspect
fn = py.path.local(inspect.getfile(function))
lineno = py.builtin._getcode(function).co_firstlineno
if fn.relto(curdir):
fn = fn.relto(curdir)
return "%s:%d" %(fn, lineno+1)
# builtin pytest.raises helper
def raises(ExpectedException, *args, **kwargs):
""" assert that a code block/function call raises @ExpectedException
and raise a failure exception otherwise.
If using Python 2.5 or above, you may use this function as a
context manager::
>>> with raises(ZeroDivisionError):
... 1/0
Or you can specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>
or you can specify an arbitrary callable with arguments::
>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
A third possibility is to use a string which which will
be executed::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
"""
__tracebackhide__ = True
if not args:
return RaisesContext(ExpectedException)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
#print "raises frame scope: %r" % frame.f_locals
try:
code = py.code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except ExpectedException:
return py.code.ExceptionInfo()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except ExpectedException:
return py.code.ExceptionInfo()
k = ", ".join(["%s=%r" % x for x in kwargs.items()])
if k:
k = ', ' + k
expr = '%s(%r%s)' %(getattr(func, '__name__', func), args, k)
pytest.fail("DID NOT RAISE")
class RaisesContext(object):
def __init__(self, ExpectedException):
self.ExpectedException = ExpectedException
self.excinfo = None
def __enter__(self):
self.excinfo = object.__new__(py.code.ExceptionInfo)
return self.excinfo
def __exit__(self, *tp):
__tracebackhide__ = True
if tp[0] is None:
pytest.fail("DID NOT RAISE")
self.excinfo.__init__(tp)
return issubclass(self.excinfo.type, self.ExpectedException)

View File

@@ -1,36 +1,4 @@
"""
helpers for asserting deprecation and other warnings.
Example usage
---------------------
You can use the ``recwarn`` funcarg to track
warnings within a test function:
.. sourcecode:: python
def test_hello(recwarn):
from warnings import warn
warn("hello", DeprecationWarning)
w = recwarn.pop(DeprecationWarning)
assert issubclass(w.category, DeprecationWarning)
assert 'hello' in str(w.message)
assert w.filename
assert w.lineno
You can also call a global helper for checking
taht a certain function call yields a Deprecation
warning:
.. sourcecode:: python
import py
def test_global():
py.test.deprecated_call(myfunction, 17)
"""
""" recording warnings during test function execution. """
import py
import sys, os
@@ -39,10 +7,10 @@ def pytest_funcarg__recwarn(request):
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
* ``clear()``: clear list of warnings
"""
if sys.version_info >= (2,7):
import warnings
import warnings
oldfilters = warnings.filters[:]
warnings.simplefilter('default')
def reset_filters():
@@ -56,20 +24,20 @@ def pytest_namespace():
return {'deprecated_call': deprecated_call}
def deprecated_call(func, *args, **kwargs):
""" assert that calling func(*args, **kwargs)
triggers a DeprecationWarning.
"""
""" assert that calling ``func(*args, **kwargs)``
triggers a DeprecationWarning.
"""
warningmodule = py.std.warnings
l = []
oldwarn_explicit = getattr(warningmodule, 'warn_explicit')
def warn_explicit(*args, **kwargs):
l.append(args)
def warn_explicit(*args, **kwargs):
l.append(args)
oldwarn_explicit(*args, **kwargs)
oldwarn = getattr(warningmodule, 'warn')
def warn(*args, **kwargs):
l.append(args)
def warn(*args, **kwargs):
l.append(args)
oldwarn(*args, **kwargs)
warningmodule.warn_explicit = warn_explicit
warningmodule.warn = warn
try:
@@ -100,10 +68,10 @@ class WarningsRecorder:
self.list.append(RecordedWarning(
message, category, filename, lineno, line))
try:
self.old_showwarning(message, category,
self.old_showwarning(message, category,
filename, lineno, line=line)
except TypeError:
# < python2.6
# < python2.6
self.old_showwarning(message, category, filename, lineno)
self.old_showwarning = warningmodule.showwarning
warningmodule.showwarning = showwarning
@@ -121,7 +89,7 @@ class WarningsRecorder:
# warnings.onceregistry.clear()
# warnings.__warningregistry__.clear()
def clear(self):
def clear(self):
self.list[:] = []
def finalize(self):

View File

@@ -1,10 +1,4 @@
"""non-xml machine-readable logging of test results.
Useful for buildbot integration code. See the `PyPy-test`_
web page for post-processing.
.. _`PyPy-test`: http://codespeak.net:8099/summary
"""
""" (disabled by default) create result information in a plain text file. """
import py
from py.builtin import print_
@@ -19,14 +13,14 @@ def pytest_configure(config):
# prevent opening resultlog on slave nodes (xdist)
if resultlog and not hasattr(config, 'slaveinput'):
logfile = open(resultlog, 'w', 1) # line buffered
config._resultlog = ResultLog(config, logfile)
config._resultlog = ResultLog(config, logfile)
config.pluginmanager.register(config._resultlog)
def pytest_unconfigure(config):
resultlog = getattr(config, '_resultlog', None)
if resultlog:
resultlog.logfile.close()
del config._resultlog
del config._resultlog
config.pluginmanager.unregister(resultlog)
def generic_path(item):
@@ -41,7 +35,7 @@ def generic_path(item):
gpath.append(':')
fspart = False
else:
gpath.append('.')
gpath.append('.')
else:
gpath.append('/')
fspart = True
@@ -57,21 +51,20 @@ class ResultLog(object):
self.config = config
self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, shortrepr, longrepr):
print_("%s %s" % (shortrepr, testpath), file=self.logfile)
def write_log_entry(self, testpath, lettercode, longrepr):
print_("%s %s" % (lettercode, testpath), file=self.logfile)
for line in longrepr.splitlines():
print_(" %s" % line, file=self.logfile)
def log_outcome(self, node, shortrepr, longrepr):
testpath = generic_path(node)
self.write_log_entry(testpath, shortrepr, longrepr)
def log_outcome(self, report, lettercode, longrepr):
testpath = getattr(report, 'nodeid', None)
if testpath is None:
testpath = report.fspath
self.write_log_entry(testpath, lettercode, longrepr)
def pytest_runtest_logreport(self, report):
res = self.config.hook.pytest_report_teststatus(report=report)
if res is not None:
code = res[1]
else:
code = report.shortrepr
code = res[1]
if code == 'x':
longrepr = str(report.longrepr)
elif code == 'X':
@@ -79,21 +72,22 @@ class ResultLog(object):
elif report.passed:
longrepr = ""
elif report.failed:
longrepr = str(report.longrepr)
longrepr = str(report.longrepr)
elif report.skipped:
longrepr = str(report.longrepr.reprcrash.message)
self.log_outcome(report.item, code, longrepr)
longrepr = str(report.longrepr[2])
self.log_outcome(report, code, longrepr)
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:
if report.failed:
code = "F"
longrepr = str(report.longrepr.reprcrash)
else:
assert report.skipped
code = "S"
longrepr = str(report.longrepr.reprcrash)
self.log_outcome(report.collector, code, longrepr)
longrepr = "%s:%d: %s" % report.longrepr
self.log_outcome(report, code, longrepr)
def pytest_internalerror(self, excrepr):
path = excrepr.reprcrash.path
path = excrepr.reprcrash.path
self.write_log_entry(path, '!', str(excrepr))

390
_pytest/runner.py Normal file
View File

@@ -0,0 +1,390 @@
""" basic collect and runtest protocol implementations """
import py, sys
from py._code.code import TerminalRepr
def pytest_namespace():
return {
'fail' : fail,
'skip' : skip,
'importorskip' : importorskip,
'exit' : exit,
}
#
# pytest plugin hooks
# XXX move to pytest_sessionstart and fix py.test owns tests
def pytest_configure(config):
config._setupstate = SetupState()
def pytest_sessionfinish(session, exitstatus):
if hasattr(session.config, '_setupstate'):
hook = session.config.hook
rep = hook.pytest__teardown_final(session=session)
if rep:
hook.pytest__teardown_final_logerror(session=session, report=rep)
session.exitstatus = 1
class NodeInfo:
def __init__(self, location):
self.location = location
def pytest_runtest_protocol(item):
item.ihook.pytest_runtest_logstart(
nodeid=item.nodeid, location=item.location,
)
runtestprotocol(item)
return True
def runtestprotocol(item, log=True):
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))
return reports
def pytest_runtest_setup(item):
item.config._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_report_teststatus(report):
if report.when in ("setup", "teardown"):
if report.failed:
# category, shortletter, verbose-word
return "error", "E", "ERROR"
elif report.skipped:
return "skipped", "s", "SKIPPED"
else:
return "", "", ""
#
# Implementation
def call_and_report(item, when, log=True):
call = call_runtest_hook(item, when)
hook = item.ihook
report = hook.pytest_runtest_makereport(item=item, call=call)
if log and (when == "call" or not report.passed):
hook.pytest_runtest_logreport(report=report)
return report
def call_runtest_hook(item, when):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(lambda: ihook(item=item), when=when)
class CallInfo:
""" Result/Exception info a function invocation. """
#: None or ExceptionInfo object.
excinfo = None
def __init__(self, func, when):
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
self.when = when
try:
self.result = func()
except KeyboardInterrupt:
raise
except:
self.excinfo = py.code.ExceptionInfo()
def __repr__(self):
if self.excinfo:
status = "exception: %s" % str(self.excinfo.value)
else:
status = "result: %r" % (self.result,)
return "<CallInfo when=%r %s>" % (self.when, status)
def getslaveinfoline(node):
try:
return node._slaveinfocache
except AttributeError:
d = node.slaveinfo
ver = "%s.%s.%s" % d['version_info'][:3]
node._slaveinfocache = s = "[%s] %s -- Python %s %s" % (
d['id'], d['sysplatform'], ver, d['executable'])
return s
class BaseReport(object):
def toterminal(self, out):
longrepr = self.longrepr
if hasattr(self, 'node'):
out.line(getslaveinfoline(self.node))
if hasattr(longrepr, 'toterminal'):
longrepr.toterminal(out)
else:
out.line(str(longrepr))
passed = property(lambda x: x.outcome == "passed")
failed = property(lambda x: x.outcome == "failed")
skipped = property(lambda x: x.outcome == "skipped")
@property
def fspath(self):
return self.nodeid.split("::")[0]
def pytest_runtest_makereport(item, call):
when = call.when
keywords = dict([(x,1) for x in item.keywords])
excinfo = call.excinfo
if not call.excinfo:
outcome = "passed"
longrepr = None
else:
excinfo = call.excinfo
if not isinstance(excinfo, py.code.ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(py.test.skip.Exception):
outcome = "skipped"
r = item._repr_failure_py(excinfo, "line").reprcrash
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo)
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when)
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):
#: normalized collection node id
self.nodeid = nodeid
#: a (filesystempath, lineno, domaininfo) tuple indicating the
#: actual location of a test item - it might be different from the
#: collected one e.g. if a method is inherited from a different module.
self.location = location
#: 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
def __repr__(self):
return "<TestReport %r when=%r outcome=%r>" % (
self.nodeid, self.when, self.outcome)
class TeardownErrorReport(BaseReport):
outcome = "failed"
when = "teardown"
def __init__(self, longrepr):
self.longrepr = longrepr
def pytest_make_collect_report(collector):
call = CallInfo(collector._memocollect, "memocollect")
longrepr = None
if not call.excinfo:
outcome = "passed"
else:
if call.excinfo.errisinstance(py.test.skip.Exception):
outcome = "skipped"
r = collector._repr_failure_py(call.excinfo, "line").reprcrash
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
errorinfo = collector.repr_failure(call.excinfo)
if not hasattr(errorinfo, "toterminal"):
errorinfo = CollectErrorRepr(errorinfo)
longrepr = errorinfo
return CollectReport(collector.nodeid, outcome, longrepr,
getattr(call, 'result', None))
class CollectReport(BaseReport):
def __init__(self, nodeid, outcome, longrepr, result):
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
self.result = result or []
@property
def location(self):
return (self.fspath, None, self.fspath)
def __repr__(self):
return "<CollectReport %r lenresult=%s outcome=%r>" % (
self.nodeid, len(self.result), self.outcome)
class CollectErrorRepr(TerminalRepr):
def __init__(self, msg):
self.longrepr = msg
def toterminal(self, out):
out.line(str(self.longrepr), red=True)
class SetupState(object):
""" shared state for setting up/tearing down test items or collectors. """
def __init__(self):
self.stack = []
self._finalizers = {}
def addfinalizer(self, finalizer, colitem):
""" 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().
"""
assert hasattr(finalizer, '__call__')
#assert colitem in self.stack
self._finalizers.setdefault(colitem, []).append(finalizer)
def _pop_and_teardown(self):
colitem = self.stack.pop()
self._teardown_with_finalization(colitem)
def _callfinalizers(self, colitem):
finalizers = self._finalizers.pop(colitem, None)
while finalizers:
fin = finalizers.pop()
fin()
def _teardown_with_finalization(self, colitem):
self._callfinalizers(colitem)
if colitem:
colitem.teardown()
for colitem in self._finalizers:
assert colitem is None or colitem in self.stack
def teardown_all(self):
while self.stack:
self._pop_and_teardown()
self._teardown_with_finalization(None)
assert not self._finalizers
def teardown_exact(self, item):
if self.stack and item == self.stack[-1]:
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()
# check if the last collection node has raised an error
for col in self.stack:
if hasattr(col, '_prepare_exc'):
py.builtin._reraise(*col._prepare_exc)
for col in needed_collectors[len(self.stack):]:
self.stack.append(col)
try:
col.setup()
except Exception:
col._prepare_exc = sys.exc_info()
raise
# =============================================================
# Test OutcomeExceptions and helpers for creating them.
class OutcomeException(Exception):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
self.msg = msg
self.pytrace = pytrace
def __repr__(self):
if self.msg:
return str(self.msg)
return "<%s instance>" %(self.__class__.__name__,)
__str__ = __repr__
class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'
class Failed(OutcomeException):
""" raised from an explicit call to py.test.fail() """
__module__ = 'builtins'
class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)
# exposed helper methods
def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)
exit.Exception = Exit
def skip(msg=""):
""" skip an executing test with the given message. Note: it's usually
better to use the py.test.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)
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.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed
def importorskip(modname, minversion=None):
""" return imported module if it has a higher __version__ than the
optionally specified 'minversion' - otherwise call py.test.skip()
with a message detailing the mismatch.
"""
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
try:
mod = __import__(modname, None, None, ['__doc__'])
except ImportError:
py.test.skip("could not import %r" %(modname,))
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if isinstance(minversion, str):
minver = minversion.split(".")
else:
minver = list(minversion)
if verattr is None or verattr.split(".") < minver:
py.test.skip("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion))
return mod

516
_pytest/session.py Normal file
View File

@@ -0,0 +1,516 @@
""" core implementation of testing process: init, session, runtest loop. """
import py
import pytest, _pytest
import os, sys
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
# exitcodes for the command line
EXIT_OK = 0
EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=('.*', 'CVS', '_darcs', '{arch}'))
#parser.addini("dirpatterns",
# "patterns specifying possible locations of test files",
# type="linelist", default=["**/test_*.txt",
# "**/test_*.py", "**/*_test.py"]
#)
group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_true", default=False,
dest="exitfirst",
help="exit instantly on first error or failed test."),
group._addoption('--maxfail', metavar="num",
action="store", type="int", dest="maxfail", default=0,
help="exit after first num failures or errors.")
group = parser.getgroup("collect", "collection")
group.addoption('--collectonly',
action="store_true", dest="collectonly",
help="only collect tests, don't execute them."),
group.addoption('--pyargs', action="store_true",
help="try to interpret all arguments as python packages.")
group.addoption("--ignore", action="append", metavar="path",
help="ignore path during collection (multi-allowed).")
group.addoption('--confcutdir', dest="confcutdir", default=None,
metavar="dir",
help="only load conftest.py's relative to specified dir.")
group = parser.getgroup("debugconfig",
"test process debugging and configuration")
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector, File=File))
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. """
session = Session(config)
session.exitstatus = EXIT_OK
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)
return session.exitstatus
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
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)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
def pytest_ignore_collect(path, config):
p = path.dirpath()
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
ignore_paths = ignore_paths or []
excludeopt = config.getvalue("ignore")
if excludeopt:
ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths
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)
return call_matching_hooks
def compatproperty(name):
def fget(self):
#print "retrieving %r property from %s" %(name, self.fspath)
py.log._apiwarn("2.0", "use pytest.%s for "
"test collection and item classes" % name)
return getattr(pytest, name)
return property(fget, None, None,
"deprecated attribute %r, use pytest.%s" % (name,name))
class Node(object):
""" base class for all Nodes in the collection tree.
Collector subclasses have children, Items are terminal nodes."""
def __init__(self, name, parent=None, config=None, session=None):
#: a unique name with the scope of the parent
self.name = name
#: the parent collector node.
self.parent = parent
#: the test config object
self.config = config or parent.config
#: the collection this node is part of
self.session = session or parent.session
#: filesystem path where this node was collected from
self.fspath = getattr(parent, 'fspath', None)
self.ihook = self.session.gethookproxy(self.fspath)
self.keywords = {self.name: True}
Module = compatproperty("Module")
Class = compatproperty("Class")
Function = compatproperty("Function")
File = compatproperty("File")
Item = compatproperty("Item")
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
# methods for ordering nodes
@property
def nodeid(self):
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
def __ne__(self, other):
return not self == other
def __hash__(self):
return hash((self.name, self.parent))
def setup(self):
pass
def teardown(self):
pass
def _memoizedcall(self, attrname, function):
exattrname = "_ex_" + attrname
failure = getattr(self, exattrname, None)
if failure is not None:
py.builtin._reraise(failure[0], failure[1], failure[2])
if hasattr(self, attrname):
return getattr(self, attrname)
try:
res = function()
except py.builtin._sysex:
raise
except:
failure = py.std.sys.exc_info()
setattr(self, exattrname, failure)
raise
setattr(self, attrname, res)
return res
def listchain(self):
""" return list of all parent collectors up to self,
starting from root of collection tree. """
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
def listnames(self):
return [x.name for x in self.listchain()]
def getplugins(self):
return self.config._getmatchingplugins(self.fspath)
def getparent(self, cls):
current = self
while current and not isinstance(current, cls):
current = current.parent
return current
def _prunetraceback(self, excinfo):
pass
def _repr_failure_py(self, excinfo, style=None):
if self.config.option.fulltrace:
style="long"
else:
self._prunetraceback(excinfo)
# XXX should excinfo.getrepr record all data and toterminal()
# process it?
if style is None:
if self.config.option.tbstyle == "short":
style = "short"
else:
style = "long"
return excinfo.getrepr(funcargs=True,
showlocals=self.config.option.showlocals,
style=style)
repr_failure = _repr_failure_py
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.
"""
class CollectError(Exception):
""" an error during collection, contains a custom message. """
def collect(self):
""" returns a list of children (items and collectors)
for this collection node.
"""
raise NotImplementedError("abstract")
def repr_failure(self, excinfo):
""" represent a collection failure. """
if excinfo.errisinstance(self.CollectError):
exc = excinfo.value
return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short")
def _memocollect(self):
""" internal helper method to cache results of calling collect(). """
return self._memoizedcall('_collected', lambda: list(self.collect()))
def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'):
path = self.fspath
traceback = excinfo.traceback
ntraceback = traceback.cut(path=self.fspath)
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
excinfo.traceback = ntraceback.filter()
class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, session=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
if rel:
name = rel
name = name.replace(os.sep, "/")
super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath
def _makeid(self):
if self == self.session:
return "."
relpath = self.session.fspath.bestrelpath(self.fspath)
if os.sep != "/":
relpath = relpath.replace(os.sep, "/")
return relpath
class File(FSCollector):
""" base class for collecting tests from a file. """
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
"""
def reportinfo(self):
return self.fspath, None, ""
@property
def location(self):
try:
return self._location
except AttributeError:
location = self.reportinfo()
fspath = self.session.fspath.bestrelpath(location[0])
location = (fspath, location[1], str(location[2]))
self._location = location
return location
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
class Session(FSCollector):
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
def __init__(self, config):
super(Session, self).__init__(py.path.local(), parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
def pytest_collectstart(self):
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
def pytest_runtest_logreport(self, report):
if report.failed and 'xfail' not in getattr(report, 'keywords', []):
self._testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(self, path):
return path in self._initialpaths
def gethookproxy(self, fspath):
return HookProxy(fspath, self.config)
def perform_collect(self, args=None, genitems=True):
if args is None:
args = self.config.args
self.trace("perform_collect", self, args)
self.trace.root.indent += 1
self._notfound = []
self._initialpaths = set()
self._initialparts = []
for arg in args:
parts = self._parsearg(arg)
self._initialparts.append(parts)
self._initialpaths.add(parts[0])
self.ihook.pytest_collectstart(collector=self)
rep = self.ihook.pytest_make_collect_report(collector=self)
self.ihook.pytest_collectreport(report=rep)
self.trace.root.indent -= 1
if self._notfound:
for arg, exc in self._notfound:
line = "(no name %r in any of %r)" % (arg, exc.args[0])
raise pytest.UsageError("not found: %s\n%s" %(arg, line))
if not genitems:
return rep.result
else:
self.items = items = []
if rep.passed:
for node in rep.result:
self.items.extend(self.genitems(node))
return items
def collect(self):
for parts in self._initialparts:
arg = "::".join(map(str, parts))
self.trace("processing argument", arg)
self.trace.root.indent += 1
try:
for x in self._collect(arg):
yield x
except NoMatch:
# we are inside a make_report hook so
# we cannot directly pass through the exception
self._notfound.append((arg, sys.exc_info()[1]))
self.trace.root.indent -= 1
break
self.trace.root.indent -= 1
def _collect(self, arg):
names = self._parsearg(arg)
path = names.pop(0)
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):
for x in self._collectfile(path):
yield x
else:
assert path.check(file=1)
for x in self.matchnodes(self._collectfile(path), names):
yield x
def _collectfile(self, path):
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
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
for pat in self._norecursepatterns:
if path.check(fnmatch=pat):
return False
ihook = self.gethookproxy(path)
ihook.pytest_collect_directory(path=path, parent=self)
return True
def _tryconvertpyarg(self, x):
try:
mod = __import__(x, None, None, ['__doc__'])
except (ValueError, ImportError):
return x
p = py.path.local(mod.__file__)
if p.purebasename == "__init__":
p = p.dirpath()
else:
p = p.new(basename=p.purebasename+".py")
return p
def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """
arg = str(arg)
if self.config.option.pyargs:
arg = self._tryconvertpyarg(arg)
parts = str(arg).split("::")
relpath = parts[0].replace("/", os.sep)
path = self.fspath.join(relpath, abs=True)
if not path.check():
if self.config.option.pyargs:
msg = "file or package not found: "
else:
msg = "file not found: "
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
nodes = self._matchnodes(matching, names)
num = len(nodes)
self.trace("matchnodes finished -> ", num, "nodes")
self.trace.root.indent -= 1
if num == 0:
raise NoMatch(matching, names[:1])
return nodes
def _matchnodes(self, matching, names):
if not matching or not names:
return matching
name = names[0]
assert name
nextnames = names[1:]
resultnodes = []
for node in matching:
if isinstance(node, pytest.Item):
if not names:
resultnodes.append(node)
continue
assert isinstance(node, pytest.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
for x in rep.result:
if x.name == name:
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
return resultnodes
def genitems(self, node):
self.trace("genitems", node)
if isinstance(node, pytest.Item):
node.ihook.pytest_itemcollected(item=node)
yield node
else:
assert isinstance(node, pytest.Collector)
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
for subnode in rep.result:
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)

View File

@@ -1,161 +1,25 @@
"""
advanced skipping for python test functions, classes or modules.
""" support for skip/xfail functions and markers. """
With this plugin you can mark test functions for conditional skipping
or as "xfail", expected-to-fail. Skipping a test will avoid running it
while xfail-marked tests will run and result in an inverted outcome:
a pass becomes a failure and a fail becomes a semi-passing one.
The need for skipping a test is usually connected to a condition.
If a test fails under all conditions then it's probably better
to mark your test as 'xfail'.
By passing ``-rxs`` to the terminal reporter you will see extra
summary information on skips and xfail-run tests at the end of a test run.
.. _skipif:
Skipping a single function
-------------------------------------------
Here is an example for marking a test function to be skipped
when run on a Python3 interpreter::
@py.test.mark.skipif("sys.version_info >= (3,0)")
def test_function():
...
During test function setup the skipif condition is
evaluated by calling ``eval(expr, namespace)``. The namespace
contains the ``sys`` and ``os`` modules and the test
``config`` object. The latter allows you to skip based
on a test configuration value e.g. like this::
@py.test.mark.skipif("not config.getvalue('db')")
def test_function(...):
...
Create a shortcut for your conditional skip decorator
at module level like this::
win32only = py.test.mark.skipif("sys.platform != 'win32'")
@win32only
def test_function():
...
skip groups of test functions
--------------------------------------
As with all metadata function marking you can do it at
`whole class- or module level`_. Here is an example
for skipping all methods of a test class based on platform::
class TestPosixCalls:
pytestmark = py.test.mark.skipif("sys.platform == 'win32'")
def test_function(self):
# will not be setup or run under 'win32' platform
#
The ``pytestmark`` decorator will be applied to each test function.
If your code targets python2.6 or above you can equivalently use
the skipif decorator on classes::
@py.test.mark.skipif("sys.platform == 'win32'")
class TestPosixCalls:
def test_function(self):
# will not be setup or run under 'win32' platform
#
It is fine in general to apply multiple "skipif" decorators
on a single function - this means that if any of the conditions
apply the function will be skipped.
.. _`whole class- or module level`: mark.html#scoped-marking
.. _xfail:
mark a test function as **expected to fail**
-------------------------------------------------------
You can use the ``xfail`` marker to indicate that you
expect the test to fail::
@py.test.mark.xfail
def test_function():
...
This test will be run but no traceback will be reported
when it fails. Instead terminal reporting will list it in the
"expected to fail" or "unexpectedly passing" sections.
Same as with skipif_ you can also selectively expect a failure
depending on platform::
@py.test.mark.xfail("sys.version_info >= (3,0)")
def test_function():
...
To not run a test and still regard it as "xfailed"::
@py.test.mark.xfail(..., run=False)
To specify an explicit reason to be shown with xfailure detail::
@py.test.mark.xfail(..., reason="my reason")
imperative xfail from within a test or setup function
------------------------------------------------------
If you cannot declare xfail-conditions at import time
you can also imperatively produce an XFail-outcome from
within test or setup code. Example::
def test_function():
if not valid_config():
py.test.xfail("unsuppored configuration")
skipping on a missing import dependency
--------------------------------------------------
You can use the following import helper at module level
or within a test or test setup function::
docutils = py.test.importorskip("docutils")
If ``docutils`` cannot be imported here, this will lead to a
skip outcome of the test. You can also skip dependeing if
if a library does not come with a high enough version::
docutils = py.test.importorskip("docutils", minversion="0.3")
The version will be read from the specified module's ``__version__`` attribute.
imperative skip from within a test or setup function
------------------------------------------------------
If for some reason you cannot declare skip-conditions
you can also imperatively produce a Skip-outcome from
within test or setup code. Example::
def test_function():
if not valid_config():
py.test.skip("unsuppored configuration")
"""
import py
import py, pytest
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption('--runxfail',
group.addoption('--runxfail',
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
def pytest_namespace():
return dict(xfail=xfail)
class XFailed(pytest.fail.Exception):
""" raised from an explicit call to py.test.xfail() """
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)
xfail.Exception = XFailed
class MarkEvaluator:
def __init__(self, item, name):
self.item = item
@@ -198,10 +62,10 @@ class MarkEvaluator:
else:
return "condition: " + self.expr
return expl
def pytest_runtest_setup(item):
if not isinstance(item, py.test.collect.Function):
if not isinstance(item, pytest.Function):
return
evalskip = MarkEvaluator(item, 'skipif')
if evalskip.istrue():
@@ -213,16 +77,16 @@ def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem)
def check_xfail_no_run(item):
if not item.config.getvalue("runxfail"):
if not item.config.option.runxfail:
evalxfail = item._evalxfail
if evalxfail.istrue():
if not evalxfail.get('run', True):
py.test.xfail("[NOTRUN] " + evalxfail.getexplanation())
def pytest_runtest_makereport(__multicall__, item, call):
if not isinstance(item, py.test.collect.Function):
if not isinstance(item, pytest.Function):
return
if not (call.excinfo and
if not (call.excinfo and
call.excinfo.errisinstance(py.test.xfail.Exception)):
evalxfail = getattr(item, '_evalxfail', None)
if not evalxfail:
@@ -231,19 +95,16 @@ def pytest_runtest_makereport(__multicall__, item, call):
if not item.config.getvalue("runxfail"):
rep = __multicall__.execute()
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
rep.skipped = True
rep.failed = False
rep.outcome = "skipped"
return rep
if call.when == "call":
rep = __multicall__.execute()
evalxfail = getattr(item, '_evalxfail')
if not item.config.getvalue("runxfail") and evalxfail.istrue():
if call.excinfo:
rep.skipped = True
rep.failed = rep.passed = False
rep.outcome = "skipped"
else:
rep.skipped = rep.passed = False
rep.failed = True
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
else:
if 'xfail' in rep.keywords:
@@ -275,9 +136,9 @@ def pytest_terminal_summary(terminalreporter):
show_xfailed(terminalreporter, lines)
elif char == "X":
show_xpassed(terminalreporter, lines)
elif char == "f":
elif char in "fF":
show_failed(terminalreporter, lines)
elif char == "s":
elif char in "sS":
show_skipped(terminalreporter, lines)
if lines:
tr._tw.sep("=", "short test summary info")
@@ -289,22 +150,24 @@ def show_failed(terminalreporter, lines):
failed = terminalreporter.stats.get("failed")
if failed:
for rep in failed:
pos = terminalreporter.gettestid(rep.item)
pos = rep.nodeid
lines.append("FAIL %s" %(pos, ))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")
if xfailed:
for rep in xfailed:
pos = terminalreporter.gettestid(rep.item)
pos = rep.nodeid
reason = rep.keywords['xfail']
lines.append("XFAIL %s %s" %(pos, reason))
lines.append("XFAIL %s" % (pos,))
if reason:
lines.append(" " + str(reason))
def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed")
if xpassed:
for rep in xpassed:
pos = terminalreporter.gettestid(rep.item)
pos = rep.nodeid
reason = rep.keywords['xfail']
lines.append("XPASS %s %s" %(pos, reason))
@@ -323,13 +186,13 @@ def cached_eval(config, expr, d):
def folded_skips(skipped):
d = {}
for event in skipped:
entry = event.longrepr.reprcrash
key = entry.path, entry.lineno, entry.message
key = event.longrepr
assert len(key) == 3, (event, key)
d.setdefault(key, []).append(event)
l = []
for key, events in d.items():
for key, events in d.items():
l.append((len(events),) + key)
return l
return l
def show_skipped(terminalreporter, lines):
tr = terminalreporter

View File

@@ -15,7 +15,7 @@ class DictImporter(object):
def find_module(self, fullname, path=None):
if fullname in self.sources:
return self
if fullname+'.__init__' in self.sources:
if fullname + '.__init__' in self.sources:
return self
return None
@@ -26,30 +26,30 @@ class DictImporter(object):
s = self.sources[fullname]
is_pkg = False
except KeyError:
s = self.sources[fullname+'.__init__']
s = self.sources[fullname + '.__init__']
is_pkg = True
co = compile(s, fullname, 'exec')
module = sys.modules.setdefault(fullname, ModuleType(fullname))
module.__file__ = "%s/%s" % (__file__, fullname)
module.__loader__ = self
if is_pkg:
module.__path__ = [fullname]
do_exec(co, module.__dict__)
return sys.modules[fullname]
def get_source(self, name):
res = self.sources.get(name)
if res is None:
res = self.sources.get(name+'.__init__')
res = self.sources.get(name + '.__init__')
return res
if __name__ == "__main__":
if sys.version_info >= (3,0):
if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
sources = sources.encode("ascii") # ensure bytes
sources = sources.encode("ascii") # ensure bytes
sources = pickle.loads(zlib.decompress(base64.decodebytes(sources)))
else:
import cPickle as pickle
@@ -59,5 +59,5 @@ if __name__ == "__main__":
importer = DictImporter(sources)
sys.meta_path.append(importer)
import py
py.cmdline.pytest()
entry = "@ENTRY@"
do_exec(entry, locals())

View File

@@ -1,15 +1,17 @@
"""
Implements terminal reporting of the full testing process.
""" terminal reporting of the full testing process.
This is a good source for looking at the various reporting hooks.
This is a good source for looking at the various reporting hooks.
"""
import py
import pytest, py
import sys
import os
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group._addoption('-v', '--verbose', action="count",
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."),
group._addoption('-r',
action="store", dest="reportchars", default=None, metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
@@ -20,40 +22,49 @@ def pytest_addoption(parser):
group._addoption('--report',
action="store", dest="report", default=None, metavar="opts",
help="(deprecated, use -r)")
group._addoption('--tb', metavar="style",
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no', 'line'],
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/no).")
group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut).")
group._addoption('--funcargs',
action="store_true", dest="showfuncargs", default=False,
help="show available function arguments, sorted by plugin")
def pytest_configure(config):
config.option.verbose -= config.option.quiet
if config.option.collectonly:
reporter = CollectonlyReporter(config)
elif config.option.showfuncargs:
config.setsessionclass(ShowFuncargSession)
reporter = None
else:
reporter = TerminalReporter(config)
if reporter:
# XXX see remote.py's XXX
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
if hasattr(config, attr):
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
name = attr.split("_")[-1]
assert hasattr(self.reporter._tw, name), name
setattr(reporter._tw, name, getattr(config, attr))
config.pluginmanager.register(reporter, 'terminalreporter')
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
reporter = TerminalReporter(config, stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
msg = " ".join(map(str, args))
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.getvalue("report")
optvalue = config.option.report
if optvalue:
py.builtin.print_("DEPRECATED: use -r instead of --report option.",
py.builtin.print_("DEPRECATED: use -r instead of --report option.",
file=py.std.sys.stderr)
if optvalue:
for setting in optvalue.split(","):
@@ -62,48 +73,66 @@ def getreportopt(config):
reportopts += "s"
elif setting == "xfailed":
reportopts += "x"
reportchars = config.getvalue("reportchars")
reportchars = config.option.reportchars
if reportchars:
for char in reportchars:
if char not in reportopts:
reportopts += char
return reportopts
def pytest_report_teststatus(report):
if report.passed:
letter = "."
elif report.skipped:
letter = "s"
elif report.failed:
letter = "F"
if report.when != "call":
letter = "f"
return report.outcome, letter, report.outcome.upper()
class TerminalReporter:
def __init__(self, config, file=None):
self.config = config
self.stats = {}
self.config = config
self.verbosity = self.config.option.verbose
self.showheader = self.verbosity >= 0
self.showfspath = self.verbosity >= 0
self.showlongtestinfo = self.verbosity > 0
self._numcollected = 0
self.stats = {}
self.curdir = py.path.local()
if file is None:
file = py.std.sys.stdout
self._tw = py.io.TerminalWriter(file)
self.currentfspath = None
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
def hasopt(self, char):
char = {'xfailed': 'x', 'skipped': 's'}.get(char,char)
return char in self.reportchars
def write_fspath_result(self, fspath, res):
fspath = self.curdir.bestrelpath(fspath)
if fspath != self.currentfspath:
self._tw.line()
relpath = self.curdir.bestrelpath(fspath)
self._tw.write(relpath + " ")
self.currentfspath = fspath
#fspath = self.curdir.bestrelpath(fspath)
self._tw.line()
#relpath = self.curdir.bestrelpath(fspath)
self._tw.write(fspath + " ")
self._tw.write(res)
def write_ensure_prefix(self, prefix, extra="", **kwargs):
if self.currentfspath != prefix:
self._tw.line()
self.currentfspath = prefix
self.currentfspath = prefix
self._tw.write(prefix)
if extra:
self._tw.write(extra, **kwargs)
self.currentfspath = -2
def ensure_newline(self):
if self.currentfspath:
if self.currentfspath:
self._tw.line()
self.currentfspath = None
@@ -112,95 +141,70 @@ class TerminalReporter:
self.ensure_newline()
self._tw.line(line, **markup)
def rewrite(self, line, **markup):
line = str(line)
self._tw.write("\r" + line, **markup)
def write_sep(self, sep, title=None, **markup):
self.ensure_newline()
self._tw.sep(sep, title, **markup)
def getcategoryletterword(self, rep):
res = self.config.hook.pytest_report_teststatus(report=rep)
if res:
return res
for cat in 'skipped failed passed ???'.split():
if getattr(rep, cat, None):
break
return cat, self.getoutcomeletter(rep), self.getoutcomeword(rep)
def getoutcomeletter(self, rep):
return rep.shortrepr
def getoutcomeword(self, rep):
if rep.passed:
return "PASS", dict(green=True)
elif rep.failed:
return "FAIL", dict(red=True)
elif rep.skipped:
return "SKIP"
else:
return "???", dict(red=True)
def gettestid(self, item, relative=True):
fspath = item.fspath
chain = [x for x in item.listchain() if x.fspath == fspath]
chain = chain[1:]
names = [x.name for x in chain if x.name != "()"]
path = item.fspath
if relative:
relpath = path.relto(self.curdir)
if relpath:
path = relpath
names.insert(0, str(path))
return "::".join(names)
def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"):
self.write_line("INTERNALERROR> " + line)
return 1
def pytest_plugin_registered(self, plugin):
if self.config.option.traceconfig:
if self.config.option.traceconfig:
msg = "PLUGIN registered: %s" %(plugin,)
# XXX this event may happen during setup/teardown time
# which unfortunately captures our output here
# which garbles our output if we use self.write_line
# XXX this event may happen during setup/teardown time
# which unfortunately captures our output here
# which garbles our output if we use self.write_line
self.write_line(msg)
def pytest_trace(self, category, msg):
if self.config.option.debug or \
self.config.option.traceconfig and category.find("config") != -1:
self.write_line("[%s] %s" %(category, msg))
def pytest_deselected(self, items):
self.stats.setdefault('deselected', []).append(items)
def pytest_itemstart(self, item, node=None):
if self.config.option.verbose:
line = self._reportinfoline(item)
self.write_ensure_prefix(line, "")
else:
# ensure that the path is printed before the
# 1st test of a module starts running
self.write_fspath_result(self._getfspath(item), "")
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
fspath = nodeid.split("::")[0]
if self.showlongtestinfo:
line = self._locationline(fspath, *location)
self.write_ensure_prefix(line, "")
elif self.showfspath:
self.write_fspath_result(fspath, "")
def pytest_runtest_logreport(self, report):
rep = report
cat, letter, word = self.getcategoryletterword(rep)
res = self.config.hook.pytest_report_teststatus(report=rep)
cat, letter, word = res
self.stats.setdefault(cat, []).append(rep)
if not letter and not word:
# probably passed setup/teardown
return
if isinstance(word, tuple):
word, markup = word
if self.verbosity <= 0:
if not hasattr(rep, 'node') and self.showfspath:
self.write_fspath_result(rep.fspath, letter)
else:
self._tw.write(letter)
else:
markup = {}
self.stats.setdefault(cat, []).append(rep)
if not self.config.option.verbose:
self.write_fspath_result(self._getfspath(rep.item), letter)
else:
line = self._reportinfoline(rep.item)
if isinstance(word, tuple):
word, markup = word
else:
if rep.passed:
markup = {'green':True}
elif rep.failed:
markup = {'red':True}
elif rep.skipped:
markup = {'yellow':True}
line = self._locationline(str(rep.fspath), *rep.location)
if not hasattr(rep, 'node'):
self.write_ensure_prefix(line, word, **markup)
#self._tw.write(word, **markup)
else:
self.ensure_newline()
if hasattr(rep, 'node'):
@@ -209,34 +213,71 @@ class TerminalReporter:
self._tw.write(" " + line)
self.currentfspath = -2
def pytest_collection(self):
if not self.hasmarkup:
self.write_line("collecting ...", bold=True)
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:
self.stats.setdefault("error", []).append(report)
self.write_fspath_result(report.collector.fspath, "E")
elif report.skipped:
self.stats.setdefault("skipped", []).append(report)
self.write_fspath_result(report.collector.fspath, "S")
if report.failed:
self.stats.setdefault("error", []).append(report)
elif report.skipped:
self.stats.setdefault("skipped", []).append(report)
items = [x for x in report.result if isinstance(x, pytest.Item)]
self._numcollected += len(items)
if self.hasmarkup:
#self.write_fspath_result(report.fspath, 'E')
self.report_collect()
def report_collect(self, final=False):
errors = len(self.stats.get('error', []))
skipped = len(self.stats.get('skipped', []))
if final:
line = "collected "
else:
line = "collecting "
line += str(self._numcollected) + " items"
if errors:
line += " / %d errors" % errors
if skipped:
line += " / %d skipped" % skipped
if self.hasmarkup:
if final:
line += " \n"
self.rewrite(line, bold=True)
else:
self.write_line(line)
def pytest_collection_modifyitems(self):
self.report_collect(True)
def pytest_sessionstart(self, session):
self.write_sep("=", "test session starts", bold=True)
self._sessionstarttime = py.std.time.time()
if not self.showheader:
return
self.write_sep("=", "test session starts", bold=True)
verinfo = ".".join(map(str, sys.version_info[:3]))
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
msg += " -- pytest-%s" % (py.__version__)
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
if hasattr(sys, 'pypy_version_info'):
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
msg += "[pypy-%s]" % verinfo
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.reverse()
for line in flatten(lines):
self.write_line(line)
for i, testarg in enumerate(self.config.args):
self.write_line("test path %d: %s" %(i+1, testarg))
def pytest_collection_finish(self):
if not self.showheader:
return
#for i, testarg in enumerate(self.config.args):
# self.write_line("test path %d: %s" %(i+1, testarg))
def pytest_sessionfinish(self, exitstatus, __multicall__):
__multicall__.execute()
__multicall__.execute()
self._tw.line("")
if exitstatus in (0, 1, 2):
self.summary_errors()
@@ -255,77 +296,72 @@ class TerminalReporter:
msg = excrepr.reprcrash.message
self.write_sep("!", msg)
if "KeyboardInterrupt" in msg:
if self.config.getvalue("fulltrace"):
if self.config.option.fulltrace:
excrepr.toterminal(self._tw)
else:
excrepr.reprcrash.toterminal(self._tw)
def _reportinfoline(self, item):
collect_fspath = self._getfspath(item)
fspath, lineno, msg = self._getreportinfo(item)
def _locationline(self, collect_fspath, fspath, lineno, domain):
if fspath and fspath != collect_fspath:
fspath = "%s <- %s" % (
self.curdir.bestrelpath(collect_fspath),
self.curdir.bestrelpath(fspath))
elif fspath:
fspath = self.curdir.bestrelpath(fspath)
fspath = "%s <- %s" % (collect_fspath, fspath)
if lineno is not None:
lineno += 1
if fspath and lineno and msg:
line = "%(fspath)s:%(lineno)s: %(msg)s"
elif fspath and msg:
line = "%(fspath)s: %(msg)s"
if fspath and lineno and domain:
line = "%(fspath)s:%(lineno)s: %(domain)s"
elif fspath and domain:
line = "%(fspath)s: %(domain)s"
elif fspath and lineno:
line = "%(fspath)s:%(lineno)s %(extrapath)s"
else:
line = "[noreportinfo]"
line = "[nolocation]"
return line % locals() + " "
def _getfailureheadline(self, rep):
if hasattr(rep, "collector"):
return str(rep.collector.fspath)
elif hasattr(rep, 'item'):
fspath, lineno, msg = self._getreportinfo(rep.item)
return msg
if hasattr(rep, 'location'):
fspath, lineno, domain = rep.location
return domain
else:
return "test session"
return "test session" # XXX?
def _getreportinfo(self, item):
def _getcrashline(self, rep):
try:
return item.__reportinfo
return str(rep.longrepr.reprcrash)
except AttributeError:
pass
reportinfo = item.config.hook.pytest_report_iteminfo(item=item)
# cache on item
item.__reportinfo = reportinfo
return reportinfo
def _getfspath(self, item):
try:
return item.fspath
except AttributeError:
fspath, lineno, msg = self._getreportinfo(item)
return fspath
try:
return str(rep.longrepr)[:50]
except AttributeError:
return ""
#
# summaries for sessionfinish
# summaries for sessionfinish
#
def getreports(self, name):
l = []
for x in self.stats.get(name, []):
if not hasattr(x, '_pdbshown'):
l.append(x)
return l
def summary_failures(self):
tbstyle = self.config.getvalue("tbstyle")
if 'failed' in self.stats and tbstyle != "no":
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
if not reports:
return
self.write_sep("=", "FAILURES")
for rep in self.stats['failed']:
if tbstyle == "line":
line = rep._getcrashline()
for rep in reports:
if self.config.option.tbstyle == "line":
line = self._getcrashline(rep)
self.write_line(line)
else:
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
rep.toterminal(self._tw)
def summary_errors(self):
if 'error' in self.stats and self.config.option.tbstyle != "no":
if self.config.option.tbstyle != "no":
reports = self.getreports('error')
if not reports:
return
self.write_sep("=", "ERRORS")
for rep in self.stats['error']:
msg = self._getfailureheadline(rep)
@@ -333,9 +369,9 @@ class TerminalReporter:
# collect
msg = "ERROR collecting " + msg
elif rep.when == "setup":
msg = "ERROR at setup of " + msg
msg = "ERROR at setup of " + msg
elif rep.when == "teardown":
msg = "ERROR at teardown of " + msg
msg = "ERROR at teardown of " + msg
self.write_sep("_", msg)
rep.toterminal(self._tw)
@@ -353,7 +389,11 @@ class TerminalReporter:
parts.append("%d %s" %(len(val), key))
line = ", ".join(parts)
# XXX coloring
self.write_sep("=", "%s in %.2f seconds" %(line, session_duration), bold=True)
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)
def summary_deselected(self):
if 'deselected' in self.stats:
@@ -365,7 +405,7 @@ class CollectonlyReporter:
INDENT = " "
def __init__(self, config, out=None):
self.config = config
self.config = config
if out is None:
out = py.std.sys.stdout
self._tw = py.io.TerminalWriter(out)
@@ -380,24 +420,31 @@ class CollectonlyReporter:
self._tw.line("INTERNALERROR> " + line)
def pytest_collectstart(self, collector):
self.outindent(collector)
self.indent += self.INDENT
def pytest_itemstart(self, item, node=None):
if collector.session != collector:
self.outindent(collector)
self.indent += self.INDENT
def pytest_itemcollected(self, item):
self.outindent(item)
def pytest_collectreport(self, report):
if not report.passed:
self.outindent("!!! %s !!!" % report.longrepr.reprcrash.message)
if hasattr(report.longrepr, 'reprcrash'):
msg = report.longrepr.reprcrash.message
else:
# XXX unify (we have CollectErrorRepr here)
msg = str(report.longrepr[2])
self.outindent("!!! %s !!!" % msg)
#self.outindent("!!! error !!!")
self._failed.append(report)
self.indent = self.indent[:-len(self.INDENT)]
def pytest_sessionfinish(self, session, exitstatus):
def pytest_collection_finish(self):
if self._failed:
self._tw.sep("!", "collection failures")
for rep in self._failed:
rep.toterminal(self._tw)
return self._failed and 1 or 0
def repr_pythonversion(v=None):
if v is None:
@@ -415,50 +462,3 @@ def flatten(l):
else:
yield x
from py._test.session import Session
class ShowFuncargSession(Session):
def main(self, colitems):
self.fspath = py.path.local()
self.sessionstarts()
try:
self.showargs(colitems[0])
finally:
self.sessionfinishes(exitstatus=1)
def showargs(self, colitem):
tw = py.io.TerminalWriter()
from py._test.funcargs import getplugins
from py._test.funcargs import FuncargRequest
plugins = getplugins(colitem, withpy=True)
verbose = self.config.getvalue("verbose")
for plugin in plugins:
available = []
for name, factory in vars(plugin).items():
if name.startswith(FuncargRequest._argprefix):
name = name[len(FuncargRequest._argprefix):]
if name not in available:
available.append([name, factory])
if available:
pluginname = plugin.__name__
for name, factory in available:
loc = self.getlocation(factory)
if verbose:
funcargspec = "%s -- %s" %(name, loc,)
else:
funcargspec = name
tw.line(funcargspec, green=True)
doc = factory.__doc__ or ""
if doc:
for line in doc.split("\n"):
tw.line(" " + line.strip())
else:
tw.line(" %s: no docstring available" %(loc,),
red=True)
def getlocation(self, function):
import inspect
fn = py.path.local(inspect.getfile(function))
lineno = py.builtin._getcode(function).co_firstlineno
if fn.relto(self.fspath):
fn = fn.relto(self.fspath)
return "%s:%d" %(fn, lineno+1)

71
_pytest/tmpdir.py Normal file
View File

@@ -0,0 +1,71 @@
""" support for providing temporary directories to test functions. """
import pytest, py
from _pytest.monkeypatch import monkeypatch
class TempdirHandler:
def __init__(self, config):
self.config = config
self.trace = config.trace.get("tmpdir")
def ensuretemp(self, string, dir=1):
""" (deprecated) return temporary directory path with
the given string as the trailing part. It is usually
better to use the 'tmpdir' function argument which
provides an empty unique-per-test-invocation directory
and is guaranteed to be empty.
"""
#py.log._apiwarn(">1.1", "use tmpdir function argument")
return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True):
basetemp = self.getbasetemp()
if not numbered:
p = basetemp.mkdir(basename)
else:
p = py.path.local.make_numbered_dir(prefix=basename,
keep=0, rootdir=basetemp, lock_timeout=None)
self.trace("mktemp", p)
return p
def getbasetemp(self):
""" return base temporary directory. """
try:
return self._basetemp
except AttributeError:
basetemp = self.config.option.basetemp
if basetemp:
basetemp = py.path.local(basetemp)
if basetemp.check():
basetemp.remove()
basetemp.mkdir()
else:
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
self._basetemp = t = basetemp
self.trace("new basetemp", t)
return t
def finish(self):
self.trace("finish")
def pytest_configure(config):
config._mp = mp = monkeypatch()
t = TempdirHandler(config)
mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
def pytest_unconfigure(config):
config._tmpdirhandler.finish()
config._mp.undo()
def pytest_funcarg__tmpdir(request):
"""return a temporary directory path object
unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
"""
name = request._pyfuncitem.name
name = py.std.re.sub("[\W]", "_", name)
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
return x.realpath()

139
_pytest/unittest.py Normal file
View File

@@ -0,0 +1,139 @@
""" discovery and running of std-library "unittest" style tests. """
import pytest, py
import sys, pdb
def pytest_pycollect_makeitem(collector, name, obj):
unittest = sys.modules.get('unittest')
if unittest is None:
return # nobody can have derived unittest.TestCase
try:
isunit = issubclass(obj, unittest.TestCase)
except KeyboardInterrupt:
raise
except Exception:
pass
else:
if isunit:
return UnitTestCase(name, parent=collector)
class UnitTestCase(pytest.Class):
def collect(self):
loader = py.std.unittest.TestLoader()
for name in loader.getTestCaseNames(self.obj):
yield TestCaseFunction(name, parent=self)
def setup(self):
meth = getattr(self.obj, 'setUpClass', None)
if meth is not None:
meth()
super(UnitTestCase, self).setup()
def teardown(self):
meth = getattr(self.obj, 'tearDownClass', None)
if meth is not None:
meth()
super(UnitTestCase, self).teardown()
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, 'setup_method'):
self._testcase.setup_method(self._obj)
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):
self._testcase.teardown_method(self._obj)
def startTest(self, testcase):
pass
def _addexcinfo(self, rawexcinfo):
# unwrap potential exception info (see twisted trial support below)
rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
try:
excinfo = py.code.ExceptionInfo(rawexcinfo)
except TypeError:
try:
try:
l = py.std.traceback.format_exception(*rawexcinfo)
l.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n")
pytest.fail("".join(l), pytrace=False)
except (pytest.fail.Exception, KeyboardInterrupt):
raise
except:
pytest.fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" %(rawexcinfo,), pytrace=False)
except KeyboardInterrupt:
raise
except pytest.fail.Exception:
excinfo = py.code.ExceptionInfo()
self.__dict__.setdefault('_excinfo', []).append(excinfo)
def addError(self, testcase, rawexcinfo):
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):
try:
pytest.xfail(str(reason))
except pytest.xfail.Exception:
self._addexcinfo(sys.exc_info())
def addUnexpectedSuccess(self, testcase, reason):
pass
def addSuccess(self, testcase):
pass
def stopTest(self, testcase):
pass
def runtest(self):
self._testcase(result=self)
@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
if item._excinfo:
call.excinfo = item._excinfo.pop(0)
del call.result
# twisted trial support
def pytest_runtest_protocol(item, __multicall__):
if isinstance(item, TestCaseFunction):
if 'twisted.trial.unittest' in sys.modules:
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):
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)
ut.Failure.__init__ = excstore
try:
return __multicall__.execute()
finally:
ut.Failure.__init__ = Failure__init__
def check_testcase_implements_trial_reporter(done=[]):
if done:
return
from zope.interface import classImplements
from twisted.trial.itrial import IReporter
classImplements(TestCaseFunction, IReporter)
done.append(1)

10
bench/bench.py Normal file
View File

@@ -0,0 +1,10 @@
if __name__ == '__main__':
import cProfile
import py
import pstats
stats = cProfile.run('py.test.cmdline.main(["empty.py", ])', 'prof')
p = pstats.Stats("prof")
p.strip_dirs()
p.sort_stats('cumulative')
print(p.print_stats(30))

3
bench/empty.py Normal file
View File

@@ -0,0 +1,3 @@
import py
for i in range(1000):
py.builtin.exec_("def test_func_%d(): pass" % i)

View File

@@ -1,6 +0,0 @@
py.test --dist=each $* \
--tx 'socket=192.168.1.106:8888'
#--tx 'popen//python=python2.6' \
#--tx 'ssh=noco//python=/usr/local/bin/python2.4//chdir=/tmp/pytest-python2.4' \

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env python
import py
import inspect
import types
def report_strange_docstring(name, obj):
if obj.__doc__ is None:
print "%s misses a docstring" % (name, )
elif obj.__doc__ == "":
print "%s has an empty" % (name, )
elif "XXX" in obj.__doc__:
print "%s has an 'XXX' in its docstring" % (name, )
def find_code(method):
return getattr(getattr(method, "im_func", None), "func_code", None)
def report_different_parameter_names(name, cls):
bases = cls.__mro__
for base in bases:
for attr in dir(base):
meth1 = getattr(base, attr)
code1 = find_code(meth1)
if code1 is None:
continue
if not callable(meth1):
continue
if not hasattr(cls, attr):
continue
meth2 = getattr(cls, attr)
code2 = find_code(meth2)
if not callable(meth2):
continue
if code2 is None:
continue
args1 = inspect.getargs(code1)[0]
args2 = inspect.getargs(code2)[0]
for a1, a2 in zip(args1, args2):
if a1 != a2:
print "%s.%s have different argument names %s, %s than the version in %s" % (name, attr, a1, a2, base)
def find_all_exported():
stack = [(name, getattr(py, name)) for name in dir(py)[::-1]
if not name.startswith("_") and name != "compat"]
seen = {}
exported = []
while stack:
name, obj = stack.pop()
if id(obj) in seen:
continue
else:
seen[id(obj)] = True
exported.append((name, obj))
if isinstance(obj, type) or isinstance(obj, type(py)):
stack.extend([("%s.%s" % (name, s), getattr(obj, s)) for s in dir(obj)
if len(s) <= 1 or not (s[0] == '_' and s[1] != '_')])
return exported
if __name__ == '__main__':
all_exported = find_all_exported()
print "strange docstrings"
print "=================="
print
for name, obj in all_exported:
if callable(obj):
report_strange_docstring(name, obj)
print "\n\ndifferent parameters"
print "===================="
print
for name, obj in all_exported:
if isinstance(obj, type):
report_different_parameter_names(name, obj)

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env python
XXX
import sys
import os
from _findpy import py
try:
import apigen
except ImportError:
print 'Can not find apigen - make sure PYTHONPATH is set correctly!'
py.std.sys.exit()
else:
args = list(sys.argv[1:])
args.extend(['-p', 'apigen'])
argkeys = [a.split('=')[0] for a in args]
if '--apigen' not in argkeys:
args.append('--apigen')
if '--apigenscript' not in argkeys:
fpath = os.path.join(
os.path.dirname(apigen.__file__), 'tool', 'py_build', 'build.py')
args.append('--apigenscript=%s' % (fpath,))
if '--apigenpath' not in argkeys:
args.append('--apigenpath=api')
py.test.cmdline.main(args)

View File

@@ -1,33 +0,0 @@
import py
bindir = py.path.local(__file__).dirpath().dirpath("bin")
assert bindir.check(), bindir
def getbasename(name):
assert name[:2] == "py"
return "py." + name[2:]
def genscript_unix(name):
basename = getbasename(name)
path = bindir.join(basename)
path.write(py.code.Source("""
#!/usr/bin/env python
from _findpy import py
py.cmdline.%s()
""" % name).strip())
path.chmod(0755)
def genscript_windows(name):
basename = getbasename(name)
winbasename = basename + ".cmd"
path = bindir.join("win32").join(winbasename)
path.write(py.code.Source("""
@echo off
python "%%~dp0\..\%s" %%*
""" % (basename)).strip())
if __name__ == "__main__":
for name in dir(py.cmdline):
if name[0] != "_":
genscript_unix(name)
genscript_windows(name)

View File

@@ -1,315 +0,0 @@
import sys
sys.path.insert(0, sys.argv[1])
import py
toolpath = py.path.local(__file__)
binpath = py.path.local(py.__file__).dirpath('bin')
def error(msg):
print >>sys.stderr, msg
raise SystemExit, 1
def reformat(text):
return " ".join(text.split())
class SetupWriter(object):
EXCLUDES = ("MANIFEST.in", "contrib")
def __init__(self, basedir, pkg, setuptools=False):
self.basedir = basedir
self.setuptools = setuptools
assert self.basedir.check()
self.pkg = pkg
self.meta = pkg.__pkg__
self.lines = []
self.allpaths = self.getallpath(self.basedir)
def getallpath(self, basedir):
contrib = self.basedir.join("contrib")
allpath = []
lines = py.process.cmdexec("hg st -mcan").split("\n")
for path in lines:
p = basedir.join(path)
assert p.check(), p
if not p.relto(contrib) and p != contrib and not self.isexcluded(p):
allpath.append(p)
return allpath
def append(self, string):
lines = string.split("\n")
while lines:
if not lines[0].strip():
lines.pop(0)
continue
break
if not lines:
self.lines.append("")
return
line = lines[0]
indent = len(line) - len(line.lstrip())
for line in lines:
if line.strip():
assert not line[:indent].strip(), line
line = line[indent:]
self.lines.append(line)
def write_winfuncs(self):
self.append('''
''')
def tip_info(self, indent=8):
old = self.basedir.chdir()
indent = " " * indent
try:
info = []
output = py.process.cmdexec(
"hg tip --template '" # tags: {tags}\n"
#"branch: {branches}\n"
"revision: {rev}:{node}\n'"
)
for line in output.split("\n"):
info.append("%s %s" %(indent, line.strip()))
return "\n".join(info)
finally:
old.chdir()
def setup_header(self):
#tooltime = "%s %s" %(py.std.time.asctime(), py.std.time.tzname[0])
toolname = toolpath.basename
#toolrevision = py.path.svnwc(toolpath).info().rev
pkgname = self.pkg.__name__
self.append('"""py lib / py.test setup.py file"""')
self.append('import os, sys')
self.append("from setuptools import setup")
def setup_trailer(self):
self.append('''
if __name__ == '__main__':
main()
''')
def setup_function(self):
params = self.__dict__.copy()
params.update(self.meta.__dict__)
self.append('long_description = """')
for line in params['long_description'].split('\n'):
self.append(line)
self.append('"""')
trunk = None
if params['version'] == 'trunk':
trunk = 'trunk'
self.append('trunk = %r' % trunk)
self.append('''
def main():
setup(
name=%(name)r,
description=%(description)r,
long_description = long_description,
version= trunk or %(version)r,
url=%(url)r,
license=%(license)r,
platforms=%(platforms)r,
author=%(author)r,
author_email=%(author_email)r,
''' % params)
indent = " " * 8
self.append_pprint(indent, entry_points={'console_scripts':self.getconsolescripts()})
self.append_pprint(indent, classifiers=self.meta.classifiers)
self.append_pprint(indent, packages=self.getpackages())
self.append_pprint(indent, package_data=self.getpackagedata())
self.append_pprint(indent, zip_safe=True)
self.append_pprint(indent, install_requires=['apipkg'])
self.lines.append(indent[4:] + ")\n")
def setup_scripts(self):
# XXX this was used for a different approach
not used
self.append("""
def getscripts():
if sys.platform == "win32":
base = "py/bin/win32/"
ext = ".cmd"
else:
base = "py/bin/"
ext = ""
l = []
for name in %r:
l.append(base + name + ext)
return l
""" % ([script.basename for script in binpath.listdir("py.*")]))
def append_pprint(self, indent, append=",", **kw):
for name, value in kw.items():
stringio = py.std.StringIO.StringIO()
value = py.std.pprint.pprint(value, stream=stringio)
stringio.seek(0)
lines = stringio.readlines()
line = lines.pop(0).rstrip()
self.lines.append(indent + "%s=%s" %(name, line))
indent = indent + " " * (len(name)+1)
for line in lines:
self.lines.append(indent + line.rstrip())
self.lines[-1] = self.lines[-1] + append
def getconsolescripts(self):
bindir = self.basedir.join('py', 'bin')
scripts = []
for p in self.allpaths:
if p.dirpath() == bindir:
if p.basename.startswith('py.'):
shortname = "py" + p.basename[3:]
scripts.append("%s = py.cmdline:%s" %
(p.basename, shortname))
return scripts
def getscripts(self):
bindir = self.basedir.join('py', 'bin')
scripts = []
for p in self.allpaths:
if p.dirpath() == bindir:
if p.basename.startswith('py.'):
scripts.append(p.relto(self.basedir))
return scripts
def getpackages(self):
packages = []
for p in self.allpaths: # contains no directories!
#if p.basename == "py":
# continue
if p.dirpath('__init__.py').check():
modpath = p.dirpath().relto(self.basedir).replace(p.sep, '.')
if modpath != "py" and not modpath.startswith("py."):
continue
if modpath in packages:
continue
for exclude in self.EXCLUDES:
if modpath.startswith(exclude):
print "EXCLUDING", modpath
break
else:
packages.append(modpath)
packages.sort()
return packages
def getpackagedata(self):
datafiles = []
pkgbase = self.basedir.join(self.pkg.__name__)
for p in self.allpaths:
if p.check(file=1) and (not p.dirpath("__init__.py").check()
or p.ext != ".py"):
if p.dirpath() != self.basedir:
x = p.relto(pkgbase)
if x:
datafiles.append(p.relto(pkgbase))
return {'py': datafiles}
def getdatafiles(self):
datafiles = []
for p in self.allpaths:
if p.check(file=1) and not p.ext == ".py":
if p.dirpath() != self.basedir:
datafiles.append(p.relto(self.basedir))
return datafiles
def setup_win32(self):
import winpath
self.append(py.std.inspect.getsource(winpath))
self.append("""
from distutils.command.install import install
class my_install(install):
def finalize_other(self):
install.finalize_other(self)
on_win32_add_to_PATH()
cmdclass = {'install': my_install}
""")
def setup_win32(self):
self.append(r'''
# scripts for windows: turn "py.SCRIPT" into "py_SCRIPT" and create
# "py.SCRIPT.cmd" files invoking "py_SCRIPT"
from distutils.command.install_scripts import install_scripts
class my_install_scripts(install_scripts):
def run(self):
install_scripts.run(self)
#print self.outfiles
for fn in self.outfiles:
basename = os.path.basename(fn)
if basename.startswith("py.") and not basename.endswith(".cmd"):
newbasename = basename.replace(".", "_")
newfn = os.path.join(os.path.dirname(fn), newbasename)
if os.path.exists(newfn):
os.remove(newfn)
os.rename(fn, newfn)
fncmd = fn + ".cmd"
if os.path.exists(fncmd):
os.remove(fncmd)
f = open(fncmd, 'w')
f.write("@echo off\n")
f.write('python "%%~dp0\%s" %%*' %(newbasename))
f.close()
if sys.platform == "win32":
cmdclass = {'install_scripts': my_install_scripts}
else:
cmdclass = {}
''')
def write_setup(self):
self.setup_header()
self.setup_function()
#self.setup_scripts()
#self.setup_win32()
self.setup_trailer()
targetfile = self.basedir.join("setup.py")
targetfile.write("\n".join(self.lines))
print "wrote", targetfile
def isexcluded(self, wcpath):
return wcpath.basename[0] == "."
rel = wcpath.relto(self.basedir)
if rel.find("testing") != -1:
return True
def write_manifest(self):
lines = []
for p in self.allpaths:
if p.check(dir=1):
continue
toadd = p.relto(self.basedir)
if toadd:
for exclude in self.EXCLUDES:
if toadd.startswith(exclude):
break
assert toadd.find(exclude) == -1, (toadd, exclude)
else:
lines.append("%s" %(toadd))
lines.sort()
targetfile = self.basedir.join("MANIFEST")
targetfile.write("\n".join(lines))
print "wrote", targetfile
def write_all(self):
#self.write_manifest()
self.write_setup()
def parseargs():
basedir = py.path.local(sys.argv[1])
if not basedir.check():
error("basedir not found: %s" %(basedir,))
pydir = basedir.join('py')
if not pydir.check():
error("no 'py' directory found in: %s" %(pydir,))
actualpydir = py.path.local(py.__file__).dirpath()
if pydir != actualpydir:
error("package dir conflict, %s != %s" %(pydir, actualpydir))
return basedir
def main(basedir=None):
if basedir is None:
basedir = parseargs()
writer = SetupWriter(basedir, py, setuptools=True)
writer.write_all()
if __name__ == "__main__":
main()

View File

@@ -1,26 +0,0 @@
import os, sys, subprocess, urllib
BUILDNAME=os.environ.get('BUILD_NUMBER', "1")
def call(*args):
ret = subprocess.call(list(args))
assert ret == 0
def bincall(*args):
args = list(args)
args[0] = os.path.join(BIN, args[0])
call(*args)
call("virtualenv", os.path.abspath(BUILDNAME), '--no-site-packages')
BIN=os.path.abspath(os.path.join(BUILDNAME, 'bin'))
if not os.path.exists(BIN):
BIN=os.path.abspath(os.path.join(BUILDNAME, 'Scripts'))
assert os.path.exists(BIN)
PYTHON=os.path.join(BIN, 'python')
bincall("python", "setup.py", "develop", "-q")
bincall("pip", "install", "-r", "testing/pip-reqs1.txt",
"-q", "--download-cache=download")
bincall("py.test", "--ignore", BUILDNAME,
"--xml=junit.xml",
"--report=skipped", "--runslowtest", *sys.argv[1:])

View File

@@ -1,301 +0,0 @@
import os, sys
WIDTH = 75
plugins = [
('advanced python testing',
'skipping mark pdb figleaf '
'monkeypatch coverage cov capture capturelog recwarn tmpdir',),
('distributed testing, CI and deployment',
'xdist pastebin junitxml resultlog genscript',),
('testing domains and conventions codecheckers',
'oejskit django unittest nose doctest restdoc'),
('internal, debugging, help functionality',
'helpconfig terminal hooklog')
#('internal plugins / core functionality',
# #'runner execnetcleanup # pytester',
# 'runner execnetcleanup' # pytester',
#)
]
externals = {
'oejskit': "run javascript tests in real life browsers",
'xdist': None,
'figleaf': None,
'capturelog': None,
'coverage': None,
'cov': None,
'codecheckers': None,
'django': "for testing django applications",
}
def warn(*args):
msg = " ".join(map(str, args))
print >>sys.stderr, "WARN:", msg
class RestWriter:
_all_links = {}
def __init__(self, target):
self.target = py.path.local(target)
self.links = []
def _getmsg(self, args):
return " ".join(map(str, args))
def Print(self, *args, **kwargs):
msg = self._getmsg(args)
if 'indent' in kwargs:
indent = kwargs['indent'] * " "
lines = [(indent + x) for x in msg.split("\n")]
msg = "\n".join(lines)
self.out.write(msg)
if not msg or msg[-1] != "\n":
self.out.write("\n")
self.out.flush()
def sourcecode(self, source):
lines = str(source).split("\n")
self.Print(".. sourcecode:: python")
self.Print()
for line in lines:
self.Print(" ", line)
def _sep(self, separator, args):
msg = self._getmsg(args)
sep = len(msg) * separator
self.Print()
self.Print(msg)
self.Print(sep)
self.Print()
def h1(self, *args):
self._sep('=', args)
def h2(self, *args):
self._sep('-', args)
def h3(self, *args):
self._sep('+', args)
def li(self, *args):
msg = self._getmsg(args)
sep = "* %s" %(msg)
self.Print(sep)
def dt(self, term):
self.Print("``%s``" % term)
def dd(self, doc):
self.Print(doc, indent=4)
def para(self, *args):
msg = self._getmsg(args)
self.Print(msg)
def add_internal_link(self, name, path):
relpath = path.new(ext=".html").relto(self.target.dirpath())
self.links.append((name, relpath))
def write_links(self):
self.Print()
self.Print(".. include:: links.txt")
for link in self.links:
key = link[0]
if key in self._all_links:
assert self._all_links[key] == link[1], (key, link[1])
else:
self._all_links[key] = link[1]
def write_all_links(cls, linkpath):
p = linkpath.new(basename="links.txt")
p_writer = RestWriter(p)
p_writer.out = p_writer.target.open("w")
for name, value in cls._all_links.items():
p_writer.Print(".. _`%s`: %s" % (name, value))
p_writer.out.close()
del p_writer.out
write_all_links = classmethod(write_all_links)
def make(self, **kwargs):
self.out = self.target.open("w")
self.makerest(**kwargs)
self.write_links()
self.out.close()
print "wrote", self.target
del self.out
class PluginOverview(RestWriter):
def makerest(self, config):
plugindir = py._pydir.join('plugin')
for cat, specs in plugins:
pluginlist = specs.split()
self.h1(cat)
for name in pluginlist:
oneliner = externals.get(name, None)
docpath = self.target.dirpath(name).new(ext=".txt")
if oneliner is not None:
htmlpath = docpath.new(ext='.html')
self.para("%s_ (external) %s" %(name, oneliner))
self.add_internal_link(name, htmlpath)
else:
doc = PluginDoc(docpath)
doc.make(config=config, name=name)
self.add_internal_link(name, doc.target)
if name in externals:
self.para("%s_ (external) %s" %(name, doc.oneliner))
else:
self.para("%s_ %s" %(name, doc.oneliner))
self.Print()
class HookSpec(RestWriter):
def makerest(self, config):
for module in config.pluginmanager.hook._hookspecs:
source = py.code.Source(module)
self.h1("hook specification sourcecode")
self.sourcecode(source)
class PluginDoc(RestWriter):
def makerest(self, config, name):
config.pluginmanager.import_plugin(name)
plugin = config.pluginmanager.getplugin(name)
assert plugin is not None, plugin
print plugin
doc = plugin.__doc__.strip()
i = doc.find("\n")
if i == -1:
oneliner = doc
moduledoc = ""
else:
oneliner = doc[:i].strip()
moduledoc = doc[i+1:].strip()
self.name = oneliner # plugin.__name__.split(".")[-1]
self.oneliner = oneliner
self.moduledoc = moduledoc
#self.h1("%s plugin" % self.name) # : %s" %(self.name, self.oneliner))
self.h1(oneliner)
#self.Print(self.oneliner)
self.Print()
self.Print(".. contents::")
self.Print(" :local:")
self.Print()
self.Print(moduledoc)
self.emit_funcargs(plugin)
self.emit_options(plugin)
if name not in externals:
self.emit_source(plugin, config.hg_changeset)
#self.sourcelink = (purename,
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
# purename + ".py")
#
def emit_source(self, plugin, hg_changeset):
basename = py.path.local(plugin.__file__).basename
if basename.endswith("pyc"):
basename = basename[:-1]
#self.para("`%s`_ source code" % basename)
#self.links.append((basename,
# "http://bitbucket.org/hpk42/py-trunk/src/tip/py/test/plugin/" +
# basename))
self.h1("Start improving this plugin in 30 seconds")
self.para(py.code.Source("""
1. Download `%s`_ plugin source code
2. put it somewhere as ``%s`` into your import path
3. a subsequent ``py.test`` run will use your local version
Checkout customize_, other plugins_ or `get in contact`_.
""" % (basename, basename)))
# your work appreciated if you offer back your version. In this case
# it probably makes sense if you `checkout the py.test
# development version`_ and apply your changes to the plugin
# version in there.
#self.links.append((basename,
# "http://bitbucket.org/hpk42/py-trunk/raw/%s/"
# "py/test/plugin/%s" %(hg_changeset, basename)))
self.links.append((basename,
"http://bitbucket.org/hpk42/py-trunk/raw/%s/"
"py/_plugin/%s" %(pyversion, basename)))
self.links.append(('customize', '../customize.html'))
self.links.append(('plugins', 'index.html'))
self.links.append(('get in contact', '../../contact.html'))
self.links.append(('checkout the py.test development version',
'../../install.html#checkout'))
if 0: # this breaks the page layout and makes large doc files
#self.h2("plugin source code")
self.Print()
self.para("For your convenience here is also an inlined version "
"of ``%s``:" %basename)
#self(or copy-paste from below)
self.Print()
self.sourcecode(py.code.Source(plugin))
def emit_funcargs(self, plugin):
funcargfuncs = []
prefix = "pytest_funcarg__"
for name in vars(plugin):
if name.startswith(prefix):
funcargfuncs.append(getattr(plugin, name))
if not funcargfuncs:
return
for func in funcargfuncs:
argname = func.__name__[len(prefix):]
self.Print()
self.Print(".. _`%s funcarg`:" % argname)
self.Print()
self.h2("the %r test function argument" % argname)
if func.__doc__:
doclines = func.__doc__.split("\n")
source = py.code.Source("\n".join(doclines[1:]))
source.lines.insert(0, doclines[0])
self.para(str(source))
else:
self.para("XXX missing docstring")
warn("missing docstring", func)
def emit_options(self, plugin):
from py._test.parseopt import Parser
options = []
parser = Parser(processopt=options.append)
if hasattr(plugin, 'pytest_addoption'):
plugin.pytest_addoption(parser)
if not options:
return
self.h2("command line options")
self.Print()
formatter = py.std.optparse.IndentedHelpFormatter()
for opt in options:
switches = formatter.format_option_strings(opt)
self.Print("``%s``" % switches)
self.Print(opt.help, indent=4)
if __name__ == "__main__":
if os.path.exists("py"):
sys.path.insert(0, os.getcwd())
import py
_config = py.test.config
_config.parse([])
_config.pluginmanager.do_configure(_config)
pydir = py.path.local(py.__file__).dirpath()
pyversion = py.version
cmd = "hg tip --template '{node}'"
old = pydir.dirpath().chdir()
_config.hg_changeset = py.process.cmdexec(cmd).strip()
testdir = pydir.dirpath("doc", 'test')
ov = PluginOverview(testdir.join("plugin", "index.txt"))
ov.make(config=_config)
ov = HookSpec(testdir.join("plugin", "hookspec.txt"))
ov.make(config=_config)
RestWriter.write_all_links(testdir.join("plugin", "links.txt"))

View File

@@ -1,228 +0,0 @@
import py
import subprocess
import os, sys
execnet = py.test.importorskip("execnet")
#
# experimental funcargs for venv/install-tests
#
pytest_plugins = 'pytest_pytester',
def pytest_funcarg__venv(request):
p = request.config.mktemp(request.function.__name__, numbered=True)
venv = VirtualEnv(str(p))
return venv
def pytest_funcarg__py_setup(request):
testdir = request.getfuncargvalue('testdir')
rootdir = py.path.local(py.__file__).dirpath().dirpath()
setup = rootdir.join('setup.py')
if not setup.check():
py.test.skip("not found: %r" % setup)
return SetupBuilder(setup, testdir.tmpdir)
class SetupBuilder:
def __init__(self, setup_path, tmpdir):
self.setup_path = setup_path
self.tmpdir = tmpdir
assert setup_path.check()
def make_sdist(self, destdir=None):
temp = self.tmpdir.mkdir('dist')
args = ['python', 'setup.py', 'sdist', '--dist-dir', str(temp)]
old = self.setup_path.dirpath().chdir()
try:
subcall(args)
finally:
old.chdir()
l = temp.listdir('py-*')
assert len(l) == 1
sdist = l[0]
if destdir is None:
destdir = self.setup_path.dirpath('build')
assert destdir.check()
else:
destdir = py.path.local(destdir)
target = destdir.join(sdist.basename)
sdist.copy(target)
return target
def subcall(args):
if hasattr(subprocess, 'check_call'):
subprocess.check_call(args)
else:
subprocess.call(args)
# code taken from Ronny Pfannenschmidt's virtualenvmanager
class VirtualEnv(object):
def __init__(self, path):
#XXX: supply the python executable
self.path = path
def __repr__(self):
return "<VirtualEnv at %r>" %(self.path)
def _cmd(self, name):
if sys.platform == "win32":
return os.path.join(self.path, 'Scripts', name)
else:
return os.path.join(self.path, 'bin', name)
def ensure(self):
if not os.path.exists(self._cmd('python')):
self.create()
def create(self, sitepackages=False):
args = ['virtualenv', self.path]
if not sitepackages:
args.append('--no-site-packages')
subcall(args)
def makegateway(self):
python = self._cmd('python')
return execnet.makegateway("popen//python=%s" %(python,))
def pcall(self, cmd, *args, **kw):
self.ensure()
return subprocess.call([
self._cmd(cmd)
] + list(args),
**kw)
def pytest_getouterr(self, *args):
self.ensure()
args = [self._cmd("py.test")] + list(args)
popen = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, err = popen.communicate()
return out
def setup_develop(self):
self.ensure()
return self.pcall("python", "setup.py", "develop")
def easy_install(self, *packages, **kw):
args = []
if 'index' in kw:
index = kw['index']
if isinstance(index, (list, tuple)):
for i in index:
args.extend(['-i', i])
else:
args.extend(['-i', index])
args.extend(packages)
self.pcall('easy_install', *args)
def test_make_sdist_and_run_it(py_setup, venv):
sdist = py_setup.make_sdist(venv.path)
venv.easy_install(str(sdist))
gw = venv.makegateway()
ch = gw.remote_exec("import py ; channel.send(py.__version__)")
version = ch.receive()
assert version == py.__version__
def test_plugin_setuptools_entry_point_integration(py_setup, venv, tmpdir):
sdist = py_setup.make_sdist(venv.path)
venv.easy_install(str(sdist))
# create a sample plugin
basedir = tmpdir.mkdir("testplugin")
basedir.join("setup.py").write("""if 1:
from setuptools import setup
setup(name="testplugin",
entry_points = {'pytest11': ['testplugin=tp1']},
py_modules = ['tp1'],
)
""")
basedir.join("tp1.py").write(py.code.Source("""
def pytest_addoption(parser):
parser.addoption("--testpluginopt", action="store_true")
"""))
basedir.chdir()
print ("created sample plugin in %s" %basedir)
venv.setup_develop()
out = venv.pytest_getouterr("-h")
assert "testpluginopt" in out
def test_cmdline_entrypoints(monkeypatch):
monkeypatch.syspath_prepend(py.path.local(__file__).dirpath().dirpath())
from setup import cmdline_entrypoints
versioned_scripts = ['py.test', 'py.which']
unversioned_scripts = versioned_scripts + [ 'py.cleanup',
'py.convert_unittest', 'py.countloc', 'py.lookup', 'py.svnwcrevert']
for ver in [(2,4,0), (2,5,0), (2,6,0), (2,7,0), (3,0,1), (3,1,1)]:
for platform in ('posix', 'win32'):
points = cmdline_entrypoints(ver, "posix", 'python')
for script in versioned_scripts:
script_ver = script + "-%s.%s" % ver[:2]
assert script_ver in points
for script in unversioned_scripts:
assert script in points
points = cmdline_entrypoints((2,5,1), "java1.6.123", 'jython')
for script in versioned_scripts:
expected = "%s-jython" % script
assert expected in points
for script in unversioned_scripts:
assert script in points
points = cmdline_entrypoints((2,5,1), "xyz", 'pypy-c-XYZ')
for script in versioned_scripts:
expected = "%s-pypy-c-XYZ" % script
assert expected in points
for script in unversioned_scripts:
assert script in points
def test_slave_popen_needs_no_pylib(testdir, venv, pytestconfig):
pytestconfig.pluginmanager.skipifmissing("xdist")
venv.ensure()
#xxx execnet optimizes popen
#ch = venv.makegateway().remote_exec("import execnet")
#py.test.raises(ch.RemoteError, ch.waitclose)
python = venv._cmd("python")
p = testdir.makepyfile("""
import py
def test_func():
pass
""")
result = testdir.runpytest(p, '--rsyncdir=%s' % str(p),
'--dist=each', '--tx=popen//python=%s' % python)
result.stdout.fnmatch_lines([
"*1 passed*"
])
def test_slave_needs_no_execnet(testdir, sshhost, pytestconfig):
pytestconfig.pluginmanager.skipifmissing("xdist")
xspec = "ssh=%s" % sshhost
gw = execnet.makegateway("ssh=%s" % sshhost)
ch = gw.remote_exec("""
import os, subprocess
subprocess.call(["virtualenv", "--no-site-packages", "subdir"])
channel.send(os.path.join(os.path.abspath("subdir"), 'bin', 'python'))
channel.send(os.path.join(os.path.abspath("subdir")))
""")
try:
path = ch.receive()
chdir = ch.receive()
except ch.RemoteError:
e = sys.exc_info()[1]
py.test.skip("could not prepare ssh slave:%s" % str(e))
gw.exit()
newspec = "%s//python=%s//chdir=%s" % (xspec, path, chdir)
gw = execnet.makegateway(newspec)
ch = gw.remote_exec("import execnet")
py.test.raises(ch.RemoteError, ch.waitclose)
gw.exit()
p = testdir.makepyfile("""
import py
def test_func():
pass
""")
result = testdir.runpytest(p, '--rsyncdir=%s' % str(p),
'--dist=each', '--tx=%s' % newspec)
result.stdout.fnmatch_lines([
"*1 passed*"
])

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env python
#
# find and import a version of 'py' that exists in a parent dir
# of the current working directory. fall back to import a
# globally available version
#
import sys
import os
from os.path import dirname as opd, exists, join, basename, abspath
def searchpy(current):
while 1:
last = current
initpy = join(current, '__init__.py')
if not exists(initpy):
pydir = join(current, 'py')
# recognize py-package and ensure it is importable
if exists(pydir) and exists(join(pydir, '__init__.py')):
#for p in sys.path:
# if p == current:
# return True
if current != sys.path[0]: # if we are already first, then ok
sys.stderr.write("inserting into sys.path: %s\n" % current)
sys.path.insert(0, current)
return True
current = opd(current)
if last == current:
return False
if not searchpy(abspath(os.curdir)):
if not searchpy(opd(abspath(sys.argv[0]))):
if not searchpy(opd(__file__)):
pass # let's hope it is just on sys.path
import py
if __name__ == '__main__':
print ("py lib is at %s" % py.__file__)

View File

@@ -1,2 +0,0 @@
@echo off
for /F "usebackq delims=" %%i in (`python "%~dp0\env.py"`) do %%i

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env python
import sys, os, os.path
progpath = sys.argv[0]
packagedir = os.path.dirname(os.path.dirname(os.path.abspath(progpath)))
packagename = os.path.basename(packagedir)
bindir = os.path.join(packagedir, 'bin')
if sys.platform == 'win32':
bindir = os.path.join(bindir, 'win32')
rootdir = os.path.dirname(packagedir)
def prepend_path(name, value):
sep = os.path.pathsep
curpath = os.environ.get(name, '')
newpath = [value] + [ x for x in curpath.split(sep) if x and x != value ]
return setenv(name, sep.join(newpath))
def setenv(name, value):
shell = os.environ.get('SHELL', '')
comspec = os.environ.get('COMSPEC', '')
if shell.endswith('csh'):
cmd = 'setenv %s "%s"' % (name, value)
elif shell.endswith('sh'):
cmd = '%s="%s"; export %s' % (name, value, name)
elif comspec.endswith('cmd.exe'):
cmd = 'set %s=%s' % (name, value)
else:
assert False, 'Shell not supported.'
return cmd
print(prepend_path('PATH', bindir))
print(prepend_path('PYTHONPATH', rootdir))

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pycleanup()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pyconvert_unittest()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pycountloc()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pylookup()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pysvnwcrevert()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pytest()

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env python
from _findpy import py
py.cmdline.pywhich()

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.cleanup" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.convert_unittest" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.countloc" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.lookup" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.svnwcrevert" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.test" %*

View File

@@ -1,2 +0,0 @@
@echo off
python "%~dp0\..\py.which" %*

View File

@@ -1,95 +0,0 @@
import py
import sys
pytest_plugins = '_pytest doctest pytester'.split()
collect_ignore = ['build', 'doc/_build']
rsyncdirs = ['conftest.py', 'bin', 'py', 'doc', 'testing']
import os, py
pid = os.getpid()
def pytest_addoption(parser):
group = parser.getgroup("pylib", "py lib testing options")
group.addoption('--sshhost',
action="store", dest="sshhost", default=None,
help=("ssh xspec for ssh functional tests. "))
group.addoption('--runslowtests',
action="store_true", dest="runslowtests", default=False,
help=("run slow tests"))
group.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
def pytest_configure(config):
if config.getvalue("lsof"):
try:
out = py.process.cmdexec("lsof -p %d" % pid)
except py.process.cmdexec.Error:
pass
else:
config._numfiles = len([x for x in out.split("\n") if "REG" in x])
def pytest_unconfigure(config, __multicall__):
if not hasattr(config, '_numfiles'):
return
__multicall__.execute()
out2 = py.process.cmdexec("lsof -p %d" % pid)
len2 = len([x for x in out2.split("\n") if "REG" in x])
assert len2 < config._numfiles + 7, out2
def pytest_funcarg__sshhost(request):
val = request.config.getvalue("sshhost")
if val:
return val
py.test.skip("need --sshhost option")
def pytest_generate_tests(metafunc):
multi = getattr(metafunc.function, 'multi', None)
if multi is not None:
assert len(multi.kwargs) == 1
for name, l in multi.kwargs.items():
for val in l:
metafunc.addcall(funcargs={name: val})
elif 'anypython' in metafunc.funcargnames:
for name in ('python2.4', 'python2.5', 'python2.6',
'python2.7', 'python3.1', 'pypy-c', 'jython'):
metafunc.addcall(id=name, param=name)
# XXX copied from execnet's conftest.py - needs to be merged
winpymap = {
'python2.7': r'C:\Python27\python.exe',
'python2.6': r'C:\Python26\python.exe',
'python2.5': r'C:\Python25\python.exe',
'python2.4': r'C:\Python24\python.exe',
'python3.1': r'C:\Python31\python.exe',
}
def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = py.path.local.sysfind(name)
if executable:
if name == "jython":
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
out, err = popen.communicate()
if not err or "2.5" not in err:
executable = None
cache[name] = executable
return executable
def pytest_funcarg__anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
py.test.skip("no %s found" % (name,))
return executable

View File

@@ -1,8 +0,0 @@
import py
def pytest_runtest_call(item, __multicall__):
cap = py.io.StdCapture()
try:
return __multicall__.execute()
finally:
outerr = cap.reset()

View File

@@ -1,374 +0,0 @@
"""XXX in progress: resultdb plugin for database logging of test results.
Saves test results to a datastore.
XXX this needs to be merged with resultlog plugin
Also mixes in some early ideas about an archive abstraction for test
results.
"""
import py
py.test.skip("XXX needs to be merged with resultlog")
from pytest_resultlog import ResultLog
def pytest_addoption(parser):
group = parser.addgroup("resultdb", "resultdb plugin options")
group.addoption('--resultdb', action="store", dest="resultdb",
metavar="path",
help="path to the file to store test results.")
group.addoption('--resultdb_format', action="store",
dest="resultdbformat", default='json',
help="data format (json, sqlite)")
def pytest_configure(config):
# XXX using config.XYZ is not good
if config.getvalue('resultdb'):
if config.option.resultdb:
# local import so missing module won't crash py.test
try:
import sqlite3
except ImportError:
raise config.Error('Could not import sqlite3 module')
try:
import simplejson
except ImportError:
raise config.Error('Could not import simplejson module')
if config.option.resultdbformat.lower() == 'json':
resultdb = ResultDB(JSONResultArchive,
config.option.resultdb)
elif config.option.resultdbformat.lower() == 'sqlite':
resultdb = ResultDB(SQLiteResultArchive,
config.option.resultdb)
else:
raise config.Error('Unknown --resultdb_format: %s' %
config.option.resultdbformat)
config.pluginmanager.register(resultdb)
class JSONResultArchive(object):
def __init__(self, archive_path):
self.archive_path = archive_path
import simplejson
self.simplejson = simplejson
def init_db(self):
if os.path.exists(self.archive_path):
data_file = open(self.archive_path)
archive = self.simplejson.load(data_file)
self.archive = archive
else:
self.archive = []
self._flush()
def append_data(self, data):
runid = py.std.uuid.uuid4()
for item in data:
item = item.copy()
item['runid'] = str(runid)
self.archive.append(item)
self._flush()
def get_all_data(self):
return self.archive
def _flush(self):
data_file = open(self.archive_path, 'w')
self.simplejson.dump(self.archive, data_file)
data_file.close()
class SQLiteResultArchive(object):
def __init__(self, archive_path):
self.archive_path = archive_path
import sqlite3
self.sqlite3 = sqlite3
def init_db(self):
if not os.path.exists(self.archive_path):
conn = self.sqlite3.connect(self.archive_path)
cursor = conn.cursor()
try:
cursor.execute(SQL_CREATE_TABLES)
conn.commit()
finally:
cursor.close()
conn.close()
def append_data(self, data):
flat_data = []
runid = py.std.uuid.uuid4()
for item in data:
item = item.copy()
item['runid'] = str(runid)
flat_data.append(self.flatten(item))
conn = self.sqlite3.connect(self.archive_path)
cursor = conn.cursor()
cursor.executemany(SQL_INSERT_DATA, flat_data)
conn.commit()
cursor.close()
conn.close()
def get_all_data(self):
conn = self.sqlite3.connect(self.archive_path)
conn.row_factory = self.sqlite3.Row
cursor = conn.cursor()
cursor.execute(SQL_SELECT_DATA)
data = cursor.fetchall()
cursor.close()
conn.close()
data = [self.unflatten(item) for item in data]
return data
def flatten(self, item):
return (item.get('runid', None),
item.get('name', None),
item.get('passed', False),
item.get('skipped', False),
item.get('failed', False),
item.get('shortrepr', None),
item.get('longrepr', None),
item.get('fspath', None),
item.get('itemname', None),
)
def unflatten(self, item):
names = ("runid name passed skipped failed shortrepr "
"longrepr fspath itemname").split()
d = {}
for i, name in enumerate(names):
d[name] = item[i]
return d
class ResultDB(ResultLog):
def __init__(self, cls, db_path):
self.archive = cls(db_path)
self.archive.init_db()
def write_log_entry(self, testpath, shortrepr, longrepr):
data = {}
event_excludes = ['colitem', 'longrepr']
for item in vars(event).keys():
if item not in event_excludes:
data[item] = getattr(event, item)
# use the locally calculated longrepr & shortrepr
data['longrepr'] = longrepr
data['shortrepr'] = shortrepr
data['testpath'] = unicode(testpath)
self.archive.append_data([data])
SQL_CREATE_TABLES = """
create table pytest_results (
runid varchar(36),
name varchar,
passed int,
skipped int,
failed int,
shortrepr varchar,
longrepr varchar,
fspath varchar,
itemname varchar
);
"""
SQL_INSERT_DATA = """
insert into pytest_results (
runid,
name,
passed,
skipped,
failed,
shortrepr,
longrepr,
fspath,
itemname)
values (?, ?, ?, ?, ?, ?, ?, ?, ?);
"""
SQL_SELECT_DATA = """
select
runid,
name,
passed,
skipped,
failed,
shortrepr,
longrepr,
fspath,
itemname
from pytest_results;
"""
# ===============================================================================
#
# plugin tests
#
# ===============================================================================
import os, StringIO
class BaseResultArchiveTests(object):
cls = None
def setup_class(cls):
# XXX refactor setup into a funcarg?
cls.tempdb = "test_tempdb"
def test_init_db(self, testdir):
tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
archive = self.cls(tempdb_path)
archive.init_db()
assert os.path.exists(tempdb_path)
def test_db_insert(self, testdir):
tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
archive = self.cls(tempdb_path)
archive.init_db()
assert len(archive.get_all_data()) == 0
data = [{'name': 'tmppackage/test_whatever.py:test_hello',
'fspath': '/Users/brian/work/tmppackage/test_whatever.py',
'name': 'test_hello',
'longrepr': '',
'passed': True,
'shortrepr': '.'
}]
archive.append_data(data)
result = archive.get_all_data()
print result
assert len(result) == 1
for key, value in data[0].items():
assert value == result[0][key]
assert 'runid' in result[0]
# make sure the data is persisted
tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
archive = self.cls(tempdb_path)
archive.init_db()
assert len(archive.get_all_data()) == 1
class TestJSONResultArchive(BaseResultArchiveTests):
cls = JSONResultArchive
def setup_method(self, method):
py.test.importorskip("simplejson")
class TestSQLiteResultArchive(BaseResultArchiveTests):
cls = SQLiteResultArchive
def setup_method(self, method):
py.test.importorskip("sqlite3")
def test_init_db_sql(self, testdir):
py.test.importorskip("sqlite3")
tempdb_path = unicode(testdir.tmpdir.join(self.tempdb))
archive = self.cls(tempdb_path)
archive.init_db()
assert os.path.exists(tempdb_path)
# is table in the database?
import sqlite3
conn = sqlite3.connect(tempdb_path)
cursor = conn.cursor()
cursor.execute("""SELECT name FROM sqlite_master
ORDER BY name;""")
tables = cursor.fetchall()
cursor.close()
conn.close()
assert len(tables) == 1
def verify_archive_item_shape(item):
names = ("runid name passed skipped failed shortrepr "
"longrepr fspath itemname").split()
for name in names:
assert name in item
class TestWithFunctionIntegration:
def getarchive(self, testdir, arg):
py.test.importorskip("sqlite3")
py.test.importorskip("simplejson")
resultdb = testdir.tmpdir.join("resultdb")
args = ["--resultdb=%s" % resultdb, "--resultdb_format=sqlite"] + [arg]
testdir.runpytest(*args)
assert resultdb.check(file=1)
archive = SQLiteResultArchive(unicode(resultdb))
archive.init_db()
return archive
def test_collection_report(self, testdir):
py.test.skip("Needs a rewrite for db version.")
ok = testdir.makepyfile(test_collection_ok="")
skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
fail = testdir.makepyfile(test_collection_fail="XXX")
lines = self.getresultdb(testdir, ok)
assert not lines
lines = self.getresultdb(testdir, skip)
assert len(lines) == 2
assert lines[0].startswith("S ")
assert lines[0].endswith("test_collection_skip.py")
assert lines[1].startswith(" ")
assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'")
lines = self.getresultdb(testdir, fail)
assert lines
assert lines[0].startswith("F ")
assert lines[0].endswith("test_collection_fail.py"), lines[0]
for x in lines[1:]:
assert x.startswith(" ")
assert "XXX" in "".join(lines[1:])
def test_log_test_outcomes(self, testdir):
mod = testdir.makepyfile(test_mod="""
import py
def test_pass(): pass
def test_skip(): py.test.skip("hello")
def test_fail(): raise ValueError("val")
""")
archive = self.getarchive(testdir, mod)
data = archive.get_all_data()
for item in data:
verify_archive_item_shape(item)
assert len(data) == 3
assert len([item for item in data if item['passed'] == True]) == 1
assert len([item for item in data if item['skipped'] == True]) == 1
assert len([item for item in data if item['failed'] == True]) == 1
def test_internal_exception(self):
py.test.skip("Needs a rewrite for db version.")
# they are produced for example by a teardown failing
# at the end of the run
try:
raise ValueError
except ValueError:
excinfo = py.code.ExceptionInfo()
reslog = ResultDB(StringIO.StringIO())
reslog.pytest_internalerror(excinfo.getrepr)
entry = reslog.logfile.getvalue()
entry_lines = entry.splitlines()
assert entry_lines[0].startswith('! ')
assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc
assert entry_lines[-1][0] == ' '
assert 'ValueError' in entry
def test_generic(testdir):
testdir.makepyfile("""
import py
def test_pass():
pass
def test_fail():
assert 0
def test_skip():
py.test.skip("")
""")
testdir.runpytest("--resultdb=result.sqlite")
#testdir.tmpdir.join("result.sqlite")

View File

@@ -1,190 +0,0 @@
"""
Allows to test twisted applications with pytest.
Notes: twisted's asynchronous behavior may have influence on the order of test-functions
TODO:
+ credits to Ralf Schmitt See: http://twistedmatrix.com/pipermail/twisted-python/2007-February/014872.html
+ get test to work
"""
import sys
try:
from twisted.internet import reactor, defer
from twisted.python import failure, log
except ImportError:
print "To use the twisted option you have to install twisted."
sys.exit(10)
try:
from greenlet import greenlet
except ImportError:
print "Since pylib 1.0 greenlet are removed and separately packaged: " \
"http://pypi.python.org/pypi/greenlet"
sys.exit(10)
def _start_twisted_logging():
"""Enables twisted internal logging"""
class Logger(object):
"""late-bound sys.stdout"""
def write(self, msg):
sys.stdout.write(msg)
def flush(self):
sys.stdout.flush()
# sys.stdout will be changed by py.test later.
log.startLogging(Logger(), setStdout=0)
def _run_twisted(logging=False):
"""Start twisted mainloop and initialize recursive calling of doit()."""
# make twisted copy traceback...
failure.Failure.cleanFailure = lambda *args: None
if logging:
_start_twisted_logging()
def fix_signal_handling():
# see http://twistedmatrix.com/trac/ticket/733
import signal
if hasattr(signal, "siginterrupt"):
signal.siginterrupt(signal.SIGCHLD, False)
def start():
fix_signal_handling()
doit(None)
# recursively called for each test-function/method due done()
def doit(val): # val always None
# switch context to wait that wrapper() passes back to test-method
res = gr_tests.switch(val)
if res is None:
reactor.stop()
return
def done(res):
reactor.callLater(0.0, doit, None) # recursive call of doit()
def err(res):
reactor.callLater(0.0, doit, res)
# the test-function *may* return a deferred
# here the test-function will actually been called
# done() is finalizing a test-process by assuring recursive invoking
# of doit()
defer.maybeDeferred(res).addCallback(done).addErrback(err)
# initially preparing the calling of doit() and starting the reactor
reactor.callLater(0.0, start)
reactor.run()
def pytest_addoption(parser):
group = parser.addgroup('twisted options')
group.addoption('--twisted-logging', action='store_true', default=False,
dest='twisted_logging',
help="switch on twisted internal logging")
def pytest_configure(config):
twisted_logging = config.getvalue("twisted_logging")
gr_twisted.switch(twisted_logging)
def pytest_unconfigure(config):
gr_twisted.switch(None)
def pytest_pyfunc_call(pyfuncitem):
# XXX1 kwargs?
# XXX2 we want to delegate actual call to next plugin
# (which may want to produce test coverage, etc.)
res = gr_twisted.switch(lambda: pyfuncitem.call())
if res:
res.raiseException()
return True # indicates that we performed the function call
gr_twisted = greenlet(_run_twisted)
gr_tests = greenlet.getcurrent()
# ===============================================================================
# plugin tests
# ===============================================================================
def test_generic(testdir):
testdir.makepyfile('''
def test_pass():
pass
from twisted.internet import defer, reactor
from twisted.python import failure
from twisted.python import log
def test_no_deferred():
assert True is True
def test_deferred():
log.msg("test_deferred() called")
d = defer.Deferred()
def done():
log.msg("test_deferred.done() CALLBACK DONE")
d.callback(None)
reactor.callLater(2.5, done)
log.msg("test_deferred() returning deferred: %r" % (d,))
return d
def test_deferred2():
log.msg("test_deferred2() called")
d = defer.Deferred()
def done():
log.msg("test_deferred2.done() CALLBACK DONE")
d.callback(None)
reactor.callLater(2.5, done)
log.msg("test_deferred2() returning deferred: %r" % (d,))
return d
def test_deferred4():
log.msg("test_deferred4() called")
from twisted.web.client import getPage
def printContents(contents):
assert contents == ""
deferred = getPage('http://twistedmatrix.com/')
deferred.addCallback(printContents)
return deferred
def test_deferred3():
log.msg("test_deferred3() called")
d = defer.Deferred()
def done():
log.msg("test_deferred3.done() CALLBACK DONE")
d.callback(None)
reactor.callLater(2.5, done)
log.msg("test_deferred3() returning deferred: %r" % (d,))
return d
class TestTwistedSetupMethod:
def setup_method(self, method):
log.msg("TestTwistedSetupMethod.setup_method() called")
def test_deferred(self):
log.msg("TestTwistedSetupMethod.test_deferred() called")
d = defer.Deferred()
def done():
log.msg("TestTwistedSetupMethod.test_deferred() CALLBACK DONE")
d.callback(None)
reactor.callLater(2.5, done)
log.msg("TestTwistedSetupMethod.test_deferred() returning deferred: %r" % (d,))
return d
def test_defer_fail():
def fun():
log.msg("provoking NameError")
rsdfg
return defer.maybeDeferred(fun)
''')
testdir.runpytest("-T")
# XXX: what to do?
# s = testdir.tmpdir.join("event.log").read()
# assert s.find("TestrunFinish") != -1

View File

@@ -1,2 +0,0 @@
pygreen: experimental IO and execnet operations through greenlets

View File

@@ -1,20 +0,0 @@
"""
this little helper allows to run tests multiple times
in the same process. useful for running tests from
a console.
NOTE: since 1.3.1 you can just call py.test.cmdline.main()
multiple times - no special logic needed.
"""
import py, sys
def pytest(argv=None):
if argv is None:
argv = []
try:
sys.argv[1:] = argv
py.cmdline.pytest()
except SystemExit:
pass
# we need to reset the global py.test.config object
py.test.config = py.test.config.__class__()

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

136
doc/Makefile Normal file
View File

@@ -0,0 +1,136 @@
# 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) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
regen:
COLUMNS=76 regendoc --update *.txt */*.txt
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: clean html
rsync -avz _build/html/ code:www-pytest/2.0.0
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."

339
doc/_static/sphinxdoc.css vendored Normal file
View File

@@ -0,0 +1,339 @@
/*
* sphinxdoc.css_t
* ~~~~~~~~~~~~~~~
*
* Sphinx stylesheet -- sphinxdoc theme. Originally created by
* Armin Ronacher for Werkzeug.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
'Verdana', sans-serif;
font-size: 1.1em;
letter-spacing: -0.01em;
line-height: 150%;
text-align: center;
background-color: #BFD1D4;
color: black;
padding: 0;
border: 1px solid #aaa;
margin: 0px 80px 0px 80px;
min-width: 740px;
}
div.document {
background-color: white;
text-align: left;
background-image: url(contents.png);
background-repeat: repeat-x;
}
div.bodywrapper {
margin: 0 240px 0 0;
border-right: 1px solid #ccc;
}
div.body {
margin: 0;
padding: 0.5em 20px 20px 20px;
}
div.related {
font-size: 1em;
}
div.related ul {
background-image: url(navigation.png);
height: 2em;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
div.related ul li {
margin: 0;
padding: 0;
height: 2em;
float: left;
}
div.related ul li.right {
float: right;
margin-right: 5px;
}
div.related ul li a {
margin: 0;
padding: 0 5px 0 5px;
line-height: 1.75em;
color: #EE9816;
}
div.related ul li a:hover {
color: #3CA8E7;
}
div.sphinxsidebarwrapper {
padding: 0;
}
div.sphinxsidebar {
margin: 0;
padding: 0.5em 15px 15px 0;
width: 210px;
float: right;
font-size: 1em;
text-align: left;
}
div.sphinxsidebar h3, div.sphinxsidebar h4 {
margin: 1em 0 0.5em 0;
font-size: 1em;
padding: 0.1em 0 0.1em 0.5em;
color: white;
border: 1px solid #86989B;
background-color: #AFC1C4;
}
div.sphinxsidebar h3 a {
color: white;
}
div.sphinxsidebar ul {
padding-left: 1.5em;
margin-top: 7px;
padding: 0;
line-height: 130%;
}
div.sphinxsidebar ul ul {
margin-left: 20px;
}
div.footer {
background-color: #E3EFF1;
color: #86989B;
padding: 3px 8px 3px 0;
clear: both;
font-size: 0.8em;
text-align: right;
}
div.footer a {
color: #86989B;
text-decoration: underline;
}
/* -- body styles ----------------------------------------------------------- */
p {
margin: 0.8em 0 0.5em 0;
}
a {
color: #CA7900;
text-decoration: none;
}
a:hover {
color: #2491CF;
}
div.body a {
text-decoration: underline;
}
h1 {
margin: 0;
padding: 0.7em 0 0.3em 0;
font-size: 1.5em;
color: #11557C;
}
h2 {
margin: 1.3em 0 0.2em 0;
font-size: 1.35em;
padding: 0;
}
h3 {
margin: 1em 0 -0.3em 0;
font-size: 1.2em;
}
div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a {
color: black!important;
}
h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
display: none;
margin: 0 0 0 0.3em;
padding: 0 0.2em 0 0.2em;
color: #aaa!important;
}
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
h5:hover a.anchor, h6:hover a.anchor {
display: inline;
}
h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
h5 a.anchor:hover, h6 a.anchor:hover {
color: #777;
background-color: #eee;
}
a.headerlink {
color: #c60f0f!important;
font-size: 1em;
margin-left: 6px;
padding: 0 4px 0 4px;
text-decoration: none!important;
}
a.headerlink:hover {
background-color: #ccc;
color: white!important;
}
cite, code, tt {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
font-size: 0.95em;
letter-spacing: 0.01em;
}
tt {
background-color: #f2f2f2;
border-bottom: 1px solid #ddd;
color: #333;
}
tt.descname, tt.descclassname, tt.xref {
border: 0;
}
hr {
border: 1px solid #abc;
margin: 2em;
}
a tt {
border: 0;
color: #CA7900;
}
a tt:hover {
color: #2491CF;
}
pre {
font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace;
font-size: 0.95em;
letter-spacing: 0.015em;
line-height: 120%;
padding: 0.5em;
border: 1px solid #ccc;
background-color: #f8f8f8;
}
pre a {
color: inherit;
text-decoration: underline;
}
td.linenos pre {
padding: 0.5em 0;
}
div.quotebar {
background-color: #f8f8f8;
max-width: 250px;
float: right;
padding: 2px 7px;
border: 1px solid #ccc;
}
div.topic {
background-color: #f8f8f8;
}
table {
border-collapse: collapse;
margin: 0 -0.5em 0 -0.5em;
}
table td, table th {
padding: 0.2em 0.5em 0.2em 0.5em;
}
div.admonition, div.warning {
font-size: 0.9em;
margin: 1em 0 1em 0;
border: 1px solid #86989B;
background-color: #f7f7f7;
padding: 0;
}
div.admonition p, div.warning p {
margin: 0.5em 1em 0.5em 1em;
padding: 0;
}
div.admonition pre, div.warning pre {
margin: 0.4em 1em 0.4em 1em;
}
div.admonition p.admonition-title,
div.warning p.admonition-title {
margin: 0;
padding: 0.1em 0 0.1em 0.5em;
color: white;
border-bottom: 1px solid #86989B;
font-weight: bold;
background-color: #AFC1C4;
}
div.warning {
border: 1px solid #940000;
}
div.warning p.admonition-title {
background-color: #CF0000;
border-bottom-color: #940000;
}
div.admonition ul, div.admonition ol,
div.warning ul, div.warning ol {
margin: 0.1em 0.5em 0.5em 3em;
padding: 0;
}
div.versioninfo {
margin: 1em 0 0 0;
border: 1px solid #ccc;
background-color: #DDEAF0;
padding: 8px;
line-height: 1.3em;
font-size: 0.9em;
}
.viewcode-back {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
'Verdana', sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}

22
doc/_templates/indexsidebar.html vendored Normal file
View File

@@ -0,0 +1,22 @@
<h3>Download</h3>
{% if version.endswith('(hg)') %}
<p>This documentation is for version <b>{{ version }}</b>, which is
not released yet.</p>
<p>You can use it from the
<a href="http://bitbucket.org/hpk42/pytest">Bitbucket Repo</a> or look for
released versions in the <a href="http://pypi.python.org/pypi/pytest">Python
Package Index</a>.</p>
{% else %}
<p><b><a href="{{ pathto('announce/index')}}">{{ version }} release</a></b>
[<a href="{{ pathto('changelog') }}">Changelog</a>]</p>
<p>
<a href="http://pypi.python.org/pypi/pytest">pytest on PyPI</a>
</p>
<pre>easy_install pytest</pre>
<pre>pip install pytest</pre>
{% endif %}
<h3>Questions? Suggestions?</h3>
<p><a href="{{ pathto('contact') }}">contact channels</a>
</p>

42
doc/_templates/layout.html vendored Normal file
View File

@@ -0,0 +1,42 @@
{% extends "!layout.html" %}
{% block relbar1 %}
{% endblock %}
{% block relbar2 %}
{% endblock %}
{% block rootrellink %}
{% endblock %}
{% block sidebarrel %}
{% endblock %}
{% block header %}
<div style="background-color: white; text-align: left; padding: 10px 10px 15px 15px">
<h1>pytest: rapid no-boilerplate testing with Python</h1>
<div style="text-align: left; font-size: 130%; vertical-align: middle;">
<a href="{{ pathto('index') }}">home</a>&nbsp;|&nbsp;
<a href="{{ pathto('contents') }}">all docs</a>&nbsp;|&nbsp;
<a href="{{ pathto('getting-started') }}">install</a>&nbsp;|&nbsp;
<a href="{{ pathto('example/index') }}">examples</a>&nbsp;|&nbsp;
<a href="{{ pathto('customize') }}">customize</a>&nbsp;|&nbsp;
<a href="{{ pathto('contact') }}">contact</a>&nbsp;
</div>
</div>
{% 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 %}

9
doc/announce/index.txt Normal file
View File

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

View File

@@ -1,7 +0,0 @@
py lib 1.0.0: XXX
======================================================================
Welcome to the 1.0.0 py lib release - a library aiming to
support agile and test-driven python development on various levels.
XXX

View File

@@ -1,27 +0,0 @@
py lib 0.9.2: bugfix release
=============================
Welcome to the 0.9.2 py lib and py.test release -
mainly fixing Windows issues, providing better
packaging and integration with setuptools.
Here is a quick summary of what the py lib provides:
* py.test: cross-project testing tool with many advanced features
* py.execnet: ad-hoc code distribution to SSH, Socket and local sub processes
* py.magic.greenlet: micro-threads on standard CPython ("stackless-light")
* py.path: path abstractions over local and subversion files
* rich documentation of py's exported API
* tested against Linux, Win32, OSX, works on python 2.3-2.6
See here for more information:
Pypi pages: http://pypi.python.org/pypi/py/
Download/Install: http://codespeak.net/py/0.9.2/download.html
Documentation/API: http://codespeak.net/py/0.9.2/index.html
best and have fun,
holger krekel

View File

@@ -1,63 +0,0 @@
pylib 1.0.0 released: testing-with-python innovations continue
--------------------------------------------------------------------
Took a few betas but finally i uploaded a `1.0.0 py lib release`_,
featuring the mature and powerful py.test tool and "execnet-style"
*elastic* distributed programming. With the new release, there are
many new advanced automated testing features - here is a quick summary:
* funcargs_ - pythonic zero-boilerplate fixtures for Python test functions :
- totally separates test code, test configuration and test setup
- ideal for integration and functional tests
- allows for flexible and natural test parametrization schemes
* new `plugin architecture`_, allowing easy-to-write project-specific and cross-project single-file plugins. The most notable new external plugin is `oejskit`_ which naturally enables **running and reporting of javascript-unittests in real-life browsers**.
* many new features done in easy-to-improve `default plugins`_, highlights:
* xfail: mark tests as "expected to fail" and report separately.
* pastebin: automatically send tracebacks to pocoo paste service
* capture: flexibly capture stdout/stderr of subprocesses, per-test ...
* monkeypatch: safely monkeypatch modules/classes from within tests
* unittest: run and integrate traditional unittest.py tests
* figleaf: generate html coverage reports with the figleaf module
* resultlog: generate buildbot-friendly reporting output
* ...
* `distributed testing`_ and `elastic distributed execution`_:
- new unified "TX" URL scheme for specifying remote processes
- new distribution modes "--dist=each" and "--dist=load"
- new sync/async ways to handle 1:N communication
- improved documentation
The py lib continues to offer most of the functionality used by
the testing tool in `independent namespaces`_.
Some non-test related code, notably greenlets/co-routines and
api-generation now live as their own projects which simplifies the
installation procedure because no C-Extensions are required anymore.
The whole package should work well with Linux, Win32 and OSX, on Python
2.3, 2.4, 2.5 and 2.6. (Expect Python3 compatibility soon!)
For more info, see the py.test and py lib documentation:
http://pytest.org
http://pylib.org
have fun,
holger
.. _`independent namespaces`: http://pylib.org
.. _`funcargs`: http://codespeak.net/py/dist/test/funcargs.html
.. _`plugin architecture`: http://codespeak.net/py/dist/test/extend.html
.. _`default plugins`: http://codespeak.net/py/dist/test/plugin/index.html
.. _`distributed testing`: http://codespeak.net/py/dist/test/dist.html
.. _`elastic distributed execution`: http://codespeak.net/py/dist/execnet.html
.. _`1.0.0 py lib release`: http://pypi.python.org/pypi/py
.. _`oejskit`: http://codespeak.net/py/dist/test/plugin/oejskit.html

View File

@@ -1,48 +0,0 @@
1.0.1: improved reporting, nose/unittest.py support, bug fixes
-----------------------------------------------------------------------
This is a bugfix release of pylib/py.test also coming with:
* improved documentation, improved navigation
* test failure reporting improvements
* support for directly running existing nose/unittest.py style tests
visit here for more info, including quickstart and tutorials:
http://pytest.org and http://pylib.org
Changelog 1.0.0 to 1.0.1
------------------------
* added a default 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* improved documentation, better navigation: see http://pytest.org
* added a "--help-config" option to show conftest.py / ENV-var names for
all longopt cmdline options, and some special conftest.py variables.
renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
* unicode fixes: capturing and unicode writes to sys.stdout
(through e.g a print statement) now work within tests,
they are encoded as "utf8" by default, also terminalwriting
was adapted and somewhat unified between windows and linux
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* fix issue #32: adding support for "incomplete" paths to wcpath.status()
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* monkeypatch setenv() now accepts a "prepend" parameter
* improved reporting of collection error tracebacks
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames

View File

@@ -1,5 +0,0 @@
1.0.2: packaging fixes
-----------------------------------------------------------------------
this release is purely a release for fixing packaging issues.

View File

@@ -1,115 +0,0 @@
py.test/pylib 1.1.0: Python3, Jython, advanced skipping, cleanups ...
--------------------------------------------------------------------------------
Features:
* compatible to Python3 (single py2/py3 source), `easy to install`_
* conditional skipping_: skip/xfail based on platform/dependencies
* generalized marking_: mark tests one a whole-class or whole-module basis
Fixes:
* code reduction and "de-magification" (e.g. 23 KLoc -> 11 KLOC)
* distribute testing requires the now separately released execnet_ package
* funcarg-setup/caching, "same-name" test modules now cause an exlicit error
* de-cluttered reporting options, --report for skipped/xfail details
Compatibilities
1.1.0 should allow running test code that already worked well with 1.0.2
plus some more due to improved unittest/nose compatibility.
More information: http://pytest.org
thanks and have fun,
holger (http://twitter.com/hpk42)
.. _execnet: http://codespeak.net/execnet
.. _`easy to install`: ../install.html
.. _marking: ../test/plugin/mark.html
.. _skipping: ../test/plugin/skipping.html
Changelog 1.0.2 -> 1.1.0
-----------------------------------------------------------------------
* remove py.rest tool and internal namespace - it was
never really advertised and can still be used with
the old release if needed. If there is interest
it could be revived into its own tool i guess.
* fix issue48 and issue59: raise an Error if the module
from an imported test file does not seem to come from
the filepath - avoids "same-name" confusion that has
been reported repeatedly
* merged Ronny's nose-compatibility hacks: now
nose-style setup_module() and setup() functions are
supported
* introduce generalized py.test.mark function marking
* reshuffle / refine command line grouping
* deprecate parser.addgroup in favour of getgroup which creates option group
* add --report command line option that allows to control showing of skipped/xfailed sections
* generalized skipping: a new way to mark python functions with skipif or xfail
at function, class and modules level based on platform or sys-module attributes.
* extend py.test.mark decorator to allow for positional args
* introduce and test "py.cleanup -d" to remove empty directories
* fix issue #59 - robustify unittest test collection
* make bpython/help interaction work by adding an __all__ attribute
to ApiModule, cleanup initpkg
* use MIT license for pylib, add some contributors
* remove py.execnet code and substitute all usages with 'execnet' proper
* fix issue50 - cached_setup now caches more to expectations
for test functions with multiple arguments.
* merge Jarko's fixes, issue #45 and #46
* add the ability to specify a path for py.lookup to search in
* fix a funcarg cached_setup bug probably only occuring
in distributed testing and "module" scope with teardown.
* many fixes and changes for making the code base python3 compatible,
many thanks to Benjamin Peterson for helping with this.
* consolidate builtins implementation to be compatible with >=2.3,
add helpers to ease keeping 2 and 3k compatible code
* deprecate py.compat.doctest|subprocess|textwrap|optparse
* deprecate py.magic.autopath, remove py/magic directory
* move pytest assertion handling to py/code and a pytest_assertion
plugin, add "--no-assert" option, deprecate py.magic namespaces
in favour of (less) py.code ones.
* consolidate and cleanup py/code classes and files
* cleanup py/misc, move tests to bin-for-dist
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
* consolidate py.log implementation, remove old approach.
* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
text/unicode and byte-streams (uses underlying standard lib io.*
if available)
* make py.unittest_convert helper script available which converts "unittest.py"
style files into the simpler assert/direct-test-classes py.test/nosetests
style. The script was written by Laura Creighton.
* simplified internal localpath implementation

View File

@@ -1,48 +0,0 @@
py.test/pylib 1.1.1: bugfix release, setuptools plugin registration
--------------------------------------------------------------------------------
This is a compatibility fixing release of pylib/py.test to work
better with previous 1.0.x test code bases. It also contains fixes
and changes to work with `execnet>=1.0.0`_ to provide distributed
testing and looponfailing testing modes. py-1.1.1 also introduces
a new mechanism for registering plugins via setuptools.
What is pylib/py.test?
-----------------------
py.test is an advanced automated testing tool working with
Python2, Python3 and Jython versions on all major operating
systems. It has an extensive plugin architecture and can run many
existing common Python test suites without modification. Moreover,
it offers some unique features not found in other
testing tools. See http://pytest.org for more info.
The pylib also contains a localpath and svnpath implementation
and some developer-oriented command line tools. See
http://pylib.org for more info.
thanks to all who helped and gave feedback,
have fun,
holger (http://twitter.com/hpk42)
.. _`execnet>=1.0.0`: http://codespeak.net/execnet
Changes between 1.1.1 and 1.1.0
=====================================
- introduce automatic plugin registration via 'pytest11'
entrypoints via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility
- svn paths: fix a bug with path.check(versioned=True) for svn paths,
allow '%' in svn paths, make svnwc.update() default to interactive mode
like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
- refine distributed tarball to contain test and no pyc files
- try harder to have deprecation warnings for py.compat.* accesses
report a correct location

View File

@@ -1,116 +0,0 @@
py.test/pylib 1.2.0: junitxml, standalone test scripts, pluginization
--------------------------------------------------------------------------------
py.test is an advanced automated testing tool working with
Python2, Python3 and Jython versions on all major operating
systems. It has a simple plugin architecture and can run many
existing common Python test suites without modification. It offers
some unique features not found in other testing tools.
See http://pytest.org for more info.
py.test 1.2.0 brings many bug fixes and interesting new abilities:
* --junitxml=path will create an XML file for use with CI processing
* --genscript=path creates a standalone py.test-equivalent test-script
* --ignore=path prevents collection of anything below that path
* --confcutdir=path only lookup conftest.py test configs below that path
* a 'pytest_report_header' hook to add info to the terminal report header
* a 'pytestconfig' function argument gives direct access to option values
* 'pytest_generate_tests' can now be put into a class as well
* on CPython py.test additionally installs as "py.test-VERSION", on
Jython as py.test-jython and on PyPy as py.test-pypy-XYZ
Apart from many bug fixes 1.2.0 also has better pluginization:
Distributed testing and looponfailing testing now live in the
separately installable 'pytest-xdist' plugin. The same is true for
'pytest-figleaf' for doing coverage reporting. Those two plugins
can serve well now as blue prints for doing your own.
thanks to all who helped and gave feedback,
have fun,
holger krekel, January 2010
Changes between 1.2.0 and 1.1.1
=====================================
- moved dist/looponfailing from py.test core into a new
separately released pytest-xdist plugin.
- new junitxml plugin: --junitxml=path will generate a junit style xml file
which is processable e.g. by the Hudson CI system.
- new option: --genscript=path will generate a standalone py.test script
which will not need any libraries installed. thanks to Ralf Schmitt.
- new option: --ignore will prevent specified path from collection.
Can be specified multiple times.
- new option: --confcutdir=dir will make py.test only consider conftest
files that are relative to the specified dir.
- new funcarg: "pytestconfig" is the pytest config object for access
to command line args and can now be easily used in a test.
- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
- new "pytestconfig" funcarg allows access to test config object
- new "pytest_report_header" hook can return additional lines
to be displayed at the header of a test run.
- (experimental) allow "py.test path::name1::name2::..." for pointing
to a test within a test collection directly. This might eventually
evolve as a full substitute to "-k" specifications.
- streamlined plugin loading: order is now as documented in
customize.html: setuptools, ENV, commandline, conftest.
also setuptools entry point names are turned to canonical namees ("pytest_*")
- automatically skip tests that need 'capfd' but have no os.dup
- allow pytest_generate_tests to be defined in classes as well
- deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate definition of Directory, Module, Class and Function nodes
in conftest.py files. Use pytest collect hooks instead.
- collection/item node specific runtest/collect hooks are only called exactly
on matching conftest.py files, i.e. ones which are exactly below
the filesystem path of an item
- change: the first pytest_collect_directory hook to return something
will now prevent further hooks to be called.
- change: figleaf plugin now requires --figleaf to run. Also
change its long command line options to be a bit shorter (see py.test -h).
- change: pytest doctest plugin is now enabled by default and has a
new option --doctest-glob to set a pattern for file matches.
- change: remove internal py._* helper vars, only keep py._pydir
- robustify capturing to survive if custom pytest_runtest_setup
code failed and prevented the capturing setup code from running.
- make py.test.* helpers provided by default plugins visible early -
works transparently both for pydoc and for interactive sessions
which will regularly see e.g. py.test.mark and py.test.importorskip.
- simplify internal plugin manager machinery
- simplify internal collection tree by introducing a RootCollector node
- fix assert reinterpreation that sees a call containing "keyword=..."
- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
hooks on slaves during dist-testing, report module/session teardown
hooks correctly.
- fix issue65: properly handle dist-testing if no
execnet/py lib installed remotely.
- skip some install-tests if no execnet is available
- fix docs, fix internal bin/ script generation

View File

@@ -1,66 +0,0 @@
py.test/pylib 1.2.1: little fixes and improvements
--------------------------------------------------------------------------------
py.test is an advanced automated testing tool working with
Python2, Python3 and Jython versions on all major operating
systems. It has a simple plugin architecture and can run many
existing common Python test suites without modification. It offers
some unique features not found in other testing tools.
See http://pytest.org for more info.
py.test 1.2.1 brings bug fixes and some new options and abilities triggered
by user feedback:
* --funcargs [testpath] will show available builtin- and project funcargs.
* display a short and concise traceback if funcarg lookup fails.
* early-load "conftest.py" files in non-dot first-level sub directories.
* --tb=line will print a single line for each failing test (issue67)
* py.cleanup has a number of new options, cleanups up setup.py related files
* fix issue78: always call python-level teardown functions even if the
according setup failed.
For more detailed information see the changelog below.
cheers and have fun,
holger
Changes between 1.2.1 and 1.2.0
=====================================
- refined usage and options for "py.cleanup"::
py.cleanup # remove "*.pyc" and "*$py.class" (jython) files
py.cleanup -e .swp -e .cache # also remove files with these extensions
py.cleanup -s # remove "build" and "dist" directory next to setup.py files
py.cleanup -d # also remove empty directories
py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'"
py.cleanup -n # dry run, only show what would be removed
- add a new option "py.test --funcargs" which shows available funcargs
and their help strings (docstrings on their respective factory function)
for a given test path
- display a short and concise traceback if a funcarg lookup fails
- early-load "conftest.py" files in non-dot first-level sub directories.
allows to conveniently keep and access test-related options in a ``test``
subdir and still add command line options.
- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
- fix issue78: always call python-level teardown functions even if the
according setup failed. This includes refinements for calling setup_module/class functions
which will now only be called once instead of the previous behaviour where they'd be called
multiple times if they raise an exception (including a Skipped exception). Any exception
will be re-corded and associated with all tests in the according module/class scope.
- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
- fix pdb debugging to be in the correct frame on raises-related errors
- update apipkg.py to fix an issue where recursive imports might
unnecessarily break importing
- fix plugin links

View File

@@ -1,580 +0,0 @@
py.test/pylib 1.3.0: new options, per-plugin hooks, fixes ...
===========================================================================
The 1.3.0 release introduces new options, bug fixes and improved compatibility
with Python3 and Jython-2.5.1 on Windows. If you already use py-1.2 chances
are you can use py-1.3.0. See the below CHANGELOG for more details and
http://pylib.org/install.html for installation instructions.
py.test is an advanced automated testing tool working with Python2,
Python3, Jython and PyPy versions on all major operating systems. It
offers a no-boilerplate testing approach and has inspired other testing
tools and enhancements in the standard Python library for more than five
years. It has a simple and extensive plugin architecture, configurable
reporting and provides unique ways to make it fit to your testing
process and needs.
See http://pytest.org for more info.
cheers and have fun,
holger krekel
Changes between 1.2.1 and 1.3.0
==================================================
- deprecate --report option in favour of a new shorter and easier to
remember -r option: it takes a string argument consisting of any
combination of 'xfsX' characters. They relate to the single chars
you see during the dotted progress printing and will print an extra line
per test at the end of the test run. This extra line indicates the exact
position or test ID that you directly paste to the py.test cmdline in order
to re-run a particular test.
- allow external plugins to register new hooks via the new
pytest_addhooks(pluginmanager) hook. The new release of
the pytest-xdist plugin for distributed and looponfailing
testing requires this feature.
- add a new pytest_ignore_collect(path, config) hook to allow projects and
plugins to define exclusion behaviour for their directory structure -
for example you may define in a conftest.py this method::
def pytest_ignore_collect(path):
return path.check(link=1)
to prevent even collection of any tests in symlinked dirs.
- new pytest_pycollect_makemodule(path, parent) hook for
allowing customization of the Module collection object for a
matching test module.
- extend and refine xfail mechanism::
@py.test.mark.xfail(run=False) do not run the decorated test
@py.test.mark.xfail(reason="...") prints the reason string in xfail summaries
specifiying ``--runxfail`` on command line ignores xfail markers to show
you the underlying traceback.
- expose (previously internal) commonly useful methods:
py.io.get_terminal_with() -> return terminal width
py.io.ansi_print(...) -> print colored/bold text on linux/win32
py.io.saferepr(obj) -> return limited representation string
- expose test outcome related exceptions as py.test.skip.Exception,
py.test.raises.Exception etc., useful mostly for plugins
doing special outcome interpretation/tweaking
- (issue85) fix junitxml plugin to handle tests with non-ascii output
- fix/refine python3 compatibility (thanks Benjamin Peterson)
- fixes for making the jython/win32 combination work, note however:
jython2.5.1/win32 does not provide a command line launcher, see
http://bugs.jython.org/issue1491 . See pylib install documentation
for how to work around.
- fixes for handling of unicode exception values and unprintable objects
- (issue87) fix unboundlocal error in assertionold code
- (issue86) improve documentation for looponfailing
- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
- ship distribute_setup.py version 0.6.10
- added links to the new capturelog and coverage plugins
Changes between 1.2.1 and 1.2.0
=====================================
- refined usage and options for "py.cleanup"::
py.cleanup # remove "*.pyc" and "*$py.class" (jython) files
py.cleanup -e .swp -e .cache # also remove files with these extensions
py.cleanup -s # remove "build" and "dist" directory next to setup.py files
py.cleanup -d # also remove empty directories
py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'"
py.cleanup -n # dry run, only show what would be removed
- add a new option "py.test --funcargs" which shows available funcargs
and their help strings (docstrings on their respective factory function)
for a given test path
- display a short and concise traceback if a funcarg lookup fails
- early-load "conftest.py" files in non-dot first-level sub directories.
allows to conveniently keep and access test-related options in a ``test``
subdir and still add command line options.
- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
- fix issue78: always call python-level teardown functions even if the
according setup failed. This includes refinements for calling setup_module/class functions
which will now only be called once instead of the previous behaviour where they'd be called
multiple times if they raise an exception (including a Skipped exception). Any exception
will be re-corded and associated with all tests in the according module/class scope.
- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
- fix pdb debugging to be in the correct frame on raises-related errors
- update apipkg.py to fix an issue where recursive imports might
unnecessarily break importing
- fix plugin links
Changes between 1.2 and 1.1.1
=====================================
- moved dist/looponfailing from py.test core into a new
separately released pytest-xdist plugin.
- new junitxml plugin: --junitxml=path will generate a junit style xml file
which is processable e.g. by the Hudson CI system.
- new option: --genscript=path will generate a standalone py.test script
which will not need any libraries installed. thanks to Ralf Schmitt.
- new option: --ignore will prevent specified path from collection.
Can be specified multiple times.
- new option: --confcutdir=dir will make py.test only consider conftest
files that are relative to the specified dir.
- new funcarg: "pytestconfig" is the pytest config object for access
to command line args and can now be easily used in a test.
- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
- new "pytestconfig" funcarg allows access to test config object
- new "pytest_report_header" hook can return additional lines
to be displayed at the header of a test run.
- (experimental) allow "py.test path::name1::name2::..." for pointing
to a test within a test collection directly. This might eventually
evolve as a full substitute to "-k" specifications.
- streamlined plugin loading: order is now as documented in
customize.html: setuptools, ENV, commandline, conftest.
also setuptools entry point names are turned to canonical namees ("pytest_*")
- automatically skip tests that need 'capfd' but have no os.dup
- allow pytest_generate_tests to be defined in classes as well
- deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate definition of Directory, Module, Class and Function nodes
in conftest.py files. Use pytest collect hooks instead.
- collection/item node specific runtest/collect hooks are only called exactly
on matching conftest.py files, i.e. ones which are exactly below
the filesystem path of an item
- change: the first pytest_collect_directory hook to return something
will now prevent further hooks to be called.
- change: figleaf plugin now requires --figleaf to run. Also
change its long command line options to be a bit shorter (see py.test -h).
- change: pytest doctest plugin is now enabled by default and has a
new option --doctest-glob to set a pattern for file matches.
- change: remove internal py._* helper vars, only keep py._pydir
- robustify capturing to survive if custom pytest_runtest_setup
code failed and prevented the capturing setup code from running.
- make py.test.* helpers provided by default plugins visible early -
works transparently both for pydoc and for interactive sessions
which will regularly see e.g. py.test.mark and py.test.importorskip.
- simplify internal plugin manager machinery
- simplify internal collection tree by introducing a RootCollector node
- fix assert reinterpreation that sees a call containing "keyword=..."
- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
hooks on slaves during dist-testing, report module/session teardown
hooks correctly.
- fix issue65: properly handle dist-testing if no
execnet/py lib installed remotely.
- skip some install-tests if no execnet is available
- fix docs, fix internal bin/ script generation
Changes between 1.1.1 and 1.1.0
=====================================
- introduce automatic plugin registration via 'pytest11'
entrypoints via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility
- svn paths: fix a bug with path.check(versioned=True) for svn paths,
allow '%' in svn paths, make svnwc.update() default to interactive mode
like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
- refine distributed tarball to contain test and no pyc files
- try harder to have deprecation warnings for py.compat.* accesses
report a correct location
Changes between 1.1.0 and 1.0.2
=====================================
* adjust and improve docs
* remove py.rest tool and internal namespace - it was
never really advertised and can still be used with
the old release if needed. If there is interest
it could be revived into its own tool i guess.
* fix issue48 and issue59: raise an Error if the module
from an imported test file does not seem to come from
the filepath - avoids "same-name" confusion that has
been reported repeatedly
* merged Ronny's nose-compatibility hacks: now
nose-style setup_module() and setup() functions are
supported
* introduce generalized py.test.mark function marking
* reshuffle / refine command line grouping
* deprecate parser.addgroup in favour of getgroup which creates option group
* add --report command line option that allows to control showing of skipped/xfailed sections
* generalized skipping: a new way to mark python functions with skipif or xfail
at function, class and modules level based on platform or sys-module attributes.
* extend py.test.mark decorator to allow for positional args
* introduce and test "py.cleanup -d" to remove empty directories
* fix issue #59 - robustify unittest test collection
* make bpython/help interaction work by adding an __all__ attribute
to ApiModule, cleanup initpkg
* use MIT license for pylib, add some contributors
* remove py.execnet code and substitute all usages with 'execnet' proper
* fix issue50 - cached_setup now caches more to expectations
for test functions with multiple arguments.
* merge Jarko's fixes, issue #45 and #46
* add the ability to specify a path for py.lookup to search in
* fix a funcarg cached_setup bug probably only occuring
in distributed testing and "module" scope with teardown.
* many fixes and changes for making the code base python3 compatible,
many thanks to Benjamin Peterson for helping with this.
* consolidate builtins implementation to be compatible with >=2.3,
add helpers to ease keeping 2 and 3k compatible code
* deprecate py.compat.doctest|subprocess|textwrap|optparse
* deprecate py.magic.autopath, remove py/magic directory
* move pytest assertion handling to py/code and a pytest_assertion
plugin, add "--no-assert" option, deprecate py.magic namespaces
in favour of (less) py.code ones.
* consolidate and cleanup py/code classes and files
* cleanup py/misc, move tests to bin-for-dist
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
* consolidate py.log implementation, remove old approach.
* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
text/unicode and byte-streams (uses underlying standard lib io.*
if available)
* make py.unittest_convert helper script available which converts "unittest.py"
style files into the simpler assert/direct-test-classes py.test/nosetests
style. The script was written by Laura Creighton.
* simplified internal localpath implementation
Changes between 1.0.1 and 1.0.2
=====================================
* fixing packaging issues, triggered by fedora redhat packaging,
also added doc, examples and contrib dirs to the tarball.
* added a documentation link to the new django plugin.
Changes between 1.0.0 and 1.0.1
=====================================
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* capturing of unicode writes or encoded strings to sys.stdout/err
work better, also terminalwriting was adapted and somewhat
unified between windows and linux.
* improved documentation layout and content a lot
* added a "--help-config" option to show conftest.py / ENV-var names for
all longopt cmdline options, and some special conftest.py variables.
renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* fix issue #32: adding support for "incomplete" paths to wcpath.status()
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* monkeypatch setenv() now accepts a "prepend" parameter
* improved reporting of collection error tracebacks
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames
Changes between 1.0.0b9 and 1.0.0
=====================================
* more terse reporting try to show filesystem path relatively to current dir
* improve xfail output a bit
Changes between 1.0.0b8 and 1.0.0b9
=====================================
* cleanly handle and report final teardown of test setup
* fix svn-1.6 compat issue with py.path.svnwc().versioned()
(thanks Wouter Vanden Hove)
* setup/teardown or collection problems now show as ERRORs
or with big "E"'s in the progress lines. they are reported
and counted separately.
* dist-testing: properly handle test items that get locally
collected but cannot be collected on the remote side - often
due to platform/dependency reasons
* simplified py.test.mark API - see keyword plugin documentation
* integrate better with logging: capturing now by default captures
test functions and their immediate setup/teardown in a single stream
* capsys and capfd funcargs now have a readouterr() and a close() method
(underlyingly py.io.StdCapture/FD objects are used which grew a
readouterr() method as well to return snapshots of captured out/err)
* make assert-reinterpretation work better with comparisons not
returning bools (reported with numpy from thanks maciej fijalkowski)
* reworked per-test output capturing into the pytest_iocapture.py plugin
and thus removed capturing code from config object
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
Changes between 1.0.0b7 and 1.0.0b8
=====================================
* pytest_unittest-plugin is now enabled by default
* introduced pytest_keyboardinterrupt hook and
refined pytest_sessionfinish hooked, added tests.
* workaround a buggy logging module interaction ("closing already closed
files"). Thanks to Sridhar Ratnakumar for triggering.
* if plugins use "py.test.importorskip" for importing
a dependency only a warning will be issued instead
of exiting the testing process.
* many improvements to docs:
- refined funcargs doc , use the term "factory" instead of "provider"
- added a new talk/tutorial doc page
- better download page
- better plugin docstrings
- added new plugins page and automatic doc generation script
* fixed teardown problem related to partially failing funcarg setups
(thanks MrTopf for reporting), "pytest_runtest_teardown" is now
always invoked even if the "pytest_runtest_setup" failed.
* tweaked doctest output for docstrings in py modules,
thanks Radomir.
Changes between 1.0.0b3 and 1.0.0b7
=============================================
* renamed py.test.xfail back to py.test.mark.xfail to avoid
two ways to decorate for xfail
* re-added py.test.mark decorator for setting keywords on functions
(it was actually documented so removing it was not nice)
* remove scope-argument from request.addfinalizer() because
request.cached_setup has the scope arg. TOOWTDI.
* perform setup finalization before reporting failures
* apply modified patches from Andreas Kloeckner to allow
test functions to have no func_code (#22) and to make
"-k" and function keywords work (#20)
* apply patch from Daniel Peolzleithner (issue #23)
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* make __name__ == "__channelexec__" for remote_exec code
Changes between 1.0.0b1 and 1.0.0b3
=============================================
* plugin classes are removed: one now defines
hooks directly in conftest.py or global pytest_*.py
files.
* added new pytest_namespace(config) hook that allows
to inject helpers directly to the py.test.* namespace.
* documented and refined many hooks
* added new style of generative tests via
pytest_generate_tests hook that integrates
well with function arguments.
Changes between 0.9.2 and 1.0.0b1
=============================================
* introduced new "funcarg" setup method,
see doc/test/funcarg.txt
* introduced plugin architecuture and many
new py.test plugins, see
doc/test/plugins.txt
* teardown_method is now guaranteed to get
called after a test method has run.
* new method: py.test.importorskip(mod,minversion)
will either import or call py.test.skip()
* completely revised internal py.test architecture
* new py.process.ForkedFunc object allowing to
fork execution of a function to a sub process
and getting a result back.
XXX lots of things missing here XXX
Changes between 0.9.1 and 0.9.2
===============================
* refined installation and metadata, created new setup.py,
now based on setuptools/ez_setup (thanks to Ralf Schmitt
for his support).
* improved the way of making py.* scripts available in
windows environments, they are now added to the
Scripts directory as ".cmd" files.
* py.path.svnwc.status() now is more complete and
uses xml output from the 'svn' command if available
(Guido Wesdorp)
* fix for py.path.svn* to work with svn 1.5
(Chris Lamb)
* fix path.relto(otherpath) method on windows to
use normcase for checking if a path is relative.
* py.test's traceback is better parseable from editors
(follows the filenames:LINENO: MSG convention)
(thanks to Osmo Salomaa)
* fix to javascript-generation, "py.test --runbrowser"
should work more reliably now
* removed previously accidentally added
py.test.broken and py.test.notimplemented helpers.
* there now is a py.__version__ attribute
Changes between 0.9.0 and 0.9.1
===============================
This is a fairly complete list of changes between 0.9 and 0.9.1, which can
serve as a reference for developers.
* allowing + signs in py.path.svn urls [39106]
* fixed support for Failed exceptions without excinfo in py.test [39340]
* added support for killing processes for Windows (as well as platforms that
support os.kill) in py.misc.killproc [39655]
* added setup/teardown for generative tests to py.test [40702]
* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
* fixed problem with calling .remove() on wcpaths of non-versioned files in
py.path [44248]
* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
* fail to run greenlet tests when pypy is available, but without stackless
[45294]
* small fixes in rsession tests [45295]
* fixed issue with 2.5 type representations in py.test [45483, 45484]
* made that internal reporting issues displaying is done atomically in py.test
[45518]
* made that non-existing files are igored by the py.lookup script [45519]
* improved exception name creation in py.test [45535]
* made that less threads are used in execnet [merge in 45539]
* removed lock required for atomical reporting issue displaying in py.test
[45545]
* removed globals from execnet [45541, 45547]
* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
get called in 2.5 (py.execnet) [45548]
* fixed bug in joining threads in py.execnet's servemain [45549]
* refactored py.test.rsession tests to not rely on exact output format anymore
[45646]
* using repr() on test outcome [45647]
* added 'Reason' classes for py.test.skip() [45648, 45649]
* killed some unnecessary sanity check in py.test.collect [45655]
* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
usable by Administrators [45901]
* added support for locking and non-recursive commits to py.path.svnwc [45994]
* locking files in py.execnet to prevent CPython from segfaulting [46010]
* added export() method to py.path.svnurl
* fixed -d -x in py.test [47277]
* fixed argument concatenation problem in py.path.svnwc [49423]
* restore py.test behaviour that it exits with code 1 when there are failures
[49974]
* don't fail on html files that don't have an accompanying .txt file [50606]
* fixed 'utestconvert.py < input' [50645]
* small fix for code indentation in py.code.source [50755]
* fix _docgen.py documentation building [51285]
* improved checks for source representation of code blocks in py.test [51292]
* added support for passing authentication to py.path.svn* objects [52000,
52001]
* removed sorted() call for py.apigen tests in favour of [].sort() to support
Python 2.3 [52481]

View File

@@ -1,104 +0,0 @@
py.test/pylib 1.3.1: new py.test.xfail, --maxfail, better reporting
===========================================================================
The pylib/py.test 1.3.1 release brings:
- the new imperative ``py.test.xfail()`` helper in order to have a test or
setup function result in an "expected failure"
- a new option ``--maxfail=NUM`` to stop the test run after some failures
- markers/decorators are now applicable to test classes (>=Python2.6)
- improved reporting, shorter tracebacks in several cases
- some simplified internals, more compatibility with Jython and PyPy
- bug fixes and various refinements
See the below CHANGELOG entry below for more details and
http://pylib.org/install.html for installation instructions.
If you used older versions of py.test you should be able to upgrade
to 1.3.1 without changes to your test source code.
py.test is an automated testing tool working with Python2,
Python3, Jython and PyPy versions on all major operating systems. It
offers a no-boilerplate testing approach and has inspired other testing
tools and enhancements in the standard Python library for more than five
years. It has a simple and extensive plugin architecture, configurable
reporting and provides unique ways to make it fit to your testing
process and needs.
See http://pytest.org for more info.
cheers and have fun,
holger krekel
Changes between 1.3.0 and 1.3.1
==================================================
New features
++++++++++++++++++
- issue91: introduce new py.test.xfail(reason) helper
to imperatively mark a test as expected to fail. Can
be used from within setup and test functions. This is
useful especially for parametrized tests when certain
configurations are expected-to-fail. In this case the
declarative approach with the @py.test.mark.xfail cannot
be used as it would mark all configurations as xfail.
- issue102: introduce new --maxfail=NUM option to stop
test runs after NUM failures. This is a generalization
of the '-x' or '--exitfirst' option which is now equivalent
to '--maxfail=1'. Both '-x' and '--maxfail' will
now also print a line near the end indicating the Interruption.
- issue89: allow py.test.mark decorators to be used on classes
(class decorators were introduced with python2.6) and
also allow to have multiple markers applied at class/module level
by specifying a list.
- improve and refine letter reporting in the progress bar:
. pass
f failed test
s skipped tests (reminder: use for dependency/platform mismatch only)
x xfailed test (test that was expected to fail)
X xpassed test (test that was expected to fail but passed)
You can use any combination of 'fsxX' with the '-r' extended
reporting option. The xfail/xpass results will show up as
skipped tests in the junitxml output - which also fixes
issue99.
- make py.test.cmdline.main() return the exitstatus instead of raising
SystemExit and also allow it to be called multiple times. This of
course requires that your application and tests are properly teared
down and don't have global state.
Fixes / Maintenance
++++++++++++++++++++++
- improved traceback presentation:
- improved and unified reporting for "--tb=short" option
- Errors during test module imports are much shorter, (using --tb=short style)
- raises shows shorter more relevant tracebacks
- --fulltrace now more systematically makes traces longer / inhibits cutting
- improve support for raises and other dynamically compiled code by
manipulating python's linecache.cache instead of the previous
rather hacky way of creating custom code objects. This makes
it seemlessly work on Jython and PyPy where it previously didn't.
- fix issue96: make capturing more resilient against Control-C
interruptions (involved somewhat substantial refactoring
to the underlying capturing functionality to avoid race
conditions).
- fix chaining of conditional skipif/xfail decorators - so it works now
as expected to use multiple @py.test.mark.skipif(condition) decorators,
including specific reporting which of the conditions lead to skipping.
- fix issue95: late-import zlib so that it's not required
for general py.test startup.
- fix issue94: make reporting more robust against bogus source code
(and internally be more careful when presenting unexpected byte sequences)

View File

@@ -1,720 +0,0 @@
py.test/pylib 1.3.2: API and reporting refinements, many fixes
===========================================================================
The pylib/py.test 1.3.2 release brings many bug fixes and some new
features. It was refined for and tested against the recently released
Python2.7 and remains compatibile to the usual armada of interpreters
(Python2.4 through to Python3.1.2, Jython and PyPy). Note that for using
distributed testing features you'll need to upgrade to the jointly released
pytest-xdist-1.4 because of some internal refactorings.
See http://pytest.org for general documentation and below for
a detailed CHANGELOG.
cheers & particular thanks to Benjamin Peterson, Ronny Pfannschmidt
and all issue and patch contributors,
holger krekel
Changes between 1.3.1 and 1.3.2
==================================================
New features
++++++++++++++++++
- fix issue103: introduce py.test.raises as context manager, examples::
with py.test.raises(ZeroDivisionError):
x = 0
1 / x
with py.test.raises(RuntimeError) as excinfo:
call_something()
# you may do extra checks on excinfo.value|type|traceback here
(thanks Ronny Pfannschmidt)
- Funcarg factories can now dynamically apply a marker to a
test invocation. This is for example useful if a factory
provides parameters to a test which are expected-to-fail::
def pytest_funcarg__arg(request):
request.applymarker(py.test.mark.xfail(reason="flaky config"))
...
def test_function(arg):
...
- improved error reporting on collection and import errors. This makes
use of a more general mechanism, namely that for custom test item/collect
nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
override it to return a string error representation of your choice
which is going to be reported as a (red) string.
- introduce '--junitprefix=STR' option to prepend a prefix
to all reports in the junitxml file.
Bug fixes / Maintenance
++++++++++++++++++++++++++
- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
you can properly check for their existence in a cross-python manner).
- refine --pdb: ignore xfailed tests, unify its TB-reporting and
don't display failures again at the end.
- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
- fix py.code.compile(source) to generate unique filenames
- fix assertion re-interp problems on PyPy, by defering code
compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
- fix py.path.local.pyimport() to work with directories
- streamline py.path.local.mkdtemp implementation and usage
- don't print empty lines when showing junitxml-filename
- add optional boolean ignore_errors parameter to py.path.local.remove
- fix terminal writing on win32/python2.4
- py.process.cmdexec() now tries harder to return properly encoded unicode objects
on all python versions
- install plain py.test/py.which scripts also for Jython, this helps to
get canonical script paths in virtualenv situations
- make path.bestrelpath(path) return ".", note that when calling
X.bestrelpath the assumption is that X is a directory.
- make initial conftest discovery ignore "--" prefixed arguments
- fix resultlog plugin when used in an multicpu/multihost xdist situation
(thanks Jakub Gustak)
- perform distributed testing related reporting in the xdist-plugin
rather than having dist-related code in the generic py.test
distribution
- fix homedir detection on Windows
- ship distribute_setup.py version 0.6.13
Changes between 1.3.0 and 1.3.1
==================================================
New features
++++++++++++++++++
- issue91: introduce new py.test.xfail(reason) helper
to imperatively mark a test as expected to fail. Can
be used from within setup and test functions. This is
useful especially for parametrized tests when certain
configurations are expected-to-fail. In this case the
declarative approach with the @py.test.mark.xfail cannot
be used as it would mark all configurations as xfail.
- issue102: introduce new --maxfail=NUM option to stop
test runs after NUM failures. This is a generalization
of the '-x' or '--exitfirst' option which is now equivalent
to '--maxfail=1'. Both '-x' and '--maxfail' will
now also print a line near the end indicating the Interruption.
- issue89: allow py.test.mark decorators to be used on classes
(class decorators were introduced with python2.6) and
also allow to have multiple markers applied at class/module level
by specifying a list.
- improve and refine letter reporting in the progress bar:
. pass
f failed test
s skipped tests (reminder: use for dependency/platform mismatch only)
x xfailed test (test that was expected to fail)
X xpassed test (test that was expected to fail but passed)
You can use any combination of 'fsxX' with the '-r' extended
reporting option. The xfail/xpass results will show up as
skipped tests in the junitxml output - which also fixes
issue99.
- make py.test.cmdline.main() return the exitstatus instead of raising
SystemExit and also allow it to be called multiple times. This of
course requires that your application and tests are properly teared
down and don't have global state.
Fixes / Maintenance
++++++++++++++++++++++
- improved traceback presentation:
- improved and unified reporting for "--tb=short" option
- Errors during test module imports are much shorter, (using --tb=short style)
- raises shows shorter more relevant tracebacks
- --fulltrace now more systematically makes traces longer / inhibits cutting
- improve support for raises and other dynamically compiled code by
manipulating python's linecache.cache instead of the previous
rather hacky way of creating custom code objects. This makes
it seemlessly work on Jython and PyPy where it previously didn't.
- fix issue96: make capturing more resilient against Control-C
interruptions (involved somewhat substantial refactoring
to the underlying capturing functionality to avoid race
conditions).
- fix chaining of conditional skipif/xfail decorators - so it works now
as expected to use multiple @py.test.mark.skipif(condition) decorators,
including specific reporting which of the conditions lead to skipping.
- fix issue95: late-import zlib so that it's not required
for general py.test startup.
- fix issue94: make reporting more robust against bogus source code
(and internally be more careful when presenting unexpected byte sequences)
Changes between 1.2.1 and 1.3.0
==================================================
- deprecate --report option in favour of a new shorter and easier to
remember -r option: it takes a string argument consisting of any
combination of 'xfsX' characters. They relate to the single chars
you see during the dotted progress printing and will print an extra line
per test at the end of the test run. This extra line indicates the exact
position or test ID that you directly paste to the py.test cmdline in order
to re-run a particular test.
- allow external plugins to register new hooks via the new
pytest_addhooks(pluginmanager) hook. The new release of
the pytest-xdist plugin for distributed and looponfailing
testing requires this feature.
- add a new pytest_ignore_collect(path, config) hook to allow projects and
plugins to define exclusion behaviour for their directory structure -
for example you may define in a conftest.py this method::
def pytest_ignore_collect(path):
return path.check(link=1)
to prevent even a collection try of any tests in symlinked dirs.
- new pytest_pycollect_makemodule(path, parent) hook for
allowing customization of the Module collection object for a
matching test module.
- extend and refine xfail mechanism:
``@py.test.mark.xfail(run=False)`` do not run the decorated test
``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
specifiying ``--runxfail`` on command line virtually ignores xfail markers
- expose (previously internal) commonly useful methods:
py.io.get_terminal_with() -> return terminal width
py.io.ansi_print(...) -> print colored/bold text on linux/win32
py.io.saferepr(obj) -> return limited representation string
- expose test outcome related exceptions as py.test.skip.Exception,
py.test.raises.Exception etc., useful mostly for plugins
doing special outcome interpretation/tweaking
- (issue85) fix junitxml plugin to handle tests with non-ascii output
- fix/refine python3 compatibility (thanks Benjamin Peterson)
- fixes for making the jython/win32 combination work, note however:
jython2.5.1/win32 does not provide a command line launcher, see
http://bugs.jython.org/issue1491 . See pylib install documentation
for how to work around.
- fixes for handling of unicode exception values and unprintable objects
- (issue87) fix unboundlocal error in assertionold code
- (issue86) improve documentation for looponfailing
- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
- ship distribute_setup.py version 0.6.10
- added links to the new capturelog and coverage plugins
Changes between 1.2.1 and 1.2.0
=====================================
- refined usage and options for "py.cleanup"::
py.cleanup # remove "*.pyc" and "*$py.class" (jython) files
py.cleanup -e .swp -e .cache # also remove files with these extensions
py.cleanup -s # remove "build" and "dist" directory next to setup.py files
py.cleanup -d # also remove empty directories
py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'"
py.cleanup -n # dry run, only show what would be removed
- add a new option "py.test --funcargs" which shows available funcargs
and their help strings (docstrings on their respective factory function)
for a given test path
- display a short and concise traceback if a funcarg lookup fails
- early-load "conftest.py" files in non-dot first-level sub directories.
allows to conveniently keep and access test-related options in a ``test``
subdir and still add command line options.
- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
- fix issue78: always call python-level teardown functions even if the
according setup failed. This includes refinements for calling setup_module/class functions
which will now only be called once instead of the previous behaviour where they'd be called
multiple times if they raise an exception (including a Skipped exception). Any exception
will be re-corded and associated with all tests in the according module/class scope.
- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
- fix pdb debugging to be in the correct frame on raises-related errors
- update apipkg.py to fix an issue where recursive imports might
unnecessarily break importing
- fix plugin links
Changes between 1.2 and 1.1.1
=====================================
- moved dist/looponfailing from py.test core into a new
separately released pytest-xdist plugin.
- new junitxml plugin: --junitxml=path will generate a junit style xml file
which is processable e.g. by the Hudson CI system.
- new option: --genscript=path will generate a standalone py.test script
which will not need any libraries installed. thanks to Ralf Schmitt.
- new option: --ignore will prevent specified path from collection.
Can be specified multiple times.
- new option: --confcutdir=dir will make py.test only consider conftest
files that are relative to the specified dir.
- new funcarg: "pytestconfig" is the pytest config object for access
to command line args and can now be easily used in a test.
- install 'py.test' and `py.which` with a ``-$VERSION`` suffix to
disambiguate between Python3, python2.X, Jython and PyPy installed versions.
- new "pytestconfig" funcarg allows access to test config object
- new "pytest_report_header" hook can return additional lines
to be displayed at the header of a test run.
- (experimental) allow "py.test path::name1::name2::..." for pointing
to a test within a test collection directly. This might eventually
evolve as a full substitute to "-k" specifications.
- streamlined plugin loading: order is now as documented in
customize.html: setuptools, ENV, commandline, conftest.
also setuptools entry point names are turned to canonical namees ("pytest_*")
- automatically skip tests that need 'capfd' but have no os.dup
- allow pytest_generate_tests to be defined in classes as well
- deprecate usage of 'disabled' attribute in favour of pytestmark
- deprecate definition of Directory, Module, Class and Function nodes
in conftest.py files. Use pytest collect hooks instead.
- collection/item node specific runtest/collect hooks are only called exactly
on matching conftest.py files, i.e. ones which are exactly below
the filesystem path of an item
- change: the first pytest_collect_directory hook to return something
will now prevent further hooks to be called.
- change: figleaf plugin now requires --figleaf to run. Also
change its long command line options to be a bit shorter (see py.test -h).
- change: pytest doctest plugin is now enabled by default and has a
new option --doctest-glob to set a pattern for file matches.
- change: remove internal py._* helper vars, only keep py._pydir
- robustify capturing to survive if custom pytest_runtest_setup
code failed and prevented the capturing setup code from running.
- make py.test.* helpers provided by default plugins visible early -
works transparently both for pydoc and for interactive sessions
which will regularly see e.g. py.test.mark and py.test.importorskip.
- simplify internal plugin manager machinery
- simplify internal collection tree by introducing a RootCollector node
- fix assert reinterpreation that sees a call containing "keyword=..."
- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
hooks on slaves during dist-testing, report module/session teardown
hooks correctly.
- fix issue65: properly handle dist-testing if no
execnet/py lib installed remotely.
- skip some install-tests if no execnet is available
- fix docs, fix internal bin/ script generation
Changes between 1.1.1 and 1.1.0
=====================================
- introduce automatic plugin registration via 'pytest11'
entrypoints via setuptools' pkg_resources.iter_entry_points
- fix py.test dist-testing to work with execnet >= 1.0.0b4
- re-introduce py.test.cmdline.main() for better backward compatibility
- svn paths: fix a bug with path.check(versioned=True) for svn paths,
allow '%' in svn paths, make svnwc.update() default to interactive mode
like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
- refine distributed tarball to contain test and no pyc files
- try harder to have deprecation warnings for py.compat.* accesses
report a correct location
Changes between 1.1.0 and 1.0.2
=====================================
* adjust and improve docs
* remove py.rest tool and internal namespace - it was
never really advertised and can still be used with
the old release if needed. If there is interest
it could be revived into its own tool i guess.
* fix issue48 and issue59: raise an Error if the module
from an imported test file does not seem to come from
the filepath - avoids "same-name" confusion that has
been reported repeatedly
* merged Ronny's nose-compatibility hacks: now
nose-style setup_module() and setup() functions are
supported
* introduce generalized py.test.mark function marking
* reshuffle / refine command line grouping
* deprecate parser.addgroup in favour of getgroup which creates option group
* add --report command line option that allows to control showing of skipped/xfailed sections
* generalized skipping: a new way to mark python functions with skipif or xfail
at function, class and modules level based on platform or sys-module attributes.
* extend py.test.mark decorator to allow for positional args
* introduce and test "py.cleanup -d" to remove empty directories
* fix issue #59 - robustify unittest test collection
* make bpython/help interaction work by adding an __all__ attribute
to ApiModule, cleanup initpkg
* use MIT license for pylib, add some contributors
* remove py.execnet code and substitute all usages with 'execnet' proper
* fix issue50 - cached_setup now caches more to expectations
for test functions with multiple arguments.
* merge Jarko's fixes, issue #45 and #46
* add the ability to specify a path for py.lookup to search in
* fix a funcarg cached_setup bug probably only occuring
in distributed testing and "module" scope with teardown.
* many fixes and changes for making the code base python3 compatible,
many thanks to Benjamin Peterson for helping with this.
* consolidate builtins implementation to be compatible with >=2.3,
add helpers to ease keeping 2 and 3k compatible code
* deprecate py.compat.doctest|subprocess|textwrap|optparse
* deprecate py.magic.autopath, remove py/magic directory
* move pytest assertion handling to py/code and a pytest_assertion
plugin, add "--no-assert" option, deprecate py.magic namespaces
in favour of (less) py.code ones.
* consolidate and cleanup py/code classes and files
* cleanup py/misc, move tests to bin-for-dist
* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
* consolidate py.log implementation, remove old approach.
* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
text/unicode and byte-streams (uses underlying standard lib io.*
if available)
* make py.unittest_convert helper script available which converts "unittest.py"
style files into the simpler assert/direct-test-classes py.test/nosetests
style. The script was written by Laura Creighton.
* simplified internal localpath implementation
Changes between 1.0.1 and 1.0.2
=====================================
* fixing packaging issues, triggered by fedora redhat packaging,
also added doc, examples and contrib dirs to the tarball.
* added a documentation link to the new django plugin.
Changes between 1.0.0 and 1.0.1
=====================================
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* capturing of unicode writes or encoded strings to sys.stdout/err
work better, also terminalwriting was adapted and somewhat
unified between windows and linux.
* improved documentation layout and content a lot
* added a "--help-config" option to show conftest.py / ENV-var names for
all longopt cmdline options, and some special conftest.py variables.
renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* fix issue #32: adding support for "incomplete" paths to wcpath.status()
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* monkeypatch setenv() now accepts a "prepend" parameter
* improved reporting of collection error tracebacks
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames
Changes between 1.0.0b9 and 1.0.0
=====================================
* more terse reporting try to show filesystem path relatively to current dir
* improve xfail output a bit
Changes between 1.0.0b8 and 1.0.0b9
=====================================
* cleanly handle and report final teardown of test setup
* fix svn-1.6 compat issue with py.path.svnwc().versioned()
(thanks Wouter Vanden Hove)
* setup/teardown or collection problems now show as ERRORs
or with big "E"'s in the progress lines. they are reported
and counted separately.
* dist-testing: properly handle test items that get locally
collected but cannot be collected on the remote side - often
due to platform/dependency reasons
* simplified py.test.mark API - see keyword plugin documentation
* integrate better with logging: capturing now by default captures
test functions and their immediate setup/teardown in a single stream
* capsys and capfd funcargs now have a readouterr() and a close() method
(underlyingly py.io.StdCapture/FD objects are used which grew a
readouterr() method as well to return snapshots of captured out/err)
* make assert-reinterpretation work better with comparisons not
returning bools (reported with numpy from thanks maciej fijalkowski)
* reworked per-test output capturing into the pytest_iocapture.py plugin
and thus removed capturing code from config object
* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
Changes between 1.0.0b7 and 1.0.0b8
=====================================
* pytest_unittest-plugin is now enabled by default
* introduced pytest_keyboardinterrupt hook and
refined pytest_sessionfinish hooked, added tests.
* workaround a buggy logging module interaction ("closing already closed
files"). Thanks to Sridhar Ratnakumar for triggering.
* if plugins use "py.test.importorskip" for importing
a dependency only a warning will be issued instead
of exiting the testing process.
* many improvements to docs:
- refined funcargs doc , use the term "factory" instead of "provider"
- added a new talk/tutorial doc page
- better download page
- better plugin docstrings
- added new plugins page and automatic doc generation script
* fixed teardown problem related to partially failing funcarg setups
(thanks MrTopf for reporting), "pytest_runtest_teardown" is now
always invoked even if the "pytest_runtest_setup" failed.
* tweaked doctest output for docstrings in py modules,
thanks Radomir.
Changes between 1.0.0b3 and 1.0.0b7
=============================================
* renamed py.test.xfail back to py.test.mark.xfail to avoid
two ways to decorate for xfail
* re-added py.test.mark decorator for setting keywords on functions
(it was actually documented so removing it was not nice)
* remove scope-argument from request.addfinalizer() because
request.cached_setup has the scope arg. TOOWTDI.
* perform setup finalization before reporting failures
* apply modified patches from Andreas Kloeckner to allow
test functions to have no func_code (#22) and to make
"-k" and function keywords work (#20)
* apply patch from Daniel Peolzleithner (issue #23)
* resolve issue #18, multiprocessing.Manager() and
redirection clash
* make __name__ == "__channelexec__" for remote_exec code
Changes between 1.0.0b1 and 1.0.0b3
=============================================
* plugin classes are removed: one now defines
hooks directly in conftest.py or global pytest_*.py
files.
* added new pytest_namespace(config) hook that allows
to inject helpers directly to the py.test.* namespace.
* documented and refined many hooks
* added new style of generative tests via
pytest_generate_tests hook that integrates
well with function arguments.
Changes between 0.9.2 and 1.0.0b1
=============================================
* introduced new "funcarg" setup method,
see doc/test/funcarg.txt
* introduced plugin architecuture and many
new py.test plugins, see
doc/test/plugins.txt
* teardown_method is now guaranteed to get
called after a test method has run.
* new method: py.test.importorskip(mod,minversion)
will either import or call py.test.skip()
* completely revised internal py.test architecture
* new py.process.ForkedFunc object allowing to
fork execution of a function to a sub process
and getting a result back.
XXX lots of things missing here XXX
Changes between 0.9.1 and 0.9.2
===============================
* refined installation and metadata, created new setup.py,
now based on setuptools/ez_setup (thanks to Ralf Schmitt
for his support).
* improved the way of making py.* scripts available in
windows environments, they are now added to the
Scripts directory as ".cmd" files.
* py.path.svnwc.status() now is more complete and
uses xml output from the 'svn' command if available
(Guido Wesdorp)
* fix for py.path.svn* to work with svn 1.5
(Chris Lamb)
* fix path.relto(otherpath) method on windows to
use normcase for checking if a path is relative.
* py.test's traceback is better parseable from editors
(follows the filenames:LINENO: MSG convention)
(thanks to Osmo Salomaa)
* fix to javascript-generation, "py.test --runbrowser"
should work more reliably now
* removed previously accidentally added
py.test.broken and py.test.notimplemented helpers.
* there now is a py.__version__ attribute
Changes between 0.9.0 and 0.9.1
===============================
This is a fairly complete list of changes between 0.9 and 0.9.1, which can
serve as a reference for developers.
* allowing + signs in py.path.svn urls [39106]
* fixed support for Failed exceptions without excinfo in py.test [39340]
* added support for killing processes for Windows (as well as platforms that
support os.kill) in py.misc.killproc [39655]
* added setup/teardown for generative tests to py.test [40702]
* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
* fixed problem with calling .remove() on wcpaths of non-versioned files in
py.path [44248]
* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
* fail to run greenlet tests when pypy is available, but without stackless
[45294]
* small fixes in rsession tests [45295]
* fixed issue with 2.5 type representations in py.test [45483, 45484]
* made that internal reporting issues displaying is done atomically in py.test
[45518]
* made that non-existing files are igored by the py.lookup script [45519]
* improved exception name creation in py.test [45535]
* made that less threads are used in execnet [merge in 45539]
* removed lock required for atomical reporting issue displaying in py.test
[45545]
* removed globals from execnet [45541, 45547]
* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
get called in 2.5 (py.execnet) [45548]
* fixed bug in joining threads in py.execnet's servemain [45549]
* refactored py.test.rsession tests to not rely on exact output format anymore
[45646]
* using repr() on test outcome [45647]
* added 'Reason' classes for py.test.skip() [45648, 45649]
* killed some unnecessary sanity check in py.test.collect [45655]
* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
usable by Administrators [45901]
* added support for locking and non-recursive commits to py.path.svnwc [45994]
* locking files in py.execnet to prevent CPython from segfaulting [46010]
* added export() method to py.path.svnurl
* fixed -d -x in py.test [47277]
* fixed argument concatenation problem in py.path.svnwc [49423]
* restore py.test behaviour that it exits with code 1 when there are failures
[49974]
* don't fail on html files that don't have an accompanying .txt file [50606]
* fixed 'utestconvert.py < input' [50645]
* small fix for code indentation in py.code.source [50755]
* fix _docgen.py documentation building [51285]
* improved checks for source representation of code blocks in py.test [51292]
* added support for passing authentication to py.path.svn* objects [52000,
52001]
* removed sorted() call for py.apigen tests in favour of [].sort() to support
Python 2.3 [52481]

View File

@@ -0,0 +1,129 @@
py.test 2.0.0: asserts++, unittest++, reporting++, config++, docs++
===========================================================================
Welcome to pytest-2.0.0, a major new release of "py.test", the rapid
easy Python testing tool. There are many new features and enhancements,
see below for summary and detailed lists. A lot of long-deprecated code
has been removed, resulting in a much smaller and cleaner
implementation. See the new docs with examples here:
http://pytest.org/2.0.0/index.html
A note on packaging: pytest used to part of the "py" distribution up
until version py-1.3.4 but this has changed now: pytest-2.0.0 only
contains py.test related code and is expected to be backward-compatible
to existing test code. If you want to install pytest, just type one of::
pip install -U pytest
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
New Features
-----------------------
- new invocations through Python interpreter and from Python::
python -m pytest # on all pythons >= 2.5
or from a python program::
import pytest ; pytest.main(arglist, pluginlist)
see http://pytest.org/2.0.0/usage.html for details.
- new and better reporting information in assert expressions
if comparing lists, sequences or strings.
see http://pytest.org/2.0.0/assert.html#newreport
- new configuration through ini-files (setup.cfg or tox.ini recognized),
for example::
[pytest]
norecursedirs = .hg data* # don't ever recurse in such dirs
addopts = -x --pyargs # add these command line options by default
see http://pytest.org/2.0.0/customize.html
- improved standard unittest support. In general py.test should now
better be able to run custom unittest.TestCases like twisted trial
or Django based TestCases. Also you can now run the tests of an
installed 'unittest' package with py.test::
py.test --pyargs unittest
- new "-q" option which decreases verbosity and prints a more
nose/unittest-style "dot" output.
- many many more detailed improvements details
Fixes
-----------------------
- fix issue126 - introduce py.test.set_trace() to trace execution via
PDB during the running of tests even if capturing is ongoing.
- fix issue124 - make reporting more resilient against tests opening
files on filedescriptor 1 (stdout).
- fix issue109 - sibling conftest.py files will not be loaded.
(and Directory collectors cannot be customized anymore from a Directory's
conftest.py - this needs to happen at least one level up).
- fix issue88 (finding custom test nodes from command line arg)
- fix issue93 stdout/stderr is captured while importing conftest.py
- fix bug: unittest collected functions now also can have "pytestmark"
applied at class/module level
Important Notes
--------------------
* The usual way in pre-2.0 times to use py.test in python code was
to import "py" and then e.g. use "py.test.raises" for the helper.
This remains valid and is not planned to be deprecated. However,
in most examples and internal code you'll find "import pytest"
and "pytest.raises" used as the recommended default way.
* pytest now first performs collection of the complete test suite
before running any test. This changes for example the semantics of when
pytest_collectstart/pytest_collectreport are called. Some plugins may
need upgrading.
* The pytest package consists of a 400 LOC core.py and about 20 builtin plugins,
summing up to roughly 5000 LOCs, including docstrings. To be fair, it also
uses generic code from the "pylib", and the new "py" package to help
with filesystem and introspection/code manipulation.
(Incompatible) Removals
-----------------------------
- py.test.config is now only available if you are in a test run.
- the following (mostly already deprecated) functionality was removed:
- removed support for Module/Class/... collection node definitions
in conftest.py files. They will cause nothing special.
- removed support for calling the pre-1.0 collection API of "run()" and "join"
- removed reading option values from conftest.py files or env variables.
This can now be done much much better and easier through the ini-file
mechanism and the "addopts" entry in particular.
- removed the "disabled" attribute in test classes. Use the skipping
and pytestmark mechanism to skip or xfail a test class.
- py.test.collect.Directory does not exist anymore and it
is not possible to provide an own "Directory" object.
If you have used this and don't know what to do, get
in contact. We'll figure someting out.
Note that pytest_collect_directory() is still called but
any return value will be ignored. This allows to keep
old code working that performed for example "py.test.skip()"
in collect() to prevent recursion into directory trees
if a certain dependency or command line option is missing.
see :ref:`changelog` for more detailed changes.

View File

@@ -1,16 +0,0 @@
=============
Release notes
=============
Contents:
.. toctree::
:maxdepth: 2
.. include: release-1.1.0
.. include: release-1.0.2
release-1.0.1
release-1.0.0
release-0.9.2
release-0.9.0

25
doc/apiref.txt Normal file
View File

@@ -0,0 +1,25 @@
.. _apiref:
py.test reference documentation
================================================
.. toctree::
:maxdepth: 2
builtin.txt
customize.txt
assert.txt
funcargs.txt
xunit_setup.txt
capture.txt
monkeypatch.txt
xdist.txt
tmpdir.txt
skipping.txt
mark.txt
recwarn.txt
unittest.txt
nose.txt
doctest.txt

141
doc/assert.txt Normal file
View File

@@ -0,0 +1,141 @@
Writing and reporting of assertions in tests
============================================
.. _`assert with the assert statement`:
assert with the ``assert`` statement
---------------------------------------------------------
``py.test`` allows to use the standard python ``assert`` for verifying
expectations and values in Python tests. For example, you can write the
following in your tests::
# content of test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
to state that your object has a certain ``attribute``. In case this
assertion fails you will see the value of ``x``::
$ py.test test_assert1.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_assert1.py
test_assert1.py F
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.03 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording 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 for this issue is reading from a file and comparing in one
line::
assert f.read() != '...'
This might fail but when re-interpretation comes along it might pass.
You can rewrite this (and any other expression with side effects) easily, though:
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::
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 informations
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.5 -- pytest-2.0.0.dev30
test path 1: test_assert2.py
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,65 +0,0 @@
======================
pylib scripts
======================
The pylib installs several scripts to support testing and (python)
development. If working from a checkout you may also add ``bin`` to
your ``PATH`` environment variable which makes the scripts available on
your shell prompt.
``py.test`` and ``py.test-$VERSION``
============================================
The ``py.test`` executable is the main tool that the py lib offers;
in fact most code in the py lib is geared towards supporting the
testing process. See the `py.test documentation`_ for extensive
documentation. The ``py.test-$VERSION`` is the same script with
an interpreter specific suffix appended to make
several versions of py.test for using specific interpreters
accessible:
* CPython2.4: py.test-2.4
* CPython2.5: py.test-2.5
* ...
* CPython3.1: py.test-3.1
* Jython-2.5.1: py.test-jython
* pypy-$SUFFIX: py.test-pypy-$SUFFIX
.. _`py.test documentation`: test/index.html
``py.which`` and ``py.which-$VERSION``
=========================================
Usage: ``py.which modulename``
Print the ``__file__`` of the module that is imported via ``import modulename``.
The version-suffix is the same as with ``py.test`` above.
``py.cleanup``
==============
Usage: ``py.cleanup [PATH]``
Delete pyc file recursively, starting from ``PATH`` (which defaults to the
current working directory). Don't follow links and don't recurse into
directories with a ".".
``py.countloc``
===============
Usage: ``py.countloc [PATHS]``
Count (non-empty) lines of python code and number of python files recursively
starting from a ``PATHS`` given on the command line (starting from the current
working directory). Distinguish between test files and normal ones and report
them separately.
``py.lookup``
=============
Usage: ``py.lookup SEARCH_STRING [options]``
Looks recursively at Python files for a ``SEARCH_STRING``, starting from the
present working directory. Prints the line, with the filename and line-number
prepended.

70
doc/builtin.txt Normal file
View File

@@ -0,0 +1,70 @@
.. _`pytest helpers`:
pytest builtin helpers
================================================
builtin pytest.* functions and helping objects
-----------------------------------------------------
You can always use an interactive Python prompt and type::
import pytest
help(pytest)
to get an overview on available globally available helpers.
.. automodule:: pytest
:members:
builtin function arguments
-----------------------------------------------------
You can ask for available builtin or project-custom
:ref:`function arguments <funcargs>` by typing::
$ py.test --funcargs
pytestconfig
the pytest config object with access to command line opts.
capsys
captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
capfd
captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
tmpdir
return a temporary directory path object
unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
monkeypatch
The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
recwarn
Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings

78
doc/capture.txt Normal file
View File

@@ -0,0 +1,78 @@
.. _`captures`:
Capturing of stdout/stderr output
=========================================================
By default ``stdout`` and ``stderr`` output is captured separately for
setup and test execution code. If a test or a setup method fails its
according output will usually be shown along with the failure traceback.
In addition, ``stdin`` is set to a "null" object which will fail all
attempts to read from it. This is important if some code paths in
test otherwise might lead to waiting for input - which is usually
not desired when running automated tests.
Setting capturing methods or disabling capturing
-------------------------------------------------
There are two ways in which ``py.test`` can perform capturing:
* ``fd`` level capturing (default): All writes going to the operating
system file descriptors 1 and 2 will be captured, for example writes such
as ``os.write(1, 'hello')``. Capturing on ``fd``-level also includes
**output from subprocesses**.
* ``sys`` level capturing: The ``sys.stdout`` and ``sys.stderr`` will
will be replaced with in-memory files and the ``print`` builtin or
output from code like ``sys.stderr.write(...)`` will be captured with
this method.
.. _`disable capturing`:
You can influence output capturing mechanisms from the command line::
py.test -s # disable all capturing
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
py.test --capture=fd # also point filedescriptors 1 and 2 to temp file
If you set capturing values in a conftest file like this::
# conftest.py
option_capture = 'fd'
then all tests in that directory will execute with "fd" style capturing.
_ `printdebugging`:
Accessing captured output from a test function
---------------------------------------------------
The :ref:`funcarg mechanism` allows test function a very easy
way to access the captured output by simply using the names
``capsys`` or ``capfd`` in the test function signature. Here
is an example test function that performs some output related
checks::
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")
sys.stderr.write("world\n")
out, err = capsys.readouterr()
assert out == "hello\n"
assert err == "world\n"
print "next"
out, err = capsys.readouterr()
assert out == "next\n"
The ``readouterr()`` call snapshots the output so far -
and capturing will be continued. After the test
function finishes the original streams will
be restored. Using ``capsys`` this way frees your
test from having to care about setting/resetting
output streams and also interacts well with py.test's
own per-test capturing.
If you want to capture on ``fd`` level you can use
the ``capfd`` function argument which offers the exact
same interface.
.. include:: links.inc

View File

@@ -1,2 +1,7 @@
.. _changelog:
Changelog history
=================================
.. include:: ../CHANGELOG

17
doc/check_sphinx.py Normal file
View File

@@ -0,0 +1,17 @@
import py
import subprocess
def test_build_docs(tmpdir):
doctrees = tmpdir.join("doctrees")
htmldir = tmpdir.join("html")
subprocess.check_call([
"sphinx-build", "-W", "-bhtml",
"-d", str(doctrees), ".", str(htmldir)])
def test_linkcheck(tmpdir):
doctrees = tmpdir.join("doctrees")
htmldir = tmpdir.join("html")
subprocess.check_call(
["sphinx-build", "-blinkcheck",
"-d", str(doctrees), ".", str(htmldir)])

View File

@@ -1,134 +0,0 @@
================================================================================
py.code: higher level python code and introspection objects
================================================================================
The ``py.code`` part of the pylib contains some functionality to help
dealing with Python code objects. Even though working with Python's internal
code objects (as found on frames and callables) can be very powerful, it's
usually also quite cumbersome, because the API provided by core Python is
relatively low level and not very accessible.
The ``py.code`` library tries to simplify accessing the code objects as well
as creating them. There is a small set of interfaces a user needs to deal with,
all nicely bundled together, and with a rich set of 'Pythonic' functionality.
Contents of the library
=======================
Every object in the ``py.code`` library wraps a code Python object related
to code objects, source code, frames and tracebacks: the ``py.code.Code``
class wraps code objects, ``py.code.Source`` source snippets,
``py.code.Traceback` exception tracebacks, ``py.code.Frame`` frame
objects (as found in e.g. tracebacks) and ``py.code.ExceptionInfo`` the
tuple provided by sys.exc_info() (containing exception and traceback
information when an exception occurs). Also in the library is a helper function
``py.code.compile()`` that provides the same functionality as Python's
built-in 'compile()' function, but returns a wrapped code object.
The wrappers
============
``py.code.Code``
-------------------
Code objects are instantiated with a code object or a callable as argument,
and provide functionality to compare themselves with other Code objects, get to
the source file or its contents, create new Code objects from scratch, etc.
A quick example::
>>> import py
>>> c = py.code.Code(py.path.local.read)
>>> c.path.basename
'common.py'
>>> isinstance(c.source(), py.code.Source)
True
>>> str(c.source()).split('\n')[0]
"def read(self, mode='r'):"
``py.code.Source``
---------------------
Source objects wrap snippets of Python source code, providing a simple yet
powerful interface to read, deindent, slice, compare, compile and manipulate
them, things that are not so easy in core Python.
Example::
>>> s = py.code.Source("""\
... def foo():
... print "foo"
... """)
>>> str(s).startswith('def') # automatic de-indentation!
True
>>> s.isparseable()
True
>>> sub = s.getstatement(1) # get the statement starting at line 1
>>> str(sub).strip() # XXX why is the strip() required?!?
'print "foo"'
``py.code.Traceback``
------------------------
Tracebacks are usually not very easy to examine, you need to access certain
somewhat hidden attributes of the traceback's items (resulting in expressions
such as 'fname = tb.tb_next.tb_frame.f_code.co_filename'). The Traceback
interface (and its TracebackItem children) tries to improve this.
Example::
>>> import sys
>>> try:
... py.path.local(100) # illegal argument
... except:
... exc, e, tb = sys.exc_info()
>>> t = py.code.Traceback(tb)
>>> first = t[1] # get the second entry (first is in this doc)
>>> first.path.basename # second is in py/path/local.py
'local.py'
>>> isinstance(first.statement, py.code.Source)
True
>>> str(first.statement).strip().startswith('raise ValueError')
True
``py.code.Frame``
--------------------
Frame wrappers are used in ``py.code.Traceback`` items, and will usually not
directly be instantiated. They provide some nice methods to evaluate code
'inside' the frame (using the frame's local variables), get to the underlying
code (frames have a code attribute that points to a ``py.code.Code`` object)
and examine the arguments.
Example (using the 'first' TracebackItem instance created above)::
>>> frame = first.frame
>>> isinstance(frame.code, py.code.Code)
True
>>> isinstance(frame.eval('self'), py.path.local)
True
>>> [namevalue[0] for namevalue in frame.getargs()]
['cls', 'path']
``py.code.ExceptionInfo``
----------------------------
A wrapper around the tuple returned by sys.exc_info() (will call sys.exc_info()
itself if the tuple is not provided as an argument), provides some handy
attributes to easily access the traceback and exception string.
Example::
>>> import sys
>>> try:
... foobar()
... except:
... excinfo = py.code.ExceptionInfo()
>>> excinfo.typename
'NameError'
>>> isinstance(excinfo.traceback, py.code.Traceback)
True
>>> excinfo.exconly()
"NameError: name 'foobar' is not defined"

272
doc/conf.py Normal file
View File

@@ -0,0 +1,272 @@
# -*- 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.
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('.'))
# -- 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 = 'index'
# General information about the project.
project = u'pytest'
copyright = u'2010, holger krekel et aliter'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0.0'
# The full version, including alpha/beta/rc tags.
import py, pytest
assert py.path.local().relto(py.path.local(pytest.__file__).dirpath().dirpath())
release = pytest.__version__
# 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', 'test', ] # XXX
# 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 = True
# 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 = None
# 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 = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# 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 = [
('index', 'pytest.tex', u'pytest Documentation',
u'holger krekel et aliter', '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 = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pytest', u'pytest Documentation',
[u'holger krekel et aliter'], 1)
]
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'pytest'
epub_author = u'holger krekel et aliter'
epub_publisher = u'holger krekel et aliter'
epub_copyright = u'2010, holger krekel et aliter'
# 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 = {} # 'http://docs.python.org/': 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

@@ -1,289 +0,0 @@
import py
from py._plugin.pytest_restdoc import convert_rest_html, strip_html_header
html = py.xml.html
class css:
#pagetitle = "pagetitle"
contentspace = "contentspace"
menubar = "menubar"
navspace = "navspace"
versioninfo = "versioninfo"
class Page(object):
doctype = ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
googlefragment = """
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
try {
var pageTracker = _gat._getTracker("UA-7597274-3");
pageTracker._trackPageview();
} catch(err) {}</script>
"""
def __init__(self, project, title, targetpath, stylesheeturl=None,
type="text/html", encoding="ISO-8859-1"):
self.project = project
self.title = project.prefix_title + title
self.targetpath = targetpath
self.stylesheeturl = stylesheeturl
self.type = type
self.encoding = encoding
self.body = html.body()
self.head = html.head()
self._root = html.html(self.head, self.body)
self.fill()
def a_href(self, name, url, **kwargs):
return html.a(name, class_="menu", href=url, **kwargs)
def a_docref(self, name, relhtmlpath):
docpath = self.project.docpath
return html.div(html.a(name, class_="menu",
href=relpath(self.targetpath.strpath,
docpath.join(relhtmlpath).strpath)))
def a_apigenref(self, name, relhtmlpath):
apipath = self.project.apigenpath
return html.a(name, class_="menu",
href=relpath(self.targetpath.strpath,
apipath.join(relhtmlpath).strpath))
def fill_menubar(self):
items = [
self.a_docref("INSTALL", "install.html"),
self.a_docref("CONTACT", "contact.html"),
self.a_docref("CHANGELOG", "changelog.html"),
self.a_docref("FAQ", "faq.html"),
html.div(
html.h3("py.test:"),
self.a_docref("Index", "test/index.html"),
self.a_docref("Quickstart", "test/quickstart.html"),
self.a_docref("Features", "test/features.html"),
self.a_docref("Funcargs", "test/funcargs.html"),
self.a_docref("Plugins", "test/plugin/index.html"),
self.a_docref("Customize", "test/customize.html"),
self.a_docref("Tutorials", "test/talks.html"),
self.a_href("hudson-tests", "http://hudson.testrun.org")
),
html.div(
html.h3("supporting APIs:"),
self.a_docref("Index", "index.html"),
self.a_docref("py.path", "path.html"),
self.a_docref("py.code", "code.html"),
)
#self.a_docref("py.code", "code.html"),
#self.a_apigenref("api", "api/index.html"),
#self.a_apigenref("source", "source/index.html"),
#self.a_href("source", "http://bitbucket.org/hpk42/py-trunk/src/"),
]
self.menubar = html.div(id=css.menubar, *[
html.div(item) for item in items])
version = py.version
announcelink = self.a_docref("%s ANN" % version,
"announce/release-%s.html" %(version,))
self.menubar.insert(0,
html.div(announcelink))
#self.a_href("%s-%s" % (self.title, py.version),
# "http://pypi.python.org/pypi/py/%s" % version,
#id="versioninfo",
def fill(self):
content_type = "%s;charset=%s" %(self.type, self.encoding)
self.head.append(html.title(self.title))
self.head.append(html.meta(name="Content-Type", content=content_type))
if self.stylesheeturl:
self.head.append(
html.link(href=self.stylesheeturl,
media="screen", rel="stylesheet",
type="text/css"))
self.fill_menubar()
self.body.append(html.div(
self.project.logo,
self.menubar,
id=css.navspace,
))
#self.body.append(html.div(self.title, id=css.pagetitle))
self.contentspace = html.div(id=css.contentspace)
self.body.append(self.contentspace)
def unicode(self, doctype=True):
page = self._root.unicode()
page = page.replace("</body>", self.googlefragment + "</body>")
if doctype:
return self.doctype + page
else:
return page
class PyPage(Page):
def get_menubar(self):
menubar = super(PyPage, self).get_menubar()
# base layout
menubar.append(
html.a("issue", href="https://codespeak.net/issue/py-dev/",
class_="menu"),
)
return menubar
def getrealname(username):
try:
import uconf
except ImportError:
return username
try:
user = uconf.system.User(username)
except KeyboardInterrupt:
raise
try:
return user.realname or username
except KeyError:
return username
class Project:
mydir = py.path.local(__file__).dirpath()
title = "py lib"
prefix_title = "" # we have a logo already containing "py lib"
encoding = 'latin1'
logo = html.div(
html.a(
html.img(alt="py lib", id='pyimg', height=114/2, width=154/2,
src="http://codespeak.net/img/pylib.png"),
href="http://pylib.org"))
Page = PyPage
def __init__(self, sourcepath=None):
if sourcepath is None:
sourcepath = self.mydir
self.setpath(sourcepath)
def setpath(self, sourcepath, docpath=None,
apigenpath=None, stylesheet=None):
self.sourcepath = sourcepath
if docpath is None:
docpath = sourcepath
self.docpath = docpath
if apigenpath is None:
apigenpath = docpath
self.apigenpath = apigenpath
if stylesheet is None:
p = sourcepath.join("style.css")
if p.check():
self.stylesheet = p
else:
self.stylesheet = None
else:
p = sourcepath.join(stylesheet)
if p.check():
stylesheet = p
self.stylesheet = stylesheet
#assert self.stylesheet
self.apigen_relpath = relpath(
self.docpath.strpath + '/', self.apigenpath.strpath + '/')
def get_content(self, txtpath, encoding):
return unicode(txtpath.read(), encoding)
def get_htmloutputpath(self, txtpath):
reloutputpath = txtpath.new(ext='.html').relto(self.sourcepath)
return self.docpath.join(reloutputpath)
def process(self, txtpath):
encoding = self.encoding
content = self.get_content(txtpath, encoding)
outputpath = self.get_htmloutputpath(txtpath)
stylesheet = self.stylesheet
if isinstance(stylesheet, py.path.local):
if not self.docpath.join(stylesheet.basename).check():
docpath.ensure(dir=True)
stylesheet.copy(docpath)
stylesheet = relpath(outputpath.strpath,
self.docpath.join(stylesheet.basename).strpath)
content = convert_rest_html(content, txtpath,
stylesheet=stylesheet, encoding=encoding)
content = strip_html_header(content, encoding=encoding)
title = txtpath.purebasename
if txtpath.dirpath().basename == "test":
title = "py.test " + title
# title = "[%s] %s" % (txtpath.purebasename, py.version)
page = self.Page(self, title,
outputpath, stylesheeturl=stylesheet)
try:
modified = py.process.cmdexec(
"hg tip --template 'modified {date|shortdate}'"
)
except py.process.cmdexec.Error:
modified = " "
#page.body.append(html.div(modified, id="docinfoline"))
page.contentspace.append(py.xml.raw(content))
outputpath.ensure().write(page.unicode().encode(encoding))
# XXX this function comes from apigen/linker.py, put it
# somewhere in py lib
import os
def relpath(p1, p2, sep=os.path.sep, back='..', normalize=True):
""" create a relative path from p1 to p2
sep is the seperator used for input and (depending
on the setting of 'normalize', see below) output
back is the string used to indicate the parent directory
when 'normalize' is True, any backslashes (\) in the path
will be replaced with forward slashes, resulting in a consistent
output on Windows and the rest of the world
paths to directories must end on a / (URL style)
"""
if normalize:
p1 = p1.replace(sep, '/')
p2 = p2.replace(sep, '/')
sep = '/'
# XXX would be cool to be able to do long filename
# expansion and drive
# letter fixes here, and such... iow: windows sucks :(
if (p1.startswith(sep) ^ p2.startswith(sep)):
raise ValueError("mixed absolute relative path: %r -> %r" %(p1, p2))
fromlist = p1.split(sep)
tolist = p2.split(sep)
# AA
# AA BB -> AA/BB
#
# AA BB
# AA CC -> CC
#
# AA BB
# AA -> ../AA
diffindex = 0
for x1, x2 in zip(fromlist, tolist):
if x1 != x2:
break
diffindex += 1
commonindex = diffindex - 1
fromlist_diff = fromlist[diffindex:]
tolist_diff = tolist[diffindex:]
if not fromlist_diff:
return sep.join(tolist[commonindex:])
backcount = len(fromlist_diff)
if tolist_diff:
return sep.join([back,]*(backcount-1) + tolist_diff)
return sep.join([back,]*(backcount) + tolist[commonindex:])

View File

@@ -1,8 +1 @@
#XXX make work: excludedirs = ['_build']
import py
pytest_plugins = ['pytest_restdoc']
collect_ignore = ['test/attic.txt']
def pytest_runtest_setup(item):
if item.fspath.ext == ".txt":
py.test.importorskip("pygments") # for raising an error
collect_ignore = ["conf.py"]

View File

@@ -1,50 +1,43 @@
Contact and Communication points
.. _`contact channels`:
.. _`contact`:
Contact channels
===================================
- `py-dev developers list`_ announcements and discussions.
- `new issue tracker`_ to report bugs or suggest features (for version
2.0 and above). You may also peek at the `old issue tracker`_ but please
don't submit bugs there anymore.
- #pylib on irc.freenode.net IRC channel for random questions.
- `Testing In Python`_: a mailing list for Python testing tools and discussion.
- `py-dev developers list`_ pytest specific announcements and discussions.
- `tetamap`_: Holger Krekel's blog, often about testing and py.test related news.
- #pylib on irc.freenode.net IRC channel for random questions.
- `Testing In Python`_: a mailing list for testing tools and discussion.
- private mail to Holger.Krekel at gmail com if you have sensitive issues to communicate
- `commit mailing list`_ or `@pylibcommit`_ to follow development commits,
- `commit mailing list`_
- `bitbucket issue tracker`_ use this bitbucket issue tracker to report
bugs or request features.
- `merlinux.eu`_ offers on-site teaching and consulting services.
- `merlinux.eu`_ offers on-site teaching and consulting services.
.. _`bitbucket issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/
.. _`new issue tracker`: http://bitbucket.org/hpk42/pytest/issues/
.. _`old issue tracker`: http://bitbucket.org/hpk42/py-trunk/issues/
.. _`merlinux.eu`: http://merlinux.eu
.. _`get an account`:
.. _`get an account`:
.. _tetamap: http://tetamap.wordpress.com
.. _`@pylibcommit`: http://twitter.com/pylibcommit
.. _`@pylibcommit`: http://twitter.com/pylibcommit
..
get an account on codespeak
---------------------------
codespeak_ is where the subversion repository is hosted. If you know
someone who is active on codespeak already or you are otherwise known in
the community (see also: FOAF_) you will get access. But even if
you are new to the python developer community please come to the IRC
or the mailing list and ask questions, get involved.
.. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python
.. _FOAF: http://en.wikipedia.org/wiki/FOAF
.. _us: http://codespeak.net/mailman/listinfo/py-dev
.. _codespeak: http://codespeak.net/
.. _`py-dev`:
.. _`development mailing list`:
.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev
.. _`py-svn`:
.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn
.. _`py-dev`:
.. _`development mailing list`:
.. _`py-dev developers list`: http://codespeak.net/mailman/listinfo/py-dev
.. _`py-svn`:
.. _`commit mailing list`: http://codespeak.net/mailman/listinfo/py-svn

33
doc/contents.txt Normal file
View File

@@ -0,0 +1,33 @@
.. _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`

126
doc/customize.txt Normal file
View File

@@ -0,0 +1,126 @@
basic test configuration
===================================
Command line options and configuration file settings
-----------------------------------------------------------------
You can get help on options and ini-config values by running::
py.test -h # prints options _and_ config file settings
This will display command line and configuration file settings
which were registered by installed plugins.
how test configuration is read from setup/tox ini-files
--------------------------------------------------------
py.test searched for the first matching ini-style configuration file
in the directories of command line argument and the directories above.
It looks for filenames in this order::
pytest.ini
tox.ini
setup.cfg
Searching stops when the first ``[pytest]`` section is found.
There is no merging of configuration values from multiple files. Example::
py.test path/to/testdir
will look in the following dirs for a config file::
path/to/testdir/pytest.ini
path/to/testdir/tox.ini
path/to/testdir/setup.cfg
path/to/pytest.ini
path/to/tox.ini
path/to/setup.cfg
... # up until root of filesystem
If argument is provided to a py.test run, the current working directory
is used to start the search.
.. _`how to change command line options defaults`:
.. _`adding default options`:
how to change command line options defaults
------------------------------------------------
py.test provides a simple way to set some default
command line options. For example, if you want
to always see detailed info on skipped and xfailed
tests, as well as have terser "dot progress output",
you can add this to your root directory::
# content of pytest.ini
# (or tox.ini or setup.cfg)
[pytest]
addopts = -rsxX -q
From now on, running ``py.test`` will implicitely add
the specified options.
builtin configuration file options
----------------------------------------------
.. confval:: minversion
specifies a minimal pytest version needed 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
had been specified by the user. Example: if you have this ini file content::
[pytest]
addopts = --maxfail=2 -rf # exit after 2 failures, report fail info
issuing ``py.test test_hello.py`` actually means::
py.test --maxfail=2 -rf test_hello.py
Default is to add no options.
.. confval:: norecursedirs
Set the directory basename patterns to avoid when recursing
for test discovery. The individual (fnmatch-style) patterns are
applied to the basename of a directory to decide if to recurse into it.
Pattern matching characters::
* matches everything
? matches any single character
[seq] matches any character in seq
[!seq] matches any char not in seq
Default patterns are ``.* _* CVS {args}``. Setting a ``norecurse``
replaces the default. Here is a customizing example for avoiding
a different set of directories::
# content of setup.cfg
[pytest]
norecursedirs = .svn _build tmp*
This would tell py.test to not recurse into typical subversion or
sphinx-build directories or into any ``tmp`` prefixed directory.
.. confval:: python_files
One or more Glob-style file patterns determining which python files
are considered as test modules.
.. confval:: python_classes
One or more name prefixes determining which test classes
are considered as test modules.
.. confval:: python_functions
One or more name prefixes determining which test functions
and methods are considered as test modules.
See :ref:`change naming conventions` for examples.

34
doc/develop.txt Normal file
View File

@@ -0,0 +1,34 @@
=================================================
Feedback and contribute to py.test
=================================================
.. toctree::
:maxdepth: 2
contact.txt
.. _checkout:
Working from version control or a tarball
=================================================
To follow development or start experiments, checkout the
complete code and documentation source with mercurial_::
hg clone https://bitbucket.org/hpk42/pytest/
You can also go to the python package index and
download and unpack a TAR file::
http://pypi.python.org/pypi/pytest/
activating a checkout with setuptools
--------------------------------------------
With a working Distribute_ or setuptools_ installation you can type::
python setup.py develop
in order to work inline with the tools and the lib of your checkout.
.. include:: links.inc

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