Merge pull request #3291 from RonnyPfannschmidt/small-moves
move some code and make nodeid computed early
This commit is contained in:
commit
0557ab431f
|
@ -300,7 +300,7 @@ class Session(nodes.FSCollector):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
nodes.FSCollector.__init__(
|
nodes.FSCollector.__init__(
|
||||||
self, config.rootdir, parent=None,
|
self, config.rootdir, parent=None,
|
||||||
config=config, session=self)
|
config=config, session=self, nodeid="")
|
||||||
self.testsfailed = 0
|
self.testsfailed = 0
|
||||||
self.testscollected = 0
|
self.testscollected = 0
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
|
@ -311,9 +311,6 @@ class Session(nodes.FSCollector):
|
||||||
|
|
||||||
self.config.pluginmanager.register(self, name="session")
|
self.config.pluginmanager.register(self, name="session")
|
||||||
|
|
||||||
def _makeid(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@hookimpl(tryfirst=True)
|
@hookimpl(tryfirst=True)
|
||||||
def pytest_collectstart(self):
|
def pytest_collectstart(self):
|
||||||
if self.shouldfail:
|
if self.shouldfail:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from collections import namedtuple
|
from collections import namedtuple, MutableMapping as MappingMixin
|
||||||
import warnings
|
import warnings
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -27,7 +27,7 @@ def istestfunc(func):
|
||||||
getattr(func, "__name__", "<lambda>") != "<lambda>"
|
getattr(func, "__name__", "<lambda>") != "<lambda>"
|
||||||
|
|
||||||
|
|
||||||
def get_empty_parameterset_mark(config, argnames, function):
|
def get_empty_parameterset_mark(config, argnames, func):
|
||||||
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||||
if requested_mark in ('', None, 'skip'):
|
if requested_mark in ('', None, 'skip'):
|
||||||
mark = MARK_GEN.skip
|
mark = MARK_GEN.skip
|
||||||
|
@ -35,9 +35,9 @@ def get_empty_parameterset_mark(config, argnames, function):
|
||||||
mark = MARK_GEN.xfail(run=False)
|
mark = MARK_GEN.xfail(run=False)
|
||||||
else:
|
else:
|
||||||
raise LookupError(requested_mark)
|
raise LookupError(requested_mark)
|
||||||
fs, lineno = getfslineno(function)
|
fs, lineno = getfslineno(func)
|
||||||
reason = "got empty parameter set %r, function %s at %s:%d" % (
|
reason = "got empty parameter set %r, function %s at %s:%d" % (
|
||||||
argnames, function.__name__, fs, lineno)
|
argnames, func.__name__, fs, lineno)
|
||||||
return mark(reason=reason)
|
return mark(reason=reason)
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,8 +53,8 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
|
||||||
def param_extract_id(id=None):
|
def param_extract_id(id=None):
|
||||||
return id
|
return id
|
||||||
|
|
||||||
id = param_extract_id(**kw)
|
id_ = param_extract_id(**kw)
|
||||||
return cls(values, marks, id)
|
return cls(values, marks, id_)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extract_from(cls, parameterset, legacy_force_tuple=False):
|
def extract_from(cls, parameterset, legacy_force_tuple=False):
|
||||||
|
@ -90,7 +90,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
|
||||||
return cls(argval, marks=newmarks, id=None)
|
return cls(argval, marks=newmarks, id=None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _for_parametrize(cls, argnames, argvalues, function, config):
|
def _for_parametrize(cls, argnames, argvalues, func, config):
|
||||||
if not isinstance(argnames, (tuple, list)):
|
if not isinstance(argnames, (tuple, list)):
|
||||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||||
force_tuple = len(argnames) == 1
|
force_tuple = len(argnames) == 1
|
||||||
|
@ -102,7 +102,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
|
||||||
del argvalues
|
del argvalues
|
||||||
|
|
||||||
if not parameters:
|
if not parameters:
|
||||||
mark = get_empty_parameterset_mark(config, argnames, function)
|
mark = get_empty_parameterset_mark(config, argnames, func)
|
||||||
parameters.append(ParameterSet(
|
parameters.append(ParameterSet(
|
||||||
values=(NOTSET,) * len(argnames),
|
values=(NOTSET,) * len(argnames),
|
||||||
marks=[mark],
|
marks=[mark],
|
||||||
|
@ -328,3 +328,40 @@ class MarkGenerator(object):
|
||||||
|
|
||||||
|
|
||||||
MARK_GEN = MarkGenerator()
|
MARK_GEN = MarkGenerator()
|
||||||
|
|
||||||
|
|
||||||
|
class NodeKeywords(MappingMixin):
|
||||||
|
def __init__(self, node):
|
||||||
|
self.node = node
|
||||||
|
self.parent = node.parent
|
||||||
|
self._markers = {node.name: True}
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
try:
|
||||||
|
return self._markers[key]
|
||||||
|
except KeyError:
|
||||||
|
if self.parent is None:
|
||||||
|
raise
|
||||||
|
return self.parent.keywords[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._markers[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
raise ValueError("cannot delete key in keywords dict")
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
seen = self._seen()
|
||||||
|
return iter(seen)
|
||||||
|
|
||||||
|
def _seen(self):
|
||||||
|
seen = set(self._markers)
|
||||||
|
if self.parent is not None:
|
||||||
|
seen.update(self.parent.keywords)
|
||||||
|
return seen
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._seen())
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<NodeKeywords for node %s>" % (self.node, )
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
from collections import MutableMapping as MappingMixin
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -7,7 +6,9 @@ import py
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
|
import _pytest._code
|
||||||
|
|
||||||
|
from _pytest.mark.structures import NodeKeywords
|
||||||
|
|
||||||
SEP = "/"
|
SEP = "/"
|
||||||
|
|
||||||
|
@ -66,47 +67,11 @@ class _CompatProperty(object):
|
||||||
return getattr(__import__('pytest'), self.name)
|
return getattr(__import__('pytest'), self.name)
|
||||||
|
|
||||||
|
|
||||||
class NodeKeywords(MappingMixin):
|
|
||||||
def __init__(self, node):
|
|
||||||
self.node = node
|
|
||||||
self.parent = node.parent
|
|
||||||
self._markers = {node.name: True}
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
try:
|
|
||||||
return self._markers[key]
|
|
||||||
except KeyError:
|
|
||||||
if self.parent is None:
|
|
||||||
raise
|
|
||||||
return self.parent.keywords[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
self._markers[key] = value
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
raise ValueError("cannot delete key in keywords dict")
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
seen = set(self._markers)
|
|
||||||
if self.parent is not None:
|
|
||||||
seen.update(self.parent.keywords)
|
|
||||||
return iter(seen)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.__iter__())
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return list(self)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<NodeKeywords for node %s>" % (self.node, )
|
|
||||||
|
|
||||||
|
|
||||||
class Node(object):
|
class Node(object):
|
||||||
""" base class for Collector and Item the test collection tree.
|
""" base class for Collector and Item the test collection tree.
|
||||||
Collector subclasses have children, Items are terminal nodes."""
|
Collector subclasses have children, Items are terminal nodes."""
|
||||||
|
|
||||||
def __init__(self, name, parent=None, config=None, session=None):
|
def __init__(self, name, parent=None, config=None, session=None, fspath=None, nodeid=None):
|
||||||
#: a unique name within the scope of the parent node
|
#: a unique name within the scope of the parent node
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
@ -120,7 +85,7 @@ class Node(object):
|
||||||
self.session = session or parent.session
|
self.session = session or parent.session
|
||||||
|
|
||||||
#: filesystem path where this node was collected from (can be None)
|
#: filesystem path where this node was collected from (can be None)
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = fspath or getattr(parent, 'fspath', None)
|
||||||
|
|
||||||
#: keywords/markers collected from all scopes
|
#: keywords/markers collected from all scopes
|
||||||
self.keywords = NodeKeywords(self)
|
self.keywords = NodeKeywords(self)
|
||||||
|
@ -131,6 +96,12 @@ class Node(object):
|
||||||
# used for storing artificial fixturedefs for direct parametrization
|
# used for storing artificial fixturedefs for direct parametrization
|
||||||
self._name2pseudofixturedef = {}
|
self._name2pseudofixturedef = {}
|
||||||
|
|
||||||
|
if nodeid is not None:
|
||||||
|
self._nodeid = nodeid
|
||||||
|
else:
|
||||||
|
assert parent is not None
|
||||||
|
self._nodeid = self.parent.nodeid + "::" + self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ihook(self):
|
def ihook(self):
|
||||||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||||
|
@ -174,14 +145,7 @@ class Node(object):
|
||||||
@property
|
@property
|
||||||
def nodeid(self):
|
def nodeid(self):
|
||||||
""" a ::-separated string denoting its collection tree address. """
|
""" a ::-separated string denoting its collection tree address. """
|
||||||
try:
|
return self._nodeid
|
||||||
return self._nodeid
|
|
||||||
except AttributeError:
|
|
||||||
self._nodeid = x = self._makeid()
|
|
||||||
return x
|
|
||||||
|
|
||||||
def _makeid(self):
|
|
||||||
return self.parent.nodeid + "::" + self.name
|
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.nodeid)
|
return hash(self.nodeid)
|
||||||
|
@ -227,7 +191,6 @@ class Node(object):
|
||||||
def listextrakeywords(self):
|
def listextrakeywords(self):
|
||||||
""" Return a set of all extra keywords in self and any parents."""
|
""" Return a set of all extra keywords in self and any parents."""
|
||||||
extra_keywords = set()
|
extra_keywords = set()
|
||||||
item = self
|
|
||||||
for item in self.listchain():
|
for item in self.listchain():
|
||||||
extra_keywords.update(item.extra_keyword_matches)
|
extra_keywords.update(item.extra_keyword_matches)
|
||||||
return extra_keywords
|
return extra_keywords
|
||||||
|
@ -319,8 +282,14 @@ class Collector(Node):
|
||||||
excinfo.traceback = ntraceback.filter()
|
excinfo.traceback = ntraceback.filter()
|
||||||
|
|
||||||
|
|
||||||
|
def _check_initialpaths_for_relpath(session, fspath):
|
||||||
|
for initial_path in session._initialpaths:
|
||||||
|
if fspath.common(initial_path) == initial_path:
|
||||||
|
return fspath.relto(initial_path.dirname)
|
||||||
|
|
||||||
|
|
||||||
class FSCollector(Collector):
|
class FSCollector(Collector):
|
||||||
def __init__(self, fspath, parent=None, config=None, session=None):
|
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
|
||||||
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
|
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
|
||||||
name = fspath.basename
|
name = fspath.basename
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
|
@ -328,22 +297,19 @@ class FSCollector(Collector):
|
||||||
if rel:
|
if rel:
|
||||||
name = rel
|
name = rel
|
||||||
name = name.replace(os.sep, SEP)
|
name = name.replace(os.sep, SEP)
|
||||||
super(FSCollector, self).__init__(name, parent, config, session)
|
|
||||||
self.fspath = fspath
|
self.fspath = fspath
|
||||||
|
|
||||||
def _check_initialpaths_for_relpath(self):
|
session = session or parent.session
|
||||||
for initialpath in self.session._initialpaths:
|
|
||||||
if self.fspath.common(initialpath) == initialpath:
|
|
||||||
return self.fspath.relto(initialpath.dirname)
|
|
||||||
|
|
||||||
def _makeid(self):
|
if nodeid is None:
|
||||||
relpath = self.fspath.relto(self.config.rootdir)
|
nodeid = self.fspath.relto(session.config.rootdir)
|
||||||
|
|
||||||
if not relpath:
|
if not nodeid:
|
||||||
relpath = self._check_initialpaths_for_relpath()
|
nodeid = _check_initialpaths_for_relpath(session, fspath)
|
||||||
if os.sep != SEP:
|
if os.sep != SEP:
|
||||||
relpath = relpath.replace(os.sep, SEP)
|
nodeid = nodeid.replace(os.sep, SEP)
|
||||||
return relpath
|
|
||||||
|
super(FSCollector, self).__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
|
||||||
|
|
||||||
|
|
||||||
class File(FSCollector):
|
class File(FSCollector):
|
||||||
|
@ -356,8 +322,8 @@ class Item(Node):
|
||||||
"""
|
"""
|
||||||
nextitem = None
|
nextitem = None
|
||||||
|
|
||||||
def __init__(self, name, parent=None, config=None, session=None):
|
def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
|
||||||
super(Item, self).__init__(name, parent, config, session)
|
super(Item, self).__init__(name, parent, config, session, nodeid=nodeid)
|
||||||
self._report_sections = []
|
self._report_sections = []
|
||||||
|
|
||||||
#: user properties is a list of tuples (name, value) that holds user
|
#: user properties is a list of tuples (name, value) that holds user
|
||||||
|
|
|
@ -22,9 +22,9 @@ from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
||||||
group._addoption('-v', '--verbose', action="count",
|
group._addoption('-v', '--verbose', action="count",
|
||||||
dest="verbose", default=0, help="increase verbosity."),
|
dest="verbose", default=0, help="increase verbosity.")
|
||||||
group._addoption('-q', '--quiet', action="count",
|
group._addoption('-q', '--quiet', action="count",
|
||||||
dest="quiet", default=0, help="decrease verbosity."),
|
dest="quiet", default=0, help="decrease verbosity.")
|
||||||
group._addoption('-r',
|
group._addoption('-r',
|
||||||
action="store", dest="reportchars", default='', metavar="chars",
|
action="store", dest="reportchars", default='', metavar="chars",
|
||||||
help="show extra test summary info as specified by chars (f)ailed, "
|
help="show extra test summary info as specified by chars (f)ailed, "
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` constructors.
|
|
@ -13,7 +13,7 @@ def test_generic_path(testdir):
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
session = Session(config)
|
session = Session(config)
|
||||||
p1 = Node('a', config=config, session=session)
|
p1 = Node('a', config=config, session=session, nodeid='a')
|
||||||
# assert p1.fspath is None
|
# assert p1.fspath is None
|
||||||
p2 = Node('B', parent=p1)
|
p2 = Node('B', parent=p1)
|
||||||
p3 = Node('()', parent=p2)
|
p3 = Node('()', parent=p2)
|
||||||
|
|
Loading…
Reference in New Issue