Skip to content

Merge master into features #6594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
00097df
tests: add test_plugin_loading_order
blueyed Jan 22, 2020
fe343a7
Remove deprecated license_file from setup.cfg
hugovk Dec 27, 2019
510be29
Merge pull request #6534 from blueyed/test_plugin_loading_order
blueyed Jan 25, 2020
b687f20
Merge pull request #6375 from hugovk/rm-deprecated-license_file-metadata
blueyed Jan 25, 2020
039d582
Fix `EncodedFile.writelines`
blueyed Jan 25, 2020
778d436
tests: test_collection_collect_only_live_logging: allow for 1s
blueyed Jan 25, 2020
df1f43e
ci: codecov: use `--retry-connrefused` with curl
blueyed Jan 25, 2020
198b1dc
Merge pull request #6573 from blueyed/codecov-curl-retry-connrefused
blueyed Jan 25, 2020
7c52a37
Merge pull request #6572 from blueyed/fix-test_collection_collect_onl…
blueyed Jan 25, 2020
3f8f395
typing: EncodedFile
blueyed Jan 25, 2020
d678d38
typing: tests: tmpfile
blueyed Jan 25, 2020
40758e8
tests: add test_via_exec
blueyed Jan 26, 2020
bf5c763
fixup! typing: tests: tmpfile
blueyed Jan 26, 2020
a9eab07
Merge pull request #6576 from blueyed/test_via_exec
Zac-HD Jan 27, 2020
d017b69
mypy: show_error_codes=True
blueyed Jan 27, 2020
20b66e6
Merge pull request #6566 from blueyed/rm-encodedfile-writelines
blueyed Jan 27, 2020
aa318e9
Merge pull request #6587 from blueyed/mypy-show_error_codes
blueyed Jan 27, 2020
440881d
typing: Testdir.__init__
blueyed Jan 26, 2020
94ac0f7
typing: self._mod_collections, collect_by_name
blueyed Jan 27, 2020
9c716e4
typing: Testdir.plugins
blueyed Jan 27, 2020
ad0f4f0
tests: cover collect_by_name with non-existing
blueyed Jan 27, 2020
d0cb160
Merge pull request #6580 from blueyed/typing-testdir-init
blueyed Jan 27, 2020
30922ee
Merge master into features
blueyed Jan 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6566.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``EncodedFile.writelines`` to call the underlying buffer's ``writelines`` method.
2 changes: 1 addition & 1 deletion scripts/report-coverage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$@"
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
23 changes: 11 additions & 12 deletions src/_pytest/capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
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
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 7 additions & 0 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
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")

Expand Down
19 changes: 14 additions & 5 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@
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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -1060,7 +1067,9 @@ def getmodulecol(self, source, configargs=(), withinit=False):
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
Expand All @@ -1069,13 +1078,13 @@ def collect_by_name(self, modcol, name):
: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,
Expand Down
2 changes: 1 addition & 1 deletion testing/logging/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,7 +946,7 @@ def test_simple():
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":
Expand Down
16 changes: 15 additions & 1 deletion testing/test_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1537,3 +1539,15 @@ def test_fails():
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()
12 changes: 8 additions & 4 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from _pytest.main import _in_venv
from _pytest.main import ExitCode
from _pytest.main import Session
from _pytest.pytester import Testdir


class TestCollector:
Expand All @@ -18,7 +19,7 @@ def test_collect_versus_item(self):
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
Expand All @@ -40,12 +41,15 @@ def test_fail(): assert 0
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(
"""
Expand Down
26 changes: 26 additions & 0 deletions testing/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,32 @@ def distributions():
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(
"""
Expand Down
9 changes: 9 additions & 0 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

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
Expand Down Expand Up @@ -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 *"]
)