Merge remote-tracking branch 'upstream/master' into release-3.10.0
This commit is contained in:
@@ -8,14 +8,13 @@ import linecache
|
||||
import sys
|
||||
import textwrap
|
||||
import tokenize
|
||||
import warnings
|
||||
from ast import PyCF_ONLY_AST as _AST_FLAG
|
||||
from bisect import bisect_right
|
||||
|
||||
import py
|
||||
import six
|
||||
|
||||
cpy_compile = compile
|
||||
|
||||
|
||||
class Source(object):
|
||||
""" an immutable object holding a source code fragment,
|
||||
@@ -161,7 +160,7 @@ class Source(object):
|
||||
filename = base + "%r %s:%d>" % (filename, fn, lineno)
|
||||
source = "\n".join(self.lines) + "\n"
|
||||
try:
|
||||
co = cpy_compile(source, filename, mode, flag)
|
||||
co = compile(source, filename, mode, flag)
|
||||
except SyntaxError:
|
||||
ex = sys.exc_info()[1]
|
||||
# re-represent syntax errors from parsing python strings
|
||||
@@ -195,7 +194,7 @@ def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
|
||||
"""
|
||||
if isinstance(source, ast.AST):
|
||||
# XXX should Source support having AST?
|
||||
return cpy_compile(source, filename, mode, flags, dont_inherit)
|
||||
return compile(source, filename, mode, flags, dont_inherit)
|
||||
_genframe = sys._getframe(1) # the caller
|
||||
s = Source(source)
|
||||
co = s.compile(filename, mode, flags, _genframe=_genframe)
|
||||
@@ -290,7 +289,11 @@ def get_statement_startend2(lineno, node):
|
||||
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
|
||||
if astnode is None:
|
||||
content = str(source)
|
||||
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
|
||||
# See #4260:
|
||||
# don't produce duplicate warnings when compiling source to find ast
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
astnode = compile(content, "source", "exec", _AST_FLAG)
|
||||
|
||||
start, end = get_statement_startend2(lineno, astnode)
|
||||
# we need to correct the end:
|
||||
|
||||
@@ -127,7 +127,7 @@ class CaptureManager(object):
|
||||
def read_global_capture(self):
|
||||
return self._global_capturing.readouterr()
|
||||
|
||||
# Fixture Control (its just forwarding, think about removing this later)
|
||||
# Fixture Control (it's just forwarding, think about removing this later)
|
||||
|
||||
def activate_fixture(self, item):
|
||||
"""If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
|
||||
|
||||
@@ -334,6 +334,14 @@ def safe_getattr(object, name, default):
|
||||
return default
|
||||
|
||||
|
||||
def safe_isclass(obj):
|
||||
"""Ignore any exception via isinstance on Python 3."""
|
||||
try:
|
||||
return isclass(obj)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _is_unittest_unexpected_success_a_failure():
|
||||
"""Return if the test suite should fail if an @expectedFailure unittest test PASSES.
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ class ArgumentError(Exception):
|
||||
class Argument(object):
|
||||
"""class that mimics the necessary behaviour of optparse.Option
|
||||
|
||||
its currently a least effort implementation
|
||||
it's currently a least effort implementation
|
||||
and ignoring choices and integer prefixes
|
||||
https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
|
||||
"""
|
||||
|
||||
@@ -619,7 +619,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
subrequest._check_scope(argname, self.scope, scope)
|
||||
|
||||
# clear sys.exc_info before invoking the fixture (python bug?)
|
||||
# if its not explicitly cleared it will leak into the call
|
||||
# if it's not explicitly cleared it will leak into the call
|
||||
exc_clear()
|
||||
try:
|
||||
# call the fixture function
|
||||
@@ -1197,6 +1197,7 @@ class FixtureManager(object):
|
||||
nodeid = p.dirpath().relto(self.config.rootdir)
|
||||
if p.sep != nodes.SEP:
|
||||
nodeid = nodeid.replace(p.sep, nodes.SEP)
|
||||
|
||||
self.parsefactories(plugin, nodeid)
|
||||
|
||||
def _getautousenames(self, nodeid):
|
||||
@@ -1301,11 +1302,18 @@ class FixtureManager(object):
|
||||
nodeid = node_or_obj.nodeid
|
||||
if holderobj in self._holderobjseen:
|
||||
return
|
||||
|
||||
from _pytest.nodes import _CompatProperty
|
||||
|
||||
self._holderobjseen.add(holderobj)
|
||||
autousenames = []
|
||||
for name in dir(holderobj):
|
||||
# The attribute can be an arbitrary descriptor, so the attribute
|
||||
# access below can raise. safe_getatt() ignores such exceptions.
|
||||
maybe_property = safe_getattr(type(holderobj), name, None)
|
||||
if isinstance(maybe_property, _CompatProperty):
|
||||
# deprecated
|
||||
continue
|
||||
obj = safe_getattr(holderobj, name, None)
|
||||
marker = getfixturemarker(obj)
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
|
||||
@@ -41,10 +41,10 @@ def pytest_namespace():
|
||||
Plugins whose users depend on the current namespace functionality should prepare to migrate to a
|
||||
namespace they actually own.
|
||||
|
||||
To support the migration its suggested to trigger ``DeprecationWarnings`` for objects they put into the
|
||||
To support the migration it's suggested to trigger ``DeprecationWarnings`` for objects they put into the
|
||||
pytest namespace.
|
||||
|
||||
An stopgap measure to avoid the warning is to monkeypatch the ``pytest`` module, but just as the
|
||||
A stopgap measure to avoid the warning is to monkeypatch the ``pytest`` module, but just as the
|
||||
``pytest_namespace`` hook this should be seen as a temporary measure to be removed in future versions after
|
||||
an appropriate transition period.
|
||||
"""
|
||||
|
||||
@@ -559,7 +559,15 @@ class Session(nodes.FSCollector):
|
||||
col = root._collectfile(argpath)
|
||||
if col:
|
||||
self._node_cache[argpath] = col
|
||||
for y in self.matchnodes(col, names):
|
||||
m = self.matchnodes(col, names)
|
||||
# If __init__.py was the only file requested, then the matched node will be
|
||||
# the corresponding Package, and the first yielded item will be the __init__
|
||||
# Module itself, so just use that. If this special case isn't taken, then all
|
||||
# the files in the package will be yielded.
|
||||
if argpath.basename == "__init__.py":
|
||||
yield next(m[0].collect())
|
||||
return
|
||||
for y in m:
|
||||
yield y
|
||||
|
||||
def _collectfile(self, path):
|
||||
|
||||
@@ -443,7 +443,7 @@ class NodeKeywords(MappingMixin):
|
||||
@attr.s(cmp=False, hash=False)
|
||||
class NodeMarkers(object):
|
||||
"""
|
||||
internal strucutre for storing marks belongong to a node
|
||||
internal structure for storing marks belonging to a node
|
||||
|
||||
..warning::
|
||||
|
||||
|
||||
@@ -230,10 +230,12 @@ class MonkeyPatch(object):
|
||||
if not isinstance(value, str):
|
||||
warnings.warn(
|
||||
pytest.PytestWarning(
|
||||
"Environment variable value {!r} should be str, converted to str implicitly".format(
|
||||
value
|
||||
"Value of environment variable {name} type should be str, but got "
|
||||
"{value!r} (type: {type}); converted to str implicitly".format(
|
||||
name=name, value=value, type=type(value).__name__
|
||||
)
|
||||
)
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
value = str(value)
|
||||
if prepend and name in os.environ:
|
||||
|
||||
@@ -35,7 +35,7 @@ get_lock_path = operator.methodcaller("joinpath", ".lock")
|
||||
|
||||
def ensure_reset_dir(path):
|
||||
"""
|
||||
ensures the given path is a empty directory
|
||||
ensures the given path is an empty directory
|
||||
"""
|
||||
if path.exists():
|
||||
rmtree(path, force=True)
|
||||
@@ -98,8 +98,8 @@ else:
|
||||
def _force_symlink(root, target, link_to):
|
||||
"""helper to create the current symlink
|
||||
|
||||
its full of race conditions that are reasonably ok to ignore
|
||||
for the contex of best effort linking to the latest testrun
|
||||
it's full of race conditions that are reasonably ok to ignore
|
||||
for the context of best effort linking to the latest testrun
|
||||
|
||||
the presumption being thatin case of much parallelism
|
||||
the inaccuracy is going to be acceptable
|
||||
@@ -116,7 +116,7 @@ def _force_symlink(root, target, link_to):
|
||||
|
||||
|
||||
def make_numbered_dir(root, prefix):
|
||||
"""create a directory with a increased number as suffix for the given prefix"""
|
||||
"""create a directory with an increased number as suffix for the given prefix"""
|
||||
for i in range(10):
|
||||
# try up to 10 times to create the folder
|
||||
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
@@ -156,7 +156,7 @@ def create_cleanup_lock(p):
|
||||
os.write(fd, spid)
|
||||
os.close(fd)
|
||||
if not lock_path.is_file():
|
||||
raise EnvironmentError("lock path got renamed after sucessfull creation")
|
||||
raise EnvironmentError("lock path got renamed after successful creation")
|
||||
return lock_path
|
||||
|
||||
|
||||
@@ -178,19 +178,29 @@ def register_cleanup_lock_removal(lock_path, register=atexit.register):
|
||||
|
||||
|
||||
def maybe_delete_a_numbered_dir(path):
|
||||
"""removes a numbered directory if its lock can be obtained"""
|
||||
"""removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
|
||||
lock_path = None
|
||||
try:
|
||||
create_cleanup_lock(path)
|
||||
lock_path = create_cleanup_lock(path)
|
||||
parent = path.parent
|
||||
|
||||
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
|
||||
path.rename(garbage)
|
||||
rmtree(garbage, force=True)
|
||||
except (OSError, EnvironmentError):
|
||||
# known races:
|
||||
# * other process did a cleanup at the same time
|
||||
# * deletable folder was found
|
||||
# * process cwd (Windows)
|
||||
return
|
||||
parent = path.parent
|
||||
|
||||
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
|
||||
path.rename(garbage)
|
||||
rmtree(garbage, force=True)
|
||||
finally:
|
||||
# if we created the lock, ensure we remove it even if we failed
|
||||
# to properly remove the numbered dir
|
||||
if lock_path is not None:
|
||||
try:
|
||||
lock_path.unlink()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
def ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
@@ -213,7 +223,7 @@ def ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
|
||||
|
||||
def try_cleanup(path, consider_lock_dead_if_created_before):
|
||||
"""tries to cleanup a folder if we can ensure its deletable"""
|
||||
"""tries to cleanup a folder if we can ensure it's deletable"""
|
||||
if ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
maybe_delete_a_numbered_dir(path)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ from _pytest.compat import NoneType
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import REGEX_TYPE
|
||||
from _pytest.compat import safe_getattr
|
||||
from _pytest.compat import safe_isclass
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.config import hookimpl
|
||||
@@ -196,7 +197,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
||||
if res is not None:
|
||||
return
|
||||
# nothing was collected elsewhere, let's do it here
|
||||
if isclass(obj):
|
||||
if safe_isclass(obj):
|
||||
if collector.istestclass(obj, name):
|
||||
Class = collector._getcustomclass("Class")
|
||||
outcome.force_result(Class(name, parent=collector))
|
||||
@@ -652,7 +653,7 @@ class Instance(PyCollector):
|
||||
_ALLOW_MARKERS = False # hack, destroy later
|
||||
# instances share the object with their parents in a way
|
||||
# that duplicates markers instances if not taken out
|
||||
# can be removed at node strucutre reorganization time
|
||||
# can be removed at node structure reorganization time
|
||||
|
||||
def _getobj(self):
|
||||
return self.parent.obj()
|
||||
@@ -1334,7 +1335,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
"""
|
||||
|
||||
_genid = None
|
||||
# disable since functions handle it themselfes
|
||||
# disable since functions handle it themselves
|
||||
_ALLOW_MARKERS = False
|
||||
|
||||
def __init__(
|
||||
|
||||
Reference in New Issue
Block a user