Skip to content

Commit

Permalink
Merge branch 'my-master' into revert
Browse files Browse the repository at this point in the history
  • Loading branch information
blueyed committed Feb 20, 2020
2 parents 338c7ba + 0de3525 commit c426585
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 127 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
- name: "windows-py37"
python: "3.7"
os: windows-latest
tox_env: "py37-coverage"
tox_env: "py37-xdist-coverage"
script_prefix: "env PYTEST_REORDER_TESTS=0"
# Coverage for:
# - verbosity=2
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
- name: Cache .tox
uses: actions/cache@v1
with:
path: ~/work/pytest/pytest/.tox/${{ matrix.tox_env }}
path: ${{ env.GITHUB_WORKSPACE }}/pytest/pytest/.tox/${{ matrix.tox_env }}
key: tox|${{ matrix.tox_env }}|${{ env.PY_CACHE_KEY }}|${{ hashFiles('tox.ini') }}|${{ hashFiles('setup.*') }}
- name: Cache .pre-commit
if: (matrix.tox_env == 'linting')
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
before_install:
- python -m pip install -U pip==19.3.1
# Work around https://github.com/jaraco/zipp/issues/40.
- python -m pip install -U 'setuptools>=34.4.0' virtualenv
- python -m pip install -U 'setuptools>=34.4.0' virtualenv==16.7.9

before_script:
- python -m tox --notest -v --durations
Expand Down
7 changes: 6 additions & 1 deletion src/_pytest/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
support for presenting detailed information in failing assertions.
"""
import sys
from typing import Any
from typing import List
from typing import Optional

from _pytest.assertion import rewrite
from _pytest.assertion import truncate
from _pytest.assertion import util
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import hookimpl

if TYPE_CHECKING:
Expand Down Expand Up @@ -169,5 +172,7 @@ def pytest_sessionfinish(session):
assertstate.hook.set_session(None)


def pytest_assertrepr_compare(config, op, left, right):
def pytest_assertrepr_compare(
config: Config, op: str, left: Any, right: Any
) -> Optional[List[str]]:
return util.assertrepr_compare(config=config, op=op, left=left, right=right)
79 changes: 57 additions & 22 deletions src/_pytest/assertion/util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Utilities for assertion debugging"""
import collections.abc
import itertools
import os
import pprint
import re
from typing import AbstractSet
from typing import Any
from typing import Callable
Expand Down Expand Up @@ -129,23 +131,14 @@ def isiterable(obj: Any) -> bool:
return False


def _gets_full_diff(op: str, left: Any, right: Any, verbose: int) -> bool:
# via _compare_eq_iterable
return verbose > 0 and op == "==" and isiterable(left) and isiterable(right)


def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands"""
verbose = config.getoption("verbose")
if verbose > 1:
left_repr = safeformat(left)
right_repr = safeformat(right)
else:
# XXX: "15 chars indentation" is wrong
# ("E AssertionError: assert "); should use term width.
maxsize = (
80 - 15 - len(op) - 2
) // 2 # 15 chars indentation, 1 space around op
left_repr = saferepr(left, maxsize=maxsize)
right_repr = saferepr(right, maxsize=maxsize)

summary = "{} {} {}".format(left_repr, op, right_repr)

explanation = None
try:
if op == "==":
Expand All @@ -163,6 +156,7 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
explanation = _compare_eq_cls(left, right, verbose, type_fn)
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)

if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
if explanation is not None:
Expand All @@ -185,6 +179,21 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
if not explanation:
return None

# Summary.
has_full_diff = "Full diff:" in explanation
if verbose > 1 and not has_full_diff:
left_repr = safeformat(left)
right_repr = safeformat(right)
else:
# XXX: "15 chars indentation" is wrong
# ("E AssertionError: assert "); should use term width.
maxsize = (
80 - 15 - len(op) - 2
) // 2 # 15 chars indentation, 1 space around op
left_repr = saferepr(left, maxsize=maxsize)
right_repr = saferepr(right, maxsize=maxsize)
summary = "{} {} {}".format(left_repr, op, right_repr)

return [summary] + explanation


Expand All @@ -195,7 +204,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
characters which are identical to keep the diff minimal.
"""
from difflib import ndiff
from wcwidth import wcswidth
from wcwidth import wcwidth

explanation = [] # type: List[str]

Expand Down Expand Up @@ -223,21 +232,44 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
]
left = left[:-i]
right = right[:-i]
keepends = True
if left.isspace() or right.isspace():
left = repr(str(left))
right = repr(str(right))
explanation += ["Strings contain only whitespace, escaping them using repr()"]

left_lines = left.splitlines(keepends)
right_lines = right.splitlines(keepends)
left_split = len(left) and re.split("(\r?\n)", left) or []
left_lines = left_split[::2]
right_split = len(right) and re.split("(\r?\n)", right) or []
right_lines = right_split[::2]

if any(wcswidth(x) == -1 for x in left_lines + right_lines):
if any(
wcwidth(ch) <= 0
for ch in [ch for lines in left_lines + right_lines for ch in lines]
):
left_lines = [repr(x) for x in left_lines]
right_lines = [repr(x) for x in right_lines]
explanation += [
"Strings contain non-printable/escape characters, escaping them using repr()"
"NOTE: Strings contain non-printable characters. Escaping them using repr()."
]
else:
max_split = min(len(left_lines), len(right_lines)) + 1
left_ends = left_split[1:max_split:2]
right_ends = right_split[1:max_split:2]
if left_ends != right_ends:
explanation += [
"NOTE: Strings contain different line-endings. Escaping them using repr()."
]
for idx, (left_line, right_line, left_end, right_end) in enumerate(
itertools.zip_longest(
left_lines, right_lines, left_ends, right_ends, fillvalue=None
)
):
if left_end == right_end:
continue
if left_end is not None:
left_lines[idx] += repr(left_end)[1:-1]
if right_end is not None:
right_lines[idx] += repr(right_end)[1:-1]

explanation += [line.strip("\n") for line in ndiff(left_lines, right_lines)]
return explanation
Expand Down Expand Up @@ -325,8 +357,11 @@ def _compare_eq_sequence(

left_repr = repr(left_value)
right_repr = repr(right_value)
gets_full_diff = verbose > 0 # via _compare_eq_iterable later.
if not gets_full_diff and len(left_repr) > 10 and len(right_repr) > 10:
if (
not _gets_full_diff("==", left, right, verbose)
and len(left_repr) > 10
and len(right_repr) > 10
):
explanation += [
"At index {} diff:".format(i),
"{} !=".format(left_repr),
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def pytest_ignore_collect(self, path):
skip_it = Path(path) not in self.last_failed_paths()
if skip_it:
self._skipped_files += 1
return skip_it
return True

def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0:
Expand Down
16 changes: 7 additions & 9 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ def __getattr__(self, name):
class MultiCapture:
out = err = in_ = None
_state = None
_in_suspended = False

def __init__(self, out=True, err=True, in_=True, Capture=None):
if in_:
Expand All @@ -459,11 +460,7 @@ def __init__(self, out=True, err=True, in_=True, Capture=None):

def __repr__(self):
return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
self.out,
self.err,
self.in_,
self._state,
getattr(self, "_in_suspended", "<UNSET>"),
self.out, self.err, self.in_, self._state, self._in_suspended,
)

def start_capturing(self):
Expand Down Expand Up @@ -500,9 +497,9 @@ def resume_capturing(self):
self.out.resume()
if self.err:
self.err.resume()
if hasattr(self, "_in_suspended"):
if self._in_suspended:
self.in_.resume()
del self._in_suspended
self._in_suspended = False

def stop_capturing(self):
""" stop capturing and reset capturing streams """
Expand Down Expand Up @@ -631,8 +628,9 @@ def snap(self):


class SysCapture:
class CLOSE_STDIN:
pass

CLOSE_STDIN = object
EMPTY_BUFFER = str()
_state = None

Expand Down Expand Up @@ -667,7 +665,7 @@ def snap(self):

def done(self):
setattr(sys, self.name, self._old)
del self._old
self._old = None
self.tmpfile.close()
self._state = "done"

Expand Down
3 changes: 2 additions & 1 deletion src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ def pytest_ignore_collect(path, config):
This hook is consulted for all files and directories prior to calling
more specific hooks.
Stops at first non-None result, see :ref:`firstresult`
Stops at first non-None result, see :ref:`firstresult`, i.e. you should
only return `False` if the file should never get ignored (by other hooks).
:param path: a :py:class:`py.path.local` - the path to analyze
:param _pytest.config.Config config: pytest config object
Expand Down
15 changes: 8 additions & 7 deletions src/_pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,22 +522,23 @@ def __init__(self, config):
if self._log_cli_enabled():
self._setup_cli_logging()

def _create_formatter(self, log_format, log_date_format, auto_indent):
def _create_formatter(
self, log_format, log_date_format, auto_indent
) -> logging.Formatter:
# color option doesn't exist if terminal plugin is disabled
color = getattr(self._config.option, "color", "no")
if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(
if color == "no" or not ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(
log_format
):
formatter = ColoredLevelFormatter(
create_terminal_writer(self._config), log_format, log_date_format
) # type: logging.Formatter
else:
formatter = logging.Formatter(log_format, log_date_format)
else:
tr = self._config.pluginmanager.getplugin("terminalreporter")
tw = tr._tw if tr else create_terminal_writer(self._config)
formatter = ColoredLevelFormatter(tw, log_format, log_date_format)

formatter._style = PercentStyleMultiline(
formatter._style._fmt, auto_indent=auto_indent
)

return formatter

def _setup_cli_logging(self):
Expand Down
4 changes: 1 addition & 3 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,6 @@ def pytest_ignore_collect(path, config):
if not allow_in_venv and _in_venv(path):
return True

return False


def pytest_collection_modifyitems(items, config):
deselect_prefixes = tuple(config.getoption("deselect") or [])
Expand Down Expand Up @@ -493,7 +491,7 @@ def _perform_collect(self, args, genitems):
self._notfound = []
initialpaths = []
self._initialparts = []
self.items = items = []
self.items = items = [] # type: List[nodes.Item]
for arg in args:
parts = self._parsearg(arg)
self._initialparts.append(parts)
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def warn(self, warning):

# methods for ordering nodes
@property
def nodeid(self):
def nodeid(self) -> str:
""" a ::-separated string denoting its collection tree address. """
return self._nodeid

Expand Down
13 changes: 9 additions & 4 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,8 @@ class Testdir:

__test__ = False

CLOSE_STDIN = object
class CLOSE_STDIN:
pass

class TimeoutExpired(Exception):
pass
Expand Down Expand Up @@ -997,7 +998,10 @@ def get_capture(fd):
if not stdin or stdin is SysCapture.CLOSE_STDIN:
tmpfile = None
else:
tmpfile = stdin
from _pytest.capture import safe_text_dupfile

tmpfile = safe_text_dupfile(stdin, mode="wb+")
tmpfile.close = lambda: None
else:
tmpfile = CaptureIO()
if tmpfile and tty is not None:
Expand Down Expand Up @@ -1304,7 +1308,7 @@ def runpython_c(self, command):
"""Run python -c "command", return a :py:class:`RunResult`."""
return self.run(sys.executable, "-c", command)

def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
def runpytest_subprocess(self, *args, stdin=CLOSE_STDIN, timeout=None) -> RunResult:
"""Run pytest as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will be added using the
Expand All @@ -1316,6 +1320,7 @@ def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
:param args: the sequence of arguments to pass to the pytest subprocess
:param timeout: the period in seconds after which to timeout and raise
:py:class:`Testdir.TimeoutExpired`
:kwarg stdin: optional standard input. Passed through to :func:`run`.
Returns a :py:class:`RunResult`.
"""
Expand All @@ -1328,7 +1333,7 @@ def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
if plugins:
args = ("-p", plugins[0]) + args
args = self._getpytestargs() + args
return self.run(*args, timeout=timeout)
return self.run(*args, timeout=timeout, stdin=stdin)

def spawn_pytest(self, *args: str, **kwargs) -> "pexpect.spawn":
"""Run pytest using pexpect.
Expand Down
Loading

0 comments on commit c426585

Please sign in to comment.