Compare commits

...

826 Commits
1.0.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
holger krekel
caf5bdbf89 re-add exclude pattern
--HG--
branch : trunk
2010-07-08 17:20:52 +02:00
holger krekel
6ea944a350 don't run too-long-filename test
--HG--
branch : trunk
2010-07-08 15:54:51 +02:00
holger krekel
9d47521cf0 finalizing docs
--HG--
branch : trunk
2010-07-08 15:28:54 +02:00
holger krekel
86440b1853 ship distribute_setup.py version 0.6.13
--HG--
branch : trunk
2010-07-08 13:40:19 +02:00
holger krekel
e98b15eb67 fix windows homedir detection
--HG--
branch : trunk
2010-07-08 12:35:43 +02:00
holger krekel
1ffe0e7b82 using improved versioing
--HG--
branch : trunk
2010-07-07 18:08:16 +02:00
holger krekel
3c069544fc bold summary line
--HG--
branch : trunk
2010-07-07 16:41:28 +02:00
holger krekel
4d4344212f fixing a doc reference, removing development dependency
--HG--
branch : trunk
2010-07-07 16:37:28 +02:00
holger krekel
eddd16d9fd progressing towards 1.3.2, adding announcement, regen docs
--HG--
branch : trunk
2010-07-07 15:41:28 +02:00
holger krekel
37a2898f18 reintroduce --junit - i think it is actually useful
--HG--
branch : trunk
2010-07-07 14:43:31 +02:00
holger krekel
af5e18e26c small fix
--HG--
branch : trunk
2010-07-07 13:07:34 +02:00
holger krekel
320835d43f split out pytest-xdist related reporting to the plugin
--HG--
branch : trunk
2010-07-07 12:41:15 +02:00
holger krekel
2664230fad fix test for python2.7
--HG--
branch : trunk
2010-07-06 13:29:32 +02:00
holger krekel
48d818742c adjust tox.ini
--HG--
branch : trunk
2010-07-06 12:04:22 +02:00
holger krekel
2b13836efa use the new sdistfile option
--HG--
branch : trunk
2010-07-05 18:40:49 +02:00
holger krekel
195d066ff8 skip sdist on hudson
--HG--
branch : trunk
2010-07-05 17:14:20 +02:00
holger krekel
1e8b59e39f updating tox.ini to new format
--HG--
branch : trunk
2010-07-05 15:55:21 +02:00
holger krekel
b28c439494 some minor compatibility issues wrt to the just released python2.7
--HG--
branch : trunk
2010-07-04 22:13:12 +02:00
holger krekel
223a04be27 fix doc env
--HG--
branch : trunk
2010-07-04 19:16:49 +02:00
holger krekel
3aefaff44f improve testing of docs
--HG--
branch : trunk
2010-07-04 18:25:22 +02:00
holger krekel
f9c5b00ffa refine and extend custom error reporting particularly for collection-related errors
--HG--
branch : trunk
2010-07-04 17:06:50 +02:00
holger krekel
e533e63bbf enable some more tests
--HG--
branch : trunk
2010-07-04 13:56:03 +02:00
holger krekel
be582b5f53 don't use dist-testing with jython to uncomplicate the testing matter
--HG--
branch : trunk
2010-07-03 14:54:48 +02:00
holger krekel
4a489af0ff remove the --junitxmlprefix feature - it's kind of YAGNI i guess -
i introduced it after 1.3.1 but don't need it anymore and thus
it's not going to be there for 1.3.2.

--HG--
branch : trunk
2010-07-03 14:44:47 +02:00
holger krekel
c19f51a3d7 refine header message
--HG--
branch : trunk
2010-07-03 14:35:27 +02:00
holger krekel
ca9b320c9c update again
--HG--
branch : trunk
2010-07-03 14:30:47 +02:00
holger krekel
ace2f975ea update tox.ini according to tox progress
--HG--
branch : trunk
2010-07-03 14:29:43 +02:00
holger krekel
e3250f4846 another correction
--HG--
branch : trunk
2010-07-02 15:33:36 +02:00
holger krekel
29217a47f4 updated tox.ini
--HG--
branch : trunk
2010-07-02 15:26:03 +02:00
holger krekel
5f9876d54e apply patch from Jakub wrt fixing resultlog/xdist combo
--HG--
branch : trunk
2010-07-02 13:01:21 +02:00
holger krekel
8c0dfb525d use the new envbindir subst
--HG--
branch : trunk
2010-07-02 10:15:30 +02:00
holger krekel
aa4308883c py.test-1.3.1 does not provide py.test proper for jython, only py.test-jython
(py.test-1.3.2 will provide py.test even for jython installs)

--HG--
branch : trunk
2010-07-01 19:54:28 +02:00
holger krekel
381b81b0e1 actually run only "testing" tests
--HG--
branch : trunk
2010-07-01 19:46:22 +02:00
holger krekel
7335c4d06d add jython env
--HG--
branch : trunk
2010-07-01 19:43:46 +02:00
holger krekel
f554fa03ae make initial conftest finding ignore "--" arguments
--HG--
branch : trunk
2010-07-01 19:27:40 +02:00
holger krekel
6fa58fd8c9 (ARGH) of windows/hudson/multi-config combo produces too long filenames, so use the global temp dir
--HG--
branch : trunk
2010-07-01 18:39:25 +02:00
holger krekel
c57a24774d use confcutdir
--HG--
branch : trunk
2010-07-01 18:12:38 +02:00
holger krekel
d51000b15d add a tox.ini file
--HG--
branch : trunk
2010-07-01 17:59:35 +02:00
holger krekel
b8db15a94f refine bestrelpath to return "." for X.bestrelpath(X) and refine its docstring
--HG--
branch : trunk
2010-06-28 16:32:43 +02:00
holger krekel
2f50ed3e99 install plain py.test script also for jython
--HG--
branch : trunk
2010-06-28 11:59:12 +02:00
holger krekel
da52304a6e adjust changelog, add fixed issue92
--HG--
branch : trunk
2010-06-28 11:26:19 +02:00
Benjamin Peterson
f8d3a80af5 name CollectOnlyReporter's output attribute "_tw" for consistency with TerminalReporter
This fixes #92.

--HG--
branch : trunk
2010-06-27 18:17:47 -05:00
Benjamin Peterson
df744e9182 merge heads
--HG--
branch : trunk
2010-06-25 12:25:44 -05:00
Benjamin Peterson
d82d65d95e remove uneeded exec
--HG--
branch : trunk
2010-06-25 12:24:51 -05:00
holger krekel
f856db29dc refine py.process.cmdexec handling wrt unicode on all python versions
--HG--
branch : trunk
2010-06-25 10:30:15 +02:00
Benjamin Peterson
4d75c703a0 correct expected message
--HG--
branch : trunk
2010-06-18 22:55:06 -05:00
holger krekel
3a8d13599e a fix from maciej who claims this fixes issues on some systems.
passes all tests so i just apply it.

--HG--
branch : trunk
2010-06-17 18:04:36 +02:00
holger krekel
504e42a62e remove tox.ini for now
--HG--
branch : trunk
2010-06-17 13:25:28 +02:00
holger krekel
149f9e1042 refine reporting with --pdb some more
--HG--
branch : trunk
2010-06-17 12:53:29 +02:00
holger krekel
3f1efe1b57 fix --pdb to not drop interactive on xfailed tests
--HG--
branch : trunk
2010-06-16 12:35:08 +02:00
holger krekel
72de7d94fd adding a link to Floris' new post
--HG--
branch : trunk
2010-06-14 15:45:31 +02:00
Benjamin Peterson
c3233b9c15 improve comment
--HG--
branch : trunk
2010-06-10 14:05:40 -05:00
Benjamin Peterson
2995d65720 fix assertion interpretation when the operator is **
--HG--
branch : trunk
2010-06-10 13:50:07 -05:00
holger krekel
77a7d576ec defer a number of other compiles to frame.eval (patch from Amaury on trunk/pypy fork, thanks)
--HG--
branch : trunk
2010-06-10 10:56:14 +02:00
holger krekel
3b30c5b67a defer compilation to frame.eval (pypy overrides frame.eval and has its own compilation
of source code to bytecode)

--HG--
branch : trunk
2010-06-10 09:53:40 +02:00
Benjamin Peterson
610cde6f85 Interpret assignments while examining asserts corrects
fixes #105

--HG--
branch : trunk
2010-06-09 14:53:11 -05:00
holger krekel
add518e6b6 use new --junitprefix option for tox reporting
--HG--
branch : trunk
2010-06-09 16:35:50 +02:00
holger krekel
bc6ead1a3c introduce a new --junitprefix option to influence xml reporting.
also internally avoid some redundant code.

--HG--
branch : trunk
2010-06-09 16:18:47 +02:00
holger krekel
0c04577f9f fix issue104 properly xml-escape names in junitxml files
--HG--
branch : trunk
2010-06-09 15:27:45 +02:00
holger krekel
523704f890 make py.test.raises as-VAR be an ExceptionInfo object
but only initialize it after the block is finished.

--HG--
branch : trunk
2010-06-09 14:45:41 +02:00
holger krekel
6951da7da0 merge Ronny's changes, add some documentation and changelog entries
--HG--
branch : trunk
2010-06-09 14:26:08 +02:00
holger krekel
d83bf93154 less imports at function level, add a CHANGELOG entry - i guess
there are not many win32/python2.4 users anymore these days.

--HG--
branch : trunk
2010-06-09 12:07:12 +02:00
holger krekel
4437ecb385 make terminal tests pass on win32/python2.4 and update tox.ini
--HG--
branch : trunk
2010-06-09 12:01:13 +02:00
Ronny Pfannschmidt
d1c8209875 support using py.test.raises in context manager style
--HG--
branch : trunk
2010-06-09 10:50:00 +02:00
holger krekel
6231bb0da3 capture a concrete idea for easing platform-specific testing.
--HG--
branch : trunk
2010-06-08 12:29:15 +02:00
holger krekel
64388832d9 introduce a new request.applymarker() function and refactor
internally to allow for dynamically adding keywords to test
items.

--HG--
branch : trunk
2010-06-08 02:34:51 +02:00
holger krekel
d00b62e0f4 fix tox.ini
--HG--
branch : trunk
2010-06-07 23:23:24 +02:00
holger krekel
804dcd3521 some adjustments to make py.test --basetemp=XYZ work where
XYZ is a subdir the checkout which contains a conftest.py

--HG--
branch : trunk
2010-06-07 21:02:26 +02:00
holger krekel
c1d0fc9aaf add ignore_errors to local.remove()
--HG--
branch : trunk
2010-06-07 20:48:36 +02:00
Ronny Pfannschmidt
8ece058256 remove py._path.gateway.* - its weird and unused
--HG--
branch : trunk
2010-06-07 15:13:28 +02:00
holger krekel
10b8de060a fix py.code.compile to generate unique filenames
--HG--
branch : trunk
2010-06-06 19:08:22 +02:00
holger krekel
10baa7f8af fix python3 failure by making a relative import work
--HG--
branch : trunk
2010-06-05 16:10:17 +02:00
holger krekel
740a668f52 adding a tox file and a note in changelog
--HG--
branch : trunk
2010-06-05 15:59:11 +02:00
holger krekel
c56f4f9444 don't depend on (and don't actually use anymore) testing/__init__.py
--HG--
branch : trunk
2010-06-04 00:39:58 +02:00
holger krekel
bdd1006e06 don't print empty lines with junitxml file printing
--HG--
branch : trunk
2010-06-04 00:39:18 +02:00
holger krekel
46f72d9350 add a CHANGELOG entry for ronny's changes
--HG--
branch : trunk
2010-06-03 15:51:59 +02:00
Ronny Pfannschmidt
f8404be1b2 add a rootdir param to py.path.local.mkdtemp
--HG--
branch : trunk
2010-06-03 11:14:32 +02:00
Ronny Pfannschmidt
2e82ca5fde use tempdir.mkdtmp instead of mktmp + repeated tries for making tmpdirs
--HG--
branch : trunk
2010-06-03 10:59:24 +02:00
Ronny Pfannschmidt
a07e494554 add kwarg support to py.errpr.checked_call
--HG--
branch : trunk
2010-06-03 10:21:48 +02:00
holger krekel
75d80ca183 fix pyimport() bug on directories
--HG--
branch : trunk
2010-05-31 17:06:46 +02:00
holger krekel
395bee4bc0 fix name of fedora package, thanks thm
--HG--
branch : trunk
2010-05-29 09:15:50 +02:00
holger krekel
b66b5e2715 fix issue 57 - make --looponfail work with xpassing tests
--HG--
branch : trunk
2010-05-26 18:55:50 +02:00
holger krekel
73d9900844 add Meme's pytest-cov plugin to the plugin index page
--HG--
branch : trunk
2010-05-26 14:48:27 +02:00
holger krekel
6cc89c9fcf Added tag 1.3.1 for changeset 8b8e7c25a13c
--HG--
branch : trunk
2010-05-25 21:09:21 +02:00
holger krekel
2e36e2619f update plugin docs, restructure release announcement a bit
--HG--
branch : trunk
2010-05-25 21:01:43 +02:00
holger krekel
3042d1442a Added tag 1.3.1 for changeset d5eacf390af7
--HG--
branch : trunk
2010-05-25 20:47:00 +02:00
holger krekel
312238c023 update release announcement
--HG--
branch : trunk
2010-05-25 20:46:51 +02:00
holger krekel
ff2b893d31 fix for py3 exception printing logic
--HG--
branch : trunk
2010-05-25 17:24:24 +02:00
holger krekel
c953c7d313 fix issue102 by introducing a --maxfailures=NUM option
also print an informative line about "stopped/interrupted" test runs
near the end.

--HG--
branch : trunk
2010-05-25 16:52:09 +02:00
holger krekel
fbcf9ec543 merge changes
--HG--
branch : trunk
2010-05-25 16:55:30 +02:00
holger krekel
9173b60677 internal test runs: do inline_run() without io-capturing
as this nested capturing can leave open FDs which breaks
larger test runs.  also introduce an internal option "--lsof"
for checking the number of file descriptors

--HG--
branch : trunk
2010-05-25 12:24:51 +02:00
holger krekel
7fc7b4307b adding xfail example
--HG--
branch : trunk
2010-05-22 17:22:31 +02:00
holger krekel
545aab85f2 py-1.3.1 release prep and version bumping
--HG--
branch : trunk
2010-05-22 17:11:30 +02:00
holger krekel
fa074da5a9 when --runxfail is supplied also show tracebacks when running a test that
calls py.test.xfail

--HG--
branch : trunk
2010-05-22 17:08:49 +02:00
holger krekel
29a5b7452e * improve and test --tb=short reporting
* show --tb=short tracebacks for importing test modules

--HG--
branch : trunk
2010-05-22 16:18:24 +02:00
holger krekel
94ce5b0a5a refine and structure CHANGELOG
--HG--
branch : trunk
2010-05-22 14:13:01 +02:00
holger krekel
93712a3ce6 terser reporting header
--HG--
branch : trunk
2010-05-22 13:59:01 +02:00
holger krekel
6f697294b2 fix for python3 - class.__dict__ is now a dict_proxy which doesn't have setdefault() anymore.
--HG--
branch : trunk
2010-05-22 10:12:57 +02:00
holger krekel
7cba3a07af update docs: mention that py.test.xfail is there
--HG--
branch : trunk
2010-05-21 18:16:16 +02:00
holger krekel
4f7ef0b63f fix issue89 - allow py.test.mark decorators to be used with classes
(if you are using >=python2.6)
also allow to have multiple markers applied at class level
and test and fix a bug with chained skip/xfail decorators:
if any of the conditions is true a test will be skipped/xfailed
with a explanation which condition evaluated to true.

--HG--
branch : trunk
2010-05-21 18:11:47 +02:00
holger krekel
67ec87e7f9 note that issue99 is also fixed
--HG--
branch : trunk
2010-05-21 16:51:15 +02:00
holger krekel
578cba20d4 fix issue94 make reporting more robust against bogus source code
(and internally be more careful when presenting unexpected byte sequences)
also make py.code.Source accept a list of lines directly.

--HG--
branch : trunk
2010-05-21 16:42:46 +02:00
holger krekel
93f91c9607 unify handling of reportcharacters across resultlog/junitxml plugins
--HG--
branch : trunk
2010-05-20 14:35:13 +02:00
holger krekel
925f75088d fix 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.

--HG--
branch : trunk
2010-05-20 13:29:51 +02:00
holger krekel
eac0345689 fix wrong test invocation
--HG--
branch : trunk
2010-05-19 17:05:13 +02:00
holger krekel
20424a9c76 fix and test "-rP" option to show xpass-test ids
--HG--
branch : trunk
2010-05-19 16:52:03 +02:00
holger krekel
2229d2d947 revert 1735 - fix issue95 differently: just shift the offending zlib
import (and others) to happen when they are actually needed

--HG--
branch : trunk
2010-05-19 16:42:22 +02:00
holger krekel
c3bd29b490 fix issue95 - treat a failing pytest_genscript import
as non-critical, give a hint.

--HG--
branch : trunk
2010-05-19 16:22:23 +02:00
holger krekel
f552749de6 a crucial close() to prevent too-many-open-files
--HG--
branch : trunk
2010-05-18 21:25:20 +02:00
holger krekel
cf255cd643 some special handling of stdin capturing, unification, un-xfail the win32 test
--HG--
branch : trunk
2010-05-18 12:12:34 -07:00
holger krekel
10296faff1 for now don't test close(0) on windows - it hangs there
--HG--
branch : trunk
2010-05-18 11:43:22 -07:00
holger krekel
9f5e6f9761 simplify and unify FDCapture API and usage:
* FDCapture now takes care through the 'patchsys' option to
  also set sys.stdin/out/err - setfiles/unsetfiles methods removed -
  i doubt anybody uses this outside of py.test's own old usage.
* stdin also goes through FDCapture now.

--HG--
branch : trunk
2010-05-18 20:03:44 +02:00
holger krekel
c790288a5f fix issue98 - xdist documentation wrongly told to set pytest_option_X
where it is only option_X that is correct.

--HG--
branch : trunk
2010-05-18 18:45:12 +02:00
holger krekel
da097c9d67 deal gracefully with invalid file descriptors - don't capture the particular stream
--HG--
branch : trunk
2010-05-18 16:52:56 +02:00
holger krekel
4f5d7948f7 - try to fix the nightly failures by refining internal capturing mechanism
and adding tests, including a "lsof" test for making sure the number of
  open file descriptors does not increase.
- also move a py.io related logging test to testing/io

--HG--
branch : trunk
2010-05-18 16:01:58 +02:00
holger krekel
1a97c59439 fix test to account for earlier capfd skipping (on jython)
--HG--
branch : trunk
2010-05-18 09:54:04 +02:00
holger krekel
e71685736e fix issue96 - make capturing more resilient against KeyboardInterrupt
--HG--
branch : trunk
2010-05-17 19:00:39 +02:00
holger krekel
5876736890 tentative fix to py3's dependency on a filename->module->__loader__ chain
for executing inspect.findsource ...

--HG--
branch : trunk
2010-05-14 23:26:27 +02:00
holger krekel
f97e082543 fix test to work on jython and cpy
--HG--
branch : trunk
2010-05-14 15:25:24 +02:00
holger krekel
91880ffc19 adding three x-failing tests for issue88, issue93 and related issues
--HG--
branch : trunk
2010-05-14 12:02:43 +02:00
holger krekel
169d8d1e54 fix test to account for jython python file ending
--HG--
branch : trunk
2010-05-12 14:12:07 +02:00
holger krekel
11bf293972 merging again - seems i am using mercurial wrong or in some confused way ...
--HG--
branch : trunk
2010-05-12 14:14:05 +02:00
holger krekel
9cc6b602b9 remove duplicate and done issues
--HG--
branch : trunk
2010-05-12 11:23:31 +02:00
holger krekel
3d70917758 merge with issue-commits
--HG--
branch : trunk
2010-05-12 10:56:37 +02:00
holger krekel
0cebee2d24 bump version to 1.3.1a1 for now
--HG--
branch : trunk
2010-05-12 10:30:34 +02:00
holger krekel
379390a8aa remove code.new() function and store lines directly into linecache.cache instead.
This avoids the need for custom code objects, improving compatibility for jython
and pypy-c.

--HG--
branch : trunk
2010-05-11 22:54:04 +02:00
holger krekel
8ba2a98e11 allow to run py.test.cmdline.main() multiple times.
--HG--
branch : trunk
2010-05-11 19:56:22 +02:00
holger krekel
0f5ed3abc7 avoid helper functions showing up in py.test tracebacks
--HG--
branch : trunk
2010-05-11 17:43:56 +02:00
holger krekel
74b8fdf28a add an issue about py.test.config deprecation
--HG--
branch : trunk
2010-05-10 18:54:17 +02:00
holger krekel
f266c8f92f fix init-check to work also on pypy-c (armin around)
--HG--
branch : trunk
2010-05-09 12:27:04 +02:00
holger krekel
afdb928b12 update ISSUES
--HG--
branch : trunk
2010-05-09 08:43:28 +02:00
holger krekel
1706947edd fix some ReST issues
--HG--
branch : trunk
2010-05-05 22:04:53 +02:00
holger krekel
54afd26c7e fix typo
--HG--
branch : trunk
2010-05-05 21:53:36 +02:00
holger krekel
05ab5ad6d5 Added tag 1.3.0 for changeset eafd3c256e87
--HG--
branch : trunk
2010-05-05 21:35:24 +02:00
holger krekel
a127767da6 fix interpreter compat
--HG--
branch : trunk
2010-05-05 21:03:43 +02:00
holger krekel
a7d646c5e7 updating docs
--HG--
branch : trunk
2010-05-05 21:01:54 +02:00
holger krekel
87fcea7eb7 update release announcement
--HG--
branch : trunk
2010-05-05 20:19:02 +02:00
holger krekel
ee036223ce deprecate --report option in favour of a new shorter and easier to remember -r option: this takes a string argument consisting of any combination of 'xsfX'
Those letters basically correspond to the letters you see during terminal reporting.

--HG--
branch : trunk
2010-05-05 19:50:59 +02:00
holger krekel
325cb0aa49 add python3 classifier
--HG--
branch : trunk
2010-05-05 14:24:02 +02:00
holger krekel
c933ada7fb new --runxfail option to ignore xfail markers on functions
--HG--
branch : trunk
2010-05-04 13:02:27 +02:00
holger krekel
28150c7486 add unit-tests for xfail and refine xfail handling and reporting
--HG--
branch : trunk
2010-05-04 12:37:56 +02:00
holger krekel
dd7fd97810 add a terminalreporter.testid method
--HG--
branch : trunk
2010-05-04 12:37:52 +02:00
holger krekel
1a8b2838fa add new parameters:
xfail(run=False) will not run expected-to-fail tests
xfail(reason=True) will report the specified reason

--HG--
branch : trunk
2010-05-02 22:13:16 +02:00
holger krekel
82d4aae571 some internal fixes regarding the new required hook-finding prefix
--HG--
branch : trunk
2010-05-02 17:10:38 +02:00
holger krekel
fd473d4002 refine and test new hook registration, now it is called "pytest_addhooks"
similar to pytest_addoption and raises on bogus input.

--HG--
branch : trunk
2010-05-02 16:36:53 +02:00
holger krekel
45e10f4c48 rename pytest_ignore_collect_path to pytest_ignore_collect before release
--HG--
branch : trunk
2010-05-02 15:24:02 +02:00
holger krekel
3efb8028fb update changelog, install info and bum version to 1.3.0
rather than 1.2.2 because of the added features

--HG--
branch : trunk
2010-05-02 15:06:20 +02:00
holger krekel
b8247bc91e update implementation ISSUES, add one for session/refinements/a collection crash
--HG--
branch : trunk
2010-04-30 20:03:57 +02:00
holger krekel
2630a452b4 fixing and adding to CHANGELOG
--HG--
branch : trunk
2010-04-30 09:53:26 +02:00
Ronny Pfannschmidt
b3ce06bbf9 add close method to DontReadFromInput so multiprocessing can close it
--HG--
branch : trunk
2010-04-29 19:46:43 +02:00
holger krekel
962d0fe2be introduce new pytest_pycollect_makemodule(path, parent) hook for
allowing customization of the Module collection object for a matching test module.

--HG--
branch : trunk
2010-04-29 16:53:29 +02:00
holger krekel
811408959f introduce a new pytest_ignore_collect_path(path, config) hook -
returning a true value will prevent considering the path for collection
The hook is called for both files and directory paths.

--HG--
branch : trunk
2010-04-29 16:20:55 +02:00
holger krekel
25ed74a77a refine to allow more characters for instance reprs like it was before
--HG--
branch : trunk
2010-04-29 15:52:59 +02:00
holger krekel
5ece3858e4 introduce new py.io.saferepr for printing the 'repr' of an object safely
and without consuming too much space

--HG--
branch : trunk
2010-04-29 14:17:07 +02:00
holger krekel
1c1623885f fix a py3k related skip - py.io.TextIO on py3k should probably
not allow to write bytes to it.

--HG--
branch : trunk
2010-04-29 10:50:20 +02:00
holger krekel
5dc66bb4ca make py.io.ansi_print and py.io.get_terminal_width() directly available.
--HG--
branch : trunk
2010-04-29 10:49:50 +02:00
holger krekel
030548bc73 expose py.code._reinterpret functions so that pypy and internal
uses don't need to go through internal implementation imports

--HG--
branch : trunk
2010-04-29 01:20:56 +02:00
Benjamin Peterson
78d67c007b be more robust about bad std stream encodings
--HG--
branch : trunk
2010-04-28 17:19:49 -05:00
Benjamin Peterson
d93016d85f remove the unused return value of fnmatch_lines
--HG--
branch : trunk
2010-04-28 17:12:38 -05:00
Benjamin Peterson
f2be437daa some py3 encoding fixes
Also return True if fnmatch succeeds.

--HG--
branch : trunk
2010-04-28 16:51:36 -05:00
holger krekel
22a50a5b88 * various jython related fixes.
* more care for print-errors including unicode-encoding related errors.

--HG--
branch : trunk
2010-04-28 15:24:38 +02:00
holger krekel
37cdf17e0e fix some py3 issues, particularly for 3.1.2 which has truncate(0) not change the file position
so that a seek(0) is needed in addition.

--HG--
branch : trunk
2010-04-28 13:22:05 +02:00
holger krekel
78d33a2f28 * rather expose internal exceptions under py.test.ACTION.Exception
with ACTION being skip, fail, exit, raises.
* move and refine test_outcome.py tests into runner tests

--HG--
branch : trunk
2010-04-28 08:42:56 +02:00
holger krekel
d5e463605e * properly expose and document runtest-protocol related Exceptions
and move all definitions to the runner plugin for now.

* also move EXIT codes to session.py, obsoleting outcome.py alltogether.

--HG--
branch : trunk
2010-04-27 21:13:09 +02:00
holger krekel
0d0a7b7fec strike unused (buggy) keyword param
--HG--
branch : trunk
2010-04-27 17:53:07 +02:00
holger krekel
f691292aaa refining changelog + draft release announcement
--HG--
branch : trunk
2010-04-27 16:37:30 +02:00
holger krekel
ed7a2d2da3 refine/fix isimportable-logic and ensure that 'tmpdir' has a python-importable name
--HG--
branch : trunk
2010-04-27 16:10:25 +02:00
holger krekel
8131f5bdc0 (fixes issue83) don't try to import conftest from an invalid package path, refine path.pyimport() logic
--HG--
branch : trunk
2010-04-27 15:49:13 +02:00
holger krekel
c8d78177b9 (fixes issue85) correctly write non-ascii test output to junitxml files, refine some internal methods for it
--HG--
branch : trunk
2010-04-27 15:15:43 +02:00
holger krekel
f6a04b92d2 fix unicode issues (port of pypy/py repo changeset r72526 by Armin)
--HG--
branch : trunk
2010-04-27 12:23:13 +02:00
holger krekel
7084313408 factor out session main loop so that distribute testing can make use of it
--HG--
branch : trunk
2010-04-26 17:56:39 +02:00
Benjamin Peterson
7629b8fda7 make test source syntax valid
--HG--
branch : trunk
2010-04-23 20:49:00 -05:00
Benjamin Peterson
1771cb0af8 fetch code object compatibly
--HG--
branch : trunk
2010-04-23 20:43:49 -05:00
Benjamin Peterson
d1b45ef3d4 add a helper to get a function's code
--HG--
branch : trunk
2010-04-23 20:39:40 -05:00
Benjamin Peterson
f16d54f9a8 merge main
--HG--
branch : trunk
2010-04-23 20:28:32 -05:00
Benjamin Peterson
d909aead4e provide encoding to dupfile() for py3
--HG--
branch : trunk
2010-04-23 20:27:34 -05:00
holger krekel
0b24a70279 this should test and fix the same issue that was committed in
the pypy svn-repo as r72534

--HG--
branch : trunk
2010-04-23 19:28:41 +02:00
holger krekel
b3a05b545e another couple of checks on jython, still some problems
--HG--
branch : trunk
2010-04-23 19:05:22 +02:00
holger krekel
221ac3e466 a couple of more mostly jython-related fixes
--HG--
branch : trunk
2010-04-23 13:29:28 +02:00
holger krekel
2ee6653ff7 update distribute
--HG--
branch : trunk
2010-04-23 12:31:11 +02:00
hpk@tannit.openend.se
4337702a6a fixes for testrun on jython
--HG--
branch : trunk
2010-04-23 12:05:29 +02:00
holger krekel
ff90b1b4fb fix version numbers
--HG--
branch : trunk
2010-04-22 16:51:42 +02:00
holger krekel
85d35f7418 introduce an experimental approach for allowing dynamic addition of hooks from plugin. Plugins may register new hooks by implementing the new
pytest_registerhooks(pluginmanager)

and call

    pluginmanager.registerhooks(module)

with the referenced 'module' object containing the hooks.

The new pytest_registerhooks is called after pytest_addoption
and before pytest_configure.

--HG--
branch : trunk
2010-04-22 11:57:57 +02:00
holger krekel
cbb4c0dadc use taskkill cmdline for jython/win32 but skip test on jython because it does not return a subprocess PID
--HG--
branch : trunk
2010-04-21 06:23:19 -07:00
holger krekel
c10f0c2c36 merge in fixes
--HG--
branch : trunk
2010-04-21 14:49:38 +02:00
holger krekel
6e84b487ca robustify a check to not randomly fail
--HG--
branch : trunk
2010-04-21 14:47:44 +02:00
holger krekel
061f4c1515 robustify check
--HG--
branch : trunk
2010-04-21 14:46:41 +02:00
holger krekel
fe34a8a15a a couple of more fixes/refinements for getting py.test to run better on jython/win32
--HG--
branch : trunk
2010-04-21 03:50:03 -07:00
holger krekel
5715bbd6f5 refining the win32 checks some further
--HG--
branch : trunk
2010-04-20 20:08:52 +02:00
holger krekel
10f6c3a432 fix check to work when there is no jython
--HG--
branch : trunk
2010-04-20 10:48:49 -07:00
holger krekel
536252cb2e refine win32 checks to also work on top of jython/win32
--HG--
branch : trunk
2010-04-20 10:45:41 -07:00
holger krekel
7cd899fd3c add capturelog generated plugin text
--HG--
branch : trunk
2010-04-19 14:44:11 +02:00
holger krekel
e45cd7e35e fix issue86 - point to pytest-xdist plugin for looponfailing feature.
--HG--
branch : trunk
2010-04-14 15:05:39 +02:00
holger krekel
75a6bf2395 fix issue78 - strike superflous "import sys" that cause unbound
local var errors.

--HG--
branch : trunk
2010-04-14 13:30:12 +02:00
holger krekel
1d28dcf140 add links to new plugins
--HG--
branch : trunk
2010-04-12 16:08:12 +02:00
Benjamin Peterson
d994c51ccd actually skip doc tests if pygments is not available
--HG--
branch : trunk
2010-03-09 16:18:27 -06:00
Benjamin Peterson
fe95ad0aa6 fix typo
--HG--
branch : trunk
2010-03-03 15:54:39 -06:00
holger krekel
c75d0faa6f remove cover and xmlresult plugins as they are not easily installable
and lead to frustration (thanks again to prologic for getting back on it)

--HG--
branch : trunk
2010-02-10 16:11:19 +01:00
holger krekel
3608d722fa fix docs to not point to a downloadable plugin if the
plugin is external (thanks to prologic for feedbacking
on this confusion)

--HG--
branch : trunk
2010-02-10 14:21:49 +01:00
holger krekel
18b5ddc4dd note down two issues after having helped prologic - also related to
earlier discussions with ronny and others.

--HG--
branch : trunk
2010-02-09 18:32:17 +01:00
holger krekel
a2fbe31a26 Added tag 1.2.1 for changeset c143a8c8840a
--HG--
branch : trunk
2010-02-08 17:39:55 +01:00
holger krekel
222a08ec03 going for the 1.2.1 release
--HG--
branch : trunk
2010-02-08 16:39:29 +01:00
holger krekel
c7326f1949 fix a pdb problem when dropping to a "raises" related failure
--HG--
branch : trunk
2010-02-08 14:17:01 +01:00
holger krekel
3bca6be46d add some issues
--HG--
branch : trunk
2010-02-07 02:55:50 +01:00
holger krekel
13488dd540 if a funcarg is misspelled/missing, hint at using "--funcargs"
--HG--
branch : trunk
2010-02-07 02:15:47 +01:00
holger krekel
ac1eed545c nice-ify --funcarg reporting, show paths instead of useless python reprs
--HG--
branch : trunk
2010-02-07 02:09:14 +01:00
holger krekel
e09e7148a3 fix docstring
--HG--
branch : trunk
2010-02-07 01:56:43 +01:00
holger krekel
d163d92b33 actually look into all non-dot subdirs for conftest.py files - recursive walk would be too heavy for large source trees but first-level subdirs are fine IMO. Note that prior to py.test 1.0 doing this "look-ahead" was not easily doable because it was hard to avoid global state in conftest.py, this is not true anymore - so i feel ok telling people to cleanup their conftest files if they get problems (you can imagine people doing all kinds of things at global conftest.py module scope, can't you?)
--HG--
branch : trunk
2010-02-06 22:37:04 +01:00
holger krekel
105ed6dcaa fix this test
--HG--
branch : trunk
2010-02-05 22:57:46 +01:00
holger krekel
a3d15b2c60 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

--HG--
branch : trunk
2010-02-05 22:50:41 +01:00
holger krekel
3234e6e978 add a --funcargs option showing available funcargs
--HG--
branch : trunk
2010-02-04 23:45:07 +01:00
holger krekel
02c129df7a fix a test
--HG--
branch : trunk
2010-02-04 16:13:30 +01:00
holger krekel
f95877a09b show a short and nice traceback for funcarg lookup errors
--HG--
branch : trunk
2010-02-04 16:01:02 +01:00
holger krekel
7bd60b5abb check and load test*/conftest.py early from anchors -
this makes it a bit more convenient to have command line options
available from a root directory of a project that does not
directly contain a conftest.py

--HG--
branch : trunk
2010-02-04 12:26:53 +01:00
holger krekel
33d78f41b2 fix title to not mention distributed programming which
is separated out into execnet.

--HG--
branch : trunk
2010-02-02 11:32:21 +01:00
holger krekel
9d64d7e27a refine setup ordering some more - test and avoid a problem with funcarg setups where the
surrounding setup_module would fail, but the funcarg setup still be called (which might
assume that setup_module has been called so would raise a confusing error)

--HG--
branch : trunk
2010-01-28 15:36:27 +01:00
holger krekel
a2af204687 again addresses issue78 : we now call teardown also if setup raised a Skipped exception.
I also made sure, setup_module/class will only be called once - before they'd be call again
and again if they raise an error or a skip - for each test in their scope.

--HG--
branch : trunk
2010-01-28 14:20:58 +01:00
holger krekel
4d5ea7be43 install pygments for tests
--HG--
branch : trunk
2010-01-27 13:02:02 +01:00
holger krekel
98608611af closes #67 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
--HG--
branch : trunk
2010-01-27 12:52:19 +01:00
holger krekel
b18ab6e03b fix issue78 - now python-level teardown functions are now called even if the setup failed.
Important detail: if the setup raises a Skipped exception, teardown will not be called.  This helps
to avoid breaking setup_module/class that performs a skip - it would otherwise internally
be considered as a "successful" setup in order to have teardown called later.  I guess
it also makes sense to treat Skip specially because it is unlikely a teardown should be
called if a Skip was raised on setup.

In any case, failing setups and teardowns will be reported separately.

--HG--
branch : trunk
2010-01-27 12:09:30 +01:00
holger krekel
17bd875444 fail doc generation if pygments is not installed
--HG--
branch : trunk
2010-01-21 23:24:33 +01:00
holger krekel
aa6d2a37e6 upgrade apipkg.py to fix a potential recursive import issue
--HG--
branch : trunk
2010-01-21 20:06:50 +01:00
holger krekel
14feeb9ca1 fix doc links, bump to dev version
--HG--
branch : trunk
2010-01-21 19:34:42 +01:00
holger krekel
f7c562e492 better default for bogus terminal getdimensions() call, fixes issue63
--HG--
branch : trunk
2010-01-19 10:34:41 +01:00
holger krekel
53fc3204fb Added tag 1.2.0 for changeset 4fc5212f7626
--HG--
branch : trunk
2010-01-18 17:04:52 +01:00
holger krekel
6756416d69 add report_header_info to release announce
and remove some ignores/excludes from MANIFEST.in for
a less verbose installation experience

--HG--
branch : trunk
2010-01-18 17:00:22 +01:00
holger krekel
d3c0ff3a1f make sure we get an absolute path when writing the genscript file
--HG--
branch : trunk
2010-01-18 16:48:20 +01:00
holger krekel
30b756a1a2 add terminal plugin to overview page again
--HG--
branch : trunk
2010-01-18 16:26:26 +01:00
holger krekel
49f7972d48 some finalizing docs bit, regen plugin docs
--HG--
branch : trunk
2010-01-18 16:18:59 +01:00
holger krekel
1e76202b65 turn this into a black-box test
--HG--
branch : trunk
2010-01-18 13:16:58 +01:00
holger krekel
55fcc5a219 always directly use basename for tracebacks, independently from code.path
fixes issue77 although i guess it was already fixed before.

--HG--
branch : trunk
2010-01-18 12:12:18 +01:00
holger krekel
866255e1f5 pushing towards 1.2.0
--HG--
branch : trunk
2010-01-18 11:19:59 +01:00
holger krekel
0149771997 refine excludepath handling to treat entries with no path as matching
--HG--
branch : trunk
2010-01-18 03:04:20 +01:00
holger krekel
89068d9471 fix python2.4 issue
--HG--
branch : trunk
2010-01-18 02:01:16 +01:00
holger krekel
d483f18374 move rsync reporting out
--HG--
branch : trunk
2010-01-18 00:41:22 +01:00
holger krekel
95de17b652 refine tests and refine code to deal with new xdist semantics.
--HG--
branch : trunk
2010-01-17 23:23:02 +01:00
holger krekel
f5e9d91f7b fix deprecation warnings
--HG--
branch : trunk
2010-01-17 10:54:36 +01:00
holger krekel
09671eb6fc rename logxml plugin to junitxml
--HG--
branch : trunk
2010-01-16 23:33:26 +01:00
holger krekel
6f0db1d193 kill unused code
--HG--
branch : trunk
2010-01-16 19:41:05 +01:00
holger krekel
76e49b57bf fix test_importall to not stop on skipped plugins and fix the uncovered failure of genscript: standalone.py template is now safely importable
--HG--
branch : trunk
2010-01-15 18:45:06 +01:00
holger krekel
4a568f43fe get rid of the funccollector node, which nice-ifies names of funcarg-generated tests nodes, also test and fix one anomaly wrt to funcarg setups and instance uniqueness
--HG--
branch : trunk
2010-01-15 17:50:02 +01:00
holger krekel
ee2f292efa remove superflous building of a dict, preserve order for nodes that have identical file:lineno
--HG--
branch : trunk
2010-01-15 17:38:09 +01:00
holger krekel
1ff37207a2 another wish
--HG--
branch : trunk
2010-01-13 18:20:38 +01:00
holger krekel
7747655905 some issue soritng related to the 1.2 series
--HG--
branch : trunk
2010-01-13 18:17:24 +01:00
holger krekel
030986dcc4 reduce usage of the global py.test.config which maybe should die or become less global at some point (along with py.test.ensuretemp)
--HG--
branch : trunk
2010-01-13 18:04:58 +01:00
holger krekel
9da1ba40ed move down py/impl/XYZ to py/_XYZ
--HG--
branch : trunk
2010-01-13 17:15:54 +01:00
holger krekel
5c27076d32 flatten test directory hierarchy and merge smaller into larger files
--HG--
branch : trunk
2010-01-13 16:17:50 +01:00
holger krekel
40f41496d8 remove dist-testing and looponfail code from core. there remain some (pytest_runner particularly) tests that test both plain and dist modes which cannot be easily dis-entangled. food for thought.
--HG--
branch : trunk
2010-01-13 16:00:33 +01:00
holger krekel
d4f5073076 remove figleaf which now lives "outside"
--HG--
branch : trunk
2010-01-13 00:47:20 +01:00
holger krekel
59f3adb46b fix reqs2 to point to released execnet
--HG--
branch : trunk
2010-01-12 21:57:26 +01:00
holger krekel
d53572a710 introduce a new pytest_report_header(hook) hook to add additional test-run relevant information to the header of a test report.
--HG--
branch : trunk
2010-01-12 21:43:25 +01:00
Ronny Pfannschmidt
676081b87a remove the PickleChannel dependency for looponfail
--HG--
branch : trunk
2010-01-12 17:35:06 +01:00
holger krekel
a9fe84d9b9 adding a second requirements file which uses execnet-1.0.2
--HG--
branch : trunk
2010-01-12 16:15:07 +01:00
holger krekel
1b0d6296dd test and fix looponfailing wrt to a bug introduced with the cmdline/session startup cleanup.
--HG--
branch : trunk
2010-01-12 16:08:48 +01:00
holger krekel
8d9e0712be refine classname normalization for junit-xml
--HG--
branch : trunk
2010-01-12 01:35:50 +01:00
holger krekel
3296939eda fix sessionstart/sessionfinish handling at the slave side, set "session.nodeid" to id of the slave and make sure "final" teardown failures are reported nicely. fixes issue66.
--HG--
branch : trunk
2010-01-11 17:09:07 +01:00
holger krekel
b6909e61a0 fix rest syntax error (thanks fijal)
--HG--
branch : trunk
2010-01-11 14:33:18 +01:00
holger krekel
ba1451330e refine rsyncing and internal dir/transferal handling: don't transfer roots in a popen- no-chdir situation and only use one py._pydir everywhere
--HG--
branch : trunk
2010-01-11 14:30:50 +01:00
holger krekel
352e305431 fix and test bug: dist-testing now works again without execnet/pylib installed remotely. fixes issue65.
--HG--
branch : trunk
2010-01-10 23:52:23 +01:00
holger krekel
3a23baf484 add to changelog, remove docstring
--HG--
branch : trunk
2010-01-10 22:10:45 +01:00
holger krekel
99301a0dae (experimental) allow cmdline arguments to deep-point to a test, also remove virtually redundant session.getinitialitems() calls
--HG--
branch : trunk
2010-01-10 21:29:36 +01:00
holger krekel
3029aa6558 fix some "import py" test issues, and prevent "genscript" script from having dist-options
--HG--
branch : trunk
2010-01-10 20:45:37 +01:00
holger krekel
45c1517580 porting latest apipkg
--HG--
branch : trunk
2010-01-10 13:54:55 +01:00
holger krekel
3239bd250b avoid dependency on directory ordering
--HG--
branch : trunk
2010-01-03 18:19:52 +01:00
holger krekel
ff5c1b6611 add potential feature from py-dev discussion
--HG--
branch : trunk
2010-01-03 14:52:26 +01:00
holger krekel
018254a907 update issues, version numbers
--HG--
branch : trunk
2010-01-03 14:33:10 +01:00
holger krekel
9fcd108091 relax a test to pass on jython and fix install docs to include genscript standalone usage.
--HG--
branch : trunk
2010-01-03 14:19:31 +01:00
holger krekel
27aa14c20f fix python3 issues, add missing plugin docs
--HG--
branch : trunk
2010-01-03 13:27:06 +01:00
holger krekel
d541713dca re-arrange "py.test -h" command line option grouping and update some plugin docs.
--HG--
branch : trunk
2010-01-03 12:41:29 +01:00
holger krekel
0b2d9a5520 bumping version number: 1.2.0a1
--HG--
branch : trunk
2010-01-03 11:42:26 +01:00
holger krekel
c6c7d041b7 disable default inclusion of figleaf plugin because it caused test
failures wrt to capturing/logging interaction.  pytest_figleaf should
anyway better become its own externally living plugin.

--HG--
branch : trunk
2010-01-03 11:22:32 +01:00
holger krekel
1b34492108 vastly simplify and cleanup collection initialization by internally
introducing a RootCollector. Note that the internal node
methods _fromtrail and _totrail are shifted to the still internal
config._rootcol.fromtrail/totrail

--HG--
branch : trunk
2010-01-03 01:02:44 +01:00
holger krekel
eebeb1b257 enable doctest plugin by default, add a --doctest-glob option and some documentation, regen plugin docs.
--HG--
branch : trunk
2010-01-02 23:30:46 +01:00
holger krekel
56a936993c enhance figleaf setup, enabled by default now (requires --figleaf). Generalize internal ability to show "hints" at the end of "-h".
--HG--
branch : trunk
2010-01-02 22:48:53 +01:00
holger krekel
1b6391d814 higher timeout to accomodate slower execution environments
--HG--
branch : trunk
2010-01-02 18:32:11 +01:00
holger krekel
f3e62e38aa streamlined plugin loading: order is now setuptools, ENV, commandline
and setuptools entry point names are turned to canonical namees ("pytest_*")

--HG--
branch : trunk
2010-01-02 17:17:13 +01:00
holger krekel
a20e60aeae slightly refine invocation of py.test: use the py lib that we got invoked with,
does away with the need to not-chdir some tests

--HG--
branch : trunk
2010-01-02 11:57:42 +01:00
holger krekel
fd76cd8f41 remove/refine some doc strings. create popen-files with absolute paths.
--HG--
branch : trunk
2010-01-01 23:05:00 +01:00
holger krekel
e0dd171e45 fix standalone script generation on windows, make tests not do a chdir() so that distributed testing discovers the transferred lib
--HG--
branch : trunk
2010-01-01 21:54:27 +01:00
holger krekel
47df1e16b6 fix some failures introduced by the last commit, document new "pytestconfig" funcarg
--HG--
branch : trunk
2010-01-01 21:03:33 +01:00
holger krekel
f8b5951103 run py.* tools through "-c import py ; py.cmdline.py*" by default
and introduce --tools-on-path to force discovery of tools from PATH

--HG--
branch : trunk
2010-01-01 20:36:58 +01:00
holger krekel
b62978a88f internal: always use scripts found in the environment
--HG--
branch : trunk
2009-12-31 16:15:11 +01:00
holger krekel
2752168a58 introduce --confcutdir option to early-inhibit lookup of conftest files above a certain directory.
--HG--
branch : trunk
2009-12-31 15:10:32 +01:00
holger krekel
eb4249322e fix xml generation for skipped collections of tests
--HG--
branch : trunk
2009-12-31 11:50:01 +01:00
holger krekel
9fa6ca885a fixing invocation
--HG--
branch : trunk
2009-12-31 11:33:27 +01:00
holger krekel
587951966f adding a logxml plugin and a --xml=path option generating a junit-xml style result log. The xml result log can be parsed nicely by hudson.
Initial code was based on Ross Lawley's pytest_xmlresult plugin.

--HG--
branch : trunk
2009-12-31 11:25:07 +01:00
holger krekel
fa0c7b18bf move standalone script to become a plugin offering "--genscript",
adjust paths accordingly and add CHANGELOG entry.

--HG--
branch : trunk
2009-12-30 19:10:49 +01:00
Ralf Schmitt
1103f2ad62 make inspect.getsource work for standalone py.test by implementing a get_source method on our DictImporter.
--HG--
branch : trunk
2009-12-30 19:01:46 +01:00
holger krekel
a42d9eb9f6 fix some standalone-script running issues:
* standalone can run standalone tests
* exception handling is more careful with assuming valid filenames
* bits here and there

--HG--
branch : trunk
2009-12-30 18:11:00 +01:00
holger krekel
6495007aba refine tests to cache single-script and make standalone work with distributed testing.
--HG--
branch : trunk
2009-12-30 17:40:50 +01:00
holger krekel
94bc770786 fix accidentally broken pylib's own conftest.py
--HG--
branch : trunk
2009-12-30 17:11:48 +01:00
holger krekel
77667c11d2 a first go at testing schmir's generate_standalone_pytest script and
making it work on python2.4-3.1 + jython.

--HG--
branch : trunk
2009-12-30 17:08:45 +01:00
holger krekel
f5ea19858c deprecate direct definition of Directory, Module, ... in conftest.py's,
add some pytest collect related tests + some refinements.

--HG--
branch : trunk
2009-12-30 16:18:59 +01:00
holger krekel
d3b20e8d24 refine deprecations, move some over to test_deprecated_api
--HG--
branch : trunk
2009-12-30 14:07:20 +01:00
holger krekel
30bbf3b042 fix aimed at passing jstests functional tests: allow to have _fillfuncargs() called even for non-pycollect-object test-items.
--HG--
branch : trunk
2009-12-30 14:05:41 +01:00
holger krekel
89f178bf4d streamline svn test setup a bit, clear caches on setup-restore, hopefully will erase random failures with test_export.
--HG--
branch : trunk
2009-12-30 13:05:08 +01:00
holger krekel
4656bc4c97 deprecate use of 'disabled'
--HG--
branch : trunk
2009-12-30 12:13:38 +01:00
holger krekel
f02b84d528 update ISSUES some more, introduce duration to RunResult and a failing dist-testing termination test.
--HG--
branch : trunk
2009-12-30 11:37:46 +01:00
holger krekel
a15afb5e48 skip tests using 'capfd' funcarg but not having os.dup. cleanup issues and regen plugin docs.
--HG--
branch : trunk
2009-12-30 11:16:20 +01:00
holger krekel
ae63605ac0 generalize hook calling from collection nodes but stop short
of allowing general hooks in python test modules. It'd be
easily possible (a 1-line change) but considering it i refrained
from it because the collector API is a bit too low level.

pytest_generate_tests and funcarg factories have a limited
directly useful interface and are thus less confusing - those
are taking advantage of hook discovery in python test modules.

--HG--
branch : trunk
2009-12-30 10:42:01 +01:00
holger krekel
631dfe9f13 only consider matching conftest plugins for discovering hooks related to collection nodes.
--HG--
branch : trunk
2009-12-30 02:36:58 +01:00
holger krekel
9d01975c78 fix capturing to be more careful during teardown when a setup never happened (due to e.g. an error in user-provided runtest_setup code)
--HG--
branch : trunk
2009-12-30 00:11:27 +01:00
holger krekel
77b640d1b7 streamline some tests and overall reduce py.test.ensuretemp usage, note down issue about deprecation .
--HG--
branch : trunk
2009-12-29 22:26:03 +01:00
holger krekel
9be7d78fb1 some debug info aimed at helping to find out about a randomly failing test_export
test setup issue

--HG--
branch : trunk
2009-12-29 21:18:17 +01:00
holger krekel
c348cec481 make looponfailing a bit more robust against relative imports and changed directories - needs more work, probably.
--HG--
branch : trunk
2009-12-29 18:41:24 +01:00
holger krekel
79af98fc29 some testing hygene: move _reparse testing functionality to actual test support code, un-xfail a now passing test, reduce direct py.test.config usage aiming for deprecation.
--HG--
branch : trunk
2009-12-29 18:02:54 +01:00
holger krekel
7780e74016 fixup funcargs docs
--HG--
branch : trunk
2009-12-29 16:57:56 +01:00
holger krekel
db21cac694 cleanup py.test.* namespace, docstrings for improved pydoc and interactive usage.
use new apipkg __onfirstaccess__ feature to initialize the py.test namespace with the default plugins.  This, besides other good implications, means that you can now type:  pydoc py.test   or help(py.test)

--HG--
branch : trunk
2009-12-29 16:29:48 +01:00
holger krekel
080fd2880e simplify Config initialization
--HG--
branch : trunk
2009-12-29 14:13:12 +01:00
holger krekel
71e332c9c4 robustiy some randomly failing tests
--HG--
branch : trunk
2009-12-29 12:36:45 +01:00
holger krekel
425e4849f3 remove/reduce internal global state: py._com.registry is now fully contained and always instantiated from the py.test PluginManager class.
--HG--
branch : trunk
2009-12-29 12:36:17 +01:00
holger krekel
8737254a74 simplify pluginmanager, move plugin validation code to plugin, remove unused code
--HG--
branch : trunk
2009-12-29 10:59:01 +01:00
holger krekel
0361b73d75 remove defaultconfest.py and make PluginManager directly do early initialization of default plugins.
--HG--
branch : trunk
2009-12-29 10:26:51 +01:00
holger krekel
27bcd2dbda always import defaultconftest by python import path. strike some redundant code.
--HG--
branch : trunk
2009-12-28 17:49:46 +01:00
Ralf Schmitt
cc82f1601c add script to generate standalone py.test
--HG--
branch : trunk
2009-12-27 23:03:04 +01:00
holger krekel
9239d2dd06 another fix for windows
--HG--
branch : trunk
2009-12-25 02:16:29 -08:00
holger krekel
0bfd3819c8 fix typo
--HG--
branch : trunk
2009-12-25 10:56:03 +01:00
holger krekel
abb05d9384 fixing windows32 svn-testing issues
--HG--
branch : trunk
2009-12-25 01:47:43 -08:00
holger krekel
516cee2a94 windows fixes and print funcargs for keyboardinterrupt traces
--HG--
branch : trunk
2009-12-25 09:53:36 +01:00
holger krekel
d28838bbb6 try to fix windows path issue
--HG--
branch : trunk
2009-12-25 00:31:51 +01:00
holger krekel
88e61467f1 fixing and cleaning up some tests
--HG--
branch : trunk
2009-12-25 00:24:58 +01:00
holger krekel
6d46efa87a introduce --ignore option to ignore paths during collection
--HG--
branch : trunk
2009-12-24 22:23:45 +01:00
holger krekel
cd96e52144 temporarily adding files for playing with hudson
--HG--
branch : trunk
2009-12-24 21:47:01 +01:00
holger krekel
7864f6a4fd rather use newest execnet always
--HG--
branch : trunk
2009-12-24 20:35:18 +01:00
holger krekel
51a684f488 adding pip requirements file
--HG--
branch : trunk
2009-12-24 20:22:19 +01:00
holger krekel
f254b6f7c1 fixes to various tests, related to execnet automatic ID generation and other bits.
also lowering the version as "1.1.1post1" for now.  1.1.2 is still a bit off.

--HG--
branch : trunk
2009-12-24 19:43:14 +01:00
Benjamin Peterson
98863d1d01 capitialize I
--HG--
branch : trunk
2009-12-24 09:06:23 -06:00
holger krekel
8a5c3c0c69 cleanup bin-script creation, fix docs, add FAQ entry about py.test --version
--HG--
branch : trunk
2009-12-24 12:27:15 +01:00
holger krekel
1580b2c8da create version/interpreter differentiated py.test$VER for cpython, jython, pypy-c's, prepare 1.1.2 release
--HG--
branch : trunk
2009-12-23 19:58:52 +01:00
holger krekel
4b1db63b35 add some issues
--HG--
branch : trunk
2009-12-20 22:11:36 +01:00
holger krekel
ee1064b13c adding a link to the tutorial page
--HG--
branch : trunk
2009-12-20 22:10:44 +01:00
holger krekel
2ca39443a3 some doc fixes
--HG--
branch : trunk
2009-12-17 09:33:41 +01:00
holger krekel
7637a6ecda fix links, partially thanks to fijal
--HG--
branch : trunk
2009-12-11 14:19:18 +01:00
holger krekel
15ed79cb08 add note related to issue64
--HG--
branch : trunk
2009-12-08 00:06:50 +01:00
holger krekel
1f45edf121 note down dist-testing/execnet issue
--HG--
branch : trunk
2009-12-07 23:54:22 +01:00
holger krekel
213c9fa119 another issue
--HG--
branch : trunk
2009-12-07 13:27:24 +01:00
holger krekel
b44e04f28d adding a warning note, see also issue64.
--HG--
branch : trunk
2009-12-07 10:23:04 +01:00
holger krekel
f93c7e0cc4 update some small issues
--HG--
branch : trunk
2009-12-06 19:18:53 +01:00
holger krekel
90306e0089 skip tests properly
--HG--
branch : trunk
2009-12-06 19:18:44 +01:00
Benjamin Peterson
ec96ab5286 2.7's TextIO requires unicode
--HG--
branch : trunk
2009-12-06 11:47:41 -06:00
holger krekel
56c1391a16 fix keyword calling
--HG--
branch : trunk
2009-11-27 20:32:21 +01:00
holger krekel
bb755ae009 starting an ISSUES.txt with a conftest issue
--HG--
branch : trunk
2009-11-25 19:50:39 +01:00
holger krekel
31bba4e087 Added tag 1.1.1 for changeset 319187fcda66
--HG--
branch : trunk
2009-11-24 18:02:02 +01:00
holger krekel
9d5b313aad adjustments and fixes to test run, distribution files. thanks thm.
--HG--
branch : trunk
2009-11-24 15:16:58 +01:00
holger krekel
8c6593cc08 by default flush log writes to files
--HG--
branch : trunk
2009-11-24 02:48:13 -08:00
holger krekel
9652be0ac1 don't consider setuptools plugins if it is not installed.
--HG--
branch : trunk
2009-11-24 10:49:04 +01:00
holger krekel
79a9a99d1e small refinements/precision regarding execnet checks
--HG--
branch : trunk
2009-11-23 17:25:46 +01:00
holger krekel
ed03eef81b introduce plugin discovery through setuptools "pytest11" entrypoints
and refine execnet dependency handling.  Prepare 1.1 release

--HG--
branch : trunk
2009-11-23 17:20:36 +01:00
holger krekel
0e03ae1ee8 some forgotten doc fixes
--HG--
branch : trunk
2009-11-23 12:23:40 +01:00
holger krekel
8292ff7d0f fixing docs, adding draft announcement
--HG--
branch : trunk
2009-11-20 10:17:49 +01:00
holger krekel
bcede77e45 fix a flaky test
--HG--
branch : trunk
2009-11-20 10:04:40 +01:00
holger krekel
ecb19b751a add % as allowed char, condense CHANGELOG
--HG--
branch : trunk
2009-11-20 09:19:29 +01:00
holger krekel
d1dcf0fa92 be a bit more helpful by default regarding --report settings
--HG--
branch : trunk
2009-11-20 09:11:04 +01:00
holger krekel
0060869e79 move CHANGELOG back to root level, add entries
--HG--
branch : trunk
2009-11-20 00:12:39 +01:00
holger krekel
452ce50d7d fix compatibility issue with svnwc.update and put CHANGELOG to rootlevel
--HG--
branch : trunk
2009-11-20 00:12:06 +01:00
holger krekel
6d9e3ac686 adapt to new execnet.Group code (since execnet-1.0.0b4), strike superflous code
--HG--
branch : trunk
2009-11-19 23:13:29 +01:00
holger krekel
3adf6687c9 reintroduce py.test.cmdline.main() (alias for py.cmdline.pytest())
resolves issue #61

--HG--
branch : trunk
2009-11-19 23:13:28 +01:00
holger krekel
41a572ee1e a bit of padding under the logo
--HG--
branch : trunk
2009-11-17 13:45:27 +01:00
holger krekel
1a86d09da4 a few internal test related fixes as to run on a osx/no-execnet situation
--HG--
branch : trunk
2009-11-12 21:15:59 +01:00
holger krekel
f4ec2d1ecd improve deprecation, start changelog
--HG--
branch : trunk
2009-11-12 13:10:30 +01:00
holger krekel
a4a652af85 fix a bug with svnwc.listdir() not accepting a checker(versioned=...)
--HG--
branch : trunk
2009-11-12 13:09:27 +01:00
holger krekel
e6f2258409 Added tag 1.1.0 for changeset 60c44bdbf093
--HG--
branch : trunk
2009-11-05 17:51:35 +01:00
holger krekel
e0bca8fe51 fix up install docs and plugin docs for the final release
have CHANGELOG be a file containing links instead of a symlink
beause it causes issues with pip-install on some systems.

--HG--
branch : trunk
2009-11-05 17:46:14 +01:00
holger krekel
a5a94c4e8f largely improve and reshuffle docs, heading strongly towards a 1.1.0
--HG--
branch : trunk
2009-11-05 03:18:55 +01:00
holger krekel
b04a04cabd make py lib a self-contained directory again
- move and merge _py/ bits back to py/
- fixes all around

--HG--
branch : trunk
2009-11-04 21:34:07 +01:00
holger krekel
4dd6c7679d adding more alternatives as asked for by bluebird75
--HG--
branch : trunk
2009-11-02 14:52:54 +01:00
holger krekel
e584892c12 update and fix docs for installation
- rework installation
- add a new FAQ entry related to issue58 Windows/setuptools/multiprocess
- strike api/source references

--HG--
branch : trunk
2009-11-02 13:00:48 +01:00
holger krekel
6a82cdb37f fix jython issue, flexibilize sysexec params
--HG--
branch : trunk
2009-10-29 23:46:14 +01:00
holger krekel
609776bf26 trying a bit harder to get a realpath for the py lib because
execnet-rsync does not support working with links

--HG--
branch : trunk
2009-10-29 20:30:09 +01:00
holger krekel
30710a9cd6 fix windows32 issues, introduce a simplistic path.samefile for it, fix tests
--HG--
branch : trunk
2009-10-29 20:10:05 +01:00
holger krekel
c02719f44c rewrite nose-optional-call check, fixes python2.4 compat
--HG--
branch : trunk
2009-10-29 18:08:05 +01:00
holger krekel
7aee121bd7 move examples to doc directory
--HG--
branch : trunk
2009-10-29 17:54:37 +01:00
holger krekel
270ac2bc0d some release preps and cleanups
- update setup.py for release
- use distributes_setup on python3
- remove unncessary package_data
- remove execnet example

--HG--
branch : trunk
2009-10-29 12:45:12 +01:00
holger krekel
92d482069c moving py/bin to rootlevel bin/ and fixing tests
--HG--
branch : trunk
2009-10-29 16:53:02 +01:00
holger krekel
cc15685015 remove pyrest and _py/rest before first 1.1. release
--HG--
branch : trunk
2009-10-29 12:25:47 +01:00
holger krekel
455b0afdfe use new apipkg version
--HG--
branch : trunk
2009-10-29 11:47:12 +01:00
holger krekel
690ccaedc1 remove unnecessary builtin directory in favour of a single file
--HG--
branch : trunk
2009-10-28 22:00:38 +01:00
holger krekel
58e1693af0 fix a test-import issue occuring when there is a second 'testing' directory in PYTHONPATH or so.
--HG--
branch : trunk
2009-10-28 21:33:26 +01:00
holger krekel
69dd2d7a78 fix three python3 issues
--HG--
branch : trunk
2009-10-28 20:39:44 +01:00
holger krekel
86fc12dd15 resolves issue #59
resolves issue #48

Have the path.pyimport() helper raise an EnvironmentError if an
import of a given file returns a module that does not appear to
be coming from the actual path.  E.g. for a directory layout like this:

    a / test_whatever.py
    b / test_whatever.py

calling py.path.local("b/test_whatever.py").pyimport() will
fail if the other globally scoped test_whatever module was
loaded already.

--HG--
branch : trunk
2009-10-28 19:51:20 +01:00
Benjamin Peterson
1f01fafec7 can't use .format() on jython :(
--HG--
branch : trunk
2009-10-27 16:45:51 -05:00
Benjamin Peterson
92cab2bae7 merge trunk
--HG--
branch : trunk
2009-10-27 16:23:39 -05:00
Benjamin Peterson
37295dff0a hack around Jython's incorrect AST heriachy
--HG--
branch : trunk
2009-10-27 16:22:46 -05:00
holger krekel
84efdacfc0 enabling assertions with jython, fixing one .format occurence
to provide the setting for http://paste.pocoo.org/show/147361/

--HG--
branch : trunk
2009-10-27 21:51:05 +01:00
holger krekel
d2e6cd0523 first round of fixing jython compatibility issues, marking some tests as xfail-on-jython
--HG--
branch : trunk
2009-10-27 21:34:11 +01:00
holger krekel
33bd39053f using apipkg 1.0b2 snapshot version - adjusting/cleaning up some impl-detail accesses
--HG--
branch : trunk
2009-10-27 21:31:42 +01:00
holger krekel
cc3404b832 merged ronny's nose-compatibility hacks, i.e. nosestyle
setup_module() and setup() functions are supported.
added a few notes to changelog and documentation about it

--HG--
branch : trunk
2009-10-27 16:49:38 +01:00
holger krekel
2b1505c0f3 fix "py.cleanup -d" - add test and check to only remove empty dirs (!)
--HG--
branch : trunk
2009-10-27 16:03:14 +01:00
holger krekel
09ba42a1bb fix bug: a false xfail expression would erranonously report XPASS on failures
--HG--
branch : trunk
2009-10-27 12:02:40 +01:00
holger krekel
a161a865c8 remove deprecated parser.addgroup usage in favour of getgroup
--HG--
branch : trunk
2009-10-27 10:03:11 +01:00
holger krekel
fb159b0d40 removing some py.execnet references and moving scripts to execnet repo
--HG--
branch : trunk
2009-10-27 09:19:23 +01:00
Ronny Pfannschmidt
6f80c985fb support nose style argument-free setup/teardown functions
--HG--
branch : trunk
2009-10-23 16:17:06 +02:00
Ronny Pfannschmidt
5f3bdf2d0b nose plugin wont call setup functions that arent made for it
--HG--
branch : trunk
2009-10-23 16:16:28 +02:00
Ronny Pfannschmidt
8e5efa7d6d better tests for the nose plugin, support module level teardown
--HG--
branch : trunk
2009-10-23 15:27:59 +02:00
Ronny Pfannschmidt
82caacd633 nosetest plugin now supports fallback to module level setup
--HG--
branch : trunk
2009-10-23 15:11:53 +02:00
holger krekel
6c2b1c4363 refine naming, API and docs for py.test.mark mechanism - now contained in pytest_mark plugin
--HG--
branch : trunk
2009-10-22 20:57:21 +02:00
holger krekel
861f34fe90 use new marking idioms, simplify generalized skipping implementation
--HG--
branch : trunk
2009-10-22 18:37:24 +02:00
holger krekel
4a76c096da extend and refine test marking
- allow to mark tests via a "pytestmark" name at class/module level.
- make combined positional args of marker calls available via an _args argument

--HG--
branch : trunk
2009-10-22 15:21:58 +02:00
holger krekel
9ac4faf3af don't visit '_' attributes on python objects for calling hooks
--HG--
branch : trunk
2009-10-21 18:44:12 +02:00
holger krekel
118eebb190 cleanup: move creation of python colitems to a default pytest_pycollect_makeitem hook impl
--HG--
branch : trunk
2009-10-21 18:42:40 +02:00
holger krekel
9910db2ca6 player nicer for missing parent Module objects for a collected function (bug triggered by oejskit)
--HG--
branch : trunk
2009-10-20 16:38:12 +02:00
Ronny Pfannschmidt
fabd967595 flush looponfail output to get around line-buffering
--HG--
branch : trunk
2009-10-18 22:30:18 +02:00
holger krekel
c38bb72205 reshuffle/refine option grouping, introduce "terminal reporting options"
--HG--
branch : trunk
2009-10-17 17:43:59 +02:00
holger krekel
80f3e33e41 deprecate addgroup / allow ordering of option groups
--HG--
branch : trunk
2009-10-17 17:43:33 +02:00
holger krekel
3795b08e95 add --report cmdline option, shift refined xfailed and skipped reporting to skipping plugin
--HG--
branch : trunk
2009-10-17 17:42:40 +02:00
holger krekel
eab7e039eb streamline pluginmanager api and test/beautify printing of plugins with --trace
--HG--
branch : trunk
2009-10-17 12:56:59 +02:00
holger krekel
6f5918f03b fix formatting of session log output
--HG--
branch : trunk
2009-10-15 23:14:51 +02:00
holger krekel
d8b9b5f1c8 - make importorskip static at py.test.importorskip because it's
used for conditional plugin loading
- fix case where xfail is defined at module/class level
- fixes and improvements to docs, correct links to plugins
- use new skip facilities here and there

--HG--
branch : trunk
2009-10-15 20:10:06 +02:00
holger krekel
3ca770b420 generalize skipping
- rename pytest_xfail to pytest_skip
- dynamic "skipif" and "xfail" decorators
- move most skipping code to the plugin

also coming with this commit:
- extend mark keyword to accept positional args + docs
- fix a few documentation related issues
- leave version as "trunk" for now

--HG--
branch : trunk
2009-10-15 16:18:57 +02:00
holger krekel
5e21e39125 resolve issue 54
triggered by @haypo's issue and patch the
process.cmdexec function now always uses
subprocess under the hood. Also fixed
some 3k related encoding issues.

--HG--
branch : trunk
2009-10-14 23:54:01 +02:00
holger krekel
df8aedba47 adding the console-runtest helper as discussed on py-dev
--HG--
branch : trunk
2009-10-12 11:28:47 +02:00
holger krekel
1bdc0896ca introduce "-d" to py.cleanup
--HG--
branch : trunk
2009-10-12 11:24:41 +02:00
holger krekel
90f39426b4 fix some tests after the py/_py split
--HG--
branch : trunk
2009-10-09 15:26:46 +02:00
holger krekel
f10bfbb7e5 resolves #59 - robustify unittest collection
--HG--
branch : trunk
2009-10-09 15:09:26 +02:00
holger krekel
a603021757 ignore more dirs and files
--HG--
branch : trunk
2009-10-08 13:38:31 +02:00
holger krekel
c15a1b698c forgot to commit the verbatim copy of apipkg in _py/apipkg.py
--HG--
branch : trunk
2009-10-05 02:22:48 +02:00
holger krekel
6e11f8cd2a * remove unused py._thread namespace, rewrite the one usage
* remove unused py/test/web directory

--HG--
branch : trunk
2009-10-03 19:57:48 +02:00
holger krekel
5791c06bf2 rewrote the initpkg mechanism and moved py lib implementation files to
_py/...  with py/__init__.py containing pointers into them

The new apipkg is only around 70 lines of code and allows
us to get rid of the infamous "py.__." by regular non-magical
"_py." imports. It is also available as a separately installable
package, see http://bitbucket.org/hpk42/apipkg

--HG--
branch : trunk
2009-10-03 01:47:39 +02:00
holger krekel
db1ff48996 * use the MIT license for the py lib
* bump version to prospective 1.1.0b1
* strike some unused code from initpkg

--HG--
branch : trunk
2009-10-03 01:11:04 +02:00
holger krekel
1f29529a24 * don't add distributed command line options when 'execnet' is not
installed, report a nice message.

* fix tests and code to work with non-existing execnet

* point execnet doc to the new package

--HG--
branch : trunk
2009-10-02 22:29:22 +02:00
holger krekel
ab9f6a75ad remove py.execnet, substitute py.execnet usages with "execnet" ones.
--HG--
branch : trunk
2009-10-02 16:58:57 +02:00
holger krekel
496e3b1138 adding internal repr for debugging
adding an example for generating multi-args/multi python tests

--HG--
branch : trunk
2009-09-30 18:36:04 +02:00
holger krekel
aed66120a2 fix typo, add ronny to authors, normalize email addresses
--HG--
branch : trunk
2009-09-30 17:59:03 +02:00
holger krekel
5914277f92 internally rename "provider" to "factory" to be consistent
with documentation.

--HG--
branch : trunk
2009-09-30 12:59:47 +02:00
holger krekel
98b2300266 fix cached_setup to deal properly for test_functions
with multiple args.  closes #50

--HG--
branch : trunk
2009-09-30 12:52:40 +02:00
Ronny Pfannschmidt
2986c5dc74 simplify serializer tests
* use generate_tests to generate the simple non-string checks
* get rid of the TestSerializer class

--HG--
branch : trunk
2009-09-28 23:43:38 +02:00
Benjamin Peterson
7466516673 the check_sequence name is more specific
--HG--
branch : trunk
2009-09-28 15:55:09 -05:00
Ronny Pfannschmidt
2c523cd0d6 enhance the serializer tests
* use generate_tests hook to generate the serialize deserialize combinations
* add dump/load funcargs to simplify the tests

--HG--
branch : trunk
2009-09-28 22:46:32 +02:00
Ronny Pfannschmidt
40e91dcd85 add separate test for the serializer bigint fail
--HG--
branch : trunk
2009-09-28 22:42:36 +02:00
Benjamin Peterson
3d2975f38e support floats
--HG--
branch : trunk
2009-09-26 18:26:32 -05:00
Benjamin Peterson
c3fd7f0247 don't need to import py
--HG--
branch : trunk
2009-09-26 14:22:01 -05:00
Benjamin Peterson
cb5bd868d9 use default argument
--HG--
branch : trunk
2009-09-26 14:21:36 -05:00
Benjamin Peterson
0f96be372d clean up unused compatibility code
--HG--
branch : trunk
2009-09-26 14:20:36 -05:00
Benjamin Peterson
4d598370b4 test cross version serialization by launching subprocesses; much cleaner!
--HG--
branch : trunk
2009-09-26 12:35:24 -05:00
holger krekel
8f69d23f18 merging jarko'S fixes, resolves issue #45, resolves issue #46
--HG--
branch : trunk
2009-09-23 19:43:43 +02:00
Benjamin Peterson
1e71a5c392 Add a simple (hopefully) cross-python marshaller
Will rewrite the tests soon...

--HG--
branch : trunk
2009-09-22 21:08:40 -05:00
Benjamin Peterson
b3ca12d435 update docstring
--HG--
branch : trunk
2009-09-22 21:07:50 -05:00
Benjamin Peterson
d80f37f14a add changelog entry for last commit
--HG--
branch : trunk
2009-09-22 21:07:07 -05:00
Benjamin Peterson
8af3ede092 allow a path to explicity given for py.lookup
--HG--
branch : trunk
2009-09-22 21:04:25 -05:00
holger krekel
6ddea4a1bc visit() now returns paths in depth-first order. fixes issue #47
--HG--
branch : trunk
2009-09-22 19:13:33 +02:00
holger krekel
e3b34c9da3 * allowing arbitrary keys for xspecs but adding some sanity checks to xspec-parsing and makegateway.
* fixing a python3 IO issue - we need to retain sys.stdout/stdin
  references to keep the underlying byte stream open.

--HG--
branch : trunk
2009-09-22 18:40:20 +02:00
Samuele Pedroni
1b97d06a09 (micke, pedronis)
teach the resultlog plugin about the xfail tweaked outcomes

--HG--
branch : trunk
2009-09-17 15:31:35 +02:00
Jurko
9fd1367845 Corrected the constructed system path value (broken by the env.cmd, env.sh & env.py file move in 4abc620bb044).
--HG--
branch : trunk
2009-09-12 00:35:57 +02:00
Jurko
62a4cf68e8 Fixed a typo in error.py causing it to fail on Windows.
--HG--
branch : trunk
2009-09-12 00:16:13 +02:00
Benjamin Peterson
81062c5e2f compiling AST to code is new in python 2.6
--HG--
branch : trunk
2009-09-11 15:24:43 -05:00
holger krekel
47bad98c07 * various cleanups and detailed doc string for gateway_base module
* remove old multi-file-send mechanism/tests now that
  only gateway_base is send to the other side.
* adding some (c) notices where i am pretty sure about them.

--HG--
branch : trunk
2009-09-11 16:26:19 +02:00
holger krekel
d4d0226058 added another funcarg example i had lying around
--HG--
branch : trunk
2009-09-11 12:05:06 +02:00
holger krekel
22c1ad9f7b fix a bug with funcarg setup and remove XXX comment because "scope=module" now would work but leaving it as session for now.
--HG--
branch : trunk
2009-09-09 23:07:42 +02:00
holger krekel
6d84da39e4 some doc about the experiemntal pytest_gwmanage_newgateway hook.
and use process-scope for execnet test funcargs because
of weird setup/teardown issues when running distributedly itself.

--HG--
branch : trunk
2009-09-09 20:45:51 +02:00
holger krekel
5df58c619d * move gateway management code to py/test/dist because it's not clear
how generally useful it is.
* provide pytest_dist_makegateway(txspec) hook so that plugins
  can add their own interpretation/keywords.

--HG--
branch : trunk
2009-09-09 20:12:03 +02:00
holger krekel
8ea2364039 ups, forgot to add a neccessary file.
--HG--
branch : trunk
2009-09-09 15:36:53 +02:00
holger krekel
b70c7a209d * moving execnet tests to funcarg-style, some cleanup
* slight refinement to FAQ license topic

--HG--
branch : trunk
2009-09-08 10:10:36 +02:00
holger krekel
f9eadc6440 relicense to LGPL, add an FAQ entry reasoning about it.
--HG--
branch : trunk
2009-09-08 09:57:19 +02:00
holger krekel
0f29b503ef monkeypatch, doc, apiwarn, deprecation fixes
--HG--
branch : trunk
2009-09-07 17:53:50 +02:00
holger krekel
29d437489d some fixes to support Jython better
--HG--
branch : trunk
2009-09-07 14:59:26 +02:00
holger krekel
3c3002ccd5 regen setup.py and docs so that "python3 setup.py build" maybe works if setuptools does
--HG--
branch : trunk
2009-09-06 17:17:37 +02:00
holger krekel
c8119d89b6 move test files out of py lib proper
* separate all tests from plugins
* simplify implicit inclusion of plugins under test
* have test_initpkg perform direct checks instead of yielding tests
* fix example tests for 3k

--HG--
branch : trunk
2009-09-06 16:59:39 +02:00
holger krekel
5cf27098cf execnet cleanup/refinements: avoid creating a shell for each subprocess
* introduce HostNotFound, raised for Socket and SshGateways
* factored out basic tests, cleaned up existing tests
* removed sshgateway identity argument which was deprecated in 1.0

--HG--
branch : trunk
2009-09-06 13:38:21 +02:00
holger krekel
734a40eb28 seems like compile is way slower than just parser.suite so
we try to see if it's available (only jython doesn't have it)

--HG--
branch : trunk
2009-09-06 12:35:52 +02:00
holger krekel
518194537e * refactor some setup/teardown/ensuretemp usages to use funcargs
* introduce monkeypatch.syspath_prepend for safe monkey patching of module import path
* fix monkeypatch naming

--HG--
branch : trunk
2009-09-05 23:21:58 +02:00
holger krekel
783c714a2c get py.test to run at least basically on top of jython
* allow and document calling of monkeypatch.undo() from a test
* default to 'sys' on platforms there no 'os.dup' is available
* use "compile" to perform "tryparsing" checks

--HG--
branch : trunk
2009-09-05 16:54:52 +02:00
holger krekel
7ab98c1b25 * delete or text files to hacking/ directory.
* split license file into authors and license file, minor fixes.
* minor unicode fixes

--HG--
branch : trunk
2009-09-05 16:09:44 +02:00
holger krekel
bde56a8246 * fixing lots of remaining 3k compatibility issues, mostly with py.test itself.
* removing very old import-tests that IIRC relate to a time when there
  was a custom import hook in use.

* basically py.test internal tests pass now except py3/py2 distributed
  testing tests

--HG--
branch : trunk
2009-09-04 21:47:49 +02:00
holger krekel
1e51844519 introduce py.builtin._tryimport to try importing modules in a row, fix a few places
--HG--
branch : trunk
2009-09-04 19:08:10 +02:00
holger krekel
32e2bf7d08 make xmlgen a single file + a single file test instead of a whole directory
--HG--
branch : trunk
2009-09-04 18:30:48 +02:00
holger krekel
a0d7ab2244 reviewing, refactoring, porting xml/html object/tree generation to work with 3k
--HG--
branch : trunk
2009-09-04 18:16:10 +02:00
holger krekel
6823fa634b various 3k related fixes and cleanups
removal of virtually unused py/rest/rst.py helpers

--HG--
branch : trunk
2009-09-04 18:15:41 +02:00
holger krekel
5851471009 fix remaining execnet 3k issues until all tests pass
--HG--
branch : trunk
2009-09-04 16:51:29 +02:00
holger krekel
45b98d4915 remove so-far superflous _getcode and pickle from builtins, some streamlining
--HG--
branch : trunk
2009-09-04 16:32:49 +02:00
Benjamin Peterson
99af33b26d I think this is supposed to be immutable
--HG--
branch : trunk
2009-09-03 17:14:12 -05:00
Benjamin Peterson
1c9760d123 fix xfail
--HG--
branch : trunk
2009-09-03 16:47:04 -05:00
Benjamin Peterson
86da34b874 add a helper to get a function's dictionary
--HG--
branch : trunk
2009-09-03 16:45:28 -05:00
Benjamin Peterson
499a982860 give code objects a filename in the replacement execfile
--HG--
branch : trunk
2009-09-03 16:38:15 -05:00
holger krekel
c7f11745cd * fix various remaining 3k issues until test_gateway.py passes with python3 py/bin/py.test
* we now wait on gateway initialization until we got a byte back after
  we sent the bootstrap

--HG--
branch : trunk
2009-09-02 21:05:08 +02:00
holger krekel
6c3e961bc5 * simplify stdout/stderr handling and modules and for now remove support
for directly stdout/stderr directly on remote_exec

--HG--
branch : trunk
2009-09-02 19:39:24 +02:00
holger krekel
73fc2f01f2 filter out and test exception printing
--HG--
branch : trunk
2009-09-02 19:05:34 +02:00
holger krekel
e30aeed876 * more tests and fixes for cross-python compatibility
* use byte-buffer files if available for io
* shift receivelock to gateway object
* kill dead code

--HG--
branch : trunk
2009-09-02 18:56:43 +02:00
holger krekel
5d2504df0a * simplify lock acquiration for received messages, review code
* try to fix seldomly occuring race condition with setcallback/receive and closing of channel

--HG--
branch : trunk
2009-09-02 15:45:59 +02:00
holger krekel
f636ed8ced * make Gateway interface more asymetric: remote_* methods
and  cleanup/atexit handling now live exclusively with the "InitiatingGateway"

* fix some cross-python io related handling

--HG--
branch : trunk
2009-09-02 14:31:48 +02:00
holger krekel
c1fcf9c4d8 * use py.builtin._getimself instead of getattr(..., '*self*') everywhere
* fix logging to work with 3k, implement buffering manually
* fix unicode capturing issue - re-introduce EncodedFile for <3K file writes

--HG--
branch : trunk
2009-09-01 16:10:21 +02:00
holger krekel
43b8bd7df7 * refactor gateway code and tests to live in fewer files, remove some lock usage
* move text files to a new "hacking" directory

--HG--
branch : trunk
2009-09-01 11:39:27 +02:00
holger krekel
54709bcae1 enable assertion reinterpretation on 3k
--HG--
branch : trunk
2009-08-31 20:06:55 +02:00
holger krekel
c791610998 * simplify and refactor path tests to use funcargs instead of the layered xunit-setup mess
* port py/path to pass 3.11
* added py.builtin._totext, _tobytes, _isbytes, _istext helpers

--HG--
branch : trunk
2009-08-31 19:51:25 +02:00
Benjamin Peterson
e336683cdb add test for conversion to string
--HG--
branch : trunk
2009-08-30 08:25:48 -05:00
Benjamin Peterson
749bfa46c4 convert argument to string
--HG--
branch : trunk
2009-08-30 08:25:40 -05:00
Ronny Pfannschmidt
318a935b89 fix module name reuse in execnet
--HG--
branch : trunk
2009-08-30 15:00:26 +02:00
Benjamin Peterson
04dd0810c3 no unbound methods in py3
--HG--
branch : trunk
2009-08-29 16:22:34 -05:00
Benjamin Peterson
ee17858cce get rid of usage of the new module
--HG--
branch : trunk
2009-08-29 16:12:06 -05:00
Benjamin Peterson
ee86950af4 use correct attribute to find the instance of a bound method
--HG--
branch : trunk
2009-08-29 16:07:48 -05:00
Benjamin Peterson
a051eb1a05 only use cmp() in 2.x
--HG--
branch : trunk
2009-08-29 16:02:59 -05:00
Benjamin Peterson
ad0c2edfd2 fix generators on python 3
--HG--
branch : trunk
2009-08-29 16:00:24 -05:00
Benjamin Peterson
e63abd631f add py.builtin.callable
--HG--
branch : trunk
2009-08-29 15:46:50 -05:00
Benjamin Peterson
1781347999 use py.builtin.execfile()
--HG--
branch : trunk
2009-08-29 15:36:27 -05:00
Benjamin Peterson
01848ca821 add py.builtin.execfile to __init__.py
--HG--
branch : trunk
2009-08-29 15:36:14 -05:00
Benjamin Peterson
39eac1be28 add a py.builtin.execfile helper
--HG--
branch : trunk
2009-08-29 15:34:24 -05:00
Benjamin Peterson
c95504e738 use py.builtin.builtins instead of import test
--HG--
branch : trunk
2009-08-29 15:18:21 -05:00
Benjamin Peterson
59892b8532 remove usage of the new module
--HG--
branch : trunk
2009-08-29 15:14:18 -05:00
Benjamin Peterson
b3e8b2f6ab handle Queue renaming
--HG--
branch : trunk
2009-08-29 15:10:40 -05:00
Benjamin Peterson
7a4bd92e33 DeprecationWarning is in the builtin namespace
--HG--
branch : trunk
2009-08-29 15:08:34 -05:00
Benjamin Peterson
711552e84c use print function
--HG--
branch : trunk
2009-08-29 15:08:26 -05:00
Benjamin Peterson
9af223e6cb fix typos in converting test_oldmagic
--HG--
branch : trunk
2009-08-29 14:54:15 -05:00
Benjamin Peterson
8a6a3183ae guard against tests trying to import this
--HG--
branch : trunk
2009-08-29 14:50:44 -05:00
Benjamin Peterson
9018fe40e3 fix syntax for py3
--HG--
branch : trunk
2009-08-29 14:50:29 -05:00
Benjamin Peterson
4369c65790 fix some broken things from syntax conversion
--HG--
branch : trunk
2009-08-29 14:39:55 -05:00
Benjamin Peterson
fb365e47dc make print write each argument individually
--HG--
branch : trunk
2009-08-29 14:39:37 -05:00
Benjamin Peterson
45a9aa536f fix need for py import
--HG--
branch : trunk
2009-08-29 14:16:54 -05:00
Benjamin Peterson
ee1747fcb4 make all syntax compatible with 3.1 and 2.5
--HG--
branch : trunk
2009-08-29 13:04:48 -05:00
Benjamin Peterson
6f4c6d36a4 allow file to be compiled on 2.5
--HG--
branch : trunk
2009-08-29 11:36:08 -05:00
Benjamin Peterson
78d0d4656b add a test which checks the syntax of the pylib on various python versions
--HG--
branch : trunk
2009-08-29 11:31:42 -05:00
holger krekel
b930565d56 * fix some syntax and 3k issues for py/path and py/process, tests only partially working
* have py.process.cmdexec return unicode/text (for now)
* rename py.builtin.basestring to _basestring

--HG--
branch : trunk
2009-08-29 16:40:03 +02:00
Benjamin Peterson
0f7a9e2da2 fix the rest of py/code tests on python 3
--HG--
branch : trunk
2009-08-29 09:37:56 -05:00
Benjamin Peterson
96ec12902d fix tests involving Queue
--HG--
branch : trunk
2009-08-29 09:02:20 -05:00
holger krekel
1dafcc6b37 fix py/io classes and tests to pass 3.1
introduce py.builtin._totext helper to make a 2k=unicode / 3k=str object, allow a string as data

--HG--
branch : trunk
2009-08-29 15:51:49 +02:00
holger krekel
d75f7b2dd7 merge the benjamins and my changes, accidentally caused a new remote head
--HG--
branch : trunk
2009-08-29 14:10:06 +02:00
Benjamin Peterson
c2d0c52086 replace iteritems() with items()
--HG--
branch : trunk
2009-08-29 07:03:19 -05:00
Benjamin Peterson
0014e65c1d fix interpreting is/is not/in/not in
--HG--
branch : trunk
2009-08-29 06:58:54 -05:00
holger krekel
fc3178a394 fixing builtin tests and print_ builtin
--HG--
branch : trunk
2009-08-29 13:47:10 +02:00
Benjamin Peterson
ac934bb2b6 only test View on 2.x
--HG--
branch : trunk
2009-08-28 20:28:09 -05:00
Benjamin Peterson
695c8038e0 new except syntax
--HG--
branch : trunk
2009-08-28 20:17:46 -05:00
Benjamin Peterson
e596d9df13 move the old assertion reinterpreting implementation to _assertionold.py
Also, seperate out some common code from the two.

--HG--
branch : trunk
2009-08-28 20:13:49 -05:00
Benjamin Peterson
130046d245 remove magic directories from install
--HG--
branch : trunk
2009-08-28 18:51:14 -05:00
Benjamin Peterson
e0e9953be2 implement assert debugging with builtin AST
--HG--
branch : trunk
2009-08-28 18:44:20 -05:00
Benjamin Peterson
3bdbb29c6f make the patched compile() work with AST
--HG--
branch : trunk
2009-08-28 18:39:51 -05:00
Benjamin Peterson
c23cc3656c fix location of magic AssertionError
--HG--
branch : trunk
2009-08-28 18:07:28 -05:00
holger krekel
783e6aeb4d temporary checking towards 3.1 compatibility
introduced some helpers to py.builtin namespace which need some review
after things begin to work more nicely

--HG--
branch : trunk
2009-08-28 19:16:15 +02:00
holger krekel
5e95feaf90 * add print_, exec_ and _reraise helpers for 2-3 compatible code
* consolidate builtins implementation to be compatible with >=2.3

--HG--
branch : trunk
2009-08-28 16:25:29 +02:00
holger krekel
91f90d27ee simplify broken-repr test for python2.4
--HG--
branch : trunk
2009-08-28 13:00:36 +02:00
holger krekel
d1932a30ed deprecate py.compat.doctest|subprocess|textwrap|...
(and for now pass through Python stdlib provided modules).

--HG--
branch : trunk
2009-08-27 21:12:55 +02:00
holger krekel
681d344eac deprecate py.magic.autopath() and finally remove py/magic directory.
--HG--
branch : trunk
2009-08-27 18:46:42 +02:00
holger krekel
13932b7f4b * deprecate py.magic.invoke/revoke in favour of
the new py.code.patch_builtins, py.code.unpatch_builtins

* deprecate py.magic.patch/revert

* deprecate py.magic.AssertionError in favour of py.code._AssertionError

* introduced pytest_assertion plugin.

--HG--
branch : trunk
2009-08-27 17:26:02 +02:00
holger krekel
e391662cff merge 1.0.x branch to trunk, fix doc link
--HG--
branch : trunk
2009-08-27 12:10:42 +02:00
holger krekel
47d56e41ba fix tests to not fail if pyc-file-writing is disabled
--HG--
branch : 1.0.x
2009-08-27 12:05:12 +02:00
holger krekel
c981ead40e switching release branch back to 1.0.x versioning
--HG--
branch : 1.0.x
2009-08-27 11:50:17 +02:00
holger krekel
e7b86d808f Added tag 1.0.2 for changeset 4816e8b80602
--HG--
branch : 1.0.x
2009-08-27 11:43:52 +02:00
holger krekel
81f7891539 merging 1.0.x branch
--HG--
branch : trunk
2009-08-26 22:57:06 +02:00
holger krekel
8ee7bef638 consolidate py/code files into code.py, simplify SafeRepr code and docs.
--HG--
branch : trunk
2009-08-25 20:24:43 +02:00
holger krekel
94aef0b771 move and rename html rest helper to rest directory - finally remove py/misc completely
--HG--
branch : trunk
2009-08-25 16:15:17 +02:00
holger krekel
e810e1774d simplify caching class
--HG--
branch : trunk
2009-08-25 16:14:20 +02:00
holger krekel
d43d69e3db death to "misc" directories. moved most files out of py/misc, either to a
private attic or to other places in the lib.

--HG--
branch : trunk
2009-08-25 16:14:15 +02:00
holger krekel
739edc26b4 simplifying errno error class creation and introduce a py.error.checked_call helper
that creates a proper errno-specific exception instead of OSErrors.  use it from
py.path.local.

--HG--
branch : trunk
2009-08-25 09:38:19 +02:00
holger krekel
58a9e71e81 add delattr/delenv/delitem methods and tests, enhance terminalwriter tests
--HG--
branch : trunk
2009-08-22 12:45:58 +02:00
holger krekel
27c08ac235 consolidate py/log into fewer files, remove one old approach, sketch simplified API
--HG--
branch : trunk
2009-08-22 09:42:12 +02:00
holger krekel
2b8f489d60 moved laura's utestconvert script to a more visible place
--HG--
branch : trunk
2009-08-21 12:56:43 +02:00
holger krekel
1fcd373bd5 * introduce py.io.TextIO and py.io.StringIO to help with 3k transition and to clarify
intentions when doing "in-memory" files. Replace most usages of StringIO.

* consolidate py.io's files and tests into fewer files, make files 3k-importable

--HG--
branch : trunk
2009-08-20 20:47:39 +02:00
holger krekel
046ac957ab fix autopath bug introduced with path refactoring
--HG--
branch : trunk
2009-08-21 12:09:23 +02:00
holger krekel
5118821c10 consolidate svn path implementations and tests into files named after the package namespaces.
--HG--
branch : trunk
2009-08-20 20:35:35 +02:00
holger krekel
f3fcb5e6d3 - strike lots of basically unused code around local path implementation.
and tweak things a bit to make py.path.local at least importable on 3k

- also strike unused somewhat related code in initpkg.py

--HG--
branch : trunk
2009-08-20 19:43:13 +02:00
holger krekel
561fdca3a2 move localpath implementation to a single file, simplify unix/posix difference and fix a bit
--HG--
branch : trunk
2009-08-20 17:37:06 +02:00
holger krekel
079a2327ec kill/replace some execnet debug code
bump version to "trunk" on trunk
add "py" to rsyncdirs

--HG--
branch : trunk
2009-08-20 16:41:44 +02:00
holger krekel
e051c9a4b8 remerge from 1.0.x after changelog addition change
--HG--
branch : trunk
2009-08-19 21:07:37 +02:00
holger krekel
9c6d5080e3 also merging the tag from 1.0.x branch
--HG--
branch : trunk
2009-08-19 19:42:33 +02:00
holger krekel
523380432a merging 1.0.1 branch
--HG--
branch : trunk
2009-08-19 18:25:46 +02:00
holger krekel
58d7f236a7 merge 1.0.x changes back to trunk
--HG--
branch : trunk
2009-08-14 18:08:48 +02:00
holger krekel
58db35e70c merging 1.0 branch
--HG--
branch : trunk
2009-08-09 19:06:36 +02:00
holger krekel
cb9b8e4632 merge 1.0.x branch
--HG--
branch : trunk
2009-08-04 12:09:11 +02:00
holger krekel
cd5a89bec9 merge 1.0.x branch
--HG--
branch : trunk
2009-07-31 15:47:08 +02:00
holger krekel
03970e0c4a merge 1.0.x branch
--HG--
branch : trunk
2009-07-22 16:10:25 +02:00
holger krekel
8336df9929 merge 1.0.x branch
--HG--
branch : trunk
2009-07-20 18:56:33 +02:00
holger krekel
4cf46c2723 merge 1.0.x changes
--HG--
branch : trunk
2009-07-14 21:37:54 +02:00
552 changed files with 21385 additions and 53777 deletions

View File

@@ -11,6 +11,15 @@ syntax:glob
*.pyo
*.swp
*.html
*.class
*.orig
*~
doc/_build
build/
py.egg-info
dist/
*.egg-info
issue/
env/
3rdparty/
.tox

14
.hgtags
View File

@@ -17,3 +17,17 @@ e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4

23
AUTHORS Normal file
View File

@@ -0,0 +1,23 @@
Holger Krekel, holger at merlinux eu
Benjamin Peterson, benjamin at python org
Ronny Pfannschmidt, Ronny.Pfannschmidt at gmx de
Guido Wesdorp, johnny at johnnydebris net
Samuele Pedroni, pedronis at openend se
Carl Friedrich Bolz, cfbolz at gmx de
Armin Rigo, arigo at tunes org
Maciek Fijalkowski, fijal at genesilico pl
Brian Dorsey, briandorsey at gmail com
merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Ross Lawley
Ralf Schmitt
Chris Lamb
Harald Armin Massa
Martijn Faassen
Ian Bicking
Jan Balster
Grig Gheorghiu
Bob Ippolito
Christian Tismer

View File

@@ -1 +0,0 @@
doc/changelog.txt

771
CHANGELOG Normal file
View File

@@ -0,0 +1,771 @@
Changes between 1.3.4 and 2.0.0dev0
----------------------------------------------
- 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):
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]

233
ISSUES.txt Normal file
View File

@@ -0,0 +1,233 @@
checks / deprecations for next release
---------------------------------------------------------------
tags: bug 2.4 core xdist
* 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
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 2.1
- introduce py.test.mark.nocollect for not considering a function for
test collection at all. maybe also introduce a py.test.mark.test to
explicitely mark a function to become a tested one. Lookup JUnit ways
of tagging tests.
- allow an easy way to customize "test_", "Test" prefixes for file paths
and test function/class names. the current customizable Item requires
too much code/concepts to influence this collection matching.
maybe introduce pytest_pycollect_filters = {
'file': 'test*.py',
'function': 'test*',
'class': 'Test*',
}
introduce py.test.mark.platform
-------------------------------------------------------
tags: feature 2.1
Introduce nice-to-spell platform-skipping, examples:
@py.test.mark.platform("python3")
@py.test.mark.platform("not python3")
@py.test.mark.platform("win32 and not python3")
@py.test.mark.platform("darwin")
@py.test.mark.platform("not (jython and win32)")
@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.
introduce py.test.mark registration
-----------------------------------------
tags: feature 2.1
introduce a hook that allows to register a named mark decorator
with documentation and add "py.test --marks" to get
a list of available marks. Deprecate "dynamic" mark
definitions.
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 2.1
allow to name conftest.py files (in sub directories) that should
be imported early, as to include command line options.
improve central py.test ini file
----------------------------------
tags: feature 2.1
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
new documentation
----------------------------------
tags: feature 2.1
- logo py.test
- examples for unittest or functional testing
- resource management for functional testing
- patterns: page object
- parametrized testing
- better / more integrated plugin docs
generalize parametrized testing to generate combinations
-------------------------------------------------------------
tags: feature 2.1
think about extending metafunc.addcall or add a new method to allow to
generate tests with combinations of all generated versions - what to do
about "id" and "param" in such combinations though?
introduce py.test.mark.multi
-----------------------------------------
tags: feature 1.3
introduce py.test.mark.multi to specify a number
of values for a given function argument.
have imported module mismatch honour relative paths
--------------------------------------------------------
tags: bug 2.1
With 1.1.1 py.test fails at least on windows if an import
is relative and compared against an absolute conftest.py
path. Normalize.
call termination with small timeout
-------------------------------------------------
tags: feature 2.1
test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node
Call gateway group termination with a small timeout if available.
Should make dist-testing less likely to leave lost processes.
consider globals: py.test.ensuretemp and config
--------------------------------------------------------------
tags: experimental-wish 2.1
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 for setup methods
--------------------------------------------------------------
tags: experimental-wish 2.1
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
attributes. However, how to handle parametrized test functions
and funcargs?
setup_function -> request can be like it is now
setup_class -> request has no request.function
setup_module -> request has no request.cls
consider pytest_addsyspath hook
-----------------------------------------
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.
Alternatively it could also be done via the config object
and pytest_configure.
show plugin information in test header
----------------------------------------------------------------
tags: feature 2.1
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.
deprecate global py.test.config usage
----------------------------------------------------------------
tags: feature 2.1
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
-------------------------------------------------------------------
tags: feature 2.1
In an effort to further simplify code, review and remove deprecated bits
in collect.py. Probably good:
- inline consider_file/dir methods, no need to have them
subclass-overridable because of hooks

View File

@@ -1 +0,0 @@
py/LICENSE

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,17 +1,7 @@
include CHANGELOG
include README.txt
include setup.py
include LICENSE
include py/LICENSE
include py/path/svn/testing/repotest.dump
include py/rest/rest.sty.template
exclude *.orig
exclude *.rej
prune .svn
prune .hg
include distribute_setup.py
include LICENSE
graft doc
graft contrib
graft example
graft py/bin
graft py/rest/testing/data
graft py/misc/testing/data
graft testing

View File

@@ -1,9 +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.execnet: ad-hoc distributed execution
* 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

220
_pytest/capture.py Normal file
View File

@@ -0,0 +1,220 @@
""" per-test stdout/stderr capturing mechanisms, ``capsys`` and ``capfd`` function arguments. """
import pytest, py
import os
def pytest_addoption(parser):
group = parser.getgroup("general")
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",
help="shortcut for --capture=no.")
def addouterr(rep, outerr):
repr = getattr(rep, 'longrepr', None)
if not hasattr(repr, 'addsection'):
return
for secname, content in zip(["out", "err"], outerr):
if content:
repr.addsection("Captured std%s" % secname, content.rstrip())
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 "", ""
class CaptureManager:
def __init__(self):
self._method2capture = {}
def _maketempfile(self):
f = py.std.tempfile.TemporaryFile()
newf = py.io.dupfile(f, encoding="UTF-8")
f.close()
return newf
def _makestringio(self):
return py.io.TextIO()
def _getcapture(self, method):
if method == "fd":
return py.io.StdCaptureFD(now=False,
out=self._maketempfile(), err=self._maketempfile()
)
elif method == "sys":
return py.io.StdCapture(now=False,
out=self._makestringio(), err=self._makestringio()
)
elif method == "no":
return NoCapture()
else:
raise ValueError("unknown capturing method: %r" % method)
def _getmethod(self, config, fspath):
if config.option.capture:
method = config.option.capture
else:
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"
return method
def resumecapture_item(self, item):
method = self._getmethod(item.config, item.fspath)
if not hasattr(item, 'outerr'):
item.outerr = ('', '') # we accumulate outerr on the item
return self.resumecapture(method)
def resumecapture(self, method):
if hasattr(self, '_capturing'):
raise ValueError("cannot resume, already capturing with %r" %
(self._capturing,))
cap = self._method2capture.get(method)
self._capturing = method
if cap is None:
self._method2capture[method] = cap = self._getcapture(method)
cap.startall()
else:
cap.resume()
def suspendcapture(self, item=None):
self.deactivate_funcargs()
if hasattr(self, '_capturing'):
method = self._capturing
cap = self._method2capture.get(method)
if cap is not None:
outerr = cap.suspend()
del self._capturing
if item:
outerr = (item.outerr[0] + outerr[0],
item.outerr[1] + outerr[1])
return outerr
if hasattr(item, 'outerr'):
return item.outerr
return "", ""
def activate_funcargs(self, pyfuncitem):
if not hasattr(pyfuncitem, 'funcargs'):
return
assert not hasattr(self, '_capturing_funcargs')
self._capturing_funcargs = capturing_funcargs = []
for name, capfuncarg in pyfuncitem.funcargs.items():
if name in ('capsys', 'capfd'):
capturing_funcargs.append(capfuncarg)
capfuncarg._start()
def deactivate_funcargs(self):
capturing_funcargs = getattr(self, '_capturing_funcargs', None)
if capturing_funcargs is not None:
while capturing_funcargs:
capfuncarg = capturing_funcargs.pop()
capfuncarg._finalize()
del self._capturing_funcargs
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
try:
self.resumecapture(method)
except ValueError:
return # recursive collect, XXX refactor capturing
# to allow for more lightweight recursive capturing
try:
rep = __multicall__.execute()
finally:
outerr = self.suspendcapture()
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)
def pytest__teardown_final(self, __multicall__, session):
method = self._getmethod(session.config, None)
self.resumecapture(method)
try:
rep = __multicall__.execute()
finally:
outerr = self.suspendcapture()
if rep:
addouterr(rep, outerr)
return rep
def pytest_keyboard_interrupt(self, excinfo):
if hasattr(self, '_capturing'):
self.suspendcapture()
@pytest.mark.tryfirst
def pytest_runtest_makereport(self, __multicall__, item, call):
self.deactivate_funcargs()
rep = __multicall__.execute()
outerr = self.suspendcapture(item)
if not rep.passed:
addouterr(rep, outerr)
if not rep.passed or rep.when == "teardown":
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(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
"""
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")
return CaptureFuncarg(py.io.StdCaptureFD)
class CaptureFuncarg:
def __init__(self, captureclass):
self.capture = captureclass(now=False)
def _start(self):
self.capture.startall()
def _finalize(self):
if hasattr(self, 'capture'):
self.capture.reset()
del self.capture
def readouterr(self):
return self.capture.readouterr()
def close(self):
self._finalize()

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

159
_pytest/helpconfig.py Normal file
View File

@@ -0,0 +1,159 @@
""" 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 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",
help="early-load given plugin (multi-allowed).")
group.addoption('--traceconfig',
action="store_true", dest="traceconfig", default=False,
help="trace considerations of conftest.py files."),
group._addoption('--nomagic',
action="store_true", dest="nomagic", default=False,
help="don't reinterpret asserts, no traceback cutting. ")
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="generate and show internal debugging information.")
def pytest_cmdline_main(config):
if config.option.version:
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.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()
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])
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'),
]
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []
items = config.pluginmanager._name2plugin.items()
for name, plugin in items:
lines.append(" %-20s: %s" %(name, repr(plugin)))
return lines
# =====================================================
# validate plugin syntax and hooks
# =====================================================
def pytest_plugin_registered(manager, plugin):
methods = collectattr(plugin)
hooks = {}
for hookspec in manager.hook._hookspecs:
hooks.update(collectattr(hookspec))
stringio = py.io.TextIO()
def Print(*args):
if args:
stringio.write(" ".join(map(str, args)))
stringio.write("\n")
fail = False
while methods:
name, method = methods.popitem()
#print "checking", name
if isgenerichook(name):
continue
if name not in hooks:
if not getattr(method, 'optionalhook', False):
Print("found unknown hook:", name)
fail = True
else:
#print "checking", method
method_args = getargs(method)
#print "method_args", method_args
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = getargs(hook)
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
Print("actual definition: %s" %(formatdef(method)))
Print("available hook arguments: %s" %
", ".join(hookargs))
fail = True
break
#if not fail:
# print "matching hook:", formatdef(method)
if fail:
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
class PluginValidationError(Exception):
""" plugin failed validation. """
def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def getargs(func):
args = inspect.getargs(py.code.getrawcode(func))[0]
startindex = inspect.ismethod(func) and 1 or 0
return args[startindex:]
def collectattr(obj):
methods = {}
for apiname in dir(obj):
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

220
_pytest/hookspec.py Normal file
View File

@@ -0,0 +1,220 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
# -------------------------------------------------------------------------
# Initialization
# -------------------------------------------------------------------------
def pytest_addhooks(pluginmanager):
"""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.
"""
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 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):
""" called before traversing a directory for collection files. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
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. """
def pytest_deselected(items):
""" called for test items deselected by keyword. """
def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport. """
pytest_make_collect_report.firstresult = True
# -------------------------------------------------------------------------
# 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
create test modules for files that do not match as a test module.
"""
pytest_pycollect_makemodule.firstresult = True
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
pytest_pycollect_makeitem.firstresult = True
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """
pytest_pyfunc_call.firstresult = True
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
# -------------------------------------------------------------------------
# generic runtest related hooks
# -------------------------------------------------------------------------
def pytest_itemstart(item, node=None):
""" (deprecated, use pytest_runtest_logstart). """
def pytest_runtest_protocol(item):
""" implements the standard runtest_setup/call/teardown protocol including
capturing exceptions and calling reporting hooks on the results accordingly.
: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(item)``. """
def pytest_runtest_call(item):
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item):
""" called after ``pytest_runtest_call``. """
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
for the given :py:class:`pytest.Item` and
:py:class:`_pytest.runner.CallInfo`.
"""
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report):
""" process item test report. """
# special handling for final teardown - somewhat internal for now
def pytest__teardown_final(session):
""" called before test session finishes. """
pytest__teardown_final.firstresult = True
def pytest__teardown_final_logerror(report, session):
""" called if runtest_teardown_final failed. """
# -------------------------------------------------------------------------
# test session related hooks
# -------------------------------------------------------------------------
def pytest_sessionstart(session):
""" before session.main() is called. """
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
# -------------------------------------------------------------------------
# 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):
""" return a string to be displayed as header info for terminal reporting."""
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting."""
pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """
# -------------------------------------------------------------------------
# doctest hooks
# -------------------------------------------------------------------------
def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_plugin_registered(plugin, manager):
""" a new py lib plugin got registered. """
def pytest_plugin_unregistered(plugin):
""" a py lib plugin got unregistered. """
def pytest_internalerror(excrepr):
""" called for internal errors. """
def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """

173
_pytest/junitxml.py Normal file
View File

@@ -0,0 +1,173 @@
""" 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",
metavar="path", default=None,
help="create junit-xml style report file at given path.")
group.addoption('--junitprefix', action="store", dest="junitprefix",
metavar="str", default=None,
help="prepend prefix to classnames in junit-xml output")
def pytest_configure(config):
xmlpath = config.option.xmlpath
if xmlpath:
config._xml = LogXML(xmlpath, config.option.junitprefix)
config.pluginmanager.register(config._xml)
def pytest_unconfigure(config):
xml = getattr(config, '_xml', None)
if xml:
del config._xml
config.pluginmanager.unregister(xml)
class LogXML(object):
def __init__(self, logfile, prefix):
self.logfile = logfile
self.prefix = prefix
self.test_logs = []
self.passed = self.skipped = 0
self.failed = self.errors = 0
self._durations = {}
def _opentestcase(self, report):
names = report.nodeid.split("::")
names[0] = names[0].replace("/", '.')
names = tuple(names)
d = {'time': self._durations.pop(names, "0")}
names = [x.replace(".py", "") for x in names if x != "()"]
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
d['classname'] = ".".join(classnames)
d['name'] = py.xml.escape(names[-1])
attrs = ['%s="%s"' % item for item in sorted(d.items())]
self.test_logs.append("\n<testcase %s>" % " ".join(attrs))
def _closetestcase(self):
self.test_logs.append("</testcase>")
def appendlog(self, fmt, *args):
args = tuple([py.xml.escape(arg) for arg in args])
self.test_logs.append(fmt % args)
def append_pass(self, report):
self.passed += 1
self._opentestcase(report)
self._closetestcase()
def append_failure(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
if "xfail" in report.keywords:
self.appendlog(
'<skipped message="xfail-marked test passes unexpectedly"/>')
self.skipped += 1
else:
self.appendlog('<failure message="test failure">%s</failure>',
report.longrepr)
self.failed += 1
self._closetestcase()
def append_collect_failure(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
self.appendlog('<failure message="collection failure">%s</failure>',
report.longrepr)
self._closetestcase()
self.errors += 1
def append_collect_skipped(self, report):
self._opentestcase(report)
#msg = str(report.longrepr.reprtraceback.extraline)
self.appendlog('<skipped message="collection skipped">%s</skipped>',
report.longrepr)
self._closetestcase()
self.skipped += 1
def append_error(self, report):
self._opentestcase(report)
self.appendlog('<error message="test setup failure">%s</error>',
report.longrepr)
self._closetestcase()
self.errors += 1
def append_skipped(self, report):
self._opentestcase(report)
if "xfail" in report.keywords:
self.appendlog(
'<skipped message="expected test failure">%s</skipped>',
report.keywords['xfail'])
else:
self.appendlog("<skipped/>")
self._closetestcase()
self.skipped += 1
def pytest_runtest_logreport(self, report):
if report.passed:
self.append_pass(report)
elif report.failed:
if report.when != "call":
self.append_error(report)
else:
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[names] = time.time() - start
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:
self.append_collect_failure(report)
else:
self.append_collect_skipped(report)
def pytest_internalerror(self, excrepr):
self.errors += 1
data = py.xml.escape(excrepr)
self.test_logs.append(
'\n<testcase classname="pytest" name="internal">'
' <error message="internal error">'
'%s</error></testcase>' % data)
def pytest_sessionstart(self, session):
self.suite_start_time = time.time()
def pytest_sessionfinish(self, session, exitstatus, __multicall__):
if py.std.sys.version_info[0] < 3:
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
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write('<testsuite ')
logfile.write('name="" ')
logfile.write('errors="%i" ' % self.errors)
logfile.write('failures="%i" ' % self.failed)
logfile.write('skips="%i" ' % self.skipped)
logfile.write('tests="%i" ' % numtests)
logfile.write('time="%.3f"' % suite_time_delta)
logfile.write(' >')
logfile.writelines(self.test_logs)
logfile.write('</testsuite>')
logfile.close()
def pytest_terminal_summary(self, terminalreporter):
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

63
_pytest/pastebin.py Normal file
View File

@@ -0,0 +1,63 @@
""" submit failure or test session information to a pastebin service. """
import py, sys
class url:
base = "http://paste.pocoo.org"
xmlrpc = base + "/xmlrpc/"
show = base + "/show/"
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'],
help="send failed|all info to Pocoo pastebin service.")
def pytest_configure(__multicall__, config):
import tempfile
__multicall__.execute()
if config.option.pastebin == "all":
config._pastebinfile = tempfile.TemporaryFile('w+')
tr = config.pluginmanager.getplugin('terminalreporter')
oldwrite = tr._tw.write
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
config._pastebinfile.write(str(s))
tr._tw.write = tee_write
def pytest_unconfigure(config):
if hasattr(config, '_pastebinfile'):
config._pastebinfile.seek(0)
sessionlog = config._pastebinfile.read()
config._pastebinfile.close()
del config._pastebinfile
proxyid = getproxy().newPaste("python", sessionlog)
pastebinurl = "%s%s" % (url.show, proxyid)
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
def pytest_terminal_summary(terminalreporter):
if terminalreporter.config.option.pastebin != "failed":
return
tr = terminalreporter
if 'failed' in tr.stats:
terminalreporter.write_sep("=", "Sending information to Paste Service")
if tr.config.option.debug:
terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
serverproxy = getproxy()
for rep in terminalreporter.stats.get('failed'):
try:
msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
except AttributeError:
msg = tr._getfailureheadline(rep)
tw = py.io.TerminalWriter(stringio=True)
rep.toterminal(tw)
s = tw.stringio.getvalue()
assert len(s)
proxyid = serverproxy.newPaste("python", s)
pastebinurl = "%s%s" % (url.show, proxyid)
tr.write_line("%s --> %s" %(msg, pastebinurl))

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)

672
_pytest/pytester.py Normal file
View File

@@ -0,0 +1,672 @@
""" (disabled by default) support for testing py.test and py.test plugins. """
import py, pytest
import sys, os
import re
import inspect
import time
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('--no-tools-on-path',
action="store_true", dest="notoolsonpath", default=False,
help=("discover tools on PATH instead of going through py.cmdline.")
)
def pytest_configure(config):
# This might be called multiple times. Only take the first.
global _pytest_fullpath
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()
def pytest_funcarg__LineMatcher(request):
return LineMatcher
def pytest_funcarg__testdir(request):
tmptestdir = TmpTestdir(request)
return tmptestdir
rex_outcome = re.compile("(\d+) (\w+)")
class RunResult:
def __init__(self, ret, outlines, errlines, duration):
self.ret = ret
self.outlines = outlines
self.errlines = errlines
self.stdout = LineMatcher(outlines)
self.stderr = LineMatcher(errlines)
self.duration = duration
def parseoutcomes(self):
for line in reversed(self.outlines):
if 'seconds' in line:
outcomes = rex_outcome.findall(line)
if outcomes:
d = {}
for num, cat in outcomes:
d[cat] = int(num)
return d
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._tmpdirhandler.ensuretemp("testdir")
name = request.function.__name__
for i in range(100):
try:
tmpdir = basetmp.mkdir(name + str(i))
except py.error.EEXIST:
continue
break
# we need to create another subdir
# because Directory.collect() currently loads
# conftest.py from sibling directories
self.tmpdir = tmpdir.mkdir(name)
self.plugins = []
self._syspathremove = []
self.chdir() # always chdir
self.request.addfinalizer(self.finalize)
def __repr__(self):
return "<TmpTestdir %r>" % (self.tmpdir,)
def finalize(self):
for p in self._syspathremove:
py.std.sys.path.remove(p)
if hasattr(self, '_olddir'):
self._olddir.chdir()
# delete modules that have been loaded from tmpdir
for name, mod in list(sys.modules.items()):
if mod:
fn = getattr(mod, '__file__', None)
if fn and fn.startswith(str(self.tmpdir)):
del sys.modules[name]
def getreportrecorder(self, obj):
if hasattr(obj, 'config'):
obj = obj.config
if hasattr(obj, 'hook'):
obj = obj.hook
assert hasattr(obj, '_hookspecs'), obj
reprec = ReportRecorder(obj)
reprec.hookrecorder = self._pytest.gethookrecorder(obj)
reprec.hook = reprec.hookrecorder.hook
return reprec
def chdir(self):
old = self.tmpdir.chdir()
if not hasattr(self, '_olddir'):
self._olddir = old
def _makefile(self, ext, args, kwargs):
items = list(kwargs.items())
if args:
source = "\n".join(map(str, args)) + "\n"
basename = self.request.function.__name__
items.insert(0, (basename, source))
ret = None
for name, value in items:
p = self.tmpdir.join(name).new(ext=ext)
source = str(py.code.Source(value)).lstrip()
p.write(source.encode("utf-8"), "wb")
if ret is None:
ret = p
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)
def maketxtfile(self, *args, **kwargs):
return self._makefile('.txt', args, kwargs)
def syspathinsert(self, path=None):
if path is None:
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)
def mkpydir(self, name):
p = self.mkdir(name)
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):
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.parseconfigure(*args)
rec = self.getreportrecorder(config)
session = Session(config)
session.perform_collect()
return session.items, rec
def runitem(self, source):
# used from runner functional tests
item = self.getitem(source)
# the test class where we are called from wants to provide the runner
testclassinstance = py.builtin._getimself(self.request.function)
runner = testclassinstance.getrunner()
return runner(item)
def inline_runsource(self, source, *cmdlineargs):
p = self.makepyfile(source)
l = list(cmdlineargs) + [p]
return self.inline_run(*l)
def inline_runsource1(self, *args):
args = list(args)
source = args.pop()
p = self.makepyfile(source)
l = list(args) + [p]
reprec = self.inline_run(*l)
reports = reprec.getreports("pytest_runtest_logreport")
assert len(reports) == 1, reports
return reports[0]
def inline_run(self, *args):
args = ("-s", ) + args # otherwise FD leakage
config = self.parseconfig(*args)
reprec = self.getreportrecorder(config)
#config.pluginmanager.do_configure(config)
config.hook.pytest_cmdline_main(config=config)
#config.pluginmanager.do_unconfigure(config)
return reprec
def config_preparse(self):
config = self.Config()
for plugin in self.plugins:
if isinstance(plugin, str):
config.pluginmanager.import_plugin(plugin)
else:
if isinstance(plugin, dict):
plugin = PseudoPlugin(plugin)
if not config.pluginmanager.isregistered(plugin):
config.pluginmanager.register(plugin)
return config
def parseconfig(self, *args):
if not args:
args = (self.tmpdir,)
config = self.config_preparse()
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
def reparseconfig(self, args=None):
""" this is used from tests that want to re-invoke parse(). """
if not args:
args = [self.tmpdir]
oldconfig = getattr(py.test, 'config', None)
try:
c = py.test.config = self.Config()
c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
keep=0, rootdir=self.tmpdir, lock_timeout=None)
c.parse(args)
return c
finally:
py.test.config = oldconfig
def parseconfigure(self, *args):
config = self.parseconfig(*args)
config.pluginmanager.do_configure(config)
self.request.addfinalizer(lambda:
config.pluginmanager.do_unconfigure(config))
return config
def getitem(self, source, funcname="test_func"):
for item in self.getitems(source):
if item.name == funcname:
return item
assert 0, "%r item not found in module:\n%s" %(funcname, source)
def getitems(self, source):
modcol = self.getmodulecol(source)
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 = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path)
#config.pluginmanager.do_unconfigure(config)
return node
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):
env = os.environ.copy()
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)
def _run(self, *cmdargs):
cmdargs = [str(x) for x in cmdargs]
p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr")
print_("running", cmdargs, "curdir=", py.path.local())
f1 = p1.open("wb")
f2 = p2.open("wb")
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
ret = popen.wait()
f1.close()
f2.close()
out = p1.read("rb")
out = getdecoded(out).splitlines()
err = p2.read("rb")
err = getdecoded(err).splitlines()
def dump_lines(lines, fp):
try:
for line in lines:
py.builtin.print_(line, file=fp)
except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,))
dump_lines(out, sys.stdout)
dump_lines(err, sys.stderr)
return RunResult(ret, out, err, time.time()-now)
def runpybin(self, scriptname, *args):
fullargs = self._getpybinargs(scriptname) + args
return self.run(*fullargs)
def _getpybinargs(self, scriptname):
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:
py.test.skip("cannot run %r with --no-tools-on-path" % scriptname)
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 self.request.config.getvalue("notoolsonpath"):
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
else:
s = ""
return s
def runpython_c(self, command):
command = self._getsysprepend() + command
return self.run(py.std.sys.executable, "-c", command)
def runpytest(self, *args):
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:
# 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):
if self.request.config.getvalue("notoolsonpath"):
py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
basetemp = self.tmpdir.mkdir("pexpect")
invoke = " ".join(map(str, self._getpybinargs("py.test")))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
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
def getdecoded(out):
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
class PseudoPlugin:
def __init__(self, vars):
self.__dict__.update(vars)
class ReportRecorder(object):
def __init__(self, hook):
self.hook = hook
self.pluginmanager = hook._pm
self.pluginmanager.register(self)
def getcall(self, name):
return self.hookrecorder.getcall(name)
def popcall(self, name):
return self.hookrecorder.popcall(name)
def getcalls(self, names):
""" return list of ParsedCall instances matching the given eventname. """
return self.hookrecorder.getcalls(names)
# 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", when=None):
""" return a testreport whose dotted import path matches """
l = []
for rep in self.getreports(names=names):
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!" %
(inamepart,))
if len(l) > 1:
raise ValueError("found more than one testreport matching %r: %s" %(
inamepart, l))
return l[0]
def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
return [rep for rep in self.getreports(names) if rep.failed]
def getfailedcollections(self):
return self.getfailures('pytest_collectreport')
def listoutcomes(self):
passed = []
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)
elif rep.failed:
failed.append(rep)
return passed, skipped, failed
def countoutcomes(self):
return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed=0, skipped=0, failed=0):
realpassed, realskipped, realfailed = self.listoutcomes()
assert passed == len(realpassed)
assert skipped == len(realskipped)
assert failed == len(realfailed)
def clear(self):
self.hookrecorder.calls[:] = []
def unregister(self):
self.pluginmanager.unregister(self)
self.hookrecorder.finish_recording()
class LineComp:
def __init__(self):
self.stringio = py.io.TextIO()
def assert_contains_lines(self, lines2):
""" assert that lines2 are contained (linearly) in lines1.
return a list of extralines found.
"""
__tracebackhide__ = True
val = self.stringio.getvalue()
self.stringio.truncate(0)
self.stringio.seek(0)
lines1 = val.split("\n")
return LineMatcher(lines1).fnmatch_lines(lines2)
class LineMatcher:
def __init__(self, lines):
self.lines = lines
def str(self):
return "\n".join(self.lines)
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
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 = []
__tracebackhide__ = True
for line in lines2:
nomatchprinted = False
while lines1:
nextline = lines1.pop(0)
if line == nextline:
show("exact match:", repr(line))
break
elif fnmatch(nextline, line):
show("fnmatch:", repr(line))
show(" with:", repr(nextline))
break
else:
if not nomatchprinted:
show("nomatch:", repr(line))
nomatchprinted = True
show(" and:", repr(nextline))
extralines.append(nextline)
else:
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)

96
_pytest/recwarn.py Normal file
View File

@@ -0,0 +1,96 @@
""" recording warnings during test function execution. """
import py
import sys, os
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
"""
if sys.version_info >= (2,7):
import warnings
oldfilters = warnings.filters[:]
warnings.simplefilter('default')
def reset_filters():
warnings.filters[:] = oldfilters
request.addfinalizer(reset_filters)
wrec = WarningsRecorder()
request.addfinalizer(wrec.finalize)
return wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call}
def deprecated_call(func, *args, **kwargs):
""" 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)
oldwarn_explicit(*args, **kwargs)
oldwarn = getattr(warningmodule, 'warn')
def warn(*args, **kwargs):
l.append(args)
oldwarn(*args, **kwargs)
warningmodule.warn_explicit = warn_explicit
warningmodule.warn = warn
try:
ret = func(*args, **kwargs)
finally:
warningmodule.warn_explicit = warn_explicit
warningmodule.warn = warn
if not l:
#print warningmodule
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
return ret
class RecordedWarning:
def __init__(self, message, category, filename, lineno, line):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.line = line
class WarningsRecorder:
def __init__(self):
warningmodule = py.std.warnings
self.list = []
def showwarning(message, category, filename, lineno, line=0):
self.list.append(RecordedWarning(
message, category, filename, lineno, line))
try:
self.old_showwarning(message, category,
filename, lineno, line=line)
except TypeError:
# < python2.6
self.old_showwarning(message, category, filename, lineno)
self.old_showwarning = warningmodule.showwarning
warningmodule.showwarning = showwarning
def pop(self, cls=Warning):
""" pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self.list):
if issubclass(w.category, cls):
return self.list.pop(i)
__tracebackhide__ = True
assert 0, "%r not found in %r" %(cls, self.list)
#def resetregistry(self):
# import warnings
# warnings.onceregistry.clear()
# warnings.__warningregistry__.clear()
def clear(self):
self.list[:] = []
def finalize(self):
py.std.warnings.showwarning = self.old_showwarning

93
_pytest/resultlog.py Normal file
View File

@@ -0,0 +1,93 @@
""" (disabled by default) create result information in a plain text file. """
import py
from py.builtin import print_
def pytest_addoption(parser):
group = parser.getgroup("resultlog", "resultlog plugin options")
group.addoption('--resultlog', action="store", dest="resultlog", metavar="path", default=None,
help="path for machine-readable result log.")
def pytest_configure(config):
resultlog = config.option.resultlog
# 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.pluginmanager.register(config._resultlog)
def pytest_unconfigure(config):
resultlog = getattr(config, '_resultlog', None)
if resultlog:
resultlog.logfile.close()
del config._resultlog
config.pluginmanager.unregister(resultlog)
def generic_path(item):
chain = item.listchain()
gpath = [chain[0].name]
fspath = chain[0].fspath
fspart = False
for node in chain[1:]:
newfspath = node.fspath
if newfspath == fspath:
if fspart:
gpath.append(':')
fspart = False
else:
gpath.append('.')
else:
gpath.append('/')
fspart = True
name = node.name
if name[0] in '([':
gpath.pop()
gpath.append(name)
fspath = newfspath
return ''.join(gpath)
class ResultLog(object):
def __init__(self, config, logfile):
self.config = config
self.logfile = logfile # preferably line buffered
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, 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)
code = res[1]
if code == 'x':
longrepr = str(report.longrepr)
elif code == 'X':
longrepr = ''
elif report.passed:
longrepr = ""
elif report.failed:
longrepr = str(report.longrepr)
elif report.skipped:
longrepr = str(report.longrepr[2])
self.log_outcome(report, code, longrepr)
def pytest_collectreport(self, report):
if not report.passed:
if report.failed:
code = "F"
longrepr = str(report.longrepr.reprcrash)
else:
assert report.skipped
code = "S"
longrepr = "%s:%d: %s" % report.longrepr
self.log_outcome(report, code, longrepr)
def pytest_internalerror(self, excrepr):
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)

213
_pytest/skipping.py Normal file
View File

@@ -0,0 +1,213 @@
""" support for skip/xfail functions and markers. """
import py, pytest
def pytest_addoption(parser):
group = parser.getgroup("general")
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
self.name = name
@property
def holder(self):
return self.item.keywords.get(self.name, None)
def __bool__(self):
return bool(self.holder)
__nonzero__ = __bool__
def istrue(self):
if self.holder:
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
if self.holder.args:
self.result = False
for expr in self.holder.args:
self.expr = expr
if isinstance(expr, str):
result = cached_eval(self.item.config, expr, d)
else:
result = expr
if result:
self.result = True
self.expr = expr
break
else:
self.result = True
return getattr(self, 'result', False)
def get(self, attr, default=None):
return self.holder.kwargs.get(attr, default)
def getexplanation(self):
expl = self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + self.expr
return expl
def pytest_runtest_setup(item):
if not isinstance(item, pytest.Function):
return
evalskip = MarkEvaluator(item, 'skipif')
if evalskip.istrue():
py.test.skip(evalskip.getexplanation())
item._evalxfail = MarkEvaluator(item, 'xfail')
check_xfail_no_run(item)
def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem)
def check_xfail_no_run(item):
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, pytest.Function):
return
if not (call.excinfo and
call.excinfo.errisinstance(py.test.xfail.Exception)):
evalxfail = getattr(item, '_evalxfail', None)
if not evalxfail:
return
if call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception):
if not item.config.getvalue("runxfail"):
rep = __multicall__.execute()
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
rep.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.outcome = "skipped"
else:
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
else:
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
if 'xfail' in report.keywords:
if report.skipped:
return "xfailed", "x", "xfail"
elif report.failed:
return "xpassed", "X", "XPASS"
# called by the terminalreporter instance/plugin
def pytest_terminal_summary(terminalreporter):
tr = terminalreporter
if not tr.reportchars:
#for name in "xfailed skipped failed xpassed":
# if not tr.stats.get(name, 0):
# tr.write_line("HINT: use '-r' option to see extra "
# "summary info about tests")
# break
return
lines = []
for char in tr.reportchars:
if char == "x":
show_xfailed(terminalreporter, lines)
elif char == "X":
show_xpassed(terminalreporter, lines)
elif char in "fF":
show_failed(terminalreporter, lines)
elif char in "sS":
show_skipped(terminalreporter, lines)
if lines:
tr._tw.sep("=", "short test summary info")
for line in lines:
tr._tw.line(line)
def show_failed(terminalreporter, lines):
tw = terminalreporter._tw
failed = terminalreporter.stats.get("failed")
if failed:
for rep in failed:
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 = rep.nodeid
reason = rep.keywords['xfail']
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 = rep.nodeid
reason = rep.keywords['xfail']
lines.append("XPASS %s %s" %(pos, reason))
def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'):
config._evalcache = {}
try:
return config._evalcache[expr]
except KeyError:
#import sys
#print >>sys.stderr, ("cache-miss: %r" % expr)
config._evalcache[expr] = x = eval(expr, d)
return x
def folded_skips(skipped):
d = {}
for event in skipped:
key = event.longrepr
assert len(key) == 3, (event, key)
d.setdefault(key, []).append(event)
l = []
for key, events in d.items():
l.append((len(events),) + key)
return l
def show_skipped(terminalreporter, lines):
tr = terminalreporter
skipped = tr.stats.get('skipped', [])
if skipped:
#if not tr.hasopt('skipped'):
# tr.write_line(
# "%d skipped tests, specify -rs for more info" %
# len(skipped))
# return
fskips = folded_skips(skipped)
if fskips:
#tr.write_sep("_", "skipped test summary")
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
lines.append("SKIP [%d] %s:%d: %s" %
(num, fspath, lineno, reason))

63
_pytest/standalonetemplate.py Executable file
View File

@@ -0,0 +1,63 @@
#! /usr/bin/env python
sources = """
@SOURCES@"""
import sys
import base64
import zlib
import imp
class DictImporter(object):
def __init__(self, sources):
self.sources = sources
def find_module(self, fullname, path=None):
if fullname in self.sources:
return self
if fullname + '.__init__' in self.sources:
return self
return None
def load_module(self, fullname):
# print "load_module:", fullname
from types import ModuleType
try:
s = self.sources[fullname]
is_pkg = False
except KeyError:
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__')
return res
if __name__ == "__main__":
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 = pickle.loads(zlib.decompress(base64.decodebytes(sources)))
else:
import cPickle as pickle
exec("def do_exec(co, loc): exec co in loc\n")
sources = pickle.loads(zlib.decompress(base64.decodestring(sources)))
importer = DictImporter(sources)
sys.meta_path.append(importer)
entry = "@ENTRY@"
do_exec(entry, locals())

464
_pytest/terminal.py Normal file
View File

@@ -0,0 +1,464 @@
""" terminal reporting of the full testing process.
This is a good source for looking at the various reporting hooks.
"""
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",
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, "
"(s)skipped, (x)failed, (X)passed.")
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
group._addoption('--report',
action="store", dest="report", default=None, metavar="opts",
help="(deprecated, use -r)")
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/no).")
group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut).")
def pytest_configure(config):
config.option.verbose -= config.option.quiet
if config.option.collectonly:
reporter = CollectonlyReporter(config)
else:
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
reporter = TerminalReporter(config, stdout)
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.option.report
if optvalue:
py.builtin.print_("DEPRECATED: use -r instead of --report option.",
file=py.std.sys.stderr)
if optvalue:
for setting in optvalue.split(","):
setting = setting.strip()
if setting == "skipped":
reportopts += "s"
elif setting == "xfailed":
reportopts += "x"
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.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.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):
if fspath != self.currentfspath:
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._tw.write(prefix)
if extra:
self._tw.write(extra, **kwargs)
self.currentfspath = -2
def ensure_newline(self):
if self.currentfspath:
self._tw.line()
self.currentfspath = None
def write_line(self, line, **markup):
line = str(line)
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 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:
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
self.write_line(msg)
def pytest_deselected(self, items):
self.stats.setdefault('deselected', []).extend(items)
def pytest__teardown_final_logerror(self, report):
self.stats.setdefault("error", []).append(report)
def pytest_runtest_logstart(self, nodeid, location):
# ensure that the path is printed before the
# 1st test of a module starts running
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
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 self.verbosity <= 0:
if not hasattr(rep, 'node') and self.showfspath:
self.write_fspath_result(rep.fspath, letter)
else:
self._tw.write(letter)
else:
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'):
self._tw.write("[%s] " % rep.node.gateway.id)
self._tw.write(word, **markup)
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 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._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)
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)
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()
self._tw.line("")
if exitstatus in (0, 1, 2):
self.summary_errors()
self.summary_failures()
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
self._report_keyboardinterrupt()
self.summary_deselected()
self.summary_stats()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
def _report_keyboardinterrupt(self):
excrepr = self._keyboardinterrupt_memo
msg = excrepr.reprcrash.message
self.write_sep("!", msg)
if "KeyboardInterrupt" in msg:
if self.config.option.fulltrace:
excrepr.toterminal(self._tw)
else:
excrepr.reprcrash.toterminal(self._tw)
def _locationline(self, collect_fspath, fspath, lineno, domain):
if fspath and fspath != collect_fspath:
fspath = "%s <- %s" % (collect_fspath, fspath)
if lineno is not None:
lineno += 1
if fspath and lineno and domain:
line = "%(fspath)s:%(lineno)s: %(domain)s"
elif fspath and domain:
line = "%(fspath)s: %(domain)s"
elif fspath and lineno:
line = "%(fspath)s:%(lineno)s %(extrapath)s"
else:
line = "[nolocation]"
return line % locals() + " "
def _getfailureheadline(self, rep):
if hasattr(rep, 'location'):
fspath, lineno, domain = rep.location
return domain
else:
return "test session" # XXX?
def _getcrashline(self, rep):
try:
return str(rep.longrepr.reprcrash)
except AttributeError:
try:
return str(rep.longrepr)[:50]
except AttributeError:
return ""
#
# 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):
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
if not reports:
return
self.write_sep("=", "FAILURES")
for rep in reports:
if self.config.option.tbstyle == "line":
line = self._getcrashline(rep)
self.write_line(line)
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
rep.toterminal(self._tw)
def summary_errors(self):
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)
if not hasattr(rep, 'when'):
# collect
msg = "ERROR collecting " + msg
elif rep.when == "setup":
msg = "ERROR at setup of " + msg
elif rep.when == "teardown":
msg = "ERROR at teardown of " + msg
self.write_sep("_", msg)
rep.toterminal(self._tw)
def summary_stats(self):
session_duration = py.std.time.time() - self._sessionstarttime
keys = "failed passed skipped deselected".split()
for key in self.stats.keys():
if key not in keys:
keys.append(key)
parts = []
for key in keys:
val = self.stats.get(key, None)
if val:
parts.append("%d %s" %(len(val), key))
line = ", ".join(parts)
# XXX coloring
msg = "%s in %.2f seconds" %(line, session_duration)
if self.verbosity >= 0:
self.write_sep("=", msg, bold=True)
else:
self.write_line(msg, bold=True)
def summary_deselected(self):
if 'deselected' in self.stats:
self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True)
class CollectonlyReporter:
INDENT = " "
def __init__(self, config, out=None):
self.config = config
if out is None:
out = py.std.sys.stdout
self._tw = py.io.TerminalWriter(out)
self.indent = ""
self._failed = []
def outindent(self, line):
self._tw.line(self.indent + str(line))
def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"):
self._tw.line("INTERNALERROR> " + line)
def pytest_collectstart(self, collector):
if collector.session != collector:
self.outindent(collector)
self.indent += self.INDENT
def pytest_itemcollected(self, item):
self.outindent(item)
def pytest_collectreport(self, report):
if not report.passed:
if hasattr(report.longrepr, 'reprcrash'):
msg = report.longrepr.reprcrash.message
else:
# XXX unify (we have CollectErrorRepr here)
msg = str(report.longrepr[2])
self.outindent("!!! %s !!!" % msg)
#self.outindent("!!! error !!!")
self._failed.append(report)
self.indent = self.indent[:-len(self.INDENT)]
def pytest_collection_finish(self):
if self._failed:
self._tw.sep("!", "collection failures")
for rep in self._failed:
rep.toterminal(self._tw)
return self._failed and 1 or 0
def repr_pythonversion(v=None):
if v is None:
v = sys.version_info
try:
return "%s.%s.%s-%s-%s" % v
except (TypeError, ValueError):
return str(v)
def flatten(l):
for x in l:
if isinstance(x, (list, tuple)):
for y in flatten(x):
yield y
else:
yield x

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 'popen//python=python2.6' \
--tx 'ssh=noco//python=/usr/local/bin/python2.4//chdir=/tmp/pytest-python2.4' \
--tx 'socket=192.168.1.106:8888'

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,35 +0,0 @@
from _findpy import py
bindir = py.magic.autopath().dirpath().dirpath("py").join("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,342 +0,0 @@
import sys
sys.path.insert(0, sys.argv[1])
import py
toolpath = py.magic.autopath()
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__
info = self.tip_info()
self.append('''
"""
py lib / py.test setup.py file, autogenerated by %(toolname)s
''' % locals())
#self.append(info)
self.append('''
"""
import os, sys
''')
if self.setuptools:
#import ez_setup
#ez_setup.use_setuptools()
self.append("""
from setuptools import setup
""")
else:
self.append("""
from distutils.core 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__)
#params['url'] = self.wcinfo.url
#params['rev'] = self.wcinfo.rev
#params['long_description'] = reformat(params['long_description'])
#print py.std.pprint.pprint(params)
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, py_modules=['_findpy',]),
#self.append_pprint(indent, scripts=self.getscripts())
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, data_files=self.getdatafiles())
self.append_pprint(indent, package_data=self.getpackagedata())
#self.append_pprint(indent, package_dir={'py': 'py'})
#self.append_pprint(indent, packages=self.getpackages())
if self.setuptools:
self.append_pprint(indent, zip_safe=False)
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,291 +0,0 @@
import py
import sys
WIDTH = 75
plugins = [
('plugins for Python test functions',
'xfail figleaf monkeypatch capture recwarn',),
('plugins for other testing styles and languages',
'oejskit unittest nose django doctest restdoc'),
('plugins for generic reporting and failure logging',
'pastebin resultlog terminal',),
('plugins for generic reporting and failure logging',
'pastebin resultlog terminal',),
('misc plugins / core functionality',
'helpconfig pdb keyword hooklog')
#('internal plugins / core functionality',
# #'pdb keyword hooklog runner execnetcleanup # pytester',
# 'pdb keyword hooklog runner execnetcleanup' # pytester',
#)
]
externals = {
'oejskit': 'run javascript tests in real life browsers',
'django': 'support 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.path.local(py.__file__).dirpath("test", "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_ %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)
self.para("%s_ %s" %(name, doc.oneliner))
self.Print()
class HookSpec(RestWriter):
def makerest(self, config):
module = 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
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 = plugin.__name__.split(".")[-1]
self.oneliner = oneliner
self.moduledoc = moduledoc
self.h1("%s plugin" % self.name) # : %s" %(self.name, self.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)
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/test/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',
'../../download.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.compat.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__":
_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,111 +0,0 @@
import py
import subprocess
import os
#
# 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):
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 py.execnet.makegateway("popen//python=%s" %(python,))
def pcall(self, cmd, *args, **kw):
self.ensure()
return subprocess.call([
self._cmd(cmd)
] + list(args),
**kw)
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__

View File

@@ -1,329 +0,0 @@
"""
Tested with coverage 2.85 and pygments 1.0
TODO:
+ 'html-output/*,cover' should be deleted
+ credits for coverage
+ credits for pygments
+ 'Install pygments' after ImportError is to less
+ is the way of determining DIR_CSS_RESOURCE ok?
+ write plugin test
+ '.coverage' still exists in py.test execution dir
"""
import os
import sys
import re
import shutil
from StringIO import StringIO
import py
try:
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter
except ImportError:
print "Install pygments" # XXX
sys.exit(0)
DIR_CUR = str(py.path.local())
REPORT_FILE = os.path.join(DIR_CUR, '.coverage')
DIR_ANNOTATE_OUTPUT = os.path.join(DIR_CUR, '.coverage_annotate')
COVERAGE_MODULES = set()
# coverage output parsing
REG_COVERAGE_SUMMARY = re.compile('([a-z_\.]+) +([0-9]+) +([0-9]+) +([0-9]+%)')
REG_COVERAGE_SUMMARY_TOTAL = re.compile('(TOTAL) +([0-9]+) +([0-9]+) +([0-9]+%)')
DEFAULT_COVERAGE_OUTPUT = '.coverage_annotation'
# HTML output specific
DIR_CSS_RESOURCE = os.path.dirname(__import__('pytest_coverage').__file__)
CSS_RESOURCE_FILES = ['header_bg.jpg', 'links.gif']
COVERAGE_TERM_HEADER = "\nCOVERAGE INFORMATION\n" \
"====================\n"
HTML_INDEX_HEADER = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>py.test - Coverage Index</title>
<style type="text/css">
table {
font-size:0.9em;
font-family: Arial, Helvetica, verdana sans-serif;
background-color:#fff;
border-collapse: collapse;
width: 500px;
}
caption {
font-size: 25px;
color: #1ba6b2;
font-weight: bold;
text-align: left;
background: url(header_bg.jpg) no-repeat top left;
padding: 10px;
margin-bottom: 2px;
}
thead th {
border-right: 1px solid #fff;
color:#fff;
text-align:center;
padding:2px;
text-transform:uppercase;
height:25px;
background-color: #a3c159;
font-weight: normal;
}
tfoot {
color:#1ba6b2;
padding:2px;
text-transform:uppercase;
font-size:1.2em;
font-weigth: bold;
margin-top:6px;
border-top: 6px solid #e9f7f6;
}
tfoot td {
text-align: left;
}
tbody tr {
background-color:#fff;
border-bottom: 1px solid #f0f0f0;
}
tbody td {
color:#414141;
padding:5px;
text-align:left;
}
tbody th {
text-align:left;
padding:2px;
}
tbody td a, tbody th a {
color:#6C8C37;
text-decoration:none;
font-weight:normal;
display:block;
background: transparent url(links.gif) no-repeat 0% 50%;
padding-left:15px;
}
tbody td a:hover, tbody th a:hover {
color:#009193;
text-decoration:none;
}
</style>
</head>
<body
<table >
<caption>Module Coverage</caption>
<tbody>
<thead>
<tr>
<th>Module</th>
<th>Statements</th>
<th>Executed</th>
<th>Coverage</th>
</tr>
</thead>'''
HTML_INDEX_FOOTER = ''' </tbody>
</table>
</body>
</html>'''
class CoverageHtmlFormatter(HtmlFormatter):
"""XXX: doc"""
def __init__(self, *args, **kwargs):
HtmlFormatter.__init__(self,*args, **kwargs)
self.annotation_infos = kwargs.get('annotation_infos')
def _highlight_lines(self, tokensource):
"""
XXX: doc
"""
hls = self.hl_lines
self.annotation_infos = [None] + self.annotation_infos
hls = [l for l, i in enumerate(self.annotation_infos) if i]
for i, (t, value) in enumerate(tokensource):
if t != 1:
yield t, value
if i + 1 in hls: # i + 1 because Python indexes start at 0
if self.annotation_infos[i+1] == "!":
yield 1, '<span style="background-color:#FFE5E5">%s</span>' \
% value
elif self.annotation_infos[i+1] == ">":
yield 1, '<span style="background-color:#CCFFEB">%s</span>' \
% value
else:
raise ValueError("HHAHA: %s" % self.annotation_infos[i+1])
else:
yield 1, value
def _rename_annotation_files(module_list, dir_annotate_output):
for m in module_list:
mod_fpath = os.path.basename(m.__file__)
if mod_fpath.endswith('pyc'):
mod_fpath = mod_fpath[:-1]
old = os.path.join(dir_annotate_output, '%s,cover'% mod_fpath)
new = os.path.join(dir_annotate_output, '%s,cover'% m.__name__)
if os.path.isfile(old):
shutil.move(old, new)
yield new
def _generate_module_coverage(mc_path, anotation_infos, src_lines):
#XXX: doc
code = "".join(src_lines)
mc_path = "%s.html" % mc_path
lexer = get_lexer_by_name("python", stripall=True)
formatter = CoverageHtmlFormatter(linenos=True, noclasses=True,
hl_lines=[1], annotation_infos=anotation_infos)
result = highlight(code, lexer, formatter)
fp = open(mc_path, 'w')
fp.write(result)
fp.close()
def _parse_modulecoverage(mc_fpath):
#XXX: doc
fd = open(mc_fpath, 'r')
anotate_infos = []
src_lines = []
for line in fd.readlines():
anotate_info = line[0:2].strip()
if not anotate_info:
anotate_info = None
src_line = line[2:]
anotate_infos.append(anotate_info)
src_lines.append(src_line)
return mc_fpath, anotate_infos, src_lines
def _parse_coverage_summary(fd):
"""Parses coverage summary output."""
if hasattr(fd, 'readlines'):
fd.seek(0)
for l in fd.readlines():
m = REG_COVERAGE_SUMMARY.match(l)
if m:
# yield name, stmts, execs, cover
yield m.group(1), m.group(2), m.group(3), m.group(4)
else:
m = REG_COVERAGE_SUMMARY_TOTAL.match(l)
if m:
# yield name, stmts, execs, cover
yield m.group(1), m.group(2), m.group(3), m.group(4)
def _get_coverage_index(mod_name, stmts, execs, cover, annotation_dir):
"""
Generates the index page where are all modulare coverage reports are
linked.
"""
if mod_name == 'TOTAL':
return '<tfoot><tr style="text-align: center;font-weigth:bold;font-size:1.2em; "><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr></tfoot>\n' % (mod_name, stmts, execs, cover)
covrep_fpath = os.path.join(annotation_dir, '%s,cover.html' % mod_name)
assert os.path.isfile(covrep_fpath) == True
fname = os.path.basename(covrep_fpath)
modlink = '<a href="%s">%s</a>' % (fname, mod_name)
return '<tr style="text-align: center;"><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (modlink, stmts, execs, cover)
class CoveragePlugin:
def pytest_addoption(self, parser):
group = parser.addgroup('coverage options')
group.addoption('-C', action='store_true', default=False,
dest = 'coverage',
help=('displays coverage information.'))
group.addoption('--coverage-html', action='store', default=False,
dest='coverage_annotation',
help='path to the coverage HTML output dir.')
group.addoption('--coverage-css-resourcesdir', action='store',
default=DIR_CSS_RESOURCE,
dest='coverage_css_ressourcedir',
help='path to dir with css-resources (%s) for '
'being copied to the HTML output dir.' % \
", ".join(CSS_RESOURCE_FILES))
def pytest_configure(self, config):
if config.getvalue('coverage'):
try:
import coverage
except ImportError:
raise config.Error("To run use the coverage option you have to install " \
"Ned Batchelder's coverage: "\
"http://nedbatchelder.com/code/modules/coverage.html")
self.coverage = coverage
self.summary = None
def pytest_terminal_summary(self, terminalreporter):
if hasattr(self, 'coverage'):
self.coverage.stop()
module_list = [sys.modules[mod] for mod in COVERAGE_MODULES]
module_list.sort()
summary_fd = StringIO()
# get coverage reports by module list
self.coverage.report(module_list, file=summary_fd)
summary = COVERAGE_TERM_HEADER + summary_fd.getvalue()
terminalreporter._tw.write(summary)
config = terminalreporter.config
dir_annotate_output = config.getvalue('coverage_annotation')
if dir_annotate_output:
if dir_annotate_output == "":
dir_annotate_output = DIR_ANNOTATE_OUTPUT
# create dir
if os.path.isdir(dir_annotate_output):
shutil.rmtree(dir_annotate_output)
os.mkdir(dir_annotate_output)
# generate annotation text files for later parsing
self.coverage.annotate(module_list, dir_annotate_output)
# generate the separate module coverage reports
for mc_fpath in _rename_annotation_files(module_list, \
dir_annotate_output):
# mc_fpath, anotate_infos, src_lines from _parse_do
_generate_module_coverage(*_parse_modulecoverage(mc_fpath))
# creating contents for the index pagee for coverage report
idxpage_html = StringIO()
idxpage_html.write(HTML_INDEX_HEADER)
total_sum = None
for args in _parse_coverage_summary(summary_fd):
# mod_name, stmts, execs, cover = args
idxpage_html.write(_get_coverage_index(*args, \
**dict(annotation_dir=dir_annotate_output)))
idxpage_html.write(HTML_INDEX_FOOTER)
idx_fpath = os.path.join(dir_annotate_output, 'index.html')
idx_fd = open(idx_fpath, 'w')
idx_fd.write(idxpage_html.getvalue())
idx_fd.close()
dir_css_resource_dir = config.getvalue('coverage_css_ressourcedir')
if dir_annotate_output and dir_css_resource_dir != "":
if not os.path.isdir(dir_css_resource_dir):
raise config.Error("CSS resource dir not found: '%s'" % \
dir_css_resource_dir)
for r in CSS_RESOURCE_FILES:
src = os.path.join(dir_css_resource_dir, r)
if os.path.isfile(src):
dest = os.path.join(dir_annotate_output, r)
shutil.copy(src, dest)
def pytest_collectstart(self, collector):
if isinstance(collector, py.__.test.pycollect.Module):
COVERAGE_MODULES.update(getattr(collector.obj,
'COVERAGE_MODULES', []))
def pytest_testrunstart(self):
print "self.coverage", self.coverage
if hasattr(self, 'coverage'):
print "START coverage"
self.coverage.erase()
self.coverage.start()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 B

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,116 +0,0 @@
#!/usr/bin/env python
"""
small utility for hot-syncing a svn repository through ssh.
uses py.execnet.
"""
import py
import sys, os
def usage():
arg0 = sys.argv[0]
print """%s [user@]remote-host:/repo/location localrepo [identity keyfile]""" % (arg0,)
def main(args):
remote = args[0]
localrepo = py.path.local(args[1])
if not localrepo.check(dir=1):
raise SystemExit("localrepo %s does not exist" %(localrepo,))
if len(args) == 3:
keyfile = py.path.local(args[2])
else:
keyfile = None
remote_host, path = remote.split(':', 1)
print "ssh-connecting to", remote_host
gw = getgateway(remote_host, keyfile)
local_rev = get_svn_youngest(localrepo)
# local protocol
# 1. client sends rev/repo -> server
# 2. server checks for newer revisions and sends dumps
# 3. client receives dumps, updates local repo
# 4. client goes back to step 1
c = gw.remote_exec("""
import py
import os
remote_rev, repopath = channel.receive()
while 1:
rev = py.process.cmdexec('svnlook youngest "%s"' % repopath)
rev = int(rev)
if rev > remote_rev:
revrange = (remote_rev+1, rev)
dumpchannel = channel.gateway.newchannel()
channel.send(revrange)
channel.send(dumpchannel)
f = os.popen(
"svnadmin dump -q --incremental -r %s:%s %s"
% (revrange[0], revrange[1], repopath), 'r')
try:
maxcount = dumpchannel.receive()
count = maxcount
while 1:
s = f.read(8192)
if not s:
raise EOFError
dumpchannel.send(s)
count = count - 1
if count <= 0:
ack = dumpchannel.receive()
count = maxcount
except EOFError:
dumpchannel.close()
remote_rev = rev
else:
# using svn-hook instead would be nice here
py.std.time.sleep(30)
""")
c.send((local_rev, path))
print "checking revisions from %d in %s" %(local_rev, remote)
while 1:
revstart, revend = c.receive()
dumpchannel = c.receive()
print "receiving revisions", revstart, "-", revend, "replaying..."
svn_load(localrepo, dumpchannel)
print "current revision", revend
def svn_load(repo, dumpchannel, maxcount=100):
# every maxcount we will send an ACK to the other
# side in order to synchronise and avoid our side
# growing buffers (py.execnet does not control
# RAM usage or receive queue sizes)
dumpchannel.send(maxcount)
f = os.popen("svnadmin load -q %s" %(repo, ), "w")
count = maxcount
for x in dumpchannel:
sys.stdout.write(".")
sys.stdout.flush()
f.write(x)
count = count - 1
if count <= 0:
dumpchannel.send(maxcount)
count = maxcount
print >>sys.stdout
f.close()
def get_svn_youngest(repo):
rev = py.process.cmdexec('svnlook youngest "%s"' % repo)
return int(rev)
def getgateway(host, keyfile=None):
return py.execnet.SshGateway(host, identity=keyfile)
if __name__ == '__main__':
if len(sys.argv) < 3:
usage()
raise SystemExit(1)
main(sys.argv[1:])

View File

@@ -1,140 +0,0 @@
"""
sysinfo.py [host1] [host2] [options]
obtain system info from remote machine.
"""
import py
import sys
optparse = py.compat.optparse
parser = optparse.OptionParser(usage=__doc__)
parser.add_option("-f", "--sshconfig", action="store", dest="ssh_config", default=None,
help="use given ssh config file, and add info all contained hosts for getting info")
parser.add_option("-i", "--ignore", action="store", dest="ignores", default=None,
help="ignore hosts (useful if the list of hostnames come from a file list)")
def parsehosts(path):
path = py.path.local(path)
l = []
rex = py.std.re.compile(r'Host\s*(\S+)')
for line in path.readlines():
m = rex.match(line)
if m is not None:
sshname, = m.groups()
l.append(sshname)
return l
class RemoteInfo:
def __init__(self, gateway):
self.gw = gateway
self._cache = {}
def exreceive(self, execstring):
if execstring not in self._cache:
channel = self.gw.remote_exec(execstring)
self._cache[execstring] = channel.receive()
return self._cache[execstring]
def getmodattr(self, modpath):
module = modpath.split(".")[0]
return self.exreceive("""
import %s
channel.send(%s)
""" %(module, modpath))
def islinux(self):
return self.getmodattr('sys.platform').find("linux") != -1
def getfqdn(self):
return self.exreceive("""
import socket
channel.send(socket.getfqdn())
""")
def getmemswap(self):
if self.islinux():
return self.exreceive("""
import commands, re
out = commands.getoutput("free")
mem = re.search(r"Mem:\s+(\S*)", out).group(1)
swap = re.search(r"Swap:\s+(\S*)", out).group(1)
channel.send((mem, swap))
""")
def getcpuinfo(self):
if self.islinux():
return self.exreceive("""
# a hyperthreaded cpu core only counts as 1, although it
# is present as 2 in /proc/cpuinfo. Counting it as 2 is
# misleading because it is *by far* not as efficient as
# two independent cores.
cpus = {}
cpuinfo = {}
f = open("/proc/cpuinfo")
lines = f.readlines()
f.close()
for line in lines + ['']:
if line.strip():
key, value = line.split(":", 1)
cpuinfo[key.strip()] = value.strip()
else:
corekey = (cpuinfo.get("physical id"),
cpuinfo.get("core id"))
cpus[corekey] = 1
numcpus = len(cpus)
model = cpuinfo.get("model name")
channel.send((numcpus, model))
""")
def debug(*args):
print >>sys.stderr, " ".join(map(str, args))
def error(*args):
debug("ERROR", args[0] + ":", *args[1:])
def getinfo(sshname, ssh_config=None, loginfo=sys.stdout):
debug("connecting to", sshname)
try:
gw = py.execnet.SshGateway(sshname, ssh_config=ssh_config)
except IOError:
error("could not get sshagteway", sshname)
else:
ri = RemoteInfo(gw)
#print "%s info:" % sshname
prefix = sshname.upper() + " "
print >>loginfo, prefix, "fqdn:", ri.getfqdn()
for attr in (
"sys.platform",
"sys.version_info",
):
loginfo.write("%s %s: " %(prefix, attr,))
loginfo.flush()
value = ri.getmodattr(attr)
loginfo.write(str(value))
loginfo.write("\n")
loginfo.flush()
memswap = ri.getmemswap()
if memswap:
mem,swap = memswap
print >>loginfo, prefix, "Memory:", mem, "Swap:", swap
cpuinfo = ri.getcpuinfo()
if cpuinfo:
numcpu, model = cpuinfo
print >>loginfo, prefix, "number of cpus:", numcpu
print >>loginfo, prefix, "cpu model", model
return ri
if __name__ == '__main__':
options, args = parser.parse_args()
hosts = list(args)
ssh_config = options.ssh_config
if ssh_config:
hosts.extend(parsehosts(ssh_config))
ignores = options.ignores or ()
if ignores:
ignores = ignores.split(",")
for host in hosts:
if host not in ignores:
getinfo(host, ssh_config=ssh_config)

485
distribute_setup.py Normal file
View File

@@ -0,0 +1,485 @@
#!python
"""Bootstrap distribute installation
If you want to use setuptools in your package's setup.py, just include this
file in the same directory with it, and add this to the top of your setup.py::
from distribute_setup import use_setuptools
use_setuptools()
If you want to require a specific version of setuptools, set a download
mirror, or use an alternate download directory, you can do so by supplying
the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools.
"""
import os
import sys
import time
import fnmatch
import tempfile
import tarfile
from distutils import log
try:
from site import USER_SITE
except ImportError:
USER_SITE = None
try:
import subprocess
def _python_cmd(*args):
args = (sys.executable,) + args
return subprocess.call(args) == 0
except ImportError:
# will be used for python 2.3
def _python_cmd(*args):
args = (sys.executable,) + args
# quoting arguments if windows
if sys.platform == 'win32':
def quote(arg):
if ' ' in arg:
return '"%s"' % arg
return arg
args = [quote(arg) for arg in args]
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
DEFAULT_VERSION = "0.6.14"
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
SETUPTOOLS_FAKED_VERSION = "0.6c11"
SETUPTOOLS_PKG_INFO = """\
Metadata-Version: 1.0
Name: setuptools
Version: %s
Summary: xxxx
Home-page: xxx
Author: xxx
Author-email: xxx
License: xxx
Description: xxx
""" % SETUPTOOLS_FAKED_VERSION
def _install(tarball):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# installing
log.warn('Installing Distribute')
if not _python_cmd('setup.py', 'install'):
log.warn('Something went wrong during the installation.')
log.warn('See the error message above.')
finally:
os.chdir(old_wd)
def _build_egg(egg, tarball, to_dir):
# extracting the tarball
tmpdir = tempfile.mkdtemp()
log.warn('Extracting in %s', tmpdir)
old_wd = os.getcwd()
try:
os.chdir(tmpdir)
tar = tarfile.open(tarball)
_extractall(tar)
tar.close()
# going in the directory
subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
os.chdir(subdir)
log.warn('Now working in %s', subdir)
# building an egg
log.warn('Building a Distribute egg in %s', to_dir)
_python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
finally:
os.chdir(old_wd)
# returning the result
log.warn(egg)
if not os.path.exists(egg):
raise IOError('Could not build the egg.')
def _do_download(version, download_base, to_dir, download_delay):
egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
% (version, sys.version_info[0], sys.version_info[1]))
if not os.path.exists(egg):
tarball = download_setuptools(version, download_base,
to_dir, download_delay)
_build_egg(egg, tarball, to_dir)
sys.path.insert(0, egg)
import setuptools
setuptools.bootstrap_install_from = egg
def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, download_delay=15, no_fake=True):
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
was_imported = 'pkg_resources' in sys.modules or \
'setuptools' in sys.modules
try:
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
if not no_fake:
_fake_setuptools()
raise ImportError
except ImportError:
return _do_download(version, download_base, to_dir, download_delay)
try:
pkg_resources.require("distribute>="+version)
return
except pkg_resources.VersionConflict:
e = sys.exc_info()[1]
if was_imported:
sys.stderr.write(
"The required version of distribute (>=%s) is not available,\n"
"and can't be installed while this script is running. Please\n"
"install a more recent version first, using\n"
"'easy_install -U distribute'."
"\n\n(Currently using %r)\n" % (version, e.args[0]))
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return _do_download(version, download_base, to_dir,
download_delay)
except pkg_resources.DistributionNotFound:
return _do_download(version, download_base, to_dir,
download_delay)
finally:
if not no_fake:
_create_fake_setuptools_pkg_info(to_dir)
def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
to_dir=os.curdir, delay=15):
"""Download distribute from a specified location and return its filename
`version` should be a valid distribute version number that is available
as an egg for download under the `download_base` URL (which should end
with a '/'). `to_dir` is the directory where the egg will be downloaded.
`delay` is the number of seconds to pause before an actual download
attempt.
"""
# making sure we use the absolute path
to_dir = os.path.abspath(to_dir)
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
tgz_name = "distribute-%s.tar.gz" % version
url = download_base + tgz_name
saveto = os.path.join(to_dir, tgz_name)
src = dst = None
if not os.path.exists(saveto): # Avoid repeated downloads
try:
log.warn("Downloading %s", url)
src = urlopen(url)
# Read/write all in one block, so we don't create a corrupt file
# if the download is interrupted.
data = src.read()
dst = open(saveto, "wb")
dst.write(data)
finally:
if src:
src.close()
if dst:
dst.close()
return os.path.realpath(saveto)
def _no_sandbox(function):
def __no_sandbox(*args, **kw):
try:
from setuptools.sandbox import DirectorySandbox
if not hasattr(DirectorySandbox, '_old'):
def violation(*args):
pass
DirectorySandbox._old = DirectorySandbox._violation
DirectorySandbox._violation = violation
patched = True
else:
patched = False
except ImportError:
patched = False
try:
return function(*args, **kw)
finally:
if patched:
DirectorySandbox._violation = DirectorySandbox._old
del DirectorySandbox._old
return __no_sandbox
def _patch_file(path, content):
"""Will backup the file then patch it"""
existing_content = open(path).read()
if existing_content == content:
# already patched
log.warn('Already patched.')
return False
log.warn('Patching...')
_rename_path(path)
f = open(path, 'w')
try:
f.write(content)
finally:
f.close()
return True
_patch_file = _no_sandbox(_patch_file)
def _same_content(path, content):
return open(path).read() == content
def _rename_path(path):
new_name = path + '.OLD.%s' % time.time()
log.warn('Renaming %s into %s', path, new_name)
os.rename(path, new_name)
return new_name
def _remove_flat_installation(placeholder):
if not os.path.isdir(placeholder):
log.warn('Unkown installation at %s', placeholder)
return False
found = False
for file in os.listdir(placeholder):
if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
found = True
break
if not found:
log.warn('Could not locate setuptools*.egg-info')
return
log.warn('Removing elements out of the way...')
pkg_info = os.path.join(placeholder, file)
if os.path.isdir(pkg_info):
patched = _patch_egg_dir(pkg_info)
else:
patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
if not patched:
log.warn('%s already patched.', pkg_info)
return False
# now let's move the files out of the way
for element in ('setuptools', 'pkg_resources.py', 'site.py'):
element = os.path.join(placeholder, element)
if os.path.exists(element):
_rename_path(element)
else:
log.warn('Could not find the %s element of the '
'Setuptools distribution', element)
return True
_remove_flat_installation = _no_sandbox(_remove_flat_installation)
def _after_install(dist):
log.warn('After install bootstrap.')
placeholder = dist.get_command_obj('install').install_purelib
_create_fake_setuptools_pkg_info(placeholder)
def _create_fake_setuptools_pkg_info(placeholder):
if not placeholder or not os.path.exists(placeholder):
log.warn('Could not find the install location')
return
pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
setuptools_file = 'setuptools-%s-py%s.egg-info' % \
(SETUPTOOLS_FAKED_VERSION, pyver)
pkg_info = os.path.join(placeholder, setuptools_file)
if os.path.exists(pkg_info):
log.warn('%s already exists', pkg_info)
return
log.warn('Creating %s', pkg_info)
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
pth_file = os.path.join(placeholder, 'setuptools.pth')
log.warn('Creating %s', pth_file)
f = open(pth_file, 'w')
try:
f.write(os.path.join(os.curdir, setuptools_file))
finally:
f.close()
_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
def _patch_egg_dir(path):
# let's check if it's already patched
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
if os.path.exists(pkg_info):
if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
log.warn('%s already patched.', pkg_info)
return False
_rename_path(path)
os.mkdir(path)
os.mkdir(os.path.join(path, 'EGG-INFO'))
pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
f = open(pkg_info, 'w')
try:
f.write(SETUPTOOLS_PKG_INFO)
finally:
f.close()
return True
_patch_egg_dir = _no_sandbox(_patch_egg_dir)
def _before_install():
log.warn('Before install bootstrap.')
_fake_setuptools()
def _under_prefix(location):
if 'install' not in sys.argv:
return True
args = sys.argv[sys.argv.index('install')+1:]
for index, arg in enumerate(args):
for option in ('--root', '--prefix'):
if arg.startswith('%s=' % option):
top_dir = arg.split('root=')[-1]
return location.startswith(top_dir)
elif arg == option:
if len(args) > index:
top_dir = args[index+1]
return location.startswith(top_dir)
if arg == '--user' and USER_SITE is not None:
return location.startswith(USER_SITE)
return True
def _fake_setuptools():
log.warn('Scanning installed packages')
try:
import pkg_resources
except ImportError:
# we're cool
log.warn('Setuptools or Distribute does not seem to be installed.')
return
ws = pkg_resources.working_set
try:
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
replacement=False))
except TypeError:
# old distribute API
setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
if setuptools_dist is None:
log.warn('No setuptools distribution found')
return
# detecting if it was already faked
setuptools_location = setuptools_dist.location
log.warn('Setuptools installation detected at %s', setuptools_location)
# if --root or --preix was provided, and if
# setuptools is not located in them, we don't patch it
if not _under_prefix(setuptools_location):
log.warn('Not patching, --root or --prefix is installing Distribute'
' in another location')
return
# let's see if its an egg
if not setuptools_location.endswith('.egg'):
log.warn('Non-egg installation')
res = _remove_flat_installation(setuptools_location)
if not res:
return
else:
log.warn('Egg installation')
pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
if (os.path.exists(pkg_info) and
_same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
log.warn('Already patched.')
return
log.warn('Patching...')
# let's create a fake egg replacing setuptools one
res = _patch_egg_dir(setuptools_location)
if not res:
return
log.warn('Patched done.')
_relaunch()
def _relaunch():
log.warn('Relaunching...')
# we have to relaunch the process
# pip marker to avoid a relaunch bug
if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
sys.argv[0] = 'setup.py'
args = [sys.executable] + sys.argv
sys.exit(subprocess.call(args))
def _extractall(self, path=".", members=None):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers().
"""
import copy
import operator
from tarfile import ExtractError
directories = []
if members is None:
members = self
for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 448 # decimal for oct 0700
self.extract(tarinfo, path)
# Reverse sort directories.
if sys.version_info < (2, 4):
def sorter(dir1, dir2):
return cmp(dir1.name, dir2.name)
directories.sort(sorter)
directories.reverse()
else:
directories.sort(key=operator.attrgetter('name'), reverse=True)
# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
except ExtractError:
e = sys.exc_info()[1]
if self.errorlevel > 1:
raise
else:
self._dbg(1, "tarfile: %s" % e)
def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall"""
tarball = download_setuptools()
_install(tarball)
if __name__ == '__main__':
main(sys.argv[1:])

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

@@ -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,14 +0,0 @@
=============
Release notes
=============
Contents:
.. toctree::
:maxdepth: 1
announce/release-1.0.2
announce/release-1.0.1
announce/release-1.0.0
announce/release-0.9.2
announce/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,70 +0,0 @@
======================
``py/bin/`` scripts
======================
The py-lib contains some scripts, most of which are
small ones (apart from ``py.test``) that help during
the python development process. If working
from a svn-checkout of py lib you may add ``py/bin``
to your shell ``PATH`` which should make the scripts
available on your command prompt.
``py.test``
===========
The ``py.test`` executable is the main entry point into the py-lib testing tool,
see the `py.test documentation`_.
.. _`py.test documentation`: test/index.html
``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.
``py.rest``
===========
Usage: ``py.rest [PATHS] [options]``
[deprecated in 1.0, will likely be separated]
Loot recursively for .txt files starting from ``PATHS`` and convert them to
html using docutils or to pdf files, if the ``--pdf`` option is used. For
conversion to PDF you will need several command line tools, on Ubuntu Linux
this is **texlive** and **texlive-extra-utils**.
``py.rest`` has some extra features over rst2html (which is shipped with
docutils). Most of these are still experimental, the one which is most likely
not going to change is the `graphviz`_ directive. With that you can embed .dot
files into your document and have them be converted to png (when outputting
html) and to eps (when outputting pdf). Otherwise the directive works mostly
like the image directive::
.. graphviz:: example.dot
:scale: 90
.. _`graphviz`: http://www.graphviz.org

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,263 +1,7 @@
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.
.. _changelog:
* added a documentation link to the new pytest_django plugin.
Changelog history
=================================
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]
.. 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,141 +0,0 @@
================================================================================
py.code: higher level python code and introspection objects
================================================================================
The :api:`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 :api:`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.
source: :source:`py/code/`
Contents of the library
=======================
Every object in the :api:`py.code` library wraps a code Python object related
to code objects, source code, frames and tracebacks: the :api:`py.code.Code`
class wraps code objects, :api:`py.code.Source` source snippets,
:api:`py.code.Traceback` exception tracebacks, :api:`py.code.Frame` frame
objects (as found in e.g. tracebacks) and :api:`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
:api:`py.code.compile()` that provides the same functionality as Python's
built-in 'compile()' function, but returns a wrapped code object.
The wrappers
============
:api:`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='rb'):"
source: :source:`py/code/code.py`
:api:`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"'
source: :source:`py/code/source.py`
:api:`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
source: :source:`py/code/traceback2.py`
:api:`py.code.Frame`
--------------------
Frame wrappers are used in :api:`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 :api:`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.local.LocalPath)
True
>>> [namevalue[0] for namevalue in frame.getargs()]
['cls', 'path']
:api:`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,290 +0,0 @@
import py
from py.__.misc.rest import convert_rest_html, strip_html_header
from py.__.misc.difftime import worded_time
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", "download.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("doc index", "test/index.html"),
self.a_docref("features", "test/features.html"),
self.a_docref("quickstart", "test/quickstart.html"),
self.a_docref("tutorials", "test/talks.html"),
self.a_docref("plugins", "test/plugin/index.html"),
self.a_docref("funcargs", "test/funcargs.html"),
self.a_docref("customize", "test/customize.html"),
),
html.div(
html.h3("supporting APIs:"),
self.a_docref("pylib index", "index.html"),
self.a_docref("py.execnet", "execnet.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
self.menubar.insert(0,
html.div("%s" % (py.version), style="font-style: italic;")
)
#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.magic.autopath().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,7 +1 @@
#XXX make work: excludedirs = ['_build']
import py
#py.test.importorskip("pygments")
pytest_plugins = ['pytest_restdoc']
rsyncdirs = ['.']
collect_ignore = ['test/attic.txt']
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

50
doc/doctest.txt Normal file
View File

@@ -0,0 +1,50 @@
doctest integration for modules and test files.
=========================================================
By default all files matching the ``test*.txt`` pattern will
be run through the python standard ``doctest`` module. You
can change the pattern by issuing::
py.test --doctest-glob='*.rst'
on the command line. You can also trigger running of doctests
from docstrings in all python modules (including regular
python test modules)::
py.test --doctest-modules
You can make these changes permanent in your project by
putting them into a conftest.py file like this::
# content of conftest.py
option_doctestmodules = True
option_doctestglob = "*.rst"
If you then have a text file like this::
# content of example.rst
hello this is a doctest
>>> x = 3
>>> x
3
and another like this::
# content of mymodule.py
def something():
""" a doctest in a docstring
>>> something()
42
"""
return 42
then you can just invoke ``py.test`` without command line options::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-66
============================= in 0.00 seconds =============================

View File

@@ -1,130 +0,0 @@
..
==============
Downloading
==============
.. _`PyPI project page`: http://pypi.python.org/pypi/py/
Latest Release, see `PyPI project page`_
using easy_install
===================================================
With a working `setuptools installation`_ you can type::
easy_install -U py
to get the latest release of the py lib. The ``-U`` switch
will trigger an upgrade if you already have an older version installed.
On Linux systems you may need to execute the command as superuser and
on Windows you might need to write down the full path to ``easy_install``.
The py lib and its tools are expected to work well on Linux,
Windows and OSX, Python versions 2.3, 2.4, 2.5 and 2.6.
**IMPORTANT NOTE**: if you are using Windows and have
0.8 versions of the py lib on your system, please download
and execute http://codespeak.net/svn/py/build/winpathclean.py
This will check that no previous files are getting in the way.
You can find out the py lib version with::
import py
print py.version
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _checkout:
.. _tarball:
Working from version control or a tarball
=================================================
To follow development or help with fixing things
for the next release, checkout the complete code
and documentation source with mercurial_::
hg clone https://bitbucket.org/hpk42/py-trunk/
This currrently contains a 1.0.x branch and the
default 'trunk' branch where mainline development
takes place. There also is a readonly subversion
checkout available::
svn co https://codespeak.net/svn/py/dist
You can also go to the python package index and
download and unpack a TAR file::
http://pypi.python.org/pypi/py/
activating checkout with setuptools
--------------------------------------------
With a working `setuptools installation`_ you can issue::
python setup.py develop
in order to work with the tools and the lib of your checkout.
.. _`no-setuptools`:
activating a checkout or tarball without setuptools
-------------------------------------------------------------
To import the py lib the ``py`` package directory needs to
be on the ``$PYTHONPATH``. If you exexute scripts directly
from ``py/bin/`` or ``py\bin\win32`` they will find their
containing py lib automatically.
It is usually a good idea to add the parent directory of the ``py`` package
directory to your ``PYTHONPATH`` and ``py/bin`` or ``py\bin\win32`` to your
system wide ``PATH`` settings. There are helper scripts that set ``PYTHONPATH`` and ``PATH`` on your system:
on windows execute::
# inside autoexec.bat or shell startup
c:\\path\to\checkout\py\env.cmd
on linux/OSX add this to your shell initialization::
# inside .bashrc
eval `python ~/path/to/checkout/py/env.py`
both of which which will get you good settings
for ``PYTHONPATH`` and ``PATH``.
note: scripts look for "nearby" py-lib
-----------------------------------------------------
Note that the `command line scripts`_ will look
for "nearby" py libs, so if you have a layout like this::
mypkg/
subpkg1/
tests/
tests/
py/
issuing ``py.test subpkg1`` will use the py lib
from that projects root directory.
.. _`command line scripts`: bin.html
Debian and RPM packages
===================================
As of August 2009 pytest/pylib 1.0 RPMs and Debian packages
are not available. You will only find 0.9 versions -
on Debian systems look for ``python-codespeak-lib``
and Dwayne Bailey has put together a Fedora `RPM`_.
If you can help with providing/upgrading distribution
packages please use of the contact_ channels in case
of questions or need for changes.
.. _contact: contact.html
.. _`RPM`: http://translate.sourceforge.net/releases/testing/fedora/pylib-0.9.2-1.fc9.noarch.rpm
.. _`setuptools installation`: http://pypi.python.org/pypi/setuptools

View File

@@ -7,9 +7,9 @@ def otherfunc(a,b):
def somefunc(x,y):
otherfunc(x,y)
def otherfunc_multi(a,b):
assert (a ==
b)
def otherfunc_multi(a,b):
assert (a ==
b)
def test_generative(param1, param2):
assert param1 * 2 < param2
@@ -37,6 +37,120 @@ class TestFailing(object):
return 42
assert not f()
class TestSpecialisedExplanations(object):
def test_eq_text(self):
assert 'spam' == 'eggs'
def test_eq_similar_text(self):
assert 'foo 1 bar' == 'foo 2 bar'
def test_eq_multiline_text(self):
assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
def test_eq_long_text(self):
a = '1'*100 + 'a' + '2'*100
b = '1'*100 + 'b' + '2'*100
assert a == b
def test_eq_long_text_multiline(self):
a = '1\n'*100 + 'a' + '2\n'*100
b = '1\n'*100 + 'b' + '2\n'*100
assert a == b
def test_eq_list(self):
assert [0, 1, 2] == [0, 1, 3]
def test_eq_list_long(self):
a = [0]*100 + [1] + [3]*100
b = [0]*100 + [2] + [3]*100
assert a == b
def test_eq_dict(self):
assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
def test_eq_set(self):
assert set([0, 10, 11, 12]) == set([0, 20, 21])
def test_eq_longer_list(self):
assert [1,2] == [1,2,3]
def test_in_list(self):
assert 1 in [0, 2, 3, 4, 5]
def test_attribute():
class Foo(object):
b = 1
i = Foo()
assert i.b == 2
def test_attribute_instance():
class Foo(object):
b = 1
assert Foo().b == 2
def test_attribute_failure():
class Foo(object):
def _get_b(self):
raise Exception('Failed to get attrib')
b = property(_get_b)
i = Foo()
assert i.b == 2
def test_attribute_multiple():
class Foo(object):
b = 1
class Bar(object):
b = 2
assert Foo().b == Bar().b
def globf(x):
return x+1
class TestRaises:
def test_raises(self):
s = 'qwe'
raises(TypeError, "int(s)")
def test_raises_doesnt(self):
raises(IOError, "int('3')")
def test_raise(self):
raise ValueError("demo error")
def test_tupleerror(self):
a,b = [1]
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
l = [1,2,3]
print ("l is %r" % l)
a,b = l.pop()
def test_some_error(self):
if namenotexi:
pass
def func1(self):
assert 41 == 42
# thanks to Matthew Scott for this test
def test_dynamic_compile_shows_nicely():
src = 'def foo():\n assert 1 == 0\n'
name = 'abc-123'
module = py.std.imp.new_module(name)
code = py.code.compile(src, name, 'exec')
py.builtin.exec_(code, module.__dict__)
py.std.sys.modules[name] = module
module.foo()
class TestMoreErrors:
def test_complex_error(self):
def f():
return 44
@@ -81,42 +195,3 @@ class TestFailing(object):
finally:
x = 0
def test_raises(self):
s = 'qwe'
raises(TypeError, "int(s)")
def test_raises_doesnt(self):
raises(IOError, "int('3')")
def test_raise(self):
raise ValueError("demo error")
def test_tupleerror(self):
a,b = [1]
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
l = [1,2,3]
print "l is", l
a,b = l.pop()
def test_some_error(self):
if namenotexi:
pass
def func1(self):
assert 41 == 42
# thanks to Matthew Scott for this test
def test_dynamic_compile_shows_nicely():
src = 'def foo():\n assert 1 == 0\n'
name = 'abc-123'
module = py.std.imp.new_module(name)
code = py.code.compile(src, name, 'exec')
exec code in module.__dict__
py.std.sys.modules[name] = module
module.foo()
def globf(x):
return x+1

View File

@@ -0,0 +1,10 @@
import pytest, py
mydir = py.path.local(__file__).dirpath()
def pytest_runtest_setup(item):
if isinstance(item, pytest.Function):
if not item.fspath.relto(mydir):
return
mod = item.getparent(pytest.Module).obj
if hasattr(mod, 'hello'):
print ("mod.hello %r" % (mod.hello,))

View File

@@ -0,0 +1,5 @@
hello = "world"
def test_func():
pass

View File

@@ -0,0 +1,14 @@
import py
failure_demo = py.path.local(__file__).dirpath('failure_demo.py')
pytest_plugins = 'pytester',
def test_failure_demo_fails_properly(testdir):
target = testdir.tmpdir.join(failure_demo.basename)
failure_demo.copy(target)
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target)
result.stdout.fnmatch_lines([
"*35 failed*"
])
assert result.ret != 0

View File

@@ -9,7 +9,7 @@ class TestStateFullThing:
cls.classcount -= 1
def setup_method(self, method):
self.id = eval(method.func_name[5:])
self.id = eval(method.__name__[5:])
def test_42(self):
assert self.classcount == 1

79
doc/example/attic.txt Normal file
View File

@@ -0,0 +1,79 @@
.. _`accept example`:
example: specifying and selecting acceptance tests
--------------------------------------------------------------
.. sourcecode:: python
# ./conftest.py
def pytest_option(parser):
group = parser.getgroup("myproject")
group.addoption("-A", dest="acceptance", action="store_true",
help="run (slow) acceptance tests")
def pytest_funcarg__accept(request):
return AcceptFuncarg(request)
class AcceptFuncarg:
def __init__(self, request):
if not request.config.option.acceptance:
pytest.skip("specify -A to run acceptance tests")
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
def run(self, cmd):
""" called by test code to execute an acceptance test. """
self.tmpdir.chdir()
return py.process.cmdexec(cmd)
and the actual test function example:
.. sourcecode:: python
def test_some_acceptance_aspect(accept):
accept.tmpdir.mkdir("somesub")
result = accept.run("ls -la")
assert "somesub" in result
If you run this test without specifying a command line option
the test will get skipped with an appropriate message. Otherwise
you can start to add convenience and test support methods
to your AcceptFuncarg and drive running of tools or
applications and provide ways to do assertions about
the output.
.. _`decorate a funcarg`:
example: decorating a funcarg in a test module
--------------------------------------------------------------
For larger scale setups it's sometimes useful to decorare
a funcarg just for a particular test module. We can
extend the `accept example`_ by putting this in our test module:
.. sourcecode:: python
def pytest_funcarg__accept(request):
# call the next factory (living in our conftest.py)
arg = request.getfuncargvalue("accept")
# create a special layout in our tempdir
arg.tmpdir.mkdir("special")
return arg
class TestSpecialAcceptance:
def test_sometest(self, accept):
assert accept.tmpdir.join("special").check()
Our module level factory will be invoked first and it can
ask its request object to call the next factory and then
decorate its result. This mechanism allows us to stay
ignorant of how/where the function argument is provided -
in our example from a `conftest plugin`_.
sidenote: the temporary directory used here are instances of
the `py.path.local`_ class which provides many of the os.path
methods in a convenient way.
.. _`py.path.local`: ../path.html#local
.. _`conftest plugin`: customize.html#conftestplugin

1
doc/example/conftest.py Normal file
View File

@@ -0,0 +1 @@
collect_ignore = ["nonpython"]

View File

@@ -1,7 +1,7 @@
def pytest_funcarg__setup(request):
return request.cached_setup(
setup=lambda: CostlySetup(),
setup=lambda: CostlySetup(),
teardown=lambda costlysetup: costlysetup.finalize(),
scope="session",
)
@@ -9,8 +9,9 @@ def pytest_funcarg__setup(request):
class CostlySetup:
def __init__(self):
import time
print ("performing costly setup")
time.sleep(5)
self.timecostly = 1
def finalize(self):
del self.timecostly
del self.timecostly

View File

@@ -0,0 +1,3 @@
def test_quick(setup):
pass

18
doc/example/index.txt Normal file
View File

@@ -0,0 +1,18 @@
.. _examples:
Usages and Examples
===========================================
Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
need more examples or have questions. Also take a look at the :ref:`comprehensive documentation <toc>` which contains many example snippets as well.
.. toctree::
:maxdepth: 2
reportingdemo.txt
simple.txt
pythoncollection.txt
mysetup.txt
parametrize.txt
nonpython.txt

View File

@@ -0,0 +1,4 @@
[pytest]
testfilepatterns =
${topdir}/tests/unit/test_${basename}
${topdir}/tests/functional/*.py

View File

@@ -0,0 +1,63 @@
"""
module containing a parametrized tests testing cross-python
serialization via the pickle module.
"""
import py
pythonlist = ['python2.4', 'python2.5', 'python2.6', 'python2.7', 'python2.8']
def pytest_generate_tests(metafunc):
if 'python1' in metafunc.funcargnames:
assert 'python2' in metafunc.funcargnames
for obj in metafunc.function.multiarg.kwargs['obj']:
for py1 in pythonlist:
for py2 in pythonlist:
metafunc.addcall(id="%s-%s-%s" % (py1, py2, obj),
param=(py1, py2, obj))
@py.test.mark.multiarg(obj=[42, {}, {1:3},])
def test_basic_objects(python1, python2, obj):
python1.dumps(obj)
python2.load_and_is_true("obj == %s" % obj)
def pytest_funcarg__python1(request):
tmpdir = request.getfuncargvalue("tmpdir")
picklefile = tmpdir.join("data.pickle")
return Python(request.param[0], picklefile)
def pytest_funcarg__python2(request):
python1 = request.getfuncargvalue("python1")
return Python(request.param[1], python1.picklefile)
def pytest_funcarg__obj(request):
return request.param[2]
class Python:
def __init__(self, version, picklefile):
self.pythonpath = py.path.local.sysfind(version)
if not self.pythonpath:
py.test.skip("%r not found" %(version,))
self.picklefile = picklefile
def dumps(self, obj):
dumpfile = self.picklefile.dirpath("dump.py")
dumpfile.write(py.code.Source("""
import pickle
f = open(%r, 'wb')
s = pickle.dump(%r, f)
f.close()
""" % (str(self.picklefile), obj)))
py.process.cmdexec("%s %s" %(self.pythonpath, dumpfile))
def load_and_is_true(self, expression):
loadfile = self.picklefile.dirpath("load.py")
loadfile.write(py.code.Source("""
import pickle
f = open(%r, 'rb')
obj = pickle.load(f)
f.close()
res = eval(%r)
if not res:
raise SystemExit(1)
""" % (str(self.picklefile), expression)))
print (loadfile)
py.process.cmdexec("%s %s" %(self.pythonpath, loadfile))

140
doc/example/mysetup.txt Normal file
View File

@@ -0,0 +1,140 @@
.. highlightlang:: python
.. _mysetup:
mysetup pattern: application specific test fixtures
==========================================================
Here is a basic useful step-by-step example for managing and interacting
with application specific test setup. The goal is to have one place
where we have the glue and test support code for bootstrapping and
configuring application objects and allow test modules and test
functions to stay ignorant of involved details.
step1: implementing the test/app-specific ``mysetup`` pattern
-------------------------------------------------------------
Let's write a simple test function using a ``mysetup`` funcarg::
# content of test_sample.py
def test_answer(mysetup):
app = mysetup.myapp()
answer = app.question()
assert answer == 42
To run this test py.test needs to find and call a factory to
obtain the required ``mysetup`` function argument. To make
an according factory findable we write down a specifically named factory
method in a :ref:`local plugin`::
# content of conftest.py
from myapp import MyApp
def pytest_funcarg__mysetup(request): # "mysetup" factory function
return MySetup()
class MySetup: # instances of this are seen by test functions
def myapp(self):
return MyApp()
To run the example we stub out a a simple ``MyApp`` application object::
# content of myapp.py
class MyApp:
def question(self):
return 6 * 9
You can now run the test::
$ py.test test_sample.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_sample.py
test_sample.py F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
mysetup = <conftest.MySetup instance at 0x16f5998>
def test_answer(mysetup):
app = mysetup.myapp()
answer = app.question()
> assert answer == 42
E assert 54 == 42
test_sample.py:4: AssertionError
========================= 1 failed in 0.02 seconds =========================
This means that our ``mysetup`` object was successfully instantiated
and ``mysetup.app()`` returned an initialized ``MyApp`` instance.
We can ask it about the question and if you are confused as to what
the concrete question or answers actually mean, please see here_.
.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
.. _`tut-cmdlineoption`:
step 2: checking a command line option and skipping tests
-----------------------------------------------------------
To add a command line option we update the ``conftest.py`` of
the previous example to add a command line option
and to offer a new mysetup method::
# content of ./conftest.py
import pytest
from myapp import MyApp
def pytest_funcarg__mysetup(request): # "mysetup" factory function
return MySetup(request)
def pytest_addoption(parser):
parser.addoption("--ssh", action="store", default=None,
help="specify ssh host to run tests with")
class MySetup:
def __init__(self, request):
self.config = request.config
def myapp(self):
return MyApp()
def getsshconnection(self):
host = self.config.option.ssh
if host is None:
pytest.skip("specify ssh host with --ssh")
return execnet.SshGateway(host)
Now any test function can use the ``mysetup.getsshconnection()`` method
like this::
# content of test_ssh.py
class TestClass:
def test_function(self, mysetup):
conn = mysetup.getsshconnection()
# work with conn
Running it yields::
$ py.test test_ssh.py -rs
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_ssh.py
test_ssh.py s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-107/conftest.py:22: specify ssh host with --ssh
======================== 1 skipped in 0.02 seconds =========================
If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.
Note that neither the ``TestClass`` nor the ``test_function`` need to
know anything about how to setup the test state. It is handled separately
in your "test setup glue" code in the ``conftest.py`` file. It is easy
to extend the ``mysetup`` object for further needs in the test code - and for use by any other test functions in the files and directories below the ``conftest.py`` file.

82
doc/example/nonpython.txt Normal file
View File

@@ -0,0 +1,82 @@
.. _`non-python tests`:
Working with non-python tests
====================================================
.. _`yaml plugin`:
a basic example for specifying tests in Yaml files
--------------------------------------------------------------
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
.. _`PyYAML`: http://pypi.python.org/pypi/PyYAML/
Here is an example ``conftest.py`` (extracted from Ali Afshnars special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yml`` files and will execute the yaml-formatted content as custom tests:
.. include:: nonpython/conftest.py
:literal:
You can create a simple example file:
.. include:: nonpython/test_simple.yml
:literal:
and if you installed `PyYAML`_ or a compatible YAML-parser you can
now execute the test specification::
nonpython $ py.test test_simple.yml
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_simple.yml
test_simple.yml .F
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
You get one dot for the passing ``sub1: sub1`` check and one failure.
Obviously in the above ``conftest.py`` you'll want to implement a more
interesting interpretation of the yaml-values. You can easily write
your own domain specific testing language this way.
.. note::
``repr_failure(excinfo)`` is called for representing test failures.
If you create custom collection nodes you can return an error
representation string of your choice. It
will be reported as a (red) string.
``reportinfo()`` is used for representing the test location and is also consulted for
reporting in ``verbose`` mode::
nonpython $ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30 -- /home/hpk/venv/0/bin/python
test path 1: /home/hpk/p/pytest/doc/example/nonpython
test_simple.yml:1: usecase: ok PASSED
test_simple.yml:1: usecase: hello FAILED
================================= FAILURES =================================
______________________________ usecase: hello ______________________________
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ py.test --collectonly
<YamlFile 'test_simple.yml'>
<YamlItem 'ok'>
<YamlItem 'hello'>

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