Merge master into features

This commit is contained in:
Daniel Hahler 2020-01-28 01:40:14 +01:00
commit 30922ee694
11 changed files with 94 additions and 25 deletions

View File

@ -0,0 +1 @@
Fix ``EncodedFile.writelines`` to call the underlying buffer's ``writelines`` method.

View File

@ -14,5 +14,5 @@ python -m coverage combine
python -m coverage xml
python -m coverage report -m
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
curl -S -L --connect-timeout 5 --retry 6 --retry-connrefused -s https://codecov.io/bash -o codecov-upload.sh
bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"

View File

@ -10,7 +10,6 @@ project_urls =
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
license = MIT license
license_file = LICENSE
keywords = test, unittest
classifiers =
Development Status :: 6 - Mature
@ -66,6 +65,7 @@ formats = sdist.tgz,bdist_wheel
mypy_path = src
ignore_missing_imports = True
no_implicit_optional = True
show_error_codes = True
strict_equality = True
warn_redundant_casts = True
warn_return_any = True

View File

@ -9,6 +9,8 @@ import os
import sys
from io import UnsupportedOperation
from tempfile import TemporaryFile
from typing import BinaryIO
from typing import Iterable
import pytest
from _pytest.compat import CaptureAndPassthroughIO
@ -416,30 +418,27 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
class EncodedFile:
errors = "strict" # possibly needed by py3 code (issue555)
def __init__(self, buffer, encoding):
def __init__(self, buffer: BinaryIO, encoding: str) -> None:
self.buffer = buffer
self.encoding = encoding
def write(self, obj):
if isinstance(obj, str):
obj = obj.encode(self.encoding, "replace")
else:
def write(self, s: str) -> int:
if not isinstance(s, str):
raise TypeError(
"write() argument must be str, not {}".format(type(obj).__name__)
"write() argument must be str, not {}".format(type(s).__name__)
)
return self.buffer.write(obj)
return self.buffer.write(s.encode(self.encoding, "replace"))
def writelines(self, linelist):
data = "".join(linelist)
self.write(data)
def writelines(self, lines: Iterable[str]) -> None:
self.buffer.writelines(x.encode(self.encoding, "replace") for x in lines)
@property
def name(self):
def name(self) -> str:
"""Ensure that file.name is a string."""
return repr(self.buffer)
@property
def mode(self):
def mode(self) -> str:
return self.buffer.mode.replace("b", "")
def __getattr__(self, name):

View File

@ -50,6 +50,13 @@ if TYPE_CHECKING:
from .argparsing import Argument
_PluggyPlugin = object
"""A type to represent plugin objects.
Plugins can be any namespace, so we can't narrow it down much, but we use an
alias to make the intent clear.
Ideally this type would be provided by pluggy itself."""
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")

View File

@ -29,12 +29,17 @@ from _pytest._io.saferepr import saferepr
from _pytest.capture import MultiCapture
from _pytest.capture import SysCapture
from _pytest.compat import TYPE_CHECKING
from _pytest.config import _PluggyPlugin
from _pytest.fixtures import FixtureRequest
from _pytest.main import ExitCode
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.pathlib import Path
from _pytest.python import Module
from _pytest.reports import TestReport
from _pytest.tmpdir import TempdirFactory
if TYPE_CHECKING:
from typing import Type
@ -534,13 +539,15 @@ class Testdir:
class TimeoutExpired(Exception):
pass
def __init__(self, request, tmpdir_factory):
def __init__(self, request: FixtureRequest, tmpdir_factory: TempdirFactory) -> None:
self.request = request
self._mod_collections = WeakKeyDictionary()
self._mod_collections = (
WeakKeyDictionary()
) # type: WeakKeyDictionary[Module, List[Union[Item, Collector]]]
name = request.function.__name__
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
self.plugins = []
self.plugins = [] # type: List[Union[str, _PluggyPlugin]]
self._cwd_snapshot = CwdSnapshot()
self._sys_path_snapshot = SysPathsSnapshot()
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
@ -1060,7 +1067,9 @@ class Testdir:
self.config = config = self.parseconfigure(path, *configargs)
return self.getnode(config, path)
def collect_by_name(self, modcol, name):
def collect_by_name(
self, modcol: Module, name: str
) -> Optional[Union[Item, Collector]]:
"""Return the collection node for name from the module collection.
This will search a module collection node for a collection node
@ -1069,13 +1078,13 @@ class Testdir:
:param modcol: a module collection node; see :py:meth:`getmodulecol`
:param name: the name of the node to return
"""
if modcol not in self._mod_collections:
self._mod_collections[modcol] = list(modcol.collect())
for colitem in self._mod_collections[modcol]:
if colitem.name == name:
return colitem
return None
def popen(
self,

View File

@ -946,7 +946,7 @@ def test_collection_collect_only_live_logging(testdir, verbose):
expected_lines.extend(
[
"*test_collection_collect_only_live_logging.py::test_simple*",
"no tests ran in 0.[0-9][0-9]s",
"no tests ran in [0-1].[0-9][0-9]s",
]
)
elif verbose == "-qq":

View File

@ -7,6 +7,8 @@ import sys
import textwrap
from io import StringIO
from io import UnsupportedOperation
from typing import BinaryIO
from typing import Generator
from typing import List
from typing import TextIO
@ -854,7 +856,7 @@ def test_dontreadfrominput():
@pytest.fixture
def tmpfile(testdir):
def tmpfile(testdir) -> Generator[BinaryIO, None, None]:
f = testdir.makepyfile("").open("wb+")
yield f
if not f.closed:
@ -1537,3 +1539,15 @@ def test_typeerror_encodedfile_write(testdir):
def test_stderr_write_returns_len(capsys):
"""Write on Encoded files, namely captured stderr, should return number of characters written."""
assert sys.stderr.write("Foo") == 3
def test_encodedfile_writelines(tmpfile: BinaryIO) -> None:
ef = capture.EncodedFile(tmpfile, "utf-8")
with pytest.raises(AttributeError):
ef.writelines([b"line1", b"line2"]) # type: ignore[list-item] # noqa: F821
assert ef.writelines(["line1", "line2"]) is None # type: ignore[func-returns-value] # noqa: F821
tmpfile.seek(0)
assert tmpfile.read() == b"line1line2"
tmpfile.close()
with pytest.raises(ValueError):
ef.read()

View File

@ -9,6 +9,7 @@ import pytest
from _pytest.main import _in_venv
from _pytest.main import ExitCode
from _pytest.main import Session
from _pytest.pytester import Testdir
class TestCollector:
@ -18,7 +19,7 @@ class TestCollector:
assert not issubclass(Collector, Item)
assert not issubclass(Item, Collector)
def test_check_equality(self, testdir):
def test_check_equality(self, testdir: Testdir) -> None:
modcol = testdir.getmodulecol(
"""
def test_pass(): pass
@ -40,12 +41,15 @@ class TestCollector:
assert fn1 != fn3
for fn in fn1, fn2, fn3:
assert fn != 3
assert isinstance(fn, pytest.Function)
assert fn != 3 # type: ignore[comparison-overlap] # noqa: F821
assert fn != modcol
assert fn != [1, 2, 3]
assert [1, 2, 3] != fn
assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821
assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821
assert modcol != fn
assert testdir.collect_by_name(modcol, "doesnotexist") is None
def test_getparent(self, testdir):
modcol = testdir.getmodulecol(
"""

View File

@ -670,6 +670,32 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
assert has_loaded == should_load
def test_plugin_loading_order(testdir):
"""Test order of plugin loading with `-p`."""
p1 = testdir.makepyfile(
"""
def test_terminal_plugin(request):
import myplugin
assert myplugin.terminal_plugin == [True, True]
""",
**{
"myplugin": """
terminal_plugin = []
def pytest_configure(config):
terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter")))
def pytest_sessionstart(session):
config = session.config
terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter")))
"""
}
)
testdir.syspathinsert()
result = testdir.runpytest("-p", "myplugin", str(p1))
assert result.ret == 0
def test_cmdline_processargs_simple(testdir):
testdir.makeconftest(
"""

View File

@ -13,6 +13,7 @@ import py
import pytest
from _pytest.main import ExitCode
from _pytest.pytester import Testdir
from _pytest.reports import BaseReport
from _pytest.terminal import _folded_skips
from _pytest.terminal import _get_line_with_reprcrash_message
@ -1978,3 +1979,11 @@ def test_collecterror(testdir):
"*= 1 error in *",
]
)
def test_via_exec(testdir: Testdir) -> None:
p1 = testdir.makepyfile("exec('def test_via_exec(): pass')")
result = testdir.runpytest(str(p1), "-vv")
result.stdout.fnmatch_lines(
["test_via_exec.py::test_via_exec <- <string> PASSED*", "*= 1 passed in *"]
)