Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6f10d502c | ||
|
|
65d6ebe7d1 | ||
|
|
33cd414420 | ||
|
|
dba2a8bc64 | ||
|
|
f203401964 | ||
|
|
c64699bba6 | ||
|
|
002c5072af | ||
|
|
b3c8991b22 | ||
|
|
1a41c9c001 | ||
|
|
df444906d6 | ||
|
|
04754f6748 | ||
|
|
d5ad91c64f | ||
|
|
7e831b66ec | ||
|
|
ba9b27fcd3 | ||
|
|
ca281b7c1b | ||
|
|
fb173a97a8 | ||
|
|
983b2d2475 | ||
|
|
e7e5ee805f | ||
|
|
67f8dd0cf2 | ||
|
|
07cc48517d | ||
|
|
fce13c3e46 | ||
|
|
573599beb3 | ||
|
|
6ebf39e9a6 | ||
|
|
6b6080ae6c | ||
|
|
427cf6f66d | ||
|
|
2fc8ee0839 | ||
|
|
6ad16936bb | ||
|
|
bcb8dc71d2 | ||
|
|
b8277bfed8 | ||
|
|
2637326782 | ||
|
|
aa79c0a4b9 | ||
|
|
05c86aeb28 | ||
|
|
f28f073c7c | ||
|
|
036557ac18 | ||
|
|
1b61fbc8ed | ||
|
|
97f03edcd6 | ||
|
|
7e5efa0005 | ||
|
|
d55fc611c4 | ||
|
|
720fe3405b | ||
|
|
c894b2b459 | ||
|
|
6d5bf4b908 | ||
|
|
40a55a640c |
2
.hgtags
2
.hgtags
@@ -50,3 +50,5 @@ e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
|
||||
3c11c5c9776f3c678719161e96cc0a08169c1cb8 2.2.3
|
||||
ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
|
||||
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
|
||||
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1
|
||||
8738b828dec53937765db71951ef955cca4c51f6 2.3.2
|
||||
|
||||
65
CHANGELOG
65
CHANGELOG
@@ -1,3 +1,68 @@
|
||||
Changes between 2.3.2 and 2.3.3
|
||||
-----------------------------------
|
||||
|
||||
- fix issue214 - parse modules that contain special objects like e. g.
|
||||
flask's request object which blows up on getattr access if no request
|
||||
is active. thanks Thomas Waldmann.
|
||||
|
||||
- fix issue213 - allow to parametrize with values like numpy arrays that
|
||||
do not support an __eq__ operator
|
||||
|
||||
- fix issue215 - split test_python.org into multiple files
|
||||
|
||||
- fix issue148 - @unittest.skip on classes is now recognized and avoids
|
||||
calling setUpClass/tearDownClass, thanks Pavel Repin
|
||||
|
||||
- fix issue209 - reintroduce python2.4 support by depending on newer
|
||||
pylib which re-introduced statement-finding for pre-AST interpreters
|
||||
|
||||
- nose support: only call setup if its a callable, thanks Andrew
|
||||
Taumoefolau
|
||||
|
||||
- fix issue219 - add py2.4-3.3 classifiers to TROVE list
|
||||
|
||||
- in tracebacks *,** arg values are now shown next to normal arguments
|
||||
(thanks Manuel Jacob)
|
||||
|
||||
- fix issue217 - support mock.patch with pytest's fixtures - note that
|
||||
you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
|
||||
|
||||
- fix issue127 - improve documentation for pytest_addoption() and
|
||||
add a ``config.getoption(name)`` helper function for consistency.
|
||||
|
||||
Changes between 2.3.1 and 2.3.2
|
||||
-----------------------------------
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
|
||||
- fix issue205 - conftests in subdirs customizing
|
||||
pytest_pycollect_makemodule and pytest_pycollect_makeitem
|
||||
now work properly
|
||||
|
||||
- fix teardown-ordering for parametrized setups
|
||||
|
||||
- fix issue127 - better documentation for pytest_addoption
|
||||
and related objects.
|
||||
|
||||
- fix unittest behaviour: TestCase.runtest only called if there are
|
||||
test methods defined
|
||||
|
||||
- improve trial support: don't collect its empty
|
||||
unittest.TestCase.runTest() method
|
||||
|
||||
- "python setup.py test" now works with pytest itself
|
||||
|
||||
- fix/improve internal/packaging related bits:
|
||||
|
||||
- exception message check of test_nose.py now passes on python33 as well
|
||||
|
||||
- issue206 - fix test_assertrewrite.py to work when a global
|
||||
PYTHONDONTWRITEBYTECODE=1 is present
|
||||
|
||||
- add tox.ini to pytest distribution so that ignore-dirs and others config
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
|
||||
Changes between 2.3.0 and 2.3.1
|
||||
-----------------------------------
|
||||
|
||||
|
||||
53
ISSUES.txt
53
ISSUES.txt
@@ -48,59 +48,6 @@ if the signature of a decorated function does not match. XXX is it
|
||||
not sufficient to always allow non-matches?
|
||||
|
||||
|
||||
unify item/request classes, generalize items
|
||||
---------------------------------------------------------------
|
||||
tags: 2.4 wish
|
||||
|
||||
in lieu of extended parametrization and the new way to specify resource
|
||||
factories in terms of the parametrize decorator, consider unification
|
||||
of the item and request class. This also is connected with allowing
|
||||
funcargs in setup functions. Example of new item API:
|
||||
|
||||
item.getresource("db") # alias for request.getfuncargvalue
|
||||
item.addfinalizer(...)
|
||||
item.cached_setup(...)
|
||||
item.applymarker(...)
|
||||
|
||||
test classes/modules could then use this api via::
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
use item API ...
|
||||
|
||||
introduction of this new method needs to be _fully_ backward compatible -
|
||||
and the documentation needs to change along to mention this new way of
|
||||
doing things.
|
||||
|
||||
impl note: probably Request._fillfixtures would be called from the
|
||||
python plugins own pytest_runtest_setup(item) and would call
|
||||
item.getresource(X) for all X in the funcargs of a function.
|
||||
|
||||
XXX is it possible to even put the above item API to Nodes, i.e. also
|
||||
to Directorty/module/file/class collectors? Problem is that current
|
||||
funcarg factories presume they are called with a per-function (even
|
||||
per-funcarg-per-function) scope. Could there be small tweaks to the new
|
||||
API that lift this restriction?
|
||||
|
||||
consider::
|
||||
|
||||
def setup_class(cls, tmpdir):
|
||||
# would get a per-class tmpdir because tmpdir parametrization
|
||||
# would know that it is called with a class scope
|
||||
#
|
||||
#
|
||||
#
|
||||
this looks very difficult because those setup functions are also used
|
||||
by nose etc. Rather consider introduction of a new setup hook:
|
||||
|
||||
def setup_test(self, item):
|
||||
self.db = item.cached_setup(..., scope='class')
|
||||
self.tmpdir = item.getresource("tmpdir")
|
||||
|
||||
this should be compatible to unittest/nose and provide much of what
|
||||
"testresources" provide. XXX This would not allow full parametrization
|
||||
such that test function could be run multiple times with different
|
||||
values. See "parametrized attributes" issue.
|
||||
|
||||
allow parametrized attributes on classes
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ include CHANGELOG
|
||||
include README.txt
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include tox.ini
|
||||
include LICENSE
|
||||
graft doc
|
||||
graft testing
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#
|
||||
__version__ = '2.3.1'
|
||||
__version__ = '2.3.3'
|
||||
|
||||
@@ -34,7 +34,7 @@ else:
|
||||
PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
|
||||
del ver, impl
|
||||
|
||||
PYC_EXT = ".py" + ("c" if __debug__ else "o")
|
||||
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
||||
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
|
||||
|
||||
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
|
||||
|
||||
@@ -19,7 +19,7 @@ def pytest_unconfigure(config):
|
||||
fin()
|
||||
|
||||
class Parser:
|
||||
""" Parser for command line arguments. """
|
||||
""" Parser for command line arguments and ini-file values. """
|
||||
|
||||
def __init__(self, usage=None, processopt=None):
|
||||
self._anonymous = OptionGroup("custom options", parser=self)
|
||||
@@ -38,9 +38,14 @@ class Parser:
|
||||
def getgroup(self, name, description="", after=None):
|
||||
""" get (or create) a named option Group.
|
||||
|
||||
:name: unique name of the option group.
|
||||
:name: name of the option group.
|
||||
:description: long description for --help output.
|
||||
:after: name of other group, used for ordering --help output.
|
||||
|
||||
The returned group object has an ``addoption`` method with the same
|
||||
signature as :py:func:`parser.addoption
|
||||
<_pytest.config.Parser.addoption>` but will be shown in the
|
||||
respective group in the output of ``pytest. --help``.
|
||||
"""
|
||||
for group in self._groups:
|
||||
if group.name == name:
|
||||
@@ -54,7 +59,19 @@ class Parser:
|
||||
return group
|
||||
|
||||
def addoption(self, *opts, **attrs):
|
||||
""" add an optparse-style option. """
|
||||
""" register a command line option.
|
||||
|
||||
:opts: option names, can be short or long options.
|
||||
:attrs: same attributes which the ``add_option()`` function of the
|
||||
`optparse library
|
||||
<http://docs.python.org/library/optparse.html#module-optparse>`_
|
||||
accepts.
|
||||
|
||||
After command line parsing options are available on the pytest config
|
||||
object via ``config.option.NAME`` where ``NAME`` is usually set
|
||||
by passing a ``dest`` attribute, for example
|
||||
``addoption("--long", dest="NAME", ...)``.
|
||||
"""
|
||||
self._anonymous.addoption(*opts, **attrs)
|
||||
|
||||
def parse(self, args):
|
||||
@@ -75,7 +92,15 @@ class Parser:
|
||||
return args
|
||||
|
||||
def addini(self, name, help, type=None, default=None):
|
||||
""" add an ini-file option with the given name and description. """
|
||||
""" register an ini-file option.
|
||||
|
||||
:name: name of the ini-variable
|
||||
:type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
|
||||
:default: default value if no ini-file option exists but is queried.
|
||||
|
||||
The value of ini-variables can be retrieved via a call to
|
||||
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
|
||||
"""
|
||||
assert type in (None, "pathlist", "args", "linelist")
|
||||
self._inidict[name] = (help, type, default)
|
||||
self._ininames.append(name)
|
||||
@@ -246,8 +271,8 @@ class CmdOptions(object):
|
||||
class Config(object):
|
||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||
def __init__(self, pluginmanager=None):
|
||||
#: command line option values, usually added via parser.addoption(...)
|
||||
#: or parser.getgroup(...).addoption(...) calls
|
||||
#: access to command line option as attributes.
|
||||
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
|
||||
self.option = CmdOptions()
|
||||
self._parser = Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
@@ -259,6 +284,7 @@ class Config(object):
|
||||
self._conftest = Conftest(onimport=self._onimportconftest)
|
||||
self.hook = self.pluginmanager.hook
|
||||
self._inicache = {}
|
||||
self._opt2dest = {}
|
||||
self._cleanup = []
|
||||
|
||||
@classmethod
|
||||
@@ -279,6 +305,9 @@ class Config(object):
|
||||
self.pluginmanager.consider_conftest(conftestmodule)
|
||||
|
||||
def _processopt(self, opt):
|
||||
for name in opt._short_opts + opt._long_opts:
|
||||
self._opt2dest[name] = opt.dest
|
||||
|
||||
if hasattr(opt, 'default') and opt.dest:
|
||||
if not hasattr(self.option, opt.dest):
|
||||
setattr(self.option, opt.dest, opt.default)
|
||||
@@ -357,8 +386,9 @@ class Config(object):
|
||||
x.append(line) # modifies the cached list inline
|
||||
|
||||
def getini(self, name):
|
||||
""" return configuration value from an ini file. If the
|
||||
specified name hasn't been registered through a prior ``parse.addini``
|
||||
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
||||
specified name hasn't been registered through a prior
|
||||
:py:func:`parser.addini <pytest.config.Parser.addini>`
|
||||
call (usually from a plugin), a ValueError is raised. """
|
||||
try:
|
||||
return self._inicache[name]
|
||||
@@ -412,8 +442,22 @@ class Config(object):
|
||||
self._checkconftest(name)
|
||||
return self._conftest.rget(name, path)
|
||||
|
||||
def getoption(self, name):
|
||||
""" return command line option value.
|
||||
|
||||
:arg name: name of the option. You may also specify
|
||||
the literal ``--OPT`` option instead of the "dest" option name.
|
||||
"""
|
||||
name = self._opt2dest.get(name, name)
|
||||
try:
|
||||
return getattr(self.option, name)
|
||||
except AttributeError:
|
||||
raise ValueError("no option named %r" % (name,))
|
||||
|
||||
def getvalue(self, name, path=None):
|
||||
""" return ``name`` value looked set from command line options.
|
||||
""" return command line option value.
|
||||
|
||||
:arg name: name of the command line option
|
||||
|
||||
(deprecated) if we can't find the option also lookup
|
||||
the name in a matching conftest file.
|
||||
@@ -451,14 +495,3 @@ def getcfg(args, inibasenames):
|
||||
return iniconfig['pytest']
|
||||
return {}
|
||||
|
||||
def findupwards(current, basename):
|
||||
current = py.path.local(current)
|
||||
while 1:
|
||||
p = current.join(basename)
|
||||
if p.check():
|
||||
return p
|
||||
p = current.dirpath()
|
||||
if p == current:
|
||||
return
|
||||
current = p
|
||||
|
||||
|
||||
@@ -23,8 +23,28 @@ def pytest_cmdline_preparse(config, args):
|
||||
"""modify command line arguments before option parsing. """
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""add optparse-style options and ini-style config values via calls
|
||||
to ``parser.addoption`` and ``parser.addini(...)``.
|
||||
"""register optparse-style options and ini-style config values.
|
||||
|
||||
This function must be implemented in a :ref:`plugin <pluginorder>` and is
|
||||
called once at the beginning of a test run.
|
||||
|
||||
:arg parser: To add command line options, call
|
||||
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
|
||||
To add ini-file values call :py:func:`parser.addini(...)
|
||||
<_pytest.config.Parser.addini>`.
|
||||
|
||||
Options can later be accessed through the
|
||||
:py:class:`config <_pytest.config.Config>` object, respectively:
|
||||
|
||||
- :py:func:`config.getoption(name) <_pytest.config.Config.getoption>` to
|
||||
retrieve the value of a command line option.
|
||||
|
||||
- :py:func:`config.getini(name) <_pytest.config.Config.getini>` to retrieve
|
||||
a value read from an ini-style file.
|
||||
|
||||
The config object is passed around on many internal objects via the ``.config``
|
||||
attribute or can be retrieved as the ``pytestconfig`` fixture or accessed
|
||||
via (deprecated) ``pytest.config``.
|
||||
"""
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
@@ -33,7 +53,7 @@ def pytest_cmdline_main(config):
|
||||
pytest_cmdline_main.firstresult = True
|
||||
|
||||
def pytest_configure(config):
|
||||
""" called after command line options have been parsed.
|
||||
""" called after command line options have been parsed
|
||||
and all plugins and initial conftest files been loaded.
|
||||
"""
|
||||
|
||||
|
||||
@@ -211,14 +211,16 @@ class Node(object):
|
||||
#: filesystem path where this node was collected from (can be None)
|
||||
self.fspath = getattr(parent, 'fspath', None)
|
||||
|
||||
#: fspath sensitive hook proxy used to call pytest hooks
|
||||
self.ihook = self.session.gethookproxy(self.fspath)
|
||||
|
||||
#: keywords/markers collected from all scopes
|
||||
self.keywords = NodeKeywords(self)
|
||||
|
||||
#self.extrainit()
|
||||
|
||||
@property
|
||||
def ihook(self):
|
||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||
return self.session.gethookproxy(self.fspath)
|
||||
|
||||
#def extrainit(self):
|
||||
# """"extra initialization after Node is initialized. Implemented
|
||||
# by some subclasses. """
|
||||
|
||||
@@ -41,7 +41,7 @@ def pytest_make_collect_report(collector):
|
||||
|
||||
def call_optional(obj, name):
|
||||
method = getattr(obj, name, None)
|
||||
if method is not None and not hasattr(method, "_pytestfixturefunction"):
|
||||
if method is not None and not hasattr(method, "_pytestfixturefunction") and py.builtin.callable(method):
|
||||
# If there's any problems allow the exception to raise rather than
|
||||
# silently ignoring them
|
||||
method()
|
||||
|
||||
@@ -161,8 +161,8 @@ def pytest_collect_file(path, parent):
|
||||
break
|
||||
else:
|
||||
return
|
||||
return parent.ihook.pytest_pycollect_makemodule(
|
||||
path=path, parent=parent)
|
||||
ihook = parent.session.gethookproxy(path)
|
||||
return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
|
||||
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
return Module(path, parent)
|
||||
@@ -288,6 +288,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
||||
return l
|
||||
|
||||
def makeitem(self, name, obj):
|
||||
#assert self.ihook.fspath == self.fspath, self
|
||||
return self.ihook.pytest_pycollect_makeitem(
|
||||
collector=self, name=name, obj=obj)
|
||||
|
||||
@@ -597,7 +598,7 @@ class CallSpec2(object):
|
||||
if valtype == "funcargs":
|
||||
self.params[arg] = id
|
||||
self._arg2scopenum[arg] = scopenum
|
||||
if val == _notexists:
|
||||
if val is _notexists:
|
||||
self._emptyparamspecified = True
|
||||
self._idlist.append(id)
|
||||
|
||||
@@ -1285,12 +1286,6 @@ scopenum_subfunction = scopes.index("subfunction")
|
||||
def scopemismatch(currentscope, newscope):
|
||||
return scopes.index(newscope) > scopes.index(currentscope)
|
||||
|
||||
def slice_kwargs(names, kwargs):
|
||||
new_kwargs = {}
|
||||
for name in names:
|
||||
new_kwargs[name] = kwargs[name]
|
||||
return new_kwargs
|
||||
|
||||
class FixtureLookupError(LookupError):
|
||||
""" could not return a requested Fixture (missing or invalid). """
|
||||
def __init__(self, argname, request, msg=None):
|
||||
@@ -1531,7 +1526,7 @@ class FixtureManager:
|
||||
item.session._setupstate._callfinalizers((name, param))
|
||||
l = self._arg2finish.get(name)
|
||||
if l is not None:
|
||||
for fin in l:
|
||||
for fin in reversed(l):
|
||||
fin()
|
||||
|
||||
def parsefactories(self, node_or_obj, nodeid=None, unittest=False):
|
||||
@@ -1550,7 +1545,15 @@ class FixtureManager:
|
||||
continue
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
# or are "@pytest.fixture" marked
|
||||
marker = getattr(obj, "_pytestfixturefunction", None)
|
||||
try:
|
||||
marker = obj._pytestfixturefunction
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception:
|
||||
# some objects raise errors like request (from flask import request)
|
||||
# we don't expect them to be fixture functions
|
||||
marker = None
|
||||
|
||||
if marker is None:
|
||||
if not name.startswith(self._argprefix):
|
||||
continue
|
||||
@@ -1656,8 +1659,14 @@ class FixtureDef:
|
||||
def getfuncargnames(function, startindex=None):
|
||||
# XXX merge with main.py's varnames
|
||||
#assert not inspect.isclass(function)
|
||||
realfunction = function
|
||||
while hasattr(realfunction, "__wrapped__"):
|
||||
realfunction = realfunction.__wrapped__
|
||||
if startindex is None:
|
||||
startindex = inspect.ismethod(function) and 1 or 0
|
||||
if realfunction != function:
|
||||
startindex += len(getattr(function, "patchings", []))
|
||||
function = realfunction
|
||||
argnames = inspect.getargs(py.code.getrawcode(function))[0]
|
||||
defaults = getattr(function, 'func_defaults',
|
||||
getattr(function, '__defaults__', None)) or ()
|
||||
|
||||
@@ -28,6 +28,7 @@ class UnitTestCase(pytest.Class):
|
||||
loader = py.std.unittest.TestLoader()
|
||||
module = self.getparent(pytest.Module).obj
|
||||
cls = self.obj
|
||||
foundsomething = False
|
||||
for name in loader.getTestCaseNames(self.obj):
|
||||
x = getattr(self.obj, name)
|
||||
funcobj = getattr(x, 'im_func', x)
|
||||
@@ -35,17 +36,26 @@ class UnitTestCase(pytest.Class):
|
||||
if hasattr(funcobj, 'todo'):
|
||||
pytest.mark.xfail(reason=str(funcobj.todo))(funcobj)
|
||||
yield TestCaseFunction(name, parent=self)
|
||||
foundsomething = True
|
||||
|
||||
if getattr(self.obj, 'runTest', None) is not None:
|
||||
yield TestCaseFunction('runTest', parent=self)
|
||||
if not foundsomething:
|
||||
runtest = getattr(self.obj, 'runTest', None)
|
||||
if runtest is not None:
|
||||
ut = sys.modules.get("twisted.trial.unittest", None)
|
||||
if ut is None or runtest != ut.TestCase.runTest:
|
||||
yield TestCaseFunction('runTest', parent=self)
|
||||
|
||||
def setup(self):
|
||||
if getattr(self.obj, '__unittest_skip__', False):
|
||||
return
|
||||
meth = getattr(self.obj, 'setUpClass', None)
|
||||
if meth is not None:
|
||||
meth()
|
||||
super(UnitTestCase, self).setup()
|
||||
|
||||
def teardown(self):
|
||||
if getattr(self.obj, '__unittest_skip__', False):
|
||||
return
|
||||
meth = getattr(self.obj, 'tearDownClass', None)
|
||||
if meth is not None:
|
||||
meth()
|
||||
|
||||
@@ -5,6 +5,9 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
release-2.3.3
|
||||
release-2.3.2
|
||||
release-2.3.1
|
||||
release-2.3.0
|
||||
release-2.2.4
|
||||
release-2.2.2
|
||||
|
||||
57
doc/en/announce/release-2.3.2.txt
Normal file
57
doc/en/announce/release-2.3.2.txt
Normal file
@@ -0,0 +1,57 @@
|
||||
pytest-2.3.2: some fixes and more traceback-printing speed
|
||||
===========================================================================
|
||||
|
||||
pytest-2.3.2 is a another stabilization release:
|
||||
|
||||
- issue 205: fixes a regression with conftest detection
|
||||
- issue 208/29: fixes traceback-printing speed in some bad cases
|
||||
- fix teardown-ordering for parametrized setups
|
||||
- fix unittest and trial compat behaviour with respect to runTest() methods
|
||||
- issue 206 and others: some improvements to packaging
|
||||
- fix issue127 and others: improve some docs
|
||||
|
||||
See
|
||||
|
||||
http://pytest.org/
|
||||
|
||||
for general information. To install or upgrade pytest:
|
||||
|
||||
pip install -U pytest # or
|
||||
easy_install -U pytest
|
||||
|
||||
best,
|
||||
holger krekel
|
||||
|
||||
|
||||
Changes between 2.3.1 and 2.3.2
|
||||
-----------------------------------
|
||||
|
||||
- fix issue208 and fix issue29 use new py version to avoid long pauses
|
||||
when printing tracebacks in long modules
|
||||
|
||||
- fix issue205 - conftests in subdirs customizing
|
||||
pytest_pycollect_makemodule and pytest_pycollect_makeitem
|
||||
now work properly
|
||||
|
||||
- fix teardown-ordering for parametrized setups
|
||||
|
||||
- fix issue127 - better documentation for pytest_addoption
|
||||
and related objects.
|
||||
|
||||
- fix unittest behaviour: TestCase.runtest only called if there are
|
||||
test methods defined
|
||||
|
||||
- improve trial support: don't collect its empty
|
||||
unittest.TestCase.runTest() method
|
||||
|
||||
- "python setup.py test" now works with pytest itself
|
||||
|
||||
- fix/improve internal/packaging related bits:
|
||||
|
||||
- exception message check of test_nose.py now passes on python33 as well
|
||||
|
||||
- issue206 - fix test_assertrewrite.py to work when a global
|
||||
PYTHONDONTWRITEBYTECODE=1 is present
|
||||
|
||||
- add tox.ini to pytest distribution so that ignore-dirs and others config
|
||||
bits are properly distributed for maintainers who run pytest-own tests
|
||||
@@ -26,7 +26,7 @@ you will see the return value of the function call::
|
||||
|
||||
$ py.test test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_assert1.py F
|
||||
@@ -110,7 +110,7 @@ if you run this module::
|
||||
|
||||
$ py.test test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_assert2.py F
|
||||
|
||||
@@ -64,7 +64,7 @@ of the failing function and hide the other one::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
|
||||
test_module.py .F
|
||||
@@ -78,7 +78,7 @@ of the failing function and hide the other one::
|
||||
|
||||
test_module.py:9: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
setting up <function test_func2 at 0x240e050>
|
||||
setting up <function test_func2 at 0x2d63d70>
|
||||
==================== 1 failed, 1 passed in 0.01 seconds ====================
|
||||
|
||||
Accessing captured output from a test function
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
version = release = "2.3.1"
|
||||
version = release = "2.3.3"
|
||||
|
||||
import sys, os
|
||||
|
||||
@@ -53,7 +53,7 @@ master_doc = 'contents'
|
||||
|
||||
# General information about the project.
|
||||
project = u'pytest'
|
||||
copyright = u'2011, holger krekel et alii'
|
||||
copyright = u'2012, holger krekel'
|
||||
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ htmlhelp_basename = 'pytestdoc'
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('contents', 'pytest.tex', u'pytest Documentation',
|
||||
u'holger krekel et alii', 'manual'),
|
||||
u'holger krekel, http://merlinux.eu', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -239,7 +239,7 @@ man_pages = [
|
||||
epub_title = u'pytest'
|
||||
epub_author = u'holger krekel at merlinux eu'
|
||||
epub_publisher = u'holger krekel at merlinux eu'
|
||||
epub_copyright = u'2011, holger krekel et alii'
|
||||
epub_copyright = u'2012, holger krekel et alii'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
|
||||
@@ -12,6 +12,8 @@ configurations files by using the general help option::
|
||||
This will display command line and configuration file settings
|
||||
which were registered by installed plugins.
|
||||
|
||||
.. _inifiles:
|
||||
|
||||
How test configuration is read from configuration INI-files
|
||||
-------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ then you can just invoke ``py.test`` without command line options::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
mymodule.py .
|
||||
|
||||
@@ -26,25 +26,25 @@ You can then restrict a test run to only run tests marked with ``webtest``::
|
||||
|
||||
$ py.test -v -m webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_server.py:3: test_send_http PASSED
|
||||
|
||||
=================== 1 tests deselected by "-m 'webtest'" ===================
|
||||
================== 1 passed, 1 deselected in 0.00 seconds ==================
|
||||
================== 1 passed, 1 deselected in 0.02 seconds ==================
|
||||
|
||||
Or the inverse, running all tests except the webtest ones::
|
||||
|
||||
$ py.test -v -m "not webtest"
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_server.py:6: test_something_quick PASSED
|
||||
|
||||
================= 1 tests deselected by "-m 'not webtest'" =================
|
||||
================== 1 passed, 1 deselected in 0.00 seconds ==================
|
||||
================== 1 passed, 1 deselected in 0.01 seconds ==================
|
||||
|
||||
Registering markers
|
||||
-------------------------------------
|
||||
@@ -145,7 +145,7 @@ the given argument::
|
||||
|
||||
$ py.test -k send_http # running with the above defined examples
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_server.py .
|
||||
@@ -157,7 +157,7 @@ And you can also run all tests except the ones that match the keyword::
|
||||
|
||||
$ py.test -k-send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_mark_classlevel.py ..
|
||||
@@ -170,7 +170,7 @@ Or to only select the class::
|
||||
|
||||
$ py.test -kTestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_mark_classlevel.py ..
|
||||
@@ -194,7 +194,7 @@ specifies via named environments::
|
||||
|
||||
import pytest
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("-E", dest="env", action="store", metavar="NAME",
|
||||
parser.addoption("-E", action="store", metavar="NAME",
|
||||
help="only run tests matching the environment NAME.")
|
||||
|
||||
def pytest_configure(config):
|
||||
@@ -206,7 +206,7 @@ specifies via named environments::
|
||||
envmarker = item.keywords.get("env", None)
|
||||
if envmarker is not None:
|
||||
envname = envmarker.args[0]
|
||||
if envname != item.config.option.env:
|
||||
if envname != item.config.getoption("-E"):
|
||||
pytest.skip("test requires env %r" % envname)
|
||||
|
||||
A test file using this local plugin::
|
||||
@@ -223,23 +223,23 @@ the test needs::
|
||||
|
||||
$ py.test -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py s
|
||||
|
||||
======================== 1 skipped in 0.00 seconds =========================
|
||||
======================== 1 skipped in 0.01 seconds =========================
|
||||
|
||||
and here is one that specifies exactly the environment needed::
|
||||
|
||||
$ py.test -E stage1
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_someenv.py .
|
||||
|
||||
========================= 1 passed in 0.00 seconds =========================
|
||||
========================= 1 passed in 0.01 seconds =========================
|
||||
|
||||
The ``--markers`` option always gives you a list of available markers::
|
||||
|
||||
@@ -351,12 +351,12 @@ then you will see two test skipped and two executed tests as expected::
|
||||
|
||||
$ py.test -rs # this option reports skip reasons
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_plat.py s.s.
|
||||
========================= short test summary info ==========================
|
||||
SKIP [2] /tmp/doc-exec-592/conftest.py:12: cannot run on platform linux2
|
||||
SKIP [2] /tmp/doc-exec-57/conftest.py:12: cannot run on platform linux2
|
||||
|
||||
=================== 2 passed, 2 skipped in 0.01 seconds ====================
|
||||
|
||||
@@ -364,7 +364,7 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
|
||||
$ py.test -m linux2
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_plat.py .
|
||||
|
||||
@@ -27,7 +27,7 @@ now execute the test specification::
|
||||
|
||||
nonpython $ py.test test_simple.yml
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
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.03 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.04 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 @@ consulted when reporting in ``verbose`` mode::
|
||||
|
||||
nonpython $ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml:1: usecase: ok PASSED
|
||||
@@ -67,17 +67,17 @@ consulted when reporting in ``verbose`` mode::
|
||||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
==================== 1 failed, 1 passed in 0.03 seconds ====================
|
||||
==================== 1 failed, 1 passed in 0.04 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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'ok'>
|
||||
<YamlItem 'hello'>
|
||||
|
||||
============================= in 0.02 seconds =============================
|
||||
============================= in 0.04 seconds =============================
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import pytest
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
def pytest_collect_file(parent, path):
|
||||
if path.ext == ".yml" and path.basename.startswith("test"):
|
||||
return YamlFile(path, parent)
|
||||
|
||||
|
||||
class YamlFile(pytest.File):
|
||||
def collect(self):
|
||||
import yaml # we need a yaml parser, e.g. PyYAML
|
||||
@@ -17,7 +17,7 @@ class YamlItem(pytest.Item):
|
||||
def __init__(self, name, parent, spec):
|
||||
super(YamlItem, self).__init__(name, parent)
|
||||
self.spec = spec
|
||||
|
||||
|
||||
def runtest(self):
|
||||
for name, value in self.spec.items():
|
||||
# some custom test execution (dumb example follows)
|
||||
|
||||
@@ -104,7 +104,7 @@ this is a fully self-contained example which you can run with::
|
||||
|
||||
$ py.test test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_scenarios.py ....
|
||||
@@ -116,7 +116,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
|
||||
$ py.test --collectonly test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
<Class 'TestSampleWithScenarios'>
|
||||
@@ -180,7 +180,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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[d1]'>
|
||||
@@ -195,7 +195,7 @@ And then when we run the test::
|
||||
================================= FAILURES =================================
|
||||
_________________________ test_db_initialized[d2] __________________________
|
||||
|
||||
db = <conftest.DB2 instance at 0x233e8c0>
|
||||
db = <conftest.DB2 instance at 0x1d8aef0>
|
||||
|
||||
def test_db_initialized(db):
|
||||
# a dummy test
|
||||
@@ -250,7 +250,7 @@ argument sets to use for each test function. Let's run it::
|
||||
================================= FAILURES =================================
|
||||
________________________ TestClass.test_equals[1-2] ________________________
|
||||
|
||||
self = <test_parametrize.TestClass instance at 0x2a73518>, a = 1, b = 2
|
||||
self = <test_parametrize.TestClass instance at 0x1628cb0>, a = 1, b = 2
|
||||
|
||||
def test_equals(self, a, b):
|
||||
> assert a == b
|
||||
|
||||
@@ -43,7 +43,7 @@ then the test collection looks like this::
|
||||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
@@ -51,7 +51,7 @@ then the test collection looks like this::
|
||||
<Function 'check_simple'>
|
||||
<Function 'check_complex'>
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
Interpreting cmdline arguments as Python packages
|
||||
-----------------------------------------------------
|
||||
@@ -82,7 +82,7 @@ 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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 3 items
|
||||
<Module 'pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
@@ -91,7 +91,7 @@ You can always peek at the collection tree without running tests like this::
|
||||
<Function 'test_method'>
|
||||
<Function 'test_anothermethod'>
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
============================= in 0.01 seconds =============================
|
||||
|
||||
customizing test collection to find all .py files
|
||||
---------------------------------------------------------
|
||||
@@ -135,7 +135,7 @@ interpreters and will leave out the setup.py file::
|
||||
|
||||
$ py.test --collectonly
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
<Module 'pkg/module_py2.py'>
|
||||
<Function 'test_only_on_python2'>
|
||||
|
||||
@@ -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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
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 0x27ee810>
|
||||
self = <failure_demo.TestFailing object at 0x1136710>
|
||||
|
||||
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 0x2786488>()
|
||||
E + and 43 = <function g at 0x2786500>()
|
||||
E + where 42 = <function f at 0x1146410>()
|
||||
E + and 43 = <function g at 0x1146488>()
|
||||
|
||||
failure_demo.py:28: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x27ee890>
|
||||
self = <failure_demo.TestFailing object at 0x11329d0>
|
||||
|
||||
def test_simple_multiline(self):
|
||||
otherfunc_multi(
|
||||
@@ -66,19 +66,19 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:11: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
self = <failure_demo.TestFailing object at 0x27eea50>
|
||||
self = <failure_demo.TestFailing object at 0x10d09d0>
|
||||
|
||||
def test_not(self):
|
||||
def f():
|
||||
return 42
|
||||
> assert not f()
|
||||
E assert not 42
|
||||
E + where 42 = <function f at 0x27867d0>()
|
||||
E + where 42 = <function f at 0x1146848>()
|
||||
|
||||
failure_demo.py:38: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x27ee9d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10ca210>
|
||||
|
||||
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 0x27961d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x11368d0>
|
||||
|
||||
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 0x2796250>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x11340d0>
|
||||
|
||||
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 0x2796090>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10cfd90>
|
||||
|
||||
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 0x278cf50>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10d0b10>
|
||||
|
||||
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 0x278c250>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142dd0>
|
||||
|
||||
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 0x278c4d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1136850>
|
||||
|
||||
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 0x278cbd0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1134e10>
|
||||
|
||||
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 0x278c750>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1169c90>
|
||||
|
||||
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 0x278c5d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142c50>
|
||||
|
||||
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 0x27880d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10d0d90>
|
||||
|
||||
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 0x2788ed0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10e0110>
|
||||
|
||||
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 0x2788fd0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x10ca7d0>
|
||||
|
||||
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 0x2788850>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1142750>
|
||||
|
||||
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 0x27882d0>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0x1134410>
|
||||
|
||||
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 0x2788910>.b
|
||||
E + where 1 = <failure_demo.Foo object at 0x10e07d0>.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 0x2788f50>.b
|
||||
E + where <failure_demo.Foo object at 0x2788f50> = <class 'failure_demo.Foo'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x1132390>.b
|
||||
E + where <failure_demo.Foo object at 0x1132390> = <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 0x29558d0>
|
||||
self = <failure_demo.Foo object at 0x1136fd0>
|
||||
|
||||
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 0x2788690>.b
|
||||
E + where <failure_demo.Foo object at 0x2788690> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x2788d10>.b
|
||||
E + where <failure_demo.Bar object at 0x2788d10> = <class 'failure_demo.Bar'>()
|
||||
E + where 1 = <failure_demo.Foo object at 0x1134c50>.b
|
||||
E + where <failure_demo.Foo object at 0x1134c50> = <class 'failure_demo.Foo'>()
|
||||
E + and 2 = <failure_demo.Bar object at 0x1134790>.b
|
||||
E + where <failure_demo.Bar object at 0x1134790> = <class 'failure_demo.Bar'>()
|
||||
|
||||
failure_demo.py:124: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x28000e0>
|
||||
self = <failure_demo.TestRaises instance at 0x10dc098>
|
||||
|
||||
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/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:850>:1: ValueError
|
||||
<0-codegen /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/_pytest/python.py:851>:1: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises instance at 0x2806b90>
|
||||
self = <failure_demo.TestRaises instance at 0x10d8320>
|
||||
|
||||
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 0x27ff518>
|
||||
self = <failure_demo.TestRaises instance at 0x10c0680>
|
||||
|
||||
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 0x27f2290>
|
||||
self = <failure_demo.TestRaises instance at 0x11604d0>
|
||||
|
||||
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 0x27f2fc8>
|
||||
self = <failure_demo.TestRaises instance at 0x10e2290>
|
||||
|
||||
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 0x27f3dd0>
|
||||
self = <failure_demo.TestRaises instance at 0x10e2f80>
|
||||
|
||||
def test_some_error(self):
|
||||
> if namenotexi:
|
||||
@@ -423,7 +423,7 @@ get on the terminal - we are working on that):
|
||||
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x28067a0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10d1b90>
|
||||
|
||||
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 0x27eddd0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x114f3b0>
|
||||
|
||||
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 0x27efbd8>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x11496c8>
|
||||
|
||||
def test_z2_type_error(self):
|
||||
l = 3
|
||||
@@ -472,19 +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 0x27f09e0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10cec20>
|
||||
|
||||
def test_startswith(self):
|
||||
s = "123"
|
||||
g = "456"
|
||||
> assert s.startswith(g)
|
||||
E assert <built-in method startswith of str object at 0x27f9ad0>('456')
|
||||
E + where <built-in method startswith of str object at 0x27f9ad0> = '123'.startswith
|
||||
E assert <built-in method startswith of str object at 0x113b918>('456')
|
||||
E + where <built-in method startswith of str object at 0x113b918> = '123'.startswith
|
||||
|
||||
failure_demo.py:188: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x27ef098>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10c87a0>
|
||||
|
||||
def test_startswith_nested(self):
|
||||
def f():
|
||||
@@ -492,15 +492,15 @@ get on the terminal - we are working on that):
|
||||
def g():
|
||||
return "456"
|
||||
> assert f().startswith(g())
|
||||
E assert <built-in method startswith of str object at 0x27f9ad0>('456')
|
||||
E + where <built-in method startswith of str object at 0x27f9ad0> = '123'.startswith
|
||||
E + where '123' = <function f at 0x28169b0>()
|
||||
E + and '456' = <function g at 0x280e8c0>()
|
||||
E assert <built-in method startswith of str object at 0x113b918>('456')
|
||||
E + where <built-in method startswith of str object at 0x113b918> = '123'.startswith
|
||||
E + where '123' = <function f at 0x10bea28>()
|
||||
E + and '456' = <function g at 0x10beaa0>()
|
||||
|
||||
failure_demo.py:195: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x2800878>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10c5488>
|
||||
|
||||
def test_global_func(self):
|
||||
> assert isinstance(globf(42), float)
|
||||
@@ -510,18 +510,18 @@ get on the terminal - we are working on that):
|
||||
failure_demo.py:198: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x27f20e0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x113f710>
|
||||
|
||||
def test_instance(self):
|
||||
self.x = 6*7
|
||||
> assert self.x != 42
|
||||
E assert 42 != 42
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x27f20e0>.x
|
||||
E + where 42 = <failure_demo.TestMoreErrors instance at 0x113f710>.x
|
||||
|
||||
failure_demo.py:202: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors instance at 0x27ec5a8>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x10bae18>
|
||||
|
||||
def test_compare(self):
|
||||
> assert globf(10) < 5
|
||||
@@ -531,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 0x27e43b0>
|
||||
self = <failure_demo.TestMoreErrors instance at 0x1160248>
|
||||
|
||||
def test_try_finally(self):
|
||||
x = 1
|
||||
@@ -540,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.16 seconds =========================
|
||||
======================== 39 failed in 0.25 seconds =========================
|
||||
|
||||
@@ -33,7 +33,7 @@ provide the ``cmdopt`` through a :ref:`fixture function <fixture function>`::
|
||||
|
||||
@pytest.fixture
|
||||
def cmdopt(request):
|
||||
return request.config.option.cmdopt
|
||||
return request.config.getoption("--cmdopt")
|
||||
|
||||
Let's run this without supplying our new option::
|
||||
|
||||
@@ -106,7 +106,7 @@ directory with the above conftest.py::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
@@ -129,7 +129,7 @@ line option to control skipping of ``slow`` marked tests::
|
||||
help="run slow tests")
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if 'slow' in item.keywords and not item.config.getvalue("runslow"):
|
||||
if 'slow' in item.keywords and not item.config.getoption("--runslow"):
|
||||
pytest.skip("need --runslow option to run")
|
||||
|
||||
We can now write a test module like this::
|
||||
@@ -150,12 +150,12 @@ 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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /tmp/doc-exec-597/conftest.py:9: need --runslow option to run
|
||||
SKIP [1] /tmp/doc-exec-62/conftest.py:9: need --runslow option to run
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.01 seconds ====================
|
||||
|
||||
@@ -163,7 +163,7 @@ Or run it including the ``slow`` marked test::
|
||||
|
||||
$ py.test --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
|
||||
test_module.py ..
|
||||
@@ -253,7 +253,7 @@ which will add the string to the test header accordingly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
project deps: mylib-1.1
|
||||
collected 0 items
|
||||
|
||||
@@ -276,7 +276,7 @@ which will add info only when run with "--v"::
|
||||
|
||||
$ py.test -v
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
collecting ... collected 0 items
|
||||
@@ -287,7 +287,7 @@ and nothing when run plainly::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 0 items
|
||||
|
||||
============================= in 0.00 seconds =============================
|
||||
@@ -319,7 +319,7 @@ Now we can profile which test functions execute the slowest::
|
||||
|
||||
$ py.test --durations=3
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 3 items
|
||||
|
||||
test_some_are_slow.py ...
|
||||
@@ -327,7 +327,7 @@ Now we can profile which test functions execute the slowest::
|
||||
========================= slowest 3 test durations =========================
|
||||
0.20s call test_some_are_slow.py::test_funcslow2
|
||||
0.10s call test_some_are_slow.py::test_funcslow1
|
||||
0.00s setup test_some_are_slow.py::test_funcfast
|
||||
0.00s call test_some_are_slow.py::test_funcfast
|
||||
========================= 3 passed in 0.31 seconds =========================
|
||||
|
||||
incremental testing - test steps
|
||||
@@ -380,7 +380,7 @@ If we run this::
|
||||
|
||||
$ py.test -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 4 items
|
||||
|
||||
test_step.py .Fx.
|
||||
@@ -388,7 +388,7 @@ If we run this::
|
||||
================================= FAILURES =================================
|
||||
____________________ TestUserHandling.test_modification ____________________
|
||||
|
||||
self = <test_step.TestUserHandling instance at 0x1aad680>
|
||||
self = <test_step.TestUserHandling instance at 0x2677b90>
|
||||
|
||||
def test_modification(self):
|
||||
> assert 0
|
||||
@@ -398,7 +398,7 @@ If we run this::
|
||||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::()::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds ===============
|
||||
============== 1 failed, 2 passed, 1 xfailed in 0.02 seconds ===============
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
failed. It is reported as an "expected failure".
|
||||
|
||||
@@ -25,10 +25,13 @@ how does py.test relate to twisted's trial?
|
||||
|
||||
Since some time py.test has builtin support for supporting tests
|
||||
written using trial. It does not itself start a reactor, however,
|
||||
and does not handle Deferreds returned from a test. Someone using
|
||||
these features might eventually write a dedicated ``pytest-twisted``
|
||||
plugin which will surely see strong support from the pytest development
|
||||
team.
|
||||
and does not handle Deferreds returned from a test in pytest style.
|
||||
If you are using trial's unittest.TestCase chances are that you can
|
||||
just run your tests even if you return Deferreds. In addition,
|
||||
there also is a dedicated `pytest-twisted
|
||||
<http://pypi.python.org/pypi/pytest-twisted`` plugin which allows to
|
||||
return deferreds from pytest-style tests, allowing to use
|
||||
:ref:`fixtures` and other features.
|
||||
|
||||
how does py.test work with Django?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -30,11 +30,10 @@ functions:
|
||||
to configuration and component options, or to re-use fixtures
|
||||
across class, module or whole test session scopes.
|
||||
|
||||
In addition, pytest continues to support :ref:`xunitsetup` which it
|
||||
originally introduced in 2005. You can mix both styles, moving
|
||||
incrementally from classic to new style, if you prefer. You can also
|
||||
start out from existing :ref:`unittest.TestCase style <unittest.TestCase>`
|
||||
or :ref:`nose based <nosestyle>` projects.
|
||||
In addition, pytest continues to support :ref:`xunitsetup`. You can mix
|
||||
both styles, moving incrementally from classic to new style, as you
|
||||
prefer. You can also start out from existing :ref:`unittest.TestCase
|
||||
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
|
||||
|
||||
.. _`funcargs`:
|
||||
.. _`funcarg mechanism`:
|
||||
@@ -66,13 +65,13 @@ using it::
|
||||
assert "merlinux" in msg
|
||||
assert 0 # for demo purposes
|
||||
|
||||
Here, the ``test_function`` needs the ``smtp`` fixture value. pytest
|
||||
Here, the ``test_ehlo`` needs the ``smtp`` fixture value. pytest
|
||||
will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
|
||||
marked ``smtp`` fixture function. Running the test looks like this::
|
||||
|
||||
$ py.test test_smtpsimple.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_smtpsimple.py F
|
||||
@@ -80,7 +79,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1ab68c0>
|
||||
smtp = <smtplib.SMTP instance at 0x1992a70>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response, msg = smtp.ehlo()
|
||||
@@ -90,7 +89,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
E assert 0
|
||||
|
||||
test_smtpsimple.py:12: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
========================= 1 failed in 0.30 seconds =========================
|
||||
|
||||
In the failure traceback we see that the test function was called with a
|
||||
``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture
|
||||
@@ -190,7 +189,7 @@ inspect what is going on and can now run the tests::
|
||||
|
||||
$ py.test test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF
|
||||
@@ -198,7 +197,7 @@ inspect what is going on and can now run the tests::
|
||||
================================= FAILURES =================================
|
||||
________________________________ test_ehlo _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2c35488>
|
||||
smtp = <smtplib.SMTP instance at 0x2b8a248>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -210,7 +209,7 @@ inspect what is going on and can now run the tests::
|
||||
test_module.py:6: AssertionError
|
||||
________________________________ test_noop _________________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x2c35488>
|
||||
smtp = <smtplib.SMTP instance at 0x2b8a248>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -219,7 +218,7 @@ inspect what is going on and can now run the tests::
|
||||
E assert 0
|
||||
|
||||
test_module.py:11: AssertionError
|
||||
========================= 2 failed in 0.13 seconds =========================
|
||||
========================= 2 failed in 0.48 seconds =========================
|
||||
|
||||
You see the two ``assert 0`` failing and more importantly you can also see
|
||||
that the same (module-scoped) ``smtp`` object was passed into the two
|
||||
@@ -272,7 +271,7 @@ using it has executed::
|
||||
|
||||
$ py.test -s -q --tb=no
|
||||
FF
|
||||
finalizing <smtplib.SMTP instance at 0x2afd830>
|
||||
finalizing <smtplib.SMTP instance at 0x1584908>
|
||||
|
||||
We see that the ``smtp`` instance is finalized after the two
|
||||
tests using it tests executed. If we had specified ``scope='function'``
|
||||
@@ -343,7 +342,7 @@ So let's just do another run::
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_ehlo[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1bd3710>
|
||||
smtp = <smtplib.SMTP instance at 0x2368248>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -355,7 +354,7 @@ So let's just do another run::
|
||||
test_module.py:6: AssertionError
|
||||
__________________________ test_noop[merlinux.eu] __________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1bd3710>
|
||||
smtp = <smtplib.SMTP instance at 0x2368248>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -366,7 +365,7 @@ So let's just do another run::
|
||||
test_module.py:11: AssertionError
|
||||
________________________ test_ehlo[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1c58128>
|
||||
smtp = <smtplib.SMTP instance at 0x2377680>
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response = smtp.ehlo()
|
||||
@@ -377,7 +376,7 @@ So let's just do another run::
|
||||
test_module.py:5: AssertionError
|
||||
________________________ test_noop[mail.python.org] ________________________
|
||||
|
||||
smtp = <smtplib.SMTP instance at 0x1c58128>
|
||||
smtp = <smtplib.SMTP instance at 0x2377680>
|
||||
|
||||
def test_noop(smtp):
|
||||
response = smtp.noop()
|
||||
@@ -425,13 +424,13 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
|
||||
$ py.test -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_appsetup.py:12: test_smtp_exists[merlinux.eu] PASSED
|
||||
test_appsetup.py:12: test_smtp_exists[mail.python.org] PASSED
|
||||
|
||||
========================= 2 passed in 0.20 seconds =========================
|
||||
========================= 2 passed in 6.79 seconds =========================
|
||||
|
||||
Due to the parametrization of ``smtp`` the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
@@ -490,7 +489,7 @@ Let's run the tests in verbose mode and with looking at the print-output::
|
||||
|
||||
$ py.test -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3 -- /home/hpk/p/pytest/.tox/regen/bin/python
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py:16: test_0[1] PASSED
|
||||
|
||||
@@ -23,7 +23,7 @@ Installation options::
|
||||
To check your installation has installed the correct version::
|
||||
|
||||
$ py.test --version
|
||||
This is py.test version 2.3.1, imported from /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/pytest.pyc
|
||||
This is py.test version 2.3.3, imported from /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/pytest.pyc
|
||||
|
||||
If you get an error checkout :ref:`installation issues`.
|
||||
|
||||
@@ -45,7 +45,7 @@ That's it. You can execute the test function now::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_sample.py F
|
||||
@@ -122,7 +122,7 @@ run the module by passing its filename::
|
||||
================================= FAILURES =================================
|
||||
____________________________ TestClass.test_two ____________________________
|
||||
|
||||
self = <test_class.TestClass instance at 0x10de710>
|
||||
self = <test_class.TestClass instance at 0x22a4d40>
|
||||
|
||||
def test_two(self):
|
||||
x = "hello"
|
||||
@@ -157,7 +157,7 @@ before performing the test function call. Let's just run it::
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_needsfiles ______________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-1516/test_needsfiles0')
|
||||
tmpdir = local('/tmp/pytest-594/test_needsfiles0')
|
||||
|
||||
def test_needsfiles(tmpdir):
|
||||
print tmpdir
|
||||
@@ -166,7 +166,7 @@ before performing the test function call. Let's just run it::
|
||||
|
||||
test_tmpdir.py:3: AssertionError
|
||||
----------------------------- Captured stdout ------------------------------
|
||||
/tmp/pytest-1516/test_needsfiles0
|
||||
/tmp/pytest-594/test_needsfiles0
|
||||
|
||||
Before the test runs, a unique-per-test-invocation temporary directory
|
||||
was created. More info at :ref:`tmpdir handling`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
.. _features:
|
||||
|
||||
pytest: makes you write better programs
|
||||
pytest: helps you write better programs
|
||||
=============================================
|
||||
|
||||
**a mature full-featured Python testing tool**
|
||||
@@ -34,8 +34,9 @@ pytest: makes you write better programs
|
||||
|
||||
**integrates many common testing methods**:
|
||||
|
||||
- can run many ``nose``, ``unittest.py`` and ``doctest.py`` style
|
||||
tests, including running testcases made for Django and trial
|
||||
- multi-paradigm: pytest can run many ``nose``, ``unittest.py`` and
|
||||
``doctest.py`` style test suites, including running testcases made for
|
||||
Django and trial
|
||||
- supports :ref:`good integration practises <goodpractises>`
|
||||
- supports extended :ref:`xUnit style setup <xunitsetup>`
|
||||
- supports domain-specific :ref:`non-python tests`
|
||||
|
||||
@@ -53,7 +53,7 @@ which will thus run three times::
|
||||
|
||||
$ py.test
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..F
|
||||
@@ -135,8 +135,8 @@ Let's also run with a stringinput that will lead to a failing test::
|
||||
|
||||
def test_valid_string(stringinput):
|
||||
> assert stringinput.isalpha()
|
||||
E assert <built-in method isalpha of str object at 0x2b21d78ed030>()
|
||||
E + where <built-in method isalpha of str object at 0x2b21d78ed030> = '!'.isalpha
|
||||
E assert <built-in method isalpha of str object at 0x2b1792721fa8>()
|
||||
E + where <built-in method isalpha of str object at 0x2b1792721fa8> = '!'.isalpha
|
||||
|
||||
test_strings.py:3: AssertionError
|
||||
|
||||
@@ -149,7 +149,7 @@ listlist::
|
||||
$ py.test -q -rs test_strings.py
|
||||
s
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] /home/hpk/p/pytest/.tox/regen/local/lib/python2.7/site-packages/_pytest/python.py:959: got empty parameter set, function test_valid_string at /tmp/doc-exec-564/test_strings.py:1
|
||||
SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python2.7/site-packages/_pytest/python.py:960: got empty parameter set, function test_valid_string at /tmp/doc-exec-26/test_strings.py:1
|
||||
|
||||
For further examples, you might want to look at :ref:`more
|
||||
parametrization examples <paramexamples>`.
|
||||
|
||||
@@ -5,7 +5,7 @@ Working with plugins and conftest files
|
||||
|
||||
py.test implements all aspects of configuration, collection, running and reporting by calling `well specified hooks`_. Virtually any Python module can be registered as a plugin. It can implement any number of hook functions (usually two or three) which all have a ``pytest_`` prefix, making hook functions easy to distinguish and find. There are three basic location types:
|
||||
|
||||
* `builtin plugins`_: loaded from py.test's own ``pytest/plugin`` directory.
|
||||
* `builtin plugins`_: loaded from py.test's internal ``_pytest`` directory.
|
||||
* `external plugins`_: modules discovered through `setuptools entry points`_
|
||||
* `conftest.py plugins`_: modules auto-discovered in test directories
|
||||
|
||||
@@ -71,6 +71,10 @@ there is no need to activate it. Here is a initial list of known plugins:
|
||||
* `pytest-django <http://pypi.python.org/pypi/pytest-django>`_: write tests
|
||||
for `django`_ apps, using pytest integration.
|
||||
|
||||
* `pytest-twisted <http://pypi.python.org/pypi/pytest-twisted>`_: write tests
|
||||
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||
processing deferreds from test functions.
|
||||
|
||||
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
|
||||
to capture and assert about messages from the logging module
|
||||
|
||||
@@ -151,6 +155,8 @@ If a package is installed this way, py.test will load
|
||||
``myproject.pluginmodule`` as a plugin which can define
|
||||
`well specified hooks`_.
|
||||
|
||||
.. _`pluginorder`:
|
||||
|
||||
Plugin discovery order at tool startup
|
||||
--------------------------------------------
|
||||
|
||||
@@ -171,6 +177,7 @@ py.test loads plugin modules at tool startup in the following way:
|
||||
* by recursively loading all plugins specified by the
|
||||
``pytest_plugins`` variable in ``conftest.py`` files
|
||||
|
||||
|
||||
Requiring/Loading plugins in a test module or conftest file
|
||||
-------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -132,7 +132,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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 6 items
|
||||
|
||||
xfail_demo.py xxxxxx
|
||||
@@ -149,7 +149,7 @@ Running it with the report-on-xfail option gives this output::
|
||||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
|
||||
======================== 6 xfailed in 0.03 seconds =========================
|
||||
======================== 6 xfailed in 0.05 seconds =========================
|
||||
|
||||
.. _`evaluation of skipif/xfail conditions`:
|
||||
|
||||
|
||||
@@ -29,7 +29,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.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 1 items
|
||||
|
||||
test_tmpdir.py F
|
||||
@@ -37,7 +37,7 @@ Running this would result in a passed test except for the last
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_create_file _____________________________
|
||||
|
||||
tmpdir = local('/tmp/pytest-1517/test_create_file0')
|
||||
tmpdir = local('/tmp/pytest-595/test_create_file0')
|
||||
|
||||
def test_create_file(tmpdir):
|
||||
p = tmpdir.mkdir("sub").join("hello.txt")
|
||||
@@ -48,7 +48,7 @@ Running this would result in a passed test except for the last
|
||||
E assert 0
|
||||
|
||||
test_tmpdir.py:7: AssertionError
|
||||
========================= 1 failed in 0.04 seconds =========================
|
||||
========================= 1 failed in 0.03 seconds =========================
|
||||
|
||||
.. _`base temporary directory`:
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ the ``self.db`` values in the traceback::
|
||||
|
||||
$ py.test test_unittest_db.py
|
||||
=========================== test session starts ============================
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.1
|
||||
platform linux2 -- Python 2.7.3 -- pytest-2.3.3
|
||||
collected 2 items
|
||||
|
||||
test_unittest_db.py FF
|
||||
@@ -101,7 +101,7 @@ the ``self.db`` values in the traceback::
|
||||
def test_method1(self):
|
||||
assert hasattr(self, "db")
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.DummyDB instance at 0x25b1998>
|
||||
E AssertionError: <conftest.DummyDB instance at 0x269e5a8>
|
||||
|
||||
test_unittest_db.py:9: AssertionError
|
||||
___________________________ MyTest.test_method2 ____________________________
|
||||
@@ -110,7 +110,7 @@ the ``self.db`` values in the traceback::
|
||||
|
||||
def test_method2(self):
|
||||
> assert 0, self.db # fail for demo purposes
|
||||
E AssertionError: <conftest.DummyDB instance at 0x25b1998>
|
||||
E AssertionError: <conftest.DummyDB instance at 0x269e5a8>
|
||||
|
||||
test_unittest_db.py:12: AssertionError
|
||||
========================= 2 failed in 0.02 seconds =========================
|
||||
|
||||
@@ -5,13 +5,16 @@
|
||||
classic xunit-style setup
|
||||
========================================
|
||||
|
||||
.. note::
|
||||
|
||||
This section describes the classic way how you can implement setup and
|
||||
teardown on a per-module/class/function basis. It remains fully
|
||||
supported but it is recommended to rather use the more flexible,
|
||||
more modular and more scalable :ref:`fixture functions
|
||||
<fixture>` for implementing for fixing test state for your tests.
|
||||
This section describes a classic and popular way how you can implement
|
||||
fixtures (setup and teardown test state) on a per-module/class/function basis.
|
||||
pytest started supporting these methods around 2005 and subsequently
|
||||
nose and the standard library introduced them (under slightly different
|
||||
names). While these setup/teardown methods are and will remain fully
|
||||
supported you may also use pytest's more powerful :ref:`fixture mechanism
|
||||
<fixture>` which leverages the concept of dependency injection, allowing
|
||||
for a more modular and more scalable approach for managing test state,
|
||||
especially for larger projects and for functional testing. It is safe
|
||||
to mix both fixture mechanisms.
|
||||
|
||||
Module level setup/teardown
|
||||
--------------------------------------
|
||||
|
||||
64
setup.py
64
setup.py
@@ -1,21 +1,45 @@
|
||||
import os, sys
|
||||
try:
|
||||
from setuptools import setup
|
||||
from setuptools import setup, Command
|
||||
except ImportError:
|
||||
from distribute_setup import use_setuptools
|
||||
use_setuptools()
|
||||
from setuptools import setup
|
||||
from setuptools import setup, Command
|
||||
|
||||
long_description = """
|
||||
cross-project testing tool for Python.
|
||||
The `py.test`` testing tool makes it easy to write small tests, yet
|
||||
scales to support complex functional testing. It provides
|
||||
|
||||
Platforms: Linux, Win32, OSX
|
||||
- `auto-discovery
|
||||
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
|
||||
of test modules and functions,
|
||||
- detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names)
|
||||
- `modular fixtures <http://pytest.org/latest/fixture.html>`_ for
|
||||
managing small or parametrized long-lived test resources.
|
||||
- multi-paradigm support: you can use ``py.test`` to run test suites based
|
||||
on `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
|
||||
`nose <http://pytest.org/latest/nose.html>`_
|
||||
- single-source compatibility to Python2.4 all the way up to Python3.3,
|
||||
PyPy and Jython.
|
||||
|
||||
Interpreters: Python versions 2.4 through to 3.3, Jython 2.5.1 and PyPy-1.9
|
||||
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
|
||||
|
||||
Bugs and issues: http://bitbucket.org/hpk42/pytest/issues/
|
||||
A simple example for a test::
|
||||
|
||||
Web page: http://pytest.org
|
||||
# content of test_module.py
|
||||
def test_function():
|
||||
i = 4
|
||||
assert i == 3
|
||||
|
||||
which can be run with ``py.test test_module.py``. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
|
||||
|
||||
For much more info, including PDF docs, see
|
||||
|
||||
http://pytest.org
|
||||
|
||||
and report bugs at:
|
||||
|
||||
http://bitbucket.org/hpk42/pytest/issues/
|
||||
|
||||
(c) Holger Krekel and others, 2004-2012
|
||||
"""
|
||||
@@ -24,15 +48,16 @@ def main():
|
||||
name='pytest',
|
||||
description='py.test: simple powerful testing with Python',
|
||||
long_description = long_description,
|
||||
version='2.3.1',
|
||||
version='2.3.3',
|
||||
url='http://pytest.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
author='Holger Krekel, Benjamin Peterson, Ronny Pfannschmidt, Floris Bruynooghe and others',
|
||||
author_email='holger at merlinux.eu',
|
||||
entry_points= make_entry_points(),
|
||||
cmdclass = {'test': PyTest},
|
||||
# the following should be enabled for release
|
||||
install_requires=['py>=1.4.10'],
|
||||
install_requires=['py>=1.4.12'],
|
||||
classifiers=['Development Status :: 6 - Mature',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
@@ -42,8 +67,10 @@ def main():
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3'],
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 3'] + [
|
||||
("Programming Language :: Python :: %s" % x) for x in
|
||||
"2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split()],
|
||||
packages=['_pytest', '_pytest.assertion'],
|
||||
py_modules=['pytest'],
|
||||
zip_safe=False,
|
||||
@@ -69,5 +96,20 @@ def make_entry_points():
|
||||
l = ["%s = %s" % (x, points[x]) for x in keys]
|
||||
return {'console_scripts': l}
|
||||
|
||||
|
||||
class PyTest(Command):
|
||||
user_options = []
|
||||
def initialize_options(self):
|
||||
pass
|
||||
def finalize_options(self):
|
||||
pass
|
||||
def run(self):
|
||||
import sys,subprocess
|
||||
PPATH=[x for x in os.environ.get("PYTHONPATH", "").split(":") if x]
|
||||
PPATH.insert(0, os.getcwd())
|
||||
os.environ["PYTHONPATH"] = ":".join(PPATH)
|
||||
errno = subprocess.call([sys.executable, 'pytest.py'])
|
||||
raise SystemExit(errno)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
721
testing/python/collect.py
Normal file
721
testing/python/collect.py
Normal file
@@ -0,0 +1,721 @@
|
||||
import pytest, py, sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.python import FixtureLookupError
|
||||
|
||||
class TestModule:
|
||||
def test_failing_import(self, testdir):
|
||||
modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
|
||||
pytest.raises(ImportError, modcol.collect)
|
||||
pytest.raises(ImportError, modcol.collect)
|
||||
|
||||
def test_import_duplicate(self, testdir):
|
||||
a = testdir.mkdir("a")
|
||||
b = testdir.mkdir("b")
|
||||
p = a.ensure("test_whatever.py")
|
||||
p.pyimport()
|
||||
del py.std.sys.modules['test_whatever']
|
||||
b.ensure("test_whatever.py")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*import*mismatch*",
|
||||
"*imported*test_whatever*",
|
||||
"*%s*" % a.join("test_whatever.py"),
|
||||
"*not the same*",
|
||||
"*%s*" % b.join("test_whatever.py"),
|
||||
"*HINT*",
|
||||
])
|
||||
|
||||
def test_syntax_error_in_module(self, testdir):
|
||||
modcol = testdir.getmodulecol("this is a syntax error")
|
||||
pytest.raises(modcol.CollectError, modcol.collect)
|
||||
pytest.raises(modcol.CollectError, modcol.collect)
|
||||
|
||||
def test_module_considers_pluginmanager_at_import(self, testdir):
|
||||
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
|
||||
pytest.raises(ImportError, "modcol.obj")
|
||||
|
||||
class TestClass:
|
||||
def test_class_with_init_not_collected(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
class TestClass1:
|
||||
def __init__(self):
|
||||
pass
|
||||
class TestClass2(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
""")
|
||||
l = modcol.collect()
|
||||
assert len(l) == 0
|
||||
|
||||
def test_class_subclassobject(self, testdir):
|
||||
testdir.getmodulecol("""
|
||||
class test(object):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*collected 0*",
|
||||
])
|
||||
|
||||
def test_setup_teardown_class_as_classmethod(self, testdir):
|
||||
testdir.makepyfile(test_mod1="""
|
||||
class TestClassMethod:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
pass
|
||||
def test_1(self):
|
||||
pass
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
||||
class TestGenerator:
|
||||
def test_generative_functions(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
|
||||
def test_gen():
|
||||
yield func1, 17, 3*5
|
||||
yield func1, 42, 6*7
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == '[0]'
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_methods(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
class TestGenMethods:
|
||||
def test_gen(self):
|
||||
yield func1, 17, 3*5
|
||||
yield func1, 42, 6*7
|
||||
""")
|
||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == '[0]'
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_functions_with_explicit_names(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
|
||||
def test_gen():
|
||||
yield "seventeen", func1, 17, 3*5
|
||||
yield "fortytwo", func1, 42, 6*7
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == "['seventeen']"
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
assert gencolitems[1].name == "['fortytwo']"
|
||||
assert gencolitems[1].obj.__name__ == 'func1'
|
||||
|
||||
def test_generative_functions_unique_explicit_names(self, testdir):
|
||||
# generative
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func(): pass
|
||||
def test_gen():
|
||||
yield "name", func
|
||||
yield "name", func
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 1
|
||||
gencol = colitems[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
pytest.raises(ValueError, "gencol.collect()")
|
||||
|
||||
def test_generative_methods_with_explicit_names(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def func1(arg, arg2):
|
||||
assert arg == arg2
|
||||
class TestGenMethods:
|
||||
def test_gen(self):
|
||||
yield "m1", func1, 17, 3*5
|
||||
yield "m2", func1, 42, 6*7
|
||||
""")
|
||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||
assert isinstance(gencol, pytest.Generator)
|
||||
gencolitems = gencol.collect()
|
||||
assert len(gencolitems) == 2
|
||||
assert isinstance(gencolitems[0], pytest.Function)
|
||||
assert isinstance(gencolitems[1], pytest.Function)
|
||||
assert gencolitems[0].name == "['m1']"
|
||||
assert gencolitems[0].obj.__name__ == 'func1'
|
||||
assert gencolitems[1].name == "['m2']"
|
||||
assert gencolitems[1].obj.__name__ == 'func1'
|
||||
|
||||
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
|
||||
o = testdir.makepyfile("""
|
||||
def test_generative_order_of_execution():
|
||||
import py, pytest
|
||||
test_list = []
|
||||
expected_list = list(range(6))
|
||||
|
||||
def list_append(item):
|
||||
test_list.append(item)
|
||||
|
||||
def assert_order_of_execution():
|
||||
py.builtin.print_('expected order', expected_list)
|
||||
py.builtin.print_('but got ', test_list)
|
||||
assert test_list == expected_list
|
||||
|
||||
for i in expected_list:
|
||||
yield list_append, i
|
||||
yield assert_order_of_execution
|
||||
""")
|
||||
reprec = testdir.inline_run(o)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 7
|
||||
assert not skipped and not failed
|
||||
|
||||
def test_order_of_execution_generator_different_codeline(self, testdir):
|
||||
o = testdir.makepyfile("""
|
||||
def test_generative_tests_different_codeline():
|
||||
import py, pytest
|
||||
test_list = []
|
||||
expected_list = list(range(3))
|
||||
|
||||
def list_append_2():
|
||||
test_list.append(2)
|
||||
|
||||
def list_append_1():
|
||||
test_list.append(1)
|
||||
|
||||
def list_append_0():
|
||||
test_list.append(0)
|
||||
|
||||
def assert_order_of_execution():
|
||||
py.builtin.print_('expected order', expected_list)
|
||||
py.builtin.print_('but got ', test_list)
|
||||
assert test_list == expected_list
|
||||
|
||||
yield list_append_0
|
||||
yield list_append_1
|
||||
yield list_append_2
|
||||
yield assert_order_of_execution
|
||||
""")
|
||||
reprec = testdir.inline_run(o)
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 4
|
||||
assert not skipped and not failed
|
||||
|
||||
def test_setupstate_is_preserved_134(self, testdir):
|
||||
# yield-based tests are messy wrt to setupstate because
|
||||
# during collection they already invoke setup functions
|
||||
# and then again when they are run. For now, we want to make sure
|
||||
# that the old 1.3.4 behaviour is preserved such that all
|
||||
# yielded functions all share the same "self" instance that
|
||||
# has been used during collection.
|
||||
o = testdir.makepyfile("""
|
||||
setuplist = []
|
||||
class TestClass:
|
||||
def setup_method(self, func):
|
||||
#print "setup_method", self, func
|
||||
setuplist.append(self)
|
||||
self.init = 42
|
||||
|
||||
def teardown_method(self, func):
|
||||
self.init = None
|
||||
|
||||
def test_func1(self):
|
||||
pass
|
||||
|
||||
def test_func2(self):
|
||||
yield self.func2
|
||||
yield self.func2
|
||||
|
||||
def func2(self):
|
||||
assert self.init
|
||||
|
||||
def test_setuplist():
|
||||
# once for test_func2 during collection
|
||||
# once for test_func1 during test run
|
||||
# once for test_func2 during test run
|
||||
#print setuplist
|
||||
assert len(setuplist) == 3, len(setuplist)
|
||||
assert setuplist[0] == setuplist[2], setuplist
|
||||
assert setuplist[1] != setuplist[2], setuplist
|
||||
""")
|
||||
reprec = testdir.inline_run(o, '-v')
|
||||
passed, skipped, failed = reprec.countoutcomes()
|
||||
assert passed == 4
|
||||
assert not skipped and not failed
|
||||
|
||||
|
||||
class TestFunction:
|
||||
def test_getmodulecollector(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
modcol = item.getparent(pytest.Module)
|
||||
assert isinstance(modcol, pytest.Module)
|
||||
assert hasattr(modcol.obj, 'test_func')
|
||||
|
||||
def test_function_equality(self, testdir, tmpdir):
|
||||
from _pytest.python import FixtureManager
|
||||
config = testdir.parseconfigure()
|
||||
session = testdir.Session(config)
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
def func1():
|
||||
pass
|
||||
def func2():
|
||||
pass
|
||||
f1 = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,), callobj=func1)
|
||||
f2 = pytest.Function(name="name",config=config,
|
||||
args=(1,), callobj=func2, parent=session)
|
||||
assert not f1 == f2
|
||||
assert f1 != f2
|
||||
f3 = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,2), callobj=func2)
|
||||
assert not f3 == f2
|
||||
assert f3 != f2
|
||||
|
||||
assert not f3 == f1
|
||||
assert f3 != f1
|
||||
|
||||
f1_b = pytest.Function(name="name", parent=session, config=config,
|
||||
args=(1,), callobj=func1)
|
||||
assert f1 == f1_b
|
||||
assert not f1 != f1_b
|
||||
|
||||
def test_issue197_parametrize_emptyset(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.mark.parametrize('arg', [])
|
||||
def test_function(arg):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(skipped=1)
|
||||
|
||||
def test_issue213_parametrize_value_no_equal(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
class A:
|
||||
def __eq__(self, other):
|
||||
raise ValueError("not possible")
|
||||
@pytest.mark.parametrize('arg', [A()])
|
||||
def test_function(arg):
|
||||
assert arg.__class__.__name__ == "A"
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_function_equality_with_callspec(self, testdir, tmpdir):
|
||||
items = testdir.getitems("""
|
||||
import pytest
|
||||
@pytest.mark.parametrize('arg', [1,2])
|
||||
def test_function(arg):
|
||||
pass
|
||||
""")
|
||||
assert items[0] != items[1]
|
||||
assert not (items[0] == items[1])
|
||||
|
||||
def test_pyfunc_call(self, testdir):
|
||||
item = testdir.getitem("def test_func(): raise ValueError")
|
||||
config = item.config
|
||||
class MyPlugin1:
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
raise ValueError
|
||||
class MyPlugin2:
|
||||
def pytest_pyfunc_call(self, pyfuncitem):
|
||||
return True
|
||||
config.pluginmanager.register(MyPlugin1())
|
||||
config.pluginmanager.register(MyPlugin2())
|
||||
config.hook.pytest_pyfunc_call(pyfuncitem=item)
|
||||
|
||||
class TestSorting:
|
||||
def test_check_equality(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def test_pass(): pass
|
||||
def test_fail(): assert 0
|
||||
""")
|
||||
fn1 = testdir.collect_by_name(modcol, "test_pass")
|
||||
assert isinstance(fn1, pytest.Function)
|
||||
fn2 = testdir.collect_by_name(modcol, "test_pass")
|
||||
assert isinstance(fn2, pytest.Function)
|
||||
|
||||
assert fn1 == fn2
|
||||
assert fn1 != modcol
|
||||
if py.std.sys.version_info < (3, 0):
|
||||
assert cmp(fn1, fn2) == 0
|
||||
assert hash(fn1) == hash(fn2)
|
||||
|
||||
fn3 = testdir.collect_by_name(modcol, "test_fail")
|
||||
assert isinstance(fn3, pytest.Function)
|
||||
assert not (fn1 == fn3)
|
||||
assert fn1 != fn3
|
||||
|
||||
for fn in fn1,fn2,fn3:
|
||||
assert fn != 3
|
||||
assert fn != modcol
|
||||
assert fn != [1,2,3]
|
||||
assert [1,2,3] != fn
|
||||
assert modcol != fn
|
||||
|
||||
def test_allow_sane_sorting_for_decorators(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
def dec(f):
|
||||
g = lambda: f(2)
|
||||
g.place_as = f
|
||||
return g
|
||||
|
||||
|
||||
def test_b(y):
|
||||
pass
|
||||
test_b = dec(test_b)
|
||||
|
||||
def test_a(y):
|
||||
pass
|
||||
test_a = dec(test_a)
|
||||
""")
|
||||
colitems = modcol.collect()
|
||||
assert len(colitems) == 2
|
||||
assert [item.name for item in colitems] == ['test_b', 'test_a']
|
||||
|
||||
|
||||
class TestConftestCustomization:
|
||||
def test_pytest_pycollect_module(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyModule(pytest.Module):
|
||||
pass
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
if path.basename == "test_xyz.py":
|
||||
return MyModule(path, parent)
|
||||
""")
|
||||
testdir.makepyfile("def test_some(): pass")
|
||||
testdir.makepyfile(test_xyz="def test_func(): pass")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*<Module*test_pytest*",
|
||||
"*<MyModule*xyz*",
|
||||
])
|
||||
|
||||
def test_customized_pymakemodule_issue205_subdir(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_pycollect_makemodule(__multicall__):
|
||||
mod = __multicall__.execute()
|
||||
mod.obj.hello = "world"
|
||||
return mod
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
def test_hello():
|
||||
assert hello == "world"
|
||||
"""))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_customized_pymakeitem(self, testdir):
|
||||
b = testdir.mkdir("a").mkdir("b")
|
||||
b.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_pycollect_makeitem(__multicall__):
|
||||
result = __multicall__.execute()
|
||||
if result:
|
||||
for func in result:
|
||||
func._some123 = "world"
|
||||
return result
|
||||
"""))
|
||||
b.join("test_module.py").write(py.code.Source("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture()
|
||||
def obj(request):
|
||||
return request.node._some123
|
||||
def test_hello(obj):
|
||||
assert obj == "world"
|
||||
"""))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_pytest_pycollect_makeitem(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
pass
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "some":
|
||||
return MyFunction(name, collector)
|
||||
""")
|
||||
testdir.makepyfile("def some(): pass")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*MyFunction*some*",
|
||||
])
|
||||
|
||||
def test_makeitem_non_underscore(self, testdir, monkeypatch):
|
||||
modcol = testdir.getmodulecol("def _hello(): pass")
|
||||
l = []
|
||||
monkeypatch.setattr(pytest.Module, 'makeitem',
|
||||
lambda self, name, obj: l.append(name))
|
||||
l = modcol.collect()
|
||||
assert '_hello' not in l
|
||||
|
||||
def test_setup_only_available_in_subdir(testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
def pytest_runtest_call(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
def pytest_runtest_teardown(item):
|
||||
assert item.fspath.purebasename == "test_in_sub1"
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
import pytest
|
||||
def pytest_runtest_setup(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
def pytest_runtest_call(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
def pytest_runtest_teardown(item):
|
||||
assert item.fspath.purebasename == "test_in_sub2"
|
||||
"""))
|
||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||
result = testdir.runpytest("-v", "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*"
|
||||
])
|
||||
|
||||
def test_modulecol_roundtrip(testdir):
|
||||
modcol = testdir.getmodulecol("pass", withinit=True)
|
||||
trail = modcol.nodeid
|
||||
newcol = modcol.session.perform_collect([trail], genitems=0)[0]
|
||||
assert modcol.name == newcol.name
|
||||
|
||||
|
||||
class TestTracebackCutting:
|
||||
def test_skip_simple(self):
|
||||
excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")')
|
||||
assert excinfo.traceback[-1].frame.code.name == "skip"
|
||||
assert excinfo.traceback[-1].ishidden()
|
||||
|
||||
def test_traceback_argsetup(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_funcarg__hello(request):
|
||||
raise ValueError("xyz")
|
||||
""")
|
||||
p = testdir.makepyfile("def test(hello): pass")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.ret != 0
|
||||
out = result.stdout.str()
|
||||
assert out.find("xyz") != -1
|
||||
assert out.find("conftest.py:2: ValueError") != -1
|
||||
numentries = out.count("_ _ _") # separator for traceback entries
|
||||
assert numentries == 0
|
||||
|
||||
result = testdir.runpytest("--fulltrace", p)
|
||||
out = result.stdout.str()
|
||||
assert out.find("conftest.py:2: ValueError") != -1
|
||||
numentries = out.count("_ _ _ _") # separator for traceback entries
|
||||
assert numentries > 3
|
||||
|
||||
def test_traceback_error_during_import(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
x = 1
|
||||
x = 2
|
||||
x = 17
|
||||
asd
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
out = result.stdout.str()
|
||||
assert "x = 1" not in out
|
||||
assert "x = 2" not in out
|
||||
result.stdout.fnmatch_lines([
|
||||
">*asd*",
|
||||
"E*NameError*",
|
||||
])
|
||||
result = testdir.runpytest("--fulltrace")
|
||||
out = result.stdout.str()
|
||||
assert "x = 1" in out
|
||||
assert "x = 2" in out
|
||||
result.stdout.fnmatch_lines([
|
||||
">*asd*",
|
||||
"E*NameError*",
|
||||
])
|
||||
|
||||
class TestReportInfo:
|
||||
def test_itemreport_reportinfo(self, testdir, linecomp):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
def reportinfo(self):
|
||||
return "ABCDE", 42, "custom"
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "test_func":
|
||||
return MyFunction(name, parent=collector)
|
||||
""")
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
runner = item.config.pluginmanager.getplugin("runner")
|
||||
assert item.location == ("ABCDE", 42, "custom")
|
||||
|
||||
def test_func_reportinfo(self, testdir):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
fspath, lineno, modpath = item.reportinfo()
|
||||
assert fspath == item.fspath
|
||||
assert lineno == 0
|
||||
assert modpath == "test_func"
|
||||
|
||||
def test_class_reportinfo(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
# lineno 0
|
||||
class TestClass:
|
||||
def test_hello(self): pass
|
||||
""")
|
||||
classcol = testdir.collect_by_name(modcol, "TestClass")
|
||||
fspath, lineno, msg = classcol.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 1
|
||||
assert msg == "TestClass"
|
||||
|
||||
def test_generator_reportinfo(self, testdir):
|
||||
modcol = testdir.getmodulecol("""
|
||||
# lineno 0
|
||||
def test_gen():
|
||||
def check(x):
|
||||
assert x
|
||||
yield check, 3
|
||||
""")
|
||||
gencol = testdir.collect_by_name(modcol, "test_gen")
|
||||
fspath, lineno, modpath = gencol.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 1
|
||||
assert modpath == "test_gen"
|
||||
|
||||
genitem = gencol.collect()[0]
|
||||
fspath, lineno, modpath = genitem.reportinfo()
|
||||
assert fspath == modcol.fspath
|
||||
assert lineno == 2
|
||||
assert modpath == "test_gen[0]"
|
||||
"""
|
||||
def test_func():
|
||||
pass
|
||||
def test_genfunc():
|
||||
def check(x):
|
||||
pass
|
||||
yield check, 3
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
|
||||
def test_customized_python_discovery(testdir):
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
python_files=check_*.py
|
||||
python_classes=Check
|
||||
python_functions=check
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def check_simple():
|
||||
pass
|
||||
class CheckMyApp:
|
||||
def check_meth(self):
|
||||
pass
|
||||
""")
|
||||
p2 = p.new(basename=p.basename.replace("test", "check"))
|
||||
p.move(p2)
|
||||
result = testdir.runpytest("--collectonly", "-s")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*check_customized*",
|
||||
"*check_simple*",
|
||||
"*CheckMyApp*",
|
||||
"*check_meth*",
|
||||
])
|
||||
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
def test_collector_attributes(testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector):
|
||||
assert collector.Function == pytest.Function
|
||||
assert collector.Class == pytest.Class
|
||||
assert collector.Instance == pytest.Instance
|
||||
assert collector.Module == pytest.Module
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
def test_customize_through_attributes(testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
class MyFunction(pytest.Function):
|
||||
pass
|
||||
class MyInstance(pytest.Instance):
|
||||
Function = MyFunction
|
||||
class MyClass(pytest.Class):
|
||||
Instance = MyInstance
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name.startswith("MyTestClass"):
|
||||
return MyClass(name, parent=collector)
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
class MyTestClass:
|
||||
def test_hello(self):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*MyClass*",
|
||||
"*MyInstance*",
|
||||
"*MyFunction*test_hello*",
|
||||
])
|
||||
|
||||
|
||||
def test_unorderable_types(testdir):
|
||||
testdir.makepyfile("""
|
||||
class TestJoinEmpty:
|
||||
pass
|
||||
|
||||
def make_test():
|
||||
class Test:
|
||||
pass
|
||||
Test.__name__ = "TestFoo"
|
||||
return Test
|
||||
TestFoo = make_test()
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert "TypeError" not in result.stdout.str()
|
||||
assert result.ret == 0
|
||||
File diff suppressed because it is too large
Load Diff
119
testing/python/integration.py
Normal file
119
testing/python/integration.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import pytest, py, sys
|
||||
|
||||
class TestOEJSKITSpecials:
|
||||
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "MyClass":
|
||||
return MyCollector(name, parent=collector)
|
||||
class MyCollector(pytest.Collector):
|
||||
def reportinfo(self):
|
||||
return self.fspath, 3, "xyz"
|
||||
""")
|
||||
modcol = testdir.getmodulecol("""
|
||||
def pytest_funcarg__arg1(request):
|
||||
return 42
|
||||
class MyClass:
|
||||
pass
|
||||
""")
|
||||
# this hook finds funcarg factories
|
||||
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||
clscol = rep.result[0]
|
||||
clscol.obj = lambda arg1: None
|
||||
clscol.funcargs = {}
|
||||
pytest._fillfuncargs(clscol)
|
||||
assert clscol.funcargs['arg1'] == 42
|
||||
|
||||
def test_autouse_fixture(self, testdir): # rough jstests usage
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if name == "MyClass":
|
||||
return MyCollector(name, parent=collector)
|
||||
class MyCollector(pytest.Collector):
|
||||
def reportinfo(self):
|
||||
return self.fspath, 3, "xyz"
|
||||
""")
|
||||
modcol = testdir.getmodulecol("""
|
||||
import pytest
|
||||
@pytest.fixture(autouse=True)
|
||||
def hello():
|
||||
pass
|
||||
def pytest_funcarg__arg1(request):
|
||||
return 42
|
||||
class MyClass:
|
||||
pass
|
||||
""")
|
||||
# this hook finds funcarg factories
|
||||
rep = modcol.ihook.pytest_make_collect_report(collector=modcol)
|
||||
clscol = rep.result[0]
|
||||
clscol.obj = lambda: None
|
||||
clscol.funcargs = {}
|
||||
pytest._fillfuncargs(clscol)
|
||||
assert not clscol.funcargs
|
||||
|
||||
|
||||
class TestMockDecoration:
|
||||
def test_wrapped_getfuncargnames(self):
|
||||
from _pytest.python import getfuncargnames
|
||||
def wrap(f):
|
||||
def func():
|
||||
pass
|
||||
func.__wrapped__ = f
|
||||
return func
|
||||
@wrap
|
||||
def f(x):
|
||||
pass
|
||||
l = getfuncargnames(f)
|
||||
assert l == ("x",)
|
||||
|
||||
def test_wrapped_getfuncargnames_patching(self):
|
||||
from _pytest.python import getfuncargnames
|
||||
def wrap(f):
|
||||
def func():
|
||||
pass
|
||||
func.__wrapped__ = f
|
||||
func.patchings = ["qwe"]
|
||||
return func
|
||||
@wrap
|
||||
def f(x, y, z):
|
||||
pass
|
||||
l = getfuncargnames(f)
|
||||
assert l == ("y", "z")
|
||||
|
||||
def test_unittest_mock(self, testdir):
|
||||
pytest.importorskip("unittest.mock")
|
||||
testdir.makepyfile("""
|
||||
import unittest.mock
|
||||
class T(unittest.TestCase):
|
||||
@unittest.mock.patch("os.path.abspath")
|
||||
def test_hello(self, abspath):
|
||||
import os
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_mock(self, testdir):
|
||||
pytest.importorskip("mock", "1.0.1")
|
||||
testdir.makepyfile("""
|
||||
import os
|
||||
import unittest
|
||||
import mock
|
||||
|
||||
class T(unittest.TestCase):
|
||||
@mock.patch("os.path.abspath")
|
||||
def test_hello(self, abspath):
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
@mock.patch("os.path.abspath")
|
||||
@mock.patch("os.path.normpath")
|
||||
def test_someting(normpath, abspath, tmpdir):
|
||||
abspath.return_value = "this"
|
||||
os.path.normpath(os.path.abspath("hello"))
|
||||
normpath.assert_any_call("this")
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=2)
|
||||
575
testing/python/metafunc.py
Normal file
575
testing/python/metafunc.py
Normal file
@@ -0,0 +1,575 @@
|
||||
import pytest, py, sys
|
||||
from _pytest import python as funcargs
|
||||
from _pytest.python import FixtureLookupError
|
||||
|
||||
class TestMetafunc:
|
||||
def Metafunc(self, func):
|
||||
# the unit tests of this class check if things work correctly
|
||||
# on the funcarg level, so we don't need a full blown
|
||||
# initiliazation
|
||||
class FixtureInfo:
|
||||
name2fixturedefs = None
|
||||
def __init__(self, names):
|
||||
self.names_closure = names
|
||||
names = funcargs.getfuncargnames(func)
|
||||
fixtureinfo = FixtureInfo(names)
|
||||
return funcargs.Metafunc(func, fixtureinfo, None)
|
||||
|
||||
def test_no_funcargs(self, testdir):
|
||||
def function(): pass
|
||||
metafunc = self.Metafunc(function)
|
||||
assert not metafunc.fixturenames
|
||||
repr(metafunc._calls)
|
||||
|
||||
def test_function_basic(self):
|
||||
def func(arg1, arg2="qwe"): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
assert len(metafunc.fixturenames) == 1
|
||||
assert 'arg1' in metafunc.fixturenames
|
||||
assert metafunc.function is func
|
||||
assert metafunc.cls is None
|
||||
|
||||
def test_addcall_no_args(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall()
|
||||
assert len(metafunc._calls) == 1
|
||||
call = metafunc._calls[0]
|
||||
assert call.id == "0"
|
||||
assert not hasattr(call, 'param')
|
||||
|
||||
def test_addcall_id(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
pytest.raises(ValueError, "metafunc.addcall(id=None)")
|
||||
|
||||
metafunc.addcall(id=1)
|
||||
pytest.raises(ValueError, "metafunc.addcall(id=1)")
|
||||
pytest.raises(ValueError, "metafunc.addcall(id='1')")
|
||||
metafunc.addcall(id=2)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_addcall_param(self):
|
||||
def func(arg1): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class obj: pass
|
||||
metafunc.addcall(param=obj)
|
||||
metafunc.addcall(param=obj)
|
||||
metafunc.addcall(param=1)
|
||||
assert len(metafunc._calls) == 3
|
||||
assert metafunc._calls[0].getparam("arg1") == obj
|
||||
assert metafunc._calls[1].getparam("arg1") == obj
|
||||
assert metafunc._calls[2].getparam("arg1") == 1
|
||||
|
||||
def test_addcall_funcargs(self):
|
||||
def func(x): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class obj: pass
|
||||
metafunc.addcall(funcargs={"x": 2})
|
||||
metafunc.addcall(funcargs={"x": 3})
|
||||
pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {'x': 2}
|
||||
assert metafunc._calls[1].funcargs == {'x': 3}
|
||||
assert not hasattr(metafunc._calls[1], 'param')
|
||||
|
||||
def test_parametrize_error(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.parametrize("x", [1,2])
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
|
||||
metafunc.parametrize("y", [1,2])
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||
|
||||
def test_parametrize_and_id(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
|
||||
metafunc.parametrize("x", [1,2], ids=['basic', 'advanced'])
|
||||
metafunc.parametrize("y", ["abc", "def"])
|
||||
ids = [x.id for x in metafunc._calls]
|
||||
assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
|
||||
|
||||
def test_parametrize_with_userobjects(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
class A:
|
||||
pass
|
||||
metafunc.parametrize("x", [A(), A()])
|
||||
metafunc.parametrize("y", list("ab"))
|
||||
assert metafunc._calls[0].id == "x0-a"
|
||||
assert metafunc._calls[1].id == "x0-b"
|
||||
assert metafunc._calls[2].id == "x1-a"
|
||||
assert metafunc._calls[3].id == "x1-b"
|
||||
|
||||
def test_idmaker_autoname(self):
|
||||
from _pytest.python import idmaker
|
||||
result = idmaker(("a", "b"), [("string", 1.0),
|
||||
("st-ring", 2.0)])
|
||||
assert result == ["string-1.0", "st-ring-2.0"]
|
||||
|
||||
result = idmaker(("a", "b"), [(object(), 1.0),
|
||||
(object(), object())])
|
||||
assert result == ["a0-1.0", "a1-b1"]
|
||||
|
||||
|
||||
def test_addcall_and_parametrize(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall({'x': 1})
|
||||
metafunc.parametrize('y', [2,3])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2}
|
||||
assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3}
|
||||
assert metafunc._calls[0].id == "0-2"
|
||||
assert metafunc._calls[1].id == "0-3"
|
||||
|
||||
def test_parametrize_indirect(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.parametrize('x', [1], indirect=True)
|
||||
metafunc.parametrize('y', [2,3], indirect=True)
|
||||
metafunc.parametrize('unnamed', [1], indirect=True)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {}
|
||||
assert metafunc._calls[1].funcargs == {}
|
||||
assert metafunc._calls[0].params == dict(x=1,y=2, unnamed=1)
|
||||
assert metafunc._calls[1].params == dict(x=1,y=3, unnamed=1)
|
||||
|
||||
def test_addcalls_and_parametrize_indirect(self):
|
||||
def func(x, y): pass
|
||||
metafunc = self.Metafunc(func)
|
||||
metafunc.addcall(param="123")
|
||||
metafunc.parametrize('x', [1], indirect=True)
|
||||
metafunc.parametrize('y', [2,3], indirect=True)
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == {}
|
||||
assert metafunc._calls[1].funcargs == {}
|
||||
assert metafunc._calls[0].params == dict(x=1,y=2)
|
||||
assert metafunc._calls[1].params == dict(x=1,y=3)
|
||||
|
||||
def test_parametrize_functional(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize('x', [1,2], indirect=True)
|
||||
metafunc.parametrize('y', [2])
|
||||
def pytest_funcarg__x(request):
|
||||
return request.param * 10
|
||||
def pytest_funcarg__y(request):
|
||||
return request.param
|
||||
|
||||
def test_simple(x,y):
|
||||
assert x in (10,20)
|
||||
assert y == 2
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_simple*1-2*",
|
||||
"*test_simple*2-2*",
|
||||
"*2 passed*",
|
||||
])
|
||||
|
||||
def test_parametrize_onearg(self):
|
||||
metafunc = self.Metafunc(lambda x: None)
|
||||
metafunc.parametrize("x", [1,2])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == dict(x=1)
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].funcargs == dict(x=2)
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_parametrize_onearg_indirect(self):
|
||||
metafunc = self.Metafunc(lambda x: None)
|
||||
metafunc.parametrize("x", [1,2], indirect=True)
|
||||
assert metafunc._calls[0].params == dict(x=1)
|
||||
assert metafunc._calls[0].id == "1"
|
||||
assert metafunc._calls[1].params == dict(x=2)
|
||||
assert metafunc._calls[1].id == "2"
|
||||
|
||||
def test_parametrize_twoargs(self):
|
||||
metafunc = self.Metafunc(lambda x,y: None)
|
||||
metafunc.parametrize(("x", "y"), [(1,2), (3,4)])
|
||||
assert len(metafunc._calls) == 2
|
||||
assert metafunc._calls[0].funcargs == dict(x=1, y=2)
|
||||
assert metafunc._calls[0].id == "1-2"
|
||||
assert metafunc._calls[1].funcargs == dict(x=3, y=4)
|
||||
assert metafunc._calls[1].id == "3-4"
|
||||
|
||||
def test_parametrize_multiple_times(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
pytestmark = pytest.mark.parametrize("x", [1,2])
|
||||
def test_func(x):
|
||||
assert 0, x
|
||||
class TestClass:
|
||||
pytestmark = pytest.mark.parametrize("y", [3,4])
|
||||
def test_meth(self, x, y):
|
||||
assert 0, x
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines([
|
||||
"*6 fail*",
|
||||
])
|
||||
|
||||
def test_parametrize_class_scenarios(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
# same as doc/en/example/parametrize scenario example
|
||||
def pytest_generate_tests(metafunc):
|
||||
idlist = []
|
||||
argvalues = []
|
||||
for scenario in metafunc.cls.scenarios:
|
||||
idlist.append(scenario[0])
|
||||
items = scenario[1].items()
|
||||
argnames = [x[0] for x in items]
|
||||
argvalues.append(([x[1] for x in items]))
|
||||
metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class")
|
||||
|
||||
class Test(object):
|
||||
scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}],
|
||||
['2', {'arg':'value2', "arg2": "value2"}]]
|
||||
|
||||
def test_1(self, arg, arg2):
|
||||
pass
|
||||
|
||||
def test_2(self, arg2, arg):
|
||||
pass
|
||||
|
||||
def test_3(self, arg, arg2):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines("""
|
||||
*test_1*1*
|
||||
*test_2*1*
|
||||
*test_3*1*
|
||||
*test_1*2*
|
||||
*test_2*2*
|
||||
*test_3*2*
|
||||
*6 passed*
|
||||
""")
|
||||
|
||||
class TestMetafuncFunctional:
|
||||
def test_attributes(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
# assumes that generate/provide runs in the same process
|
||||
import py, pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=metafunc)
|
||||
|
||||
def pytest_funcarg__metafunc(request):
|
||||
assert request._pyfuncitem._genid == "0"
|
||||
return request.param
|
||||
|
||||
def test_function(metafunc, pytestconfig):
|
||||
assert metafunc.config == pytestconfig
|
||||
assert metafunc.module.__name__ == __name__
|
||||
assert metafunc.function == test_function
|
||||
assert metafunc.cls is None
|
||||
|
||||
class TestClass:
|
||||
def test_method(self, metafunc, pytestconfig):
|
||||
assert metafunc.config == pytestconfig
|
||||
assert metafunc.module.__name__ == __name__
|
||||
if py.std.sys.version_info > (3, 0):
|
||||
unbound = TestClass.test_method
|
||||
else:
|
||||
unbound = TestClass.test_method.im_func
|
||||
# XXX actually have an unbound test function here?
|
||||
assert metafunc.function == unbound
|
||||
assert metafunc.cls == TestClass
|
||||
""")
|
||||
result = testdir.runpytest(p, "-v")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*2 passed in*",
|
||||
])
|
||||
|
||||
def test_addcall_with_two_funcargs_generators(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*0*PASS*",
|
||||
"*test_myfunc*1*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=10)
|
||||
metafunc.addcall(param=20)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param
|
||||
|
||||
def test_func1(arg1):
|
||||
assert arg1 == 10
|
||||
def test_func2(arg1):
|
||||
assert arg1 in (10, 20)
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func1*0*PASS*",
|
||||
"*test_func1*1*FAIL*",
|
||||
"*test_func2*PASS*",
|
||||
"*1 failed, 3 passed*"
|
||||
])
|
||||
|
||||
def test_noself_in_method(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert 'xyz' not in metafunc.fixturenames
|
||||
|
||||
class TestHello:
|
||||
def test_hello(xyz):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 pass*",
|
||||
])
|
||||
|
||||
|
||||
def test_generate_plugin_and_module(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.addcall(id="world", param=(2,100))
|
||||
""")
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=(1,1), id="hello")
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param[0]
|
||||
def pytest_funcarg__arg2(request):
|
||||
return request.param[1]
|
||||
|
||||
class TestClass:
|
||||
def test_myfunc(self, arg1, arg2):
|
||||
assert arg1 == arg2
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*hello*PASS*",
|
||||
"*test_myfunc*world*FAIL*",
|
||||
"*1 failed, 1 passed*"
|
||||
])
|
||||
|
||||
def test_generate_tests_in_class(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
class TestClass:
|
||||
def pytest_generate_tests(self, metafunc):
|
||||
metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
|
||||
|
||||
def test_myfunc(self, hello):
|
||||
assert hello == "world"
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_myfunc*hello*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_two_functions_not_same_instance(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 10})
|
||||
metafunc.addcall({'arg1': 20})
|
||||
|
||||
class TestClass:
|
||||
def test_func(self, arg1):
|
||||
assert not hasattr(self, 'x')
|
||||
self.x = 1
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func*0*PASS*",
|
||||
"*test_func*1*PASS*",
|
||||
"*2 pass*",
|
||||
])
|
||||
|
||||
def test_issue28_setup_method_in_generate_tests(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 1})
|
||||
|
||||
class TestClass:
|
||||
def test_method(self, arg1):
|
||||
assert arg1 == self.val
|
||||
def setup_method(self, func):
|
||||
self.val = 1
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*1 pass*",
|
||||
])
|
||||
|
||||
def test_parametrize_functional2(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("arg1", [1,2])
|
||||
metafunc.parametrize("arg2", [4,5])
|
||||
def test_hello(arg1, arg2):
|
||||
assert 0, (arg1, arg2)
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*(1, 4)*",
|
||||
"*(1, 5)*",
|
||||
"*(2, 4)*",
|
||||
"*(2, 5)*",
|
||||
"*4 failed*",
|
||||
])
|
||||
|
||||
def test_parametrize_and_inner_getfuncargvalue(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize("arg1", [1], indirect=True)
|
||||
metafunc.parametrize("arg2", [10], indirect=True)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
x = request.getfuncargvalue("arg2")
|
||||
return x + request.param
|
||||
|
||||
def pytest_funcarg__arg2(request):
|
||||
return request.param
|
||||
|
||||
def test_func1(arg1, arg2):
|
||||
assert arg1 == 11
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func1*1*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_parametrize_on_setup_arg(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "arg1" in metafunc.fixturenames
|
||||
metafunc.parametrize("arg1", [1], indirect=True)
|
||||
|
||||
def pytest_funcarg__arg1(request):
|
||||
return request.param
|
||||
|
||||
def pytest_funcarg__arg2(request, arg1):
|
||||
return 10 * arg1
|
||||
|
||||
def test_func(arg2):
|
||||
assert arg2 == 10
|
||||
""")
|
||||
result = testdir.runpytest("-v", p)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_func*1*PASS*",
|
||||
"*1 passed*"
|
||||
])
|
||||
|
||||
def test_parametrize_with_ids(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize(("a", "b"), [(1,1), (1,2)],
|
||||
ids=["basic", "advanced"])
|
||||
|
||||
def test_function(a, b):
|
||||
assert a == b
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines_random([
|
||||
"*test_function*basic*PASSED",
|
||||
"*test_function*advanced*FAILED",
|
||||
])
|
||||
|
||||
def test_parametrize_without_ids(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.parametrize(("a", "b"),
|
||||
[(1,object()), (1.3,object())])
|
||||
|
||||
def test_function(a, b):
|
||||
assert 1
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*test_function*1-b0*
|
||||
*test_function*1.3-b1*
|
||||
""")
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("scope", "length"),
|
||||
[("module", 2), ("function", 4)])
|
||||
def test_parametrize_scope_overrides(self, testdir, scope, length):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
l = []
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "arg" in metafunc.funcargnames:
|
||||
metafunc.parametrize("arg", [1,2], indirect=True,
|
||||
scope=%r)
|
||||
def pytest_funcarg__arg(request):
|
||||
l.append(request.param)
|
||||
return request.param
|
||||
def test_hello(arg):
|
||||
assert arg in (1,2)
|
||||
def test_world(arg):
|
||||
assert arg in (1,2)
|
||||
def test_checklength():
|
||||
assert len(l) == %d
|
||||
""" % (scope, length))
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=5)
|
||||
|
||||
def test_usefixtures_seen_in_generate_tests(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert "abc" in metafunc.fixturenames
|
||||
metafunc.parametrize("abc", [1])
|
||||
|
||||
@pytest.mark.usefixtures("abc")
|
||||
def test_function():
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_generate_tests_only_done_in_subdir(self, testdir):
|
||||
sub1 = testdir.mkpydir("sub1")
|
||||
sub2 = testdir.mkpydir("sub2")
|
||||
sub1.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert metafunc.function.__name__ == "test_1"
|
||||
"""))
|
||||
sub2.join("conftest.py").write(py.code.Source("""
|
||||
def pytest_generate_tests(metafunc):
|
||||
assert metafunc.function.__name__ == "test_2"
|
||||
"""))
|
||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
|
||||
result.stdout.fnmatch_lines([
|
||||
"*3 passed*"
|
||||
])
|
||||
|
||||
|
||||
64
testing/python/raises.py
Normal file
64
testing/python/raises.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import pytest
|
||||
|
||||
class TestRaises:
|
||||
def test_raises(self):
|
||||
source = "int('qwe')"
|
||||
excinfo = pytest.raises(ValueError, source)
|
||||
code = excinfo.traceback[-1].frame.code
|
||||
s = str(code.fullsource)
|
||||
assert s == source
|
||||
|
||||
def test_raises_exec(self):
|
||||
pytest.raises(ValueError, "a,x = []")
|
||||
|
||||
def test_raises_syntax_error(self):
|
||||
pytest.raises(SyntaxError, "qwe qwe qwe")
|
||||
|
||||
def test_raises_function(self):
|
||||
pytest.raises(ValueError, int, 'hello')
|
||||
|
||||
def test_raises_callable_no_exception(self):
|
||||
class A:
|
||||
def __call__(self):
|
||||
pass
|
||||
try:
|
||||
pytest.raises(ValueError, A())
|
||||
except pytest.raises.Exception:
|
||||
pass
|
||||
|
||||
def test_raises_flip_builtin_AssertionError(self):
|
||||
# we replace AssertionError on python level
|
||||
# however c code might still raise the builtin one
|
||||
from _pytest.assertion.util import BuiltinAssertionError
|
||||
pytest.raises(AssertionError,"""
|
||||
raise BuiltinAssertionError
|
||||
""")
|
||||
|
||||
@pytest.mark.skipif('sys.version < "2.5"')
|
||||
def test_raises_as_contextmanager(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
from __future__ import with_statement
|
||||
import py, pytest
|
||||
|
||||
def test_simple():
|
||||
with pytest.raises(ZeroDivisionError) as excinfo:
|
||||
assert isinstance(excinfo, py.code.ExceptionInfo)
|
||||
1/0
|
||||
print (excinfo)
|
||||
assert excinfo.type == ZeroDivisionError
|
||||
|
||||
def test_noraise():
|
||||
with pytest.raises(pytest.raises.Exception):
|
||||
with pytest.raises(ValueError):
|
||||
int()
|
||||
|
||||
def test_raise_wrong_exception_passes_by():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
with pytest.raises(ValueError):
|
||||
1/0
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*3 passed*',
|
||||
])
|
||||
|
||||
@@ -354,18 +354,21 @@ def test_no_bytecode():
|
||||
@pytest.mark.skipif('"__pypy__" in sys.modules')
|
||||
def test_pyc_vs_pyo(self, testdir, monkeypatch):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_optimized():
|
||||
"hello"
|
||||
assert test_optimized.__doc__ is None""")
|
||||
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")
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
assert testdir.runpybin("py.test", tmp).ret == 0
|
||||
tagged = "test_pyc_vs_pyo." + PYTEST_TAG
|
||||
assert tagged + ".pyo" in os.listdir("__pycache__")
|
||||
monkeypatch.undo()
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
assert testdir.runpybin("py.test", tmp).ret == 1
|
||||
assert tagged + ".pyc" in os.listdir("__pycache__")
|
||||
|
||||
|
||||
@@ -103,6 +103,16 @@ class TestConfigAPI:
|
||||
assert config.getvalue("x", o) == 1
|
||||
pytest.raises(KeyError, 'config.getvalue("y", o)')
|
||||
|
||||
def test_config_getoption(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--hello", "-X", dest="hello")
|
||||
""")
|
||||
config = testdir.parseconfig("--hello=this")
|
||||
for x in ("hello", "--hello", "-X"):
|
||||
assert config.getoption(x) == "this"
|
||||
pytest.raises(ValueError, "config.getoption('qweqwe')")
|
||||
|
||||
def test_config_getvalueorskip(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
pytest.raises(pytest.skip.Exception,
|
||||
@@ -304,3 +314,4 @@ def test_cmdline_processargs_simple(testdir):
|
||||
"*pytest*",
|
||||
"*-h*",
|
||||
])
|
||||
|
||||
|
||||
@@ -237,6 +237,25 @@ class TestFunctional:
|
||||
assert l[1].args == ()
|
||||
assert l[2].args == ("pos1", )
|
||||
|
||||
@pytest.mark.xfail(reason='unfixed')
|
||||
def test_merging_markers_deep(self, testdir):
|
||||
# issue 199 - propagate markers into nested classes
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
class TestA:
|
||||
pytestmark = pytest.mark.a
|
||||
def test_b(self):
|
||||
assert True
|
||||
class TestC:
|
||||
# this one didnt get marked
|
||||
def test_d(self):
|
||||
assert True
|
||||
""")
|
||||
items, rec = testdir.inline_genitems(p)
|
||||
for item in items:
|
||||
print (item, item.keywords)
|
||||
assert 'a' in item.keywords
|
||||
|
||||
def test_mark_with_wrong_marker(self, testdir):
|
||||
reprec = testdir.inline_runsource("""
|
||||
import pytest
|
||||
|
||||
@@ -35,6 +35,12 @@ def test_setup_func_with_setup_decorator():
|
||||
assert not l
|
||||
|
||||
|
||||
def test_setup_func_not_callable():
|
||||
from _pytest.nose import call_optional
|
||||
class A:
|
||||
f = 1
|
||||
call_optional(A(), "f")
|
||||
|
||||
def test_nose_setup_func(testdir):
|
||||
p = testdir.makepyfile("""
|
||||
from nose.tools import with_setup
|
||||
@@ -85,7 +91,7 @@ def test_nose_setup_func_failure(testdir):
|
||||
""")
|
||||
result = testdir.runpytest(p, '-p', 'nose')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*TypeError: <lambda>() takes exactly 1*0 given*"
|
||||
"*TypeError: <lambda>()*"
|
||||
])
|
||||
|
||||
|
||||
@@ -97,21 +103,13 @@ def test_nose_setup_func_failure_2(testdir):
|
||||
my_teardown = 2
|
||||
|
||||
def test_hello():
|
||||
print (l)
|
||||
assert l == [1]
|
||||
|
||||
def test_world():
|
||||
print (l)
|
||||
assert l == [1,2]
|
||||
assert l == []
|
||||
|
||||
test_hello.setup = my_setup
|
||||
test_hello.teardown = my_teardown
|
||||
""")
|
||||
result = testdir.runpytest(p, '-p', 'nose')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*TypeError: 'int' object is not callable*"
|
||||
])
|
||||
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_nose_setup_partial(testdir):
|
||||
py.test.importorskip("functools")
|
||||
|
||||
@@ -18,12 +18,21 @@ def test_runTest_method(testdir):
|
||||
testpath=testdir.makepyfile("""
|
||||
import unittest
|
||||
pytest_plugins = "pytest_unittest"
|
||||
class MyTestCase(unittest.TestCase):
|
||||
class MyTestCaseWithRunTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
self.assertEquals('foo', 'foo')
|
||||
class MyTestCaseWithoutRunTest(unittest.TestCase):
|
||||
def runTest(self):
|
||||
self.assertEquals('foo', 'foo')
|
||||
def test_something(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run(testpath)
|
||||
assert reprec.matchreport('runTest').passed
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines("""
|
||||
*MyTestCaseWithRunTest.runTest*
|
||||
*MyTestCaseWithoutRunTest.test_something*
|
||||
*2 passed*
|
||||
""")
|
||||
|
||||
def test_isclasscheck_issue53(testdir):
|
||||
testpath = testdir.makepyfile("""
|
||||
@@ -87,6 +96,25 @@ def test_teardown(testdir):
|
||||
assert passed == 2
|
||||
assert passed + skipped + failed == 2
|
||||
|
||||
@pytest.mark.skipif("sys.version_info < (3,1)")
|
||||
def test_unittest_skip_issue148(testdir):
|
||||
testpath = testdir.makepyfile("""
|
||||
import unittest
|
||||
|
||||
@unittest.skip("hello")
|
||||
class MyTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
xxx
|
||||
def test_one(self):
|
||||
pass
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
xxx
|
||||
""")
|
||||
reprec = testdir.inline_run(testpath)
|
||||
reprec.assertoutcome(skipped=1)
|
||||
|
||||
def test_method_and_teardown_failing_reporting(testdir):
|
||||
testdir.makepyfile("""
|
||||
import unittest, pytest
|
||||
@@ -268,7 +296,27 @@ def test_testfunction_skip_property(testdir):
|
||||
|
||||
class TestTrialUnittest:
|
||||
def setup_class(cls):
|
||||
pytest.importorskip("twisted.trial.unittest")
|
||||
cls.ut = pytest.importorskip("twisted.trial.unittest")
|
||||
|
||||
def test_trial_testcase_runtest_not_collected(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
class TC(TestCase):
|
||||
def test_hello(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
testdir.makepyfile("""
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
class TC(TestCase):
|
||||
def runTest(self):
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_trial_exceptions_with_skips(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
@@ -313,7 +361,7 @@ class TestTrialUnittest:
|
||||
"*i2wanto*",
|
||||
"*sys.version_info*",
|
||||
"*skip_in_method*",
|
||||
"*5 skipped*3 xfail*1 xpass*",
|
||||
"*4 skipped*3 xfail*1 xpass*",
|
||||
])
|
||||
|
||||
def test_trial_error(self, testdir):
|
||||
|
||||
23
tox.ini
23
tox.ini
@@ -1,10 +1,10 @@
|
||||
[tox]
|
||||
distshare={homedir}/.tox/distshare
|
||||
envlist=py26,py27,py31,py32,py33,py27-xdist,py25,trial
|
||||
envlist=py24,py26,py27,py27-nobyte,py31,py32,py33,py27-xdist,py25,trial
|
||||
indexserver=
|
||||
pypi = http://pypi.python.org/simple
|
||||
testrun = http://pypi.testrun.org
|
||||
default = http://pypi.testrun.org
|
||||
#default = http://pypi.testrun.org
|
||||
|
||||
[testenv]
|
||||
changedir=testing
|
||||
@@ -16,22 +16,32 @@ deps=
|
||||
[testenv:genscript]
|
||||
changedir=.
|
||||
commands= py.test --genscript=pytest1
|
||||
deps=py>=1.4.0
|
||||
|
||||
[testenv:py27-xdist]
|
||||
changedir=.
|
||||
basepython=python2.7
|
||||
deps=pytest-xdist
|
||||
:pypi:mock
|
||||
:pypi:nose
|
||||
commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--ignore .tox --junitxml={envlogdir}/junit-{envname}.xml testing
|
||||
--junitxml={envlogdir}/junit-{envname}.xml testing
|
||||
|
||||
[testenv:py27-nobyte]
|
||||
changedir=.
|
||||
basepython=python2.7
|
||||
deps=pytest-xdist
|
||||
setenv=
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml []
|
||||
|
||||
[testenv:trial]
|
||||
changedir=.
|
||||
basepython=python2.6
|
||||
deps=:pypi:twisted
|
||||
:pypi:pexpect
|
||||
py>=1.4.5.dev1
|
||||
commands=
|
||||
py.test -rsxf testing/test_unittest.py \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml {posargs:testing/test_unittest.py}
|
||||
@@ -74,6 +84,7 @@ deps=py>=1.4.0
|
||||
|
||||
[testenv:py33]
|
||||
deps=py>=1.4.0
|
||||
:pypi:nose
|
||||
|
||||
[testenv:jython]
|
||||
changedir=testing
|
||||
@@ -87,7 +98,7 @@ plugins=pytester
|
||||
#--pyargs --doctest-modules --ignore=.tox
|
||||
addopts= -rxs
|
||||
rsyncdirs=tox.ini pytest.py _pytest testing
|
||||
python_files=test_*.py *_test.py
|
||||
python_files=test_*.py *_test.py testing/*/*.py
|
||||
python_classes=Test Acceptance
|
||||
python_functions=test
|
||||
pep8ignore = E401 E225 E261 E128 E124 E302
|
||||
|
||||
Reference in New Issue
Block a user