Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
@@ -8,6 +8,7 @@ import marshal
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import string
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
@@ -16,7 +17,8 @@ import atomicwrites
|
||||
import py
|
||||
|
||||
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__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
@@ -45,14 +47,6 @@ else:
|
||||
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):
|
||||
"""PEP302 Import hook which rewrites asserts."""
|
||||
|
||||
@@ -198,14 +192,14 @@ class AssertionRewritingHook(object):
|
||||
return False
|
||||
|
||||
# For matching the name it must be as if it was a filename.
|
||||
parts[-1] = parts[-1] + ".py"
|
||||
fn_pypath = py.path.local(os.path.sep.join(parts))
|
||||
path = PurePath(os.path.sep.join(parts) + ".py")
|
||||
|
||||
for pat in self.fnpats:
|
||||
# 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
|
||||
if os.path.dirname(pat):
|
||||
return False
|
||||
if fn_pypath.fnmatch(pat):
|
||||
if fnmatch_ex(pat, path):
|
||||
return False
|
||||
|
||||
if self._is_marked_for_rewrite(name, state):
|
||||
@@ -473,10 +467,14 @@ def _saferepr(obj):
|
||||
|
||||
"""
|
||||
r = py.io.saferepr(obj)
|
||||
if isinstance(r, six.text_type):
|
||||
return r.replace(u"\n", u"\\n")
|
||||
else:
|
||||
return r.replace(b"\n", b"\\n")
|
||||
# only occurs in python2.x, repr must return text in python3+
|
||||
if isinstance(r, bytes):
|
||||
# Represent unprintable bytes as `\x##`
|
||||
r = u"".join(
|
||||
u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode()
|
||||
for c in r
|
||||
)
|
||||
return r.replace(u"\n", u"\\n")
|
||||
|
||||
|
||||
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
|
||||
|
||||
@@ -115,15 +115,18 @@ class Cache(object):
|
||||
else:
|
||||
with f:
|
||||
json.dump(value, f, indent=2, sort_keys=True)
|
||||
self._ensure_readme()
|
||||
|
||||
def _ensure_readme(self):
|
||||
self._ensure_supporting_files()
|
||||
|
||||
def _ensure_supporting_files(self):
|
||||
"""Create supporting files in the cache dir that are not really part of the cache."""
|
||||
if self._cachedir.is_dir():
|
||||
readme_path = self._cachedir / "README.md"
|
||||
if not readme_path.is_file():
|
||||
readme_path.write_text(README_CONTENT)
|
||||
|
||||
msg = u"# created by pytest automatically, do not change\n*"
|
||||
self._cachedir.joinpath(".gitignore").write_text(msg, encoding="UTF-8")
|
||||
|
||||
|
||||
class LFPlugin(object):
|
||||
""" Plugin which implements the --lf (run last-failing) option """
|
||||
|
||||
@@ -23,7 +23,7 @@ except ImportError: # pragma: no cover
|
||||
# Only available in Python 3.4+ or as a backport
|
||||
enum = None
|
||||
|
||||
__all__ = ["Path"]
|
||||
__all__ = ["Path", "PurePath"]
|
||||
|
||||
_PY3 = sys.version_info > (3, 0)
|
||||
_PY2 = not _PY3
|
||||
@@ -42,9 +42,9 @@ PY36 = sys.version_info[:2] >= (3, 6)
|
||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||
|
||||
if PY36:
|
||||
from pathlib import Path
|
||||
from pathlib import Path, PurePath
|
||||
else:
|
||||
from pathlib2 import Path
|
||||
from pathlib2 import Path, PurePath
|
||||
|
||||
|
||||
if _PY3:
|
||||
@@ -56,6 +56,14 @@ else:
|
||||
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):
|
||||
return str(signature(func))
|
||||
|
||||
|
||||
@@ -460,8 +460,8 @@ class LoggingPlugin(object):
|
||||
try:
|
||||
yield # run test
|
||||
finally:
|
||||
del item.catch_log_handler
|
||||
if when == "teardown":
|
||||
del item.catch_log_handler
|
||||
del item.catch_log_handlers
|
||||
|
||||
if self.print_logs:
|
||||
|
||||
@@ -504,13 +504,14 @@ class Session(nodes.FSCollector):
|
||||
pkginit = parent.join("__init__.py")
|
||||
if pkginit.isfile():
|
||||
if pkginit in self._node_cache:
|
||||
root = self._node_cache[pkginit]
|
||||
root = self._node_cache[pkginit][0]
|
||||
else:
|
||||
col = root._collectfile(pkginit)
|
||||
if col:
|
||||
if isinstance(col[0], Package):
|
||||
root = col[0]
|
||||
self._node_cache[root.fspath] = root
|
||||
# always store a list in the cache, matchnodes expects it
|
||||
self._node_cache[root.fspath] = [root]
|
||||
|
||||
# If it's a directory argument, recurse and look for any Subpackages.
|
||||
# Let the Package collector deal with subnodes, don't collect here.
|
||||
@@ -530,8 +531,8 @@ class Session(nodes.FSCollector):
|
||||
if (type(x), x.fspath) in self._node_cache:
|
||||
yield self._node_cache[(type(x), x.fspath)]
|
||||
else:
|
||||
yield x
|
||||
self._node_cache[(type(x), x.fspath)] = x
|
||||
yield x
|
||||
else:
|
||||
assert argpath.check(file=1)
|
||||
|
||||
|
||||
@@ -219,8 +219,8 @@ class MonkeyPatch(object):
|
||||
self.setitem(os.environ, name, value)
|
||||
|
||||
def delenv(self, name, raising=True):
|
||||
""" Delete ``name`` from the environment. Raise KeyError it does not
|
||||
exist.
|
||||
""" Delete ``name`` from the environment. Raise KeyError if it does
|
||||
not exist.
|
||||
|
||||
If ``raising`` is set to False, no exception will be raised if the
|
||||
environment variable is missing.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
from .compat import Path
|
||||
from os.path import expanduser, expandvars, isabs
|
||||
from os.path import expanduser, expandvars, isabs, sep
|
||||
from posixpath import sep as posix_sep
|
||||
import fnmatch
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from .compat import Path, PurePath
|
||||
|
||||
|
||||
def resolve_from_str(input, root):
|
||||
@@ -11,3 +17,36 @@ def resolve_from_str(input, root):
|
||||
return Path(input)
|
||||
else:
|
||||
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.
|
||||
|
||||
References:
|
||||
* https://bugs.python.org/issue29249
|
||||
* https://bugs.python.org/issue34731
|
||||
"""
|
||||
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)
|
||||
|
||||
@@ -37,6 +37,7 @@ from _pytest.compat import (
|
||||
getlocation,
|
||||
enum,
|
||||
get_default_arg_names,
|
||||
getimfunc,
|
||||
)
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.mark.structures import (
|
||||
@@ -681,14 +682,12 @@ class Class(PyCollector):
|
||||
def setup(self):
|
||||
setup_class = _get_xunit_func(self.obj, "setup_class")
|
||||
if setup_class is not None:
|
||||
setup_class = getattr(setup_class, "im_func", setup_class)
|
||||
setup_class = getattr(setup_class, "__func__", setup_class)
|
||||
setup_class = getimfunc(setup_class)
|
||||
setup_class(self.obj)
|
||||
|
||||
fin_class = getattr(self.obj, "teardown_class", None)
|
||||
if fin_class is not None:
|
||||
fin_class = getattr(fin_class, "im_func", fin_class)
|
||||
fin_class = getattr(fin_class, "__func__", fin_class)
|
||||
fin_class = getimfunc(fin_class)
|
||||
self.addfinalizer(lambda: fin_class(self.obj))
|
||||
|
||||
|
||||
@@ -1145,13 +1144,18 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||
"""
|
||||
from _pytest.fixtures import scopes
|
||||
|
||||
indirect_as_list = isinstance(indirect, (list, tuple))
|
||||
all_arguments_are_fixtures = (
|
||||
indirect is True or indirect_as_list and len(indirect) == argnames
|
||||
)
|
||||
if isinstance(indirect, (list, tuple)):
|
||||
all_arguments_are_fixtures = len(indirect) == len(argnames)
|
||||
else:
|
||||
all_arguments_are_fixtures = bool(indirect)
|
||||
|
||||
if all_arguments_are_fixtures:
|
||||
fixturedefs = arg2fixturedefs or {}
|
||||
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
|
||||
used_scopes = [
|
||||
fixturedef[0].scope
|
||||
for name, fixturedef in fixturedefs.items()
|
||||
if name in argnames
|
||||
]
|
||||
if used_scopes:
|
||||
# Takes the most narrow scope from used fixtures
|
||||
for scope in reversed(scopes):
|
||||
@@ -1436,7 +1440,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
@property
|
||||
def function(self):
|
||||
"underlying python 'function' object"
|
||||
return getattr(self.obj, "im_func", self.obj)
|
||||
return getimfunc(self.obj)
|
||||
|
||||
def _getobj(self):
|
||||
name = self.name
|
||||
|
||||
@@ -9,6 +9,7 @@ import _pytest._code
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.outcomes import fail, skip, xfail
|
||||
from _pytest.python import transfer_markers, Class, Module, Function
|
||||
from _pytest.compat import getimfunc
|
||||
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
@@ -53,7 +54,7 @@ class UnitTestCase(Class):
|
||||
x = getattr(self.obj, name)
|
||||
if not getattr(x, "__test__", True):
|
||||
continue
|
||||
funcobj = getattr(x, "im_func", x)
|
||||
funcobj = getimfunc(x)
|
||||
transfer_markers(funcobj, cls, module)
|
||||
yield TestCaseFunction(name, parent=self, callobj=funcobj)
|
||||
foundsomething = True
|
||||
|
||||
Reference in New Issue
Block a user