Compare commits
201 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448f1c0d9c | ||
|
|
346da57a8a | ||
|
|
9d92b19ed1 | ||
|
|
e2201fe3a9 | ||
|
|
45b98d6e70 | ||
|
|
29b4082b00 | ||
|
|
6ac638ba87 | ||
|
|
f2512017ea | ||
|
|
3bd3ba133f | ||
|
|
be249dcfe5 | ||
|
|
45afb1b7d1 | ||
|
|
922a283f99 | ||
|
|
7e857e9068 | ||
|
|
172d46abd0 | ||
|
|
ac9192e4f8 | ||
|
|
b490047b1c | ||
|
|
ad785a476c | ||
|
|
d37af98db3 | ||
|
|
4316cf2121 | ||
|
|
fb6fc673b8 | ||
|
|
eaec527a60 | ||
|
|
2bc4065a00 | ||
|
|
5c32421f2e | ||
|
|
fab7615c8a | ||
|
|
2315de8321 | ||
|
|
25711a0879 | ||
|
|
0e05a4fbcf | ||
|
|
8675cf640d | ||
|
|
8b211983ff | ||
|
|
661a8a4a92 | ||
|
|
abe080c6b4 | ||
|
|
574d230c22 | ||
|
|
88c5299a94 | ||
|
|
09933b8b04 | ||
|
|
fb1b1d9aae | ||
|
|
68a08840e1 | ||
|
|
41b8a03b05 | ||
|
|
fba2079292 | ||
|
|
9675b0f65c | ||
|
|
c426a67b0e | ||
|
|
6ca3c980bf | ||
|
|
5bd34f8ecc | ||
|
|
7636dc76e0 | ||
|
|
c5dee7b549 | ||
|
|
643ab120f4 | ||
|
|
f86c8469f5 | ||
|
|
22335acd09 | ||
|
|
8b866aa065 | ||
|
|
2c4964d290 | ||
|
|
a70293fdb7 | ||
|
|
43113f9a9d | ||
|
|
650c3bcfde | ||
|
|
ade9b9aa8e | ||
|
|
7576b3c7d0 | ||
|
|
85415135a4 | ||
|
|
3cc8697744 | ||
|
|
703da22831 | ||
|
|
14ceaf2459 | ||
|
|
f3bc197afb | ||
|
|
aafe6a8e34 | ||
|
|
46b1348b79 | ||
|
|
709da3fe84 | ||
|
|
a59c2c9e17 | ||
|
|
6096aeca53 | ||
|
|
8cd68494bf | ||
|
|
dd4b252749 | ||
|
|
59067684cd | ||
|
|
81f4a07548 | ||
|
|
50c8218501 | ||
|
|
814c6c70f1 | ||
|
|
85118e9019 | ||
|
|
c2cdc66eca | ||
|
|
bc66cd85b1 | ||
|
|
639f35bbc4 | ||
|
|
8c683acad1 | ||
|
|
dc8225afea | ||
|
|
8713f4ba60 | ||
|
|
c40dc9f779 | ||
|
|
d1684e8052 | ||
|
|
c25ea2cbe2 | ||
|
|
6a523b4f59 | ||
|
|
fb043c355e | ||
|
|
0ef23dd31f | ||
|
|
c13fa886d9 | ||
|
|
79ac8c6681 | ||
|
|
418cd482b1 | ||
|
|
491f58ab26 | ||
|
|
df85ddf0d2 | ||
|
|
e7c8fc7db9 | ||
|
|
92f8eef836 | ||
|
|
758b5e3511 | ||
|
|
e91dc7c895 | ||
|
|
4e8b9fab3c | ||
|
|
d105e75d87 | ||
|
|
46950ef19a | ||
|
|
407ca5b120 | ||
|
|
a4fe63c08d | ||
|
|
fefdca5787 | ||
|
|
c7d120ec1c | ||
|
|
ae8ee08ac0 | ||
|
|
1707168b62 | ||
|
|
4fcd346838 | ||
|
|
aa7f7a1c71 | ||
|
|
48b76c7544 | ||
|
|
d52ff3e2b9 | ||
|
|
f286a02582 | ||
|
|
c6e3606c6b | ||
|
|
f4eb15632d | ||
|
|
d027f9d546 | ||
|
|
8de50347fb | ||
|
|
4b4a2c8162 | ||
|
|
29d58ffdf2 | ||
|
|
9ea58242d4 | ||
|
|
8772b8c928 | ||
|
|
8e81ed693a | ||
|
|
d853d9a9af | ||
|
|
57a3d4d6d8 | ||
|
|
8f6477f695 | ||
|
|
2618e3640f | ||
|
|
43de6c270f | ||
|
|
ce1b456762 | ||
|
|
332bceeb7a | ||
|
|
e3b2792677 | ||
|
|
67859158d4 | ||
|
|
5dfce4a0ca | ||
|
|
6c90059342 | ||
|
|
5690beab5a | ||
|
|
8bc9fdc8d3 | ||
|
|
00dee742b0 | ||
|
|
5e31624315 | ||
|
|
5e311d3bfc | ||
|
|
4b7293428b | ||
|
|
6fdcecb864 | ||
|
|
f63ff5267c | ||
|
|
5498fe960f | ||
|
|
4c885cf0d2 | ||
|
|
326b63adf8 | ||
|
|
70dc7a976d | ||
|
|
89a98e3276 | ||
|
|
410438f187 | ||
|
|
8dc4e732f0 | ||
|
|
e98057130d | ||
|
|
70d22fbe9a | ||
|
|
56b40ebd75 | ||
|
|
5f75c5851f | ||
|
|
606ea870f0 | ||
|
|
e56838cb6c | ||
|
|
e22d3e03fe | ||
|
|
d53feaf6f0 | ||
|
|
914f689ee8 | ||
|
|
971f34147a | ||
|
|
16b4f54545 | ||
|
|
abb07fc732 | ||
|
|
cf6949c9a3 | ||
|
|
2f984e0c23 | ||
|
|
0a7237b72f | ||
|
|
f684a9ed56 | ||
|
|
196cece338 | ||
|
|
241ff0b43a | ||
|
|
411e9b136b | ||
|
|
96521ada68 | ||
|
|
7cf8afef47 | ||
|
|
657522b629 | ||
|
|
dd199d255c | ||
|
|
89d6defd68 | ||
|
|
c4d761fe99 | ||
|
|
bf3d9f3737 | ||
|
|
32a67f9622 | ||
|
|
d438a0bd83 | ||
|
|
d3645758ea | ||
|
|
15b9e8ed7d | ||
|
|
ee64da4bad | ||
|
|
4fe13e59a7 | ||
|
|
250160b4b0 | ||
|
|
f423ce9c01 | ||
|
|
491c05cea7 | ||
|
|
e02d22aa4f | ||
|
|
c0910abf2f | ||
|
|
b061e71da9 | ||
|
|
fa412675fc | ||
|
|
0bb84abca7 | ||
|
|
f5decc90ca | ||
|
|
7fc2f8786f | ||
|
|
76cede83c0 | ||
|
|
993efe927b | ||
|
|
9c4f6791e5 | ||
|
|
7ba8fee3dc | ||
|
|
265b7458cb | ||
|
|
dc3e39e95c | ||
|
|
4f2166c997 | ||
|
|
e0c128beec | ||
|
|
bf039fea74 | ||
|
|
78be3db9bb | ||
|
|
aae89cd021 | ||
|
|
9ac818fb5c | ||
|
|
9e6dfaefd9 | ||
|
|
c742e47de0 | ||
|
|
95ddd5059f | ||
|
|
b6815538c5 | ||
|
|
ea936213bc | ||
|
|
7e65f346f4 |
4
.hgtags
4
.hgtags
@@ -39,3 +39,7 @@ e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
|
||||
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
|
||||
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
|
||||
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
|
||||
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
|
||||
363e5a5a59c803e6bc176a6f9cc4bf1a1ca2dab0 2.0.3
|
||||
e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
|
||||
5864412c6f3c903384243bd315639d101d7ebc67 2.1.2
|
||||
|
||||
17
AUTHORS
17
AUTHORS
@@ -1,16 +1,17 @@
|
||||
Holger Krekel, holger at merlinux eu
|
||||
Benjamin Peterson, benjamin at python org
|
||||
Ronny Pfannschmidt, Ronny.Pfannschmidt at gmx de
|
||||
Guido Wesdorp, johnny at johnnydebris net
|
||||
Samuele Pedroni, pedronis at openend se
|
||||
Carl Friedrich Bolz, cfbolz at gmx de
|
||||
Armin Rigo, arigo at tunes org
|
||||
Maciek Fijalkowski, fijal at genesilico pl
|
||||
Brian Dorsey, briandorsey at gmail com
|
||||
merlinux GmbH, Germany, office at merlinux eu
|
||||
|
||||
Contributors include::
|
||||
|
||||
Ronny Pfannschmidt
|
||||
Benjamin Peterson
|
||||
Floris Bruynooghe
|
||||
Samuele Pedroni
|
||||
Carl Friedrich Bolz
|
||||
Armin Rigo
|
||||
Maciek Fijalkowski
|
||||
Guido Wesdorp
|
||||
Brian Dorsey
|
||||
Ross Lawley
|
||||
Ralf Schmitt
|
||||
Chris Lamb
|
||||
|
||||
52
CHANGELOG
52
CHANGELOG
@@ -1,3 +1,55 @@
|
||||
Changes between 2.1.2 and 2.1.3
|
||||
----------------------------------------
|
||||
|
||||
- fix issue79: assertion rewriting failed on some comparisons in boolops
|
||||
- correctly handle zero length arguments (a la pytest '')
|
||||
- fix issue67 / junitxml now contains correct test durations, thanks ronny
|
||||
- fix issue75 / skipping test failure on jython
|
||||
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
|
||||
|
||||
Changes between 2.1.1 and 2.1.2
|
||||
----------------------------------------
|
||||
|
||||
- fix assertion rewriting on files with windows newlines on some Python versions
|
||||
- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
|
||||
- fix issue69 / assertion rewriting fixed on some boolean operations
|
||||
- fix issue68 / packages now work with assertion rewriting
|
||||
- fix issue66: use different assertion rewriting caches when the -O option is passed
|
||||
- don't try assertion rewriting on Jython, use reinterp
|
||||
|
||||
Changes between 2.1.0 and 2.1.1
|
||||
----------------------------------------------
|
||||
|
||||
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
|
||||
- fix issue60 / fix error conditions involving the creation of __pycache__
|
||||
- fix issue63 / assertion rewriting on inserts involving strings containing '%'
|
||||
- fix assertion rewriting on calls with a ** arg
|
||||
- don't cache rewritten modules if bytecode generation is disabled
|
||||
- fix assertion rewriting in read-only directories
|
||||
- fix issue59: provide system-out/err tags for junitxml output
|
||||
- fix issue61: assertion rewriting on boolean operations with 3 or more operands
|
||||
- you can now build a man page with "cd doc ; make man"
|
||||
|
||||
Changes between 2.0.3 and 2.1.0.DEV
|
||||
----------------------------------------------
|
||||
|
||||
- fix issue53 call nosestyle setup functions with correct ordering
|
||||
- fix issue58 and issue59: new assertion code fixes
|
||||
- merge Benjamin's assertionrewrite branch: now assertions
|
||||
for test modules on python 2.6 and above are done by rewriting
|
||||
the AST and saving the pyc file before the test module is imported.
|
||||
see doc/assert.txt for more info.
|
||||
- fix issue43: improve doctests with better traceback reporting on
|
||||
unexpected exceptions
|
||||
- fix issue47: timing output in junitxml for test cases is now correct
|
||||
- fix issue48: typo in MarkInfo repr leading to exception
|
||||
- fix issue49: avoid confusing error when initizaliation partially fails
|
||||
- fix issue44: env/username expansion for junitxml file path
|
||||
- show releaselevel information in test runs for pypy
|
||||
- reworked doc pages for better navigation and PDF generation
|
||||
- report KeyboardInterrupt even if interrupted during session startup
|
||||
- fix issue 35 - provide PDF doc version and download link from index page
|
||||
|
||||
Changes between 2.0.2 and 2.0.3
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
77
ISSUES.txt
77
ISSUES.txt
@@ -7,17 +7,27 @@ tags: bug 2.4 core xdist
|
||||
the protocol now - setup/teardown is called at module level.
|
||||
consider making calling of setup/teardown configurable
|
||||
|
||||
profiling / hook call optimization
|
||||
-------------------------------------
|
||||
tags: enhancement 2.1
|
||||
optimizations
|
||||
---------------------------------------------------------------
|
||||
tags: 2.4 core
|
||||
|
||||
bench/bench.py reveals that for very quick running
|
||||
unit tests the hook architecture is a bit slow.
|
||||
Profile and improve hook calls.
|
||||
- look at ihook optimization such that all lookups for
|
||||
hooks relating to the same fspath are cached.
|
||||
|
||||
fix start/finish partial finailization problem
|
||||
---------------------------------------------------------------
|
||||
tags: bug core
|
||||
|
||||
if a configure/runtest_setup/sessionstart/... hook invocation partially
|
||||
fails the sessionfinishes is not called. Each hook implementation
|
||||
should better be repsonsible for registering a cleanup/finalizer
|
||||
appropriately to avoid this issue. Moreover/Alternatively, we could
|
||||
record which implementations of a hook succeeded and only call their
|
||||
teardown.
|
||||
|
||||
do early-teardown of test modules
|
||||
-----------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
currently teardowns are called when the next tests is setup
|
||||
except for the function/method level where interally
|
||||
@@ -29,7 +39,7 @@ prints of teardown-code appear in the setup of the next test.
|
||||
|
||||
consider and document __init__ file usage in test directories
|
||||
---------------------------------------------------------------
|
||||
tags: bug 2.1 core
|
||||
tags: bug 2.2 core
|
||||
|
||||
Currently, a test module is imported with its fully qualified
|
||||
package path, determined by checking __init__ files upwards.
|
||||
@@ -44,7 +54,7 @@ certain scenarios makes sense.
|
||||
|
||||
relax requirement to have tests/testing contain an __init__
|
||||
----------------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
bb: http://bitbucket.org/hpk42/py-trunk/issue/64
|
||||
|
||||
A local test run of a "tests" directory may work
|
||||
@@ -55,25 +65,24 @@ i.e. port the nose-logic of unloading a test module.
|
||||
|
||||
customize test function collection
|
||||
-------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
- introduce py.test.mark.nocollect for not considering a function for
|
||||
test collection at all. maybe also introduce a py.test.mark.test to
|
||||
explicitely mark a function to become a tested one. Lookup JUnit ways
|
||||
of tagging tests.
|
||||
|
||||
- allow an easy way to customize "test_", "Test" prefixes for file paths
|
||||
and test function/class names. the current customizable Item requires
|
||||
too much code/concepts to influence this collection matching.
|
||||
maybe introduce pytest_pycollect_filters = {
|
||||
'file': 'test*.py',
|
||||
'function': 'test*',
|
||||
'class': 'Test*',
|
||||
}
|
||||
introduce pytest.mark.importorskip
|
||||
-------------------------------------------------------
|
||||
tags: feature 2.2
|
||||
|
||||
in addition to the imperative pytest.importorskip also introduce
|
||||
a pytest.mark.importorskip so that the test count is more correct.
|
||||
|
||||
|
||||
introduce py.test.mark.platform
|
||||
-------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
Introduce nice-to-spell platform-skipping, examples:
|
||||
|
||||
@@ -90,7 +99,7 @@ interpreter versions.
|
||||
|
||||
pytest.mark.xfail signature change
|
||||
-------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
change to pytest.mark.xfail(reason, (optional)condition)
|
||||
to better implement the word meaning. It also signals
|
||||
@@ -100,7 +109,7 @@ Compatibility? Maybe rename to "pytest.mark.xfail"?
|
||||
|
||||
introduce py.test.mark registration
|
||||
-----------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
introduce a hook that allows to register a named mark decorator
|
||||
with documentation and add "py.test --marks" to get
|
||||
@@ -109,7 +118,7 @@ definitions.
|
||||
|
||||
allow to non-intrusively apply skipfs/xfail/marks
|
||||
---------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
use case: mark a module or directory structures
|
||||
to be skipped on certain platforms (i.e. no import
|
||||
@@ -120,14 +129,14 @@ from conftests or plugins.
|
||||
|
||||
explicit referencing of conftest.py files
|
||||
-----------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
allow to name conftest.py files (in sub directories) that should
|
||||
be imported early, as to include command line options.
|
||||
|
||||
improve central py.test ini file
|
||||
----------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
introduce more declarative configuration options:
|
||||
- (to-be-collected test directories)
|
||||
@@ -138,7 +147,7 @@ introduce more declarative configuration options:
|
||||
|
||||
new documentation
|
||||
----------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
- logo py.test
|
||||
- examples for unittest or functional testing
|
||||
@@ -149,7 +158,7 @@ tags: feature 2.1
|
||||
|
||||
generalize parametrized testing to generate combinations
|
||||
-------------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
think about extending metafunc.addcall or add a new method to allow to
|
||||
generate tests with combinations of all generated versions - what to do
|
||||
@@ -164,7 +173,7 @@ of values for a given function argument.
|
||||
|
||||
have imported module mismatch honour relative paths
|
||||
--------------------------------------------------------
|
||||
tags: bug 2.1
|
||||
tags: bug 2.2
|
||||
|
||||
With 1.1.1 py.test fails at least on windows if an import
|
||||
is relative and compared against an absolute conftest.py
|
||||
@@ -172,7 +181,7 @@ path. Normalize.
|
||||
|
||||
call termination with small timeout
|
||||
-------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
test: testing/pytest/dist/test_dsession.py - test_terminate_on_hanging_node
|
||||
|
||||
Call gateway group termination with a small timeout if available.
|
||||
@@ -180,7 +189,7 @@ Should make dist-testing less likely to leave lost processes.
|
||||
|
||||
consider globals: py.test.ensuretemp and config
|
||||
--------------------------------------------------------------
|
||||
tags: experimental-wish 2.1
|
||||
tags: experimental-wish 2.2
|
||||
|
||||
consider deprecating py.test.ensuretemp and py.test.config
|
||||
to further reduce py.test globality. Also consider
|
||||
@@ -189,7 +198,7 @@ a plugin rather than being there from the start.
|
||||
|
||||
consider allowing funcargs for setup methods
|
||||
--------------------------------------------------------------
|
||||
tags: experimental-wish 2.1
|
||||
tags: experimental-wish 2.2
|
||||
|
||||
Users have expressed the wish to have funcargs available to setup
|
||||
functions. Experiment with allowing funcargs there - it might
|
||||
@@ -205,7 +214,7 @@ setup_module -> request has no request.cls
|
||||
|
||||
consider pytest_addsyspath hook
|
||||
-----------------------------------------
|
||||
tags: 2.1
|
||||
tags: 2.2
|
||||
|
||||
py.test could call a new pytest_addsyspath() in order to systematically
|
||||
allow manipulation of sys.path and to inhibit it via --no-addsyspath
|
||||
@@ -217,7 +226,7 @@ and pytest_configure.
|
||||
|
||||
show plugin information in test header
|
||||
----------------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
Now that external plugins are becoming more numerous
|
||||
it would be useful to have external plugins along with
|
||||
@@ -225,7 +234,7 @@ their versions displayed as a header line.
|
||||
|
||||
deprecate global py.test.config usage
|
||||
----------------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
py.test.ensuretemp and py.test.config are probably the last
|
||||
objects containing global state. Often using them is not
|
||||
@@ -235,7 +244,7 @@ as others.
|
||||
|
||||
remove deprecated bits in collect.py
|
||||
-------------------------------------------------------------------
|
||||
tags: feature 2.1
|
||||
tags: feature 2.2
|
||||
|
||||
In an effort to further simplify code, review and remove deprecated bits
|
||||
in collect.py. Probably good:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#
|
||||
__version__ = '2.0.3'
|
||||
__version__ = '2.1.3'
|
||||
|
||||
119
_pytest/assertion/__init__.py
Normal file
119
_pytest/assertion/__init__.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
support for presenting detailed information in failing assertions.
|
||||
"""
|
||||
import py
|
||||
import sys
|
||||
import pytest
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
from _pytest.assertion import util
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("debugconfig")
|
||||
group.addoption('--assert', action="store", dest="assertmode",
|
||||
choices=("rewrite", "reinterp", "plain",),
|
||||
default="rewrite", metavar="MODE",
|
||||
help="""control assertion debugging tools.
|
||||
'plain' performs no assertion debugging.
|
||||
'reinterp' reinterprets assert statements after they failed to provide assertion expression information.
|
||||
'rewrite' (the default) rewrites assert statements in test modules on import
|
||||
to provide assert expression information. """)
|
||||
group.addoption('--no-assert', action="store_true", default=False,
|
||||
dest="noassert", help="DEPRECATED equivalent to --assert=plain")
|
||||
group.addoption('--nomagic', action="store_true", default=False,
|
||||
dest="nomagic", help="DEPRECATED equivalent to --assert=plain")
|
||||
|
||||
class AssertionState:
|
||||
"""State for the assertion plugin."""
|
||||
|
||||
def __init__(self, config, mode):
|
||||
self.mode = mode
|
||||
self.trace = config.trace.root.get("assertion")
|
||||
|
||||
def pytest_configure(config):
|
||||
mode = config.getvalue("assertmode")
|
||||
if config.getvalue("noassert") or config.getvalue("nomagic"):
|
||||
mode = "plain"
|
||||
if mode == "rewrite":
|
||||
try:
|
||||
import ast
|
||||
except ImportError:
|
||||
mode = "reinterp"
|
||||
else:
|
||||
if sys.platform.startswith('java'):
|
||||
mode = "reinterp"
|
||||
if mode != "plain":
|
||||
_load_modules(mode)
|
||||
m = monkeypatch()
|
||||
config._cleanup.append(m.undo)
|
||||
m.setattr(py.builtin.builtins, 'AssertionError',
|
||||
reinterpret.AssertionError)
|
||||
hook = None
|
||||
if mode == "rewrite":
|
||||
hook = rewrite.AssertionRewritingHook()
|
||||
sys.meta_path.append(hook)
|
||||
warn_about_missing_assertion(mode)
|
||||
config._assertstate = AssertionState(config, mode)
|
||||
config._assertstate.hook = hook
|
||||
config._assertstate.trace("configured with mode set to %r" % (mode,))
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
hook = config._assertstate.hook
|
||||
if hook is not None:
|
||||
sys.meta_path.remove(hook)
|
||||
|
||||
def pytest_collection(session):
|
||||
# this hook is only called when test modules are collected
|
||||
# so for example not in the master process of pytest-xdist
|
||||
# (which does not collect test modules)
|
||||
hook = session.config._assertstate.hook
|
||||
if hook is not None:
|
||||
hook.set_session(session)
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
def callbinrepr(op, left, right):
|
||||
hook_result = item.ihook.pytest_assertrepr_compare(
|
||||
config=item.config, op=op, left=left, right=right)
|
||||
for new_expl in hook_result:
|
||||
if new_expl:
|
||||
res = '\n~'.join(new_expl)
|
||||
if item.config.getvalue("assertmode") == "rewrite":
|
||||
# The result will be fed back a python % formatting
|
||||
# operation, which will fail if there are extraneous
|
||||
# '%'s in the string. Escape them here.
|
||||
res = res.replace("%", "%%")
|
||||
return res
|
||||
util._reprcompare = callbinrepr
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
util._reprcompare = None
|
||||
|
||||
def pytest_sessionfinish(session):
|
||||
hook = session.config._assertstate.hook
|
||||
if hook is not None:
|
||||
hook.session = None
|
||||
|
||||
def _load_modules(mode):
|
||||
"""Lazily import assertion related code."""
|
||||
global rewrite, reinterpret
|
||||
from _pytest.assertion import reinterpret
|
||||
if mode == "rewrite":
|
||||
from _pytest.assertion import rewrite
|
||||
|
||||
def warn_about_missing_assertion(mode):
|
||||
try:
|
||||
assert False
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
if mode == "rewrite":
|
||||
specifically = ("assertions which are not in test modules "
|
||||
"will be ignored")
|
||||
else:
|
||||
specifically = "failing tests may report as passing"
|
||||
|
||||
sys.stderr.write("WARNING: " + specifically +
|
||||
" because assert statements are not executed "
|
||||
"by the underlying Python interpreter "
|
||||
"(are you using python -O?)\n")
|
||||
|
||||
pytest_assertrepr_compare = util.assertrepr_compare
|
||||
333
_pytest/assertion/newinterpret.py
Normal file
333
_pytest/assertion/newinterpret.py
Normal file
@@ -0,0 +1,333 @@
|
||||
"""
|
||||
Find intermediate evalutation results in assert statements through builtin AST.
|
||||
This should replace oldinterpret.py eventually.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import ast
|
||||
|
||||
import py
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.reinterpret import BuiltinAssertionError
|
||||
|
||||
|
||||
if sys.platform.startswith("java") and sys.version_info < (2, 5, 2):
|
||||
# See http://bugs.jython.org/issue1497
|
||||
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
|
||||
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
|
||||
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
|
||||
"List", "Tuple")
|
||||
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
|
||||
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
|
||||
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
|
||||
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
|
||||
_expr_nodes = set(getattr(ast, name) for name in _exprs)
|
||||
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
|
||||
def _is_ast_expr(node):
|
||||
return node.__class__ in _expr_nodes
|
||||
def _is_ast_stmt(node):
|
||||
return node.__class__ in _stmt_nodes
|
||||
else:
|
||||
def _is_ast_expr(node):
|
||||
return isinstance(node, ast.expr)
|
||||
def _is_ast_stmt(node):
|
||||
return isinstance(node, ast.stmt)
|
||||
|
||||
|
||||
class Failure(Exception):
|
||||
"""Error found while interpreting AST."""
|
||||
|
||||
def __init__(self, explanation=""):
|
||||
self.cause = sys.exc_info()
|
||||
self.explanation = explanation
|
||||
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
mod = ast.parse(source)
|
||||
visitor = DebugInterpreter(frame)
|
||||
try:
|
||||
visitor.visit(mod)
|
||||
except Failure:
|
||||
failure = sys.exc_info()[1]
|
||||
return getfailure(failure)
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --assert=plain)")
|
||||
|
||||
def run(offending_line, frame=None):
|
||||
if frame is None:
|
||||
frame = py.code.Frame(sys._getframe(1))
|
||||
return interpret(offending_line, frame)
|
||||
|
||||
def getfailure(e):
|
||||
explanation = util.format_explanation(e.explanation)
|
||||
value = e.cause[1]
|
||||
if str(value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.cause[0].__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
operator_map = {
|
||||
ast.BitOr : "|",
|
||||
ast.BitXor : "^",
|
||||
ast.BitAnd : "&",
|
||||
ast.LShift : "<<",
|
||||
ast.RShift : ">>",
|
||||
ast.Add : "+",
|
||||
ast.Sub : "-",
|
||||
ast.Mult : "*",
|
||||
ast.Div : "/",
|
||||
ast.FloorDiv : "//",
|
||||
ast.Mod : "%",
|
||||
ast.Eq : "==",
|
||||
ast.NotEq : "!=",
|
||||
ast.Lt : "<",
|
||||
ast.LtE : "<=",
|
||||
ast.Gt : ">",
|
||||
ast.GtE : ">=",
|
||||
ast.Pow : "**",
|
||||
ast.Is : "is",
|
||||
ast.IsNot : "is not",
|
||||
ast.In : "in",
|
||||
ast.NotIn : "not in"
|
||||
}
|
||||
|
||||
unary_map = {
|
||||
ast.Not : "not %s",
|
||||
ast.Invert : "~%s",
|
||||
ast.USub : "-%s",
|
||||
ast.UAdd : "+%s"
|
||||
}
|
||||
|
||||
|
||||
class DebugInterpreter(ast.NodeVisitor):
|
||||
"""Interpret AST nodes to gleam useful debugging information. """
|
||||
|
||||
def __init__(self, frame):
|
||||
self.frame = frame
|
||||
|
||||
def generic_visit(self, node):
|
||||
# Fallback when we don't have a special implementation.
|
||||
if _is_ast_expr(node):
|
||||
mod = ast.Expression(node)
|
||||
co = self._compile(mod)
|
||||
try:
|
||||
result = self.frame.eval(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
explanation = self.frame.repr(result)
|
||||
return explanation, result
|
||||
elif _is_ast_stmt(node):
|
||||
mod = ast.Module([node])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co)
|
||||
except Exception:
|
||||
raise Failure()
|
||||
return None, None
|
||||
else:
|
||||
raise AssertionError("can't handle %s" %(node,))
|
||||
|
||||
def _compile(self, source, mode="eval"):
|
||||
return compile(source, "<assertion interpretation>", mode)
|
||||
|
||||
def visit_Expr(self, expr):
|
||||
return self.visit(expr.value)
|
||||
|
||||
def visit_Module(self, mod):
|
||||
for stmt in mod.body:
|
||||
self.visit(stmt)
|
||||
|
||||
def visit_Name(self, name):
|
||||
explanation, result = self.generic_visit(name)
|
||||
# See if the name is local.
|
||||
source = "%r in locals() is not globals()" % (name.id,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
local = self.frame.eval(co)
|
||||
except Exception:
|
||||
# have to assume it isn't
|
||||
local = None
|
||||
if local is None or not self.frame.is_true(local):
|
||||
return name.id, result
|
||||
return explanation, result
|
||||
|
||||
def visit_Compare(self, comp):
|
||||
left = comp.left
|
||||
left_explanation, left_result = self.visit(left)
|
||||
for op, next_op in zip(comp.ops, comp.comparators):
|
||||
next_explanation, next_result = self.visit(next_op)
|
||||
op_symbol = operator_map[op.__class__]
|
||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||
next_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
try:
|
||||
if not self.frame.is_true(result):
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
break
|
||||
left_explanation, left_result = next_explanation, next_result
|
||||
|
||||
if util._reprcompare is not None:
|
||||
res = util._reprcompare(op_symbol, left_result, next_result)
|
||||
if res:
|
||||
explanation = res
|
||||
return explanation, result
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
is_or = isinstance(boolop.op, ast.Or)
|
||||
explanations = []
|
||||
for operand in boolop.values:
|
||||
explanation, result = self.visit(operand)
|
||||
explanations.append(explanation)
|
||||
if result == is_or:
|
||||
break
|
||||
name = is_or and " or " or " and "
|
||||
explanation = "(" + name.join(explanations) + ")"
|
||||
return explanation, result
|
||||
|
||||
def visit_UnaryOp(self, unary):
|
||||
pattern = unary_map[unary.op.__class__]
|
||||
operand_explanation, operand_result = self.visit(unary.operand)
|
||||
explanation = pattern % (operand_explanation,)
|
||||
co = self._compile(pattern % ("__exprinfo_expr",))
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=operand_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_BinOp(self, binop):
|
||||
left_explanation, left_result = self.visit(binop.left)
|
||||
right_explanation, right_result = self.visit(binop.right)
|
||||
symbol = operator_map[binop.op.__class__]
|
||||
explanation = "(%s %s %s)" % (left_explanation, symbol,
|
||||
right_explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_left=left_result,
|
||||
__exprinfo_right=right_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Call(self, call):
|
||||
func_explanation, func = self.visit(call.func)
|
||||
arg_explanations = []
|
||||
ns = {"__exprinfo_func" : func}
|
||||
arguments = []
|
||||
for arg in call.args:
|
||||
arg_explanation, arg_result = self.visit(arg)
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append(arg_name)
|
||||
arg_explanations.append(arg_explanation)
|
||||
for keyword in call.keywords:
|
||||
arg_explanation, arg_result = self.visit(keyword.value)
|
||||
arg_name = "__exprinfo_%s" % (len(ns),)
|
||||
ns[arg_name] = arg_result
|
||||
keyword_source = "%s=%%s" % (keyword.arg)
|
||||
arguments.append(keyword_source % (arg_name,))
|
||||
arg_explanations.append(keyword_source % (arg_explanation,))
|
||||
if call.starargs:
|
||||
arg_explanation, arg_result = self.visit(call.starargs)
|
||||
arg_name = "__exprinfo_star"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("*%s" % (arg_name,))
|
||||
arg_explanations.append("*%s" % (arg_explanation,))
|
||||
if call.kwargs:
|
||||
arg_explanation, arg_result = self.visit(call.kwargs)
|
||||
arg_name = "__exprinfo_kwds"
|
||||
ns[arg_name] = arg_result
|
||||
arguments.append("**%s" % (arg_name,))
|
||||
arg_explanations.append("**%s" % (arg_explanation,))
|
||||
args_explained = ", ".join(arg_explanations)
|
||||
explanation = "%s(%s)" % (func_explanation, args_explained)
|
||||
args = ", ".join(arguments)
|
||||
source = "__exprinfo_func(%s)" % (args,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, **ns)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
rep = self.frame.repr(result)
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def _is_builtin_name(self, name):
|
||||
pattern = "%r not in globals() and %r not in locals()"
|
||||
source = pattern % (name.id, name.id)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
return self.frame.eval(co)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def visit_Attribute(self, attr):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
source_explanation, source_result = self.visit(attr.value)
|
||||
explanation = "%s.%s" % (source_explanation, attr.attr)
|
||||
source = "__exprinfo_expr.%s" % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
result = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
|
||||
self.frame.repr(result),
|
||||
source_explanation, attr.attr)
|
||||
# Check if the attr is from an instance.
|
||||
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
|
||||
source = source % (attr.attr,)
|
||||
co = self._compile(source)
|
||||
try:
|
||||
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
|
||||
except Exception:
|
||||
from_instance = None
|
||||
if from_instance is None or self.frame.is_true(from_instance):
|
||||
rep = self.frame.repr(result)
|
||||
pattern = "%s\n{%s = %s\n}"
|
||||
explanation = pattern % (rep, rep, explanation)
|
||||
return explanation, result
|
||||
|
||||
def visit_Assert(self, assrt):
|
||||
test_explanation, test_result = self.visit(assrt.test)
|
||||
explanation = "assert %s" % (test_explanation,)
|
||||
if not self.frame.is_true(test_result):
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, test_result
|
||||
|
||||
def visit_Assign(self, assign):
|
||||
value_explanation, value_result = self.visit(assign.value)
|
||||
explanation = "... = %s" % (value_explanation,)
|
||||
name = ast.Name("__exprinfo_expr", ast.Load(),
|
||||
lineno=assign.value.lineno,
|
||||
col_offset=assign.value.col_offset)
|
||||
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
|
||||
col_offset=assign.col_offset)
|
||||
mod = ast.Module([new_assign])
|
||||
co = self._compile(mod, "exec")
|
||||
try:
|
||||
self.frame.exec_(co, __exprinfo_expr=value_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
return explanation, value_result
|
||||
552
_pytest/assertion/oldinterpret.py
Normal file
552
_pytest/assertion/oldinterpret.py
Normal file
@@ -0,0 +1,552 @@
|
||||
import py
|
||||
import sys, inspect
|
||||
from compiler import parse, ast, pycodegen
|
||||
from _pytest.assertion.util import format_explanation
|
||||
from _pytest.assertion.reinterpret import BuiltinAssertionError
|
||||
|
||||
passthroughex = py.builtin._sysex
|
||||
|
||||
class Failure:
|
||||
def __init__(self, node):
|
||||
self.exc, self.value, self.tb = sys.exc_info()
|
||||
self.node = node
|
||||
|
||||
class View(object):
|
||||
"""View base class.
|
||||
|
||||
If C is a subclass of View, then C(x) creates a proxy object around
|
||||
the object x. The actual class of the proxy is not C in general,
|
||||
but a *subclass* of C determined by the rules below. To avoid confusion
|
||||
we call view class the class of the proxy (a subclass of C, so of View)
|
||||
and object class the class of x.
|
||||
|
||||
Attributes and methods not found in the proxy are automatically read on x.
|
||||
Other operations like setting attributes are performed on the proxy, as
|
||||
determined by its view class. The object x is available from the proxy
|
||||
as its __obj__ attribute.
|
||||
|
||||
The view class selection is determined by the __view__ tuples and the
|
||||
optional __viewkey__ method. By default, the selected view class is the
|
||||
most specific subclass of C whose __view__ mentions the class of x.
|
||||
If no such subclass is found, the search proceeds with the parent
|
||||
object classes. For example, C(True) will first look for a subclass
|
||||
of C with __view__ = (..., bool, ...) and only if it doesn't find any
|
||||
look for one with __view__ = (..., int, ...), and then ..., object,...
|
||||
If everything fails the class C itself is considered to be the default.
|
||||
|
||||
Alternatively, the view class selection can be driven by another aspect
|
||||
of the object x, instead of the class of x, by overriding __viewkey__.
|
||||
See last example at the end of this module.
|
||||
"""
|
||||
|
||||
_viewcache = {}
|
||||
__view__ = ()
|
||||
|
||||
def __new__(rootclass, obj, *args, **kwds):
|
||||
self = object.__new__(rootclass)
|
||||
self.__obj__ = obj
|
||||
self.__rootclass__ = rootclass
|
||||
key = self.__viewkey__()
|
||||
try:
|
||||
self.__class__ = self._viewcache[key]
|
||||
except KeyError:
|
||||
self.__class__ = self._selectsubclass(key)
|
||||
return self
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# attributes not found in the normal hierarchy rooted on View
|
||||
# are looked up in the object's real class
|
||||
return getattr(self.__obj__, attr)
|
||||
|
||||
def __viewkey__(self):
|
||||
return self.__obj__.__class__
|
||||
|
||||
def __matchkey__(self, key, subclasses):
|
||||
if inspect.isclass(key):
|
||||
keys = inspect.getmro(key)
|
||||
else:
|
||||
keys = [key]
|
||||
for key in keys:
|
||||
result = [C for C in subclasses if key in C.__view__]
|
||||
if result:
|
||||
return result
|
||||
return []
|
||||
|
||||
def _selectsubclass(self, key):
|
||||
subclasses = list(enumsubclasses(self.__rootclass__))
|
||||
for C in subclasses:
|
||||
if not isinstance(C.__view__, tuple):
|
||||
C.__view__ = (C.__view__,)
|
||||
choices = self.__matchkey__(key, subclasses)
|
||||
if not choices:
|
||||
return self.__rootclass__
|
||||
elif len(choices) == 1:
|
||||
return choices[0]
|
||||
else:
|
||||
# combine the multiple choices
|
||||
return type('?', tuple(choices), {})
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
|
||||
|
||||
|
||||
def enumsubclasses(cls):
|
||||
for subcls in cls.__subclasses__():
|
||||
for subsubclass in enumsubclasses(subcls):
|
||||
yield subsubclass
|
||||
yield cls
|
||||
|
||||
|
||||
class Interpretable(View):
|
||||
"""A parse tree node with a few extra methods."""
|
||||
explanation = None
|
||||
|
||||
def is_builtin(self, frame):
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
# fall-back for unknown expression nodes
|
||||
try:
|
||||
expr = ast.Expression(self.__obj__)
|
||||
expr.filename = '<eval>'
|
||||
self.__obj__.filename = '<eval>'
|
||||
co = pycodegen.ExpressionCodeGenerator(expr).getCode()
|
||||
result = frame.eval(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.result = result
|
||||
self.explanation = self.explanation or frame.repr(self.result)
|
||||
|
||||
def run(self, frame):
|
||||
# fall-back for unknown statement nodes
|
||||
try:
|
||||
expr = ast.Module(None, ast.Stmt([self.__obj__]))
|
||||
expr.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(expr).getCode()
|
||||
frame.exec_(co)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
def nice_explanation(self):
|
||||
return format_explanation(self.explanation)
|
||||
|
||||
|
||||
class Name(Interpretable):
|
||||
__view__ = ast.Name
|
||||
|
||||
def is_local(self, frame):
|
||||
source = '%r in locals() is not globals()' % self.name
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_global(self, frame):
|
||||
source = '%r in globals()' % self.name
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_builtin(self, frame):
|
||||
source = '%r not in locals() and %r not in globals()' % (
|
||||
self.name, self.name)
|
||||
try:
|
||||
return frame.is_true(frame.eval(source))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
super(Name, self).eval(frame)
|
||||
if not self.is_local(frame):
|
||||
self.explanation = self.name
|
||||
|
||||
class Compare(Interpretable):
|
||||
__view__ = ast.Compare
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
for operation, expr2 in self.ops:
|
||||
if hasattr(self, 'result'):
|
||||
# shortcutting in chained expressions
|
||||
if not frame.is_true(self.result):
|
||||
break
|
||||
expr2 = Interpretable(expr2)
|
||||
expr2.eval(frame)
|
||||
self.explanation = "%s %s %s" % (
|
||||
expr.explanation, operation, expr2.explanation)
|
||||
source = "__exprinfo_left %s __exprinfo_right" % operation
|
||||
try:
|
||||
self.result = frame.eval(source,
|
||||
__exprinfo_left=expr.result,
|
||||
__exprinfo_right=expr2.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
expr = expr2
|
||||
|
||||
class And(Interpretable):
|
||||
__view__ = ast.And
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if not frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' and '.join(explanations) + ')'
|
||||
|
||||
class Or(Interpretable):
|
||||
__view__ = ast.Or
|
||||
|
||||
def eval(self, frame):
|
||||
explanations = []
|
||||
for expr in self.nodes:
|
||||
expr = Interpretable(expr)
|
||||
expr.eval(frame)
|
||||
explanations.append(expr.explanation)
|
||||
self.result = expr.result
|
||||
if frame.is_true(expr.result):
|
||||
break
|
||||
self.explanation = '(' + ' or '.join(explanations) + ')'
|
||||
|
||||
|
||||
# == Unary operations ==
|
||||
keepalive = []
|
||||
for astclass, astpattern in {
|
||||
ast.Not : 'not __exprinfo_expr',
|
||||
ast.Invert : '(~__exprinfo_expr)',
|
||||
}.items():
|
||||
|
||||
class UnaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.explanation = astpattern.replace('__exprinfo_expr',
|
||||
expr.explanation)
|
||||
try:
|
||||
self.result = frame.eval(astpattern,
|
||||
__exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(UnaryArith)
|
||||
|
||||
# == Binary operations ==
|
||||
for astclass, astpattern in {
|
||||
ast.Add : '(__exprinfo_left + __exprinfo_right)',
|
||||
ast.Sub : '(__exprinfo_left - __exprinfo_right)',
|
||||
ast.Mul : '(__exprinfo_left * __exprinfo_right)',
|
||||
ast.Div : '(__exprinfo_left / __exprinfo_right)',
|
||||
ast.Mod : '(__exprinfo_left % __exprinfo_right)',
|
||||
ast.Power : '(__exprinfo_left ** __exprinfo_right)',
|
||||
}.items():
|
||||
|
||||
class BinaryArith(Interpretable):
|
||||
__view__ = astclass
|
||||
|
||||
def eval(self, frame, astpattern=astpattern):
|
||||
left = Interpretable(self.left)
|
||||
left.eval(frame)
|
||||
right = Interpretable(self.right)
|
||||
right.eval(frame)
|
||||
self.explanation = (astpattern
|
||||
.replace('__exprinfo_left', left .explanation)
|
||||
.replace('__exprinfo_right', right.explanation))
|
||||
try:
|
||||
self.result = frame.eval(astpattern,
|
||||
__exprinfo_left=left.result,
|
||||
__exprinfo_right=right.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
keepalive.append(BinaryArith)
|
||||
|
||||
|
||||
class CallFunc(Interpretable):
|
||||
__view__ = ast.CallFunc
|
||||
|
||||
def is_bool(self, frame):
|
||||
source = 'isinstance(__exprinfo_value, bool)'
|
||||
try:
|
||||
return frame.is_true(frame.eval(source,
|
||||
__exprinfo_value=self.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
||||
def eval(self, frame):
|
||||
node = Interpretable(self.node)
|
||||
node.eval(frame)
|
||||
explanations = []
|
||||
vars = {'__exprinfo_fn': node.result}
|
||||
source = '__exprinfo_fn('
|
||||
for a in self.args:
|
||||
if isinstance(a, ast.Keyword):
|
||||
keyword = a.name
|
||||
a = a.expr
|
||||
else:
|
||||
keyword = None
|
||||
a = Interpretable(a)
|
||||
a.eval(frame)
|
||||
argname = '__exprinfo_%d' % len(vars)
|
||||
vars[argname] = a.result
|
||||
if keyword is None:
|
||||
source += argname + ','
|
||||
explanations.append(a.explanation)
|
||||
else:
|
||||
source += '%s=%s,' % (keyword, argname)
|
||||
explanations.append('%s=%s' % (keyword, a.explanation))
|
||||
if self.star_args:
|
||||
star_args = Interpretable(self.star_args)
|
||||
star_args.eval(frame)
|
||||
argname = '__exprinfo_star'
|
||||
vars[argname] = star_args.result
|
||||
source += '*' + argname + ','
|
||||
explanations.append('*' + star_args.explanation)
|
||||
if self.dstar_args:
|
||||
dstar_args = Interpretable(self.dstar_args)
|
||||
dstar_args.eval(frame)
|
||||
argname = '__exprinfo_kwds'
|
||||
vars[argname] = dstar_args.result
|
||||
source += '**' + argname + ','
|
||||
explanations.append('**' + dstar_args.explanation)
|
||||
self.explanation = "%s(%s)" % (
|
||||
node.explanation, ', '.join(explanations))
|
||||
if source.endswith(','):
|
||||
source = source[:-1]
|
||||
source += ')'
|
||||
try:
|
||||
self.result = frame.eval(source, **vars)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
if not node.is_builtin(frame) or not self.is_bool(frame):
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
class Getattr(Interpretable):
|
||||
__view__ = ast.Getattr
|
||||
|
||||
def eval(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
source = '__exprinfo_expr.%s' % self.attrname
|
||||
try:
|
||||
self.result = frame.eval(source, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
self.explanation = '%s.%s' % (expr.explanation, self.attrname)
|
||||
# if the attribute comes from the instance, its value is interesting
|
||||
source = ('hasattr(__exprinfo_expr, "__dict__") and '
|
||||
'%r in __exprinfo_expr.__dict__' % self.attrname)
|
||||
try:
|
||||
from_instance = frame.is_true(
|
||||
frame.eval(source, __exprinfo_expr=expr.result))
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
from_instance = True
|
||||
if from_instance:
|
||||
r = frame.repr(self.result)
|
||||
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
|
||||
|
||||
# == Re-interpretation of full statements ==
|
||||
|
||||
class Assert(Interpretable):
|
||||
__view__ = ast.Assert
|
||||
|
||||
def run(self, frame):
|
||||
test = Interpretable(self.test)
|
||||
test.eval(frame)
|
||||
# print the result as 'assert <explanation>'
|
||||
self.result = test.result
|
||||
self.explanation = 'assert ' + test.explanation
|
||||
if not frame.is_true(test.result):
|
||||
try:
|
||||
raise BuiltinAssertionError
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Assign(Interpretable):
|
||||
__view__ = ast.Assign
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = '... = ' + expr.explanation
|
||||
# fall-back-run the rest of the assignment
|
||||
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
|
||||
mod = ast.Module(None, ast.Stmt([ass]))
|
||||
mod.filename = '<run>'
|
||||
co = pycodegen.ModuleCodeGenerator(mod).getCode()
|
||||
try:
|
||||
frame.exec_(co, __exprinfo_expr=expr.result)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
raise Failure(self)
|
||||
|
||||
class Discard(Interpretable):
|
||||
__view__ = ast.Discard
|
||||
|
||||
def run(self, frame):
|
||||
expr = Interpretable(self.expr)
|
||||
expr.eval(frame)
|
||||
self.result = expr.result
|
||||
self.explanation = expr.explanation
|
||||
|
||||
class Stmt(Interpretable):
|
||||
__view__ = ast.Stmt
|
||||
|
||||
def run(self, frame):
|
||||
for stmt in self.nodes:
|
||||
stmt = Interpretable(stmt)
|
||||
stmt.run(frame)
|
||||
|
||||
|
||||
def report_failure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if explanation:
|
||||
explanation = ", in: " + explanation
|
||||
else:
|
||||
explanation = ""
|
||||
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
|
||||
|
||||
def check(s, frame=None):
|
||||
if frame is None:
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
expr = parse(s, 'eval')
|
||||
assert isinstance(expr, ast.Expression)
|
||||
node = Interpretable(expr.node)
|
||||
try:
|
||||
node.eval(frame)
|
||||
except passthroughex:
|
||||
raise
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
else:
|
||||
if not frame.is_true(node.result):
|
||||
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
|
||||
|
||||
|
||||
###########################################################
|
||||
# API / Entry points
|
||||
# #########################################################
|
||||
|
||||
def interpret(source, frame, should_fail=False):
|
||||
module = Interpretable(parse(source, 'exec').node)
|
||||
#print "got module", module
|
||||
if isinstance(frame, py.std.types.FrameType):
|
||||
frame = py.code.Frame(frame)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
return getfailure(e)
|
||||
except passthroughex:
|
||||
raise
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if should_fail:
|
||||
return ("(assertion failed, but when it was re-run for "
|
||||
"printing intermediate values, it did not fail. Suggestions: "
|
||||
"compute assert expression before the assert or use --assert=plain)")
|
||||
else:
|
||||
return None
|
||||
|
||||
def getmsg(excinfo):
|
||||
if isinstance(excinfo, tuple):
|
||||
excinfo = py.code.ExceptionInfo(excinfo)
|
||||
#frame, line = gettbline(tb)
|
||||
#frame = py.code.Frame(frame)
|
||||
#return interpret(line, frame)
|
||||
|
||||
tb = excinfo.traceback[-1]
|
||||
source = str(tb.statement).strip()
|
||||
x = interpret(source, tb.frame, should_fail=True)
|
||||
if not isinstance(x, str):
|
||||
raise TypeError("interpret returned non-string %r" % (x,))
|
||||
return x
|
||||
|
||||
def getfailure(e):
|
||||
explanation = e.node.nice_explanation()
|
||||
if str(e.value):
|
||||
lines = explanation.split('\n')
|
||||
lines[0] += " << %s" % (e.value,)
|
||||
explanation = '\n'.join(lines)
|
||||
text = "%s: %s" % (e.exc.__name__, explanation)
|
||||
if text.startswith('AssertionError: assert '):
|
||||
text = text[16:]
|
||||
return text
|
||||
|
||||
def run(s, frame=None):
|
||||
if frame is None:
|
||||
frame = sys._getframe(1)
|
||||
frame = py.code.Frame(frame)
|
||||
module = Interpretable(parse(s, 'exec').node)
|
||||
try:
|
||||
module.run(frame)
|
||||
except Failure:
|
||||
e = sys.exc_info()[1]
|
||||
report_failure(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# example:
|
||||
def f():
|
||||
return 5
|
||||
def g():
|
||||
return 3
|
||||
def h(x):
|
||||
return 'never'
|
||||
check("f() * g() == 5")
|
||||
check("not f()")
|
||||
check("not (f() and g() or 0)")
|
||||
check("f() == g()")
|
||||
i = 4
|
||||
check("i == f()")
|
||||
check("len(f()) == 0")
|
||||
check("isinstance(2+3+4, float)")
|
||||
|
||||
run("x = i")
|
||||
check("x == 5")
|
||||
|
||||
run("assert not f(), 'oops'")
|
||||
run("a, b, c = 1, 2")
|
||||
run("a, b, c = f()")
|
||||
|
||||
check("max([f(),g()]) == 4")
|
||||
check("'hello'[g()] == 'h'")
|
||||
run("'guk%d' % h(f())")
|
||||
48
_pytest/assertion/reinterpret.py
Normal file
48
_pytest/assertion/reinterpret.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import sys
|
||||
import py
|
||||
|
||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
|
||||
class AssertionError(BuiltinAssertionError):
|
||||
def __init__(self, *args):
|
||||
BuiltinAssertionError.__init__(self, *args)
|
||||
if args:
|
||||
try:
|
||||
self.msg = str(args[0])
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
||||
args[0].__class__, id(args[0]))
|
||||
else:
|
||||
f = py.code.Frame(sys._getframe(1))
|
||||
try:
|
||||
source = f.code.fullsource
|
||||
if source is not None:
|
||||
try:
|
||||
source = source.getstatement(f.lineno, assertion=True)
|
||||
except IndexError:
|
||||
source = None
|
||||
else:
|
||||
source = str(source.deindent()).strip()
|
||||
except py.error.ENOENT:
|
||||
source = None
|
||||
# this can also occur during reinterpretation, when the
|
||||
# co_filename is set to "<run>".
|
||||
if source:
|
||||
self.msg = reinterpret(source, f, should_fail=True)
|
||||
else:
|
||||
self.msg = "<could not determine information>"
|
||||
if not self.args:
|
||||
self.args = (self.msg,)
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
AssertionError.__module__ = "builtins"
|
||||
reinterpret_old = "old reinterpretation not available for py3"
|
||||
else:
|
||||
from _pytest.assertion.oldinterpret import interpret as reinterpret_old
|
||||
if sys.version_info >= (2, 6) or (sys.platform.startswith("java")):
|
||||
from _pytest.assertion.newinterpret import interpret as reinterpret
|
||||
else:
|
||||
reinterpret = reinterpret_old
|
||||
|
||||
598
_pytest/assertion/rewrite.py
Normal file
598
_pytest/assertion/rewrite.py
Normal file
@@ -0,0 +1,598 @@
|
||||
"""Rewrite assertion AST to produce nice error messages"""
|
||||
|
||||
import ast
|
||||
import collections
|
||||
import errno
|
||||
import itertools
|
||||
import imp
|
||||
import marshal
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
|
||||
import py
|
||||
from _pytest.assertion import util
|
||||
|
||||
|
||||
# Windows gives ENOENT in places *nix gives ENOTDIR.
|
||||
if sys.platform.startswith("win"):
|
||||
PATH_COMPONENT_NOT_DIR = errno.ENOENT
|
||||
else:
|
||||
PATH_COMPONENT_NOT_DIR = errno.ENOTDIR
|
||||
|
||||
# py.test caches rewritten pycs in __pycache__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
PYTEST_TAG = imp.get_tag() + "-PYTEST"
|
||||
else:
|
||||
if hasattr(sys, "pypy_version_info"):
|
||||
impl = "pypy"
|
||||
elif sys.platform == "java":
|
||||
impl = "jython"
|
||||
else:
|
||||
impl = "cpython"
|
||||
ver = sys.version_info
|
||||
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
|
||||
del ver, impl
|
||||
|
||||
PYC_EXT = ".py" + "c" if __debug__ else "o"
|
||||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||
|
||||
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
|
||||
|
||||
class AssertionRewritingHook(object):
|
||||
"""Import hook which rewrites asserts."""
|
||||
|
||||
def __init__(self):
|
||||
self.session = None
|
||||
self.modules = {}
|
||||
|
||||
def set_session(self, session):
|
||||
self.fnpats = session.config.getini("python_files")
|
||||
self.session = session
|
||||
|
||||
def find_module(self, name, path=None):
|
||||
if self.session is None:
|
||||
return None
|
||||
sess = self.session
|
||||
state = sess.config._assertstate
|
||||
state.trace("find_module called for: %s" % name)
|
||||
names = name.rsplit(".", 1)
|
||||
lastname = names[-1]
|
||||
pth = None
|
||||
if path is not None and len(path) == 1:
|
||||
pth = path[0]
|
||||
if pth is None:
|
||||
try:
|
||||
fd, fn, desc = imp.find_module(lastname, path)
|
||||
except ImportError:
|
||||
return None
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
tp = desc[2]
|
||||
if tp == imp.PY_COMPILED:
|
||||
if hasattr(imp, "source_from_cache"):
|
||||
fn = imp.source_from_cache(fn)
|
||||
else:
|
||||
fn = fn[:-1]
|
||||
elif tp != imp.PY_SOURCE:
|
||||
# Don't know what this is.
|
||||
return None
|
||||
else:
|
||||
fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
|
||||
fn_pypath = py.path.local(fn)
|
||||
# Is this a test file?
|
||||
if not sess.isinitpath(fn):
|
||||
# We have to be very careful here because imports in this code can
|
||||
# trigger a cycle.
|
||||
self.session = None
|
||||
try:
|
||||
for pat in self.fnpats:
|
||||
if fn_pypath.fnmatch(pat):
|
||||
state.trace("matched test file %r" % (fn,))
|
||||
break
|
||||
else:
|
||||
return None
|
||||
finally:
|
||||
self.session = sess
|
||||
else:
|
||||
state.trace("matched test file (was specified on cmdline): %r" % (fn,))
|
||||
# The requested module looks like a test file, so rewrite it. This is
|
||||
# the most magical part of the process: load the source, rewrite the
|
||||
# asserts, and load the rewritten source. We also cache the rewritten
|
||||
# module code in a special pyc. We must be aware of the possibility of
|
||||
# concurrent py.test processes rewriting and loading pycs. To avoid
|
||||
# tricky race conditions, we maintain the following invariant: The
|
||||
# cached pyc is always a complete, valid pyc. Operations on it must be
|
||||
# atomic. POSIX's atomic rename comes in handy.
|
||||
write = not sys.dont_write_bytecode
|
||||
cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
|
||||
if write:
|
||||
try:
|
||||
os.mkdir(cache_dir)
|
||||
except OSError:
|
||||
e = sys.exc_info()[1].errno
|
||||
if e == errno.EEXIST:
|
||||
# Either the __pycache__ directory already exists (the
|
||||
# common case) or it's blocked by a non-dir node. In the
|
||||
# latter case, we'll ignore it in _write_pyc.
|
||||
pass
|
||||
elif e == PATH_COMPONENT_NOT_DIR:
|
||||
# One of the path components was not a directory, likely
|
||||
# because we're in a zip file.
|
||||
write = False
|
||||
elif e == errno.EACCES:
|
||||
state.trace("read only directory: %r" % (fn_pypath.dirname,))
|
||||
write = False
|
||||
else:
|
||||
raise
|
||||
cache_name = fn_pypath.basename[:-3] + PYC_TAIL
|
||||
pyc = os.path.join(cache_dir, cache_name)
|
||||
# Notice that even if we're in a read-only directory, I'm going to check
|
||||
# for a cached pyc. This may not be optimal...
|
||||
co = _read_pyc(fn_pypath, pyc)
|
||||
if co is None:
|
||||
state.trace("rewriting %r" % (fn,))
|
||||
co = _rewrite_test(state, fn_pypath)
|
||||
if co is None:
|
||||
# Probably a SyntaxError in the test.
|
||||
return None
|
||||
if write:
|
||||
_make_rewritten_pyc(state, fn_pypath, pyc, co)
|
||||
else:
|
||||
state.trace("found cached rewritten pyc for %r" % (fn,))
|
||||
self.modules[name] = co, pyc
|
||||
return self
|
||||
|
||||
def load_module(self, name):
|
||||
co, pyc = self.modules.pop(name)
|
||||
# I wish I could just call imp.load_compiled here, but __file__ has to
|
||||
# be set properly. In Python 3.2+, this all would be handled correctly
|
||||
# by load_compiled.
|
||||
mod = sys.modules[name] = imp.new_module(name)
|
||||
try:
|
||||
mod.__file__ = co.co_filename
|
||||
# Normally, this attribute is 3.2+.
|
||||
mod.__cached__ = pyc
|
||||
py.builtin.exec_(co, mod.__dict__)
|
||||
except:
|
||||
del sys.modules[name]
|
||||
raise
|
||||
return sys.modules[name]
|
||||
|
||||
def _write_pyc(co, source_path, pyc):
|
||||
# Technically, we don't have to have the same pyc format as (C)Python, since
|
||||
# these "pycs" should never be seen by builtin import. However, there's
|
||||
# little reason deviate, and I hope sometime to be able to use
|
||||
# imp.load_compiled to load them. (See the comment in load_module above.)
|
||||
mtime = int(source_path.mtime())
|
||||
try:
|
||||
fp = open(pyc, "wb")
|
||||
except IOError:
|
||||
err = sys.exc_info()[1].errno
|
||||
if err == PATH_COMPONENT_NOT_DIR:
|
||||
# This happens when we get a EEXIST in find_module creating the
|
||||
# __pycache__ directory and __pycache__ is by some non-dir node.
|
||||
return False
|
||||
raise
|
||||
try:
|
||||
fp.write(imp.get_magic())
|
||||
fp.write(struct.pack("<l", mtime))
|
||||
marshal.dump(co, fp)
|
||||
finally:
|
||||
fp.close()
|
||||
return True
|
||||
|
||||
RN = "\r\n".encode("utf-8")
|
||||
N = "\n".encode("utf-8")
|
||||
|
||||
def _rewrite_test(state, fn):
|
||||
"""Try to read and rewrite *fn* and return the code object."""
|
||||
try:
|
||||
source = fn.read("rb")
|
||||
except EnvironmentError:
|
||||
return None
|
||||
# On Python versions which are not 2.7 and less than or equal to 3.1, the
|
||||
# parser expects *nix newlines.
|
||||
if REWRITE_NEWLINES:
|
||||
source = source.replace(RN, N) + N
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
except SyntaxError:
|
||||
# Let this pop up again in the real import.
|
||||
state.trace("failed to parse: %r" % (fn,))
|
||||
return None
|
||||
rewrite_asserts(tree)
|
||||
try:
|
||||
co = compile(tree, fn.strpath, "exec")
|
||||
except SyntaxError:
|
||||
# It's possible that this error is from some bug in the
|
||||
# assertion rewriting, but I don't know of a fast way to tell.
|
||||
state.trace("failed to compile: %r" % (fn,))
|
||||
return None
|
||||
return co
|
||||
|
||||
def _make_rewritten_pyc(state, fn, pyc, co):
|
||||
"""Try to dump rewritten code to *pyc*."""
|
||||
if sys.platform.startswith("win"):
|
||||
# Windows grants exclusive access to open files and doesn't have atomic
|
||||
# rename, so just write into the final file.
|
||||
_write_pyc(co, fn, pyc)
|
||||
else:
|
||||
# When not on windows, assume rename is atomic. Dump the code object
|
||||
# into a file specific to this process and atomically replace it.
|
||||
proc_pyc = pyc + "." + str(os.getpid())
|
||||
if _write_pyc(co, fn, proc_pyc):
|
||||
os.rename(proc_pyc, pyc)
|
||||
|
||||
def _read_pyc(source, pyc):
|
||||
"""Possibly read a py.test pyc containing rewritten code.
|
||||
|
||||
Return rewritten code if successful or None if not.
|
||||
"""
|
||||
try:
|
||||
fp = open(pyc, "rb")
|
||||
except IOError:
|
||||
return None
|
||||
try:
|
||||
try:
|
||||
mtime = int(source.mtime())
|
||||
data = fp.read(8)
|
||||
except EnvironmentError:
|
||||
return None
|
||||
# Check for invalid or out of date pyc file.
|
||||
if (len(data) != 8 or
|
||||
data[:4] != imp.get_magic() or
|
||||
struct.unpack("<l", data[4:])[0] != mtime):
|
||||
return None
|
||||
co = marshal.load(fp)
|
||||
if not isinstance(co, types.CodeType):
|
||||
# That's interesting....
|
||||
return None
|
||||
return co
|
||||
finally:
|
||||
fp.close()
|
||||
|
||||
|
||||
def rewrite_asserts(mod):
|
||||
"""Rewrite the assert statements in mod."""
|
||||
AssertionRewriter().run(mod)
|
||||
|
||||
|
||||
_saferepr = py.io.saferepr
|
||||
from _pytest.assertion.util import format_explanation as _format_explanation
|
||||
|
||||
def _format_boolop(explanations, is_or):
|
||||
return "(" + (is_or and " or " or " and ").join(explanations) + ")"
|
||||
|
||||
def _call_reprcompare(ops, results, expls, each_obj):
|
||||
for i, res, expl in zip(range(len(ops)), results, expls):
|
||||
try:
|
||||
done = not res
|
||||
except Exception:
|
||||
done = True
|
||||
if done:
|
||||
break
|
||||
if util._reprcompare is not None:
|
||||
custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
|
||||
if custom is not None:
|
||||
return custom
|
||||
return expl
|
||||
|
||||
|
||||
unary_map = {
|
||||
ast.Not : "not %s",
|
||||
ast.Invert : "~%s",
|
||||
ast.USub : "-%s",
|
||||
ast.UAdd : "+%s"
|
||||
}
|
||||
|
||||
binop_map = {
|
||||
ast.BitOr : "|",
|
||||
ast.BitXor : "^",
|
||||
ast.BitAnd : "&",
|
||||
ast.LShift : "<<",
|
||||
ast.RShift : ">>",
|
||||
ast.Add : "+",
|
||||
ast.Sub : "-",
|
||||
ast.Mult : "*",
|
||||
ast.Div : "/",
|
||||
ast.FloorDiv : "//",
|
||||
ast.Mod : "%",
|
||||
ast.Eq : "==",
|
||||
ast.NotEq : "!=",
|
||||
ast.Lt : "<",
|
||||
ast.LtE : "<=",
|
||||
ast.Gt : ">",
|
||||
ast.GtE : ">=",
|
||||
ast.Pow : "**",
|
||||
ast.Is : "is",
|
||||
ast.IsNot : "is not",
|
||||
ast.In : "in",
|
||||
ast.NotIn : "not in"
|
||||
}
|
||||
|
||||
|
||||
def set_location(node, lineno, col_offset):
|
||||
"""Set node location information recursively."""
|
||||
def _fix(node, lineno, col_offset):
|
||||
if "lineno" in node._attributes:
|
||||
node.lineno = lineno
|
||||
if "col_offset" in node._attributes:
|
||||
node.col_offset = col_offset
|
||||
for child in ast.iter_child_nodes(node):
|
||||
_fix(child, lineno, col_offset)
|
||||
_fix(node, lineno, col_offset)
|
||||
return node
|
||||
|
||||
|
||||
class AssertionRewriter(ast.NodeVisitor):
|
||||
|
||||
def run(self, mod):
|
||||
"""Find all assert statements in *mod* and rewrite them."""
|
||||
if not mod.body:
|
||||
# Nothing to do.
|
||||
return
|
||||
# Insert some special imports at the top of the module but after any
|
||||
# docstrings and __future__ imports.
|
||||
aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
|
||||
ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
|
||||
expect_docstring = True
|
||||
pos = 0
|
||||
lineno = 0
|
||||
for item in mod.body:
|
||||
if (expect_docstring and isinstance(item, ast.Expr) and
|
||||
isinstance(item.value, ast.Str)):
|
||||
doc = item.value.s
|
||||
if "PYTEST_DONT_REWRITE" in doc:
|
||||
# The module has disabled assertion rewriting.
|
||||
return
|
||||
lineno += len(doc) - 1
|
||||
expect_docstring = False
|
||||
elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
|
||||
item.module != "__future__"):
|
||||
lineno = item.lineno
|
||||
break
|
||||
pos += 1
|
||||
imports = [ast.Import([alias], lineno=lineno, col_offset=0)
|
||||
for alias in aliases]
|
||||
mod.body[pos:pos] = imports
|
||||
# Collect asserts.
|
||||
nodes = [mod]
|
||||
while nodes:
|
||||
node = nodes.pop()
|
||||
for name, field in ast.iter_fields(node):
|
||||
if isinstance(field, list):
|
||||
new = []
|
||||
for i, child in enumerate(field):
|
||||
if isinstance(child, ast.Assert):
|
||||
# Transform assert.
|
||||
new.extend(self.visit(child))
|
||||
else:
|
||||
new.append(child)
|
||||
if isinstance(child, ast.AST):
|
||||
nodes.append(child)
|
||||
setattr(node, name, new)
|
||||
elif (isinstance(field, ast.AST) and
|
||||
# Don't recurse into expressions as they can't contain
|
||||
# asserts.
|
||||
not isinstance(field, ast.expr)):
|
||||
nodes.append(field)
|
||||
|
||||
def variable(self):
|
||||
"""Get a new variable."""
|
||||
# Use a character invalid in python identifiers to avoid clashing.
|
||||
name = "@py_assert" + str(next(self.variable_counter))
|
||||
self.variables.append(name)
|
||||
return name
|
||||
|
||||
def assign(self, expr):
|
||||
"""Give *expr* a name."""
|
||||
name = self.variable()
|
||||
self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
|
||||
return ast.Name(name, ast.Load())
|
||||
|
||||
def display(self, expr):
|
||||
"""Call py.io.saferepr on the expression."""
|
||||
return self.helper("saferepr", expr)
|
||||
|
||||
def helper(self, name, *args):
|
||||
"""Call a helper in this module."""
|
||||
py_name = ast.Name("@pytest_ar", ast.Load())
|
||||
attr = ast.Attribute(py_name, "_" + name, ast.Load())
|
||||
return ast.Call(attr, list(args), [], None, None)
|
||||
|
||||
def builtin(self, name):
|
||||
"""Return the builtin called *name*."""
|
||||
builtin_name = ast.Name("@py_builtins", ast.Load())
|
||||
return ast.Attribute(builtin_name, name, ast.Load())
|
||||
|
||||
def explanation_param(self, expr):
|
||||
specifier = "py" + str(next(self.variable_counter))
|
||||
self.explanation_specifiers[specifier] = expr
|
||||
return "%(" + specifier + ")s"
|
||||
|
||||
def push_format_context(self):
|
||||
self.explanation_specifiers = {}
|
||||
self.stack.append(self.explanation_specifiers)
|
||||
|
||||
def pop_format_context(self, expl_expr):
|
||||
current = self.stack.pop()
|
||||
if self.stack:
|
||||
self.explanation_specifiers = self.stack[-1]
|
||||
keys = [ast.Str(key) for key in current.keys()]
|
||||
format_dict = ast.Dict(keys, list(current.values()))
|
||||
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
||||
name = "@py_format" + str(next(self.variable_counter))
|
||||
self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
|
||||
return ast.Name(name, ast.Load())
|
||||
|
||||
def generic_visit(self, node):
|
||||
"""Handle expressions we don't have custom code for."""
|
||||
assert isinstance(node, ast.expr)
|
||||
res = self.assign(node)
|
||||
return res, self.explanation_param(self.display(res))
|
||||
|
||||
def visit_Assert(self, assert_):
|
||||
if assert_.msg:
|
||||
# There's already a message. Don't mess with it.
|
||||
return [assert_]
|
||||
self.statements = []
|
||||
self.cond_chain = ()
|
||||
self.variables = []
|
||||
self.variable_counter = itertools.count()
|
||||
self.stack = []
|
||||
self.on_failure = []
|
||||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
top_condition, explanation = self.visit(assert_.test)
|
||||
# Create failure message.
|
||||
body = self.on_failure
|
||||
negation = ast.UnaryOp(ast.Not(), top_condition)
|
||||
self.statements.append(ast.If(negation, body, []))
|
||||
explanation = "assert " + explanation
|
||||
template = ast.Str(explanation)
|
||||
msg = self.pop_format_context(template)
|
||||
fmt = self.helper("format_explanation", msg)
|
||||
err_name = ast.Name("AssertionError", ast.Load())
|
||||
exc = ast.Call(err_name, [fmt], [], None, None)
|
||||
if sys.version_info[0] >= 3:
|
||||
raise_ = ast.Raise(exc, None)
|
||||
else:
|
||||
raise_ = ast.Raise(exc, None, None)
|
||||
body.append(raise_)
|
||||
# Clear temporary variables by setting them to None.
|
||||
if self.variables:
|
||||
variables = [ast.Name(name, ast.Store()) for name in self.variables]
|
||||
clear = ast.Assign(variables, ast.Name("None", ast.Load()))
|
||||
self.statements.append(clear)
|
||||
# Fix line numbers.
|
||||
for stmt in self.statements:
|
||||
set_location(stmt, assert_.lineno, assert_.col_offset)
|
||||
return self.statements
|
||||
|
||||
def visit_Name(self, name):
|
||||
# Check if the name is local or not.
|
||||
locs = ast.Call(self.builtin("locals"), [], [], None, None)
|
||||
globs = ast.Call(self.builtin("globals"), [], [], None, None)
|
||||
ops = [ast.In(), ast.IsNot()]
|
||||
test = ast.Compare(ast.Str(name.id), ops, [locs, globs])
|
||||
expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
res_var = self.variable()
|
||||
expl_list = self.assign(ast.List([], ast.Load()))
|
||||
app = ast.Attribute(expl_list, "append", ast.Load())
|
||||
is_or = int(isinstance(boolop.op, ast.Or))
|
||||
body = save = self.statements
|
||||
fail_save = self.on_failure
|
||||
levels = len(boolop.values) - 1
|
||||
self.push_format_context()
|
||||
# Process each operand, short-circuting if needed.
|
||||
for i, v in enumerate(boolop.values):
|
||||
if i:
|
||||
fail_inner = []
|
||||
self.on_failure.append(ast.If(cond, fail_inner, []))
|
||||
self.on_failure = fail_inner
|
||||
self.push_format_context()
|
||||
res, expl = self.visit(v)
|
||||
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
||||
expl_format = self.pop_format_context(ast.Str(expl))
|
||||
call = ast.Call(app, [expl_format], [], None, None)
|
||||
self.on_failure.append(ast.Expr(call))
|
||||
if i < levels:
|
||||
cond = res
|
||||
if is_or:
|
||||
cond = ast.UnaryOp(ast.Not(), cond)
|
||||
inner = []
|
||||
self.statements.append(ast.If(cond, inner, []))
|
||||
self.statements = body = inner
|
||||
self.statements = save
|
||||
self.on_failure = fail_save
|
||||
expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
|
||||
expl = self.pop_format_context(expl_template)
|
||||
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
|
||||
|
||||
def visit_UnaryOp(self, unary):
|
||||
pattern = unary_map[unary.op.__class__]
|
||||
operand_res, operand_expl = self.visit(unary.operand)
|
||||
res = self.assign(ast.UnaryOp(unary.op, operand_res))
|
||||
return res, pattern % (operand_expl,)
|
||||
|
||||
def visit_BinOp(self, binop):
|
||||
symbol = binop_map[binop.op.__class__]
|
||||
left_expr, left_expl = self.visit(binop.left)
|
||||
right_expr, right_expl = self.visit(binop.right)
|
||||
explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
|
||||
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
|
||||
return res, explanation
|
||||
|
||||
def visit_Call(self, call):
|
||||
new_func, func_expl = self.visit(call.func)
|
||||
arg_expls = []
|
||||
new_args = []
|
||||
new_kwargs = []
|
||||
new_star = new_kwarg = None
|
||||
for arg in call.args:
|
||||
res, expl = self.visit(arg)
|
||||
new_args.append(res)
|
||||
arg_expls.append(expl)
|
||||
for keyword in call.keywords:
|
||||
res, expl = self.visit(keyword.value)
|
||||
new_kwargs.append(ast.keyword(keyword.arg, res))
|
||||
arg_expls.append(keyword.arg + "=" + expl)
|
||||
if call.starargs:
|
||||
new_star, expl = self.visit(call.starargs)
|
||||
arg_expls.append("*" + expl)
|
||||
if call.kwargs:
|
||||
new_kwarg, expl = self.visit(call.kwargs)
|
||||
arg_expls.append("**" + expl)
|
||||
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
|
||||
new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg)
|
||||
res = self.assign(new_call)
|
||||
res_expl = self.explanation_param(self.display(res))
|
||||
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
|
||||
return res, outer_expl
|
||||
|
||||
def visit_Attribute(self, attr):
|
||||
if not isinstance(attr.ctx, ast.Load):
|
||||
return self.generic_visit(attr)
|
||||
value, value_expl = self.visit(attr.value)
|
||||
res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
|
||||
res_expl = self.explanation_param(self.display(res))
|
||||
pat = "%s\n{%s = %s.%s\n}"
|
||||
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
|
||||
return res, expl
|
||||
|
||||
def visit_Compare(self, comp):
|
||||
self.push_format_context()
|
||||
left_res, left_expl = self.visit(comp.left)
|
||||
res_variables = [self.variable() for i in range(len(comp.ops))]
|
||||
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
|
||||
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
|
||||
it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
|
||||
expls = []
|
||||
syms = []
|
||||
results = [left_res]
|
||||
for i, op, next_operand in it:
|
||||
next_res, next_expl = self.visit(next_operand)
|
||||
results.append(next_res)
|
||||
sym = binop_map[op.__class__]
|
||||
syms.append(ast.Str(sym))
|
||||
expl = "%s %s %s" % (left_expl, sym, next_expl)
|
||||
expls.append(ast.Str(expl))
|
||||
res_expr = ast.Compare(left_res, [op], [next_res])
|
||||
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
||||
left_res, left_expl = next_res, next_expl
|
||||
# Use py.code._reprcompare if that's available.
|
||||
expl_call = self.helper("call_reprcompare",
|
||||
ast.Tuple(syms, ast.Load()),
|
||||
ast.Tuple(load_names, ast.Load()),
|
||||
ast.Tuple(expls, ast.Load()),
|
||||
ast.Tuple(results, ast.Load()))
|
||||
if len(comp.ops) > 1:
|
||||
res = ast.BoolOp(ast.And(), load_names)
|
||||
else:
|
||||
res = load_names[0]
|
||||
return res, self.explanation_param(self.pop_format_context(expl_call))
|
||||
@@ -1,43 +1,79 @@
|
||||
"""
|
||||
support for presented detailed information in failing assertions.
|
||||
"""
|
||||
"""Utilities for assertion debugging"""
|
||||
|
||||
import py
|
||||
import sys
|
||||
from _pytest.monkeypatch import monkeypatch
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("debugconfig")
|
||||
group._addoption('--no-assert', action="store_true", default=False,
|
||||
dest="noassert",
|
||||
help="disable python assert expression reinterpretation."),
|
||||
|
||||
def pytest_configure(config):
|
||||
# The _reprcompare attribute on the py.code module is used by
|
||||
# py._code._assertionnew to detect this plugin was loaded and in
|
||||
# turn call the hooks defined here as part of the
|
||||
# DebugInterpreter.
|
||||
m = monkeypatch()
|
||||
config._cleanup.append(m.undo)
|
||||
warn_about_missing_assertion()
|
||||
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
|
||||
def callbinrepr(op, left, right):
|
||||
hook_result = config.hook.pytest_assertrepr_compare(
|
||||
config=config, op=op, left=left, right=right)
|
||||
for new_expl in hook_result:
|
||||
if new_expl:
|
||||
return '\n~'.join(new_expl)
|
||||
m.setattr(py.builtin.builtins,
|
||||
'AssertionError', py.code._AssertionError)
|
||||
m.setattr(py.code, '_reprcompare', callbinrepr)
|
||||
# The _reprcompare attribute on the util module is used by the new assertion
|
||||
# interpretation code and assertion rewriter to detect this plugin was
|
||||
# loaded and in turn call the hooks defined here as part of the
|
||||
# DebugInterpreter.
|
||||
_reprcompare = None
|
||||
|
||||
def format_explanation(explanation):
|
||||
"""This formats an explanation
|
||||
|
||||
Normally all embedded newlines are escaped, however there are
|
||||
three exceptions: \n{, \n} and \n~. The first two are intended
|
||||
cover nested explanations, see function and attribute explanations
|
||||
for examples (.visit_Call(), visit_Attribute()). The last one is
|
||||
for when one explanation needs to span multiple lines, e.g. when
|
||||
displaying diffs.
|
||||
"""
|
||||
# simplify 'assert False where False = ...'
|
||||
where = 0
|
||||
while True:
|
||||
start = where = explanation.find("False\n{False = ", where)
|
||||
if where == -1:
|
||||
break
|
||||
level = 0
|
||||
for i, c in enumerate(explanation[start:]):
|
||||
if c == "{":
|
||||
level += 1
|
||||
elif c == "}":
|
||||
level -= 1
|
||||
if not level:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("unbalanced braces: %r" % (explanation,))
|
||||
end = start + i
|
||||
where = end
|
||||
if explanation[end - 1] == '\n':
|
||||
explanation = (explanation[:start] + explanation[start+15:end-1] +
|
||||
explanation[end+1:])
|
||||
where -= 17
|
||||
raw_lines = (explanation or '').split('\n')
|
||||
# escape newlines not followed by {, } and ~
|
||||
lines = [raw_lines[0]]
|
||||
for l in raw_lines[1:]:
|
||||
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
||||
lines.append(l)
|
||||
else:
|
||||
lines[-1] += '\\n' + l
|
||||
|
||||
result = lines[:1]
|
||||
stack = [0]
|
||||
stackcnt = [0]
|
||||
for line in lines[1:]:
|
||||
if line.startswith('{'):
|
||||
if stackcnt[-1]:
|
||||
s = 'and '
|
||||
else:
|
||||
s = 'where '
|
||||
stack.append(len(result))
|
||||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
||||
elif line.startswith('}'):
|
||||
assert line.startswith('}')
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
result[stack[-1]] += line[1:]
|
||||
else:
|
||||
assert line.startswith('~')
|
||||
result.append(' '*len(stack) + line[1:])
|
||||
assert len(stack) == 1
|
||||
return '\n'.join(result)
|
||||
|
||||
def warn_about_missing_assertion():
|
||||
try:
|
||||
assert False
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
sys.stderr.write("WARNING: failing tests may report as passing because "
|
||||
"assertions are turned off! (are you using python -O?)\n")
|
||||
|
||||
# Provide basestring in python3
|
||||
try:
|
||||
@@ -46,7 +82,7 @@ except NameError:
|
||||
basestring = str
|
||||
|
||||
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
def assertrepr_compare(op, left, right):
|
||||
"""return specialised explanations for some operators/operands"""
|
||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
||||
@@ -12,12 +12,9 @@ def pytest_addoption(parser):
|
||||
help="shortcut for --capture=no.")
|
||||
|
||||
def addouterr(rep, outerr):
|
||||
repr = getattr(rep, 'longrepr', None)
|
||||
if not hasattr(repr, 'addsection'):
|
||||
return
|
||||
for secname, content in zip(["out", "err"], outerr):
|
||||
if content:
|
||||
repr.addsection("Captured std%s" % secname, content.rstrip())
|
||||
rep.sections.append(("Captured std%s" % secname, content))
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
# registered in config.py during early conftest.py loading
|
||||
|
||||
@@ -8,8 +8,6 @@ import pytest
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
config = Config(pluginmanager)
|
||||
config.parse(args)
|
||||
if config.option.debug:
|
||||
config.trace.root.setwriter(sys.stderr.write)
|
||||
return config
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
@@ -334,6 +332,7 @@ class Config(object):
|
||||
# Note that this can only be called once per testing process.
|
||||
assert not hasattr(self, 'args'), (
|
||||
"can only parse cmdline args at most once per Config object")
|
||||
self._origargs = args
|
||||
self._preparse(args)
|
||||
self._parser.hints.extend(self.pluginmanager._hints)
|
||||
args = self._parser.parse_setoption(args, self.option)
|
||||
@@ -422,7 +421,7 @@ class Config(object):
|
||||
|
||||
|
||||
def getcfg(args, inibasenames):
|
||||
args = [x for x in args if str(x)[0] != "-"]
|
||||
args = [x for x in args if not str(x).startswith("-")]
|
||||
if not args:
|
||||
args = [py.path.local()]
|
||||
for arg in args:
|
||||
|
||||
@@ -16,11 +16,10 @@ default_plugins = (
|
||||
"junitxml resultlog doctest").split()
|
||||
|
||||
class TagTracer:
|
||||
def __init__(self, prefix="[pytest] "):
|
||||
def __init__(self):
|
||||
self._tag2proc = {}
|
||||
self.writer = None
|
||||
self.indent = 0
|
||||
self.prefix = prefix
|
||||
|
||||
def get(self, name):
|
||||
return TagTracerSub(self, (name,))
|
||||
@@ -30,7 +29,7 @@ class TagTracer:
|
||||
if args:
|
||||
indent = " " * self.indent
|
||||
content = " ".join(map(str, args))
|
||||
self.writer("%s%s%s\n" %(self.prefix, indent, content))
|
||||
self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags)))
|
||||
try:
|
||||
self._tag2proc[tags](tags, args)
|
||||
except KeyError:
|
||||
|
||||
@@ -59,7 +59,7 @@ class DoctestItem(pytest.Item):
|
||||
inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||
lines += ["UNEXPECTED EXCEPTION: %s" %
|
||||
repr(inner_excinfo.value)]
|
||||
|
||||
lines += py.std.traceback.format_exception(*excinfo.value.exc_info)
|
||||
return ReprFailDoctest(reprlocation, lines)
|
||||
else:
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
""" version info, help messages, tracing configuration. """
|
||||
import py
|
||||
import pytest
|
||||
import inspect, sys
|
||||
import os, inspect, sys
|
||||
from _pytest.core import varnames
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -16,12 +16,31 @@ def pytest_addoption(parser):
|
||||
group.addoption('--traceconfig',
|
||||
action="store_true", dest="traceconfig", default=False,
|
||||
help="trace considerations of conftest.py files."),
|
||||
group._addoption('--nomagic',
|
||||
action="store_true", dest="nomagic", default=False,
|
||||
help="don't reinterpret asserts, no traceback cutting. ")
|
||||
group.addoption('--debug',
|
||||
action="store_true", dest="debug", default=False,
|
||||
help="generate and show internal debugging information.")
|
||||
help="store internal tracing debug information in 'pytestdebug.log'.")
|
||||
|
||||
|
||||
def pytest_cmdline_parse(__multicall__):
|
||||
config = __multicall__.execute()
|
||||
if config.option.debug:
|
||||
path = os.path.abspath("pytestdebug.log")
|
||||
f = open(path, 'w')
|
||||
config._debugfile = f
|
||||
f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %(
|
||||
pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)),
|
||||
os.getcwd(), config._origargs))
|
||||
config.trace.root.setwriter(f.write)
|
||||
sys.stderr.write("writing pytestdebug information to %s\n" % path)
|
||||
return config
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_unconfigure(config):
|
||||
if hasattr(config, '_debugfile'):
|
||||
config._debugfile.close()
|
||||
sys.stderr.write("wrote pytestdebug information to %s\n" %
|
||||
config._debugfile.name)
|
||||
config.trace.root.setwriter(None)
|
||||
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
|
||||
@@ -65,18 +65,18 @@ def pytest_unconfigure(config):
|
||||
|
||||
class LogXML(object):
|
||||
def __init__(self, logfile, prefix):
|
||||
self.logfile = logfile
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.normpath(logfile)
|
||||
self.prefix = prefix
|
||||
self.test_logs = []
|
||||
self.passed = self.skipped = 0
|
||||
self.failed = self.errors = 0
|
||||
self._durations = {}
|
||||
|
||||
def _opentestcase(self, report):
|
||||
names = report.nodeid.split("::")
|
||||
names[0] = names[0].replace("/", '.')
|
||||
names = tuple(names)
|
||||
d = {'time': self._durations.pop(names, "0")}
|
||||
d = {'time': getattr(report, 'duration', 0)}
|
||||
names = [x.replace(".py", "") for x in names if x != "()"]
|
||||
classnames = names[:-1]
|
||||
if self.prefix:
|
||||
@@ -113,8 +113,14 @@ class LogXML(object):
|
||||
'<skipped message="xfail-marked test passes unexpectedly"/>')
|
||||
self.skipped += 1
|
||||
else:
|
||||
sec = dict(report.sections)
|
||||
self.appendlog('<failure message="test failure">%s</failure>',
|
||||
report.longrepr)
|
||||
for name in ('out', 'err'):
|
||||
content = sec.get("Captured std%s" % name)
|
||||
if content:
|
||||
self.appendlog(
|
||||
"<system-%s>%%s</system-%s>" % (name, name), content)
|
||||
self.failed += 1
|
||||
self._closetestcase()
|
||||
|
||||
@@ -169,14 +175,6 @@ class LogXML(object):
|
||||
elif report.skipped:
|
||||
self.append_skipped(report)
|
||||
|
||||
def pytest_runtest_call(self, item, __multicall__):
|
||||
names = tuple(item.listnames())
|
||||
start = time.time()
|
||||
try:
|
||||
return __multicall__.execute()
|
||||
finally:
|
||||
self._durations[names] = time.time() - start
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
if report.failed:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import py
|
||||
import pytest, _pytest
|
||||
import os, sys
|
||||
import os, sys, imp
|
||||
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
||||
|
||||
# exitcodes for the command line
|
||||
@@ -46,23 +46,25 @@ def pytest_addoption(parser):
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return dict(collect=dict(Item=Item, Collector=Collector, File=File))
|
||||
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
|
||||
return dict(collect=collect)
|
||||
|
||||
def pytest_configure(config):
|
||||
py.test.config = config # compatibiltiy
|
||||
if config.option.exitfirst:
|
||||
config.option.maxfail = 1
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
""" default command line protocol for initialization, session,
|
||||
running tests and reporting. """
|
||||
def wrap_session(config, doit):
|
||||
"""Skeleton command line program"""
|
||||
session = Session(config)
|
||||
session.exitstatus = EXIT_OK
|
||||
initstate = 0
|
||||
try:
|
||||
config.pluginmanager.do_configure(config)
|
||||
initstate = 1
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
config.hook.pytest_collection(session=session)
|
||||
config.hook.pytest_runtestloop(session=session)
|
||||
initstate = 2
|
||||
doit(config, session)
|
||||
except pytest.UsageError:
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
@@ -77,18 +79,24 @@ def pytest_cmdline_main(config):
|
||||
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||
if not session.exitstatus and session._testsfailed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
config.hook.pytest_sessionfinish(session=session,
|
||||
exitstatus=session.exitstatus)
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
if initstate >= 2:
|
||||
config.hook.pytest_sessionfinish(session=session,
|
||||
exitstatus=session.exitstatus)
|
||||
if initstate >= 1:
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
return session.exitstatus
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
return wrap_session(config, _main)
|
||||
|
||||
def _main(config, session):
|
||||
""" default command line protocol for initialization, session,
|
||||
running tests and reporting. """
|
||||
config.hook.pytest_collection(session=session)
|
||||
config.hook.pytest_runtestloop(session=session)
|
||||
|
||||
def pytest_collection(session):
|
||||
session.perform_collect()
|
||||
hook = session.config.hook
|
||||
hook.pytest_collection_modifyitems(session=session,
|
||||
config=session.config, items=session.items)
|
||||
hook.pytest_collection_finish(session=session)
|
||||
return True
|
||||
return session.perform_collect()
|
||||
|
||||
def pytest_runtestloop(session):
|
||||
if session.config.option.collectonly:
|
||||
@@ -374,6 +382,16 @@ class Session(FSCollector):
|
||||
return HookProxy(fspath, self.config)
|
||||
|
||||
def perform_collect(self, args=None, genitems=True):
|
||||
hook = self.config.hook
|
||||
try:
|
||||
items = self._perform_collect(args, genitems)
|
||||
hook.pytest_collection_modifyitems(session=self,
|
||||
config=self.config, items=items)
|
||||
finally:
|
||||
hook.pytest_collection_finish(session=self)
|
||||
return items
|
||||
|
||||
def _perform_collect(self, args, genitems):
|
||||
if args is None:
|
||||
args = self.config.args
|
||||
self.trace("perform_collect", self, args)
|
||||
@@ -451,16 +469,22 @@ class Session(FSCollector):
|
||||
return True
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
try:
|
||||
mod = __import__(x, None, None, ['__doc__'])
|
||||
except (ValueError, ImportError):
|
||||
return x
|
||||
p = py.path.local(mod.__file__)
|
||||
if p.purebasename == "__init__":
|
||||
p = p.dirpath()
|
||||
else:
|
||||
p = p.new(basename=p.purebasename+".py")
|
||||
return str(p)
|
||||
mod = None
|
||||
path = [os.path.abspath('.')] + sys.path
|
||||
for name in x.split('.'):
|
||||
try:
|
||||
fd, mod, type_ = imp.find_module(name, path)
|
||||
except ImportError:
|
||||
return x
|
||||
else:
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
|
||||
if type_[2] != imp.PKG_DIRECTORY:
|
||||
path = [os.path.dirname(mod)]
|
||||
else:
|
||||
path = [mod]
|
||||
return mod
|
||||
|
||||
def _parsearg(self, arg):
|
||||
""" return (fspath, names) tuple after checking the file exists. """
|
||||
|
||||
@@ -153,7 +153,7 @@ class MarkInfo:
|
||||
|
||||
def __repr__(self):
|
||||
return "<MarkInfo %r args=%r kwargs=%r>" % (
|
||||
self._name, self.args, self.kwargs)
|
||||
self.name, self.args, self.kwargs)
|
||||
|
||||
def pytest_itemcollected(item):
|
||||
if not isinstance(item, pytest.Function):
|
||||
|
||||
@@ -13,6 +13,7 @@ def pytest_runtest_makereport(__multicall__, item, call):
|
||||
call.excinfo = call2.excinfo
|
||||
|
||||
|
||||
@pytest.mark.trylast
|
||||
def pytest_runtest_setup(item):
|
||||
if isinstance(item, (pytest.Function)):
|
||||
if isinstance(item.parent, pytest.Generator):
|
||||
|
||||
@@ -19,11 +19,13 @@ def pytest_configure(config):
|
||||
class pytestPDB:
|
||||
""" Pseudo PDB that defers to the real pdb. """
|
||||
item = None
|
||||
collector = None
|
||||
|
||||
def set_trace(self):
|
||||
""" invoke PDB set_trace debugging, dropping any IO capturing. """
|
||||
frame = sys._getframe().f_back
|
||||
item = getattr(self, 'item', None)
|
||||
item = self.item or self.collector
|
||||
|
||||
if item is not None:
|
||||
capman = item.config.pluginmanager.getplugin("capturemanager")
|
||||
out, err = capman.suspendcapture()
|
||||
@@ -38,6 +40,14 @@ def pdbitem(item):
|
||||
pytestPDB.item = item
|
||||
pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_make_collect_report(__multicall__, collector):
|
||||
try:
|
||||
pytestPDB.collector = collector
|
||||
return __multicall__.execute()
|
||||
finally:
|
||||
pytestPDB.collector = None
|
||||
|
||||
def pytest_runtest_makereport():
|
||||
pytestPDB.item = None
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
import inspect
|
||||
import time
|
||||
from fnmatch import fnmatch
|
||||
from _pytest.main import Session
|
||||
from _pytest.main import Session, EXIT_OK
|
||||
from py.builtin import print_
|
||||
from _pytest.core import HookRelay
|
||||
|
||||
@@ -25,6 +25,7 @@ def pytest_configure(config):
|
||||
_pytest_fullpath
|
||||
except NameError:
|
||||
_pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
|
||||
_pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py")
|
||||
|
||||
def pytest_funcarg___pytest(request):
|
||||
return PytestArg(request)
|
||||
@@ -292,13 +293,19 @@ class TmpTestdir:
|
||||
assert '::' not in str(arg)
|
||||
p = py.path.local(arg)
|
||||
x = session.fspath.bestrelpath(p)
|
||||
return session.perform_collect([x], genitems=False)[0]
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
res = session.perform_collect([x], genitems=False)[0]
|
||||
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
|
||||
return res
|
||||
|
||||
def getpathnode(self, path):
|
||||
config = self.parseconfig(path)
|
||||
config = self.parseconfigure(path)
|
||||
session = Session(config)
|
||||
x = session.fspath.bestrelpath(path)
|
||||
return session.perform_collect([x], genitems=False)[0]
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
res = session.perform_collect([x], genitems=False)[0]
|
||||
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
|
||||
return res
|
||||
|
||||
def genitems(self, colitems):
|
||||
session = colitems[0].session
|
||||
@@ -312,7 +319,9 @@ class TmpTestdir:
|
||||
config = self.parseconfigure(*args)
|
||||
rec = self.getreportrecorder(config)
|
||||
session = Session(config)
|
||||
config.hook.pytest_sessionstart(session=session)
|
||||
session.perform_collect()
|
||||
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
|
||||
return session.items, rec
|
||||
|
||||
def runitem(self, source):
|
||||
@@ -382,6 +391,8 @@ class TmpTestdir:
|
||||
c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
|
||||
keep=0, rootdir=self.tmpdir, lock_timeout=None)
|
||||
c.parse(args)
|
||||
c.pluginmanager.do_configure(c)
|
||||
self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c))
|
||||
return c
|
||||
finally:
|
||||
py.test.config = oldconfig
|
||||
|
||||
@@ -374,7 +374,7 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
|
||||
# test generators are seen as collectors but they also
|
||||
# invoke setup/teardown on popular request
|
||||
# (induced by the common "test_*" naming shared with normal tests)
|
||||
self.config._setupstate.prepare(self)
|
||||
self.session._setupstate.prepare(self)
|
||||
# see FunctionMixin.setup and test_setupstate_is_preserved_134
|
||||
self._preservedparent = self.parent.obj
|
||||
l = []
|
||||
@@ -721,7 +721,7 @@ class FuncargRequest:
|
||||
|
||||
def _addfinalizer(self, finalizer, scope):
|
||||
colitem = self._getscopeitem(scope)
|
||||
self.config._setupstate.addfinalizer(
|
||||
self._pyfuncitem.session._setupstate.addfinalizer(
|
||||
finalizer=finalizer, colitem=colitem)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -742,8 +742,10 @@ class FuncargRequest:
|
||||
raise self.LookupError(msg)
|
||||
|
||||
def showfuncargs(config):
|
||||
from _pytest.main import Session
|
||||
session = Session(config)
|
||||
from _pytest.main import wrap_session
|
||||
return wrap_session(config, _showfuncargs_main)
|
||||
|
||||
def _showfuncargs_main(config, session):
|
||||
session.perform_collect()
|
||||
if session.items:
|
||||
plugins = session.items[0].getplugins()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
""" basic collect and runtest protocol implementations """
|
||||
|
||||
import py, sys
|
||||
import py, sys, time
|
||||
from py._code.code import TerminalRepr
|
||||
|
||||
def pytest_namespace():
|
||||
@@ -14,17 +14,15 @@ def pytest_namespace():
|
||||
#
|
||||
# pytest plugin hooks
|
||||
|
||||
# XXX move to pytest_sessionstart and fix py.test owns tests
|
||||
def pytest_configure(config):
|
||||
config._setupstate = SetupState()
|
||||
def pytest_sessionstart(session):
|
||||
session._setupstate = SetupState()
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
if hasattr(session.config, '_setupstate'):
|
||||
hook = session.config.hook
|
||||
rep = hook.pytest__teardown_final(session=session)
|
||||
if rep:
|
||||
hook.pytest__teardown_final_logerror(session=session, report=rep)
|
||||
session.exitstatus = 1
|
||||
hook = session.config.hook
|
||||
rep = hook.pytest__teardown_final(session=session)
|
||||
if rep:
|
||||
hook.pytest__teardown_final_logerror(session=session, report=rep)
|
||||
session.exitstatus = 1
|
||||
|
||||
class NodeInfo:
|
||||
def __init__(self, location):
|
||||
@@ -46,16 +44,16 @@ def runtestprotocol(item, log=True):
|
||||
return reports
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
item.config._setupstate.prepare(item)
|
||||
item.session._setupstate.prepare(item)
|
||||
|
||||
def pytest_runtest_call(item):
|
||||
item.runtest()
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
item.config._setupstate.teardown_exact(item)
|
||||
item.session._setupstate.teardown_exact(item)
|
||||
|
||||
def pytest__teardown_final(session):
|
||||
call = CallInfo(session.config._setupstate.teardown_all, when="teardown")
|
||||
call = CallInfo(session._setupstate.teardown_all, when="teardown")
|
||||
if call.excinfo:
|
||||
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
|
||||
call.excinfo.traceback = ntraceback.filter()
|
||||
@@ -97,12 +95,16 @@ class CallInfo:
|
||||
#: context of invocation: one of "setup", "call",
|
||||
#: "teardown", "memocollect"
|
||||
self.when = when
|
||||
self.start = time.time()
|
||||
try:
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.excinfo = py.code.ExceptionInfo()
|
||||
try:
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
self.excinfo = py.code.ExceptionInfo()
|
||||
finally:
|
||||
self.stop = time.time()
|
||||
|
||||
def __repr__(self):
|
||||
if self.excinfo:
|
||||
@@ -141,6 +143,7 @@ class BaseReport(object):
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
when = call.when
|
||||
duration = call.stop-call.start
|
||||
keywords = dict([(x,1) for x in item.keywords])
|
||||
excinfo = call.excinfo
|
||||
if not call.excinfo:
|
||||
@@ -162,14 +165,15 @@ def pytest_runtest_makereport(item, call):
|
||||
else: # exception in setup or teardown
|
||||
longrepr = item._repr_failure_py(excinfo)
|
||||
return TestReport(item.nodeid, item.location,
|
||||
keywords, outcome, longrepr, when)
|
||||
keywords, outcome, longrepr, when,
|
||||
duration=duration)
|
||||
|
||||
class TestReport(BaseReport):
|
||||
""" Basic test report object (also used for setup and teardown calls if
|
||||
they fail).
|
||||
"""
|
||||
def __init__(self, nodeid, location,
|
||||
keywords, outcome, longrepr, when):
|
||||
keywords, outcome, longrepr, when, sections=(), duration=0):
|
||||
#: normalized collection node id
|
||||
self.nodeid = nodeid
|
||||
|
||||
@@ -191,6 +195,13 @@ class TestReport(BaseReport):
|
||||
#: one of 'setup', 'call', 'teardown' to indicate runtest phase.
|
||||
self.when = when
|
||||
|
||||
#: list of (secname, data) extra information which needs to
|
||||
#: marshallable
|
||||
self.sections = list(sections)
|
||||
|
||||
#: time it took to run just the test
|
||||
self.duration = duration
|
||||
|
||||
def __repr__(self):
|
||||
return "<TestReport %r when=%r outcome=%r>" % (
|
||||
self.nodeid, self.when, self.outcome)
|
||||
@@ -200,6 +211,7 @@ class TeardownErrorReport(BaseReport):
|
||||
when = "teardown"
|
||||
def __init__(self, longrepr):
|
||||
self.longrepr = longrepr
|
||||
self.sections = []
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
call = CallInfo(collector._memocollect, "memocollect")
|
||||
@@ -221,11 +233,12 @@ def pytest_make_collect_report(collector):
|
||||
getattr(call, 'result', None))
|
||||
|
||||
class CollectReport(BaseReport):
|
||||
def __init__(self, nodeid, outcome, longrepr, result):
|
||||
def __init__(self, nodeid, outcome, longrepr, result, sections=()):
|
||||
self.nodeid = nodeid
|
||||
self.outcome = outcome
|
||||
self.longrepr = longrepr
|
||||
self.result = result or []
|
||||
self.sections = list(sections)
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
|
||||
@@ -169,21 +169,23 @@ def pytest_terminal_summary(terminalreporter):
|
||||
elif char == "X":
|
||||
show_xpassed(terminalreporter, lines)
|
||||
elif char in "fF":
|
||||
show_failed(terminalreporter, lines)
|
||||
show_simple(terminalreporter, lines, 'failed', "FAIL %s")
|
||||
elif char in "sS":
|
||||
show_skipped(terminalreporter, lines)
|
||||
elif char == "E":
|
||||
show_simple(terminalreporter, lines, 'error', "ERROR %s")
|
||||
if lines:
|
||||
tr._tw.sep("=", "short test summary info")
|
||||
for line in lines:
|
||||
tr._tw.line(line)
|
||||
|
||||
def show_failed(terminalreporter, lines):
|
||||
def show_simple(terminalreporter, lines, stat, format):
|
||||
tw = terminalreporter._tw
|
||||
failed = terminalreporter.stats.get("failed")
|
||||
failed = terminalreporter.stats.get(stat)
|
||||
if failed:
|
||||
for rep in failed:
|
||||
pos = rep.nodeid
|
||||
lines.append("FAIL %s" %(pos, ))
|
||||
lines.append(format %(pos, ))
|
||||
|
||||
def show_xfailed(terminalreporter, lines):
|
||||
xfailed = terminalreporter.stats.get("xfailed")
|
||||
|
||||
@@ -15,7 +15,7 @@ def pytest_addoption(parser):
|
||||
group._addoption('-r',
|
||||
action="store", dest="reportchars", default=None, metavar="chars",
|
||||
help="show extra test summary info as specified by chars (f)ailed, "
|
||||
"(s)skipped, (x)failed, (X)passed.")
|
||||
"(E)error, (s)skipped, (x)failed, (X)passed.")
|
||||
group._addoption('-l', '--showlocals',
|
||||
action="store_true", dest="showlocals", default=False,
|
||||
help="show locals in tracebacks (disabled by default).")
|
||||
@@ -259,7 +259,7 @@ class TerminalReporter:
|
||||
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
|
||||
msg += "[pypy-%s]" % verinfo
|
||||
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
|
||||
msg += " -- pytest-%s" % (py.test.__version__)
|
||||
if self.verbosity > 0 or self.config.option.debug or \
|
||||
getattr(self.config.option, 'pastebin', None):
|
||||
@@ -318,12 +318,17 @@ class TerminalReporter:
|
||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
if exitstatus == 2:
|
||||
self._report_keyboardinterrupt()
|
||||
del self._keyboardinterrupt_memo
|
||||
self.summary_deselected()
|
||||
self.summary_stats()
|
||||
|
||||
def pytest_keyboard_interrupt(self, excinfo):
|
||||
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
|
||||
|
||||
def pytest_unconfigure(self):
|
||||
if hasattr(self, '_keyboardinterrupt_memo'):
|
||||
self._report_keyboardinterrupt()
|
||||
|
||||
def _report_keyboardinterrupt(self):
|
||||
excrepr = self._keyboardinterrupt_memo
|
||||
msg = excrepr.reprcrash.message
|
||||
@@ -388,7 +393,7 @@ class TerminalReporter:
|
||||
else:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg)
|
||||
rep.toterminal(self._tw)
|
||||
self._outrep_summary(rep)
|
||||
|
||||
def summary_errors(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
@@ -406,7 +411,15 @@ class TerminalReporter:
|
||||
elif rep.when == "teardown":
|
||||
msg = "ERROR at teardown of " + msg
|
||||
self.write_sep("_", msg)
|
||||
rep.toterminal(self._tw)
|
||||
self._outrep_summary(rep)
|
||||
|
||||
def _outrep_summary(self, rep):
|
||||
rep.toterminal(self._tw)
|
||||
for secname, content in rep.sections:
|
||||
self._tw.sep("-", secname)
|
||||
if content[-1:] == "\n":
|
||||
content = content[:-1]
|
||||
self._tw.line(content)
|
||||
|
||||
def summary_stats(self):
|
||||
session_duration = py.std.time.time() - self._sessionstarttime
|
||||
|
||||
@@ -46,7 +46,7 @@ except ImportError:
|
||||
args = [quote(arg) for arg in args]
|
||||
return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
|
||||
|
||||
DEFAULT_VERSION = "0.6.14"
|
||||
DEFAULT_VERSION = "0.6.19"
|
||||
DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
|
||||
SETUPTOOLS_FAKED_VERSION = "0.6c11"
|
||||
|
||||
|
||||
10
doc/Makefile
10
doc/Makefile
@@ -39,8 +39,14 @@ help:
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
install: clean html
|
||||
rsync -avz _build/html/ code:www-pytest/
|
||||
install: html
|
||||
@rsync -avz _build/html/ pytest.org:/www/pytest.org/latest
|
||||
|
||||
installpdf: latexpdf
|
||||
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/latest
|
||||
|
||||
installall: clean install installpdf
|
||||
@echo "done"
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
||||
2
doc/_static/sphinxdoc.css
vendored
2
doc/_static/sphinxdoc.css
vendored
@@ -48,7 +48,7 @@ div.body {
|
||||
}
|
||||
|
||||
div.related {
|
||||
font-size: 1em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
div.related ul {
|
||||
|
||||
2
doc/_templates/indexsidebar.html
vendored
2
doc/_templates/indexsidebar.html
vendored
@@ -10,7 +10,7 @@
|
||||
<p><b><a href="{{ pathto('announce/index')}}">{{ release }} release</a></b>
|
||||
[<a href="{{ pathto('changelog') }}">Changelog</a>]</p>
|
||||
<p>
|
||||
<a href="http://pypi.python.org/pypi/pytest">pytest on PyPI</a>
|
||||
<a href="http://pypi.python.org/pypi/pytest">pytest/PyPI</a>
|
||||
</p>
|
||||
<pre>easy_install pytest</pre>
|
||||
<pre>pip install pytest</pre>
|
||||
|
||||
27
doc/_templates/layout.html
vendored
27
doc/_templates/layout.html
vendored
@@ -1,28 +1,8 @@
|
||||
{% extends "!layout.html" %}
|
||||
|
||||
{% block relbar1 %}
|
||||
{% endblock %}
|
||||
{% block relbar2 %}
|
||||
{% endblock %}
|
||||
|
||||
{% block rootrellink %}
|
||||
{% endblock %}
|
||||
{% block sidebarrel %}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<div style="background-color: white; text-align: left; padding: 10px 10px 15px 15px">
|
||||
<h1>pytest: rapid no-boilerplate testing with Python</h1>
|
||||
<div style="text-align: left; font-size: 130%; vertical-align: middle;">
|
||||
<a href="{{ pathto('index') }}">home</a> |
|
||||
<a href="{{ pathto('contents') }}">all docs</a> |
|
||||
<a href="{{ pathto('getting-started') }}">install</a> |
|
||||
<a href="{{ pathto('example/index') }}">examples</a> |
|
||||
<a href="{{ pathto('customize') }}">customize</a> |
|
||||
<a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues</a>|
|
||||
<a href="{{ pathto('contact') }}">contact</a>
|
||||
</div>
|
||||
</div>
|
||||
{% block relbaritems %}
|
||||
{{ super() }}
|
||||
<g:plusone></g:plusone>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
@@ -40,4 +20,5 @@
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
37
doc/_templates/localtoc.html
vendored
Normal file
37
doc/_templates/localtoc.html
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
{%- if pagename != "search" %}
|
||||
<div id="searchbox" style="display: none">
|
||||
<form class="search" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" size="18" />
|
||||
<input type="submit" value="{{ _('Search') }}" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/javascript">$('#searchbox').show(0);</script>
|
||||
{%- endif %}
|
||||
|
||||
<h3>quicklinks</h3>
|
||||
<div style="text-align: left; font-size: 100%; vertical-align: middle;">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ pathto('index') }}">home</a>
|
||||
</td><td>
|
||||
<a href="{{ pathto('contents') }}">TOC/contents</a>
|
||||
</td></tr><tr><td>
|
||||
<a href="{{ pathto('getting-started') }}">install</a>
|
||||
</td><td>
|
||||
<a href="{{ pathto('changelog') }}">changelog</a>
|
||||
</td></tr><tr><td>
|
||||
<a href="{{ pathto('example/index') }}">examples</a>
|
||||
</td><td>
|
||||
<a href="{{ pathto('customize') }}">customize</a>
|
||||
</td></tr><tr><td>
|
||||
<a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues[bb]</a>
|
||||
</td><td>
|
||||
<a href="{{ pathto('contact') }}">contact</a>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
{% extends "basic/localtoc.html" %}
|
||||
|
||||
0
doc/_templates/searchbox.html
vendored
Normal file
0
doc/_templates/searchbox.html
vendored
Normal file
@@ -5,6 +5,10 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
release-2.1.3
|
||||
release-2.1.2
|
||||
release-2.1.1
|
||||
release-2.1.0
|
||||
release-2.0.3
|
||||
release-2.0.2
|
||||
release-2.0.1
|
||||
|
||||
48
doc/announce/release-2.1.0.txt
Normal file
48
doc/announce/release-2.1.0.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
py.test 2.1.0: perfected assertions and bug fixes
|
||||
===========================================================================
|
||||
|
||||
Welcome to the relase of pytest-2.1, a mature testing tool for Python,
|
||||
supporting CPython 2.4-3.2, Jython and latest PyPy interpreters. See
|
||||
the improved extensive docs (now also as PDF!) with tested examples here:
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
The single biggest news about this release are **perfected assertions**
|
||||
courtesy of Benjamin Peterson. You can now safely use ``assert``
|
||||
statements in test modules without having to worry about side effects
|
||||
or python optimization ("-OO") options. This is achieved by rewriting
|
||||
assert statements in test modules upon import, using a PEP302 hook.
|
||||
See http://pytest.org/assert.html#advanced-assertion-introspection for
|
||||
detailed information. The work has been partly sponsored by my company,
|
||||
merlinux GmbH.
|
||||
|
||||
For further details on bug fixes and smaller enhancements see below.
|
||||
|
||||
If you want to install or upgrade pytest, just type one of::
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel / http://merlinux.eu
|
||||
|
||||
Changes between 2.0.3 and 2.1.0
|
||||
----------------------------------------------
|
||||
|
||||
- fix issue53 call nosestyle setup functions with correct ordering
|
||||
- fix issue58 and issue59: new assertion code fixes
|
||||
- merge Benjamin's assertionrewrite branch: now assertions
|
||||
for test modules on python 2.6 and above are done by rewriting
|
||||
the AST and saving the pyc file before the test module is imported.
|
||||
see doc/assert.txt for more info.
|
||||
- fix issue43: improve doctests with better traceback reporting on
|
||||
unexpected exceptions
|
||||
- fix issue47: timing output in junitxml for test cases is now correct
|
||||
- fix issue48: typo in MarkInfo repr leading to exception
|
||||
- fix issue49: avoid confusing error when initizaliation partially fails
|
||||
- fix issue44: env/username expansion for junitxml file path
|
||||
- show releaselevel information in test runs for pypy
|
||||
- reworked doc pages for better navigation and PDF generation
|
||||
- report KeyboardInterrupt even if interrupted during session startup
|
||||
- fix issue 35 - provide PDF doc version and download link from index page
|
||||
|
||||
37
doc/announce/release-2.1.1.txt
Normal file
37
doc/announce/release-2.1.1.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
py.test 2.1.1: assertion fixes and improved junitxml output
|
||||
===========================================================================
|
||||
|
||||
pytest-2.1.1 is a backward compatible maintenance release of the
|
||||
popular py.test testing tool. See extensive docs with examples here:
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
Most bug fixes address remaining issues with the perfected assertions
|
||||
introduced with 2.1.0 - many thanks to the bug reporters and to Benjamin
|
||||
Peterson for helping to fix them. Also, junitxml output now produces
|
||||
system-out/err tags which lead to better displays of tracebacks with Jenkins.
|
||||
|
||||
Also a quick note to package maintainers and others interested: there now
|
||||
is a "pytest" man page which can be generated with "make man" in doc/.
|
||||
|
||||
If you want to install or upgrade pytest, just type one of::
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel / http://merlinux.eu
|
||||
|
||||
Changes between 2.1.0 and 2.1.1
|
||||
----------------------------------------------
|
||||
|
||||
- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
|
||||
- fix issue60 / fix error conditions involving the creation of __pycache__
|
||||
- fix issue63 / assertion rewriting on inserts involving strings containing '%'
|
||||
- fix assertion rewriting on calls with a ** arg
|
||||
- don't cache rewritten modules if bytecode generation is disabled
|
||||
- fix assertion rewriting in read-only directories
|
||||
- fix issue59: provide system-out/err tags for junitxml output
|
||||
- fix issue61: assertion rewriting on boolean operations with 3 or more operands
|
||||
- you can now build a man page with "cd doc ; make man"
|
||||
|
||||
33
doc/announce/release-2.1.2.txt
Normal file
33
doc/announce/release-2.1.2.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
py.test 2.1.2: bug fixes and fixes for jython
|
||||
===========================================================================
|
||||
|
||||
pytest-2.1.2 is a minor backward compatible maintenance release of the
|
||||
popular py.test testing tool. pytest is commonly used for unit,
|
||||
functional- and integration testing. See extensive docs with examples
|
||||
here:
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
Most bug fixes address remaining issues with the perfected assertions
|
||||
introduced in the 2.1 series - many thanks to the bug reporters and to Benjamin
|
||||
Peterson for helping to fix them. pytest should also work better with
|
||||
Jython-2.5.1 (and Jython trunk).
|
||||
|
||||
If you want to install or upgrade pytest, just type one of::
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel / http://merlinux.eu
|
||||
|
||||
Changes between 2.1.1 and 2.1.2
|
||||
----------------------------------------
|
||||
|
||||
- fix assertion rewriting on files with windows newlines on some Python versions
|
||||
- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
|
||||
- fix issue69 / assertion rewriting fixed on some boolean operations
|
||||
- fix issue68 / packages now work with assertion rewriting
|
||||
- fix issue66: use different assertion rewriting caches when the -O option is passed
|
||||
- don't try assertion rewriting on Jython, use reinterp
|
||||
|
||||
32
doc/announce/release-2.1.3.txt
Normal file
32
doc/announce/release-2.1.3.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
py.test 2.1.3: just some more fixes
|
||||
===========================================================================
|
||||
|
||||
pytest-2.1.3 is a minor backward compatible maintenance release of the
|
||||
popular py.test testing tool. It is commonly used for unit, functional-
|
||||
and integration testing. See extensive docs with examples here:
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
The release contains another fix to the perfected assertions introduced
|
||||
with the 2.1 series as well as the new possibility to customize reporting
|
||||
for assertion expressions on a per-directory level.
|
||||
|
||||
If you want to install or upgrade pytest, just type one of::
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
Thanks to the bug reporters and to Ronny Pfannschmidt, Benjamin Peterson
|
||||
and Floris Bruynooghe who implemented the fixes.
|
||||
|
||||
best,
|
||||
holger krekel
|
||||
|
||||
Changes between 2.1.2 and 2.1.3
|
||||
----------------------------------------
|
||||
|
||||
- fix issue79: assertion rewriting failed on some comparisons in boolops,
|
||||
- correctly handle zero length arguments (a la pytest '')
|
||||
- fix issue67 / junitxml now contains correct test durations
|
||||
- fix issue75 / skipping test failure on jython
|
||||
- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
|
||||
154
doc/assert.txt
154
doc/assert.txt
@@ -4,7 +4,7 @@ The writing and reporting of assertions in tests
|
||||
|
||||
.. _`assert with the assert statement`:
|
||||
|
||||
assert with the ``assert`` statement
|
||||
Asserting with the ``assert`` statement
|
||||
---------------------------------------------------------
|
||||
|
||||
``py.test`` allows you to use the standard python ``assert`` for verifying
|
||||
@@ -18,12 +18,12 @@ following::
|
||||
def test_function():
|
||||
assert f() == 4
|
||||
|
||||
to assert that your object returns a certain value. If this
|
||||
assertion fails you will see the value of ``x``::
|
||||
to assert that your function returns a certain value. If this assertion fails
|
||||
you will see the return value of the function call::
|
||||
|
||||
$ py.test test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_assert1.py F
|
||||
@@ -37,27 +37,24 @@ assertion fails you will see the value of ``x``::
|
||||
E + where 3 = f()
|
||||
|
||||
test_assert1.py:5: AssertionError
|
||||
========================= 1 failed in 0.02 seconds =========================
|
||||
========================= 1 failed in 0.03 seconds =========================
|
||||
|
||||
Reporting details about the failing assertion is achieved by re-evaluating
|
||||
the assert expression and recording the intermediate values.
|
||||
py.test has support for showing the values of the most common subexpressions
|
||||
including calls, attributes, comparisons, and binary and unary
|
||||
operators. (See :ref:`tbreportdemo`). This allows you to use the
|
||||
idiomatic python constructs without boilerplate code while not losing
|
||||
introspection information.
|
||||
|
||||
Note: If evaluating the assert expression has side effects you may get a
|
||||
warning that the intermediate values could not be determined safely. A
|
||||
common example of this issue is an assertion which reads from a file::
|
||||
However, if you specify a message with the assertion like this::
|
||||
|
||||
assert f.read() != '...'
|
||||
assert a % 2 == 0, "value was odd, should be even"
|
||||
|
||||
If this assertion fails then the re-evaluation will probably succeed!
|
||||
This is because ``f.read()`` will return an empty string when it is
|
||||
called the second time during the re-evaluation. However, it is
|
||||
easy to rewrite the assertion and avoid any trouble::
|
||||
then no assertion introspection takes places at all and the message
|
||||
will be simply shown in the traceback.
|
||||
|
||||
content = f.read()
|
||||
assert content != '...'
|
||||
See :ref:`assert-details` for more information on assertion introspection.
|
||||
|
||||
|
||||
assertions about expected exceptions
|
||||
Assertions about expected exceptions
|
||||
------------------------------------------
|
||||
|
||||
In order to write assertions about raised exceptions, you can use
|
||||
@@ -108,7 +105,7 @@ if you run this module::
|
||||
|
||||
$ py.test test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_assert2.py F
|
||||
@@ -127,7 +124,7 @@ if you run this module::
|
||||
E '5'
|
||||
|
||||
test_assert2.py:5: AssertionError
|
||||
========================= 1 failed in 0.03 seconds =========================
|
||||
========================= 1 failed in 0.02 seconds =========================
|
||||
|
||||
Special comparisons are done for a number of cases:
|
||||
|
||||
@@ -137,8 +134,117 @@ Special comparisons are done for a number of cases:
|
||||
|
||||
See the :ref:`reporting demo <tbreportdemo>` for many more examples.
|
||||
|
||||
..
|
||||
Defining your own comparison
|
||||
----------------------------------------------
|
||||
Defining your own assertion comparison
|
||||
----------------------------------------------
|
||||
|
||||
It is possible to add your own detailed explanations by implementing
|
||||
the ``pytest_assertrepr_compare`` hook.
|
||||
|
||||
.. autofunction:: _pytest.hookspec.pytest_assertrepr_compare
|
||||
|
||||
As an example consider adding the following hook in a conftest.py which
|
||||
provides an alternative explanation for ``Foo`` objects::
|
||||
|
||||
# content of conftest.py
|
||||
from test_foocompare import Foo
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
|
||||
return ['Comparing Foo instances:',
|
||||
' vals: %s != %s' % (left.val, right.val)]
|
||||
|
||||
now, given this test module::
|
||||
|
||||
# content of test_foocompare.py
|
||||
class Foo:
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def test_compare():
|
||||
f1 = Foo(1)
|
||||
f2 = Foo(2)
|
||||
assert f1 == f2
|
||||
|
||||
you can run the test module and get the custom output defined in
|
||||
the conftest file::
|
||||
|
||||
$ py.test -q test_foocompare.py
|
||||
collecting ... collected 1 items
|
||||
F
|
||||
================================= FAILURES =================================
|
||||
_______________________________ test_compare _______________________________
|
||||
|
||||
def test_compare():
|
||||
f1 = Foo(1)
|
||||
f2 = Foo(2)
|
||||
> assert f1 == f2
|
||||
E assert Comparing Foo instances:
|
||||
E vals: 1 != 2
|
||||
|
||||
test_foocompare.py:8: AssertionError
|
||||
1 failed in 0.02 seconds
|
||||
|
||||
.. _assert-details:
|
||||
.. _`assert introspection`:
|
||||
|
||||
Advanced assertion introspection
|
||||
----------------------------------
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
|
||||
Reporting details about a failing assertion is achieved either by rewriting
|
||||
assert statements before they are run or re-evaluating the assert expression and
|
||||
recording the intermediate values. Which technique is used depends on the
|
||||
location of the assert, py.test's configuration, and Python version being used
|
||||
to run py.test. Note that for assert statements with a manually provided
|
||||
message, i.e. ``assert expr, message``, no assertion introspection takes place
|
||||
and the manually provided message will be rendered in tracebacks.
|
||||
|
||||
By default, if the Python version is greater than or equal to 2.6, py.test
|
||||
rewrites assert statements in test modules. Rewritten assert statements put
|
||||
introspection information into the assertion failure message. py.test only
|
||||
rewrites test modules directly discovered by its test collection process, so
|
||||
asserts in supporting modules which are not themselves test modules will not be
|
||||
rewritten.
|
||||
|
||||
.. note::
|
||||
|
||||
py.test rewrites test modules on import. It does this by using an import hook
|
||||
to write a new pyc files. Most of the time this works transparently. However,
|
||||
if you are messing with import yourself, the import hook may interfere. If
|
||||
this is the case, simply use ``--assert=reinterp`` or
|
||||
``--assert=plain``. Additionally, rewriting will fail silently if it cannot
|
||||
write new pycs, i.e. in a read-only filesystem or a zipfile.
|
||||
|
||||
If an assert statement has not been rewritten or the Python version is less than
|
||||
2.6, py.test falls back on assert reinterpretation. In assert reinterpretation,
|
||||
py.test walks the frame of the function containing the assert statement to
|
||||
discover sub-expression results of the failing assert statement. You can force
|
||||
py.test to always use assertion reinterpretation by passing the
|
||||
``--assert=reinterp`` option.
|
||||
|
||||
Assert reinterpretation has a caveat not present with assert rewriting: If
|
||||
evaluating the assert expression has side effects you may get a warning that the
|
||||
intermediate values could not be determined safely. A common example of this
|
||||
issue is an assertion which reads from a file::
|
||||
|
||||
assert f.read() != '...'
|
||||
|
||||
If this assertion fails then the re-evaluation will probably succeed!
|
||||
This is because ``f.read()`` will return an empty string when it is
|
||||
called the second time during the re-evaluation. However, it is
|
||||
easy to rewrite the assertion and avoid any trouble::
|
||||
|
||||
content = f.read()
|
||||
assert content != '...'
|
||||
|
||||
All assert introspection can be turned off by passing ``--assert=plain``.
|
||||
|
||||
For further information, Benjamin Peterson wrote up `Behind the scenes of py.test's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
Add assert rewriting as an alternate introspection technique.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Introduce the ``--assert`` option. Deprecate ``--no-assert`` and
|
||||
``--nomagic``.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
.. _`pytest helpers`:
|
||||
|
||||
pytest builtin helpers
|
||||
Pytest builtin helpers
|
||||
================================================
|
||||
|
||||
builtin pytest.* functions and helping objects
|
||||
@@ -17,13 +17,16 @@ to get an overview on the globally available helpers.
|
||||
.. automodule:: pytest
|
||||
:members:
|
||||
|
||||
builtin function arguments
|
||||
Builtin function arguments
|
||||
-----------------------------------------------------
|
||||
|
||||
You can ask for available builtin or project-custom
|
||||
:ref:`function arguments <funcargs>` by typing::
|
||||
|
||||
$ py.test --funcargs
|
||||
=========================== test session starts ============================
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collected 0 items
|
||||
pytestconfig
|
||||
the pytest config object with access to command line opts.
|
||||
capsys
|
||||
@@ -69,3 +72,7 @@ You can ask for available builtin or project-custom
|
||||
See http://docs.python.org/library/warnings.html for information
|
||||
on warning categories.
|
||||
|
||||
cov
|
||||
A pytest funcarg that provides access to the underlying coverage object.
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
|
||||
@@ -64,7 +64,7 @@ of the failing function and hide the other one::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_module.py .F
|
||||
@@ -78,8 +78,8 @@ of the failing function and hide the other one::
|
||||
|
||||
test_module.py:9: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
setting up <function test_func2 at 0x238c410>
|
||||
==================== 1 failed, 1 passed in 0.02 seconds ====================
|
||||
setting up <function test_func2 at 0x10130ccf8>
|
||||
==================== 1 failed, 1 passed in 0.03 seconds ====================
|
||||
|
||||
Accessing captured output from a test function
|
||||
---------------------------------------------------
|
||||
|
||||
40
doc/conf.py
40
doc/conf.py
@@ -38,7 +38,7 @@ source_suffix = '.txt'
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = 'contents'
|
||||
|
||||
# General information about the project.
|
||||
project = u'pytest'
|
||||
@@ -48,13 +48,11 @@ copyright = u'2011, holger krekel et alii'
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
import py, pytest
|
||||
import pytest
|
||||
release = pytest.__version__
|
||||
# The short X.Y version.
|
||||
version = ".".join(release.split(".")[:2])
|
||||
#assert py.path.local().relto(py.path.local(pytest.__file__).dirpath().dirpath())
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@@ -68,7 +66,10 @@ version = ".".join(release.split(".")[:2])
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['links.inc', '_build', 'test', ] # XXX
|
||||
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
|
||||
'example/attic.txt',
|
||||
]
|
||||
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
@@ -107,10 +108,10 @@ html_theme_options = {}
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
html_short_title = "pytest-%s" % release
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
@@ -136,7 +137,7 @@ html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
html_sidebars = {'index': 'indexsidebar.html'}
|
||||
#html_sidebars = {'index': 'indexsidebar.html'}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
@@ -145,16 +146,16 @@ html_sidebars = {'index': 'indexsidebar.html'}
|
||||
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
html_use_index = False
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
@@ -185,7 +186,7 @@ htmlhelp_basename = 'pytestdoc'
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'pytest.tex', u'pytest Documentation',
|
||||
('contents', 'pytest.tex', u'pytest Documentation',
|
||||
u'holger krekel et alii', 'manual'),
|
||||
]
|
||||
|
||||
@@ -210,16 +211,15 @@ latex_documents = [
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
latex_domain_indices = False
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pytest', u'pytest Documentation',
|
||||
[u'holger krekel et alii'], 1)
|
||||
('usage', 'pytest', u'pytest usage',
|
||||
[u'holger krekel at merlinux eu'], 1)
|
||||
]
|
||||
|
||||
|
||||
@@ -227,9 +227,9 @@ man_pages = [
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'pytest'
|
||||
epub_author = u'holger krekel et alii'
|
||||
epub_publisher = u'holger krekel et alii'
|
||||
epub_copyright = u'2010, holger krekel et alii'
|
||||
epub_author = u'holger krekel at merlinux eu'
|
||||
epub_publisher = u'holger krekel at merlinux eu'
|
||||
epub_copyright = u'2011, holger krekel et alii'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
|
||||
.. _toc:
|
||||
|
||||
Table of Contents
|
||||
Full pytest documenation
|
||||
========================
|
||||
|
||||
.. note::
|
||||
version 2.0 introduces :ref:`pytest as the main Python import name <naming20>`
|
||||
`Download latest version as PDF <http://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
|
||||
|
||||
.. `Download latest version as EPUB <http://media.readthedocs.org/epub/pytest/latest/pytest.epub>`_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
example/index
|
||||
apiref
|
||||
plugins
|
||||
example/index
|
||||
talks
|
||||
develop
|
||||
announce/index
|
||||
@@ -22,12 +23,4 @@ Table of Contents
|
||||
:hidden:
|
||||
|
||||
changelog.txt
|
||||
naming20.txt
|
||||
example/attic
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
basic test configuration
|
||||
Basic test configuration
|
||||
===================================
|
||||
|
||||
Command line options and configuration file settings
|
||||
@@ -59,7 +59,7 @@ progress output, you can write it into a configuration file::
|
||||
|
||||
From now on, running ``py.test`` will add the specified options.
|
||||
|
||||
builtin configuration file options
|
||||
Builtin configuration file options
|
||||
----------------------------------------------
|
||||
|
||||
.. confval:: minversion
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
doctest integration for modules and test files
|
||||
Doctest integration for modules and test files
|
||||
=========================================================
|
||||
|
||||
By default all files matching the ``test*.txt`` pattern will
|
||||
@@ -44,9 +44,10 @@ then you can just invoke ``py.test`` without command line options::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
mymodule.py .
|
||||
|
||||
========================= 1 passed in 0.40 seconds =========================
|
||||
========================= 1 passed in 0.06 seconds =========================
|
||||
[?1034h
|
||||
@@ -7,6 +7,10 @@ Usages and Examples
|
||||
Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
|
||||
need more examples or have questions. Also take a look at the :ref:`comprehensive documentation <toc>` which contains many example snippets as well.
|
||||
|
||||
.. note::
|
||||
|
||||
see :doc:`../getting-started` for basic introductionary examples
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.. _mysetup:
|
||||
|
||||
mysetup pattern: application specific test fixtures
|
||||
Mysetup pattern: application specific test fixtures
|
||||
==========================================================
|
||||
|
||||
Here is a basic useful step-by-step example for managing and interacting
|
||||
@@ -12,8 +12,8 @@ where we have the glue and test support code for bootstrapping and
|
||||
configuring application objects and allow test modules and test
|
||||
functions to stay ignorant of involved details.
|
||||
|
||||
step1: implementing the test/app-specific ``mysetup`` pattern
|
||||
-------------------------------------------------------------
|
||||
Step 1: Implementing the test/app-specific ``mysetup`` pattern
|
||||
--------------------------------------------------------------
|
||||
|
||||
Let's write a simple test function using a ``mysetup`` funcarg::
|
||||
|
||||
@@ -49,7 +49,7 @@ You can now run the test::
|
||||
|
||||
$ py.test test_sample.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
@@ -57,7 +57,7 @@ You can now run the test::
|
||||
================================= FAILURES =================================
|
||||
_______________________________ test_answer ________________________________
|
||||
|
||||
mysetup = <conftest.MySetup instance at 0x2c1b128>
|
||||
mysetup = <conftest.MySetup instance at 0x1013145a8>
|
||||
|
||||
def test_answer(mysetup):
|
||||
app = mysetup.myapp()
|
||||
@@ -76,7 +76,7 @@ the concrete question or answers actually mean, please see here_.
|
||||
.. _here: http://uncyclopedia.wikia.com/wiki/The_Hitchhiker's_Guide_to_the_Galaxy
|
||||
.. _`tut-cmdlineoption`:
|
||||
|
||||
step 2: checking a command line option and skipping tests
|
||||
Step 2: Checking a command line option and skipping tests
|
||||
-----------------------------------------------------------
|
||||
|
||||
To add a command line option we update the ``conftest.py`` of
|
||||
@@ -122,14 +122,14 @@ Running it yields::
|
||||
|
||||
$ py.test test_ssh.py -rs
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_ssh.py s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /tmp/doc-exec-37/conftest.py:22: specify ssh host with --ssh
|
||||
SKIP [1] /Users/hpk/tmp/doc-exec-167/conftest.py:22: specify ssh host with --ssh
|
||||
|
||||
======================== 1 skipped in 0.01 seconds =========================
|
||||
======================== 1 skipped in 0.02 seconds =========================
|
||||
|
||||
If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Working with non-python tests
|
||||
|
||||
.. _`yaml plugin`:
|
||||
|
||||
a basic example for specifying tests in Yaml files
|
||||
A basic example for specifying tests in Yaml files
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
|
||||
@@ -27,7 +27,7 @@ now execute the test specification::
|
||||
|
||||
nonpython $ py.test test_simple.yml
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml .F
|
||||
@@ -37,7 +37,7 @@ now execute the test specification::
|
||||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.24 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.09 seconds ====================
|
||||
|
||||
You get one dot for the passing ``sub1: sub1`` check and one failure.
|
||||
Obviously in the above ``conftest.py`` you'll want to implement a more
|
||||
@@ -56,7 +56,7 @@ reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml:1: usecase: ok PASSED
|
||||
@@ -67,17 +67,17 @@ reporting in ``verbose`` mode::
|
||||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.07 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.09 seconds ====================
|
||||
|
||||
While developing your custom test collection and execution it's also
|
||||
interesting to just look at the collection tree::
|
||||
|
||||
nonpython $ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlItem 'hello'>
|
||||
|
||||
============================= in 0.07 seconds =============================
|
||||
============================= in 0.08 seconds =============================
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
|
||||
.. _paramexamples:
|
||||
|
||||
parametrizing tests
|
||||
Parametrizing tests
|
||||
=================================================
|
||||
|
||||
py.test allows to easily implement your own custom
|
||||
parametrization scheme for tests. Here we provide
|
||||
some examples for inspiration and re-use.
|
||||
|
||||
generating parameters combinations, depending on command line
|
||||
Generating parameters combinations, depending on command line
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -44,7 +44,7 @@ This means that we only run 2 tests if we do not pass ``--all``::
|
||||
$ py.test -q test_compute.py
|
||||
collecting ... collected 2 items
|
||||
..
|
||||
2 passed in 0.01 seconds
|
||||
2 passed in 0.02 seconds
|
||||
|
||||
We run only two computations, so we see two dots.
|
||||
let's run the full monty::
|
||||
@@ -114,7 +114,7 @@ Let's first see how it looks like at collection time::
|
||||
|
||||
$ py.test test_backends.py --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[0]'>
|
||||
@@ -130,7 +130,7 @@ And then when we run the test::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_db_initialized[1] __________________________
|
||||
|
||||
db = <conftest.DB2 instance at 0x2bf7bd8>
|
||||
db = <conftest.DB2 instance at 0x101316b90>
|
||||
|
||||
def test_db_initialized(db):
|
||||
# a dummy test
|
||||
@@ -139,7 +139,7 @@ And then when we run the test::
|
||||
E Failed: deliberately failing for demo purposes
|
||||
|
||||
test_backends.py:6: Failed
|
||||
1 failed, 1 passed in 0.03 seconds
|
||||
1 failed, 1 passed in 0.02 seconds
|
||||
|
||||
Now you see that one invocation of the test passes and another fails,
|
||||
as it to be expected.
|
||||
@@ -184,7 +184,7 @@ the respective settings::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_db_initialized[1] __________________________
|
||||
|
||||
db = <conftest.DB2 instance at 0x19bcb90>
|
||||
db = <conftest.DB2 instance at 0x10131c488>
|
||||
|
||||
def test_db_initialized(db):
|
||||
# a dummy test
|
||||
@@ -195,7 +195,7 @@ the respective settings::
|
||||
test_backends.py:6: Failed
|
||||
_________________________ TestClass.test_equals[0] _________________________
|
||||
|
||||
self = <test_parametrize.TestClass instance at 0x19ca8c0>, a = 1, b = 2
|
||||
self = <test_parametrize.TestClass instance at 0x101322170>, a = 1, b = 2
|
||||
|
||||
def test_equals(self, a, b):
|
||||
> assert a == b
|
||||
@@ -204,7 +204,7 @@ the respective settings::
|
||||
test_parametrize.py:17: AssertionError
|
||||
______________________ TestClass.test_zerodivision[1] ______________________
|
||||
|
||||
self = <test_parametrize.TestClass instance at 0x19cd4d0>, a = 3, b = 2
|
||||
self = <test_parametrize.TestClass instance at 0x1013228c0>, a = 3, b = 2
|
||||
|
||||
def test_zerodivision(self, a, b):
|
||||
> pytest.raises(ZeroDivisionError, "a/b")
|
||||
@@ -252,7 +252,7 @@ Running it gives similar results as before::
|
||||
================================= FAILURES =================================
|
||||
_________________________ TestClass.test_equals[0] _________________________
|
||||
|
||||
self = <test_parametrize2.TestClass instance at 0x1cf1170>, a = 1, b = 2
|
||||
self = <test_parametrize2.TestClass instance at 0x10130ac20>, a = 1, b = 2
|
||||
|
||||
@params([dict(a=1, b=2), dict(a=3, b=3), ])
|
||||
def test_equals(self, a, b):
|
||||
@@ -262,7 +262,7 @@ Running it gives similar results as before::
|
||||
test_parametrize2.py:19: AssertionError
|
||||
______________________ TestClass.test_zerodivision[1] ______________________
|
||||
|
||||
self = <test_parametrize2.TestClass instance at 0x1d02170>, a = 3, b = 2
|
||||
self = <test_parametrize2.TestClass instance at 0x10131c878>, a = 3, b = 2
|
||||
|
||||
@params([dict(a=1, b=0), dict(a=3, b=2)])
|
||||
def test_zerodivision(self, a, b):
|
||||
@@ -270,9 +270,9 @@ Running it gives similar results as before::
|
||||
E Failed: DID NOT RAISE
|
||||
|
||||
test_parametrize2.py:23: Failed
|
||||
2 failed, 2 passed in 0.03 seconds
|
||||
2 failed, 2 passed in 0.04 seconds
|
||||
|
||||
checking serialization between Python interpreters
|
||||
Checking serialization between Python interpreters
|
||||
--------------------------------------------------------------
|
||||
|
||||
Here is a stripped down real-life example of using parametrized
|
||||
@@ -290,5 +290,5 @@ Running it (with Python-2.4 through to Python2.7 installed)::
|
||||
|
||||
. $ py.test -q multipython.py
|
||||
collecting ... collected 75 items
|
||||
....s....s....s....ssssss....s....s....s....ssssss....s....s....s....ssssss
|
||||
48 passed, 27 skipped in 2.04 seconds
|
||||
ssssss...ss...ss...ssssssssssss...ss...ss...ssssssssssss...ss...ss...ssssss
|
||||
27 passed, 48 skipped in 3.04 seconds
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Changing standard (Python) test discovery
|
||||
===============================================
|
||||
|
||||
changing directory recursion
|
||||
Changing directory recursion
|
||||
-----------------------------------------------------
|
||||
|
||||
You can set the :confval:`norecursedirs` option in an ini-file, for example your ``setup.cfg`` in the project root directory::
|
||||
@@ -14,7 +14,7 @@ This would tell py.test to not recurse into typical subversion or sphinx-build d
|
||||
|
||||
.. _`change naming conventions`:
|
||||
|
||||
change naming conventions
|
||||
Changing naming conventions
|
||||
-----------------------------------------------------
|
||||
|
||||
You can configure different naming conventions by setting
|
||||
@@ -43,7 +43,7 @@ then the test collection looks like this::
|
||||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
@@ -51,9 +51,9 @@ then the test collection looks like this::
|
||||
<Function 'check_simple'>
|
||||
<Function 'check_complex'>
|
||||
|
||||
============================= in 0.01 seconds =============================
|
||||
============================= in 0.02 seconds =============================
|
||||
|
||||
interpret cmdline arguments as Python packages
|
||||
Interpreting cmdline arguments as Python packages
|
||||
-----------------------------------------------------
|
||||
|
||||
You can use the ``--pyargs`` option to make py.test try
|
||||
@@ -75,14 +75,14 @@ Now a simple invocation of ``py.test NAME`` will check
|
||||
if NAME exists as an importable package/module and otherwise
|
||||
treat it as a filesystem path.
|
||||
|
||||
finding out what is collected
|
||||
Finding out what is collected
|
||||
-----------------------------------------------
|
||||
|
||||
You can always peek at the collection tree without running tests like this::
|
||||
|
||||
. $ py.test --collectonly pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 3 items
|
||||
<Module 'pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
|
||||
@@ -13,7 +13,7 @@ get on the terminal - we are working on that):
|
||||
|
||||
assertion $ py.test failure_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 39 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
@@ -30,7 +30,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:15: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x14b9890>
|
||||
self = <failure_demo.TestFailing object at 0x10134bc50>
|
||||
|
||||
def test_simple(self):
|
||||
def f():
|
||||
@@ -40,13 +40,13 @@ get on the terminal - we are working on that):
|
||||
|
||||
> assert f() == g()
|
||||
E assert 42 == 43
|
||||
E + where 42 = <function f at 0x14a5e60>()
|
||||
E + and 43 = <function g at 0x14bc1b8>()
|
||||
E + where 42 = <function f at 0x101322320>()
|
||||
E + and 43 = <function g at 0x101322398>()
|
||||
|
||||
failure_demo.py:28: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x14b9b50>
|
||||
self = <failure_demo.TestFailing object at 0x10134b150>
|
||||
|
||||
def test_simple_multiline(self):
|
||||
otherfunc_multi(
|
||||
@@ -59,26 +59,26 @@ get on the terminal - we are working on that):
|
||||
a = 42, b = 54
|
||||
|
||||
def otherfunc_multi(a,b):
|
||||
assert (a ==
|
||||
> b)
|
||||
> assert (a ==
|
||||
b)
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:12: AssertionError
|
||||
failure_demo.py:11: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x14b9790>
|
||||
self = <failure_demo.TestFailing object at 0x10134b710>
|
||||
|
||||
def test_not(self):
|
||||
def f():
|
||||
return 42
|
||||
> assert not f()
|
||||
E assert not 42
|
||||
E + where 42 = <function f at 0x14bc398>()
|
||||
E + where 42 = <function f at 0x101322398>()
|
||||
|
||||
failure_demo.py:38: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14aa810>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10134be10>
|
||||
|
||||
def test_eq_text(self):
|
||||
> assert 'spam' == 'eggs'
|
||||
@@ -89,7 +89,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:42: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1576190>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101347110>
|
||||
|
||||
def test_eq_similar_text(self):
|
||||
> assert 'foo 1 bar' == 'foo 2 bar'
|
||||
@@ -102,7 +102,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:45: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14a7450>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343d50>
|
||||
|
||||
def test_eq_multiline_text(self):
|
||||
> assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
|
||||
@@ -115,7 +115,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:48: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14b9350>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10134b210>
|
||||
|
||||
def test_eq_long_text(self):
|
||||
a = '1'*100 + 'a' + '2'*100
|
||||
@@ -132,7 +132,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:53: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x15764d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343d90>
|
||||
|
||||
def test_eq_long_text_multiline(self):
|
||||
a = '1\n'*100 + 'a' + '2\n'*100
|
||||
@@ -156,7 +156,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:58: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1576350>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343dd0>
|
||||
|
||||
def test_eq_list(self):
|
||||
> assert [0, 1, 2] == [0, 1, 3]
|
||||
@@ -166,7 +166,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:61: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1576f10>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343b90>
|
||||
|
||||
def test_eq_list_long(self):
|
||||
a = [0]*100 + [1] + [3]*100
|
||||
@@ -178,7 +178,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:66: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1576390>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343210>
|
||||
|
||||
def test_eq_dict(self):
|
||||
> assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
|
||||
@@ -191,7 +191,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:69: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14bd790>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343990>
|
||||
|
||||
def test_eq_set(self):
|
||||
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||
@@ -207,7 +207,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:72: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x157a7d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343590>
|
||||
|
||||
def test_eq_longer_list(self):
|
||||
> assert [1,2] == [1,2,3]
|
||||
@@ -217,7 +217,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:75: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x157ab50>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343e50>
|
||||
|
||||
def test_in_list(self):
|
||||
> assert 1 in [0, 2, 3, 4, 5]
|
||||
@@ -226,7 +226,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:78: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x157a090>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10133bb10>
|
||||
|
||||
def test_not_in_text_multiline(self):
|
||||
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
|
||||
@@ -244,7 +244,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:82: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x14aaa50>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10133b990>
|
||||
|
||||
def test_not_in_text_single(self):
|
||||
text = 'single foo line'
|
||||
@@ -257,7 +257,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:86: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x157ab90>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10133bbd0>
|
||||
|
||||
def test_not_in_text_single_long(self):
|
||||
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
|
||||
@@ -270,7 +270,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:90: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1576ed0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x101343510>
|
||||
|
||||
def test_not_in_text_single_long_term(self):
|
||||
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
|
||||
@@ -289,7 +289,7 @@ get on the terminal - we are working on that):
|
||||
i = Foo()
|
||||
> assert i.b == 2
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x157a910>.b
|
||||
E + where 1 = <failure_demo.Foo object at 0x10133b390>.b
|
||||
|
||||
failure_demo.py:101: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
@@ -299,8 +299,8 @@ get on the terminal - we are working on that):
|
||||
b = 1
|
||||
> assert Foo().b == 2
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x1584610>.b
|
||||
E + where <failure_demo.Foo object at 0x1584610> = <class 'failure_demo.Foo'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x10133b250>.b
|
||||
E + where <failure_demo.Foo object at 0x10133b250> = <class 'failure_demo.Foo'>()
|
||||
|
||||
failure_demo.py:107: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
@@ -316,7 +316,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:116:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.Foo object at 0x157a3d0>
|
||||
self = <failure_demo.Foo object at 0x10133bd50>
|
||||
|
||||
def _get_b(self):
|
||||
> raise Exception('Failed to get attrib')
|
||||
@@ -332,15 +332,15 @@ get on the terminal - we are working on that):
|
||||
b = 2
|
||||
> assert Foo().b == Bar().b
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.Foo object at 0x157a1d0>.b
|
||||
E + where <failure_demo.Foo object at 0x157a1d0> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x157a9d0>.b
|
||||
E + where <failure_demo.Bar object at 0x157a9d0> = <class 'failure_demo.Bar'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x10133bb50>.b
|
||||
E + where <failure_demo.Foo object at 0x10133bb50> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x10133b1d0>.b
|
||||
E + where <failure_demo.Bar object at 0x10133b1d0> = <class 'failure_demo.Bar'>()
|
||||
|
||||
failure_demo.py:124: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x157d7e8>
|
||||
self = <failure_demo.TestRaises instance at 0x1013697e8>
|
||||
|
||||
def test_raises(self):
|
||||
s = 'qwe'
|
||||
@@ -352,10 +352,10 @@ get on the terminal - we are working on that):
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen /home/hpk/p/pytest/_pytest/python.py:831>:1: ValueError
|
||||
<0-codegen /Users/hpk/p/pytest/_pytest/python.py:833>:1: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x158ae60>
|
||||
self = <failure_demo.TestRaises instance at 0x101372a70>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(IOError, "int('3')")
|
||||
@@ -364,7 +364,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:136: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x158bb90>
|
||||
self = <failure_demo.TestRaises instance at 0x10136a908>
|
||||
|
||||
def test_raise(self):
|
||||
> raise ValueError("demo error")
|
||||
@@ -373,7 +373,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:139: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x157cd40>
|
||||
self = <failure_demo.TestRaises instance at 0x10136c710>
|
||||
|
||||
def test_tupleerror(self):
|
||||
> a,b = [1]
|
||||
@@ -382,7 +382,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:142: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x157d488>
|
||||
self = <failure_demo.TestRaises instance at 0x101365488>
|
||||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
l = [1,2,3]
|
||||
@@ -395,7 +395,7 @@ get on the terminal - we are working on that):
|
||||
l is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x158a7e8>
|
||||
self = <failure_demo.TestRaises instance at 0x101367248>
|
||||
|
||||
def test_some_error(self):
|
||||
> if namenotexi:
|
||||
@@ -420,10 +420,10 @@ get on the terminal - we are working on that):
|
||||
> assert 1 == 0
|
||||
E assert 1 == 0
|
||||
|
||||
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/example/assertion/failure_demo.py:162>:2: AssertionError
|
||||
<2-codegen 'abc-123' /Users/hpk/p/pytest/doc/example/assertion/failure_demo.py:162>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x158f8c0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101380f38>
|
||||
|
||||
def test_complex_error(self):
|
||||
def f():
|
||||
@@ -452,7 +452,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:5: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x158c998>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101367f80>
|
||||
|
||||
def test_z1_unpack_error(self):
|
||||
l = []
|
||||
@@ -462,7 +462,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:179: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x15854d0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101363dd0>
|
||||
|
||||
def test_z2_type_error(self):
|
||||
l = 3
|
||||
@@ -472,20 +472,19 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:183: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x14b65a8>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101364bd8>
|
||||
|
||||
def test_startswith(self):
|
||||
s = "123"
|
||||
g = "456"
|
||||
> assert s.startswith(g)
|
||||
E assert False
|
||||
E + where False = <built-in method startswith of str object at 0x14902a0>('456')
|
||||
E + where <built-in method startswith of str object at 0x14902a0> = '123'.startswith
|
||||
E assert <built-in method startswith of str object at 0x1013524e0>('456')
|
||||
E + where <built-in method startswith of str object at 0x1013524e0> = '123'.startswith
|
||||
|
||||
failure_demo.py:188: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x158d518>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101363fc8>
|
||||
|
||||
def test_startswith_nested(self):
|
||||
def f():
|
||||
@@ -493,39 +492,36 @@ get on the terminal - we are working on that):
|
||||
def g():
|
||||
return "456"
|
||||
> assert f().startswith(g())
|
||||
E assert False
|
||||
E + where False = <built-in method startswith of str object at 0x14902a0>('456')
|
||||
E + where <built-in method startswith of str object at 0x14902a0> = '123'.startswith
|
||||
E + where '123' = <function f at 0x15806e0>()
|
||||
E + and '456' = <function g at 0x1580aa0>()
|
||||
E assert <built-in method startswith of str object at 0x1013524e0>('456')
|
||||
E + where <built-in method startswith of str object at 0x1013524e0> = '123'.startswith
|
||||
E + where '123' = <function f at 0x10132c500>()
|
||||
E + and '456' = <function g at 0x10132c8c0>()
|
||||
|
||||
failure_demo.py:195: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1593440>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1013696c8>
|
||||
|
||||
def test_global_func(self):
|
||||
> assert isinstance(globf(42), float)
|
||||
E assert False
|
||||
E + where False = isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
E assert isinstance(43, float)
|
||||
E + where 43 = globf(42)
|
||||
|
||||
failure_demo.py:198: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x15952d8>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1013671b8>
|
||||
|
||||
def test_instance(self):
|
||||
self.x = 6*7
|
||||
> assert self.x != 42
|
||||
E assert 42 != 42
|
||||
E + where 42 = 42
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x15952d8>.x
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x1013671b8>.x
|
||||
|
||||
failure_demo.py:202: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1593758>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x101366560>
|
||||
|
||||
def test_compare(self):
|
||||
> assert globf(10) < 5
|
||||
@@ -535,7 +531,7 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:205: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x157cd88>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1013613b0>
|
||||
|
||||
def test_try_finally(self):
|
||||
x = 1
|
||||
@@ -544,4 +540,4 @@ get on the terminal - we are working on that):
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:210: AssertionError
|
||||
======================== 39 failed in 0.23 seconds =========================
|
||||
======================== 39 failed in 0.39 seconds =========================
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
.. highlightlang:: python
|
||||
|
||||
basic patterns and examples
|
||||
Basic patterns and examples
|
||||
==========================================================
|
||||
|
||||
pass different values to a test function, depending on command line options
|
||||
Pass different values to a test function, depending on command line options
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -53,7 +53,7 @@ Let's run this without supplying our new command line option::
|
||||
test_sample.py:6: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
first
|
||||
1 failed in 0.03 seconds
|
||||
1 failed in 0.02 seconds
|
||||
|
||||
And now with supplying a command line option::
|
||||
|
||||
@@ -85,7 +85,7 @@ next example or refer to :ref:`mysetup` for more information
|
||||
on real-life examples.
|
||||
|
||||
|
||||
dynamically adding command line options
|
||||
Dynamically adding command line options
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -109,17 +109,17 @@ directory with the above conftest.py::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
gw0 I / gw1 I / gw2 I / gw3 I
|
||||
gw0 [0] / gw1 [0] / gw2 [0] / gw3 [0]
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
gw0 I
|
||||
gw0 [0]
|
||||
|
||||
scheduling tests via LoadScheduling
|
||||
|
||||
============================= in 0.52 seconds =============================
|
||||
============================= in 0.48 seconds =============================
|
||||
|
||||
.. _`excontrolskip`:
|
||||
|
||||
control skipping of tests according to command line option
|
||||
Control skipping of tests according to command line option
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -156,27 +156,27 @@ and when running it will see a skipped "slow" test::
|
||||
|
||||
$ py.test -rs # "-rs" means report details on the little 's'
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_module.py .s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /tmp/doc-exec-42/conftest.py:9: need --runslow option to run
|
||||
SKIP [1] /Users/hpk/tmp/doc-exec-172/conftest.py:9: need --runslow option to run
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.01 seconds ====================
|
||||
=================== 1 passed, 1 skipped in 0.02 seconds ====================
|
||||
|
||||
Or run it including the ``slow`` marked test::
|
||||
|
||||
$ py.test --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_module.py ..
|
||||
|
||||
========================= 2 passed in 0.01 seconds =========================
|
||||
========================= 2 passed in 0.02 seconds =========================
|
||||
|
||||
writing well integrated assertion helpers
|
||||
Writing well integrated assertion helpers
|
||||
--------------------------------------------------
|
||||
|
||||
.. regendoc:wipe
|
||||
@@ -261,7 +261,7 @@ which will add the string to the test header accordingly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
project deps: mylib-1.1
|
||||
collecting ... collected 0 items
|
||||
|
||||
@@ -284,7 +284,7 @@ which will add info only when run with "--v"::
|
||||
|
||||
$ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
collecting ... collected 0 items
|
||||
@@ -295,7 +295,7 @@ and nothing when run plainly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
|
||||
20
doc/faq.txt
20
doc/faq.txt
@@ -47,19 +47,23 @@ customizable testing frameworks for Python. However,
|
||||
``py.test`` still uses many metaprogramming techniques and
|
||||
reading its source is thus likely not something for Python beginners.
|
||||
|
||||
A second "magic" issue arguably the assert statement re-intepreation:
|
||||
When an ``assert`` statement fails, py.test re-interprets the expression
|
||||
to show intermediate values if a test fails. If your expression
|
||||
has side effects (better to avoid them anyway!) the intermediate values
|
||||
may not be the same, obfuscating the initial error (this is also
|
||||
explained at the command line if it happens).
|
||||
``py.test --no-assert`` turns off assert re-interpretation.
|
||||
A second "magic" issue arguably the assert statement debugging feature. When
|
||||
loading test modules py.test rewrites the source code of assert statements. When
|
||||
a rewritten assert statement fails, its error message has more information than
|
||||
the original. py.test also has a second assert debugging technique. When an
|
||||
``assert`` statement that was missed by the rewriter fails, py.test
|
||||
re-interprets the expression to show intermediate values if a test fails. This
|
||||
second technique suffers from caveat that the rewriting does not: If your
|
||||
expression has side effects (better to avoid them anyway!) the intermediate
|
||||
values may not be the same, confusing the reinterpreter and obfuscating the
|
||||
initial error (this is also explained at the command line if it happens).
|
||||
You can turn off all assertion debugging with ``py.test --assertmode=off``.
|
||||
|
||||
.. _`py namespaces`: index.html
|
||||
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
|
||||
|
||||
|
||||
function arguments, parametrized tests and setup
|
||||
Function arguments, parametrized tests and setup
|
||||
-------------------------------------------------------
|
||||
|
||||
.. _funcargs: test/funcargs.html
|
||||
|
||||
@@ -61,7 +61,7 @@ Running the test looks like this::
|
||||
|
||||
$ py.test test_simplefactory.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_simplefactory.py F
|
||||
@@ -167,7 +167,7 @@ Running this::
|
||||
|
||||
$ py.test test_example.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 10 items
|
||||
|
||||
test_example.py .........F
|
||||
@@ -182,7 +182,7 @@ Running this::
|
||||
E assert 9 < 9
|
||||
|
||||
test_example.py:7: AssertionError
|
||||
==================== 1 failed, 9 passed in 0.03 seconds ====================
|
||||
==================== 1 failed, 9 passed in 0.04 seconds ====================
|
||||
|
||||
Note that the ``pytest_generate_tests(metafunc)`` hook is called during
|
||||
the test collection phase which is separate from the actual test running.
|
||||
@@ -190,7 +190,7 @@ Let's just look at what is collected::
|
||||
|
||||
$ py.test --collectonly test_example.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 10 items
|
||||
<Module 'test_example.py'>
|
||||
<Function 'test_func[0]'>
|
||||
@@ -204,19 +204,37 @@ Let's just look at what is collected::
|
||||
<Function 'test_func[8]'>
|
||||
<Function 'test_func[9]'>
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
If you want to select only the run with the value ``7`` you could do::
|
||||
|
||||
$ py.test -v -k 7 test_example.py # or -k test_func[7]
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3 -- /home/hpk/venv/0/bin/python
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3 -- /Users/hpk/venv/0/bin/python
|
||||
collecting ... collected 10 items
|
||||
|
||||
test_example.py:6: test_func[0] PASSED
|
||||
test_example.py:6: test_func[1] PASSED
|
||||
test_example.py:6: test_func[2] PASSED
|
||||
test_example.py:6: test_func[3] PASSED
|
||||
test_example.py:6: test_func[4] PASSED
|
||||
test_example.py:6: test_func[5] PASSED
|
||||
test_example.py:6: test_func[6] PASSED
|
||||
test_example.py:6: test_func[7] PASSED
|
||||
test_example.py:6: test_func[8] PASSED
|
||||
test_example.py:6: test_func[9] FAILED
|
||||
|
||||
======================== 9 tests deselected by '7' =========================
|
||||
================== 1 passed, 9 deselected in 0.01 seconds ==================
|
||||
================================= FAILURES =================================
|
||||
_______________________________ test_func[9] _______________________________
|
||||
|
||||
numiter = 9
|
||||
|
||||
def test_func(numiter):
|
||||
> assert numiter < 9
|
||||
E assert 9 < 9
|
||||
|
||||
test_example.py:7: AssertionError
|
||||
==================== 1 failed, 9 passed in 0.05 seconds ====================
|
||||
|
||||
You might want to look at :ref:`more parametrization examples <paramexamples>`.
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
Installation and Getting Started
|
||||
===================================
|
||||
|
||||
**Compatibility**: Python 2.4-3.2, Jython, PyPy on Unix/Posix and Windows
|
||||
**Pythons**: Python 2.4-3.2, Jython, PyPy
|
||||
|
||||
**Platforms**: Unix/Posix and Windows
|
||||
|
||||
**PyPI package name**: `pytest <http://pypi.python.org/pypi/pytest>`_
|
||||
|
||||
**documentation as PDF**: `download latest <http://pytest.org/latest/pytest.pdf>`_
|
||||
|
||||
.. _`getstarted`:
|
||||
|
||||
@@ -10,16 +16,16 @@ Installation
|
||||
|
||||
Installation options::
|
||||
|
||||
easy_install -U pytest # or
|
||||
pip install -U pytest
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
To check your installation has installed the correct version::
|
||||
|
||||
$ py.test --version
|
||||
This is py.test version 2.0.3, imported from /home/hpk/p/pytest/pytest.pyc
|
||||
This is py.test version 2.1.3, imported from /Users/hpk/p/pytest/pytest.pyc
|
||||
setuptools registered plugins:
|
||||
pytest-xdist-1.6.dev3 at /home/hpk/p/pytest-xdist/xdist/plugin.pyc
|
||||
pytest-incremental-0.1.0 at /home/hpk/venv/0/lib/python2.6/site-packages/pytest_incremental.pyc
|
||||
pytest-cov-1.4 at /Users/hpk/venv/0/lib/python2.7/site-packages/pytest_cov.pyc
|
||||
pytest-xdist-1.6 at /Users/hpk/venv/0/lib/python2.7/site-packages/xdist/plugin.pyc
|
||||
|
||||
If you get an error checkout :ref:`installation issues`.
|
||||
|
||||
@@ -41,7 +47,7 @@ That's it. You can execute the test function now::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
@@ -61,22 +67,12 @@ py.test found the ``test_answer`` function by following :ref:`standard test disc
|
||||
|
||||
.. note::
|
||||
|
||||
You can simply use the ``assert`` statement for asserting
|
||||
expectations because intermediate values will be presented to you.
|
||||
This is arguably easier than learning all the `the JUnit legacy
|
||||
methods`_.
|
||||
You can simply use the ``assert`` statement for asserting test
|
||||
expectations. pytest's :ref:`assert introspection` will intelligently
|
||||
report intermediate values of the assert expression freeing
|
||||
you from the need to learn the many names of `JUnit legacy methods`_.
|
||||
|
||||
However, there remains one caveat to using simple asserts: your
|
||||
assertion expression should better be side-effect free. Because
|
||||
after an assertion failed py.test will re-evaluate the expression
|
||||
in order to present intermediate values. You will get a nice warning
|
||||
and you can easily fix it: compute the value ahead of the assert and
|
||||
then do the assertion. Or maybe just use the assert "explicit message"
|
||||
syntax::
|
||||
|
||||
assert expr, "message" # show "message" if expr is not True
|
||||
|
||||
.. _`the JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
|
||||
.. _`JUnit legacy methods`: http://docs.python.org/library/unittest.html#test-cases
|
||||
|
||||
.. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
|
||||
|
||||
@@ -131,13 +127,12 @@ run the module by passing its filename::
|
||||
================================= FAILURES =================================
|
||||
____________________________ TestClass.test_two ____________________________
|
||||
|
||||
self = <test_class.TestClass instance at 0x142c320>
|
||||
self = <test_class.TestClass instance at 0x1013167a0>
|
||||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
> assert hasattr(x, 'check')
|
||||
E assert False
|
||||
E + where False = hasattr('hello', 'check')
|
||||
E assert hasattr('hello', 'check')
|
||||
|
||||
test_class.py:8: AssertionError
|
||||
1 failed, 1 passed in 0.03 seconds
|
||||
@@ -169,7 +164,7 @@ before performing the test function call. Let's just run it::
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_needsfiles ______________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-10/test_needsfiles0')
|
||||
tmpdir = local('/Users/hpk/tmp/pytest-93/test_needsfiles0')
|
||||
|
||||
def test_needsfiles(tmpdir):
|
||||
print tmpdir
|
||||
@@ -178,8 +173,8 @@ before performing the test function call. Let's just run it::
|
||||
|
||||
test_tmpdir.py:3: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
/tmp/pytest-10/test_needsfiles0
|
||||
1 failed in 0.13 seconds
|
||||
/Users/hpk/tmp/pytest-93/test_needsfiles0
|
||||
1 failed in 0.04 seconds
|
||||
|
||||
Before the test runs, a unique-per-test-invocation temporary directory
|
||||
was created. More info at :ref:`tmpdir handling`.
|
||||
@@ -188,7 +183,7 @@ You can find out what kind of builtin :ref:`funcargs` exist by typing::
|
||||
|
||||
py.test --funcargs # shows builtin and custom function arguments
|
||||
|
||||
where to go next
|
||||
Where to go next
|
||||
-------------------------------------
|
||||
|
||||
Here are a few suggestions where to go next:
|
||||
@@ -206,10 +201,15 @@ Known Installation issues
|
||||
easy_install or pip not found?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Consult `distribute docs`_ to install the ``easy_install``
|
||||
tool on your machine. You may also use the older
|
||||
`setuptools`_ project but it lacks bug fixes and does not
|
||||
work on Python3. If you use Python2 you may also install pip_.
|
||||
.. _`install pip`: http://www.pip-installer.org/en/latest/index.html
|
||||
|
||||
`Install pip`_ for a state of the art python package installer.
|
||||
|
||||
Or consult `distribute docs`_ to install the ``easy_install``
|
||||
tool on your machine.
|
||||
|
||||
You may also use the older `setuptools`_ project but it lacks bug fixes
|
||||
and does not work on Python3.
|
||||
|
||||
py.test not found on Windows despite installation?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -53,7 +53,7 @@ You can tell people to download the script and then e.g. run it like this::
|
||||
Integrating with distutils / ``python setup.py test``
|
||||
--------------------------------------------------------
|
||||
|
||||
You can easily integrate test runs into your distutils or
|
||||
You can integrate test runs into your distutils or
|
||||
setuptools based project. Use the `genscript method`_
|
||||
to generate a standalone py.test script::
|
||||
|
||||
@@ -106,9 +106,9 @@ Conventions for Python test discovery
|
||||
* ``Test`` prefixed test classes (without an ``__init__`` method)
|
||||
* ``test_`` prefixed test functions or methods are test items
|
||||
|
||||
For examples of how to cnd cusotmize your test discovery :doc:`example/pythoncollection`.
|
||||
For examples of how to customize your test discovery :doc:`example/pythoncollection`.
|
||||
|
||||
py.test additionally discovers tests using the standard
|
||||
Within Python modules, py.test also discovers tests using the standard
|
||||
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
|
||||
|
||||
Choosing a test layout / import rules
|
||||
@@ -138,7 +138,10 @@ py.test supports common test layouts:
|
||||
test_app.py
|
||||
...
|
||||
|
||||
You can always run your tests by pointing to it::
|
||||
In both cases you usually need to make sure that ``mypkg`` is importable,
|
||||
for example by using the setuptools ``python setup.py develop`` method.
|
||||
|
||||
You can run your tests by pointing to it::
|
||||
|
||||
py.test tests/test_app.py # for external test dirs
|
||||
py.test mypkg/test/test_app.py # for inlined test dirs
|
||||
@@ -150,18 +153,27 @@ You can always run your tests by pointing to it::
|
||||
|
||||
.. note::
|
||||
|
||||
Test modules are imported under their fully qualified name as follows:
|
||||
If py.test finds a "a/b/test_module.py" test file while
|
||||
recursing into the filesystem it determines the import name
|
||||
as follows:
|
||||
|
||||
* find ``basedir`` -- this is the first "upward" (towards the root)
|
||||
directory not containing an ``__init__.py``
|
||||
directory not containing an ``__init__.py``. If both the ``a``
|
||||
and ``b`` directories contain an ``__init__.py`` the basedir will
|
||||
be the parent dir of ``a``.
|
||||
|
||||
* perform ``sys.path.insert(0, basedir)`` to make the fully
|
||||
qualified test module path importable.
|
||||
* perform ``sys.path.insert(0, basedir)`` to make the test module
|
||||
importable under the fully qualified import name.
|
||||
|
||||
* ``import path.to.test_module`` where the path is determined
|
||||
by converting path separators into "." files. This means
|
||||
* ``import a.b.test_module`` where the path is determined
|
||||
by converting path separators ``/`` into "." characters. This means
|
||||
you must follow the convention of having directory and file
|
||||
names map to the import names.
|
||||
names map directly to the import names.
|
||||
|
||||
The reason for this somewhat evolved importing technique is
|
||||
that in larger projects multiple test modules might import
|
||||
from each other and thus deriving a canonical import name helps
|
||||
to avoid surprises such as a test modules getting imported twice.
|
||||
|
||||
|
||||
.. include:: links.inc
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
|
||||
|
||||
Welcome to ``py.test``!
|
||||
Welcome to pytest!
|
||||
=============================================
|
||||
|
||||
|
||||
- **a mature full-featured testing tool**
|
||||
|
||||
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
|
||||
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython-2.5.1
|
||||
- :ref:`comprehensive online <toc>` and `PDF documentation <pytest.pdf>`_
|
||||
- continuously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
|
||||
- used in :ref:`many projects and organisations <projects>`, in test
|
||||
suites ranging from 10 to 10s of thousands of tests
|
||||
- has :ref:`comprehensive documentation <toc>`
|
||||
- comes with :ref:`tested examples <examples>`
|
||||
- comes with many :ref:`tested examples <examples>`
|
||||
- supports :ref:`good integration practises <goodpractises>`
|
||||
|
||||
- **provides no-boilerplate testing**
|
||||
@@ -54,9 +53,4 @@ Welcome to ``py.test``!
|
||||
|
||||
.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
contact.txt
|
||||
contents.txt
|
||||
|
||||
|
||||
14
doc/mark.txt
14
doc/mark.txt
@@ -1,7 +1,7 @@
|
||||
|
||||
.. _mark:
|
||||
|
||||
mark test functions with attributes
|
||||
Marking test functions with attributes
|
||||
=================================================================
|
||||
|
||||
.. currentmodule:: _pytest.mark
|
||||
@@ -88,35 +88,35 @@ You can use the ``-k`` command line option to select tests::
|
||||
|
||||
$ py.test -k webtest # running with the above defined examples yields
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 4 items
|
||||
|
||||
test_mark.py ..
|
||||
test_mark_classlevel.py ..
|
||||
|
||||
========================= 4 passed in 0.01 seconds =========================
|
||||
========================= 4 passed in 0.03 seconds =========================
|
||||
|
||||
And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
$ py.test -k-webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 4 items
|
||||
|
||||
===================== 4 tests deselected by '-webtest' =====================
|
||||
======================= 4 deselected in 0.01 seconds =======================
|
||||
======================= 4 deselected in 0.02 seconds =======================
|
||||
|
||||
Or to only select the class::
|
||||
|
||||
$ py.test -kTestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 4 items
|
||||
|
||||
test_mark_classlevel.py ..
|
||||
|
||||
==================== 2 tests deselected by 'TestClass' =====================
|
||||
================== 2 passed, 2 deselected in 0.01 seconds ==================
|
||||
================== 2 passed, 2 deselected in 0.02 seconds ==================
|
||||
|
||||
API reference for mark related objects
|
||||
------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
monkeypatching/mocking modules and environments
|
||||
Monkeypatching/mocking modules and environments
|
||||
================================================================
|
||||
|
||||
.. currentmodule:: _pytest.monkeypatch
|
||||
@@ -39,7 +39,7 @@ will be undone.
|
||||
.. background check:
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Running test written for nose
|
||||
Running tests written for nose
|
||||
=======================================
|
||||
|
||||
.. include:: links.inc
|
||||
|
||||
@@ -5,6 +5,7 @@ Getting started basics
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
index.txt
|
||||
getting-started.txt
|
||||
usage.txt
|
||||
goodpractises.txt
|
||||
|
||||
@@ -203,7 +203,7 @@ and their names. It will also print local plugins aka
|
||||
|
||||
.. _`cmdunregister`:
|
||||
|
||||
deactivate / unregister a plugin by name
|
||||
Deactivating / unregistering a plugin by name
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
You can prevent plugins from loading or unregister them::
|
||||
@@ -253,7 +253,7 @@ in the `pytest repository <http://bitbucket.org/hpk42/pytest/>`_.
|
||||
py.test hook reference
|
||||
====================================
|
||||
|
||||
hook specification and validation
|
||||
Hook specification and validation
|
||||
-----------------------------------------
|
||||
|
||||
py.test calls hook functions to implement initialization, running,
|
||||
@@ -264,7 +264,7 @@ specification. However, a hook function may accept *fewer* parameters
|
||||
by simply not specifying them. If you mistype argument names or the
|
||||
hook name itself you get an error showing the available arguments.
|
||||
|
||||
initialisation, command line and configuration hooks
|
||||
Initialisation, command line and configuration hooks
|
||||
--------------------------------------------------------------------
|
||||
|
||||
.. currentmodule:: _pytest.hookspec
|
||||
@@ -277,7 +277,7 @@ initialisation, command line and configuration hooks
|
||||
.. autofunction:: pytest_configure
|
||||
.. autofunction:: pytest_unconfigure
|
||||
|
||||
generic "runtest" hooks
|
||||
Generic "runtest" hooks
|
||||
------------------------------
|
||||
|
||||
All all runtest related hooks receive a :py:class:`pytest.Item` object.
|
||||
@@ -297,7 +297,7 @@ into interactive debugging when a test failure occurs.
|
||||
The :py:mod:`_pytest.terminal` reported specifically uses
|
||||
the reporting hook to print information about a test run.
|
||||
|
||||
collection hooks
|
||||
Collection hooks
|
||||
------------------------------
|
||||
|
||||
py.test calls the following hooks for collecting files and directories:
|
||||
@@ -312,7 +312,7 @@ you can use the following hook:
|
||||
.. autofunction:: pytest_pycollect_makeitem
|
||||
|
||||
|
||||
reporting hooks
|
||||
Reporting hooks
|
||||
------------------------------
|
||||
|
||||
Session related reporting hooks:
|
||||
|
||||
@@ -5,7 +5,8 @@ Project examples
|
||||
|
||||
Here are some examples of projects using py.test (please send notes via :ref:`contact`):
|
||||
|
||||
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over `16000 tests <http://test.pypy.org>`_
|
||||
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over
|
||||
`16000 tests <http://buildbot.pypy.org/summary?branch=%3Ctrunk%3E>`_
|
||||
* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
|
||||
* `tox <http://codespeak.net/tox>`_, virtualenv/Hudson integration tool
|
||||
* `PIDA <http://pida.co.uk>`_ framework for integrated development
|
||||
@@ -42,9 +43,10 @@ Some organisations using py.test
|
||||
-----------------------------------
|
||||
|
||||
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
|
||||
* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
|
||||
* `Tandberg <http://www.tandberg.com/>`_
|
||||
* `Shootq <http://web.shootq.com/>`_
|
||||
* `Stups department of Heinrich Heine University Düsseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
|
||||
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
|
||||
* `cellzome <http://www.cellzome.com/>`_
|
||||
* `Open End, Gothenborg <http://www.openend.se>`_
|
||||
* `Laboraratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
asserting deprecation and other warnings.
|
||||
Asserting deprecation and other warnings.
|
||||
=====================================================
|
||||
|
||||
recwarn function argument
|
||||
The recwarn function argument
|
||||
------------------------------------
|
||||
|
||||
You can use the ``recwarn`` funcarg to assert that code triggers
|
||||
@@ -24,7 +24,7 @@ The ``recwarn`` function argument provides these methods:
|
||||
* ``pop(category=None)``: return last warning matching the category.
|
||||
* ``clear()``: clear list of warnings
|
||||
|
||||
ensuring a function triggers a deprecation warning
|
||||
Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
You can also call a global helper for checking
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.. _`skip and xfail`:
|
||||
|
||||
skip and xfail: dealing with tests that can not succeed
|
||||
Skip and xfail: dealing with tests that can not succeed
|
||||
=====================================================================
|
||||
|
||||
If you have test functions that cannot be run on certain platforms
|
||||
@@ -62,7 +62,7 @@ decorator at module level like this::
|
||||
def test_function():
|
||||
...
|
||||
|
||||
skip all test functions of a class
|
||||
Skip all test functions of a class
|
||||
--------------------------------------
|
||||
|
||||
As with all function :ref:`marking <mark>` you can skip test functions at the
|
||||
@@ -92,7 +92,7 @@ Using multiple "skipif" decorators on a single function is generally fine - it m
|
||||
|
||||
.. _xfail:
|
||||
|
||||
mark a test function as expected to fail
|
||||
Mark a test function as expected to fail
|
||||
-------------------------------------------------------
|
||||
|
||||
You can use the ``xfail`` marker to indicate that you
|
||||
@@ -130,7 +130,7 @@ Running it with the report-on-xfail option gives this output::
|
||||
|
||||
example $ py.test -rx xfail_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 6 items
|
||||
|
||||
xfail_demo.py xxxxxx
|
||||
@@ -147,11 +147,11 @@ Running it with the report-on-xfail option gives this output::
|
||||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
|
||||
======================== 6 xfailed in 0.05 seconds =========================
|
||||
======================== 6 xfailed in 0.11 seconds =========================
|
||||
|
||||
.. _`evaluation of skipif/xfail conditions`:
|
||||
|
||||
evaluation of skipif/xfail expressions
|
||||
Evaluation of skipif/xfail expressions
|
||||
----------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0.2
|
||||
@@ -174,7 +174,7 @@ which you might have added::
|
||||
...
|
||||
|
||||
|
||||
imperative xfail from within a test or setup function
|
||||
Imperative xfail from within a test or setup function
|
||||
------------------------------------------------------
|
||||
|
||||
If you cannot declare xfail-conditions at import time
|
||||
@@ -186,7 +186,7 @@ within test or setup code. Example::
|
||||
pytest.xfail("unsupported configuration")
|
||||
|
||||
|
||||
skipping on a missing import dependency
|
||||
Skipping on a missing import dependency
|
||||
--------------------------------------------------
|
||||
|
||||
You can use the following import helper at module level
|
||||
@@ -202,7 +202,7 @@ version number of a library::
|
||||
|
||||
The version will be read from the specified module's ``__version__`` attribute.
|
||||
|
||||
imperative skip from within a test or setup function
|
||||
Imperative skip from within a test or setup function
|
||||
------------------------------------------------------
|
||||
|
||||
If for some reason you cannot declare skip-conditions
|
||||
|
||||
@@ -4,35 +4,40 @@ Talks and Tutorials
|
||||
|
||||
.. _`funcargs`: funcargs.html
|
||||
|
||||
tutorial examples and blog postings
|
||||
Tutorial examples and blog postings
|
||||
---------------------------------------------
|
||||
|
||||
.. _`tutorial1 repository`: http://bitbucket.org/hpk42/pytest-tutorial1/
|
||||
.. _`pycon 2010 tutorial PDF`: http://bitbucket.org/hpk42/pytest-tutorial1/raw/tip/pytest-basic.pdf
|
||||
|
||||
basic usage and funcargs:
|
||||
Basic usage and funcargs:
|
||||
|
||||
- `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_
|
||||
|
||||
function arguments:
|
||||
Function arguments:
|
||||
|
||||
- :ref:`mysetup`
|
||||
- `application setup in test functions with funcargs`_
|
||||
- `monkey patching done right`_ (blog post, consult `monkeypatch
|
||||
plugin`_ for actual 1.0 API)
|
||||
|
||||
test parametrization:
|
||||
Test parametrization:
|
||||
|
||||
- `generating parametrized tests with funcargs`_
|
||||
- `test generators and cached setup`_
|
||||
- `parametrizing tests, generalized`_ (blog post)
|
||||
- `putting test-hooks into local or global plugins`_ (blog post)
|
||||
|
||||
distributed testing:
|
||||
Assertion introspection:
|
||||
|
||||
- `(07/2011) Behind the scenes of py.test's new assertion rewriting
|
||||
<http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_
|
||||
|
||||
Distributed testing:
|
||||
|
||||
- `simultaneously test your code on all platforms`_ (blog entry)
|
||||
|
||||
plugin specific examples:
|
||||
Plugin specific examples:
|
||||
|
||||
- `skipping slow tests by default in py.test`_ (blog entry)
|
||||
|
||||
@@ -49,7 +54,7 @@ plugin specific examples:
|
||||
.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
|
||||
.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
|
||||
|
||||
conference talks and tutorials
|
||||
Conference talks and tutorials
|
||||
----------------------------------------
|
||||
|
||||
- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
|
||||
.. _`tmpdir handling`:
|
||||
|
||||
temporary directories and files
|
||||
Temporary directories and files
|
||||
================================================
|
||||
|
||||
the 'tmpdir' test function argument
|
||||
The 'tmpdir' test function argument
|
||||
-----------------------------------
|
||||
|
||||
You can use the ``tmpdir`` function argument which will
|
||||
@@ -28,7 +28,7 @@ Running this would result in a passed test except for the last
|
||||
|
||||
$ py.test test_tmpdir.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_tmpdir.py F
|
||||
@@ -36,7 +36,7 @@ Running this would result in a passed test except for the last
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_create_file _____________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-11/test_create_file0')
|
||||
tmpdir = local('/Users/hpk/tmp/pytest-94/test_create_file0')
|
||||
|
||||
def test_create_file(tmpdir):
|
||||
p = tmpdir.mkdir("sub").join("hello.txt")
|
||||
@@ -47,11 +47,11 @@ Running this would result in a passed test except for the last
|
||||
E assert 0
|
||||
|
||||
test_tmpdir.py:7: AssertionError
|
||||
========================= 1 failed in 0.06 seconds =========================
|
||||
========================= 1 failed in 0.05 seconds =========================
|
||||
|
||||
.. _`base temporary directory`:
|
||||
|
||||
the default base temporary directory
|
||||
The default base temporary directory
|
||||
-----------------------------------------------
|
||||
|
||||
Temporary directories are by default created as sub-directories of
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
.. _`unittest.TestCase`:
|
||||
|
||||
unittest.TestCase support
|
||||
Support for unittest.TestCase
|
||||
=====================================================================
|
||||
|
||||
py.test has limited support for running Python `unittest.py style`_ tests.
|
||||
@@ -24,7 +24,7 @@ Running it yields::
|
||||
|
||||
$ py.test test_unittest.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.6.6 -- pytest-2.0.3
|
||||
platform darwin -- Python 2.7.1 -- pytest-2.1.3
|
||||
collecting ... collected 1 items
|
||||
|
||||
test_unittest.py F
|
||||
@@ -42,7 +42,7 @@ Running it yields::
|
||||
test_unittest.py:8: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
hello
|
||||
========================= 1 failed in 0.02 seconds =========================
|
||||
========================= 1 failed in 0.04 seconds =========================
|
||||
|
||||
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Usage and Invocations
|
||||
|
||||
.. _cmdline:
|
||||
|
||||
calling pytest through ``python -m pytest``
|
||||
Calling pytest through ``python -m pytest``
|
||||
-----------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
@@ -38,7 +38,7 @@ To stop the testing process after the first (N) failures::
|
||||
py.test -x # stop after first failure
|
||||
py.test -maxfail=2 # stop after two failures
|
||||
|
||||
specifying tests / selecting tests
|
||||
Specifying tests / selecting tests
|
||||
---------------------------------------------------
|
||||
|
||||
Several test run options::
|
||||
@@ -98,7 +98,7 @@ can use a helper::
|
||||
In previous versions you could only enter PDB tracing if
|
||||
you disable capturing on the command line via ``py.test -s``.
|
||||
|
||||
creating JUnitXML format files
|
||||
Creating JUnitXML format files
|
||||
----------------------------------------------------
|
||||
|
||||
To create result files which can be read by Hudson_ or other Continuous
|
||||
@@ -108,7 +108,7 @@ integration servers, use this invocation::
|
||||
|
||||
to create an XML file at ``path``.
|
||||
|
||||
creating resultlog format files
|
||||
Creating resultlog format files
|
||||
----------------------------------------------------
|
||||
|
||||
To create plain-text machine-readable result files you can issue::
|
||||
@@ -121,7 +121,7 @@ by the `PyPy-test`_ web page to show test results over several revisions.
|
||||
.. _`PyPy-test`: http://codespeak.net:8099/summary
|
||||
|
||||
|
||||
send test report to pocoo pastebin service
|
||||
Sending test report to pocoo pastebin service
|
||||
-----------------------------------------------------
|
||||
|
||||
**Creating a URL for each test failure**::
|
||||
@@ -138,7 +138,7 @@ for example ``-x`` if you only want to send one particular failure.
|
||||
|
||||
Currently only pasting to the http://paste.pocoo.org service is implemented.
|
||||
|
||||
calling pytest from Python code
|
||||
Calling pytest from Python code
|
||||
----------------------------------------------------
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.. _xunitsetup:
|
||||
|
||||
====================================
|
||||
extended xUnit style setup fixtures
|
||||
Extended xUnit style setup fixtures
|
||||
====================================
|
||||
|
||||
.. _`funcargs`: funcargs.html
|
||||
@@ -16,7 +16,7 @@ before running a test function and ``teardown`` after it has finished.
|
||||
handling by optionally calling per-module and per-class hooks.
|
||||
|
||||
|
||||
module level setup/teardown
|
||||
Module level setup/teardown
|
||||
=============================================
|
||||
|
||||
If you have multiple test functions and test classes in a single
|
||||
@@ -33,7 +33,7 @@ which will usually be called once for all the functions::
|
||||
with a setup_module method.
|
||||
"""
|
||||
|
||||
class level setup/teardown
|
||||
Class level setup/teardown
|
||||
=============================================
|
||||
|
||||
Similarly, the following methods are called at class level before
|
||||
@@ -51,7 +51,7 @@ and after all test methods of the class are called::
|
||||
with a call to setup_class.
|
||||
"""
|
||||
|
||||
method and function level setup/teardown
|
||||
Method and function level setup/teardown
|
||||
=============================================
|
||||
|
||||
Similarly, the following methods are called around each method invocation::
|
||||
|
||||
21
setup.py
21
setup.py
@@ -1,36 +1,39 @@
|
||||
import os, sys
|
||||
if sys.version_info >= (3,0):
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup
|
||||
from setuptools import setup
|
||||
|
||||
long_description = """
|
||||
cross-project testing tool for Python.
|
||||
|
||||
Platforms: Linux, Win32, OSX
|
||||
|
||||
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy
|
||||
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy-1.5
|
||||
|
||||
Bugs and issues: http://bitbucket.org/hpk42/pytest/issues/
|
||||
|
||||
Web page: http://pytest.org
|
||||
|
||||
(c) Holger Krekel and others, 2004-2010
|
||||
(c) Holger Krekel and others, 2004-2011
|
||||
"""
|
||||
def main():
|
||||
setup(
|
||||
name='pytest',
|
||||
description='py.test: simple powerful testing with Python',
|
||||
long_description = long_description,
|
||||
version='2.0.3',
|
||||
version='2.1.3',
|
||||
url='http://pytest.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
|
||||
author='Holger Krekel, Benjamin Peterson, Ronny Pfannschmidt, Floris Bruynooghe and others',
|
||||
author_email='holger at merlinux.eu',
|
||||
entry_points= make_entry_points(),
|
||||
install_requires=['py>1.4.1'],
|
||||
classifiers=['Development Status :: 5 - Production/Stable',
|
||||
# the following should be enabled for release
|
||||
install_requires=['py>=1.4.5'],
|
||||
classifiers=['Development Status :: 6 - Mature',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: POSIX',
|
||||
@@ -41,7 +44,7 @@ def main():
|
||||
'Topic :: Utilities',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3'],
|
||||
packages=['_pytest', ],
|
||||
packages=['_pytest', '_pytest.assertion'],
|
||||
py_modules=['pytest'],
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestGeneralUsage:
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
#XXX on jython this fails: "> import import_fails",
|
||||
"E ImportError: No module named does_not_work",
|
||||
"E ImportError: No module named *does_not_work*",
|
||||
])
|
||||
assert result.ret == 1
|
||||
|
||||
@@ -155,7 +155,7 @@ class TestGeneralUsage:
|
||||
assert result.ret != 0
|
||||
assert "should be seen" in result.stdout.str()
|
||||
|
||||
@pytest.mark.skipif("not hasattr(os, 'symlink')")
|
||||
@pytest.mark.skipif("not hasattr(py.path.local, 'mksymlinkto')")
|
||||
def test_chdir(self, testdir):
|
||||
testdir.tmpdir.join("py").mksymlinkto(py._pydir)
|
||||
p = testdir.tmpdir.join("main.py")
|
||||
@@ -231,7 +231,7 @@ class TestGeneralUsage:
|
||||
res = testdir.runpytest(p)
|
||||
assert res.ret == 0
|
||||
res.stdout.fnmatch_lines(["*1 skipped*"])
|
||||
|
||||
|
||||
def test_direct_addressing_selects(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
@@ -259,6 +259,19 @@ class TestGeneralUsage:
|
||||
if name.startswith("pytest_"):
|
||||
assert value.__doc__, "no docstring for %s" % name
|
||||
|
||||
def test_initialization_error_issue49(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_configure():
|
||||
x
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 3 # internal error
|
||||
result.stderr.fnmatch_lines([
|
||||
"INTERNAL*pytest_configure*",
|
||||
"INTERNAL*x*",
|
||||
])
|
||||
assert 'sessionstarttime' not in result.stderr.str()
|
||||
|
||||
class TestInvocationVariants:
|
||||
def test_earlyinit(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
@@ -268,6 +281,7 @@ class TestInvocationVariants:
|
||||
result = testdir.runpython(p)
|
||||
assert result.ret == 0
|
||||
|
||||
@pytest.mark.xfail("sys.platform.startswith('java')")
|
||||
def test_pydoc(self, testdir):
|
||||
for name in ('py.test', 'pytest'):
|
||||
result = testdir.runpython_c("import %s;help(%s)" % (name, name))
|
||||
@@ -351,7 +365,7 @@ class TestInvocationVariants:
|
||||
retcode = testdir.pytestmain(testdir.tmpdir)
|
||||
assert not retcode
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
|
||||
def test_invoke_plugin_api(self, capsys):
|
||||
class MyPlugin:
|
||||
def pytest_addoption(self, parser):
|
||||
@@ -361,6 +375,19 @@ class TestInvocationVariants:
|
||||
out, err = capsys.readouterr()
|
||||
assert "--myopt" in out
|
||||
|
||||
def test_pyargs_importerror(self, testdir, monkeypatch):
|
||||
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
||||
path = testdir.mkpydir("tpkg")
|
||||
path.join("test_hello.py").write('raise ImportError')
|
||||
|
||||
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
|
||||
assert result.ret != 0
|
||||
# FIXME: It would be more natural to match NOT
|
||||
# "ERROR*file*or*package*not*found*".
|
||||
result.stdout.fnmatch_lines([
|
||||
"*collected 0 items*"
|
||||
])
|
||||
|
||||
def test_cmdline_python_package(self, testdir, monkeypatch):
|
||||
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
|
||||
path = testdir.mkpydir("tpkg")
|
||||
@@ -385,7 +412,7 @@ class TestInvocationVariants:
|
||||
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
|
||||
assert result.ret != 0
|
||||
result.stderr.fnmatch_lines([
|
||||
"*file*not*found*test_hello*",
|
||||
"*not*found*test_hello*",
|
||||
])
|
||||
|
||||
def test_cmdline_python_package_not_exists(self, testdir):
|
||||
|
||||
@@ -53,7 +53,7 @@ def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(funcargs={name: val})
|
||||
elif 'anypython' in metafunc.funcargnames:
|
||||
for name in ('python2.4', 'python2.5', 'python2.6',
|
||||
'python2.7', 'python3.1', 'pypy-c', 'jython'):
|
||||
'python2.7', 'python3.1', 'pypy', 'jython'):
|
||||
metafunc.addcall(id=name, param=name)
|
||||
|
||||
# XXX copied from execnet's conftest.py - needs to be merged
|
||||
@@ -78,6 +78,8 @@ def getexecutable(name, cache={}):
|
||||
out, err = popen.communicate()
|
||||
if not err or "2.5" not in err:
|
||||
executable = None
|
||||
if "2.5.2" in err:
|
||||
executable = None # http://bugs.jython.org/issue1790
|
||||
cache[name] = executable
|
||||
return executable
|
||||
|
||||
@@ -91,5 +93,5 @@ def pytest_funcarg__anypython(request):
|
||||
executable = py.path.local(executable)
|
||||
if executable.check():
|
||||
return executable
|
||||
pytest.skip("no %s found" % (name,))
|
||||
pytest.skip("no suitable %s found" % (name,))
|
||||
return executable
|
||||
|
||||
327
testing/test_assertinterpret.py
Normal file
327
testing/test_assertinterpret.py
Normal file
@@ -0,0 +1,327 @@
|
||||
"PYTEST_DONT_REWRITE"
|
||||
import pytest, py
|
||||
|
||||
from _pytest.assertion import util
|
||||
|
||||
def exvalue():
|
||||
return py.std.sys.exc_info()[1]
|
||||
|
||||
def f():
|
||||
return 2
|
||||
|
||||
def test_not_being_rewritten():
|
||||
assert "@py_builtins" not in globals()
|
||||
|
||||
def test_assert():
|
||||
try:
|
||||
assert f() == 3
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith('assert 2 == 3\n')
|
||||
|
||||
def test_assert_with_explicit_message():
|
||||
try:
|
||||
assert f() == 3, "hello"
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert e.msg == 'hello'
|
||||
|
||||
def test_assert_within_finally():
|
||||
class A:
|
||||
def f():
|
||||
pass
|
||||
excinfo = py.test.raises(TypeError, """
|
||||
try:
|
||||
A().f()
|
||||
finally:
|
||||
i = 42
|
||||
""")
|
||||
s = excinfo.exconly()
|
||||
assert s.find("takes no argument") != -1
|
||||
|
||||
#def g():
|
||||
# A.f()
|
||||
#excinfo = getexcinfo(TypeError, g)
|
||||
#msg = getmsg(excinfo)
|
||||
#assert msg.find("must be called with A") != -1
|
||||
|
||||
|
||||
def test_assert_multiline_1():
|
||||
try:
|
||||
assert (f() ==
|
||||
3)
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith('assert 2 == 3\n')
|
||||
|
||||
def test_assert_multiline_2():
|
||||
try:
|
||||
assert (f() == (4,
|
||||
3)[-1])
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith('assert 2 ==')
|
||||
|
||||
def test_in():
|
||||
try:
|
||||
assert "hi" in [1, 2]
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith("assert 'hi' in")
|
||||
|
||||
def test_is():
|
||||
try:
|
||||
assert 1 is 2
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith("assert 1 is 2")
|
||||
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_attrib():
|
||||
class Foo(object):
|
||||
b = 1
|
||||
i = Foo()
|
||||
try:
|
||||
assert i.b == 2
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith("assert 1 == 2")
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_attrib_inst():
|
||||
class Foo(object):
|
||||
b = 1
|
||||
try:
|
||||
assert Foo().b == 2
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith("assert 1 == 2")
|
||||
|
||||
def test_len():
|
||||
l = list(range(42))
|
||||
try:
|
||||
assert len(l) == 100
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert s.startswith("assert 42 == 100")
|
||||
assert "where 42 = len([" in s
|
||||
|
||||
def test_assert_non_string_message():
|
||||
class A:
|
||||
def __str__(self):
|
||||
return "hello"
|
||||
try:
|
||||
assert 0 == 1, A()
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert e.msg == "hello"
|
||||
|
||||
def test_assert_keyword_arg():
|
||||
def f(x=3):
|
||||
return False
|
||||
try:
|
||||
assert f(x=5)
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert "x=5" in e.msg
|
||||
|
||||
# These tests should both fail, but should fail nicely...
|
||||
class WeirdRepr:
|
||||
def __repr__(self):
|
||||
return '<WeirdRepr\nsecond line>'
|
||||
|
||||
def bug_test_assert_repr():
|
||||
v = WeirdRepr()
|
||||
try:
|
||||
assert v == 1
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert e.msg.find('WeirdRepr') != -1
|
||||
assert e.msg.find('second line') != -1
|
||||
assert 0
|
||||
|
||||
def test_assert_non_string():
|
||||
try:
|
||||
assert 0, ['list']
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert e.msg.find("list") != -1
|
||||
|
||||
def test_assert_implicit_multiline():
|
||||
try:
|
||||
x = [1,2,3]
|
||||
assert x != [1,
|
||||
2, 3]
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert e.msg.find('assert [1, 2, 3] !=') != -1
|
||||
|
||||
|
||||
def test_assert_with_brokenrepr_arg():
|
||||
class BrokenRepr:
|
||||
def __repr__(self): 0 / 0
|
||||
e = AssertionError(BrokenRepr())
|
||||
if e.msg.find("broken __repr__") == -1:
|
||||
py.test.fail("broken __repr__ not handle correctly")
|
||||
|
||||
def test_multiple_statements_per_line():
|
||||
try:
|
||||
a = 1; assert a == 2
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert "assert 1 == 2" in e.msg
|
||||
|
||||
def test_power():
|
||||
try:
|
||||
assert 2**3 == 7
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
assert "assert (2 ** 3) == 7" in e.msg
|
||||
|
||||
|
||||
class TestView:
|
||||
|
||||
def setup_class(cls):
|
||||
cls.View = pytest.importorskip("_pytest.assertion.oldinterpret").View
|
||||
|
||||
def test_class_dispatch(self):
|
||||
### Use a custom class hierarchy with existing instances
|
||||
|
||||
class Picklable(self.View):
|
||||
pass
|
||||
|
||||
class Simple(Picklable):
|
||||
__view__ = object
|
||||
def pickle(self):
|
||||
return repr(self.__obj__)
|
||||
|
||||
class Seq(Picklable):
|
||||
__view__ = list, tuple, dict
|
||||
def pickle(self):
|
||||
return ';'.join(
|
||||
[Picklable(item).pickle() for item in self.__obj__])
|
||||
|
||||
class Dict(Seq):
|
||||
__view__ = dict
|
||||
def pickle(self):
|
||||
return Seq.pickle(self) + '!' + Seq(self.values()).pickle()
|
||||
|
||||
assert Picklable(123).pickle() == '123'
|
||||
assert Picklable([1,[2,3],4]).pickle() == '1;2;3;4'
|
||||
assert Picklable({1:2}).pickle() == '1!2'
|
||||
|
||||
def test_viewtype_class_hierarchy(self):
|
||||
# Use a custom class hierarchy based on attributes of existing instances
|
||||
class Operation:
|
||||
"Existing class that I don't want to change."
|
||||
def __init__(self, opname, *args):
|
||||
self.opname = opname
|
||||
self.args = args
|
||||
|
||||
existing = [Operation('+', 4, 5),
|
||||
Operation('getitem', '', 'join'),
|
||||
Operation('setattr', 'x', 'y', 3),
|
||||
Operation('-', 12, 1)]
|
||||
|
||||
class PyOp(self.View):
|
||||
def __viewkey__(self):
|
||||
return self.opname
|
||||
def generate(self):
|
||||
return '%s(%s)' % (self.opname, ', '.join(map(repr, self.args)))
|
||||
|
||||
class PyBinaryOp(PyOp):
|
||||
__view__ = ('+', '-', '*', '/')
|
||||
def generate(self):
|
||||
return '%s %s %s' % (self.args[0], self.opname, self.args[1])
|
||||
|
||||
codelines = [PyOp(op).generate() for op in existing]
|
||||
assert codelines == ["4 + 5", "getitem('', 'join')",
|
||||
"setattr('x', 'y', 3)", "12 - 1"]
|
||||
|
||||
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||
def test_assert_customizable_reprcompare(monkeypatch):
|
||||
monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
|
||||
try:
|
||||
assert 3 == 4
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert "hello" in s
|
||||
|
||||
def test_assert_long_source_1():
|
||||
try:
|
||||
assert len == [
|
||||
(None, ['somet text', 'more text']),
|
||||
]
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert 're-run' not in s
|
||||
assert 'somet text' in s
|
||||
|
||||
def test_assert_long_source_2():
|
||||
try:
|
||||
assert(len == [
|
||||
(None, ['somet text', 'more text']),
|
||||
])
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert 're-run' not in s
|
||||
assert 'somet text' in s
|
||||
|
||||
def test_assert_raise_alias(testdir):
|
||||
testdir.makepyfile("""
|
||||
"PYTEST_DONT_REWRITE"
|
||||
import sys
|
||||
EX = AssertionError
|
||||
def test_hello():
|
||||
raise EX("hello"
|
||||
"multi"
|
||||
"line")
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*def test_hello*",
|
||||
"*raise EX*",
|
||||
"*1 failed*",
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5)")
|
||||
def test_assert_raise_subclass():
|
||||
class SomeEx(AssertionError):
|
||||
def __init__(self, *args):
|
||||
super(SomeEx, self).__init__()
|
||||
try:
|
||||
raise SomeEx("hello")
|
||||
except AssertionError:
|
||||
s = str(exvalue())
|
||||
assert 're-run' not in s
|
||||
assert 'could not determine' in s
|
||||
|
||||
def test_assert_raises_in_nonzero_of_object_pytest_issue10():
|
||||
class A(object):
|
||||
def __nonzero__(self):
|
||||
raise ValueError(42)
|
||||
def __lt__(self, other):
|
||||
return A()
|
||||
def __repr__(self):
|
||||
return "<MY42 object>"
|
||||
def myany(x):
|
||||
return True
|
||||
try:
|
||||
assert not(myany(A() < 0))
|
||||
except AssertionError:
|
||||
e = exvalue()
|
||||
s = str(e)
|
||||
assert "<MY42 object> < 0" in s
|
||||
@@ -2,11 +2,12 @@ import sys
|
||||
|
||||
import py, pytest
|
||||
import _pytest.assertion as plugin
|
||||
from _pytest.assertion import reinterpret, util
|
||||
|
||||
needsnewassert = pytest.mark.skipif("sys.version_info < (2,6)")
|
||||
|
||||
def interpret(expr):
|
||||
return py.code._reinterpret(expr, py.code.Frame(sys._getframe(1)))
|
||||
return reinterpret.reinterpret(expr, py.code.Frame(sys._getframe(1)))
|
||||
|
||||
class TestBinReprIntegration:
|
||||
pytestmark = needsnewassert
|
||||
@@ -25,7 +26,7 @@ class TestBinReprIntegration:
|
||||
self.right = right
|
||||
mockhook = MockHook()
|
||||
monkeypatch = request.getfuncargvalue("monkeypatch")
|
||||
monkeypatch.setattr(py.code, '_reprcompare', mockhook)
|
||||
monkeypatch.setattr(util, '_reprcompare', mockhook)
|
||||
return mockhook
|
||||
|
||||
def test_pytest_assertrepr_compare_called(self, hook):
|
||||
@@ -39,15 +40,6 @@ class TestBinReprIntegration:
|
||||
assert hook.left == [0, 1]
|
||||
assert hook.right == [0, 2]
|
||||
|
||||
def test_configure_unconfigure(self, testdir, hook):
|
||||
assert hook == py.code._reprcompare
|
||||
config = testdir.parseconfig()
|
||||
plugin.pytest_configure(config)
|
||||
assert hook != py.code._reprcompare
|
||||
from _pytest.config import pytest_unconfigure
|
||||
pytest_unconfigure(config)
|
||||
assert hook == py.code._reprcompare
|
||||
|
||||
def callequal(left, right):
|
||||
return plugin.pytest_assertrepr_compare('==', left, right)
|
||||
|
||||
@@ -119,6 +111,14 @@ class TestAssert_reprcompare:
|
||||
expl = ' '.join(callequal('foo', 'bar'))
|
||||
assert 'raised in repr()' not in expl
|
||||
|
||||
@needsnewassert
|
||||
def test_rewritten(testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_rewritten():
|
||||
assert "@py_builtins" in globals()
|
||||
""")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_reprcompare_notin():
|
||||
detail = plugin.pytest_assertrepr_compare('not in', 'foo', 'aaafoobbb')[1:]
|
||||
assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++']
|
||||
@@ -158,8 +158,30 @@ def test_sequence_comparison_uses_repr(testdir):
|
||||
"*E*'y'*",
|
||||
])
|
||||
|
||||
@needsnewassert
|
||||
def test_assertrepr_loaded_per_dir(testdir):
|
||||
testdir.makepyfile(test_base=['def test_base(): assert 1 == 2'])
|
||||
a = testdir.mkdir('a')
|
||||
a_test = a.join('test_a.py')
|
||||
a_test.write('def test_a(): assert 1 == 2')
|
||||
a_conftest = a.join('conftest.py')
|
||||
a_conftest.write('def pytest_assertrepr_compare(): return ["summary a"]')
|
||||
b = testdir.mkdir('b')
|
||||
b_test = b.join('test_b.py')
|
||||
b_test.write('def test_b(): assert 1 == 2')
|
||||
b_conftest = b.join('conftest.py')
|
||||
b_conftest.write('def pytest_assertrepr_compare(): return ["summary b"]')
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*def test_base():*',
|
||||
'*E*assert 1 == 2*',
|
||||
'*def test_a():*',
|
||||
'*E*assert summary a*',
|
||||
'*def test_b():*',
|
||||
'*E*assert summary b*'])
|
||||
|
||||
def test_functional(testdir):
|
||||
|
||||
def test_assertion_options(testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
x = 3
|
||||
@@ -167,8 +189,24 @@ def test_functional(testdir):
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert "3 == 4" in result.stdout.str()
|
||||
result = testdir.runpytest("--no-assert")
|
||||
assert "3 == 4" not in result.stdout.str()
|
||||
off_options = (("--no-assert",),
|
||||
("--nomagic",),
|
||||
("--no-assert", "--nomagic"),
|
||||
("--assert=plain",),
|
||||
("--assert=plain", "--no-assert"),
|
||||
("--assert=plain", "--nomagic"),
|
||||
("--assert=plain", "--no-assert", "--nomagic"))
|
||||
for opt in off_options:
|
||||
result = testdir.runpytest(*opt)
|
||||
assert "3 == 4" not in result.stdout.str()
|
||||
|
||||
def test_old_assert_mode(testdir):
|
||||
testdir.makepyfile("""
|
||||
def test_in_old_mode():
|
||||
assert "@py_builtins" not in globals()
|
||||
""")
|
||||
result = testdir.runpytest("--assert=reinterp")
|
||||
assert result.ret == 0
|
||||
|
||||
def test_triple_quoted_string_issue113(testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -210,14 +248,14 @@ def test_traceback_failure(testdir):
|
||||
"*test_traceback_failure.py:4: AssertionError"
|
||||
])
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names")
|
||||
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" )
|
||||
def test_warn_missing(testdir):
|
||||
p1 = testdir.makepyfile("")
|
||||
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
|
||||
result.stderr.fnmatch_lines([
|
||||
"*WARNING*assertion*",
|
||||
"*WARNING*assert statements are not executed*",
|
||||
])
|
||||
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--no-assert")
|
||||
result.stderr.fnmatch_lines([
|
||||
"*WARNING*assertion*",
|
||||
"*WARNING*assert statements are not executed*",
|
||||
])
|
||||
|
||||
376
testing/test_assertrewrite.py
Normal file
376
testing/test_assertrewrite.py
Normal file
@@ -0,0 +1,376 @@
|
||||
import sys
|
||||
import zipfile
|
||||
import py
|
||||
import pytest
|
||||
|
||||
ast = pytest.importorskip("ast")
|
||||
if sys.platform.startswith("java"):
|
||||
# XXX should be xfail
|
||||
pytest.skip("assert rewrite does currently not work on jython")
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.assertion.rewrite import rewrite_asserts
|
||||
|
||||
|
||||
def setup_module(mod):
|
||||
mod._old_reprcompare = util._reprcompare
|
||||
py.code._reprcompare = None
|
||||
|
||||
def teardown_module(mod):
|
||||
util._reprcompare = mod._old_reprcompare
|
||||
del mod._old_reprcompare
|
||||
|
||||
|
||||
def rewrite(src):
|
||||
tree = ast.parse(src)
|
||||
rewrite_asserts(tree)
|
||||
return tree
|
||||
|
||||
def getmsg(f, extra_ns=None, must_pass=False):
|
||||
"""Rewrite the assertions in f, run it, and get the failure message."""
|
||||
src = '\n'.join(py.code.Code(f).source().lines)
|
||||
mod = rewrite(src)
|
||||
code = compile(mod, "<test>", "exec")
|
||||
ns = {}
|
||||
if extra_ns is not None:
|
||||
ns.update(extra_ns)
|
||||
py.builtin.exec_(code, ns)
|
||||
func = ns[f.__name__]
|
||||
try:
|
||||
func()
|
||||
except AssertionError:
|
||||
if must_pass:
|
||||
pytest.fail("shouldn't have raised")
|
||||
s = str(sys.exc_info()[1])
|
||||
if not s.startswith("assert"):
|
||||
return "AssertionError: " + s
|
||||
return s
|
||||
else:
|
||||
if not must_pass:
|
||||
pytest.fail("function didn't raise at all")
|
||||
|
||||
|
||||
class TestAssertionRewrite:
|
||||
|
||||
def test_place_initial_imports(self):
|
||||
s = """'Doc string'\nother = stuff"""
|
||||
m = rewrite(s)
|
||||
assert isinstance(m.body[0], ast.Expr)
|
||||
assert isinstance(m.body[0].value, ast.Str)
|
||||
for imp in m.body[1:3]:
|
||||
assert isinstance(imp, ast.Import)
|
||||
assert imp.lineno == 2
|
||||
assert imp.col_offset == 0
|
||||
assert isinstance(m.body[3], ast.Assign)
|
||||
s = """from __future__ import with_statement\nother_stuff"""
|
||||
m = rewrite(s)
|
||||
assert isinstance(m.body[0], ast.ImportFrom)
|
||||
for imp in m.body[1:3]:
|
||||
assert isinstance(imp, ast.Import)
|
||||
assert imp.lineno == 2
|
||||
assert imp.col_offset == 0
|
||||
assert isinstance(m.body[3], ast.Expr)
|
||||
s = """'doc string'\nfrom __future__ import with_statement\nother"""
|
||||
m = rewrite(s)
|
||||
assert isinstance(m.body[0], ast.Expr)
|
||||
assert isinstance(m.body[0].value, ast.Str)
|
||||
assert isinstance(m.body[1], ast.ImportFrom)
|
||||
for imp in m.body[2:4]:
|
||||
assert isinstance(imp, ast.Import)
|
||||
assert imp.lineno == 3
|
||||
assert imp.col_offset == 0
|
||||
assert isinstance(m.body[4], ast.Expr)
|
||||
s = """from . import relative\nother_stuff"""
|
||||
m = rewrite(s)
|
||||
for imp in m.body[0:2]:
|
||||
assert isinstance(imp, ast.Import)
|
||||
assert imp.lineno == 1
|
||||
assert imp.col_offset == 0
|
||||
assert isinstance(m.body[3], ast.Expr)
|
||||
|
||||
def test_dont_rewrite(self):
|
||||
s = """'PYTEST_DONT_REWRITE'\nassert 14"""
|
||||
m = rewrite(s)
|
||||
assert len(m.body) == 2
|
||||
assert isinstance(m.body[0].value, ast.Str)
|
||||
assert isinstance(m.body[1], ast.Assert)
|
||||
assert m.body[1].msg is None
|
||||
|
||||
def test_name(self):
|
||||
def f():
|
||||
assert False
|
||||
assert getmsg(f) == "assert False"
|
||||
def f():
|
||||
f = False
|
||||
assert f
|
||||
assert getmsg(f) == "assert False"
|
||||
def f():
|
||||
assert a_global
|
||||
assert getmsg(f, {"a_global" : False}) == "assert a_global"
|
||||
|
||||
def test_assert_already_has_message(self):
|
||||
def f():
|
||||
assert False, "something bad!"
|
||||
assert getmsg(f) == "AssertionError: something bad!"
|
||||
|
||||
def test_boolop(self):
|
||||
def f():
|
||||
f = g = False
|
||||
assert f and g
|
||||
assert getmsg(f) == "assert (False)"
|
||||
def f():
|
||||
f = True
|
||||
g = False
|
||||
assert f and g
|
||||
assert getmsg(f) == "assert (True and False)"
|
||||
def f():
|
||||
f = False
|
||||
g = True
|
||||
assert f and g
|
||||
assert getmsg(f) == "assert (False)"
|
||||
def f():
|
||||
f = g = False
|
||||
assert f or g
|
||||
assert getmsg(f) == "assert (False or False)"
|
||||
def f():
|
||||
f = g = False
|
||||
assert not f and not g
|
||||
getmsg(f, must_pass=True)
|
||||
def x():
|
||||
return False
|
||||
def f():
|
||||
assert x() and x()
|
||||
assert getmsg(f, {"x" : x}) == "assert (x())"
|
||||
def f():
|
||||
assert False or x()
|
||||
assert getmsg(f, {"x" : x}) == "assert (False or x())"
|
||||
def f():
|
||||
assert 1 in {} and 2 in {}
|
||||
assert getmsg(f) == "assert (1 in {})"
|
||||
def f():
|
||||
x = 1
|
||||
y = 2
|
||||
assert x in {1 : None} and y in {}
|
||||
assert getmsg(f) == "assert (1 in {1: None} and 2 in {})"
|
||||
def f():
|
||||
f = True
|
||||
g = False
|
||||
assert f or g
|
||||
getmsg(f, must_pass=True)
|
||||
def f():
|
||||
f = g = h = lambda: True
|
||||
assert f() and g() and h()
|
||||
getmsg(f, must_pass=True)
|
||||
|
||||
def test_short_circut_evaluation(self):
|
||||
def f():
|
||||
assert True or explode
|
||||
getmsg(f, must_pass=True)
|
||||
def f():
|
||||
x = 1
|
||||
assert x == 1 or x == 2
|
||||
getmsg(f, must_pass=True)
|
||||
|
||||
def test_unary_op(self):
|
||||
def f():
|
||||
x = True
|
||||
assert not x
|
||||
assert getmsg(f) == "assert not True"
|
||||
def f():
|
||||
x = 0
|
||||
assert ~x + 1
|
||||
assert getmsg(f) == "assert (~0 + 1)"
|
||||
def f():
|
||||
x = 3
|
||||
assert -x + x
|
||||
assert getmsg(f) == "assert (-3 + 3)"
|
||||
def f():
|
||||
x = 0
|
||||
assert +x + x
|
||||
assert getmsg(f) == "assert (+0 + 0)"
|
||||
|
||||
def test_binary_op(self):
|
||||
def f():
|
||||
x = 1
|
||||
y = -1
|
||||
assert x + y
|
||||
assert getmsg(f) == "assert (1 + -1)"
|
||||
|
||||
def test_call(self):
|
||||
def g(a=42, *args, **kwargs):
|
||||
return False
|
||||
ns = {"g" : g}
|
||||
def f():
|
||||
assert g()
|
||||
assert getmsg(f, ns) == """assert g()"""
|
||||
def f():
|
||||
assert g(1)
|
||||
assert getmsg(f, ns) == """assert g(1)"""
|
||||
def f():
|
||||
assert g(1, 2)
|
||||
assert getmsg(f, ns) == """assert g(1, 2)"""
|
||||
def f():
|
||||
assert g(1, g=42)
|
||||
assert getmsg(f, ns) == """assert g(1, g=42)"""
|
||||
def f():
|
||||
assert g(1, 3, g=23)
|
||||
assert getmsg(f, ns) == """assert g(1, 3, g=23)"""
|
||||
def f():
|
||||
seq = [1, 2, 3]
|
||||
assert g(*seq)
|
||||
assert getmsg(f, ns) == """assert g(*[1, 2, 3])"""
|
||||
def f():
|
||||
x = "a"
|
||||
assert g(**{x : 2})
|
||||
assert getmsg(f, ns) == """assert g(**{'a': 2})"""
|
||||
|
||||
def test_attribute(self):
|
||||
class X(object):
|
||||
g = 3
|
||||
ns = {"X" : X, "x" : X()}
|
||||
def f():
|
||||
assert not x.g
|
||||
assert getmsg(f, ns) == """assert not 3
|
||||
+ where 3 = x.g"""
|
||||
def f():
|
||||
x.a = False
|
||||
assert x.a
|
||||
assert getmsg(f, ns) == """assert x.a"""
|
||||
|
||||
def test_comparisons(self):
|
||||
def f():
|
||||
a, b = range(2)
|
||||
assert b < a
|
||||
assert getmsg(f) == """assert 1 < 0"""
|
||||
def f():
|
||||
a, b, c = range(3)
|
||||
assert a > b > c
|
||||
assert getmsg(f) == """assert 0 > 1"""
|
||||
def f():
|
||||
a, b, c = range(3)
|
||||
assert a < b > c
|
||||
assert getmsg(f) == """assert 1 > 2"""
|
||||
def f():
|
||||
a, b, c = range(3)
|
||||
assert a < b <= c
|
||||
getmsg(f, must_pass=True)
|
||||
def f():
|
||||
a, b, c = range(3)
|
||||
assert a < b
|
||||
assert b < c
|
||||
getmsg(f, must_pass=True)
|
||||
|
||||
def test_len(self):
|
||||
def f():
|
||||
l = list(range(10))
|
||||
assert len(l) == 11
|
||||
assert getmsg(f).startswith("""assert 10 == 11
|
||||
+ where 10 = len([""")
|
||||
|
||||
def test_custom_reprcompare(self, monkeypatch):
|
||||
def my_reprcompare(op, left, right):
|
||||
return "42"
|
||||
monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
|
||||
def f():
|
||||
assert 42 < 3
|
||||
assert getmsg(f) == "assert 42"
|
||||
def my_reprcompare(op, left, right):
|
||||
return "%s %s %s" % (left, op, right)
|
||||
monkeypatch.setattr(util, "_reprcompare", my_reprcompare)
|
||||
def f():
|
||||
assert 1 < 3 < 5 <= 4 < 7
|
||||
assert getmsg(f) == "assert 5 <= 4"
|
||||
|
||||
def test_assert_raising_nonzero_in_comparison(self):
|
||||
def f():
|
||||
class A(object):
|
||||
def __nonzero__(self):
|
||||
raise ValueError(42)
|
||||
def __lt__(self, other):
|
||||
return A()
|
||||
def __repr__(self):
|
||||
return "<MY42 object>"
|
||||
def myany(x):
|
||||
return False
|
||||
assert myany(A() < 0)
|
||||
assert "<MY42 object> < 0" in getmsg(f)
|
||||
|
||||
def test_formatchar(self):
|
||||
def f():
|
||||
assert "%test" == "test"
|
||||
assert getmsg(f).startswith("assert '%test' == 'test'")
|
||||
|
||||
|
||||
class TestRewriteOnImport:
|
||||
|
||||
def test_pycache_is_a_file(self, testdir):
|
||||
testdir.tmpdir.join("__pycache__").write("Hello")
|
||||
testdir.makepyfile("""
|
||||
def test_rewritten():
|
||||
assert "@py_builtins" in globals()""")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_zipfile(self, testdir):
|
||||
z = testdir.tmpdir.join("myzip.zip")
|
||||
z_fn = str(z)
|
||||
f = zipfile.ZipFile(z_fn, "w")
|
||||
try:
|
||||
f.writestr("test_gum/__init__.py", "")
|
||||
f.writestr("test_gum/test_lizard.py", "")
|
||||
finally:
|
||||
f.close()
|
||||
z.chmod(256)
|
||||
testdir.makepyfile("""
|
||||
import sys
|
||||
sys.path.append(%r)
|
||||
import test_gum.test_lizard""" % (z_fn,))
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_readonly(self, testdir):
|
||||
sub = testdir.mkdir("testing")
|
||||
sub.join("test_readonly.py").write(
|
||||
py.builtin._totext("""
|
||||
def test_rewritten():
|
||||
assert "@py_builtins" in globals()
|
||||
""").encode("utf-8"), "wb")
|
||||
sub.chmod(320)
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_dont_write_bytecode(self, testdir, monkeypatch):
|
||||
testdir.makepyfile("""
|
||||
import os
|
||||
def test_no_bytecode():
|
||||
assert "__pycache__" in __cached__
|
||||
assert not os.path.exists(__cached__)
|
||||
assert not os.path.exists(os.path.dirname(__cached__))""")
|
||||
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_pyc_vs_pyo(self, testdir, monkeypatch):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_optimized():
|
||||
"hello"
|
||||
assert test_optimized.__doc__ is None""")
|
||||
p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
|
||||
rootdir=testdir.tmpdir)
|
||||
tmp = "--basetemp=%s" % p
|
||||
monkeypatch.setenv("PYTHONOPTIMIZE", "2")
|
||||
assert testdir.runpybin("py.test", tmp).ret == 0
|
||||
monkeypatch.undo()
|
||||
assert testdir.runpybin("py.test", tmp).ret == 1
|
||||
|
||||
def test_package(self, testdir):
|
||||
pkg = testdir.tmpdir.join("pkg")
|
||||
pkg.mkdir()
|
||||
pkg.join("__init__.py").ensure()
|
||||
pkg.join("test_blah.py").write("""
|
||||
def test_rewritten():
|
||||
assert "@py_builtins" in globals()""")
|
||||
assert testdir.runpytest().ret == 0
|
||||
|
||||
def test_translate_newlines(self, testdir):
|
||||
content = "def test_rewritten():\r\n assert '@py_builtins' in globals()"
|
||||
b = content.encode("utf-8")
|
||||
testdir.tmpdir.join("test_newlines.py").write(b, "wb")
|
||||
assert testdir.runpytest().ret == 0
|
||||
@@ -313,7 +313,7 @@ class TestSession:
|
||||
def test_collect_topdir(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
id = "::".join([p.basename, "test_func"])
|
||||
config = testdir.parseconfig(id)
|
||||
config = testdir.parseconfigure(id)
|
||||
topdir = testdir.tmpdir
|
||||
rcol = Session(config)
|
||||
assert topdir == rcol.fspath
|
||||
@@ -328,7 +328,7 @@ class TestSession:
|
||||
def test_collect_protocol_single_function(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
id = "::".join([p.basename, "test_func"])
|
||||
config = testdir.parseconfig(id)
|
||||
config = testdir.parseconfigure(id)
|
||||
topdir = testdir.tmpdir
|
||||
rcol = Session(config)
|
||||
assert topdir == rcol.fspath
|
||||
@@ -363,7 +363,7 @@ class TestSession:
|
||||
p.basename + "::TestClass::()",
|
||||
normid,
|
||||
]:
|
||||
config = testdir.parseconfig(id)
|
||||
config = testdir.parseconfigure(id)
|
||||
rcol = Session(config=config)
|
||||
rcol.perform_collect()
|
||||
items = rcol.items
|
||||
@@ -388,7 +388,7 @@ class TestSession:
|
||||
""" % p.basename)
|
||||
id = p.basename
|
||||
|
||||
config = testdir.parseconfig(id)
|
||||
config = testdir.parseconfigure(id)
|
||||
rcol = Session(config)
|
||||
hookrec = testdir.getreportrecorder(config)
|
||||
rcol.perform_collect()
|
||||
@@ -413,7 +413,7 @@ class TestSession:
|
||||
aaa = testdir.mkpydir("aaa")
|
||||
test_aaa = aaa.join("test_aaa.py")
|
||||
p.move(test_aaa)
|
||||
config = testdir.parseconfig()
|
||||
config = testdir.parseconfigure()
|
||||
rcol = Session(config)
|
||||
hookrec = testdir.getreportrecorder(config)
|
||||
rcol.perform_collect()
|
||||
@@ -437,7 +437,7 @@ class TestSession:
|
||||
p.move(test_bbb)
|
||||
|
||||
id = "."
|
||||
config = testdir.parseconfig(id)
|
||||
config = testdir.parseconfigure(id)
|
||||
rcol = Session(config)
|
||||
hookrec = testdir.getreportrecorder(config)
|
||||
rcol.perform_collect()
|
||||
@@ -455,7 +455,7 @@ class TestSession:
|
||||
|
||||
def test_serialization_byid(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
config = testdir.parseconfig()
|
||||
config = testdir.parseconfigure()
|
||||
rcol = Session(config)
|
||||
rcol.perform_collect()
|
||||
items = rcol.items
|
||||
@@ -476,7 +476,7 @@ class TestSession:
|
||||
pass
|
||||
""")
|
||||
arg = p.basename + ("::TestClass::test_method")
|
||||
config = testdir.parseconfig(arg)
|
||||
config = testdir.parseconfigure(arg)
|
||||
rcol = Session(config)
|
||||
rcol.perform_collect()
|
||||
items = rcol.items
|
||||
|
||||
@@ -16,6 +16,9 @@ class TestParseIni:
|
||||
config._preparse([sub])
|
||||
assert config.inicfg['name'] == 'value'
|
||||
|
||||
def test_getcfg_empty_path(self, tmpdir):
|
||||
cfg = getcfg([''], ['setup.cfg']) #happens on py.test ""
|
||||
|
||||
def test_append_parse_args(self, tmpdir):
|
||||
tmpdir.join("setup.cfg").write(py.code.Source("""
|
||||
[pytest]
|
||||
@@ -88,7 +91,7 @@ class TestConfigAPI:
|
||||
config.trace.root.setwriter(l.append)
|
||||
config.trace("hello")
|
||||
assert len(l) == 1
|
||||
assert l[0] == "[pytest] hello\n"
|
||||
assert l[0] == "hello [config]\n"
|
||||
|
||||
def test_config_getvalue_honours_conftest(self, testdir):
|
||||
testdir.makepyfile(conftest="x=1")
|
||||
|
||||
@@ -89,7 +89,7 @@ class TestConftestValueAccessGlobal:
|
||||
assert value == 1.5
|
||||
path = py.path.local(mod.__file__)
|
||||
assert path.dirpath() == basedir.join("adir", "b")
|
||||
assert path.purebasename == "conftest"
|
||||
assert path.purebasename.startswith("conftest")
|
||||
|
||||
def test_conftest_in_nonpkg_with_init(tmpdir):
|
||||
tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
|
||||
|
||||
@@ -586,17 +586,17 @@ class TestHookRelay:
|
||||
class TestTracer:
|
||||
def test_simple(self):
|
||||
from _pytest.core import TagTracer
|
||||
rootlogger = TagTracer("[my] ")
|
||||
rootlogger = TagTracer()
|
||||
log = rootlogger.get("pytest")
|
||||
log("hello")
|
||||
l = []
|
||||
rootlogger.setwriter(l.append)
|
||||
log("world")
|
||||
assert len(l) == 1
|
||||
assert l[0] == "[my] world\n"
|
||||
assert l[0] == "world [pytest]\n"
|
||||
sublog = log.get("collection")
|
||||
sublog("hello")
|
||||
assert l[1] == "[my] hello\n"
|
||||
assert l[1] == "hello [pytest:collection]\n"
|
||||
|
||||
def test_indent(self):
|
||||
from _pytest.core import TagTracer
|
||||
@@ -616,7 +616,7 @@ class TestTracer:
|
||||
log.root.indent -= 1
|
||||
log("last")
|
||||
assert len(l) == 7
|
||||
names = [x.rstrip()[len(rootlogger.prefix):] for x in l]
|
||||
names = [x[:x.rfind(' [')] for x in l]
|
||||
assert names == ['hello', ' line1', ' line2',
|
||||
' line3', ' line4', ' line5', 'last']
|
||||
|
||||
|
||||
@@ -59,6 +59,21 @@ class TestDoctests:
|
||||
"*UNEXPECTED*ZeroDivision*",
|
||||
])
|
||||
|
||||
def test_doctest_unex_importerror(self, testdir):
|
||||
testdir.tmpdir.join("hello.py").write(py.code.Source("""
|
||||
import asdalsdkjaslkdjasd
|
||||
"""))
|
||||
p = testdir.maketxtfile("""
|
||||
>>> import hello
|
||||
>>>
|
||||
""")
|
||||
result = testdir.runpytest("--doctest-modules")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*>>> import hello",
|
||||
"*UNEXPECTED*ImportError*",
|
||||
"*import asdals*",
|
||||
])
|
||||
|
||||
def test_doctestmodule(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
'''
|
||||
@@ -99,7 +114,7 @@ class TestDoctests:
|
||||
>>> i + 1
|
||||
2
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result = testdir.runpytest(p, "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
'001 >>> i = 0',
|
||||
'002 >>> i + 1',
|
||||
|
||||
@@ -62,3 +62,17 @@ def test_traceconfig(testdir):
|
||||
"*using*pytest*py*",
|
||||
"*active plugins*",
|
||||
])
|
||||
|
||||
def test_debug(testdir, monkeypatch):
|
||||
result = testdir.runpytest("--debug")
|
||||
assert result.ret == 0
|
||||
p = testdir.tmpdir.join("pytestdebug.log")
|
||||
assert "pytest_sessionstart" in p.read()
|
||||
|
||||
def test_PYTEST_DEBUG(testdir, monkeypatch):
|
||||
monkeypatch.setenv("PYTEST_DEBUG", "1")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stderr.fnmatch_lines([
|
||||
"*registered*PluginManager*"
|
||||
])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
from xml.dom import minidom
|
||||
import py, sys
|
||||
import py, sys, os
|
||||
|
||||
def runandparse(testdir, *args):
|
||||
resultpath = testdir.tmpdir.join("junit.xml")
|
||||
@@ -39,6 +39,18 @@ class TestPython:
|
||||
node = dom.getElementsByTagName("testsuite")[0]
|
||||
assert_attr(node, errors=0, failures=1, skips=3, tests=2)
|
||||
|
||||
def test_timing_function(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import time, pytest
|
||||
def test_sleep():
|
||||
time.sleep(0.01)
|
||||
""")
|
||||
result, dom = runandparse(testdir)
|
||||
node = dom.getElementsByTagName("testsuite")[0]
|
||||
tnode = node.getElementsByTagName("testcase")[0]
|
||||
val = tnode.getAttributeNode("time").value
|
||||
assert float(val) >= 0.001
|
||||
|
||||
def test_setup_error(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def pytest_funcarg__arg(request):
|
||||
@@ -119,7 +131,14 @@ class TestPython:
|
||||
assert "Division" in fnode.toxml()
|
||||
|
||||
def test_failure_function(self, testdir):
|
||||
testdir.makepyfile("def test_fail(): raise ValueError(42)")
|
||||
testdir.makepyfile("""
|
||||
import sys
|
||||
def test_fail():
|
||||
print ("hello-stdout")
|
||||
sys.stderr.write("hello-stderr\\n")
|
||||
raise ValueError(42)
|
||||
""")
|
||||
|
||||
result, dom = runandparse(testdir)
|
||||
assert result.ret
|
||||
node = dom.getElementsByTagName("testsuite")[0]
|
||||
@@ -131,6 +150,12 @@ class TestPython:
|
||||
fnode = tnode.getElementsByTagName("failure")[0]
|
||||
assert_attr(fnode, message="test failure")
|
||||
assert "ValueError" in fnode.toxml()
|
||||
systemout = fnode.nextSibling
|
||||
assert systemout.tagName == "system-out"
|
||||
assert "hello-stdout" in systemout.toxml()
|
||||
systemerr = systemout.nextSibling
|
||||
assert systemerr.tagName == "system-err"
|
||||
assert "hello-stderr" in systemerr.toxml()
|
||||
|
||||
def test_failure_escape(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -351,3 +376,16 @@ def test_invalid_xml_escape(testdir):
|
||||
assert '#x%04X' % i in text
|
||||
for i in valid:
|
||||
assert chr(i) in text
|
||||
|
||||
def test_logxml_path_expansion():
|
||||
from _pytest.junitxml import LogXML
|
||||
|
||||
home_tilde = os.path.normpath(os.path.expanduser('~/test.xml'))
|
||||
# this is here for when $HOME is not set correct
|
||||
home_var = os.path.normpath(os.path.expandvars('$HOME/test.xml'))
|
||||
|
||||
xml_tilde = LogXML('~/test.xml', None)
|
||||
assert xml_tilde.logfile == home_tilde
|
||||
|
||||
xml_var = LogXML('$HOME/test.xml', None)
|
||||
assert xml_var.logfile == home_var
|
||||
|
||||
@@ -2,6 +2,11 @@ import py, pytest
|
||||
from _pytest.mark import MarkGenerator as Mark
|
||||
|
||||
class TestMark:
|
||||
def test_markinfo_repr(self):
|
||||
from _pytest.mark import MarkInfo
|
||||
m = MarkInfo("hello", (1,2), {})
|
||||
repr(m)
|
||||
|
||||
def test_pytest_exists_in_namespace_all(self):
|
||||
assert 'mark' in py.test.__all__
|
||||
assert 'mark' in pytest.__all__
|
||||
|
||||
@@ -255,3 +255,19 @@ def test_nose_style_setup_teardown(testdir):
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
def test_nose_setup_ordering(testdir):
|
||||
testdir.makepyfile("""
|
||||
def setup_module(mod):
|
||||
mod.visited = True
|
||||
|
||||
class TestClass:
|
||||
def setup(self):
|
||||
assert visited
|
||||
def test_first(self):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
@@ -144,6 +144,19 @@ class TestPDB:
|
||||
child.sendeof()
|
||||
child.wait()
|
||||
|
||||
def test_pdb_used_in_generate_tests(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
pytest.set_trace()
|
||||
x = 5
|
||||
def test_foo(a):
|
||||
pass
|
||||
""")
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("x = 5")
|
||||
child.sendeof()
|
||||
child.wait()
|
||||
def test_pdb_collection_failure_is_shown(self, testdir):
|
||||
p1 = testdir.makepyfile("""xxx """)
|
||||
result = testdir.runpytest("--pdb", p1)
|
||||
|
||||
@@ -689,11 +689,11 @@ class TestRequest:
|
||||
def test_func(something): pass
|
||||
""")
|
||||
req = funcargs.FuncargRequest(item)
|
||||
req.config._setupstate.prepare(item) # XXX
|
||||
req._pyfuncitem.session._setupstate.prepare(item) # XXX
|
||||
req._fillfuncargs()
|
||||
# successively check finalization calls
|
||||
teardownlist = item.getparent(pytest.Module).obj.teardownlist
|
||||
ss = item.config._setupstate
|
||||
ss = item.session._setupstate
|
||||
assert not teardownlist
|
||||
ss.teardown_exact(item)
|
||||
print(ss.stack)
|
||||
@@ -818,11 +818,11 @@ class TestRequestCachedSetup:
|
||||
ret1 = req1.cached_setup(setup, teardown, scope="function")
|
||||
assert l == ['setup']
|
||||
# artificial call of finalizer
|
||||
req1.config._setupstate._callfinalizers(item1)
|
||||
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
|
||||
assert l == ["setup", "teardown"]
|
||||
ret2 = req1.cached_setup(setup, teardown, scope="function")
|
||||
assert l == ["setup", "teardown", "setup"]
|
||||
req1.config._setupstate._callfinalizers(item1)
|
||||
req1._pyfuncitem.session._setupstate._callfinalizers(item1)
|
||||
assert l == ["setup", "teardown", "setup", "teardown"]
|
||||
|
||||
def test_request_cached_setup_two_args(self, testdir):
|
||||
|
||||
@@ -186,7 +186,7 @@ def test_setup_fails_again_on_all_tests(testdir):
|
||||
])
|
||||
assert "passed" not in result.stdout.str()
|
||||
|
||||
def test_setup_funcarg_setup_not_called_if_outer_scope_fails(testdir):
|
||||
def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
def setup_module(mod):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from _pytest.skipping import MarkEvaluator, folded_skips
|
||||
from _pytest.skipping import pytest_runtest_setup
|
||||
@@ -471,6 +472,21 @@ def test_reportchars(testdir):
|
||||
"SKIP*four*",
|
||||
])
|
||||
|
||||
def test_reportchars_error(testdir):
|
||||
testdir.makepyfile(
|
||||
conftest="""
|
||||
def pytest_runtest_teardown():
|
||||
assert 0
|
||||
""",
|
||||
test_simple="""
|
||||
def test_foo():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest('-rE')
|
||||
result.stdout.fnmatch_lines([
|
||||
'ERROR*test_foo*',
|
||||
])
|
||||
|
||||
@pytest.mark.xfail("hasattr(sys, 'pypy_version_info')")
|
||||
def test_errors_in_xfail_skip_expressions(testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -486,6 +502,10 @@ def test_errors_in_xfail_skip_expressions(testdir):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
markline = " ^"
|
||||
if sys.platform.startswith("java"):
|
||||
# XXX report this to java
|
||||
markline = "*" + markline[8:]
|
||||
result.stdout.fnmatch_lines([
|
||||
"*ERROR*test_nameerror*",
|
||||
"*evaluating*skipif*expression*",
|
||||
@@ -493,7 +513,7 @@ def test_errors_in_xfail_skip_expressions(testdir):
|
||||
"*ERROR*test_syntax*",
|
||||
"*evaluating*xfail*expression*",
|
||||
" syntax error",
|
||||
" ^",
|
||||
markline,
|
||||
"SyntaxError: invalid syntax",
|
||||
"*1 pass*2 error*",
|
||||
])
|
||||
|
||||
@@ -167,6 +167,19 @@ class TestTerminal:
|
||||
])
|
||||
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
|
||||
|
||||
def test_keyboard_in_sessionstart(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_sessionstart():
|
||||
raise KeyboardInterrupt
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def test_foobar():
|
||||
pass
|
||||
""")
|
||||
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 2
|
||||
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
|
||||
|
||||
|
||||
class TestCollectonly:
|
||||
@@ -245,10 +258,13 @@ class TestCollectonly:
|
||||
|
||||
|
||||
def test_repr_python_version(monkeypatch):
|
||||
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
|
||||
assert repr_pythonversion() == "2.5.1-final-0"
|
||||
py.std.sys.version_info = x = (2,3)
|
||||
assert repr_pythonversion() == str(x)
|
||||
try:
|
||||
monkeypatch.setattr(sys, 'version_info', (2, 5, 1, 'final', 0))
|
||||
assert repr_pythonversion() == "2.5.1-final-0"
|
||||
py.std.sys.version_info = x = (2,3)
|
||||
assert repr_pythonversion() == str(x)
|
||||
finally:
|
||||
monkeypatch.undo() # do this early as pytest can get confused
|
||||
|
||||
class TestFixtureReporting:
|
||||
def test_setup_fixture_error(self, testdir):
|
||||
@@ -508,21 +524,6 @@ def test_traceconfig(testdir, monkeypatch):
|
||||
])
|
||||
assert result.ret == 0
|
||||
|
||||
def test_debug(testdir, monkeypatch):
|
||||
result = testdir.runpytest("--debug")
|
||||
result.stderr.fnmatch_lines([
|
||||
"*pytest_sessionstart*session*",
|
||||
])
|
||||
assert result.ret == 0
|
||||
|
||||
def test_PYTEST_DEBUG(testdir, monkeypatch):
|
||||
monkeypatch.setenv("PYTEST_DEBUG", "1")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stderr.fnmatch_lines([
|
||||
"*registered*PluginManager*"
|
||||
])
|
||||
|
||||
|
||||
class TestGenericReporting:
|
||||
""" this test class can be subclassed with a different option
|
||||
@@ -533,7 +534,7 @@ class TestGenericReporting:
|
||||
result = testdir.runpytest(*option.args)
|
||||
result.stdout.fnmatch_lines([
|
||||
"> import xyz",
|
||||
"E ImportError: No module named xyz",
|
||||
"E ImportError: No module named *xyz*",
|
||||
"*1 error*",
|
||||
])
|
||||
|
||||
|
||||
8
tox.ini
8
tox.ini
@@ -30,9 +30,11 @@ commands=
|
||||
changedir=.
|
||||
basepython=python2.6
|
||||
deps=:pypi:twisted
|
||||
:pypi:pexpect
|
||||
py>=1.4.5.dev1
|
||||
commands=
|
||||
py.test -rsxf \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml [testing/test_unittest.py]
|
||||
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
|
||||
[testenv:doctest]
|
||||
changedir=.
|
||||
commands=py.test --doctest-modules _pytest
|
||||
@@ -64,13 +66,13 @@ deps=py>=1.4.0
|
||||
[testenv:jython]
|
||||
changedir=testing
|
||||
commands=
|
||||
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
|
||||
{envpython} {envbindir}/py.test-jython \
|
||||
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml []
|
||||
|
||||
[pytest]
|
||||
minversion=2.0
|
||||
plugins=pytester
|
||||
#addopts= -rxf --pyargs --doctest-modules --ignore=.tox
|
||||
addopts= -rxs #--pyargs --doctest-modules --ignore=.tox
|
||||
rsyncdirs=tox.ini pytest.py _pytest testing
|
||||
python_files=test_*.py *_test.py
|
||||
python_classes=Test Acceptance
|
||||
|
||||
Reference in New Issue
Block a user