Merge master into features
This commit is contained in:
commit
30922ee694
|
@ -0,0 +1 @@
|
||||||
|
Fix ``EncodedFile.writelines`` to call the underlying buffer's ``writelines`` method.
|
|
@ -14,5 +14,5 @@ python -m coverage combine
|
||||||
python -m coverage xml
|
python -m coverage xml
|
||||||
python -m coverage report -m
|
python -m coverage report -m
|
||||||
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
|
# 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 "$@"
|
bash codecov-upload.sh -Z -X fix -f coverage.xml "$@"
|
||||||
|
|
|
@ -10,7 +10,6 @@ project_urls =
|
||||||
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||||
|
|
||||||
license = MIT license
|
license = MIT license
|
||||||
license_file = LICENSE
|
|
||||||
keywords = test, unittest
|
keywords = test, unittest
|
||||||
classifiers =
|
classifiers =
|
||||||
Development Status :: 6 - Mature
|
Development Status :: 6 - Mature
|
||||||
|
@ -66,6 +65,7 @@ formats = sdist.tgz,bdist_wheel
|
||||||
mypy_path = src
|
mypy_path = src
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
no_implicit_optional = True
|
no_implicit_optional = True
|
||||||
|
show_error_codes = True
|
||||||
strict_equality = True
|
strict_equality = True
|
||||||
warn_redundant_casts = True
|
warn_redundant_casts = True
|
||||||
warn_return_any = True
|
warn_return_any = True
|
||||||
|
|
|
@ -9,6 +9,8 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from io import UnsupportedOperation
|
from io import UnsupportedOperation
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
|
from typing import BinaryIO
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import CaptureAndPassthroughIO
|
from _pytest.compat import CaptureAndPassthroughIO
|
||||||
|
@ -416,30 +418,27 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
|
||||||
class EncodedFile:
|
class EncodedFile:
|
||||||
errors = "strict" # possibly needed by py3 code (issue555)
|
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.buffer = buffer
|
||||||
self.encoding = encoding
|
self.encoding = encoding
|
||||||
|
|
||||||
def write(self, obj):
|
def write(self, s: str) -> int:
|
||||||
if isinstance(obj, str):
|
if not isinstance(s, str):
|
||||||
obj = obj.encode(self.encoding, "replace")
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
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):
|
def writelines(self, lines: Iterable[str]) -> None:
|
||||||
data = "".join(linelist)
|
self.buffer.writelines(x.encode(self.encoding, "replace") for x in lines)
|
||||||
self.write(data)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self) -> str:
|
||||||
"""Ensure that file.name is a string."""
|
"""Ensure that file.name is a string."""
|
||||||
return repr(self.buffer)
|
return repr(self.buffer)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self):
|
def mode(self) -> str:
|
||||||
return self.buffer.mode.replace("b", "")
|
return self.buffer.mode.replace("b", "")
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
|
|
@ -50,6 +50,13 @@ if TYPE_CHECKING:
|
||||||
from .argparsing import Argument
|
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")
|
hookimpl = HookimplMarker("pytest")
|
||||||
hookspec = HookspecMarker("pytest")
|
hookspec = HookspecMarker("pytest")
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,17 @@ from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.capture import MultiCapture
|
from _pytest.capture import MultiCapture
|
||||||
from _pytest.capture import SysCapture
|
from _pytest.capture import SysCapture
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
from _pytest.config import _PluggyPlugin
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.main import ExitCode
|
from _pytest.main import ExitCode
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
from _pytest.nodes import Collector
|
||||||
|
from _pytest.nodes import Item
|
||||||
from _pytest.pathlib import Path
|
from _pytest.pathlib import Path
|
||||||
|
from _pytest.python import Module
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
|
from _pytest.tmpdir import TempdirFactory
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
@ -534,13 +539,15 @@ class Testdir:
|
||||||
class TimeoutExpired(Exception):
|
class TimeoutExpired(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, request, tmpdir_factory):
|
def __init__(self, request: FixtureRequest, tmpdir_factory: TempdirFactory) -> None:
|
||||||
self.request = request
|
self.request = request
|
||||||
self._mod_collections = WeakKeyDictionary()
|
self._mod_collections = (
|
||||||
|
WeakKeyDictionary()
|
||||||
|
) # type: WeakKeyDictionary[Module, List[Union[Item, Collector]]]
|
||||||
name = request.function.__name__
|
name = request.function.__name__
|
||||||
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
||||||
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + 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._cwd_snapshot = CwdSnapshot()
|
||||||
self._sys_path_snapshot = SysPathsSnapshot()
|
self._sys_path_snapshot = SysPathsSnapshot()
|
||||||
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
|
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
|
||||||
|
@ -1060,7 +1067,9 @@ class Testdir:
|
||||||
self.config = config = self.parseconfigure(path, *configargs)
|
self.config = config = self.parseconfigure(path, *configargs)
|
||||||
return self.getnode(config, path)
|
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.
|
"""Return the collection node for name from the module collection.
|
||||||
|
|
||||||
This will search a module collection node for a collection node
|
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 modcol: a module collection node; see :py:meth:`getmodulecol`
|
||||||
|
|
||||||
:param name: the name of the node to return
|
:param name: the name of the node to return
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if modcol not in self._mod_collections:
|
if modcol not in self._mod_collections:
|
||||||
self._mod_collections[modcol] = list(modcol.collect())
|
self._mod_collections[modcol] = list(modcol.collect())
|
||||||
for colitem in self._mod_collections[modcol]:
|
for colitem in self._mod_collections[modcol]:
|
||||||
if colitem.name == name:
|
if colitem.name == name:
|
||||||
return colitem
|
return colitem
|
||||||
|
return None
|
||||||
|
|
||||||
def popen(
|
def popen(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -946,7 +946,7 @@ def test_collection_collect_only_live_logging(testdir, verbose):
|
||||||
expected_lines.extend(
|
expected_lines.extend(
|
||||||
[
|
[
|
||||||
"*test_collection_collect_only_live_logging.py::test_simple*",
|
"*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":
|
elif verbose == "-qq":
|
||||||
|
|
|
@ -7,6 +7,8 @@ import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from io import UnsupportedOperation
|
from io import UnsupportedOperation
|
||||||
|
from typing import BinaryIO
|
||||||
|
from typing import Generator
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
|
|
||||||
|
@ -854,7 +856,7 @@ def test_dontreadfrominput():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def tmpfile(testdir):
|
def tmpfile(testdir) -> Generator[BinaryIO, None, None]:
|
||||||
f = testdir.makepyfile("").open("wb+")
|
f = testdir.makepyfile("").open("wb+")
|
||||||
yield f
|
yield f
|
||||||
if not f.closed:
|
if not f.closed:
|
||||||
|
@ -1537,3 +1539,15 @@ def test_typeerror_encodedfile_write(testdir):
|
||||||
def test_stderr_write_returns_len(capsys):
|
def test_stderr_write_returns_len(capsys):
|
||||||
"""Write on Encoded files, namely captured stderr, should return number of characters written."""
|
"""Write on Encoded files, namely captured stderr, should return number of characters written."""
|
||||||
assert sys.stderr.write("Foo") == 3
|
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()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import pytest
|
||||||
from _pytest.main import _in_venv
|
from _pytest.main import _in_venv
|
||||||
from _pytest.main import ExitCode
|
from _pytest.main import ExitCode
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
|
|
||||||
class TestCollector:
|
class TestCollector:
|
||||||
|
@ -18,7 +19,7 @@ class TestCollector:
|
||||||
assert not issubclass(Collector, Item)
|
assert not issubclass(Collector, Item)
|
||||||
assert not issubclass(Item, Collector)
|
assert not issubclass(Item, Collector)
|
||||||
|
|
||||||
def test_check_equality(self, testdir):
|
def test_check_equality(self, testdir: Testdir) -> None:
|
||||||
modcol = testdir.getmodulecol(
|
modcol = testdir.getmodulecol(
|
||||||
"""
|
"""
|
||||||
def test_pass(): pass
|
def test_pass(): pass
|
||||||
|
@ -40,12 +41,15 @@ class TestCollector:
|
||||||
assert fn1 != fn3
|
assert fn1 != fn3
|
||||||
|
|
||||||
for fn in fn1, fn2, 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 != modcol
|
||||||
assert fn != [1, 2, 3]
|
assert fn != [1, 2, 3] # type: ignore[comparison-overlap] # noqa: F821
|
||||||
assert [1, 2, 3] != fn
|
assert [1, 2, 3] != fn # type: ignore[comparison-overlap] # noqa: F821
|
||||||
assert modcol != fn
|
assert modcol != fn
|
||||||
|
|
||||||
|
assert testdir.collect_by_name(modcol, "doesnotexist") is None
|
||||||
|
|
||||||
def test_getparent(self, testdir):
|
def test_getparent(self, testdir):
|
||||||
modcol = testdir.getmodulecol(
|
modcol = testdir.getmodulecol(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -670,6 +670,32 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
|
||||||
assert has_loaded == 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):
|
def test_cmdline_processargs_simple(testdir):
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -13,6 +13,7 @@ import py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.main import ExitCode
|
from _pytest.main import ExitCode
|
||||||
|
from _pytest.pytester import Testdir
|
||||||
from _pytest.reports import BaseReport
|
from _pytest.reports import BaseReport
|
||||||
from _pytest.terminal import _folded_skips
|
from _pytest.terminal import _folded_skips
|
||||||
from _pytest.terminal import _get_line_with_reprcrash_message
|
from _pytest.terminal import _get_line_with_reprcrash_message
|
||||||
|
@ -1978,3 +1979,11 @@ def test_collecterror(testdir):
|
||||||
"*= 1 error in *",
|
"*= 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 *"]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue