Skip to content

Commit

Permalink
Merge pull request #3291 from RonnyPfannschmidt/small-moves
Browse files Browse the repository at this point in the history
move some code and make nodeid computed early
  • Loading branch information
nicoddemus authored Mar 12, 2018
2 parents d6ddeb3 + 0302622 commit 0557ab4
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 78 deletions.
5 changes: 1 addition & 4 deletions _pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ class Session(nodes.FSCollector):
def __init__(self, config):
nodes.FSCollector.__init__(
self, config.rootdir, parent=None,
config=config, session=self)
config=config, session=self, nodeid="")
self.testsfailed = 0
self.testscollected = 0
self.shouldstop = False
Expand All @@ -311,9 +311,6 @@ def __init__(self, config):

self.config.pluginmanager.register(self, name="session")

def _makeid(self):
return ""

@hookimpl(tryfirst=True)
def pytest_collectstart(self):
if self.shouldfail:
Expand Down
53 changes: 45 additions & 8 deletions _pytest/mark/structures.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections import namedtuple
from collections import namedtuple, MutableMapping as MappingMixin
import warnings
from operator import attrgetter
import inspect
Expand Down Expand Up @@ -27,17 +27,17 @@ def istestfunc(func):
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)
if requested_mark in ('', None, 'skip'):
mark = MARK_GEN.skip
elif requested_mark == 'xfail':
mark = MARK_GEN.xfail(run=False)
else:
raise LookupError(requested_mark)
fs, lineno = getfslineno(function)
fs, lineno = getfslineno(func)
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)


Expand All @@ -53,8 +53,8 @@ def param(cls, *values, **kw):
def param_extract_id(id=None):
return id

id = param_extract_id(**kw)
return cls(values, marks, id)
id_ = param_extract_id(**kw)
return cls(values, marks, id_)

@classmethod
def extract_from(cls, parameterset, legacy_force_tuple=False):
Expand Down Expand Up @@ -90,7 +90,7 @@ def extract_from(cls, parameterset, legacy_force_tuple=False):
return cls(argval, marks=newmarks, id=None)

@classmethod
def _for_parametrize(cls, argnames, argvalues, function, config):
def _for_parametrize(cls, argnames, argvalues, func, config):
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
force_tuple = len(argnames) == 1
Expand All @@ -102,7 +102,7 @@ def _for_parametrize(cls, argnames, argvalues, function, config):
del argvalues

if not parameters:
mark = get_empty_parameterset_mark(config, argnames, function)
mark = get_empty_parameterset_mark(config, argnames, func)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
Expand Down Expand Up @@ -328,3 +328,40 @@ def _check(self, name):


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, )
92 changes: 29 additions & 63 deletions _pytest/nodes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from __future__ import absolute_import, division, print_function
from collections import MutableMapping as MappingMixin
import os

import six
import py
import attr

import _pytest
import _pytest._code

from _pytest.mark.structures import NodeKeywords

SEP = "/"

Expand Down Expand Up @@ -66,47 +67,11 @@ def __get__(self, obj, owner):
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):
""" base class for Collector and Item the test collection tree.
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
self.name = name

Expand All @@ -120,7 +85,7 @@ def __init__(self, name, parent=None, config=None, session=None):
self.session = session or parent.session

#: 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
self.keywords = NodeKeywords(self)
Expand All @@ -131,6 +96,12 @@ def __init__(self, name, parent=None, config=None, session=None):
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}

if nodeid is not None:
self._nodeid = nodeid
else:
assert parent is not None
self._nodeid = self.parent.nodeid + "::" + self.name

@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
Expand Down Expand Up @@ -174,14 +145,7 @@ def warn(self, code, message):
@property
def nodeid(self):
""" a ::-separated string denoting its collection tree address. """
try:
return self._nodeid
except AttributeError:
self._nodeid = x = self._makeid()
return x

def _makeid(self):
return self.parent.nodeid + "::" + self.name
return self._nodeid

def __hash__(self):
return hash(self.nodeid)
Expand Down Expand Up @@ -227,7 +191,6 @@ def get_marker(self, name):
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()
item = self
for item in self.listchain():
extra_keywords.update(item.extra_keyword_matches)
return extra_keywords
Expand Down Expand Up @@ -319,31 +282,34 @@ def _prunetraceback(self, excinfo):
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):
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?
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
if rel:
name = rel
name = name.replace(os.sep, SEP)
super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath

def _check_initialpaths_for_relpath(self):
for initialpath in self.session._initialpaths:
if self.fspath.common(initialpath) == initialpath:
return self.fspath.relto(initialpath.dirname)
session = session or parent.session

if nodeid is None:
nodeid = self.fspath.relto(session.config.rootdir)

def _makeid(self):
relpath = self.fspath.relto(self.config.rootdir)
if not nodeid:
nodeid = _check_initialpaths_for_relpath(session, fspath)
if os.sep != SEP:
nodeid = nodeid.replace(os.sep, SEP)

if not relpath:
relpath = self._check_initialpaths_for_relpath()
if os.sep != SEP:
relpath = relpath.replace(os.sep, SEP)
return relpath
super(FSCollector, self).__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)


class File(FSCollector):
Expand All @@ -356,8 +322,8 @@ class Item(Node):
"""
nextitem = None

def __init__(self, name, parent=None, config=None, session=None):
super(Item, self).__init__(name, parent, config, session)
def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
super(Item, self).__init__(name, parent, config, session, nodeid=nodeid)
self._report_sections = []

#: user properties is a list of tuples (name, value) that holds user
Expand Down
4 changes: 2 additions & 2 deletions _pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
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",
dest="quiet", default=0, help="decrease verbosity."),
dest="quiet", default=0, help="decrease verbosity.")
group._addoption('-r',
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
Expand Down
1 change: 1 addition & 0 deletions changelog/3291.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` constructors.
2 changes: 1 addition & 1 deletion testing/test_resultlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_generic_path(testdir):
from _pytest.main import Session
config = testdir.parseconfig()
session = Session(config)
p1 = Node('a', config=config, session=session)
p1 = Node('a', config=config, session=session, nodeid='a')
# assert p1.fspath is None
p2 = Node('B', parent=p1)
p3 = Node('()', parent=p2)
Expand Down

0 comments on commit 0557ab4

Please sign in to comment.