Merge branch 'master' into features

Conflicts:
	src/_pytest/main.py
This commit is contained in:
Daniel Hahler 2018-11-08 02:48:59 +01:00
commit 9d838fa861
14 changed files with 152 additions and 130 deletions

View File

@ -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

View File

@ -0,0 +1 @@
Replace byte/unicode helpers in test_capture with python level syntax.

View File

@ -0,0 +1 @@
Parse ``minversion`` as an actual version and not as dot-separated strings.

View File

@ -0,0 +1 @@
Fix duplicate collection due to multiple args matching the same packages.

View File

@ -1 +0,0 @@
Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class.

View File

@ -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`:

View File

@ -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

View File

@ -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()

View File

@ -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'"
% ( % (

View File

@ -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

View File

@ -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):

View File

@ -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)

View File

@ -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"

View File

@ -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()