Use a PurePath instance to do matching against patterns in assertion rewrite
This way we don't need to have real file system path, which prevents the original #3973 bug.
This commit is contained in:
parent
1df6d28080
commit
37d2469266
|
@ -16,7 +16,8 @@ import atomicwrites
|
||||||
import py
|
import py
|
||||||
|
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
|
from _pytest.compat import PurePath, spec_from_file_location
|
||||||
|
from _pytest.paths import fnmatch_ex
|
||||||
|
|
||||||
# pytest caches rewritten pycs in __pycache__.
|
# pytest caches rewritten pycs in __pycache__.
|
||||||
if hasattr(imp, "get_tag"):
|
if hasattr(imp, "get_tag"):
|
||||||
|
@ -45,14 +46,6 @@ else:
|
||||||
return ast.Call(a, b, c, None, None)
|
return ast.Call(a, b, c, None, None)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 4):
|
|
||||||
from importlib.util import spec_from_file_location
|
|
||||||
else:
|
|
||||||
|
|
||||||
def spec_from_file_location(*_, **__):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class AssertionRewritingHook(object):
|
class AssertionRewritingHook(object):
|
||||||
"""PEP302 Import hook which rewrites asserts."""
|
"""PEP302 Import hook which rewrites asserts."""
|
||||||
|
|
||||||
|
@ -198,18 +191,14 @@ class AssertionRewritingHook(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# For matching the name it must be as if it was a filename.
|
# For matching the name it must be as if it was a filename.
|
||||||
parts[-1] = parts[-1] + ".py"
|
path = PurePath(os.path.sep.join(parts) + ".py")
|
||||||
try:
|
|
||||||
fn_pypath = py.path.local(os.path.sep.join(parts))
|
|
||||||
except EnvironmentError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for pat in self.fnpats:
|
for pat in self.fnpats:
|
||||||
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
|
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
|
||||||
# on the name alone because we need to match against the full path
|
# on the name alone because we need to match against the full path
|
||||||
if os.path.dirname(pat):
|
if os.path.dirname(pat):
|
||||||
return False
|
return False
|
||||||
if fn_pypath.fnmatch(pat):
|
if fnmatch_ex(pat, path):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self._is_marked_for_rewrite(name, state):
|
if self._is_marked_for_rewrite(name, state):
|
||||||
|
|
|
@ -23,7 +23,7 @@ except ImportError: # pragma: no cover
|
||||||
# Only available in Python 3.4+ or as a backport
|
# Only available in Python 3.4+ or as a backport
|
||||||
enum = None
|
enum = None
|
||||||
|
|
||||||
__all__ = ["Path"]
|
__all__ = ["Path", "PurePath"]
|
||||||
|
|
||||||
_PY3 = sys.version_info > (3, 0)
|
_PY3 = sys.version_info > (3, 0)
|
||||||
_PY2 = not _PY3
|
_PY2 = not _PY3
|
||||||
|
@ -42,9 +42,9 @@ PY36 = sys.version_info[:2] >= (3, 6)
|
||||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||||
|
|
||||||
if PY36:
|
if PY36:
|
||||||
from pathlib import Path
|
from pathlib import Path, PurePath
|
||||||
else:
|
else:
|
||||||
from pathlib2 import Path
|
from pathlib2 import Path, PurePath
|
||||||
|
|
||||||
|
|
||||||
if _PY3:
|
if _PY3:
|
||||||
|
@ -56,6 +56,14 @@ else:
|
||||||
from collections import Mapping, Sequence # noqa
|
from collections import Mapping, Sequence # noqa
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 4):
|
||||||
|
from importlib.util import spec_from_file_location
|
||||||
|
else:
|
||||||
|
|
||||||
|
def spec_from_file_location(*_, **__):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _format_args(func):
|
def _format_args(func):
|
||||||
return str(signature(func))
|
return str(signature(func))
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
from .compat import Path
|
from os.path import expanduser, expandvars, isabs, sep
|
||||||
from os.path import expanduser, expandvars, isabs
|
from posixpath import sep as posix_sep
|
||||||
|
import fnmatch
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from .compat import Path, PurePath
|
||||||
|
|
||||||
|
|
||||||
def resolve_from_str(input, root):
|
def resolve_from_str(input, root):
|
||||||
|
@ -11,3 +17,32 @@ def resolve_from_str(input, root):
|
||||||
return Path(input)
|
return Path(input)
|
||||||
else:
|
else:
|
||||||
return root.joinpath(input)
|
return root.joinpath(input)
|
||||||
|
|
||||||
|
|
||||||
|
def fnmatch_ex(pattern, path):
|
||||||
|
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||||
|
|
||||||
|
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||||
|
for each part of the path, while this algorithm uses the whole path instead.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||||
|
PurePath.match().
|
||||||
|
|
||||||
|
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||||
|
this logic.
|
||||||
|
"""
|
||||||
|
path = PurePath(path)
|
||||||
|
iswin32 = sys.platform.startswith("win")
|
||||||
|
|
||||||
|
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||||
|
# Running on Windows, the pattern has no Windows path separators,
|
||||||
|
# and the pattern has one or more Posix path separators. Replace
|
||||||
|
# the Posix path separators with the Windows path separator.
|
||||||
|
pattern = pattern.replace(posix_sep, sep)
|
||||||
|
|
||||||
|
if sep not in pattern:
|
||||||
|
name = path.name
|
||||||
|
else:
|
||||||
|
name = six.text_type(path)
|
||||||
|
return fnmatch.fnmatch(name, pattern)
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from _pytest.paths import fnmatch_ex
|
||||||
|
|
||||||
|
|
||||||
|
class TestPort:
|
||||||
|
"""Test that our port of py.common.FNMatcher (fnmatch_ex) produces the same results as the
|
||||||
|
original py.path.local.fnmatch method.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.fixture(params=["pathlib", "py.path"])
|
||||||
|
def match(self, request):
|
||||||
|
if request.param == "py.path":
|
||||||
|
|
||||||
|
def match_(pattern, path):
|
||||||
|
return py.path.local(path).fnmatch(pattern)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert request.param == "pathlib"
|
||||||
|
|
||||||
|
def match_(pattern, path):
|
||||||
|
return fnmatch_ex(pattern, path)
|
||||||
|
|
||||||
|
return match_
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
drv1 = "c:"
|
||||||
|
drv2 = "d:"
|
||||||
|
else:
|
||||||
|
drv1 = "/c"
|
||||||
|
drv2 = "/d"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern, path",
|
||||||
|
[
|
||||||
|
("*.py", "foo.py"),
|
||||||
|
("*.py", "bar/foo.py"),
|
||||||
|
("test_*.py", "foo/test_foo.py"),
|
||||||
|
("tests/*.py", "tests/foo.py"),
|
||||||
|
(drv1 + "/*.py", drv1 + "/foo.py"),
|
||||||
|
(drv1 + "/foo/*.py", drv1 + "/foo/foo.py"),
|
||||||
|
("tests/**/test*.py", "tests/foo/test_foo.py"),
|
||||||
|
("tests/**/doc/test*.py", "tests/foo/bar/doc/test_foo.py"),
|
||||||
|
("tests/**/doc/**/test*.py", "tests/foo/doc/bar/test_foo.py"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_matching(self, match, pattern, path):
|
||||||
|
assert match(pattern, path)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"pattern, path",
|
||||||
|
[
|
||||||
|
("*.py", "foo.pyc"),
|
||||||
|
("*.py", "foo/foo.pyc"),
|
||||||
|
("tests/*.py", "foo/foo.py"),
|
||||||
|
(drv1 + "/*.py", drv2 + "/foo.py"),
|
||||||
|
(drv1 + "/foo/*.py", drv2 + "/foo/foo.py"),
|
||||||
|
("tests/**/test*.py", "tests/foo.py"),
|
||||||
|
("tests/**/test*.py", "foo/test_foo.py"),
|
||||||
|
("tests/**/doc/test*.py", "tests/foo/bar/doc/foo.py"),
|
||||||
|
("tests/**/doc/test*.py", "tests/foo/bar/test_foo.py"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_not_matching(self, match, pattern, path):
|
||||||
|
assert not match(pattern, path)
|
Loading…
Reference in New Issue