Merge remote-tracking branch 'upstream/master' into release-3.8.0
This commit is contained in:
@@ -67,14 +67,24 @@ class AssertionRewritingHook(object):
|
||||
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
||||
# which might result in infinite recursion (#3506)
|
||||
self._writing_pyc = False
|
||||
self._basenames_to_check_rewrite = {"conftest"}
|
||||
self._marked_for_rewrite_cache = {}
|
||||
self._session_paths_checked = False
|
||||
|
||||
def set_session(self, session):
|
||||
self.session = session
|
||||
self._session_paths_checked = False
|
||||
|
||||
def _imp_find_module(self, name, path=None):
|
||||
"""Indirection so we can mock calls to find_module originated from the hook during testing"""
|
||||
return imp.find_module(name, path)
|
||||
|
||||
def find_module(self, name, path=None):
|
||||
if self._writing_pyc:
|
||||
return None
|
||||
state = self.config._assertstate
|
||||
if self._early_rewrite_bailout(name, state):
|
||||
return None
|
||||
state.trace("find_module called for: %s" % name)
|
||||
names = name.rsplit(".", 1)
|
||||
lastname = names[-1]
|
||||
@@ -87,7 +97,7 @@ class AssertionRewritingHook(object):
|
||||
pth = path[0]
|
||||
if pth is None:
|
||||
try:
|
||||
fd, fn, desc = imp.find_module(lastname, path)
|
||||
fd, fn, desc = self._imp_find_module(lastname, path)
|
||||
except ImportError:
|
||||
return None
|
||||
if fd is not None:
|
||||
@@ -166,6 +176,44 @@ class AssertionRewritingHook(object):
|
||||
self.modules[name] = co, pyc
|
||||
return self
|
||||
|
||||
def _early_rewrite_bailout(self, name, state):
|
||||
"""
|
||||
This is a fast way to get out of rewriting modules. Profiling has
|
||||
shown that the call to imp.find_module (inside of the find_module
|
||||
from this class) is a major slowdown, so, this method tries to
|
||||
filter what we're sure won't be rewritten before getting to it.
|
||||
"""
|
||||
if self.session is not None and not self._session_paths_checked:
|
||||
self._session_paths_checked = True
|
||||
for path in self.session._initialpaths:
|
||||
# Make something as c:/projects/my_project/path.py ->
|
||||
# ['c:', 'projects', 'my_project', 'path.py']
|
||||
parts = str(path).split(os.path.sep)
|
||||
# add 'path' to basenames to be checked.
|
||||
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
|
||||
|
||||
# Note: conftest already by default in _basenames_to_check_rewrite.
|
||||
parts = name.split(".")
|
||||
if parts[-1] in self._basenames_to_check_rewrite:
|
||||
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))
|
||||
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):
|
||||
return False
|
||||
|
||||
if self._is_marked_for_rewrite(name, state):
|
||||
return False
|
||||
|
||||
state.trace("early skip of rewriting module: %s" % (name,))
|
||||
return True
|
||||
|
||||
def _should_rewrite(self, name, fn_pypath, state):
|
||||
# always rewrite conftest files
|
||||
fn = str(fn_pypath)
|
||||
@@ -185,12 +233,20 @@ class AssertionRewritingHook(object):
|
||||
state.trace("matched test file %r" % (fn,))
|
||||
return True
|
||||
|
||||
for marked in self._must_rewrite:
|
||||
if name == marked or name.startswith(marked + "."):
|
||||
state.trace("matched marked file %r (from %r)" % (name, marked))
|
||||
return True
|
||||
return self._is_marked_for_rewrite(name, state)
|
||||
|
||||
return False
|
||||
def _is_marked_for_rewrite(self, name, state):
|
||||
try:
|
||||
return self._marked_for_rewrite_cache[name]
|
||||
except KeyError:
|
||||
for marked in self._must_rewrite:
|
||||
if name == marked or name.startswith(marked + "."):
|
||||
state.trace("matched marked file %r (from %r)" % (name, marked))
|
||||
self._marked_for_rewrite_cache[name] = True
|
||||
return True
|
||||
|
||||
self._marked_for_rewrite_cache[name] = False
|
||||
return False
|
||||
|
||||
def mark_rewrite(self, *names):
|
||||
"""Mark import names as needing to be rewritten.
|
||||
@@ -207,6 +263,7 @@ class AssertionRewritingHook(object):
|
||||
):
|
||||
self._warn_already_imported(name)
|
||||
self._must_rewrite.update(names)
|
||||
self._marked_for_rewrite_cache.clear()
|
||||
|
||||
def _warn_already_imported(self, name):
|
||||
from _pytest.warning_types import PytestWarning
|
||||
@@ -245,7 +302,7 @@ class AssertionRewritingHook(object):
|
||||
|
||||
def is_package(self, name):
|
||||
try:
|
||||
fd, fn, desc = imp.find_module(name)
|
||||
fd, fn, desc = self._imp_find_module(name)
|
||||
except ImportError:
|
||||
return False
|
||||
if fd is not None:
|
||||
|
||||
@@ -51,6 +51,8 @@ def main(args=None, plugins=None):
|
||||
:arg plugins: list of plugin objects to be auto-registered during
|
||||
initialization.
|
||||
"""
|
||||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
try:
|
||||
try:
|
||||
config = _prepareconfig(args, plugins)
|
||||
@@ -69,7 +71,7 @@ def main(args=None, plugins=None):
|
||||
tw = py.io.TerminalWriter(sys.stderr)
|
||||
for msg in e.args:
|
||||
tw.line("ERROR: {}\n".format(msg), red=True)
|
||||
return 4
|
||||
return EXIT_USAGEERROR
|
||||
|
||||
|
||||
class cmdline(object): # compatibility namespace
|
||||
|
||||
@@ -383,6 +383,7 @@ class Session(nodes.FSCollector):
|
||||
self.trace = config.trace.root.get("collection")
|
||||
self._norecursepatterns = config.getini("norecursedirs")
|
||||
self.startdir = py.path.local()
|
||||
self._initialpaths = frozenset()
|
||||
# Keep track of any collected nodes in here, so we don't duplicate fixtures
|
||||
self._node_cache = {}
|
||||
|
||||
@@ -441,13 +442,14 @@ class Session(nodes.FSCollector):
|
||||
self.trace("perform_collect", self, args)
|
||||
self.trace.root.indent += 1
|
||||
self._notfound = []
|
||||
self._initialpaths = set()
|
||||
initialpaths = []
|
||||
self._initialparts = []
|
||||
self.items = items = []
|
||||
for arg in args:
|
||||
parts = self._parsearg(arg)
|
||||
self._initialparts.append(parts)
|
||||
self._initialpaths.add(parts[0])
|
||||
initialpaths.append(parts[0])
|
||||
self._initialpaths = frozenset(initialpaths)
|
||||
rep = collect_one_node(self)
|
||||
self.ihook.pytest_collectreport(report=rep)
|
||||
self.trace.root.indent -= 1
|
||||
@@ -564,7 +566,6 @@ class Session(nodes.FSCollector):
|
||||
"""Convert a dotted module name to path.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
with _patched_find_module():
|
||||
loader = pkgutil.find_loader(x)
|
||||
|
||||
@@ -67,13 +67,19 @@ exit.Exception = Exit
|
||||
|
||||
|
||||
def skip(msg="", **kwargs):
|
||||
""" skip an executing test with the given message. Note: it's usually
|
||||
better to use the pytest.mark.skipif marker to declare a test to be
|
||||
skipped under certain conditions like mismatching platforms or
|
||||
dependencies. See the pytest_skipping plugin for details.
|
||||
"""
|
||||
Skip an executing test with the given message.
|
||||
|
||||
This function should be called only during testing (setup, call or teardown) or
|
||||
during collection by using the ``allow_module_level`` flag.
|
||||
|
||||
:kwarg bool allow_module_level: allows this function to be called at
|
||||
module level, skipping the rest of the module. Default to False.
|
||||
|
||||
.. note::
|
||||
It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
|
||||
skipped under certain conditions like mismatching platforms or
|
||||
dependencies.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
allow_module_level = kwargs.pop("allow_module_level", False)
|
||||
@@ -87,10 +93,12 @@ skip.Exception = Skipped
|
||||
|
||||
|
||||
def fail(msg="", pytrace=True):
|
||||
""" explicitly fail a currently-executing test with the given Message.
|
||||
"""
|
||||
Explicitly fail an executing test with the given message.
|
||||
|
||||
:arg pytrace: if false the msg represents the full failure information
|
||||
and no python traceback will be reported.
|
||||
:param str msg: the message to show the user as reason for the failure.
|
||||
:param bool pytrace: if false the msg represents the full failure information and no
|
||||
python traceback will be reported.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
raise Failed(msg=msg, pytrace=pytrace)
|
||||
@@ -104,7 +112,15 @@ class XFailed(fail.Exception):
|
||||
|
||||
|
||||
def xfail(reason=""):
|
||||
""" xfail an executing test or setup functions with the given reason."""
|
||||
"""
|
||||
Imperatively xfail an executing test or setup functions with the given reason.
|
||||
|
||||
This function should be called only during testing (setup, call or teardown).
|
||||
|
||||
.. note::
|
||||
It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be
|
||||
xfailed under certain conditions like known bugs or missing features.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
raise XFailed(reason)
|
||||
|
||||
|
||||
@@ -435,10 +435,8 @@ class TerminalReporter(object):
|
||||
if last_item:
|
||||
self._write_progress_information_filling_space()
|
||||
else:
|
||||
past_edge = (
|
||||
self._tw.chars_on_current_line + progress_length + 1
|
||||
>= self._screen_width
|
||||
)
|
||||
w = self._width_of_current_line
|
||||
past_edge = w + progress_length + 1 >= self._screen_width
|
||||
if past_edge:
|
||||
msg = self._get_progress_information_message()
|
||||
self._tw.write(msg + "\n", cyan=True)
|
||||
@@ -462,10 +460,18 @@ class TerminalReporter(object):
|
||||
|
||||
def _write_progress_information_filling_space(self):
|
||||
msg = self._get_progress_information_message()
|
||||
fill = " " * (
|
||||
self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1
|
||||
)
|
||||
self.write(fill + msg, cyan=True)
|
||||
w = self._width_of_current_line
|
||||
fill = self._tw.fullwidth - w - 1
|
||||
self.write(msg.rjust(fill), cyan=True)
|
||||
|
||||
@property
|
||||
def _width_of_current_line(self):
|
||||
"""Return the width of current line, using the superior implementation of py-1.6 when available"""
|
||||
try:
|
||||
return self._tw.width_of_current_line
|
||||
except AttributeError:
|
||||
# py < 1.6.0
|
||||
return self._tw.chars_on_current_line
|
||||
|
||||
def pytest_collection(self):
|
||||
if not self.isatty and self.config.option.verbose >= 1:
|
||||
|
||||
Reference in New Issue
Block a user