Merge pull request #3291 from RonnyPfannschmidt/small-moves

move some code and make nodeid computed early
This commit is contained in:
Bruno Oliveira 2018-03-12 18:49:13 -03:00 committed by GitHub
commit 0557ab431f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 79 additions and 78 deletions

View File

@ -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:

View File

@ -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, )

View File

@ -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

View File

@ -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, "

View File

@ -0,0 +1 @@
``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` constructors.

View File

@ -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)