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 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 "$@"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
"""
|
||||
|
|
|
@ -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(
|
||||
"""
|
||||
|
|
|
@ -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 *"]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue