Merge pull request #2862 from tom-dalton-fanduel/issue-2836-fixture-collection-bug
Issue 2836 fixture collection bug
This commit is contained in:
commit
5631a86296
1
AUTHORS
1
AUTHORS
|
@ -164,6 +164,7 @@ Stephan Obermann
|
||||||
Tareq Alayan
|
Tareq Alayan
|
||||||
Ted Xiao
|
Ted Xiao
|
||||||
Thomas Grainger
|
Thomas Grainger
|
||||||
|
Tom Dalton
|
||||||
Tom Viner
|
Tom Viner
|
||||||
Trevor Bekolay
|
Trevor Bekolay
|
||||||
Tyler Goodlet
|
Tyler Goodlet
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
import sys
|
|
||||||
|
|
||||||
from py._code.code import FormattedExcinfo
|
|
||||||
|
|
||||||
import py
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import py
|
||||||
|
from py._code.code import FormattedExcinfo
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
|
from _pytest import nodes
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest.compat import (
|
from _pytest.compat import (
|
||||||
NOTSET, exc_clear, _format_args,
|
NOTSET, exc_clear, _format_args,
|
||||||
|
@ -15,9 +16,10 @@ from _pytest.compat import (
|
||||||
is_generator, isclass, getimfunc,
|
is_generator, isclass, getimfunc,
|
||||||
getlocation, getfuncargnames,
|
getlocation, getfuncargnames,
|
||||||
safe_getattr,
|
safe_getattr,
|
||||||
|
FuncargnamesCompatAttr,
|
||||||
)
|
)
|
||||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||||
from _pytest.compat import FuncargnamesCompatAttr
|
|
||||||
|
|
||||||
if sys.version_info[:2] == (2, 6):
|
if sys.version_info[:2] == (2, 6):
|
||||||
from ordereddict import OrderedDict
|
from ordereddict import OrderedDict
|
||||||
|
@ -981,8 +983,8 @@ class FixtureManager:
|
||||||
# by their test id)
|
# by their test id)
|
||||||
if p.basename.startswith("conftest.py"):
|
if p.basename.startswith("conftest.py"):
|
||||||
nodeid = p.dirpath().relto(self.config.rootdir)
|
nodeid = p.dirpath().relto(self.config.rootdir)
|
||||||
if p.sep != "/":
|
if p.sep != nodes.SEP:
|
||||||
nodeid = nodeid.replace(p.sep, "/")
|
nodeid = nodeid.replace(p.sep, nodes.SEP)
|
||||||
self.parsefactories(plugin, nodeid)
|
self.parsefactories(plugin, nodeid)
|
||||||
|
|
||||||
def _getautousenames(self, nodeid):
|
def _getautousenames(self, nodeid):
|
||||||
|
@ -1132,5 +1134,5 @@ class FixtureManager:
|
||||||
|
|
||||||
def _matchfactories(self, fixturedefs, nodeid):
|
def _matchfactories(self, fixturedefs, nodeid):
|
||||||
for fixturedef in fixturedefs:
|
for fixturedef in fixturedefs:
|
||||||
if nodeid.startswith(fixturedef.baseid):
|
if nodes.ischildnode(fixturedef.baseid, nodeid):
|
||||||
yield fixturedef
|
yield fixturedef
|
||||||
|
|
|
@ -17,6 +17,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest import nodes
|
||||||
from _pytest.config import filename_arg
|
from _pytest.config import filename_arg
|
||||||
|
|
||||||
# Python 2.X and 3.X compatibility
|
# Python 2.X and 3.X compatibility
|
||||||
|
@ -252,7 +253,7 @@ def mangle_test_address(address):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
# convert file path to dotted path
|
# convert file path to dotted path
|
||||||
names[0] = names[0].replace("/", '.')
|
names[0] = names[0].replace(nodes.SEP, '.')
|
||||||
names[0] = _py_ext_re.sub("", names[0])
|
names[0] = _py_ext_re.sub("", names[0])
|
||||||
# put any params back
|
# put any params back
|
||||||
names[-1] += possible_open_bracket + params
|
names[-1] += possible_open_bracket + params
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import _pytest
|
import _pytest
|
||||||
|
from _pytest import nodes
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import py
|
import py
|
||||||
try:
|
try:
|
||||||
|
@ -14,8 +15,8 @@ except ImportError:
|
||||||
from UserDict import DictMixin as MappingMixin
|
from UserDict import DictMixin as MappingMixin
|
||||||
|
|
||||||
from _pytest.config import directory_arg, UsageError, hookimpl
|
from _pytest.config import directory_arg, UsageError, hookimpl
|
||||||
from _pytest.runner import collect_one_node
|
|
||||||
from _pytest.outcomes import exit
|
from _pytest.outcomes import exit
|
||||||
|
from _pytest.runner import collect_one_node
|
||||||
|
|
||||||
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
|
||||||
|
|
||||||
|
@ -516,14 +517,14 @@ class FSCollector(Collector):
|
||||||
rel = fspath.relto(parent.fspath)
|
rel = fspath.relto(parent.fspath)
|
||||||
if rel:
|
if rel:
|
||||||
name = rel
|
name = rel
|
||||||
name = name.replace(os.sep, "/")
|
name = name.replace(os.sep, nodes.SEP)
|
||||||
super(FSCollector, self).__init__(name, parent, config, session)
|
super(FSCollector, self).__init__(name, parent, config, session)
|
||||||
self.fspath = fspath
|
self.fspath = fspath
|
||||||
|
|
||||||
def _makeid(self):
|
def _makeid(self):
|
||||||
relpath = self.fspath.relto(self.config.rootdir)
|
relpath = self.fspath.relto(self.config.rootdir)
|
||||||
if os.sep != "/":
|
if os.sep != nodes.SEP:
|
||||||
relpath = relpath.replace(os.sep, "/")
|
relpath = relpath.replace(os.sep, nodes.SEP)
|
||||||
return relpath
|
return relpath
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
SEP = "/"
|
||||||
|
|
||||||
|
|
||||||
|
def _splitnode(nodeid):
|
||||||
|
"""Split a nodeid into constituent 'parts'.
|
||||||
|
|
||||||
|
Node IDs are strings, and can be things like:
|
||||||
|
''
|
||||||
|
'testing/code'
|
||||||
|
'testing/code/test_excinfo.py'
|
||||||
|
'testing/code/test_excinfo.py::TestFormattedExcinfo::()'
|
||||||
|
|
||||||
|
Return values are lists e.g.
|
||||||
|
[]
|
||||||
|
['testing', 'code']
|
||||||
|
['testing', 'code', 'test_excinfo.py']
|
||||||
|
['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()']
|
||||||
|
"""
|
||||||
|
if nodeid == '':
|
||||||
|
# If there is no root node at all, return an empty list so the caller's logic can remain sane
|
||||||
|
return []
|
||||||
|
parts = nodeid.split(SEP)
|
||||||
|
# Replace single last element 'test_foo.py::Bar::()' with multiple elements 'test_foo.py', 'Bar', '()'
|
||||||
|
parts[-1:] = parts[-1].split("::")
|
||||||
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
def ischildnode(baseid, nodeid):
|
||||||
|
"""Return True if the nodeid is a child node of the baseid.
|
||||||
|
|
||||||
|
E.g. 'foo/bar::Baz::()' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
|
||||||
|
"""
|
||||||
|
base_parts = _splitnode(baseid)
|
||||||
|
node_parts = _splitnode(nodeid)
|
||||||
|
if len(node_parts) < len(base_parts):
|
||||||
|
return False
|
||||||
|
return node_parts[:len(base_parts)] == base_parts
|
|
@ -217,7 +217,8 @@ class ApproxScalar(ApproxBase):
|
||||||
absolute tolerance or a relative tolerance, depending on what the user
|
absolute tolerance or a relative tolerance, depending on what the user
|
||||||
specified or which would be larger.
|
specified or which would be larger.
|
||||||
"""
|
"""
|
||||||
def set_default(x, default): return x if x is not None else default
|
def set_default(x, default):
|
||||||
|
return x if x is not None else default
|
||||||
|
|
||||||
# Figure out what the absolute tolerance should be. ``self.abs`` is
|
# Figure out what the absolute tolerance should be. ``self.abs`` is
|
||||||
# either None or a value specified by the user.
|
# either None or a value specified by the user.
|
||||||
|
|
|
@ -13,6 +13,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
from _pytest import nodes
|
||||||
import _pytest._pluggy as pluggy
|
import _pytest._pluggy as pluggy
|
||||||
|
|
||||||
|
|
||||||
|
@ -452,7 +453,7 @@ class TerminalReporter:
|
||||||
|
|
||||||
if fspath:
|
if fspath:
|
||||||
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
||||||
if nodeid.split("::")[0] != fspath.replace("\\", "/"):
|
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
|
||||||
res += " <- " + self.startdir.bestrelpath(fspath)
|
res += " <- " + self.startdir.bestrelpath(fspath)
|
||||||
else:
|
else:
|
||||||
res = "[location]"
|
res = "[location]"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Match fixture paths against actual path segments in order to avoid matching folders which share a prefix.
|
|
@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function
|
||||||
import pytest
|
import pytest
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
import _pytest._code
|
||||||
from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv
|
from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv
|
||||||
|
|
||||||
|
|
||||||
|
@ -830,3 +831,28 @@ def test_continue_on_collection_errors_maxfail(testdir):
|
||||||
"*Interrupted: stopping after 3 failures*",
|
"*Interrupted: stopping after 3 failures*",
|
||||||
"*1 failed, 2 error*",
|
"*1 failed, 2 error*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixture_scope_sibling_conftests(testdir):
|
||||||
|
"""Regression test case for https://github.com/pytest-dev/pytest/issues/2836"""
|
||||||
|
foo_path = testdir.mkpydir("foo")
|
||||||
|
foo_path.join("conftest.py").write(_pytest._code.Source("""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def fix():
|
||||||
|
return 1
|
||||||
|
"""))
|
||||||
|
foo_path.join("test_foo.py").write("def test_foo(fix): assert fix == 1")
|
||||||
|
|
||||||
|
# Tests in `food/` should not see the conftest fixture from `foo/`
|
||||||
|
food_path = testdir.mkpydir("food")
|
||||||
|
food_path.join("test_food.py").write("def test_food(fix): assert fix == 1")
|
||||||
|
|
||||||
|
res = testdir.runpytest()
|
||||||
|
assert res.ret == 1
|
||||||
|
|
||||||
|
res.stdout.fnmatch_lines([
|
||||||
|
"*ERROR at setup of test_food*",
|
||||||
|
"E*fixture 'fix' not found",
|
||||||
|
"*1 passed, 1 error*",
|
||||||
|
])
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from _pytest import nodes
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("baseid, nodeid, expected", (
|
||||||
|
('', '', True),
|
||||||
|
('', 'foo', True),
|
||||||
|
('', 'foo/bar', True),
|
||||||
|
('', 'foo/bar::TestBaz::()', True),
|
||||||
|
('foo', 'food', False),
|
||||||
|
('foo/bar::TestBaz::()', 'foo/bar', False),
|
||||||
|
('foo/bar::TestBaz::()', 'foo/bar::TestBop::()', False),
|
||||||
|
('foo/bar', 'foo/bar::TestBop::()', True),
|
||||||
|
))
|
||||||
|
def test_ischildnode(baseid, nodeid, expected):
|
||||||
|
result = nodes.ischildnode(baseid, nodeid)
|
||||||
|
assert result is expected
|
Loading…
Reference in New Issue