2010-11-06 18:38:53 +08:00
|
|
|
""" Python test discovery, setup and run of test functions. """
|
2017-03-17 09:21:30 +08:00
|
|
|
from __future__ import absolute_import, division, print_function
|
2016-03-08 08:43:53 +08:00
|
|
|
|
2014-10-17 06:27:10 +08:00
|
|
|
import fnmatch
|
2009-09-01 22:10:21 +08:00
|
|
|
import inspect
|
2010-07-04 23:06:50 +08:00
|
|
|
import sys
|
2017-04-20 03:26:56 +08:00
|
|
|
import os
|
2016-03-23 05:58:28 +08:00
|
|
|
import collections
|
2017-10-28 00:34:52 +08:00
|
|
|
import warnings
|
2017-07-15 19:33:11 +08:00
|
|
|
from textwrap import dedent
|
2016-08-22 02:44:37 +08:00
|
|
|
from itertools import count
|
2015-11-27 22:43:01 +08:00
|
|
|
|
2017-10-28 00:34:52 +08:00
|
|
|
|
2015-11-27 22:43:01 +08:00
|
|
|
import py
|
2017-08-03 01:33:52 +08:00
|
|
|
import six
|
2016-08-22 02:44:37 +08:00
|
|
|
from _pytest.mark import MarkerError
|
2017-03-16 01:00:59 +08:00
|
|
|
from _pytest.config import hookimpl
|
2015-08-08 21:25:10 +08:00
|
|
|
|
2010-11-13 18:10:45 +08:00
|
|
|
import _pytest
|
2017-08-25 23:46:55 +08:00
|
|
|
import pluggy
|
2016-07-10 02:36:00 +08:00
|
|
|
from _pytest import fixtures
|
2017-12-17 22:19:01 +08:00
|
|
|
from _pytest import nodes
|
2017-10-28 00:34:52 +08:00
|
|
|
from _pytest import deprecated
|
2016-07-10 02:36:00 +08:00
|
|
|
from _pytest.compat import (
|
2017-10-03 22:09:24 +08:00
|
|
|
isclass, isfunction, is_generator, ascii_escaped,
|
2016-07-10 02:36:00 +08:00
|
|
|
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
|
|
|
|
get_real_func, getfslineno, safe_getattr,
|
2017-03-30 01:38:14 +08:00
|
|
|
safe_str, getlocation, enum,
|
2016-07-10 02:36:00 +08:00
|
|
|
)
|
2015-09-19 07:03:05 +08:00
|
|
|
from _pytest.outcomes import fail
|
2017-06-22 14:45:10 +08:00
|
|
|
from _pytest.mark import transfer_markers
|
2017-06-23 17:59:03 +08:00
|
|
|
|
2018-01-09 07:27:53 +08:00
|
|
|
|
|
|
|
# relative paths that we use to filter traceback entries from appearing to the user;
|
|
|
|
# see filter_traceback
|
|
|
|
# note: if we need to add more paths than what we have now we should probably use a list
|
|
|
|
# for better maintenance
|
|
|
|
_pluggy_dir = py.path.local(pluggy.__file__.rstrip("oc"))
|
|
|
|
# pluggy is either a package or a single module depending on the version
|
|
|
|
if _pluggy_dir.basename == '__init__.py':
|
|
|
|
_pluggy_dir = _pluggy_dir.dirpath()
|
|
|
|
_pytest_dir = py.path.local(_pytest.__file__).dirpath()
|
|
|
|
_py_dir = py.path.local(py.__file__).dirpath()
|
2015-04-29 22:40:51 +08:00
|
|
|
|
2010-10-10 19:48:49 +08:00
|
|
|
|
2015-04-29 22:40:51 +08:00
|
|
|
def filter_traceback(entry):
|
2016-10-04 08:46:44 +08:00
|
|
|
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
|
|
|
* dynamically generated code (no code to show up for it);
|
|
|
|
* internal traceback from pytest or its internal libraries, py and pluggy.
|
|
|
|
"""
|
2015-10-17 07:08:12 +08:00
|
|
|
# entry.path might sometimes return a str object when the entry
|
2015-10-01 04:15:53 +08:00
|
|
|
# points to dynamically generated code
|
2015-09-30 09:29:43 +08:00
|
|
|
# see https://bitbucket.org/pytest-dev/py/issues/71
|
2015-10-01 04:15:53 +08:00
|
|
|
raw_filename = entry.frame.code.raw.co_filename
|
|
|
|
is_generated = '<' in raw_filename and '>' in raw_filename
|
|
|
|
if is_generated:
|
|
|
|
return False
|
2018-01-09 07:27:53 +08:00
|
|
|
# entry.path might point to an non-existing file, in which case it will
|
|
|
|
# also return a str object. see #1133
|
2015-10-17 07:08:12 +08:00
|
|
|
p = py.path.local(entry.path)
|
2018-01-09 07:27:53 +08:00
|
|
|
return not p.relto(_pluggy_dir) and not p.relto(_pytest_dir) and not p.relto(_py_dir)
|
2015-04-29 22:40:51 +08:00
|
|
|
|
|
|
|
|
2012-07-16 16:46:44 +08:00
|
|
|
def pyobj_property(name):
|
|
|
|
def get(self):
|
2017-03-16 01:00:59 +08:00
|
|
|
node = self.getparent(getattr(__import__('pytest'), name))
|
2012-07-16 16:46:44 +08:00
|
|
|
if node is not None:
|
|
|
|
return node.obj
|
|
|
|
doc = "python %s object this node was collected from (can be None)." % (
|
|
|
|
name.lower(),)
|
|
|
|
return property(get, None, None, doc)
|
|
|
|
|
2012-06-25 23:35:33 +08:00
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
def pytest_addoption(parser):
|
2010-11-07 05:17:33 +08:00
|
|
|
group = parser.getgroup("general")
|
2012-10-17 18:57:05 +08:00
|
|
|
group.addoption('--fixtures', '--funcargs',
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store_true", dest="showfixtures", default=False,
|
|
|
|
help="show available fixtures, sorted by plugin appearance")
|
2016-06-12 07:20:06 +08:00
|
|
|
group.addoption(
|
|
|
|
'--fixtures-per-test',
|
|
|
|
action="store_true",
|
|
|
|
dest="show_fixtures_per_test",
|
|
|
|
default=False,
|
|
|
|
help="show fixtures per test",
|
|
|
|
)
|
2012-10-16 22:13:12 +08:00
|
|
|
parser.addini("usefixtures", type="args", default=[],
|
2017-07-17 07:25:07 +08:00
|
|
|
help="list of default fixtures to be used with this project")
|
2010-11-25 05:01:04 +08:00
|
|
|
parser.addini("python_files", type="args",
|
2017-07-17 07:25:07 +08:00
|
|
|
default=['test_*.py', '*_test.py'],
|
|
|
|
help="glob-style file patterns for Python test module discovery")
|
2017-07-17 07:25:08 +08:00
|
|
|
parser.addini("python_classes", type="args", default=["Test", ],
|
2017-07-17 07:25:07 +08:00
|
|
|
help="prefixes or glob names for Python test class discovery")
|
2017-07-17 07:25:08 +08:00
|
|
|
parser.addini("python_functions", type="args", default=["test", ],
|
2017-07-17 07:25:07 +08:00
|
|
|
help="prefixes or glob names for Python test function and "
|
|
|
|
"method discovery")
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2015-09-17 04:12:20 +08:00
|
|
|
group.addoption("--import-mode", default="prepend",
|
2017-07-17 07:25:07 +08:00
|
|
|
choices=["prepend", "append"], dest="importmode",
|
|
|
|
help="prepend/append to sys.path when importing test modules, "
|
|
|
|
"default is to prepend.")
|
2015-09-17 04:12:20 +08:00
|
|
|
|
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
def pytest_cmdline_main(config):
|
2012-10-05 20:24:44 +08:00
|
|
|
if config.option.showfixtures:
|
|
|
|
showfixtures(config)
|
2010-09-26 00:23:26 +08:00
|
|
|
return 0
|
2016-06-12 07:20:06 +08:00
|
|
|
if config.option.show_fixtures_per_test:
|
|
|
|
show_fixtures_per_test(config)
|
|
|
|
return 0
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2011-11-17 19:09:21 +08:00
|
|
|
|
|
|
|
def pytest_generate_tests(metafunc):
|
2015-08-07 13:51:59 +08:00
|
|
|
# those alternative spellings are common - raise a specific error to alert
|
|
|
|
# the user
|
|
|
|
alt_spellings = ['parameterize', 'parametrise', 'parameterise']
|
|
|
|
for attr in alt_spellings:
|
|
|
|
if hasattr(metafunc.function, attr):
|
|
|
|
msg = "{0} has '{1}', spelling should be 'parametrize'"
|
|
|
|
raise MarkerError(msg.format(metafunc.function.__name__, attr))
|
2011-11-17 19:09:21 +08:00
|
|
|
try:
|
2012-10-06 17:34:06 +08:00
|
|
|
markers = metafunc.function.parametrize
|
2011-11-17 19:09:21 +08:00
|
|
|
except AttributeError:
|
|
|
|
return
|
2012-10-06 17:34:06 +08:00
|
|
|
for marker in markers:
|
|
|
|
metafunc.parametrize(*marker.args, **marker.kwargs)
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2011-11-17 19:09:21 +08:00
|
|
|
def pytest_configure(config):
|
|
|
|
config.addinivalue_line("markers",
|
2017-07-17 07:25:07 +08:00
|
|
|
"parametrize(argnames, argvalues): call a test function multiple "
|
|
|
|
"times passing in different arguments in turn. argvalues generally "
|
|
|
|
"needs to be a list of values if argnames specifies only one name "
|
|
|
|
"or a list of tuples of values if argnames specifies multiple names. "
|
|
|
|
"Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
|
|
|
|
"decorated test function, one with arg1=1 and another with arg1=2."
|
|
|
|
"see http://pytest.org/latest/parametrize.html for more info and "
|
|
|
|
"examples."
|
2017-07-17 07:25:07 +08:00
|
|
|
)
|
2012-10-19 16:17:13 +08:00
|
|
|
config.addinivalue_line("markers",
|
2017-07-17 07:25:07 +08:00
|
|
|
"usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
|
|
|
|
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
|
2017-07-17 07:25:07 +08:00
|
|
|
)
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2012-06-25 23:35:33 +08:00
|
|
|
|
2017-03-16 01:00:59 +08:00
|
|
|
@hookimpl(trylast=True)
|
2014-10-09 02:23:40 +08:00
|
|
|
def pytest_pyfunc_call(pyfuncitem):
|
|
|
|
testfunction = pyfuncitem.obj
|
|
|
|
if pyfuncitem._isyieldedfunction():
|
|
|
|
testfunction(*pyfuncitem._args)
|
|
|
|
else:
|
|
|
|
funcargs = pyfuncitem.funcargs
|
|
|
|
testargs = {}
|
|
|
|
for arg in pyfuncitem._fixtureinfo.argnames:
|
|
|
|
testargs[arg] = funcargs[arg]
|
|
|
|
testfunction(**testargs)
|
|
|
|
return True
|
2012-07-19 01:49:14 +08:00
|
|
|
|
2017-03-16 01:00:59 +08:00
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
def pytest_collect_file(path, parent):
|
|
|
|
ext = path.ext
|
2010-11-25 05:01:04 +08:00
|
|
|
if ext == ".py":
|
|
|
|
if not parent.session.isinitpath(path):
|
|
|
|
for pat in parent.config.getini('python_files'):
|
|
|
|
if path.fnmatch(pat):
|
|
|
|
break
|
|
|
|
else:
|
2017-07-17 07:25:06 +08:00
|
|
|
return
|
2012-10-22 16:17:50 +08:00
|
|
|
ihook = parent.session.gethookproxy(path)
|
|
|
|
return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
def pytest_pycollect_makemodule(path, parent):
|
2010-10-14 00:41:53 +08:00
|
|
|
return Module(path, parent)
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-03-16 01:00:59 +08:00
|
|
|
@hookimpl(hookwrapper=True)
|
2014-10-09 02:23:40 +08:00
|
|
|
def pytest_pycollect_makeitem(collector, name, obj):
|
|
|
|
outcome = yield
|
|
|
|
res = outcome.get_result()
|
2010-09-26 00:23:26 +08:00
|
|
|
if res is not None:
|
2017-02-01 12:37:55 +08:00
|
|
|
return
|
2014-10-09 02:23:40 +08:00
|
|
|
# nothing was collected elsewhere, let's do it here
|
2014-03-12 05:10:18 +08:00
|
|
|
if isclass(obj):
|
2015-08-07 02:36:59 +08:00
|
|
|
if collector.istestclass(obj, name):
|
2013-02-15 17:18:00 +08:00
|
|
|
Class = collector._getcustomclass("Class")
|
2014-10-09 02:23:40 +08:00
|
|
|
outcome.force_result(Class(name, parent=collector))
|
2015-08-07 02:36:59 +08:00
|
|
|
elif collector.istestfunction(obj, name):
|
2014-10-09 02:23:40 +08:00
|
|
|
# mock seems to store unbound methods (issue473), normalize it
|
2014-04-08 18:50:13 +08:00
|
|
|
obj = getattr(obj, "__func__", obj)
|
2015-09-23 07:06:55 +08:00
|
|
|
# We need to try and unwrap the function if it's a functools.partial
|
|
|
|
# or a funtools.wrapped.
|
|
|
|
# We musn't if it's been wrapped with mock.patch (python 2 only)
|
|
|
|
if not (isfunction(obj) or isfunction(get_real_func(obj))):
|
2017-07-17 07:25:08 +08:00
|
|
|
collector.warn(code="C2", message="cannot collect %r because it is not a function."
|
2017-07-17 07:25:07 +08:00
|
|
|
% name, )
|
2015-09-23 07:06:55 +08:00
|
|
|
elif getattr(obj, "__test__", True):
|
2014-04-10 19:37:39 +08:00
|
|
|
if is_generator(obj):
|
2014-10-09 02:23:40 +08:00
|
|
|
res = Generator(name, parent=collector)
|
2014-04-10 19:37:39 +08:00
|
|
|
else:
|
2014-10-09 02:23:40 +08:00
|
|
|
res = list(collector._genfunctions(name, obj))
|
|
|
|
outcome.force_result(res)
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-01-16 11:09:46 +08:00
|
|
|
def pytest_make_parametrize_id(config, val, argname=None):
|
2016-04-25 22:11:47 +08:00
|
|
|
return None
|
|
|
|
|
2016-07-10 02:36:00 +08:00
|
|
|
|
2012-07-16 16:46:44 +08:00
|
|
|
class PyobjContext(object):
|
|
|
|
module = pyobj_property("Module")
|
|
|
|
cls = pyobj_property("Class")
|
|
|
|
instance = pyobj_property("Instance")
|
2012-06-25 23:35:33 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2012-07-19 01:49:14 +08:00
|
|
|
class PyobjMixin(PyobjContext):
|
2010-07-27 03:15:15 +08:00
|
|
|
def obj():
|
2008-08-16 23:26:59 +08:00
|
|
|
def fget(self):
|
2016-09-18 23:34:48 +08:00
|
|
|
obj = getattr(self, '_obj', None)
|
|
|
|
if obj is None:
|
2010-07-27 03:15:15 +08:00
|
|
|
self._obj = obj = self._getobj()
|
2016-09-18 23:34:48 +08:00
|
|
|
return obj
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def fset(self, value):
|
|
|
|
self._obj = value
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
return property(fget, fset, None, "underlying python object")
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
obj = obj()
|
|
|
|
|
|
|
|
def _getobj(self):
|
|
|
|
return getattr(self.parent.obj, self.name)
|
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def getmodpath(self, stopatmodule=True, includemodule=False):
|
2008-08-16 23:26:59 +08:00
|
|
|
""" return python path relative to the containing module. """
|
|
|
|
chain = self.listchain()
|
|
|
|
chain.reverse()
|
|
|
|
parts = []
|
|
|
|
for node in chain:
|
|
|
|
if isinstance(node, Instance):
|
|
|
|
continue
|
2010-07-27 03:15:15 +08:00
|
|
|
name = node.name
|
2008-09-02 22:31:42 +08:00
|
|
|
if isinstance(node, Module):
|
2017-04-20 03:26:56 +08:00
|
|
|
name = os.path.splitext(name)[0]
|
2009-02-27 18:18:27 +08:00
|
|
|
if stopatmodule:
|
|
|
|
if includemodule:
|
|
|
|
parts.append(name)
|
|
|
|
break
|
2008-09-02 22:31:42 +08:00
|
|
|
parts.append(name)
|
2008-08-16 23:26:59 +08:00
|
|
|
parts.reverse()
|
|
|
|
s = ".".join(parts)
|
|
|
|
return s.replace(".[", "[")
|
|
|
|
|
2009-05-12 19:39:09 +08:00
|
|
|
def _getfslineno(self):
|
2012-07-20 20:16:28 +08:00
|
|
|
return getfslineno(self.obj)
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-05-12 23:02:22 +08:00
|
|
|
def reportinfo(self):
|
2010-11-07 06:45:48 +08:00
|
|
|
# XXX caching?
|
2010-11-05 06:21:23 +08:00
|
|
|
obj = self.obj
|
2015-11-30 23:13:15 +08:00
|
|
|
compat_co_firstlineno = getattr(obj, 'compat_co_firstlineno', None)
|
|
|
|
if isinstance(compat_co_firstlineno, int):
|
2010-11-05 06:21:23 +08:00
|
|
|
# nose compatibility
|
|
|
|
fspath = sys.modules[obj.__module__].__file__
|
|
|
|
if fspath.endswith(".pyc"):
|
|
|
|
fspath = fspath[:-1]
|
2015-11-30 23:13:15 +08:00
|
|
|
lineno = compat_co_firstlineno
|
2010-11-05 06:21:23 +08:00
|
|
|
else:
|
2012-07-20 20:16:28 +08:00
|
|
|
fspath, lineno = getfslineno(obj)
|
2013-12-03 18:23:22 +08:00
|
|
|
modpath = self.getmodpath()
|
2011-12-14 18:56:51 +08:00
|
|
|
assert isinstance(lineno, int)
|
2010-07-27 03:15:15 +08:00
|
|
|
return fspath, lineno, modpath
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-12-17 22:19:01 +08:00
|
|
|
class PyCollector(PyobjMixin, nodes.Collector):
|
2010-07-27 03:15:15 +08:00
|
|
|
|
|
|
|
def funcnamefilter(self, name):
|
2014-10-17 06:27:10 +08:00
|
|
|
return self._matches_prefix_or_glob_option('python_functions', name)
|
2011-01-18 19:47:31 +08:00
|
|
|
|
2015-08-07 02:36:59 +08:00
|
|
|
def isnosetest(self, obj):
|
|
|
|
""" Look for the __test__ attribute, which is applied by the
|
|
|
|
@nose.tools.istest decorator
|
|
|
|
"""
|
2015-11-30 23:41:13 +08:00
|
|
|
# We explicitly check for "is True" here to not mistakenly treat
|
|
|
|
# classes with a custom __getattr__ returning something truthy (like a
|
|
|
|
# function) as test classes.
|
|
|
|
return safe_getattr(obj, '__test__', False) is True
|
2015-08-07 02:36:59 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def classnamefilter(self, name):
|
2014-10-17 06:27:10 +08:00
|
|
|
return self._matches_prefix_or_glob_option('python_classes', name)
|
|
|
|
|
2015-08-07 02:36:59 +08:00
|
|
|
def istestfunction(self, obj, name):
|
2017-06-26 01:56:50 +08:00
|
|
|
if self.funcnamefilter(name) or self.isnosetest(obj):
|
|
|
|
if isinstance(obj, staticmethod):
|
|
|
|
# static methods need to be unwrapped
|
|
|
|
obj = safe_getattr(obj, '__func__', False)
|
|
|
|
if obj is False:
|
|
|
|
# Python 2.6 wraps in a different way that we won't try to handle
|
2017-07-18 08:16:14 +08:00
|
|
|
msg = "cannot collect static method %r because " \
|
|
|
|
"it is not a function (always the case in Python 2.6)"
|
|
|
|
self.warn(
|
|
|
|
code="C2", message=msg % name)
|
2017-06-26 01:56:50 +08:00
|
|
|
return False
|
|
|
|
return (
|
|
|
|
safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
return False
|
2015-08-07 02:36:59 +08:00
|
|
|
|
|
|
|
def istestclass(self, obj, name):
|
|
|
|
return self.classnamefilter(name) or self.isnosetest(obj)
|
|
|
|
|
2014-10-17 06:27:10 +08:00
|
|
|
def _matches_prefix_or_glob_option(self, option_name, name):
|
|
|
|
"""
|
|
|
|
checks if the given name matches the prefix or glob-pattern defined
|
|
|
|
in ini configuration.
|
|
|
|
"""
|
|
|
|
for option in self.config.getini(option_name):
|
|
|
|
if name.startswith(option):
|
|
|
|
return True
|
2014-10-21 04:36:31 +08:00
|
|
|
# check that name looks like a glob-string before calling fnmatch
|
|
|
|
# because this is called for every name in each collected module,
|
|
|
|
# and fnmatch is somewhat expensive to call
|
|
|
|
elif ('*' in option or '?' in option or '[' in option) and \
|
|
|
|
fnmatch.fnmatch(name, option):
|
2010-11-25 05:01:04 +08:00
|
|
|
return True
|
2014-10-17 06:27:10 +08:00
|
|
|
return False
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2008-09-02 16:58:14 +08:00
|
|
|
def collect(self):
|
2014-04-10 18:46:27 +08:00
|
|
|
if not getattr(self.obj, "__test__", True):
|
|
|
|
return []
|
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
# NB. we avoid random getattrs and peek in the __dict__ instead
|
2010-10-26 05:08:56 +08:00
|
|
|
# (XXX originally introduced from a PyPy need, still true?)
|
2008-08-16 23:26:59 +08:00
|
|
|
dicts = [getattr(self.obj, '__dict__', {})]
|
2009-09-01 22:10:21 +08:00
|
|
|
for basecls in inspect.getmro(self.obj.__class__):
|
2008-08-16 23:26:59 +08:00
|
|
|
dicts.append(basecls.__dict__)
|
|
|
|
seen = {}
|
2017-11-04 23:17:20 +08:00
|
|
|
values = []
|
2008-08-16 23:26:59 +08:00
|
|
|
for dic in dicts:
|
2015-12-10 03:32:19 +08:00
|
|
|
for name, obj in list(dic.items()):
|
2008-08-16 23:26:59 +08:00
|
|
|
if name in seen:
|
|
|
|
continue
|
|
|
|
seen[name] = True
|
2017-10-28 00:34:52 +08:00
|
|
|
res = self._makeitem(name, obj)
|
2012-11-28 00:58:08 +08:00
|
|
|
if res is None:
|
|
|
|
continue
|
|
|
|
if not isinstance(res, list):
|
|
|
|
res = [res]
|
2017-11-04 23:17:20 +08:00
|
|
|
values.extend(res)
|
|
|
|
values.sort(key=lambda item: item.reportinfo()[:2])
|
|
|
|
return values
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def makeitem(self, name, obj):
|
2017-10-28 00:34:52 +08:00
|
|
|
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
|
|
|
|
self._makeitem(name, obj)
|
|
|
|
|
|
|
|
def _makeitem(self, name, obj):
|
2017-07-17 07:25:09 +08:00
|
|
|
# assert self.ihook.fspath == self.fspath, self
|
2009-12-30 09:36:58 +08:00
|
|
|
return self.ihook.pytest_pycollect_makeitem(
|
2009-05-08 05:12:17 +08:00
|
|
|
collector=self, name=name, obj=obj)
|
2009-05-12 01:23:57 +08:00
|
|
|
|
|
|
|
def _genfunctions(self, name, funcobj):
|
2009-05-20 00:13:33 +08:00
|
|
|
module = self.getparent(Module).obj
|
|
|
|
clscol = self.getparent(Class)
|
2009-05-12 01:23:57 +08:00
|
|
|
cls = clscol and clscol.obj or None
|
2011-12-29 00:47:08 +08:00
|
|
|
transfer_markers(funcobj, cls, module)
|
2012-10-17 17:20:45 +08:00
|
|
|
fm = self.session._fixturemanager
|
|
|
|
fixtureinfo = fm.getfixtureinfo(self, funcobj, cls)
|
2012-10-16 19:47:59 +08:00
|
|
|
metafunc = Metafunc(funcobj, fixtureinfo, self.config,
|
|
|
|
cls=cls, module=module)
|
2015-04-25 17:29:11 +08:00
|
|
|
methods = []
|
|
|
|
if hasattr(module, "pytest_generate_tests"):
|
|
|
|
methods.append(module.pytest_generate_tests)
|
2014-10-01 18:19:11 +08:00
|
|
|
if hasattr(cls, "pytest_generate_tests"):
|
|
|
|
methods.append(cls().pytest_generate_tests)
|
2015-04-25 17:29:11 +08:00
|
|
|
if methods:
|
2015-04-25 19:38:30 +08:00
|
|
|
self.ihook.pytest_generate_tests.call_extra(methods,
|
|
|
|
dict(metafunc=metafunc))
|
2015-04-25 17:29:11 +08:00
|
|
|
else:
|
|
|
|
self.ihook.pytest_generate_tests(metafunc=metafunc)
|
2014-10-01 18:19:11 +08:00
|
|
|
|
2011-03-05 21:16:27 +08:00
|
|
|
Function = self._getcustomclass("Function")
|
2012-07-19 15:20:14 +08:00
|
|
|
if not metafunc._calls:
|
2014-10-06 20:06:17 +08:00
|
|
|
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
|
2012-10-06 17:34:06 +08:00
|
|
|
else:
|
2013-12-07 23:37:46 +08:00
|
|
|
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
|
2016-07-10 02:36:00 +08:00
|
|
|
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
|
2013-12-07 23:37:46 +08:00
|
|
|
|
2012-10-06 17:34:06 +08:00
|
|
|
for callspec in metafunc._calls:
|
2016-08-04 09:56:12 +08:00
|
|
|
subname = "%s[%s]" % (name, callspec.id)
|
2012-10-06 17:34:06 +08:00
|
|
|
yield Function(name=subname, parent=self,
|
|
|
|
callspec=callspec, callobj=funcobj,
|
2014-10-06 20:06:17 +08:00
|
|
|
fixtureinfo=fixtureinfo,
|
2017-07-17 07:25:08 +08:00
|
|
|
keywords={callspec.id: True},
|
2016-08-04 09:56:12 +08:00
|
|
|
originalname=name,
|
|
|
|
)
|
2010-07-27 03:15:15 +08:00
|
|
|
|
2015-07-25 22:28:18 +08:00
|
|
|
|
2017-12-17 22:19:01 +08:00
|
|
|
class Module(nodes.File, PyCollector):
|
2012-06-25 23:35:33 +08:00
|
|
|
""" Collector for test classes and functions. """
|
2016-08-27 18:53:18 +08:00
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
def _getobj(self):
|
2016-08-27 18:53:18 +08:00
|
|
|
return self._importtestmodule()
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2012-07-20 20:16:28 +08:00
|
|
|
def collect(self):
|
2012-10-16 22:13:12 +08:00
|
|
|
self.session._fixturemanager.parsefactories(self)
|
2012-07-20 20:16:28 +08:00
|
|
|
return super(Module, self).collect()
|
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def _importtestmodule(self):
|
2010-07-27 03:15:15 +08:00
|
|
|
# we assume we are only called once per module
|
2015-09-17 04:12:20 +08:00
|
|
|
importmode = self.config.getoption("--import-mode")
|
2010-07-04 23:06:50 +08:00
|
|
|
try:
|
2015-09-17 04:12:20 +08:00
|
|
|
mod = self.fspath.pyimport(ensuresyspath=importmode)
|
2010-07-04 23:06:50 +08:00
|
|
|
except SyntaxError:
|
2014-04-02 21:34:36 +08:00
|
|
|
raise self.CollectError(
|
2015-11-27 22:43:01 +08:00
|
|
|
_pytest._code.ExceptionInfo().getrepr(style="short"))
|
2010-07-04 23:06:50 +08:00
|
|
|
except self.fspath.ImportMismatchError:
|
|
|
|
e = sys.exc_info()[1]
|
|
|
|
raise self.CollectError(
|
|
|
|
"import file mismatch:\n"
|
2010-07-27 03:15:15 +08:00
|
|
|
"imported module %r has this __file__ attribute:\n"
|
2010-07-04 23:06:50 +08:00
|
|
|
" %s\n"
|
|
|
|
"which is not the same as the test file we want to collect:\n"
|
|
|
|
" %s\n"
|
2011-12-20 20:20:59 +08:00
|
|
|
"HINT: remove __pycache__ / .pyc files and/or use a "
|
|
|
|
"unique basename for your test file modules"
|
2017-07-17 07:25:07 +08:00
|
|
|
% e.args
|
2010-07-04 23:06:50 +08:00
|
|
|
)
|
2016-04-17 18:43:04 +08:00
|
|
|
except ImportError:
|
2016-10-04 08:46:44 +08:00
|
|
|
from _pytest._code.code import ExceptionInfo
|
|
|
|
exc_info = ExceptionInfo()
|
|
|
|
if self.config.getoption('verbose') < 2:
|
|
|
|
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
|
|
|
exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
|
2017-03-30 01:38:14 +08:00
|
|
|
formatted_tb = safe_str(exc_repr)
|
2016-04-17 18:43:04 +08:00
|
|
|
raise self.CollectError(
|
2016-10-04 07:47:44 +08:00
|
|
|
"ImportError while importing test module '{fspath}'.\n"
|
|
|
|
"Hint: make sure your test modules/packages have valid Python names.\n"
|
2016-10-04 08:46:44 +08:00
|
|
|
"Traceback:\n"
|
2016-10-04 07:47:44 +08:00
|
|
|
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
|
2016-04-17 18:43:04 +08:00
|
|
|
)
|
2016-08-20 05:21:25 +08:00
|
|
|
except _pytest.runner.Skipped as e:
|
|
|
|
if e.allow_module_level:
|
|
|
|
raise
|
2016-04-11 01:57:45 +08:00
|
|
|
raise self.CollectError(
|
2017-07-04 18:16:42 +08:00
|
|
|
"Using pytest.skip outside of a test is not allowed. "
|
|
|
|
"To decorate a test function, use the @pytest.mark.skip "
|
|
|
|
"or @pytest.mark.skipif decorators instead, and to skip a "
|
|
|
|
"module use `pytestmark = pytest.mark.{skip,skipif}."
|
2016-04-11 01:57:45 +08:00
|
|
|
)
|
2009-04-09 22:03:09 +08:00
|
|
|
self.config.pluginmanager.consider_module(mod)
|
2009-02-27 18:18:27 +08:00
|
|
|
return mod
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def setup(self):
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
|
2013-06-20 22:43:42 +08:00
|
|
|
if setup_module is None:
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
|
2012-08-13 19:37:14 +08:00
|
|
|
if setup_module is not None:
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_module()
|
|
|
|
|
|
|
|
teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule')
|
|
|
|
if teardown_module is None:
|
|
|
|
teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module')
|
|
|
|
if teardown_module is not None:
|
|
|
|
self.addfinalizer(teardown_module)
|
|
|
|
|
|
|
|
|
|
|
|
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
|
|
|
|
"""
|
|
|
|
Return a callable to perform xunit-style setup or teardown if
|
|
|
|
the function exists in the ``holder`` object.
|
|
|
|
The ``param_obj`` parameter is the parameter which will be passed to the function
|
|
|
|
when the callable is called without arguments, defaults to the ``holder`` object.
|
|
|
|
Return ``None`` if a suitable callable is not found.
|
|
|
|
"""
|
|
|
|
param_obj = param_obj if param_obj is not None else holder
|
|
|
|
result = _get_xunit_func(holder, attr_name)
|
|
|
|
if result is not None:
|
|
|
|
arg_count = result.__code__.co_argcount
|
|
|
|
if inspect.ismethod(result):
|
|
|
|
arg_count -= 1
|
|
|
|
if arg_count:
|
|
|
|
return lambda: result(param_obj)
|
|
|
|
else:
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def _get_xunit_func(obj, name):
|
|
|
|
"""Return the attribute from the given object to be used as a setup/teardown
|
|
|
|
xunit-style function, but only if not marked as a fixture to
|
|
|
|
avoid calling it twice.
|
|
|
|
"""
|
|
|
|
meth = getattr(obj, name, None)
|
|
|
|
if fixtures.getfixturemarker(meth) is None:
|
|
|
|
return meth
|
2013-08-02 15:52:40 +08:00
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2012-07-16 16:46:44 +08:00
|
|
|
class Class(PyCollector):
|
2012-06-25 23:35:33 +08:00
|
|
|
""" Collector for test methods. """
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2008-09-02 16:58:14 +08:00
|
|
|
def collect(self):
|
2016-11-30 19:17:51 +08:00
|
|
|
if not safe_getattr(self.obj, "__test__", True):
|
|
|
|
return []
|
2013-02-15 17:18:00 +08:00
|
|
|
if hasinit(self.obj):
|
2014-03-12 05:10:18 +08:00
|
|
|
self.warn("C1", "cannot collect test class %r because it has a "
|
2017-07-17 07:25:07 +08:00
|
|
|
"__init__ constructor" % self.obj.__name__)
|
2014-03-12 05:10:18 +08:00
|
|
|
return []
|
2016-07-23 23:37:58 +08:00
|
|
|
elif hasnew(self.obj):
|
|
|
|
self.warn("C1", "cannot collect test class %r because it has a "
|
|
|
|
"__new__ constructor" % self.obj.__name__)
|
|
|
|
return []
|
2011-03-05 21:16:27 +08:00
|
|
|
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def setup(self):
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_class = _get_xunit_func(self.obj, 'setup_class')
|
2010-07-27 03:15:15 +08:00
|
|
|
if setup_class is not None:
|
|
|
|
setup_class = getattr(setup_class, 'im_func', setup_class)
|
2012-05-07 05:03:16 +08:00
|
|
|
setup_class = getattr(setup_class, '__func__', setup_class)
|
2010-07-27 03:15:15 +08:00
|
|
|
setup_class(self.obj)
|
|
|
|
|
2013-08-02 15:52:40 +08:00
|
|
|
fin_class = getattr(self.obj, 'teardown_class', None)
|
|
|
|
if fin_class is not None:
|
|
|
|
fin_class = getattr(fin_class, 'im_func', fin_class)
|
|
|
|
fin_class = getattr(fin_class, '__func__', fin_class)
|
|
|
|
self.addfinalizer(lambda: fin_class(self.obj))
|
2010-07-27 03:15:15 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2012-07-16 16:46:44 +08:00
|
|
|
class Instance(PyCollector):
|
2010-07-27 03:15:15 +08:00
|
|
|
def _getobj(self):
|
2016-07-23 23:37:58 +08:00
|
|
|
return self.parent.obj()
|
2012-07-20 20:16:28 +08:00
|
|
|
|
|
|
|
def collect(self):
|
2012-10-16 22:13:12 +08:00
|
|
|
self.session._fixturemanager.parsefactories(self)
|
2012-07-20 20:16:28 +08:00
|
|
|
return super(Instance, self).collect()
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def newinstance(self):
|
2008-08-16 23:26:59 +08:00
|
|
|
self.obj = self._getobj()
|
|
|
|
return self.obj
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
class FunctionMixin(PyobjMixin):
|
|
|
|
""" mixin for the code common to Function and Generator.
|
|
|
|
"""
|
2012-07-19 01:49:14 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
def setup(self):
|
2008-08-16 23:26:59 +08:00
|
|
|
""" perform setup for this test function. """
|
2011-01-14 20:30:36 +08:00
|
|
|
if hasattr(self, '_preservedparent'):
|
|
|
|
obj = self._preservedparent
|
|
|
|
elif isinstance(self.parent, Instance):
|
2008-08-16 23:26:59 +08:00
|
|
|
obj = self.parent.newinstance()
|
|
|
|
self.obj = self._getobj()
|
|
|
|
else:
|
2010-07-27 03:15:15 +08:00
|
|
|
obj = self.parent.obj
|
2011-03-03 01:03:43 +08:00
|
|
|
if inspect.ismethod(self.obj):
|
2013-08-02 15:52:40 +08:00
|
|
|
setup_name = 'setup_method'
|
|
|
|
teardown_name = 'teardown_method'
|
2011-03-03 01:03:43 +08:00
|
|
|
else:
|
2013-08-02 15:52:40 +08:00
|
|
|
setup_name = 'setup_function'
|
|
|
|
teardown_name = 'teardown_function'
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj)
|
2010-07-27 03:15:15 +08:00
|
|
|
if setup_func_or_method is not None:
|
2016-07-15 08:28:59 +08:00
|
|
|
setup_func_or_method()
|
|
|
|
teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj)
|
|
|
|
if teardown_func_or_method is not None:
|
|
|
|
self.addfinalizer(teardown_func_or_method)
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2010-11-06 06:37:31 +08:00
|
|
|
def _prunetraceback(self, excinfo):
|
2010-07-27 03:15:15 +08:00
|
|
|
if hasattr(self, '_obj') and not self.config.option.fulltrace:
|
2015-11-27 22:43:01 +08:00
|
|
|
code = _pytest._code.Code(get_real_func(self.obj))
|
2010-07-27 03:15:15 +08:00
|
|
|
path, firstlineno = code.path, code.firstlineno
|
2010-11-06 06:37:31 +08:00
|
|
|
traceback = excinfo.traceback
|
2008-08-16 23:26:59 +08:00
|
|
|
ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
|
|
|
|
if ntraceback == traceback:
|
|
|
|
ntraceback = ntraceback.cut(path=path)
|
2009-03-01 19:24:52 +08:00
|
|
|
if ntraceback == traceback:
|
2015-04-29 22:40:51 +08:00
|
|
|
ntraceback = ntraceback.filter(filter_traceback)
|
|
|
|
if not ntraceback:
|
|
|
|
ntraceback = traceback
|
|
|
|
|
2010-11-06 06:37:31 +08:00
|
|
|
excinfo.traceback = ntraceback.filter()
|
2014-06-29 19:32:53 +08:00
|
|
|
# issue364: mark all but first and last frames to
|
|
|
|
# only show a single-line message for each frame
|
|
|
|
if self.config.option.tbstyle == "auto":
|
|
|
|
if len(excinfo.traceback) > 2:
|
|
|
|
for entry in excinfo.traceback[1:-1]:
|
|
|
|
entry.set_repr_style('short')
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2010-05-22 22:18:24 +08:00
|
|
|
def _repr_failure_py(self, excinfo, style="long"):
|
2017-03-16 01:00:59 +08:00
|
|
|
if excinfo.errisinstance(fail.Exception):
|
2010-11-24 23:43:55 +08:00
|
|
|
if not excinfo.value.pytrace:
|
2016-03-06 03:09:01 +08:00
|
|
|
return py._builtin._totext(excinfo.value)
|
2010-07-27 03:15:15 +08:00
|
|
|
return super(FunctionMixin, self)._repr_failure_py(excinfo,
|
2017-07-17 07:25:07 +08:00
|
|
|
style=style)
|
2010-02-04 23:01:02 +08:00
|
|
|
|
2009-07-26 00:09:01 +08:00
|
|
|
def repr_failure(self, excinfo, outerr=None):
|
|
|
|
assert outerr is None, "XXX outerr usage is deprecated"
|
2014-06-29 19:32:53 +08:00
|
|
|
style = self.config.option.tbstyle
|
|
|
|
if style == "auto":
|
|
|
|
style = "long"
|
|
|
|
return self._repr_failure_py(excinfo, style=style)
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2011-11-19 00:01:29 +08:00
|
|
|
|
2012-07-16 16:46:44 +08:00
|
|
|
class Generator(FunctionMixin, PyCollector):
|
2008-09-02 16:58:14 +08:00
|
|
|
def collect(self):
|
2010-07-27 03:15:15 +08:00
|
|
|
# test generators are seen as collectors but they also
|
|
|
|
# invoke setup/teardown on popular request
|
2009-05-07 21:44:56 +08:00
|
|
|
# (induced by the common "test_*" naming shared with normal tests)
|
2016-07-23 22:56:04 +08:00
|
|
|
from _pytest import deprecated
|
2011-05-27 06:08:56 +08:00
|
|
|
self.session._setupstate.prepare(self)
|
2011-01-14 20:30:36 +08:00
|
|
|
# see FunctionMixin.setup and test_setupstate_is_preserved_134
|
|
|
|
self._preservedparent = self.parent.obj
|
2017-11-04 23:17:20 +08:00
|
|
|
values = []
|
2009-03-19 22:34:33 +08:00
|
|
|
seen = {}
|
2010-07-27 03:15:15 +08:00
|
|
|
for i, x in enumerate(self.obj()):
|
2008-11-26 00:10:16 +08:00
|
|
|
name, call, args = self.getcallargs(x)
|
2012-10-19 16:07:11 +08:00
|
|
|
if not callable(call):
|
2017-07-17 07:25:08 +08:00
|
|
|
raise TypeError("%r yielded non callable test %r" % (self.obj, call,))
|
2008-11-26 00:10:16 +08:00
|
|
|
if name is None:
|
|
|
|
name = "[%d]" % i
|
|
|
|
else:
|
|
|
|
name = "['%s']" % name
|
2009-03-19 22:34:33 +08:00
|
|
|
if name in seen:
|
2017-07-17 07:25:08 +08:00
|
|
|
raise ValueError("%r generated tests with non-unique name %r" % (self, name))
|
2009-03-19 22:34:33 +08:00
|
|
|
seen[name] = True
|
2017-11-04 23:17:20 +08:00
|
|
|
values.append(self.Function(name, self, args=args, callobj=call))
|
2017-07-11 03:07:55 +08:00
|
|
|
self.warn('C1', deprecated.YIELD_TESTS)
|
2017-11-04 23:17:20 +08:00
|
|
|
return values
|
2010-07-27 03:15:15 +08:00
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
def getcallargs(self, obj):
|
2008-11-26 00:10:16 +08:00
|
|
|
if not isinstance(obj, (tuple, list)):
|
|
|
|
obj = (obj,)
|
2017-02-15 23:00:18 +08:00
|
|
|
# explicit naming
|
2017-08-03 01:33:52 +08:00
|
|
|
if isinstance(obj[0], six.string_types):
|
2008-11-26 00:10:16 +08:00
|
|
|
name = obj[0]
|
|
|
|
obj = obj[1:]
|
2008-08-16 23:26:59 +08:00
|
|
|
else:
|
2008-11-26 00:10:16 +08:00
|
|
|
name = None
|
|
|
|
call, args = obj[0], obj[1:]
|
2010-07-27 03:15:15 +08:00
|
|
|
return name, call, args
|
|
|
|
|
2008-08-16 23:26:59 +08:00
|
|
|
|
2009-08-07 00:15:21 +08:00
|
|
|
def hasinit(obj):
|
|
|
|
init = getattr(obj, '__init__', None)
|
|
|
|
if init:
|
2016-07-23 23:37:58 +08:00
|
|
|
return init != object.__init__
|
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2016-07-23 23:37:58 +08:00
|
|
|
def hasnew(obj):
|
|
|
|
new = getattr(obj, '__new__', None)
|
|
|
|
if new:
|
|
|
|
return new != object.__new__
|
2010-09-26 00:23:26 +08:00
|
|
|
|
|
|
|
|
2011-11-17 19:09:21 +08:00
|
|
|
class CallSpec2(object):
|
|
|
|
def __init__(self, metafunc):
|
|
|
|
self.metafunc = metafunc
|
|
|
|
self.funcargs = {}
|
|
|
|
self._idlist = []
|
|
|
|
self.params = {}
|
2016-07-10 02:36:00 +08:00
|
|
|
self._globalid = NOTSET
|
2011-11-17 19:09:21 +08:00
|
|
|
self._globalid_args = set()
|
2016-07-10 02:36:00 +08:00
|
|
|
self._globalparam = NOTSET
|
2012-07-30 16:46:03 +08:00
|
|
|
self._arg2scopenum = {} # used for sorting parametrized resources
|
2017-08-10 15:05:22 +08:00
|
|
|
self.marks = []
|
2013-12-04 04:05:19 +08:00
|
|
|
self.indices = {}
|
2011-11-17 19:09:21 +08:00
|
|
|
|
|
|
|
def copy(self, metafunc):
|
|
|
|
cs = CallSpec2(self.metafunc)
|
|
|
|
cs.funcargs.update(self.funcargs)
|
|
|
|
cs.params.update(self.params)
|
2017-08-10 15:05:22 +08:00
|
|
|
cs.marks.extend(self.marks)
|
2013-12-04 04:05:19 +08:00
|
|
|
cs.indices.update(self.indices)
|
2012-07-30 16:46:03 +08:00
|
|
|
cs._arg2scopenum.update(self._arg2scopenum)
|
2011-11-17 19:09:21 +08:00
|
|
|
cs._idlist = list(self._idlist)
|
|
|
|
cs._globalid = self._globalid
|
|
|
|
cs._globalid_args = self._globalid_args
|
|
|
|
cs._globalparam = self._globalparam
|
|
|
|
return cs
|
|
|
|
|
|
|
|
def _checkargnotcontained(self, arg):
|
|
|
|
if arg in self.params or arg in self.funcargs:
|
2017-07-17 07:25:08 +08:00
|
|
|
raise ValueError("duplicate %r" % (arg,))
|
2011-11-17 19:09:21 +08:00
|
|
|
|
|
|
|
def getparam(self, name):
|
|
|
|
try:
|
|
|
|
return self.params[name]
|
|
|
|
except KeyError:
|
2016-07-10 02:36:00 +08:00
|
|
|
if self._globalparam is NOTSET:
|
2011-11-17 19:09:21 +08:00
|
|
|
raise ValueError(name)
|
|
|
|
return self._globalparam
|
|
|
|
|
|
|
|
@property
|
|
|
|
def id(self):
|
2011-12-28 23:47:19 +08:00
|
|
|
return "-".join(map(str, filter(None, self._idlist)))
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2017-08-10 15:05:22 +08:00
|
|
|
def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum,
|
|
|
|
param_index):
|
2017-07-17 07:25:08 +08:00
|
|
|
for arg, val in zip(argnames, valset):
|
2011-11-17 19:09:21 +08:00
|
|
|
self._checkargnotcontained(arg)
|
2015-08-02 21:40:40 +08:00
|
|
|
valtype_for_arg = valtypes[arg]
|
|
|
|
getattr(self, valtype_for_arg)[arg] = val
|
2013-12-04 04:05:19 +08:00
|
|
|
self.indices[arg] = param_index
|
2012-07-30 16:46:03 +08:00
|
|
|
self._arg2scopenum[arg] = scopenum
|
2011-11-17 19:09:21 +08:00
|
|
|
self._idlist.append(id)
|
2017-08-10 15:05:22 +08:00
|
|
|
self.marks.extend(marks)
|
2011-11-17 19:09:21 +08:00
|
|
|
|
|
|
|
def setall(self, funcargs, id, param):
|
|
|
|
for x in funcargs:
|
|
|
|
self._checkargnotcontained(x)
|
|
|
|
self.funcargs.update(funcargs)
|
2016-07-10 02:36:00 +08:00
|
|
|
if id is not NOTSET:
|
2011-11-17 19:09:21 +08:00
|
|
|
self._idlist.append(id)
|
2016-07-10 02:36:00 +08:00
|
|
|
if param is not NOTSET:
|
|
|
|
assert self._globalparam is NOTSET
|
2011-11-17 19:09:21 +08:00
|
|
|
self._globalparam = param
|
2013-12-07 23:37:46 +08:00
|
|
|
for arg in funcargs:
|
2016-07-10 02:36:00 +08:00
|
|
|
self._arg2scopenum[arg] = fixtures.scopenum_function
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2016-07-10 02:36:00 +08:00
|
|
|
class Metafunc(fixtures.FuncargnamesCompatAttr):
|
2015-07-10 08:50:38 +08:00
|
|
|
"""
|
|
|
|
Metafunc objects are passed to the ``pytest_generate_tests`` hook.
|
|
|
|
They help to inspect a test function and to generate tests according to
|
|
|
|
test configuration or values specified in the class or module where a
|
|
|
|
test function is defined.
|
|
|
|
"""
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2012-10-16 19:47:59 +08:00
|
|
|
def __init__(self, function, fixtureinfo, config, cls=None, module=None):
|
2016-08-23 10:35:41 +08:00
|
|
|
#: access to the :class:`_pytest.config.Config` object for the test session
|
2012-10-16 19:47:59 +08:00
|
|
|
self.config = config
|
2016-08-23 10:35:41 +08:00
|
|
|
|
|
|
|
#: the module object where the test function is defined in.
|
2010-09-26 00:23:26 +08:00
|
|
|
self.module = module
|
2016-08-23 10:35:41 +08:00
|
|
|
|
|
|
|
#: underlying python test function
|
2010-09-26 00:23:26 +08:00
|
|
|
self.function = function
|
2016-08-23 10:35:41 +08:00
|
|
|
|
|
|
|
#: set of fixture names required by the test function
|
2012-10-16 19:47:59 +08:00
|
|
|
self.fixturenames = fixtureinfo.names_closure
|
2016-08-23 10:35:41 +08:00
|
|
|
|
|
|
|
#: class object where the test function is defined in or ``None``.
|
2010-09-26 00:23:26 +08:00
|
|
|
self.cls = cls
|
2016-08-23 10:35:41 +08:00
|
|
|
|
2010-09-26 00:23:26 +08:00
|
|
|
self._calls = []
|
2017-08-03 01:33:52 +08:00
|
|
|
self._ids = set()
|
2016-08-23 10:35:41 +08:00
|
|
|
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2012-07-30 16:46:03 +08:00
|
|
|
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
|
2017-07-17 07:25:07 +08:00
|
|
|
scope=None):
|
2011-12-05 18:10:48 +08:00
|
|
|
""" Add new invocations to the underlying test function using the list
|
|
|
|
of argvalues for the given argnames. Parametrization is performed
|
2011-11-20 07:45:05 +08:00
|
|
|
during the collection phase. If you need to setup expensive resources
|
2015-08-04 05:48:41 +08:00
|
|
|
see about setting indirect to do it rather at test setup time.
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2013-05-28 16:32:54 +08:00
|
|
|
:arg argnames: a comma-separated string denoting one or more argument
|
|
|
|
names, or a list/tuple of argument strings.
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2013-05-22 21:24:58 +08:00
|
|
|
:arg argvalues: The list of argvalues determines how often a
|
|
|
|
test is invoked with different argument values. If only one
|
2015-09-22 22:45:49 +08:00
|
|
|
argname was specified argvalues is a list of values. If N
|
2013-05-22 21:24:58 +08:00
|
|
|
argnames were specified, argvalues must be a list of N-tuples,
|
|
|
|
where each tuple-element specifies a value for its respective
|
|
|
|
argname.
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2015-08-02 21:40:40 +08:00
|
|
|
:arg indirect: The list of argnames or boolean. A list of arguments'
|
|
|
|
names (subset of argnames). If True the list contains all names from
|
|
|
|
the argnames. Each argvalue corresponding to an argname in this list will
|
2012-10-09 20:35:17 +08:00
|
|
|
be passed as request.param to its respective argname fixture
|
|
|
|
function so that it can perform more expensive setups during the
|
|
|
|
setup phase of a test rather than at collection time.
|
2011-11-17 19:09:21 +08:00
|
|
|
|
2014-04-18 03:08:49 +08:00
|
|
|
:arg ids: list of string ids, or a callable.
|
|
|
|
If strings, each is corresponding to the argvalues so that they are
|
2016-03-21 00:54:48 +08:00
|
|
|
part of the test id. If None is given as id of specific test, the
|
|
|
|
automatically generated id for that argument will be used.
|
2014-04-18 03:08:49 +08:00
|
|
|
If callable, it should take one argument (a single argvalue) and return
|
|
|
|
a string or return None. If None, the automatically generated id for that
|
|
|
|
argument will be used.
|
|
|
|
If no ids are provided they will be generated automatically from
|
|
|
|
the argvalues.
|
2012-10-07 03:01:13 +08:00
|
|
|
|
2012-10-09 20:35:17 +08:00
|
|
|
:arg scope: if specified it denotes the scope of the parameters.
|
|
|
|
The scope is used for grouping tests by parameter instances.
|
|
|
|
It will also override any fixture-function defined scope, allowing
|
|
|
|
to set a dynamic scope using test context or configuration.
|
2011-11-17 19:09:21 +08:00
|
|
|
"""
|
2016-09-07 02:16:25 +08:00
|
|
|
from _pytest.fixtures import scope2index
|
2017-10-27 23:52:14 +08:00
|
|
|
from _pytest.mark import ParameterSet
|
2016-08-24 08:07:41 +08:00
|
|
|
from py.io import saferepr
|
2018-01-31 06:22:54 +08:00
|
|
|
|
2018-01-31 06:20:43 +08:00
|
|
|
argnames, parameters = ParameterSet._for_parametrize(
|
|
|
|
argnames, argvalues, self.function, self.config)
|
2016-09-07 17:00:27 +08:00
|
|
|
del argvalues
|
|
|
|
|
2012-07-30 18:39:45 +08:00
|
|
|
if scope is None:
|
2016-08-23 07:21:31 +08:00
|
|
|
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
|
|
|
|
2016-09-07 17:00:27 +08:00
|
|
|
scopenum = scope2index(scope, descr='call to {0}'.format(self.parametrize))
|
2015-08-04 05:02:03 +08:00
|
|
|
valtypes = {}
|
2015-08-23 18:42:40 +08:00
|
|
|
for arg in argnames:
|
|
|
|
if arg not in self.fixturenames:
|
2016-07-23 22:49:20 +08:00
|
|
|
if isinstance(indirect, (tuple, list)):
|
|
|
|
name = 'fixture' if arg in indirect else 'argument'
|
|
|
|
else:
|
2016-07-24 16:47:06 +08:00
|
|
|
name = 'fixture' if indirect else 'argument'
|
2016-07-23 22:49:20 +08:00
|
|
|
raise ValueError(
|
|
|
|
"%r uses no %s %r" % (
|
2017-07-17 07:25:07 +08:00
|
|
|
self.function, name, arg))
|
2015-08-23 18:42:40 +08:00
|
|
|
|
2015-08-04 05:02:03 +08:00
|
|
|
if indirect is True:
|
|
|
|
valtypes = dict.fromkeys(argnames, "params")
|
|
|
|
elif indirect is False:
|
|
|
|
valtypes = dict.fromkeys(argnames, "funcargs")
|
|
|
|
elif isinstance(indirect, (tuple, list)):
|
|
|
|
valtypes = dict.fromkeys(argnames, "funcargs")
|
|
|
|
for arg in indirect:
|
|
|
|
if arg not in argnames:
|
2016-07-23 22:49:20 +08:00
|
|
|
raise ValueError("indirect given to %r: fixture %r doesn't exist" % (
|
2015-08-04 05:02:03 +08:00
|
|
|
self.function, arg))
|
|
|
|
valtypes[arg] = "params"
|
2014-04-18 03:08:49 +08:00
|
|
|
idfn = None
|
|
|
|
if callable(ids):
|
|
|
|
idfn = ids
|
|
|
|
ids = None
|
2016-08-24 08:07:41 +08:00
|
|
|
if ids:
|
2016-09-07 17:00:27 +08:00
|
|
|
if len(ids) != len(parameters):
|
|
|
|
raise ValueError('%d tests specified with %d ids' % (
|
|
|
|
len(parameters), len(ids)))
|
2016-08-24 08:07:41 +08:00
|
|
|
for id_value in ids:
|
2017-08-03 01:33:52 +08:00
|
|
|
if id_value is not None and not isinstance(id_value, six.string_types):
|
2016-08-24 08:07:41 +08:00
|
|
|
msg = 'ids must be list of strings, found: %s (type: %s)'
|
|
|
|
raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
|
2016-09-07 17:00:27 +08:00
|
|
|
ids = idmaker(argnames, parameters, idfn, ids, self.config)
|
2011-11-17 19:09:21 +08:00
|
|
|
newcalls = []
|
|
|
|
for callspec in self._calls or [CallSpec2(self)]:
|
2016-09-07 17:00:27 +08:00
|
|
|
elements = zip(ids, parameters, count())
|
|
|
|
for a_id, param, param_index in elements:
|
2017-05-04 06:04:53 +08:00
|
|
|
if len(param.values) != len(argnames):
|
2017-04-29 19:32:09 +08:00
|
|
|
raise ValueError(
|
|
|
|
'In "parametrize" the number of values ({0}) must be '
|
|
|
|
'equal to the number of names ({1})'.format(
|
2017-05-04 06:04:53 +08:00
|
|
|
param.values, argnames))
|
2011-11-17 19:09:21 +08:00
|
|
|
newcallspec = callspec.copy(self)
|
2017-08-10 15:05:22 +08:00
|
|
|
newcallspec.setmulti2(valtypes, argnames, param.values, a_id,
|
|
|
|
param.marks, scopenum, param_index)
|
2011-11-17 19:09:21 +08:00
|
|
|
newcalls.append(newcallspec)
|
|
|
|
self._calls = newcalls
|
|
|
|
|
2016-07-10 02:36:00 +08:00
|
|
|
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
2017-11-15 23:48:08 +08:00
|
|
|
""" Add a new call to the underlying test function during the collection phase of a test run.
|
|
|
|
|
|
|
|
.. deprecated:: 3.3
|
|
|
|
|
|
|
|
Use :meth:`parametrize` instead.
|
|
|
|
|
|
|
|
Note that request.addcall() is called during the test collection phase prior and
|
2011-12-05 18:10:48 +08:00
|
|
|
independently to actual test execution. You should only use addcall()
|
|
|
|
if you need to specify multiple arguments of a test function.
|
2011-01-18 19:47:31 +08:00
|
|
|
|
2010-10-11 05:45:45 +08:00
|
|
|
:arg funcargs: argument keyword dictionary used when invoking
|
|
|
|
the test function.
|
2011-01-18 19:47:31 +08:00
|
|
|
|
2010-11-05 06:21:23 +08:00
|
|
|
:arg id: used for reporting and identification purposes. If you
|
2011-11-17 19:09:21 +08:00
|
|
|
don't supply an `id` an automatic unique id will be generated.
|
2010-10-11 05:45:45 +08:00
|
|
|
|
2012-10-05 20:24:44 +08:00
|
|
|
:arg param: a parameter which will be exposed to a later fixture function
|
2011-11-17 19:09:21 +08:00
|
|
|
invocation through the ``request.param`` attribute.
|
2010-10-11 05:45:45 +08:00
|
|
|
"""
|
2017-11-15 23:48:08 +08:00
|
|
|
if self.config:
|
|
|
|
self.config.warn('C1', message=deprecated.METAFUNC_ADD_CALL, fslocation=None)
|
2010-09-26 00:23:26 +08:00
|
|
|
assert funcargs is None or isinstance(funcargs, dict)
|
2011-03-05 19:11:35 +08:00
|
|
|
if funcargs is not None:
|
|
|
|
for name in funcargs:
|
2012-10-05 20:24:44 +08:00
|
|
|
if name not in self.fixturenames:
|
2017-03-20 22:47:25 +08:00
|
|
|
fail("funcarg %r not used in this function." % name)
|
2011-11-17 19:09:21 +08:00
|
|
|
else:
|
|
|
|
funcargs = {}
|
2010-09-26 00:23:26 +08:00
|
|
|
if id is None:
|
|
|
|
raise ValueError("id=None not allowed")
|
2016-07-10 02:36:00 +08:00
|
|
|
if id is NOTSET:
|
2010-09-26 00:23:26 +08:00
|
|
|
id = len(self._calls)
|
|
|
|
id = str(id)
|
|
|
|
if id in self._ids:
|
|
|
|
raise ValueError("duplicate id %r" % id)
|
|
|
|
self._ids.add(id)
|
2011-11-17 19:09:21 +08:00
|
|
|
|
|
|
|
cs = CallSpec2(self)
|
|
|
|
cs.setall(funcargs, id, param)
|
|
|
|
self._calls.append(cs)
|
|
|
|
|
2014-04-18 03:08:49 +08:00
|
|
|
|
2016-08-23 07:21:31 +08:00
|
|
|
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
|
|
|
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
|
|
|
|
|
|
|
When there's at least one direct argument, always use "function" scope.
|
|
|
|
|
|
|
|
When a test function is parametrized and all its arguments are indirect
|
|
|
|
(e.g. fixtures), return the most narrow scope based on the fixtures used.
|
|
|
|
|
|
|
|
Related to issue #1832, based on code posted by @Kingdread.
|
|
|
|
"""
|
|
|
|
from _pytest.fixtures import scopes
|
|
|
|
indirect_as_list = isinstance(indirect, (list, tuple))
|
|
|
|
all_arguments_are_fixtures = indirect is True or \
|
2017-07-17 07:25:07 +08:00
|
|
|
indirect_as_list and len(indirect) == argnames
|
2016-08-23 07:21:31 +08:00
|
|
|
if all_arguments_are_fixtures:
|
|
|
|
fixturedefs = arg2fixturedefs or {}
|
|
|
|
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
|
|
|
|
if used_scopes:
|
|
|
|
# Takes the most narrow scope from used fixtures
|
|
|
|
for scope in reversed(scopes):
|
|
|
|
if scope in used_scopes:
|
|
|
|
return scope
|
|
|
|
|
|
|
|
return 'function'
|
2016-03-22 13:31:48 +08:00
|
|
|
|
2015-09-23 09:48:22 +08:00
|
|
|
|
2016-04-25 22:48:28 +08:00
|
|
|
def _idval(val, argname, idx, idfn, config=None):
|
2014-04-18 03:08:49 +08:00
|
|
|
if idfn:
|
2016-12-31 01:37:09 +08:00
|
|
|
s = None
|
2014-04-18 03:08:49 +08:00
|
|
|
try:
|
|
|
|
s = idfn(val)
|
|
|
|
except Exception:
|
2016-12-31 01:37:09 +08:00
|
|
|
# See issue https://github.com/pytest-dev/pytest/issues/2169
|
|
|
|
import warnings
|
|
|
|
msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx)
|
|
|
|
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
|
2017-03-21 09:01:22 +08:00
|
|
|
warnings.warn(msg, DeprecationWarning)
|
2016-12-31 01:37:09 +08:00
|
|
|
if s:
|
2017-10-03 22:09:24 +08:00
|
|
|
return ascii_escaped(s)
|
2015-08-07 13:31:04 +08:00
|
|
|
|
2016-04-25 22:11:47 +08:00
|
|
|
if config:
|
2017-01-16 11:09:46 +08:00
|
|
|
hook_id = config.hook.pytest_make_parametrize_id(
|
|
|
|
config=config, val=val, argname=argname)
|
2016-04-25 22:11:47 +08:00
|
|
|
if hook_id:
|
|
|
|
return hook_id
|
|
|
|
|
2016-07-10 02:36:00 +08:00
|
|
|
if isinstance(val, STRING_TYPES):
|
2017-10-03 22:09:24 +08:00
|
|
|
return ascii_escaped(val)
|
2016-04-02 00:27:17 +08:00
|
|
|
elif isinstance(val, (float, int, bool, NoneType)):
|
2014-04-18 03:08:49 +08:00
|
|
|
return str(val)
|
2015-08-08 18:57:54 +08:00
|
|
|
elif isinstance(val, REGEX_TYPE):
|
2017-10-03 22:09:24 +08:00
|
|
|
return ascii_escaped(val.pattern)
|
2015-08-07 13:31:04 +08:00
|
|
|
elif enum is not None and isinstance(val, enum.Enum):
|
|
|
|
return str(val)
|
2017-11-30 00:17:49 +08:00
|
|
|
elif (isclass(val) or isfunction(val)) and hasattr(val, '__name__'):
|
2015-08-07 13:31:04 +08:00
|
|
|
return val.__name__
|
2017-07-17 07:25:08 +08:00
|
|
|
return str(argname) + str(idx)
|
2014-04-18 03:08:49 +08:00
|
|
|
|
2017-03-20 22:47:25 +08:00
|
|
|
|
2016-09-07 17:00:27 +08:00
|
|
|
def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
|
|
|
|
if parameterset.id is not None:
|
|
|
|
return parameterset.id
|
2016-08-24 05:14:15 +08:00
|
|
|
if ids is None or (idx >= len(ids) or ids[idx] is None):
|
2016-04-25 22:11:47 +08:00
|
|
|
this_id = [_idval(val, argname, idx, idfn, config)
|
2016-09-07 17:00:27 +08:00
|
|
|
for val, argname in zip(parameterset.values, argnames)]
|
2016-03-20 03:22:49 +08:00
|
|
|
return "-".join(this_id)
|
|
|
|
else:
|
2017-10-03 22:09:24 +08:00
|
|
|
return ascii_escaped(ids[idx])
|
2014-04-18 03:08:49 +08:00
|
|
|
|
2017-03-20 22:47:25 +08:00
|
|
|
|
2016-09-07 17:00:27 +08:00
|
|
|
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
|
|
|
|
ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config)
|
|
|
|
for valindex, parameterset in enumerate(parametersets)]
|
2016-03-24 00:47:27 +08:00
|
|
|
if len(set(ids)) != len(ids):
|
2016-03-23 05:58:28 +08:00
|
|
|
# The ids are not unique
|
2016-03-24 00:47:27 +08:00
|
|
|
duplicates = [testid for testid in ids if ids.count(testid) > 1]
|
2016-03-23 05:58:28 +08:00
|
|
|
counters = collections.defaultdict(lambda: 0)
|
|
|
|
for index, testid in enumerate(ids):
|
|
|
|
if testid in duplicates:
|
|
|
|
ids[index] = testid + str(counters[testid])
|
|
|
|
counters[testid] += 1
|
2014-04-18 03:08:49 +08:00
|
|
|
return ids
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2016-06-12 07:20:06 +08:00
|
|
|
|
|
|
|
def show_fixtures_per_test(config):
|
|
|
|
from _pytest.main import wrap_session
|
|
|
|
return wrap_session(config, _show_fixtures_per_test)
|
|
|
|
|
|
|
|
|
|
|
|
def _show_fixtures_per_test(config, session):
|
|
|
|
import _pytest.config
|
|
|
|
session.perform_collect()
|
|
|
|
curdir = py.path.local()
|
|
|
|
tw = _pytest.config.create_terminal_writer(config)
|
|
|
|
verbose = config.getvalue("verbose")
|
|
|
|
|
2017-09-19 18:41:12 +08:00
|
|
|
def get_best_relpath(func):
|
2016-06-12 07:20:06 +08:00
|
|
|
loc = getlocation(func, curdir)
|
|
|
|
return curdir.bestrelpath(loc)
|
|
|
|
|
|
|
|
def write_fixture(fixture_def):
|
|
|
|
argname = fixture_def.argname
|
|
|
|
if verbose <= 0 and argname.startswith("_"):
|
|
|
|
return
|
|
|
|
if verbose > 0:
|
2017-09-19 18:41:12 +08:00
|
|
|
bestrel = get_best_relpath(fixture_def.func)
|
2016-06-12 07:52:03 +08:00
|
|
|
funcargspec = "{0} -- {1}".format(argname, bestrel)
|
2016-06-12 07:20:06 +08:00
|
|
|
else:
|
|
|
|
funcargspec = argname
|
|
|
|
tw.line(funcargspec, green=True)
|
|
|
|
fixture_doc = fixture_def.func.__doc__
|
|
|
|
if fixture_doc:
|
2017-07-15 19:33:11 +08:00
|
|
|
write_docstring(tw, fixture_doc)
|
2016-06-12 07:20:06 +08:00
|
|
|
else:
|
2017-07-15 19:33:11 +08:00
|
|
|
tw.line(' no docstring available', red=True)
|
2016-06-12 07:20:06 +08:00
|
|
|
|
|
|
|
def write_item(item):
|
2017-09-19 18:41:12 +08:00
|
|
|
try:
|
|
|
|
info = item._fixtureinfo
|
|
|
|
except AttributeError:
|
|
|
|
# doctests items have no _fixtureinfo attribute
|
|
|
|
return
|
|
|
|
if not info.name2fixturedefs:
|
|
|
|
# this test item does not use any fixtures
|
2016-06-12 07:20:06 +08:00
|
|
|
return
|
|
|
|
tw.line()
|
2016-06-12 07:52:03 +08:00
|
|
|
tw.sep('-', 'fixtures used by {0}'.format(item.name))
|
2017-09-19 18:41:12 +08:00
|
|
|
tw.sep('-', '({0})'.format(get_best_relpath(item.function)))
|
|
|
|
# dict key not used in loop but needed for sorting
|
|
|
|
for _, fixturedefs in sorted(info.name2fixturedefs.items()):
|
|
|
|
assert fixturedefs is not None
|
|
|
|
if not fixturedefs:
|
2016-06-12 07:20:06 +08:00
|
|
|
continue
|
2017-09-19 18:41:12 +08:00
|
|
|
# last item is expected to be the one used by the test item
|
|
|
|
write_fixture(fixturedefs[-1])
|
2016-06-12 07:20:06 +08:00
|
|
|
|
2017-09-19 18:41:12 +08:00
|
|
|
for session_item in session.items:
|
|
|
|
write_item(session_item)
|
2016-06-12 07:20:06 +08:00
|
|
|
|
|
|
|
|
2012-10-05 20:24:44 +08:00
|
|
|
def showfixtures(config):
|
2011-05-27 08:09:42 +08:00
|
|
|
from _pytest.main import wrap_session
|
2012-10-05 20:24:44 +08:00
|
|
|
return wrap_session(config, _showfixtures_main)
|
2011-05-27 08:09:42 +08:00
|
|
|
|
2017-03-20 22:47:25 +08:00
|
|
|
|
2012-10-05 20:24:44 +08:00
|
|
|
def _showfixtures_main(config, session):
|
2015-07-19 03:39:55 +08:00
|
|
|
import _pytest.config
|
2010-11-07 17:19:58 +08:00
|
|
|
session.perform_collect()
|
2012-10-09 22:49:04 +08:00
|
|
|
curdir = py.path.local()
|
2015-07-19 03:39:55 +08:00
|
|
|
tw = _pytest.config.create_terminal_writer(config)
|
2010-09-26 00:23:26 +08:00
|
|
|
verbose = config.getvalue("verbose")
|
2012-10-09 22:49:04 +08:00
|
|
|
|
|
|
|
fm = session._fixturemanager
|
|
|
|
|
|
|
|
available = []
|
2016-08-02 04:59:51 +08:00
|
|
|
seen = set()
|
|
|
|
|
2015-07-13 04:32:39 +08:00
|
|
|
for argname, fixturedefs in fm._arg2fixturedefs.items():
|
2012-10-16 19:59:12 +08:00
|
|
|
assert fixturedefs is not None
|
|
|
|
if not fixturedefs:
|
2012-10-09 22:49:04 +08:00
|
|
|
continue
|
2016-05-31 01:00:31 +08:00
|
|
|
for fixturedef in fixturedefs:
|
|
|
|
loc = getlocation(fixturedef.func, curdir)
|
2016-08-17 06:33:07 +08:00
|
|
|
if (fixturedef.argname, loc) in seen:
|
2016-08-02 04:59:51 +08:00
|
|
|
continue
|
2016-08-17 06:33:07 +08:00
|
|
|
seen.add((fixturedef.argname, loc))
|
2016-05-31 01:00:31 +08:00
|
|
|
available.append((len(fixturedef.baseid),
|
|
|
|
fixturedef.func.__module__,
|
|
|
|
curdir.bestrelpath(loc),
|
2016-08-17 06:33:07 +08:00
|
|
|
fixturedef.argname, fixturedef))
|
2012-10-09 22:49:04 +08:00
|
|
|
|
|
|
|
available.sort()
|
2012-10-17 18:57:05 +08:00
|
|
|
currentmodule = None
|
|
|
|
for baseid, module, bestrel, argname, fixturedef in available:
|
|
|
|
if currentmodule != module:
|
|
|
|
if not module.startswith("_pytest."):
|
|
|
|
tw.line()
|
2017-07-17 07:25:08 +08:00
|
|
|
tw.sep("-", "fixtures defined from %s" % (module,))
|
2012-10-17 18:57:05 +08:00
|
|
|
currentmodule = module
|
|
|
|
if verbose <= 0 and argname[0] == "_":
|
|
|
|
continue
|
2012-10-09 22:49:04 +08:00
|
|
|
if verbose > 0:
|
2017-07-17 07:25:08 +08:00
|
|
|
funcargspec = "%s -- %s" % (argname, bestrel,)
|
2012-10-09 22:49:04 +08:00
|
|
|
else:
|
2012-10-17 18:57:05 +08:00
|
|
|
funcargspec = argname
|
2012-10-09 22:49:04 +08:00
|
|
|
tw.line(funcargspec, green=True)
|
|
|
|
loc = getlocation(fixturedef.func, curdir)
|
|
|
|
doc = fixturedef.func.__doc__ or ""
|
|
|
|
if doc:
|
2017-07-15 19:33:11 +08:00
|
|
|
write_docstring(tw, doc)
|
2012-10-09 22:49:04 +08:00
|
|
|
else:
|
2017-07-17 07:25:08 +08:00
|
|
|
tw.line(" %s: no docstring available" % (loc,),
|
2017-07-17 07:25:07 +08:00
|
|
|
red=True)
|
2010-09-26 00:23:26 +08:00
|
|
|
|
2010-11-06 06:37:31 +08:00
|
|
|
|
2017-07-15 19:33:11 +08:00
|
|
|
def write_docstring(tw, doc):
|
|
|
|
INDENT = " "
|
|
|
|
doc = doc.rstrip()
|
|
|
|
if "\n" in doc:
|
|
|
|
firstline, rest = doc.split("\n", 1)
|
|
|
|
else:
|
|
|
|
firstline, rest = doc, ""
|
|
|
|
|
|
|
|
if firstline.strip():
|
|
|
|
tw.line(INDENT + firstline.strip())
|
|
|
|
|
|
|
|
if rest:
|
|
|
|
for line in dedent(rest).split("\n"):
|
|
|
|
tw.write(INDENT + line + "\n")
|
|
|
|
|
|
|
|
|
2017-12-17 22:19:01 +08:00
|
|
|
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
2012-06-25 23:35:33 +08:00
|
|
|
""" a Function Item is responsible for setting up and executing a
|
|
|
|
Python test function.
|
|
|
|
"""
|
|
|
|
_genid = None
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2012-10-16 19:47:59 +08:00
|
|
|
def __init__(self, name, parent, args=None, config=None,
|
2014-10-06 20:06:17 +08:00
|
|
|
callspec=None, callobj=NOTSET, keywords=None, session=None,
|
2016-08-04 09:56:12 +08:00
|
|
|
fixtureinfo=None, originalname=None):
|
2012-06-25 23:35:33 +08:00
|
|
|
super(Function, self).__init__(name, parent, config=config,
|
|
|
|
session=session)
|
|
|
|
self._args = args
|
2013-11-21 20:15:32 +08:00
|
|
|
if callobj is not NOTSET:
|
2012-10-16 19:48:00 +08:00
|
|
|
self.obj = callobj
|
|
|
|
|
2014-10-06 20:06:17 +08:00
|
|
|
self.keywords.update(self.obj.__dict__)
|
2013-05-17 16:46:36 +08:00
|
|
|
if callspec:
|
2013-04-22 16:35:48 +08:00
|
|
|
self.callspec = callspec
|
2017-08-10 15:05:22 +08:00
|
|
|
# this is total hostile and a mess
|
|
|
|
# keywords are broken by design by now
|
|
|
|
# this will be redeemed later
|
|
|
|
for mark in callspec.marks:
|
|
|
|
# feel free to cry, this was broken for years before
|
|
|
|
# and keywords cant fix it per design
|
|
|
|
self.keywords[mark.name] = mark
|
2014-10-06 20:06:17 +08:00
|
|
|
if keywords:
|
|
|
|
self.keywords.update(keywords)
|
|
|
|
|
|
|
|
if fixtureinfo is None:
|
|
|
|
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
|
|
|
|
self.parent, self.obj, self.cls,
|
|
|
|
funcargs=not self._isyieldedfunction())
|
|
|
|
self._fixtureinfo = fixtureinfo
|
|
|
|
self.fixturenames = fixtureinfo.names_closure
|
2013-04-22 16:35:48 +08:00
|
|
|
self._initrequest()
|
|
|
|
|
2016-08-04 09:56:12 +08:00
|
|
|
#: original function name, without any decorations (for example
|
|
|
|
#: parametrization adds a ``"[...]"`` suffix to function names).
|
|
|
|
#:
|
|
|
|
#: .. versionadded:: 3.0
|
|
|
|
self.originalname = originalname
|
|
|
|
|
2013-04-22 16:35:48 +08:00
|
|
|
def _initrequest(self):
|
2013-12-07 23:37:46 +08:00
|
|
|
self.funcargs = {}
|
2013-04-22 16:35:48 +08:00
|
|
|
if self._isyieldedfunction():
|
|
|
|
assert not hasattr(self, "callspec"), (
|
2012-06-25 23:35:33 +08:00
|
|
|
"yielded functions (deprecated) cannot have funcargs")
|
|
|
|
else:
|
2013-04-22 16:35:48 +08:00
|
|
|
if hasattr(self, "callspec"):
|
|
|
|
callspec = self.callspec
|
2013-12-07 23:37:46 +08:00
|
|
|
assert not callspec.funcargs
|
2012-06-25 23:35:33 +08:00
|
|
|
self._genid = callspec.id
|
|
|
|
if hasattr(callspec, "param"):
|
|
|
|
self.param = callspec.param
|
2016-07-10 02:36:00 +08:00
|
|
|
self._request = fixtures.FixtureRequest(self)
|
2012-10-05 16:21:35 +08:00
|
|
|
|
2012-06-25 23:35:33 +08:00
|
|
|
@property
|
|
|
|
def function(self):
|
|
|
|
"underlying python 'function' object"
|
|
|
|
return getattr(self.obj, 'im_func', self.obj)
|
|
|
|
|
|
|
|
def _getobj(self):
|
|
|
|
name = self.name
|
2017-07-17 07:25:09 +08:00
|
|
|
i = name.find("[") # parametrization
|
2012-06-25 23:35:33 +08:00
|
|
|
if i != -1:
|
|
|
|
name = name[:i]
|
|
|
|
return getattr(self.parent.obj, name)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _pyfuncitem(self):
|
|
|
|
"(compatonly) for code expecting pytest-2.2 style request objects"
|
|
|
|
return self
|
|
|
|
|
|
|
|
def _isyieldedfunction(self):
|
|
|
|
return getattr(self, "_args", None) is not None
|
|
|
|
|
|
|
|
def runtest(self):
|
|
|
|
""" execute the underlying test function. """
|
|
|
|
self.ihook.pytest_pyfunc_call(pyfuncitem=self)
|
|
|
|
|
|
|
|
def setup(self):
|
|
|
|
super(Function, self).setup()
|
2016-07-10 02:36:00 +08:00
|
|
|
fixtures.fillfixtures(self)
|