Merge pull request #4338 from blueyed/merge-master
Merge master into features
This commit is contained in:
		
						commit
						5dd509c963
					
				| 
						 | 
					@ -30,7 +30,7 @@ Features
 | 
				
			||||||
  existing ``pytest_enter_pdb`` hook.
 | 
					  existing ``pytest_enter_pdb`` hook.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``-sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test.  See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`__ for more info.
 | 
					- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``--sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test.  See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`__ for more info.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed.
 | 
					- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed.
 | 
				
			||||||
| 
						 | 
					@ -60,6 +60,8 @@ Bug Fixes
 | 
				
			||||||
- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and
 | 
					- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and
 | 
				
			||||||
  should not be overwritten as it will lead to internal errors.
 | 
					  should not be overwritten as it will lead to internal errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `#4266 <https://github.com/pytest-dev/pytest/issues/4266>`_: Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Improved Documentation
 | 
					Improved Documentation
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					Replace byte/unicode helpers in test_capture with python level syntax.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					Parse ``minversion`` as an actual version and not as dot-separated strings.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					Fix duplicate collection due to multiple args matching the same packages.
 | 
				
			||||||
| 
						 | 
					@ -1 +0,0 @@
 | 
				
			||||||
Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.
 | 
					 | 
				
			||||||
| 
						 | 
					@ -59,9 +59,9 @@ To see a complete list of all plugins with their latest testing
 | 
				
			||||||
status against different pytest and Python versions, please visit
 | 
					status against different pytest and Python versions, please visit
 | 
				
			||||||
`plugincompat <http://plugincompat.herokuapp.com/>`_.
 | 
					`plugincompat <http://plugincompat.herokuapp.com/>`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You may also discover more plugins through a `pytest- pypi.python.org search`_.
 | 
					You may also discover more plugins through a `pytest- pypi.org search`_.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _`pytest- pypi.python.org search`: https://pypi.org/search/?q=pytest-
 | 
					.. _`pytest- pypi.org search`: https://pypi.org/search/?q=pytest-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _`available installable plugins`:
 | 
					.. _`available installable plugins`:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,8 +11,6 @@ import six
 | 
				
			||||||
import _pytest._code
 | 
					import _pytest._code
 | 
				
			||||||
from ..compat import Sequence
 | 
					from ..compat import Sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
u = six.text_type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# The _reprcompare attribute on the util module is used by the new assertion
 | 
					# The _reprcompare attribute on the util module is used by the new assertion
 | 
				
			||||||
# interpretation code and assertion rewriter to detect this plugin was
 | 
					# interpretation code and assertion rewriter to detect this plugin was
 | 
				
			||||||
# loaded and in turn call the hooks defined here as part of the
 | 
					# loaded and in turn call the hooks defined here as part of the
 | 
				
			||||||
| 
						 | 
					@ -23,9 +21,9 @@ _reprcompare = None
 | 
				
			||||||
# the re-encoding is needed for python2 repr
 | 
					# the re-encoding is needed for python2 repr
 | 
				
			||||||
# with non-ascii characters (see issue 877 and 1379)
 | 
					# with non-ascii characters (see issue 877 and 1379)
 | 
				
			||||||
def ecu(s):
 | 
					def ecu(s):
 | 
				
			||||||
    try:
 | 
					    if isinstance(s, bytes):
 | 
				
			||||||
        return u(s, "utf-8", "replace")
 | 
					        return s.decode("UTF-8", "replace")
 | 
				
			||||||
    except TypeError:
 | 
					    else:
 | 
				
			||||||
        return s
 | 
					        return s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,7 +40,7 @@ def format_explanation(explanation):
 | 
				
			||||||
    explanation = ecu(explanation)
 | 
					    explanation = ecu(explanation)
 | 
				
			||||||
    lines = _split_explanation(explanation)
 | 
					    lines = _split_explanation(explanation)
 | 
				
			||||||
    result = _format_lines(lines)
 | 
					    result = _format_lines(lines)
 | 
				
			||||||
    return u("\n").join(result)
 | 
					    return u"\n".join(result)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _split_explanation(explanation):
 | 
					def _split_explanation(explanation):
 | 
				
			||||||
| 
						 | 
					@ -52,7 +50,7 @@ def _split_explanation(explanation):
 | 
				
			||||||
    Any other newlines will be escaped and appear in the line as the
 | 
					    Any other newlines will be escaped and appear in the line as the
 | 
				
			||||||
    literal '\n' characters.
 | 
					    literal '\n' characters.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    raw_lines = (explanation or u("")).split("\n")
 | 
					    raw_lines = (explanation or u"").split("\n")
 | 
				
			||||||
    lines = [raw_lines[0]]
 | 
					    lines = [raw_lines[0]]
 | 
				
			||||||
    for values in raw_lines[1:]:
 | 
					    for values in raw_lines[1:]:
 | 
				
			||||||
        if values and values[0] in ["{", "}", "~", ">"]:
 | 
					        if values and values[0] in ["{", "}", "~", ">"]:
 | 
				
			||||||
| 
						 | 
					@ -77,13 +75,13 @@ def _format_lines(lines):
 | 
				
			||||||
    for line in lines[1:]:
 | 
					    for line in lines[1:]:
 | 
				
			||||||
        if line.startswith("{"):
 | 
					        if line.startswith("{"):
 | 
				
			||||||
            if stackcnt[-1]:
 | 
					            if stackcnt[-1]:
 | 
				
			||||||
                s = u("and   ")
 | 
					                s = u"and   "
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                s = u("where ")
 | 
					                s = u"where "
 | 
				
			||||||
            stack.append(len(result))
 | 
					            stack.append(len(result))
 | 
				
			||||||
            stackcnt[-1] += 1
 | 
					            stackcnt[-1] += 1
 | 
				
			||||||
            stackcnt.append(0)
 | 
					            stackcnt.append(0)
 | 
				
			||||||
            result.append(u(" +") + u("  ") * (len(stack) - 1) + s + line[1:])
 | 
					            result.append(u" +" + u"  " * (len(stack) - 1) + s + line[1:])
 | 
				
			||||||
        elif line.startswith("}"):
 | 
					        elif line.startswith("}"):
 | 
				
			||||||
            stack.pop()
 | 
					            stack.pop()
 | 
				
			||||||
            stackcnt.pop()
 | 
					            stackcnt.pop()
 | 
				
			||||||
| 
						 | 
					@ -92,7 +90,7 @@ def _format_lines(lines):
 | 
				
			||||||
            assert line[0] in ["~", ">"]
 | 
					            assert line[0] in ["~", ">"]
 | 
				
			||||||
            stack[-1] += 1
 | 
					            stack[-1] += 1
 | 
				
			||||||
            indent = len(stack) if line.startswith("~") else len(stack) - 1
 | 
					            indent = len(stack) if line.startswith("~") else len(stack) - 1
 | 
				
			||||||
            result.append(u("  ") * indent + line[1:])
 | 
					            result.append(u"  " * indent + line[1:])
 | 
				
			||||||
    assert len(stack) == 1
 | 
					    assert len(stack) == 1
 | 
				
			||||||
    return result
 | 
					    return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,7 +108,7 @@ def assertrepr_compare(config, op, left, right):
 | 
				
			||||||
    left_repr = py.io.saferepr(left, maxsize=int(width // 2))
 | 
					    left_repr = py.io.saferepr(left, maxsize=int(width // 2))
 | 
				
			||||||
    right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
 | 
					    right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    summary = u("%s %s %s") % (ecu(left_repr), op, ecu(right_repr))
 | 
					    summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def issequence(x):
 | 
					    def issequence(x):
 | 
				
			||||||
        return isinstance(x, Sequence) and not isinstance(x, basestring)
 | 
					        return isinstance(x, Sequence) and not isinstance(x, basestring)
 | 
				
			||||||
| 
						 | 
					@ -155,11 +153,9 @@ def assertrepr_compare(config, op, left, right):
 | 
				
			||||||
                explanation = _notin_text(left, right, verbose)
 | 
					                explanation = _notin_text(left, right, verbose)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        explanation = [
 | 
					        explanation = [
 | 
				
			||||||
            u(
 | 
					            u"(pytest_assertion plugin: representation of details failed.  "
 | 
				
			||||||
                "(pytest_assertion plugin: representation of details failed.  "
 | 
					            u"Probably an object has a faulty __repr__.)",
 | 
				
			||||||
                "Probably an object has a faulty __repr__.)"
 | 
					            six.text_type(_pytest._code.ExceptionInfo()),
 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            u(_pytest._code.ExceptionInfo()),
 | 
					 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not explanation:
 | 
					    if not explanation:
 | 
				
			||||||
| 
						 | 
					@ -203,8 +199,7 @@ def _diff_text(left, right, verbose=False):
 | 
				
			||||||
        if i > 42:
 | 
					        if i > 42:
 | 
				
			||||||
            i -= 10  # Provide some context
 | 
					            i -= 10  # Provide some context
 | 
				
			||||||
            explanation = [
 | 
					            explanation = [
 | 
				
			||||||
                u("Skipping %s identical leading characters in diff, use -v to show")
 | 
					                u"Skipping %s identical leading characters in diff, use -v to show" % i
 | 
				
			||||||
                % i
 | 
					 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
            left = left[i:]
 | 
					            left = left[i:]
 | 
				
			||||||
            right = right[i:]
 | 
					            right = right[i:]
 | 
				
			||||||
| 
						 | 
					@ -215,11 +210,8 @@ def _diff_text(left, right, verbose=False):
 | 
				
			||||||
            if i > 42:
 | 
					            if i > 42:
 | 
				
			||||||
                i -= 10  # Provide some context
 | 
					                i -= 10  # Provide some context
 | 
				
			||||||
                explanation += [
 | 
					                explanation += [
 | 
				
			||||||
                    u(
 | 
					                    u"Skipping {} identical trailing "
 | 
				
			||||||
                        "Skipping %s identical trailing "
 | 
					                    u"characters in diff, use -v to show".format(i)
 | 
				
			||||||
                        "characters in diff, use -v to show"
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    % i
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
                left = left[:-i]
 | 
					                left = left[:-i]
 | 
				
			||||||
                right = right[:-i]
 | 
					                right = right[:-i]
 | 
				
			||||||
| 
						 | 
					@ -237,21 +229,21 @@ def _diff_text(left, right, verbose=False):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _compare_eq_iterable(left, right, verbose=False):
 | 
					def _compare_eq_iterable(left, right, verbose=False):
 | 
				
			||||||
    if not verbose:
 | 
					    if not verbose:
 | 
				
			||||||
        return [u("Use -v to get the full diff")]
 | 
					        return [u"Use -v to get the full diff"]
 | 
				
			||||||
    # dynamic import to speedup pytest
 | 
					    # dynamic import to speedup pytest
 | 
				
			||||||
    import difflib
 | 
					    import difflib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        left_formatting = pprint.pformat(left).splitlines()
 | 
					        left_formatting = pprint.pformat(left).splitlines()
 | 
				
			||||||
        right_formatting = pprint.pformat(right).splitlines()
 | 
					        right_formatting = pprint.pformat(right).splitlines()
 | 
				
			||||||
        explanation = [u("Full diff:")]
 | 
					        explanation = [u"Full diff:"]
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
 | 
					        # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
 | 
				
			||||||
        # sorted() on a list would raise. See issue #718.
 | 
					        # sorted() on a list would raise. See issue #718.
 | 
				
			||||||
        # As a workaround, the full diff is generated by using the repr() string of each item of each container.
 | 
					        # As a workaround, the full diff is generated by using the repr() string of each item of each container.
 | 
				
			||||||
        left_formatting = sorted(repr(x) for x in left)
 | 
					        left_formatting = sorted(repr(x) for x in left)
 | 
				
			||||||
        right_formatting = sorted(repr(x) for x in right)
 | 
					        right_formatting = sorted(repr(x) for x in right)
 | 
				
			||||||
        explanation = [u("Full diff (fallback to calling repr on each item):")]
 | 
					        explanation = [u"Full diff (fallback to calling repr on each item):"]
 | 
				
			||||||
    explanation.extend(
 | 
					    explanation.extend(
 | 
				
			||||||
        line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
 | 
					        line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					@ -262,16 +254,16 @@ def _compare_eq_sequence(left, right, verbose=False):
 | 
				
			||||||
    explanation = []
 | 
					    explanation = []
 | 
				
			||||||
    for i in range(min(len(left), len(right))):
 | 
					    for i in range(min(len(left), len(right))):
 | 
				
			||||||
        if left[i] != right[i]:
 | 
					        if left[i] != right[i]:
 | 
				
			||||||
            explanation += [u("At index %s diff: %r != %r") % (i, left[i], right[i])]
 | 
					            explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
    if len(left) > len(right):
 | 
					    if len(left) > len(right):
 | 
				
			||||||
        explanation += [
 | 
					        explanation += [
 | 
				
			||||||
            u("Left contains more items, first extra item: %s")
 | 
					            u"Left contains more items, first extra item: %s"
 | 
				
			||||||
            % py.io.saferepr(left[len(right)])
 | 
					            % py.io.saferepr(left[len(right)])
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    elif len(left) < len(right):
 | 
					    elif len(left) < len(right):
 | 
				
			||||||
        explanation += [
 | 
					        explanation += [
 | 
				
			||||||
            u("Right contains more items, first extra item: %s")
 | 
					            u"Right contains more items, first extra item: %s"
 | 
				
			||||||
            % py.io.saferepr(right[len(left)])
 | 
					            % py.io.saferepr(right[len(left)])
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    return explanation
 | 
					    return explanation
 | 
				
			||||||
| 
						 | 
					@ -282,11 +274,11 @@ def _compare_eq_set(left, right, verbose=False):
 | 
				
			||||||
    diff_left = left - right
 | 
					    diff_left = left - right
 | 
				
			||||||
    diff_right = right - left
 | 
					    diff_right = right - left
 | 
				
			||||||
    if diff_left:
 | 
					    if diff_left:
 | 
				
			||||||
        explanation.append(u("Extra items in the left set:"))
 | 
					        explanation.append(u"Extra items in the left set:")
 | 
				
			||||||
        for item in diff_left:
 | 
					        for item in diff_left:
 | 
				
			||||||
            explanation.append(py.io.saferepr(item))
 | 
					            explanation.append(py.io.saferepr(item))
 | 
				
			||||||
    if diff_right:
 | 
					    if diff_right:
 | 
				
			||||||
        explanation.append(u("Extra items in the right set:"))
 | 
					        explanation.append(u"Extra items in the right set:")
 | 
				
			||||||
        for item in diff_right:
 | 
					        for item in diff_right:
 | 
				
			||||||
            explanation.append(py.io.saferepr(item))
 | 
					            explanation.append(py.io.saferepr(item))
 | 
				
			||||||
    return explanation
 | 
					    return explanation
 | 
				
			||||||
| 
						 | 
					@ -297,26 +289,26 @@ def _compare_eq_dict(left, right, verbose=False):
 | 
				
			||||||
    common = set(left).intersection(set(right))
 | 
					    common = set(left).intersection(set(right))
 | 
				
			||||||
    same = {k: left[k] for k in common if left[k] == right[k]}
 | 
					    same = {k: left[k] for k in common if left[k] == right[k]}
 | 
				
			||||||
    if same and verbose < 2:
 | 
					    if same and verbose < 2:
 | 
				
			||||||
        explanation += [u("Omitting %s identical items, use -vv to show") % len(same)]
 | 
					        explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
 | 
				
			||||||
    elif same:
 | 
					    elif same:
 | 
				
			||||||
        explanation += [u("Common items:")]
 | 
					        explanation += [u"Common items:"]
 | 
				
			||||||
        explanation += pprint.pformat(same).splitlines()
 | 
					        explanation += pprint.pformat(same).splitlines()
 | 
				
			||||||
    diff = {k for k in common if left[k] != right[k]}
 | 
					    diff = {k for k in common if left[k] != right[k]}
 | 
				
			||||||
    if diff:
 | 
					    if diff:
 | 
				
			||||||
        explanation += [u("Differing items:")]
 | 
					        explanation += [u"Differing items:"]
 | 
				
			||||||
        for k in diff:
 | 
					        for k in diff:
 | 
				
			||||||
            explanation += [
 | 
					            explanation += [
 | 
				
			||||||
                py.io.saferepr({k: left[k]}) + " != " + py.io.saferepr({k: right[k]})
 | 
					                py.io.saferepr({k: left[k]}) + " != " + py.io.saferepr({k: right[k]})
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
    extra_left = set(left) - set(right)
 | 
					    extra_left = set(left) - set(right)
 | 
				
			||||||
    if extra_left:
 | 
					    if extra_left:
 | 
				
			||||||
        explanation.append(u("Left contains more items:"))
 | 
					        explanation.append(u"Left contains more items:")
 | 
				
			||||||
        explanation.extend(
 | 
					        explanation.extend(
 | 
				
			||||||
            pprint.pformat({k: left[k] for k in extra_left}).splitlines()
 | 
					            pprint.pformat({k: left[k] for k in extra_left}).splitlines()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    extra_right = set(right) - set(left)
 | 
					    extra_right = set(right) - set(left)
 | 
				
			||||||
    if extra_right:
 | 
					    if extra_right:
 | 
				
			||||||
        explanation.append(u("Right contains more items:"))
 | 
					        explanation.append(u"Right contains more items:")
 | 
				
			||||||
        explanation.extend(
 | 
					        explanation.extend(
 | 
				
			||||||
            pprint.pformat({k: right[k] for k in extra_right}).splitlines()
 | 
					            pprint.pformat({k: right[k] for k in extra_right}).splitlines()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -329,14 +321,14 @@ def _notin_text(term, text, verbose=False):
 | 
				
			||||||
    tail = text[index + len(term) :]
 | 
					    tail = text[index + len(term) :]
 | 
				
			||||||
    correct_text = head + tail
 | 
					    correct_text = head + tail
 | 
				
			||||||
    diff = _diff_text(correct_text, text, verbose)
 | 
					    diff = _diff_text(correct_text, text, verbose)
 | 
				
			||||||
    newdiff = [u("%s is contained here:") % py.io.saferepr(term, maxsize=42)]
 | 
					    newdiff = [u"%s is contained here:" % py.io.saferepr(term, maxsize=42)]
 | 
				
			||||||
    for line in diff:
 | 
					    for line in diff:
 | 
				
			||||||
        if line.startswith(u("Skipping")):
 | 
					        if line.startswith(u"Skipping"):
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        if line.startswith(u("- ")):
 | 
					        if line.startswith(u"- "):
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        if line.startswith(u("+ ")):
 | 
					        if line.startswith(u"+ "):
 | 
				
			||||||
            newdiff.append(u("  ") + line[2:])
 | 
					            newdiff.append(u"  " + line[2:])
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            newdiff.append(line)
 | 
					            newdiff.append(line)
 | 
				
			||||||
    return newdiff
 | 
					    return newdiff
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -504,7 +504,7 @@ class FDCaptureBinary(object):
 | 
				
			||||||
    snap() produces `bytes`
 | 
					    snap() produces `bytes`
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    EMPTY_BUFFER = bytes()
 | 
					    EMPTY_BUFFER = b""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, targetfd, tmpfile=None):
 | 
					    def __init__(self, targetfd, tmpfile=None):
 | 
				
			||||||
        self.targetfd = targetfd
 | 
					        self.targetfd = targetfd
 | 
				
			||||||
| 
						 | 
					@ -630,7 +630,7 @@ class SysCapture(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SysCaptureBinary(SysCapture):
 | 
					class SysCaptureBinary(SysCapture):
 | 
				
			||||||
    EMPTY_BUFFER = bytes()
 | 
					    EMPTY_BUFFER = b""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def snap(self):
 | 
					    def snap(self):
 | 
				
			||||||
        res = self.tmpfile.buffer.getvalue()
 | 
					        res = self.tmpfile.buffer.getvalue()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import shlex
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import types
 | 
					import types
 | 
				
			||||||
import warnings
 | 
					import warnings
 | 
				
			||||||
 | 
					from distutils.version import LooseVersion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import py
 | 
					import py
 | 
				
			||||||
import six
 | 
					import six
 | 
				
			||||||
| 
						 | 
					@ -817,9 +818,7 @@ class Config(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        minver = self.inicfg.get("minversion", None)
 | 
					        minver = self.inicfg.get("minversion", None)
 | 
				
			||||||
        if minver:
 | 
					        if minver:
 | 
				
			||||||
            ver = minver.split(".")
 | 
					            if LooseVersion(minver) > LooseVersion(pytest.__version__):
 | 
				
			||||||
            myver = pytest.__version__.split(".")
 | 
					 | 
				
			||||||
            if myver < ver:
 | 
					 | 
				
			||||||
                raise pytest.UsageError(
 | 
					                raise pytest.UsageError(
 | 
				
			||||||
                    "%s:%d: requires pytest-%s, actual pytest-%s'"
 | 
					                    "%s:%d: requires pytest-%s, actual pytest-%s'"
 | 
				
			||||||
                    % (
 | 
					                    % (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,6 @@ from _pytest.config import directory_arg
 | 
				
			||||||
from _pytest.config import hookimpl
 | 
					from _pytest.config import hookimpl
 | 
				
			||||||
from _pytest.config import UsageError
 | 
					from _pytest.config import UsageError
 | 
				
			||||||
from _pytest.outcomes import exit
 | 
					from _pytest.outcomes import exit
 | 
				
			||||||
from _pytest.pathlib import parts
 | 
					 | 
				
			||||||
from _pytest.runner import collect_one_node
 | 
					from _pytest.runner import collect_one_node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -399,6 +398,7 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
        # Keep track of any collected nodes in here, so we don't duplicate fixtures
 | 
					        # Keep track of any collected nodes in here, so we don't duplicate fixtures
 | 
				
			||||||
        self._node_cache = {}
 | 
					        self._node_cache = {}
 | 
				
			||||||
        self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
 | 
					        self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
 | 
				
			||||||
 | 
					        self._pkg_roots = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.config.pluginmanager.register(self, name="session")
 | 
					        self.config.pluginmanager.register(self, name="session")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -505,30 +505,26 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        names = self._parsearg(arg)
 | 
					        names = self._parsearg(arg)
 | 
				
			||||||
        argpath = names.pop(0).realpath()
 | 
					        argpath = names.pop(0).realpath()
 | 
				
			||||||
        paths = set()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        root = self
 | 
					 | 
				
			||||||
        # Start with a Session root, and delve to argpath item (dir or file)
 | 
					        # Start with a Session root, and delve to argpath item (dir or file)
 | 
				
			||||||
        # and stack all Packages found on the way.
 | 
					        # and stack all Packages found on the way.
 | 
				
			||||||
        # No point in finding packages when collecting doctests
 | 
					        # No point in finding packages when collecting doctests
 | 
				
			||||||
        if not self.config.option.doctestmodules:
 | 
					        if not self.config.option.doctestmodules:
 | 
				
			||||||
            for parent in argpath.parts():
 | 
					 | 
				
			||||||
            pm = self.config.pluginmanager
 | 
					            pm = self.config.pluginmanager
 | 
				
			||||||
 | 
					            for parent in argpath.parts():
 | 
				
			||||||
                if pm._confcutdir and pm._confcutdir.relto(parent):
 | 
					                if pm._confcutdir and pm._confcutdir.relto(parent):
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if parent.isdir():
 | 
					                if parent.isdir():
 | 
				
			||||||
                    pkginit = parent.join("__init__.py")
 | 
					                    pkginit = parent.join("__init__.py")
 | 
				
			||||||
                    if pkginit.isfile():
 | 
					                    if pkginit.isfile():
 | 
				
			||||||
                        if pkginit in self._node_cache:
 | 
					                        if pkginit not in self._node_cache:
 | 
				
			||||||
                            root = self._node_cache[pkginit][0]
 | 
					                            col = self._collectfile(pkginit, handle_dupes=False)
 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            col = root._collectfile(pkginit)
 | 
					 | 
				
			||||||
                            if col:
 | 
					                            if col:
 | 
				
			||||||
                                if isinstance(col[0], Package):
 | 
					                                if isinstance(col[0], Package):
 | 
				
			||||||
                                    root = col[0]
 | 
					                                    self._pkg_roots[parent] = col[0]
 | 
				
			||||||
                                # always store a list in the cache, matchnodes expects it
 | 
					                                # always store a list in the cache, matchnodes expects it
 | 
				
			||||||
                                self._node_cache[root.fspath] = [root]
 | 
					                                self._node_cache[col[0].fspath] = [col[0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If it's a directory argument, recurse and look for any Subpackages.
 | 
					        # If it's a directory argument, recurse and look for any Subpackages.
 | 
				
			||||||
        # Let the Package collector deal with subnodes, don't collect here.
 | 
					        # Let the Package collector deal with subnodes, don't collect here.
 | 
				
			||||||
| 
						 | 
					@ -551,15 +547,20 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            ):
 | 
					            ):
 | 
				
			||||||
                dirpath = path.dirpath()
 | 
					                dirpath = path.dirpath()
 | 
				
			||||||
                if dirpath not in seen_dirs:
 | 
					                if dirpath not in seen_dirs:
 | 
				
			||||||
 | 
					                    # Collect packages first.
 | 
				
			||||||
                    seen_dirs.add(dirpath)
 | 
					                    seen_dirs.add(dirpath)
 | 
				
			||||||
                    pkginit = dirpath.join("__init__.py")
 | 
					                    pkginit = dirpath.join("__init__.py")
 | 
				
			||||||
                    if pkginit.exists() and parts(pkginit.strpath).isdisjoint(paths):
 | 
					                    if pkginit.exists():
 | 
				
			||||||
                        for x in root._collectfile(pkginit):
 | 
					                        collect_root = self._pkg_roots.get(dirpath, self)
 | 
				
			||||||
 | 
					                        for x in collect_root._collectfile(pkginit):
 | 
				
			||||||
                            yield x
 | 
					                            yield x
 | 
				
			||||||
                            paths.add(x.fspath.dirpath())
 | 
					                            if isinstance(x, Package):
 | 
				
			||||||
 | 
					                                self._pkg_roots[dirpath] = x
 | 
				
			||||||
 | 
					                if dirpath in self._pkg_roots:
 | 
				
			||||||
 | 
					                    # Do not collect packages here.
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if parts(path.strpath).isdisjoint(paths):
 | 
					                for x in self._collectfile(path):
 | 
				
			||||||
                    for x in root._collectfile(path):
 | 
					 | 
				
			||||||
                    key = (type(x), x.fspath)
 | 
					                    key = (type(x), x.fspath)
 | 
				
			||||||
                    if key in self._node_cache:
 | 
					                    if key in self._node_cache:
 | 
				
			||||||
                        yield self._node_cache[key]
 | 
					                        yield self._node_cache[key]
 | 
				
			||||||
| 
						 | 
					@ -572,7 +573,8 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            if argpath in self._node_cache:
 | 
					            if argpath in self._node_cache:
 | 
				
			||||||
                col = self._node_cache[argpath]
 | 
					                col = self._node_cache[argpath]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                col = root._collectfile(argpath)
 | 
					                collect_root = self._pkg_roots.get(argpath.dirname, self)
 | 
				
			||||||
 | 
					                col = collect_root._collectfile(argpath)
 | 
				
			||||||
                if col:
 | 
					                if col:
 | 
				
			||||||
                    self._node_cache[argpath] = col
 | 
					                    self._node_cache[argpath] = col
 | 
				
			||||||
            m = self.matchnodes(col, names)
 | 
					            m = self.matchnodes(col, names)
 | 
				
			||||||
| 
						 | 
					@ -586,13 +588,13 @@ class Session(nodes.FSCollector):
 | 
				
			||||||
            for y in m:
 | 
					            for y in m:
 | 
				
			||||||
                yield y
 | 
					                yield y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _collectfile(self, path):
 | 
					    def _collectfile(self, path, handle_dupes=True):
 | 
				
			||||||
        ihook = self.gethookproxy(path)
 | 
					        ihook = self.gethookproxy(path)
 | 
				
			||||||
        if not self.isinitpath(path):
 | 
					        if not self.isinitpath(path):
 | 
				
			||||||
            if ihook.pytest_ignore_collect(path=path, config=self.config):
 | 
					            if ihook.pytest_ignore_collect(path=path, config=self.config):
 | 
				
			||||||
                return ()
 | 
					                return ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Skip duplicate paths.
 | 
					        if handle_dupes:
 | 
				
			||||||
            keepduplicates = self.config.getoption("keepduplicates")
 | 
					            keepduplicates = self.config.getoption("keepduplicates")
 | 
				
			||||||
            if not keepduplicates:
 | 
					            if not keepduplicates:
 | 
				
			||||||
                duplicate_paths = self.config.pluginmanager._duplicatepaths
 | 
					                duplicate_paths = self.config.pluginmanager._duplicatepaths
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -545,11 +545,24 @@ class Package(Module):
 | 
				
			||||||
            proxy = self.config.hook
 | 
					            proxy = self.config.hook
 | 
				
			||||||
        return proxy
 | 
					        return proxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _collectfile(self, path):
 | 
					    def _collectfile(self, path, handle_dupes=True):
 | 
				
			||||||
        ihook = self.gethookproxy(path)
 | 
					        ihook = self.gethookproxy(path)
 | 
				
			||||||
        if not self.isinitpath(path):
 | 
					        if not self.isinitpath(path):
 | 
				
			||||||
            if ihook.pytest_ignore_collect(path=path, config=self.config):
 | 
					            if ihook.pytest_ignore_collect(path=path, config=self.config):
 | 
				
			||||||
                return ()
 | 
					                return ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if handle_dupes:
 | 
				
			||||||
 | 
					            keepduplicates = self.config.getoption("keepduplicates")
 | 
				
			||||||
 | 
					            if not keepduplicates:
 | 
				
			||||||
 | 
					                duplicate_paths = self.config.pluginmanager._duplicatepaths
 | 
				
			||||||
 | 
					                if path in duplicate_paths:
 | 
				
			||||||
 | 
					                    return ()
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    duplicate_paths.add(path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.fspath == path:  # __init__.py
 | 
				
			||||||
 | 
					            return [self]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ihook.pytest_collect_file(path=path, parent=self)
 | 
					        return ihook.pytest_collect_file(path=path, parent=self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def isinitpath(self, path):
 | 
					    def isinitpath(self, path):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -539,11 +539,8 @@ class TestAssert_reprcompare(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_mojibake(self):
 | 
					    def test_mojibake(self):
 | 
				
			||||||
        # issue 429
 | 
					        # issue 429
 | 
				
			||||||
        left = "e"
 | 
					        left = b"e"
 | 
				
			||||||
        right = "\xc3\xa9"
 | 
					        right = b"\xc3\xa9"
 | 
				
			||||||
        if not isinstance(left, bytes):
 | 
					 | 
				
			||||||
            left = bytes(left, "utf-8")
 | 
					 | 
				
			||||||
            right = bytes(right, "utf-8")
 | 
					 | 
				
			||||||
        expl = callequal(left, right)
 | 
					        expl = callequal(left, right)
 | 
				
			||||||
        for line in expl:
 | 
					        for line in expl:
 | 
				
			||||||
            assert isinstance(line, six.text_type)
 | 
					            assert isinstance(line, six.text_type)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,24 +27,6 @@ needsosdup = pytest.mark.skipif(
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def tobytes(obj):
 | 
					 | 
				
			||||||
    if isinstance(obj, text_type):
 | 
					 | 
				
			||||||
        obj = obj.encode("UTF-8")
 | 
					 | 
				
			||||||
    assert isinstance(obj, bytes)
 | 
					 | 
				
			||||||
    return obj
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def totext(obj):
 | 
					 | 
				
			||||||
    if isinstance(obj, bytes):
 | 
					 | 
				
			||||||
        obj = text_type(obj, "UTF-8")
 | 
					 | 
				
			||||||
    assert isinstance(obj, text_type)
 | 
					 | 
				
			||||||
    return obj
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def oswritebytes(fd, obj):
 | 
					 | 
				
			||||||
    os.write(fd, tobytes(obj))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def StdCaptureFD(out=True, err=True, in_=True):
 | 
					def StdCaptureFD(out=True, err=True, in_=True):
 | 
				
			||||||
    return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture)
 | 
					    return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -836,10 +818,11 @@ class TestCaptureIO(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_bytes_io():
 | 
					def test_bytes_io():
 | 
				
			||||||
    f = py.io.BytesIO()
 | 
					    f = py.io.BytesIO()
 | 
				
			||||||
    f.write(tobytes("hello"))
 | 
					    f.write(b"hello")
 | 
				
			||||||
    pytest.raises(TypeError, "f.write(totext('hello'))")
 | 
					    with pytest.raises(TypeError):
 | 
				
			||||||
 | 
					        f.write(u"hello")
 | 
				
			||||||
    s = f.getvalue()
 | 
					    s = f.getvalue()
 | 
				
			||||||
    assert s == tobytes("hello")
 | 
					    assert s == b"hello"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_dontreadfrominput():
 | 
					def test_dontreadfrominput():
 | 
				
			||||||
| 
						 | 
					@ -952,7 +935,7 @@ class TestFDCapture(object):
 | 
				
			||||||
    def test_simple(self, tmpfile):
 | 
					    def test_simple(self, tmpfile):
 | 
				
			||||||
        fd = tmpfile.fileno()
 | 
					        fd = tmpfile.fileno()
 | 
				
			||||||
        cap = capture.FDCapture(fd)
 | 
					        cap = capture.FDCapture(fd)
 | 
				
			||||||
        data = tobytes("hello")
 | 
					        data = b"hello"
 | 
				
			||||||
        os.write(fd, data)
 | 
					        os.write(fd, data)
 | 
				
			||||||
        s = cap.snap()
 | 
					        s = cap.snap()
 | 
				
			||||||
        cap.done()
 | 
					        cap.done()
 | 
				
			||||||
| 
						 | 
					@ -992,10 +975,10 @@ class TestFDCapture(object):
 | 
				
			||||||
        cap.start()
 | 
					        cap.start()
 | 
				
			||||||
        x = os.read(0, 100).strip()
 | 
					        x = os.read(0, 100).strip()
 | 
				
			||||||
        cap.done()
 | 
					        cap.done()
 | 
				
			||||||
        assert x == tobytes("")
 | 
					        assert x == b""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_writeorg(self, tmpfile):
 | 
					    def test_writeorg(self, tmpfile):
 | 
				
			||||||
        data1, data2 = tobytes("foo"), tobytes("bar")
 | 
					        data1, data2 = b"foo", b"bar"
 | 
				
			||||||
        cap = capture.FDCapture(tmpfile.fileno())
 | 
					        cap = capture.FDCapture(tmpfile.fileno())
 | 
				
			||||||
        cap.start()
 | 
					        cap.start()
 | 
				
			||||||
        tmpfile.write(data1)
 | 
					        tmpfile.write(data1)
 | 
				
			||||||
| 
						 | 
					@ -1003,7 +986,7 @@ class TestFDCapture(object):
 | 
				
			||||||
        cap.writeorg(data2)
 | 
					        cap.writeorg(data2)
 | 
				
			||||||
        scap = cap.snap()
 | 
					        scap = cap.snap()
 | 
				
			||||||
        cap.done()
 | 
					        cap.done()
 | 
				
			||||||
        assert scap == totext(data1)
 | 
					        assert scap == data1.decode("ascii")
 | 
				
			||||||
        with open(tmpfile.name, "rb") as stmp_file:
 | 
					        with open(tmpfile.name, "rb") as stmp_file:
 | 
				
			||||||
            stmp = stmp_file.read()
 | 
					            stmp = stmp_file.read()
 | 
				
			||||||
            assert stmp == data2
 | 
					            assert stmp == data2
 | 
				
			||||||
| 
						 | 
					@ -1012,17 +995,17 @@ class TestFDCapture(object):
 | 
				
			||||||
        with saved_fd(1):
 | 
					        with saved_fd(1):
 | 
				
			||||||
            cap = capture.FDCapture(1)
 | 
					            cap = capture.FDCapture(1)
 | 
				
			||||||
            cap.start()
 | 
					            cap.start()
 | 
				
			||||||
            data = tobytes("hello")
 | 
					            data = b"hello"
 | 
				
			||||||
            os.write(1, data)
 | 
					            os.write(1, data)
 | 
				
			||||||
            sys.stdout.write("whatever")
 | 
					            sys.stdout.write("whatever")
 | 
				
			||||||
            s = cap.snap()
 | 
					            s = cap.snap()
 | 
				
			||||||
            assert s == "hellowhatever"
 | 
					            assert s == "hellowhatever"
 | 
				
			||||||
            cap.suspend()
 | 
					            cap.suspend()
 | 
				
			||||||
            os.write(1, tobytes("world"))
 | 
					            os.write(1, b"world")
 | 
				
			||||||
            sys.stdout.write("qlwkej")
 | 
					            sys.stdout.write("qlwkej")
 | 
				
			||||||
            assert not cap.snap()
 | 
					            assert not cap.snap()
 | 
				
			||||||
            cap.resume()
 | 
					            cap.resume()
 | 
				
			||||||
            os.write(1, tobytes("but now"))
 | 
					            os.write(1, b"but now")
 | 
				
			||||||
            sys.stdout.write(" yes\n")
 | 
					            sys.stdout.write(" yes\n")
 | 
				
			||||||
            s = cap.snap()
 | 
					            s = cap.snap()
 | 
				
			||||||
            assert s == "but now yes\n"
 | 
					            assert s == "but now yes\n"
 | 
				
			||||||
| 
						 | 
					@ -1193,14 +1176,14 @@ class TestStdCaptureFD(TestStdCapture):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_intermingling(self):
 | 
					    def test_intermingling(self):
 | 
				
			||||||
        with self.getcapture() as cap:
 | 
					        with self.getcapture() as cap:
 | 
				
			||||||
            oswritebytes(1, "1")
 | 
					            os.write(1, b"1")
 | 
				
			||||||
            sys.stdout.write(str(2))
 | 
					            sys.stdout.write(str(2))
 | 
				
			||||||
            sys.stdout.flush()
 | 
					            sys.stdout.flush()
 | 
				
			||||||
            oswritebytes(1, "3")
 | 
					            os.write(1, b"3")
 | 
				
			||||||
            oswritebytes(2, "a")
 | 
					            os.write(2, b"a")
 | 
				
			||||||
            sys.stderr.write("b")
 | 
					            sys.stderr.write("b")
 | 
				
			||||||
            sys.stderr.flush()
 | 
					            sys.stderr.flush()
 | 
				
			||||||
            oswritebytes(2, "c")
 | 
					            os.write(2, b"c")
 | 
				
			||||||
            out, err = cap.readouterr()
 | 
					            out, err = cap.readouterr()
 | 
				
			||||||
        assert out == "123"
 | 
					        assert out == "123"
 | 
				
			||||||
        assert err == "abc"
 | 
					        assert err == "abc"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -951,26 +951,58 @@ def test_collect_init_tests(testdir):
 | 
				
			||||||
    result = testdir.runpytest(p, "--collect-only")
 | 
					    result = testdir.runpytest(p, "--collect-only")
 | 
				
			||||||
    result.stdout.fnmatch_lines(
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            "*<Module '__init__.py'>",
 | 
					            "collected 2 items",
 | 
				
			||||||
            "*<Function 'test_init'>",
 | 
					            "<Package *",
 | 
				
			||||||
            "*<Module 'test_foo.py'>",
 | 
					            "  <Module '__init__.py'>",
 | 
				
			||||||
            "*<Function 'test_foo'>",
 | 
					            "    <Function 'test_init'>",
 | 
				
			||||||
 | 
					            "  <Module 'test_foo.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_foo'>",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest("./tests", "--collect-only")
 | 
					    result = testdir.runpytest("./tests", "--collect-only")
 | 
				
			||||||
    result.stdout.fnmatch_lines(
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
        [
 | 
					        [
 | 
				
			||||||
            "*<Module '__init__.py'>",
 | 
					            "collected 2 items",
 | 
				
			||||||
            "*<Function 'test_init'>",
 | 
					            "<Package *",
 | 
				
			||||||
            "*<Module 'test_foo.py'>",
 | 
					            "  <Module '__init__.py'>",
 | 
				
			||||||
            "*<Function 'test_foo'>",
 | 
					            "    <Function 'test_init'>",
 | 
				
			||||||
 | 
					            "  <Module 'test_foo.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_foo'>",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # Ignores duplicates with "." and pkginit (#4310).
 | 
				
			||||||
 | 
					    result = testdir.runpytest("./tests", ".", "--collect-only")
 | 
				
			||||||
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "collected 2 items",
 | 
				
			||||||
 | 
					            "<Package */tests'>",
 | 
				
			||||||
 | 
					            "  <Module '__init__.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_init'>",
 | 
				
			||||||
 | 
					            "  <Module 'test_foo.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_foo'>",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # Same as before, but different order.
 | 
				
			||||||
 | 
					    result = testdir.runpytest(".", "tests", "--collect-only")
 | 
				
			||||||
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            "collected 2 items",
 | 
				
			||||||
 | 
					            "<Package */tests'>",
 | 
				
			||||||
 | 
					            "  <Module '__init__.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_init'>",
 | 
				
			||||||
 | 
					            "  <Module 'test_foo.py'>",
 | 
				
			||||||
 | 
					            "    <Function 'test_foo'>",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    result = testdir.runpytest("./tests/test_foo.py", "--collect-only")
 | 
					    result = testdir.runpytest("./tests/test_foo.py", "--collect-only")
 | 
				
			||||||
    result.stdout.fnmatch_lines(["*<Module 'test_foo.py'>", "*<Function 'test_foo'>"])
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        ["<Package */tests'>", "  <Module 'test_foo.py'>", "    <Function 'test_foo'>"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert "test_init" not in result.stdout.str()
 | 
					    assert "test_init" not in result.stdout.str()
 | 
				
			||||||
    result = testdir.runpytest("./tests/__init__.py", "--collect-only")
 | 
					    result = testdir.runpytest("./tests/__init__.py", "--collect-only")
 | 
				
			||||||
    result.stdout.fnmatch_lines(["*<Module '__init__.py'>", "*<Function 'test_init'>"])
 | 
					    result.stdout.fnmatch_lines(
 | 
				
			||||||
 | 
					        ["<Package */tests'>", "  <Module '__init__.py'>", "    <Function 'test_init'>"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    assert "test_foo" not in result.stdout.str()
 | 
					    assert "test_foo" not in result.stdout.str()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue