Merge remote-tracking branch 'upstream/master' into mm

Conflicts:
 * 	src/_pytest/_code/code.py
 * 	src/_pytest/main.py
 * 	testing/python/metafunc.py
 * 	testing/test_parseopt.py
 * 	testing/test_pytester.py
This commit is contained in:
Bruno Oliveira 2020-01-22 11:03:45 -03:00
commit 93b74d28d2
53 changed files with 641 additions and 548 deletions

View File

@ -140,18 +140,18 @@ jobs:
run: "tox -e ${{ matrix.tox_env }}" run: "tox -e ${{ matrix.tox_env }}"
- name: Prepare coverage token - name: Prepare coverage token
if: success() && !matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' ) if: (!matrix.skip_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' ))
run: | run: |
python scripts/append_codecov_token.py python scripts/append_codecov_token.py
- name: Combine coverage - name: Combine coverage
if: success() && !matrix.skip_coverage if: (!matrix.skip_coverage)
run: | run: |
python -m coverage combine python -m coverage combine
python -m coverage xml python -m coverage xml
- name: Codecov upload - name: Codecov upload
if: success() && !matrix.skip_coverage if: (!matrix.skip_coverage)
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
with: with:
token: ${{ secrets.codecov }} token: ${{ secrets.codecov }}

View File

@ -55,7 +55,6 @@ Charles Cloud
Charles Machalow Charles Machalow
Charnjit SiNGH (CCSJ) Charnjit SiNGH (CCSJ)
Chris Lamb Chris Lamb
Chris NeJame
Christian Boelsen Christian Boelsen
Christian Fetzer Christian Fetzer
Christian Neumüller Christian Neumüller

View File

@ -1 +0,0 @@
Captured output during teardown is shown with ``-rP``.

View File

@ -1,2 +0,0 @@
Fix a ``pytest-xdist`` crash when dealing with exceptions raised in subprocesses created by the
``multiprocessing`` module.

View File

@ -1 +0,0 @@
Optimized automatic renaming of test parameter IDs.

View File

@ -1,3 +0,0 @@
:class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and
parameterized fixtures that execute before them in the fixture stack so they are torn
down at the right times, and in the right order.

View File

@ -1 +0,0 @@
Fix parsing of outcomes containing multiple errors with ``testdir`` results (regression in 5.3.0).

View File

@ -6,6 +6,8 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-5.3.4
release-5.3.3
release-5.3.2 release-5.3.2
release-5.3.1 release-5.3.1
release-5.3.0 release-5.3.0

View File

@ -0,0 +1,30 @@
pytest-5.3.3
=======================================
pytest 5.3.3 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Adam Johnson
* Alexandre Mulatinho
* Anthony Sottile
* Bruno Oliveira
* Chris NeJame
* Daniel Hahler
* Hugo van Kemenade
* Marcelo Duarte Trevisani
* PaulC
* Ran Benita
* Ryan Barner
* Seth Junot
* marc
Happy testing,
The pytest Development Team

View File

@ -0,0 +1,20 @@
pytest-5.3.4
=======================================
pytest 5.3.4 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Bruno Oliveira
* Daniel Hahler
* Ran Benita
Happy testing,
The pytest Development Team

View File

@ -28,6 +28,44 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 5.3.4 (2020-01-20)
=========================
Bug Fixes
---------
- `#6496 <https://github.com/pytest-dev/pytest/issues/6496>`_: Revert `#6436 <https://github.com/pytest-dev/pytest/issues/6436>`__: unfortunately this change has caused a number of regressions in many suites,
so the team decided to revert this change and make a new release while we continue to look for a solution.
pytest 5.3.3 (2020-01-16)
=========================
Bug Fixes
---------
- `#2780 <https://github.com/pytest-dev/pytest/issues/2780>`_: Captured output during teardown is shown with ``-rP``.
- `#5971 <https://github.com/pytest-dev/pytest/issues/5971>`_: Fix a ``pytest-xdist`` crash when dealing with exceptions raised in subprocesses created by the
``multiprocessing`` module.
- `#6436 <https://github.com/pytest-dev/pytest/issues/6436>`_: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and
parameterized fixtures that execute before them in the fixture stack so they are torn
down at the right times, and in the right order.
- `#6532 <https://github.com/pytest-dev/pytest/issues/6532>`_: Fix parsing of outcomes containing multiple errors with ``testdir`` results (regression in 5.3.0).
Trivial/Internal Changes
------------------------
- `#6350 <https://github.com/pytest-dev/pytest/issues/6350>`_: Optimized automatic renaming of test parameter IDs.
pytest 5.3.2 (2019-12-13) pytest 5.3.2 (2019-12-13)
========================= =========================
@ -4842,7 +4880,7 @@ time or change existing behaviors in order to make them less surprising/more use
* Updated docstrings with a more uniform style. * Updated docstrings with a more uniform style.
* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. * Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown.
Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@jgsonesen`_ and Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to @jgsonesen and
`@tomviner`_ for the PR. `@tomviner`_ for the PR.
* No longer display the incorrect test deselection reason (`#1372`_). * No longer display the incorrect test deselection reason (`#1372`_).
@ -4974,7 +5012,6 @@ time or change existing behaviors in order to make them less surprising/more use
.. _@gprasad84: https://github.com/gprasad84 .. _@gprasad84: https://github.com/gprasad84
.. _@graingert: https://github.com/graingert .. _@graingert: https://github.com/graingert
.. _@hartym: https://github.com/hartym .. _@hartym: https://github.com/hartym
.. _@jgsonesen: https://github.com/jgsonesen
.. _@kalekundert: https://github.com/kalekundert .. _@kalekundert: https://github.com/kalekundert
.. _@kvas-it: https://github.com/kvas-it .. _@kvas-it: https://github.com/kvas-it
.. _@marscher: https://github.com/marscher .. _@marscher: https://github.com/marscher

View File

@ -20,8 +20,6 @@ which were registered by installed plugins.
Initialization: determining rootdir and inifile Initialization: determining rootdir and inifile
----------------------------------------------- -----------------------------------------------
pytest determines a ``rootdir`` for each test run which depends on pytest determines a ``rootdir`` for each test run which depends on
the command line arguments (specified test files, paths) and on the command line arguments (specified test files, paths) and on
the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are
@ -30,17 +28,17 @@ printed as part of the pytest header during startup.
Here's a summary what ``pytest`` uses ``rootdir`` for: Here's a summary what ``pytest`` uses ``rootdir`` for:
* Construct *nodeids* during collection; each test is assigned * Construct *nodeids* during collection; each test is assigned
a unique *nodeid* which is rooted at the ``rootdir`` and takes in account full path, a unique *nodeid* which is rooted at the ``rootdir`` and takes into account
class name, function name and parametrization (if any). the full path, class name, function name and parametrization (if any).
* Is used by plugins as a stable location to store project/test run specific information; * Is used by plugins as a stable location to store project/test run specific information;
for example, the internal :ref:`cache <cache>` plugin creates a ``.pytest_cache`` subdirectory for example, the internal :ref:`cache <cache>` plugin creates a ``.pytest_cache`` subdirectory
in ``rootdir`` to store its cross-test run state. in ``rootdir`` to store its cross-test run state.
Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or
influence how modules are imported. See :ref:`pythonpath` for more details. influence how modules are imported. See :ref:`pythonpath` for more details.
``--rootdir=path`` command-line option can be used to force a specific directory. The ``--rootdir=path`` command-line option can be used to force a specific directory.
The directory passed may contain environment variables when it is used in conjunction The directory passed may contain environment variables when it is used in conjunction
with ``addopts`` in a ``pytest.ini`` file. with ``addopts`` in a ``pytest.ini`` file.

View File

@ -443,7 +443,7 @@ Now we can profile which test functions execute the slowest:
========================= slowest 3 test durations ========================= ========================= slowest 3 test durations =========================
0.30s call test_some_are_slow.py::test_funcslow2 0.30s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1 0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast 0.11s call test_some_are_slow.py::test_funcfast
============================ 3 passed in 0.12s ============================= ============================ 3 passed in 0.12s =============================
incremental testing - test steps incremental testing - test steps

View File

@ -100,7 +100,7 @@ def pre_release(version, *, skip_check_links):
print() print()
print(f"{Fore.CYAN}[generate.pre_release] {Fore.GREEN}All done!") print(f"{Fore.CYAN}[generate.pre_release] {Fore.GREEN}All done!")
print() print()
print(f"Please push your branch and open a PR.") print("Please push your branch and open a PR.")
def changelog(version, write_out=False): def changelog(version, write_out=False):

View File

@ -41,7 +41,7 @@ if TYPE_CHECKING:
from _pytest._code import Source from _pytest._code import Source
_TracebackStyle = Literal["long", "short", "no", "native"] _TracebackStyle = Literal["long", "short", "line", "no", "native"]
class Code: class Code:
@ -67,9 +67,10 @@ class Code:
return not self == other return not self == other
@property @property
def path(self): def path(self) -> Union[py.path.local, str]:
""" return a path object pointing to source code (note that it """ return a path object pointing to source code (or a str in case
might not point to an actually existing file). """ of OSError / non-existing file).
"""
try: try:
p = py.path.local(self.raw.co_filename) p = py.path.local(self.raw.co_filename)
# maybe don't try this checking # maybe don't try this checking
@ -335,7 +336,7 @@ class Traceback(List[TracebackEntry]):
(path is None or codepath == path) (path is None or codepath == path)
and ( and (
excludepath is None excludepath is None
or not hasattr(codepath, "relto") or not isinstance(codepath, py.path.local)
or not codepath.relto(excludepath) or not codepath.relto(excludepath)
) )
and (lineno is None or x.lineno == lineno) and (lineno is None or x.lineno == lineno)
@ -919,7 +920,7 @@ class TerminalRepr:
def __repr__(self) -> str: def __repr__(self) -> str:
return "<{} instance at {:0x}>".format(self.__class__, id(self)) return "<{} instance at {:0x}>".format(self.__class__, id(self))
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
raise NotImplementedError() raise NotImplementedError()
@ -930,7 +931,7 @@ class ExceptionRepr(TerminalRepr):
def addsection(self, name: str, content: str, sep: str = "-") -> None: def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep)) self.sections.append((name, content, sep))
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
for name, content, sep in self.sections: for name, content, sep in self.sections:
tw.sep(sep, name) tw.sep(sep, name)
tw.line(content) tw.line(content)
@ -950,7 +951,7 @@ class ExceptionChainRepr(ExceptionRepr):
self.reprtraceback = chain[-1][0] self.reprtraceback = chain[-1][0]
self.reprcrash = chain[-1][1] self.reprcrash = chain[-1][1]
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
for element in self.chain: for element in self.chain:
element[0].toterminal(tw) element[0].toterminal(tw)
if element[2] is not None: if element[2] is not None:
@ -967,7 +968,7 @@ class ReprExceptionInfo(ExceptionRepr):
self.reprtraceback = reprtraceback self.reprtraceback = reprtraceback
self.reprcrash = reprcrash self.reprcrash = reprcrash
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
self.reprtraceback.toterminal(tw) self.reprtraceback.toterminal(tw)
super().toterminal(tw) super().toterminal(tw)
@ -985,7 +986,7 @@ class ReprTraceback(TerminalRepr):
self.extraline = extraline self.extraline = extraline
self.style = style self.style = style
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
# the entries might have different styles # the entries might have different styles
for i, entry in enumerate(self.reprentries): for i, entry in enumerate(self.reprentries):
if entry.style == "long": if entry.style == "long":
@ -1017,7 +1018,7 @@ class ReprEntryNative(TerminalRepr):
def __init__(self, tblines: Sequence[str]) -> None: def __init__(self, tblines: Sequence[str]) -> None:
self.lines = tblines self.lines = tblines
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
tw.write("".join(self.lines)) tw.write("".join(self.lines))
@ -1036,7 +1037,7 @@ class ReprEntry(TerminalRepr):
self.reprfileloc = filelocrepr self.reprfileloc = filelocrepr
self.style = style self.style = style
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
if self.style == "short": if self.style == "short":
assert self.reprfileloc is not None assert self.reprfileloc is not None
self.reprfileloc.toterminal(tw) self.reprfileloc.toterminal(tw)
@ -1071,7 +1072,7 @@ class ReprFileLocation(TerminalRepr):
self.lineno = lineno self.lineno = lineno
self.message = message self.message = message
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
# filename and lineno output for each entry, # filename and lineno output for each entry,
# using an output format that most editors understand # using an output format that most editors understand
msg = self.message msg = self.message
@ -1086,7 +1087,7 @@ class ReprLocals(TerminalRepr):
def __init__(self, lines: Sequence[str]) -> None: def __init__(self, lines: Sequence[str]) -> None:
self.lines = lines self.lines = lines
def toterminal(self, tw, indent="") -> None: def toterminal(self, tw: py.io.TerminalWriter, indent="") -> None:
for line in self.lines: for line in self.lines:
tw.line(indent + line) tw.line(indent + line)
@ -1095,7 +1096,7 @@ class ReprFuncArgs(TerminalRepr):
def __init__(self, args: Sequence[Tuple[str, object]]) -> None: def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
self.args = args self.args = args
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
if self.args: if self.args:
linesofar = "" linesofar = ""
for name, value in self.args: for name, value in self.args:

View File

@ -5,8 +5,8 @@ import sys
import textwrap import textwrap
import tokenize import tokenize
import warnings import warnings
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right from bisect import bisect_right
from types import CodeType
from types import FrameType from types import FrameType
from typing import Iterator from typing import Iterator
from typing import List from typing import List
@ -18,6 +18,10 @@ from typing import Union
import py import py
from _pytest.compat import overload from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import Literal
class Source: class Source:
@ -121,7 +125,7 @@ class Source:
start, end = self.getstatementrange(lineno) start, end = self.getstatementrange(lineno)
return self[start:end] return self[start:end]
def getstatementrange(self, lineno: int): def getstatementrange(self, lineno: int) -> Tuple[int, int]:
""" return (start, end) tuple which spans the minimal """ return (start, end) tuple which spans the minimal
statement region which containing the given lineno. statement region which containing the given lineno.
""" """
@ -154,14 +158,36 @@ class Source:
def __str__(self) -> str: def __str__(self) -> str:
return "\n".join(self.lines) return "\n".join(self.lines)
@overload
def compile( def compile(
self, self,
filename=None, filename: Optional[str] = ...,
mode="exec", mode: str = ...,
flag: "Literal[0]" = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile( # noqa: F811
self,
filename: Optional[str] = ...,
mode: str = ...,
flag: int = ...,
dont_inherit: int = ...,
_genframe: Optional[FrameType] = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile( # noqa: F811
self,
filename: Optional[str] = None,
mode: str = "exec",
flag: int = 0, flag: int = 0,
dont_inherit: int = 0, dont_inherit: int = 0,
_genframe: Optional[FrameType] = None, _genframe: Optional[FrameType] = None,
): ) -> Union[CodeType, ast.AST]:
""" return compiled code object. if filename is None """ return compiled code object. if filename is None
invent an artificial filename which displays invent an artificial filename which displays
the source/line position of the caller frame. the source/line position of the caller frame.
@ -191,8 +217,10 @@ class Source:
newex.text = ex.text newex.text = ex.text
raise newex raise newex
else: else:
if flag & _AST_FLAG: if flag & ast.PyCF_ONLY_AST:
assert isinstance(co, ast.AST)
return co return co
assert isinstance(co, CodeType)
lines = [(x + "\n") for x in self.lines] lines = [(x + "\n") for x in self.lines]
# Type ignored because linecache.cache is private. # Type ignored because linecache.cache is private.
linecache.cache[filename] = (1, None, lines, filename) # type: ignore linecache.cache[filename] = (1, None, lines, filename) # type: ignore
@ -204,7 +232,35 @@ class Source:
# #
def compile_(source, filename=None, mode="exec", flags: int = 0, dont_inherit: int = 0): @overload
def compile_(
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: "Literal[0]" = ...,
dont_inherit: int = ...,
) -> CodeType:
raise NotImplementedError()
@overload # noqa: F811
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = ...,
mode: str = ...,
flags: int = ...,
dont_inherit: int = ...,
) -> Union[CodeType, ast.AST]:
raise NotImplementedError()
def compile_( # noqa: F811
source: Union[str, bytes, ast.mod, ast.AST],
filename: Optional[str] = None,
mode: str = "exec",
flags: int = 0,
dont_inherit: int = 0,
) -> Union[CodeType, ast.AST]:
""" compile the given source to a raw code object, """ compile the given source to a raw code object,
and maintain an internal cache which allows later and maintain an internal cache which allows later
retrieval of the source code for the code object retrieval of the source code for the code object
@ -212,14 +268,16 @@ def compile_(source, filename=None, mode="exec", flags: int = 0, dont_inherit: i
""" """
if isinstance(source, ast.AST): if isinstance(source, ast.AST):
# XXX should Source support having AST? # XXX should Source support having AST?
return compile(source, filename, mode, flags, dont_inherit) assert filename is not None
co = compile(source, filename, mode, flags, dont_inherit)
assert isinstance(co, (CodeType, ast.AST))
return co
_genframe = sys._getframe(1) # the caller _genframe = sys._getframe(1) # the caller
s = Source(source) s = Source(source)
co = s.compile(filename, mode, flags, _genframe=_genframe) return s.compile(filename, mode, flags, _genframe=_genframe)
return co
def getfslineno(obj): def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
""" Return source location (path, lineno) for the given object. """ Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1). If the source cannot be determined return ("", -1).
@ -316,7 +374,7 @@ def getstatementrange_ast(
# don't produce duplicate warnings when compiling source to find ast # don't produce duplicate warnings when compiling source to find ast
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
astnode = compile(content, "source", "exec", _AST_FLAG) astnode = ast.parse(content, "source", "exec")
start, end = get_statement_startend2(lineno, astnode) start, end = get_statement_startend2(lineno, astnode)
# we need to correct the end: # we need to correct the end:

View File

@ -98,8 +98,10 @@ def getlocation(function, curdir=None) -> str:
function = get_real_func(function) function = get_real_func(function)
fn = py.path.local(inspect.getfile(function)) fn = py.path.local(inspect.getfile(function))
lineno = function.__code__.co_firstlineno lineno = function.__code__.co_firstlineno
if curdir is not None and fn.relto(curdir): if curdir is not None:
fn = fn.relto(curdir) relfn = fn.relto(curdir)
if relfn:
return "%s:%d" % (relfn, lineno + 1)
return "%s:%d" % (fn, lineno + 1) return "%s:%d" % (fn, lineno + 1)

View File

@ -121,7 +121,9 @@ def determine_setup(
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"] sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
for section in sections: for section in sections:
try: try:
inicfg = iniconfig[section] inicfg = iniconfig[
section
] # type: Optional[py.iniconfig._SectionWrapper]
if is_cfg_file and section == "pytest" and config is not None: if is_cfg_file and section == "pytest" and config is not None:
fail( fail(
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False

View File

@ -13,6 +13,8 @@ from typing import Sequence
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
import py
import pytest import pytest
from _pytest import outcomes from _pytest import outcomes
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
@ -137,7 +139,7 @@ class ReprFailDoctest(TerminalRepr):
): ):
self.reprlocation_lines = reprlocation_lines self.reprlocation_lines = reprlocation_lines
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
for reprlocation, lines in self.reprlocation_lines: for reprlocation, lines in self.reprlocation_lines:
for line in lines: for line in lines:
tw.line(line) tw.line(line)

View File

@ -425,7 +425,7 @@ class FixtureRequest:
return self._pyfuncitem.getparent(_pytest.python.Module).obj return self._pyfuncitem.getparent(_pytest.python.Module).obj
@scopeproperty() @scopeproperty()
def fspath(self): def fspath(self) -> py.path.local:
""" the file system path of the test module which collected this test. """ """ the file system path of the test module which collected this test. """
return self._pyfuncitem.fspath return self._pyfuncitem.fspath
@ -749,7 +749,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
self.firstlineno = firstlineno self.firstlineno = firstlineno
self.argname = argname self.argname = argname
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
# tw.line("FixtureLookupError: %s" %(self.argname), red=True) # tw.line("FixtureLookupError: %s" %(self.argname), red=True)
for tbline in self.tblines: for tbline in self.tblines:
tw.line(tbline.rstrip()) tw.line(tbline.rstrip())
@ -881,7 +881,9 @@ class FixtureDef:
self._finalizers = [] self._finalizers = []
def execute(self, request): def execute(self, request):
for argname in self._dependee_fixture_argnames(request): # get required arguments and register our own finish()
# with their finalization
for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname) fixturedef = request._get_active_fixturedef(argname)
if argname != "request": if argname != "request":
fixturedef.addfinalizer(functools.partial(self.finish, request=request)) fixturedef.addfinalizer(functools.partial(self.finish, request=request))
@ -904,61 +906,6 @@ class FixtureDef:
hook = self._fixturemanager.session.gethookproxy(request.node.fspath) hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request) return hook.pytest_fixture_setup(fixturedef=self, request=request)
def _dependee_fixture_argnames(self, request):
"""A list of argnames for fixtures that this fixture depends on.
Given a request, this looks at the currently known list of fixture argnames, and
attempts to determine what slice of the list contains fixtures that it can know
should execute before it. This information is necessary so that this fixture can
know what fixtures to register its finalizer with to make sure that if they
would be torn down, they would tear down this fixture before themselves. It's
crucial for fixtures to be torn down in the inverse order that they were set up
in so that they don't try to clean up something that another fixture is still
depending on.
When autouse fixtures are involved, it can be tricky to figure out when fixtures
should be torn down. To solve this, this method leverages the ``fixturenames``
list provided by the ``request`` object, as this list is at least somewhat
sorted (in terms of the order fixtures are set up in) by the time this method is
reached. It's sorted enough that the starting point of fixtures that depend on
this one can be found using the ``self._parent_request`` stack.
If a request in the ``self._parent_request`` stack has a ``:class:FixtureDef``
associated with it, then that fixture is dependent on this one, so any fixture
names that appear in the list of fixture argnames that come after it can also be
ruled out. The argnames of all fixtures associated with a request in the
``self._parent_request`` stack are found, and the lowest index argname is
considered the earliest point in the list of fixture argnames where everything
from that point onward can be considered to execute after this fixture.
Everything before this point can be considered fixtures that this fixture
depends on, and so this fixture should register its finalizer with all of them
to ensure that if any of them are to be torn down, they will tear this fixture
down first.
This is the first part of the list of fixture argnames that is returned. The last
part of the list is everything in ``self.argnames`` as those are explicit
dependees of this fixture, so this fixture should definitely register its
finalizer with them.
"""
all_fix_names = request.fixturenames
try:
current_fix_index = all_fix_names.index(self.argname)
except ValueError:
current_fix_index = len(request.fixturenames)
parent_fixture_indexes = set()
parent_request = request._parent_request
while hasattr(parent_request, "_parent_request"):
if hasattr(parent_request, "_fixturedef"):
parent_fix_name = parent_request._fixturedef.argname
if parent_fix_name in all_fix_names:
parent_fixture_indexes.add(all_fix_names.index(parent_fix_name))
parent_request = parent_request._parent_request
stack_slice_index = min([current_fix_index, *parent_fixture_indexes])
active_fixture_argnames = all_fix_names[:stack_slice_index]
return {*active_fixture_argnames, *self.argnames}
def cache_key(self, request): def cache_key(self, request):
return request.param_index if not hasattr(request, "param") else request.param return request.param_index if not hasattr(request, "param") else request.param

View File

@ -378,7 +378,9 @@ class _bestrelpath_cache(dict):
class Session(nodes.FSCollector): class Session(nodes.FSCollector):
Interrupted = Interrupted Interrupted = Interrupted
Failed = Failed Failed = Failed
# Set on the session by runner.pytest_sessionstart.
_setupstate = None # type: SetupState _setupstate = None # type: SetupState
# Set on the session by fixtures.pytest_sessionstart.
_fixturemanager = None # type: FixtureManager _fixturemanager = None # type: FixtureManager
def __init__(self, config): def __init__(self, config):

View File

@ -90,7 +90,7 @@ class Node(metaclass=NodeMeta):
def __init__( def __init__(
self, self,
name, name: str,
parent: Optional["Node"] = None, parent: Optional["Node"] = None,
config: Optional[Config] = None, config: Optional[Config] = None,
session: Optional["Session"] = None, session: Optional["Session"] = None,
@ -476,7 +476,7 @@ class Item(Node):
if content: if content:
self._report_sections.append((when, key, content)) self._report_sections.append((when, key, content))
def reportinfo(self) -> Tuple[str, Optional[int], str]: def reportinfo(self) -> Tuple[Union[py.path.local, str], Optional[int], str]:
return self.fspath, None, "" return self.fspath, None, ""
@cached_property @cached_property

View File

@ -39,6 +39,8 @@ from _pytest.reports import TestReport
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Type from typing import Type
import pexpect
IGNORE_PAM = [ # filenames added when obtaining details about the current user IGNORE_PAM = [ # filenames added when obtaining details about the current user
"/var/lib/sss/mc/passwd" "/var/lib/sss/mc/passwd"
@ -1235,7 +1237,9 @@ class Testdir:
args = self._getpytestargs() + args args = self._getpytestargs() + args
return self.run(*args, timeout=timeout) return self.run(*args, timeout=timeout)
def spawn_pytest(self, string, expect_timeout=10.0): def spawn_pytest(
self, string: str, expect_timeout: float = 10.0
) -> "pexpect.spawn":
"""Run pytest using pexpect. """Run pytest using pexpect.
This makes sure to use the right pytest and sets up the temporary This makes sure to use the right pytest and sets up the temporary
@ -1249,7 +1253,7 @@ class Testdir:
cmd = "{} --basetemp={} {}".format(invoke, basetemp, string) cmd = "{} --basetemp={} {}".format(invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout) return self.spawn(cmd, expect_timeout=expect_timeout)
def spawn(self, cmd, expect_timeout=10.0): def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
"""Run a command using pexpect. """Run a command using pexpect.
The pexpect child is returned. The pexpect child is returned.

View File

@ -57,7 +57,7 @@ def deprecated_call(func=None, *args, **kwargs):
@overload @overload
def warns( def warns(
expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]],
*, *,
match: "Optional[Union[str, Pattern]]" = ... match: "Optional[Union[str, Pattern]]" = ...
) -> "WarningsChecker": ) -> "WarningsChecker":
@ -66,7 +66,7 @@ def warns(
@overload # noqa: F811 @overload # noqa: F811
def warns( # noqa: F811 def warns( # noqa: F811
expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]],
func: Callable, func: Callable,
*args: Any, *args: Any,
match: Optional[Union[str, "Pattern"]] = ..., match: Optional[Union[str, "Pattern"]] = ...,
@ -76,7 +76,7 @@ def warns( # noqa: F811
def warns( # noqa: F811 def warns( # noqa: F811
expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]], expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]],
*args: Any, *args: Any,
match: Optional[Union[str, "Pattern"]] = None, match: Optional[Union[str, "Pattern"]] = None,
**kwargs: Any **kwargs: Any

View File

@ -1,5 +1,6 @@
from io import StringIO from io import StringIO
from pprint import pprint from pprint import pprint
from typing import Any
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
@ -17,6 +18,7 @@ from _pytest._code.code import ReprFuncArgs
from _pytest._code.code import ReprLocals from _pytest._code.code import ReprLocals
from _pytest._code.code import ReprTraceback from _pytest._code.code import ReprTraceback
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
from _pytest.compat import TYPE_CHECKING
from _pytest.nodes import Node from _pytest.nodes import Node
from _pytest.outcomes import skip from _pytest.outcomes import skip
from _pytest.pathlib import Path from _pytest.pathlib import Path
@ -41,9 +43,14 @@ class BaseReport:
sections = [] # type: List[Tuple[str, str]] sections = [] # type: List[Tuple[str, str]]
nodeid = None # type: str nodeid = None # type: str
def __init__(self, **kw): def __init__(self, **kw: Any) -> None:
self.__dict__.update(kw) self.__dict__.update(kw)
if TYPE_CHECKING:
# Can have arbitrary fields given to __init__().
def __getattr__(self, key: str) -> Any:
raise NotImplementedError()
def toterminal(self, out) -> None: def toterminal(self, out) -> None:
if hasattr(self, "node"): if hasattr(self, "node"):
out.line(getslaveinfoline(self.node)) # type: ignore out.line(getslaveinfoline(self.node)) # type: ignore
@ -114,7 +121,7 @@ class BaseReport:
skipped = property(lambda x: x.outcome == "skipped") skipped = property(lambda x: x.outcome == "skipped")
@property @property
def fspath(self): def fspath(self) -> str:
return self.nodeid.split("::")[0] return self.nodeid.split("::")[0]
@property @property

View File

@ -15,7 +15,9 @@ from .reports import CollectErrorRepr
from .reports import CollectReport from .reports import CollectReport
from .reports import TestReport from .reports import TestReport
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ExceptionRepr
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
from _pytest.nodes import Collector
from _pytest.nodes import Node from _pytest.nodes import Node
from _pytest.outcomes import Exit from _pytest.outcomes import Exit
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
@ -251,7 +253,7 @@ def pytest_runtest_makereport(item, call):
return TestReport.from_item_and_call(item, call) return TestReport.from_item_and_call(item, call)
def pytest_make_collect_report(collector) -> CollectReport: def pytest_make_collect_report(collector: Collector) -> CollectReport:
call = CallInfo.from_call(lambda: list(collector.collect()), "collect") call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
longrepr = None longrepr = None
if not call.excinfo: if not call.excinfo:
@ -264,7 +266,10 @@ def pytest_make_collect_report(collector) -> CollectReport:
skip_exceptions.append(unittest.SkipTest) # type: ignore skip_exceptions.append(unittest.SkipTest) # type: ignore
if call.excinfo.errisinstance(tuple(skip_exceptions)): if call.excinfo.errisinstance(tuple(skip_exceptions)):
outcome = "skipped" outcome = "skipped"
r = collector._repr_failure_py(call.excinfo, "line").reprcrash r_ = collector._repr_failure_py(call.excinfo, "line")
assert isinstance(r_, ExceptionRepr), r_
r = r_.reprcrash
assert r
longrepr = (str(r.path), r.lineno, r.message) longrepr = (str(r.path), r.lineno, r.message)
else: else:
outcome = "failed" outcome = "failed"

View File

@ -606,7 +606,7 @@ class TestInvocationVariants:
def test_equivalence_pytest_pytest(self): def test_equivalence_pytest_pytest(self):
assert pytest.main == py.test.cmdline.main assert pytest.main == py.test.cmdline.main
def test_invoke_with_invalid_type(self, capsys): def test_invoke_with_invalid_type(self):
with pytest.raises( with pytest.raises(
TypeError, match="expected to be a list or tuple of strings, got: '-h'" TypeError, match="expected to be a list or tuple of strings, got: '-h'"
): ):
@ -617,7 +617,7 @@ class TestInvocationVariants:
assert retcode == ExitCode.NO_TESTS_COLLECTED assert retcode == ExitCode.NO_TESTS_COLLECTED
out, err = capsys.readouterr() out, err = capsys.readouterr()
def test_invoke_plugin_api(self, testdir, capsys): def test_invoke_plugin_api(self, capsys):
class MyPlugin: class MyPlugin:
def pytest_addoption(self, parser): def pytest_addoption(self, parser):
parser.addoption("--myopt") parser.addoption("--myopt")

View File

@ -21,8 +21,6 @@ except ImportError:
else: else:
invalidate_import_caches = getattr(importlib, "invalidate_caches", None) invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
@pytest.fixture @pytest.fixture
def limited_recursion_depth(): def limited_recursion_depth():
@ -857,7 +855,7 @@ raise ValueError()
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
class MyRepr(TerminalRepr): class MyRepr(TerminalRepr):
def toterminal(self, tw) -> None: def toterminal(self, tw: py.io.TerminalWriter) -> None:
tw.line("я") tw.line("я")
x = str(MyRepr()) x = str(MyRepr())

View File

@ -4,10 +4,13 @@
import ast import ast
import inspect import inspect
import sys import sys
from types import CodeType
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
import py
import _pytest._code import _pytest._code
import pytest import pytest
from _pytest._code import Source from _pytest._code import Source
@ -147,6 +150,10 @@ class TestAccesses:
assert len(x.lines) == 2 assert len(x.lines) == 2
assert str(x) == "def f(x):\n pass" assert str(x) == "def f(x):\n pass"
def test_getrange_step_not_supported(self) -> None:
with pytest.raises(IndexError, match=r"step"):
self.source[::2]
def test_getline(self) -> None: def test_getline(self) -> None:
x = self.source[0] x = self.source[0]
assert x == "def f(x):" assert x == "def f(x):"
@ -449,6 +456,14 @@ def test_idem_compile_and_getsource() -> None:
assert src == expected assert src == expected
def test_compile_ast() -> None:
# We don't necessarily want to support this.
# This test was added just for coverage.
stmt = ast.parse("def x(): pass")
co = _pytest._code.compile(stmt, filename="foo.py")
assert isinstance(co, CodeType)
def test_findsource_fallback() -> None: def test_findsource_fallback() -> None:
from _pytest._code.source import findsource from _pytest._code.source import findsource
@ -488,6 +503,7 @@ def test_getfslineno() -> None:
fspath, lineno = getfslineno(f) fspath, lineno = getfslineno(f)
assert isinstance(fspath, py.path.local)
assert fspath.basename == "test_source.py" assert fspath.basename == "test_source.py"
assert lineno == f.__code__.co_firstlineno - 1 # see findsource assert lineno == f.__code__.co_firstlineno - 1 # see findsource

View File

@ -17,7 +17,7 @@ if sys.gettrace():
@pytest.hookimpl(hookwrapper=True, tryfirst=True) @pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_collection_modifyitems(config, items): def pytest_collection_modifyitems(items):
"""Prefer faster tests. """Prefer faster tests.
Use a hookwrapper to do this in the beginning, so e.g. --ff still works Use a hookwrapper to do this in the beginning, so e.g. --ff still works

View File

@ -626,7 +626,7 @@ def test_log_cli_ini_level(testdir):
"cli_args", "cli_args",
["", "--log-level=WARNING", "--log-file-level=WARNING", "--log-cli-level=WARNING"], ["", "--log-level=WARNING", "--log-file-level=WARNING", "--log-cli-level=WARNING"],
) )
def test_log_cli_auto_enable(testdir, request, cli_args): def test_log_cli_auto_enable(testdir, cli_args):
"""Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI. """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI.
It should not be auto enabled if the same configs are set on the INI file. It should not be auto enabled if the same configs are set on the INI file.
""" """

View File

@ -286,7 +286,7 @@ class TestFunction:
return pytest.Function.from_parent(config=config, parent=session, **kwargs) return pytest.Function.from_parent(config=config, parent=session, **kwargs)
def test_function_equality(self, testdir, tmpdir): def test_function_equality(self, testdir):
def func1(): def func1():
pass pass
@ -492,7 +492,7 @@ class TestFunction:
) )
assert "foo" in keywords[1] and "bar" in keywords[1] and "baz" in keywords[1] assert "foo" in keywords[1] and "bar" in keywords[1] and "baz" in keywords[1]
def test_function_equality_with_callspec(self, testdir, tmpdir): def test_function_equality_with_callspec(self, testdir):
items = testdir.getitems( items = testdir.getitems(
""" """
import pytest import pytest
@ -509,11 +509,11 @@ class TestFunction:
config = item.config config = item.config
class MyPlugin1: class MyPlugin1:
def pytest_pyfunc_call(self, pyfuncitem): def pytest_pyfunc_call(self):
raise ValueError raise ValueError
class MyPlugin2: class MyPlugin2:
def pytest_pyfunc_call(self, pyfuncitem): def pytest_pyfunc_call(self):
return True return True
config.pluginmanager.register(MyPlugin1()) config.pluginmanager.register(MyPlugin1())
@ -1015,7 +1015,7 @@ class TestTracebackCutting:
class TestReportInfo: class TestReportInfo:
def test_itemreport_reportinfo(self, testdir, linecomp): def test_itemreport_reportinfo(self, testdir):
testdir.makeconftest( testdir.makeconftest(
""" """
import pytest import pytest

View File

@ -1716,138 +1716,6 @@ class TestAutouseDiscovery:
reprec.assertoutcome(passed=3) reprec.assertoutcome(passed=3)
class TestMultiLevelAutouseAndParameterization:
def test_setup_and_teardown_order(self, testdir):
"""Tests that parameterized fixtures effect subsequent fixtures. (#6436)
If a fixture uses a parameterized fixture, or, for any other reason, is executed
after the parameterized fixture in the fixture stack, then it should be affected
by the parameterization, and as a result, should be torn down before the
parameterized fixture, every time the parameterized fixture is torn down. This
should be the case even if autouse is involved and/or the linear order of
fixture execution isn't deterministic. In other words, before any fixture can be
torn down, every fixture that was executed after it must also be torn down.
"""
testdir.makepyfile(
test_auto="""
import pytest
def f(param):
return param
@pytest.fixture(scope="session", autouse=True)
def s_fix(request):
yield
@pytest.fixture(scope="package", params=["p1", "p2"], ids=f, autouse=True)
def p_fix(request):
yield
@pytest.fixture(scope="module", params=["m1", "m2"], ids=f, autouse=True)
def m_fix(request):
yield
@pytest.fixture(scope="class", autouse=True)
def another_c_fix(m_fix):
yield
@pytest.fixture(scope="class")
def c_fix():
yield
@pytest.fixture(scope="function", params=["f1", "f2"], ids=f, autouse=True)
def f_fix(request):
yield
class TestFixtures:
def test_a(self, c_fix):
pass
def test_b(self, c_fix):
pass
"""
)
result = testdir.runpytest("--setup-plan")
test_fixtures_used = (
"(fixtures used: another_c_fix, c_fix, f_fix, m_fix, p_fix, request, s_fix)"
)
result.stdout.fnmatch_lines(
"""
SETUP S s_fix
SETUP P p_fix[p1]
SETUP M m_fix[m1]
SETUP C another_c_fix (fixtures used: m_fix)
SETUP C c_fix
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_a[p1-m1-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_a[p1-m1-f2] {0}
TEARDOWN F f_fix[f2]
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_b[p1-m1-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_b[p1-m1-f2] {0}
TEARDOWN F f_fix[f2]
TEARDOWN C c_fix
TEARDOWN C another_c_fix
TEARDOWN M m_fix[m1]
SETUP M m_fix[m2]
SETUP C another_c_fix (fixtures used: m_fix)
SETUP C c_fix
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_a[p1-m2-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_a[p1-m2-f2] {0}
TEARDOWN F f_fix[f2]
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_b[p1-m2-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_b[p1-m2-f2] {0}
TEARDOWN F f_fix[f2]
TEARDOWN C c_fix
TEARDOWN C another_c_fix
TEARDOWN M m_fix[m2]
TEARDOWN P p_fix[p1]
SETUP P p_fix[p2]
SETUP M m_fix[m1]
SETUP C another_c_fix (fixtures used: m_fix)
SETUP C c_fix
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_a[p2-m1-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_a[p2-m1-f2] {0}
TEARDOWN F f_fix[f2]
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_b[p2-m1-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_b[p2-m1-f2] {0}
TEARDOWN F f_fix[f2]
TEARDOWN C c_fix
TEARDOWN C another_c_fix
TEARDOWN M m_fix[m1]
SETUP M m_fix[m2]
SETUP C another_c_fix (fixtures used: m_fix)
SETUP C c_fix
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_a[p2-m2-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_a[p2-m2-f2] {0}
TEARDOWN F f_fix[f2]
SETUP F f_fix[f1]
test_auto.py::TestFixtures::test_b[p2-m2-f1] {0}
TEARDOWN F f_fix[f1]
SETUP F f_fix[f2]
test_auto.py::TestFixtures::test_b[p2-m2-f2] {0}
TEARDOWN F f_fix[f2]
TEARDOWN C c_fix
TEARDOWN C another_c_fix
TEARDOWN M m_fix[m2]
TEARDOWN P p_fix[p2]
TEARDOWN S s_fix
""".format(
test_fixtures_used
)
)
class TestAutouseManagement: class TestAutouseManagement:
def test_autouse_conftest_mid_directory(self, testdir): def test_autouse_conftest_mid_directory(self, testdir):
pkgdir = testdir.mkpydir("xyz123") pkgdir = testdir.mkpydir("xyz123")
@ -4238,7 +4106,7 @@ def test_fixture_named_request(testdir):
) )
def test_fixture_duplicated_arguments(testdir): def test_fixture_duplicated_arguments():
"""Raise error if there are positional and keyword arguments for the same parameter (#1682).""" """Raise error if there are positional and keyword arguments for the same parameter (#1682)."""
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
@ -4253,7 +4121,7 @@ def test_fixture_duplicated_arguments(testdir):
) )
def test_fixture_with_positionals(testdir): def test_fixture_with_positionals():
"""Raise warning, but the positionals should still works (#1682).""" """Raise warning, but the positionals should still works (#1682)."""
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS

View File

@ -33,7 +33,7 @@ class TestMetafunc:
definition = DefinitionMock._create(func) definition = DefinitionMock._create(func)
return python.Metafunc(definition, fixtureinfo, config) return python.Metafunc(definition, fixtureinfo, config)
def test_no_funcargs(self, testdir): def test_no_funcargs(self):
def function(): def function():
pass pass
@ -96,7 +96,7 @@ class TestMetafunc:
): ):
metafunc.parametrize("x", [1, 2, 3], ids=gen()) metafunc.parametrize("x", [1, 2, 3], ids=gen())
def test_parametrize_bad_scope(self, testdir): def test_parametrize_bad_scope(self):
def func(x): def func(x):
pass pass
@ -188,7 +188,7 @@ class TestMetafunc:
ids = [x.id for x in metafunc._calls] ids = [x.id for x in metafunc._calls]
assert ids == ["basic", "advanced"] assert ids == ["basic", "advanced"]
def test_parametrize_with_wrong_number_of_ids(self, testdir): def test_parametrize_with_wrong_number_of_ids(self):
def func(x, y): def func(x, y):
pass pass
@ -712,7 +712,7 @@ class TestMetafunc:
result = testdir.runpytest("-v") result = testdir.runpytest("-v")
result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"]) result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"])
def test_parametrize_indirect_list_error(self, testdir): def test_parametrize_indirect_list_error(self):
"""#714""" """#714"""
def func(x, y): def func(x, y):

View File

@ -1299,7 +1299,7 @@ def test_AssertionError_message(testdir):
) )
def test_diff_newline_at_end(monkeypatch, testdir): def test_diff_newline_at_end(testdir):
testdir.makepyfile( testdir.makepyfile(
r""" r"""
def test_diff(): def test_diff():
@ -1354,7 +1354,7 @@ def test_assert_indirect_tuple_no_warning(testdir):
assert "WR1" not in output assert "WR1" not in output
def test_assert_with_unicode(monkeypatch, testdir): def test_assert_with_unicode(testdir):
testdir.makepyfile( testdir.makepyfile(
"""\ """\
def test_unicode(): def test_unicode():

View File

@ -268,9 +268,11 @@ class TestLastFailed:
"*1 failed*2 passed*", "*1 failed*2 passed*",
] ]
) )
testdir.tmpdir.join(".pytest_cache").mkdir(".git")
result = testdir.runpytest(str(p), "--lf", "--cache-clear") result = testdir.runpytest(str(p), "--lf", "--cache-clear")
result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) result.stdout.fnmatch_lines(["*1 failed*2 passed*"])
assert testdir.tmpdir.join(".pytest_cache", "README.md").isfile() assert testdir.tmpdir.join(".pytest_cache", "README.md").isfile()
assert testdir.tmpdir.join(".pytest_cache", ".git").isdir()
# Run this again to make sure clear-cache is robust # Run this again to make sure clear-cache is robust
if os.path.isdir(".pytest_cache"): if os.path.isdir(".pytest_cache"):

View File

@ -960,7 +960,7 @@ class TestFDCapture:
cap.done() cap.done()
assert s == "hello\n" assert s == "hello\n"
def test_stdin(self, tmpfile): def test_stdin(self):
cap = capture.FDCapture(0) cap = capture.FDCapture(0)
cap.start() cap.start()
x = os.read(0, 100).strip() x = os.read(0, 100).strip()
@ -981,7 +981,7 @@ class TestFDCapture:
stmp = stmp_file.read() stmp = stmp_file.read()
assert stmp == data2 assert stmp == data2
def test_simple_resume_suspend(self, tmpfile): def test_simple_resume_suspend(self):
with saved_fd(1): with saved_fd(1):
cap = capture.FDCapture(1) cap = capture.FDCapture(1)
cap.start() cap.start()

View File

@ -243,7 +243,7 @@ class TestCollectPluginHookRelay:
wascalled = [] wascalled = []
class Plugin: class Plugin:
def pytest_collect_file(self, path, parent): def pytest_collect_file(self, path):
if not path.basename.startswith("."): if not path.basename.startswith("."):
# Ignore hidden files, e.g. .testmondata. # Ignore hidden files, e.g. .testmondata.
wascalled.append(path) wascalled.append(path)
@ -257,7 +257,7 @@ class TestCollectPluginHookRelay:
wascalled = [] wascalled = []
class Plugin: class Plugin:
def pytest_collect_directory(self, path, parent): def pytest_collect_directory(self, path):
wascalled.append(path.basename) wascalled.append(path.basename)
testdir.mkdir("hello") testdir.mkdir("hello")
@ -1210,7 +1210,7 @@ def test_collect_symlink_out_of_tree(testdir):
assert result.ret == 0 assert result.ret == 0
def test_collectignore_via_conftest(testdir, monkeypatch): def test_collectignore_via_conftest(testdir):
"""collect_ignore in parent conftest skips importing child (issue #4592).""" """collect_ignore in parent conftest skips importing child (issue #4592)."""
tests = testdir.mkpydir("tests") tests = testdir.mkpydir("tests")
tests.ensure("conftest.py").write("collect_ignore = ['ignore_me']") tests.ensure("conftest.py").write("collect_ignore = ['ignore_me']")

View File

@ -32,7 +32,7 @@ class TestParseIni:
) )
) )
) )
rootdir, inifile, cfg = getcfg([sub]) _, _, cfg = getcfg([sub])
assert cfg["name"] == "value" assert cfg["name"] == "value"
config = testdir.parseconfigure(sub) config = testdir.parseconfigure(sub)
assert config.inicfg["name"] == "value" assert config.inicfg["name"] == "value"
@ -441,8 +441,6 @@ class TestConfigAPI:
class TestConfigFromdictargs: class TestConfigFromdictargs:
def test_basic_behavior(self, _sys_snapshot): def test_basic_behavior(self, _sys_snapshot):
from _pytest.config import Config
option_dict = {"verbose": 444, "foo": "bar", "capture": "no"} option_dict = {"verbose": 444, "foo": "bar", "capture": "no"}
args = ["a", "b"] args = ["a", "b"]
@ -456,8 +454,6 @@ class TestConfigFromdictargs:
def test_invocation_params_args(self, _sys_snapshot): def test_invocation_params_args(self, _sys_snapshot):
"""Show that fromdictargs can handle args in their "orig" format""" """Show that fromdictargs can handle args in their "orig" format"""
from _pytest.config import Config
option_dict = {} option_dict = {}
args = ["-vvvv", "-s", "a", "b"] args = ["-vvvv", "-s", "a", "b"]
@ -477,8 +473,6 @@ class TestConfigFromdictargs:
) )
) )
from _pytest.config import Config
inifile = "../../foo/bar.ini" inifile = "../../foo/bar.ini"
option_dict = {"inifilename": inifile, "capture": "no"} option_dict = {"inifilename": inifile, "capture": "no"}
@ -771,23 +765,23 @@ def test_notify_exception(testdir, capfd):
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
raise ValueError(1) raise ValueError(1)
config.notify_exception(excinfo, config.option) config.notify_exception(excinfo, config.option)
out, err = capfd.readouterr() _, err = capfd.readouterr()
assert "ValueError" in err assert "ValueError" in err
class A: class A:
def pytest_internalerror(self, excrepr): def pytest_internalerror(self):
return True return True
config.pluginmanager.register(A()) config.pluginmanager.register(A())
config.notify_exception(excinfo, config.option) config.notify_exception(excinfo, config.option)
out, err = capfd.readouterr() _, err = capfd.readouterr()
assert not err assert not err
config = testdir.parseconfig("-p", "no:terminal") config = testdir.parseconfig("-p", "no:terminal")
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
raise ValueError(1) raise ValueError(1)
config.notify_exception(excinfo, config.option) config.notify_exception(excinfo, config.option)
out, err = capfd.readouterr() _, err = capfd.readouterr()
assert "ValueError" in err assert "ValueError" in err
@ -797,7 +791,7 @@ def test_no_terminal_discovery_error(testdir):
assert result.ret == ExitCode.INTERRUPTED assert result.ret == ExitCode.INTERRUPTED
def test_load_initial_conftest_last_ordering(testdir, _config_for_test): def test_load_initial_conftest_last_ordering(_config_for_test):
pm = _config_for_test.pluginmanager pm = _config_for_test.pluginmanager
class My: class My:
@ -866,21 +860,21 @@ class TestRootdir:
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
b = a.mkdir("b") b = a.mkdir("b")
for args in ([tmpdir], [a], [b]): for args in ([tmpdir], [a], [b]):
rootdir, inifile, inicfg = determine_setup(None, args) rootdir, parsed_inifile, _ = determine_setup(None, args)
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile == inifile assert parsed_inifile == inifile
rootdir, inifile, inicfg = determine_setup(None, [b, a]) rootdir, parsed_inifile, _ = determine_setup(None, [b, a])
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile == inifile assert parsed_inifile == inifile
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split()) @pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
def test_pytestini_overrides_empty_other(self, tmpdir, name) -> None: def test_pytestini_overrides_empty_other(self, tmpdir, name) -> None:
inifile = tmpdir.ensure("pytest.ini") inifile = tmpdir.ensure("pytest.ini")
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
a.ensure(name) a.ensure(name)
rootdir, inifile, inicfg = determine_setup(None, [a]) rootdir, parsed_inifile, _ = determine_setup(None, [a])
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile == inifile assert parsed_inifile == inifile
def test_setuppy_fallback(self, tmpdir) -> None: def test_setuppy_fallback(self, tmpdir) -> None:
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
@ -900,7 +894,7 @@ class TestRootdir:
def test_with_specific_inifile(self, tmpdir) -> None: def test_with_specific_inifile(self, tmpdir) -> None:
inifile = tmpdir.ensure("pytest.ini") inifile = tmpdir.ensure("pytest.ini")
rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) rootdir, _, _ = determine_setup(inifile, [tmpdir])
assert rootdir == tmpdir assert rootdir == tmpdir
@ -1043,7 +1037,7 @@ class TestOverrideIniArgs:
monkeypatch.chdir(str(tmpdir)) monkeypatch.chdir(str(tmpdir))
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
b = tmpdir.mkdir("b") b = tmpdir.mkdir("b")
rootdir, inifile, inicfg = determine_setup(None, [a, b]) rootdir, inifile, _ = determine_setup(None, [a, b])
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile is None assert inifile is None
@ -1051,14 +1045,14 @@ class TestOverrideIniArgs:
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
b = tmpdir.mkdir("b") b = tmpdir.mkdir("b")
inifile = a.ensure("pytest.ini") inifile = a.ensure("pytest.ini")
rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b]) rootdir, parsed_inifile, _ = determine_setup(None, [a, b])
assert rootdir == a assert rootdir == a
assert inifile == parsed_inifile assert inifile == parsed_inifile
@pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"])) @pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"]))
def test_with_non_dir_arg(self, dirs, tmpdir) -> None: def test_with_non_dir_arg(self, dirs, tmpdir) -> None:
with tmpdir.ensure(dir=True).as_cwd(): with tmpdir.ensure(dir=True).as_cwd():
rootdir, inifile, inicfg = determine_setup(None, dirs) rootdir, inifile, _ = determine_setup(None, dirs)
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile is None assert inifile is None
@ -1066,7 +1060,7 @@ class TestOverrideIniArgs:
a = tmpdir.mkdir("a") a = tmpdir.mkdir("a")
a.ensure("exist") a.ensure("exist")
with tmpdir.as_cwd(): with tmpdir.as_cwd():
rootdir, inifile, inicfg = determine_setup(None, ["a/exist"]) rootdir, inifile, _ = determine_setup(None, ["a/exist"])
assert rootdir == tmpdir assert rootdir == tmpdir
assert inifile is None assert inifile is None
@ -1111,7 +1105,7 @@ class TestOverrideIniArgs:
config._preparse(["-o", "cache_dir=/cache", "/some/test/path"]) config._preparse(["-o", "cache_dir=/cache", "/some/test/path"])
assert config._override_ini == ["cache_dir=/cache"] assert config._override_ini == ["cache_dir=/cache"]
def test_multiple_override_ini_options(self, testdir, request): def test_multiple_override_ini_options(self, testdir):
"""Ensure a file path following a '-o' option does not generate an error (#3103)""" """Ensure a file path following a '-o' option does not generate an error (#3103)"""
testdir.makepyfile( testdir.makepyfile(
**{ **{
@ -1201,7 +1195,7 @@ def test_help_and_version_after_argument_error(testdir):
assert result.ret == ExitCode.USAGE_ERROR assert result.ret == ExitCode.USAGE_ERROR
def test_help_formatter_uses_py_get_terminal_width(testdir, monkeypatch): def test_help_formatter_uses_py_get_terminal_width(monkeypatch):
from _pytest.config.argparsing import DropShorterLongHelpFormatter from _pytest.config.argparsing import DropShorterLongHelpFormatter
monkeypatch.setenv("COLUMNS", "90") monkeypatch.setenv("COLUMNS", "90")

View File

@ -357,7 +357,7 @@ def test_conftest_import_order(testdir, monkeypatch):
assert conftest._getconftestmodules(sub) == [ct1, ct2] assert conftest._getconftestmodules(sub) == [ct1, ct2]
def test_fixture_dependency(testdir, monkeypatch): def test_fixture_dependency(testdir):
ct1 = testdir.makeconftest("") ct1 = testdir.makeconftest("")
ct1 = testdir.makepyfile("__init__.py") ct1 = testdir.makepyfile("__init__.py")
ct1.write("") ct1.write("")

View File

@ -463,7 +463,7 @@ class TestPDB:
child.read() child.read()
self.flush(child) self.flush(child)
def test_pdb_interaction_doctest(self, testdir, monkeypatch): def test_pdb_interaction_doctest(self, testdir):
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
def function_1(): def function_1():
@ -489,7 +489,7 @@ class TestPDB:
assert "1 failed" in rest assert "1 failed" in rest
self.flush(child) self.flush(child)
def test_doctest_set_trace_quit(self, testdir, monkeypatch): def test_doctest_set_trace_quit(self, testdir):
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
def function_1(): def function_1():
@ -833,7 +833,7 @@ class TestPDB:
] ]
) )
def test_pdb_validate_usepdb_cls(self, testdir): def test_pdb_validate_usepdb_cls(self):
assert _validate_usepdb_cls("os.path:dirname.__name__") == ( assert _validate_usepdb_cls("os.path:dirname.__name__") == (
"os.path", "os.path",
"dirname.__name__", "dirname.__name__",

View File

@ -82,7 +82,7 @@ def test_timeout(testdir, enabled):
@pytest.mark.parametrize("hook_name", ["pytest_enter_pdb", "pytest_exception_interact"]) @pytest.mark.parametrize("hook_name", ["pytest_enter_pdb", "pytest_exception_interact"])
def test_cancel_timeout_on_hook(monkeypatch, pytestconfig, hook_name): def test_cancel_timeout_on_hook(monkeypatch, hook_name):
"""Make sure that we are cancelling any scheduled traceback dumping due """Make sure that we are cancelling any scheduled traceback dumping due
to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any other interactive to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any other interactive
exception (pytest-dev/pytest-faulthandler#14). exception (pytest-dev/pytest-faulthandler#14).

View File

@ -57,7 +57,7 @@ def test_traceconfig(testdir):
result.stdout.fnmatch_lines(["*using*pytest*py*", "*active plugins*"]) result.stdout.fnmatch_lines(["*using*pytest*py*", "*active plugins*"])
def test_debug(testdir, monkeypatch): def test_debug(testdir):
result = testdir.runpytest_subprocess("--debug") result = testdir.runpytest_subprocess("--debug")
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
p = testdir.tmpdir.join("pytestdebug.log") p = testdir.tmpdir.join("pytestdebug.log")

View File

@ -41,7 +41,7 @@ class TestMark:
mark._some_name mark._some_name
def test_marked_class_run_twice(testdir, request): def test_marked_class_run_twice(testdir):
"""Test fails file is run twice that contains marked class. """Test fails file is run twice that contains marked class.
See issue#683. See issue#683.
""" """

View File

@ -375,3 +375,17 @@ def test_skip_test_with_unicode(testdir):
) )
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["* 1 skipped *"]) result.stdout.fnmatch_lines(["* 1 skipped *"])
def test_issue_6517(testdir):
testdir.makepyfile(
"""
from nose.tools import raises
@raises(RuntimeError)
def test_fail_without_tcp():
raise RuntimeError
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["* 1 passed *"])

View File

@ -251,14 +251,14 @@ class TestParser:
assert args.func_arg is False assert args.func_arg is False
assert args.file_or_dir == ["abcd"] assert args.file_or_dir == ["abcd"]
def test_drop_short_help0(self, parser: parseopt.Parser, capsys) -> None: def test_drop_short_help0(self, parser: parseopt.Parser) -> None:
parser.addoption("--func-args", "--doit", help="foo", action="store_true") parser.addoption("--func-args", "--doit", help="foo", action="store_true")
parser.parse([]) parser.parse([])
help = parser.optparser.format_help() help = parser.optparser.format_help()
assert "--func-args, --doit foo" in help assert "--func-args, --doit foo" in help
# testing would be more helpful with all help generated # testing would be more helpful with all help generated
def test_drop_short_help1(self, parser: parseopt.Parser, capsys) -> None: def test_drop_short_help1(self, parser: parseopt.Parser) -> None:
group = parser.getgroup("general") group = parser.getgroup("general")
group.addoption("--doit", "--func-args", action="store_true", help="foo") group.addoption("--doit", "--func-args", action="store_true", help="foo")
group._addoption( group._addoption(

View File

@ -71,7 +71,7 @@ class TestPytestPluginInteractions:
values = [] values = []
class A: class A:
def pytest_configure(self, config): def pytest_configure(self):
values.append(self) values.append(self)
config.pluginmanager.register(A()) config.pluginmanager.register(A())

View File

@ -2,6 +2,7 @@ import os
import subprocess import subprocess
import sys import sys
import time import time
from typing import List
import py.path import py.path
@ -9,6 +10,7 @@ import _pytest.pytester as pytester
import pytest import pytest
from _pytest.config import PytestPluginManager from _pytest.config import PytestPluginManager
from _pytest.main import ExitCode from _pytest.main import ExitCode
from _pytest.outcomes import Failed
from _pytest.pytester import CwdSnapshot from _pytest.pytester import CwdSnapshot
from _pytest.pytester import HookRecorder from _pytest.pytester import HookRecorder
from _pytest.pytester import LineMatcher from _pytest.pytester import LineMatcher
@ -16,7 +18,7 @@ from _pytest.pytester import SysModulesSnapshot
from _pytest.pytester import SysPathsSnapshot from _pytest.pytester import SysPathsSnapshot
def test_make_hook_recorder(testdir): def test_make_hook_recorder(testdir) -> None:
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
recorder = testdir.make_hook_recorder(item.config.pluginmanager) recorder = testdir.make_hook_recorder(item.config.pluginmanager)
assert not recorder.getfailures() assert not recorder.getfailures()
@ -36,23 +38,23 @@ def test_make_hook_recorder(testdir):
failures = recorder.getfailures() failures = recorder.getfailures()
assert failures == [rep] assert failures == [rep]
class rep: class rep2:
excinfo = None excinfo = None
passed = False passed = False
failed = False failed = False
skipped = True skipped = True
when = "call" when = "call"
rep.passed = False rep2.passed = False
rep.skipped = True rep2.skipped = True
recorder.hook.pytest_runtest_logreport(report=rep) recorder.hook.pytest_runtest_logreport(report=rep2)
modcol = testdir.getmodulecol("") modcol = testdir.getmodulecol("")
rep = modcol.config.hook.pytest_make_collect_report(collector=modcol) rep3 = modcol.config.hook.pytest_make_collect_report(collector=modcol)
rep.passed = False rep3.passed = False
rep.failed = True rep3.failed = True
rep.skipped = False rep3.skipped = False
recorder.hook.pytest_collectreport(report=rep) recorder.hook.pytest_collectreport(report=rep3)
passed, skipped, failed = recorder.listoutcomes() passed, skipped, failed = recorder.listoutcomes()
assert not passed and skipped and failed assert not passed and skipped and failed
@ -65,17 +67,17 @@ def test_make_hook_recorder(testdir):
recorder.unregister() recorder.unregister()
recorder.clear() recorder.clear()
recorder.hook.pytest_runtest_logreport(report=rep) recorder.hook.pytest_runtest_logreport(report=rep3)
pytest.raises(ValueError, recorder.getfailures) pytest.raises(ValueError, recorder.getfailures)
def test_parseconfig(testdir): def test_parseconfig(testdir) -> None:
config1 = testdir.parseconfig() config1 = testdir.parseconfig()
config2 = testdir.parseconfig() config2 = testdir.parseconfig()
assert config2 is not config1 assert config2 is not config1
def test_testdir_runs_with_plugin(testdir): def test_testdir_runs_with_plugin(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
pytest_plugins = "pytester" pytest_plugins = "pytester"
@ -87,7 +89,7 @@ def test_testdir_runs_with_plugin(testdir):
result.assert_outcomes(passed=1) result.assert_outcomes(passed=1)
def test_runresult_assertion_on_xfail(testdir): def test_runresult_assertion_on_xfail(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@ -104,7 +106,7 @@ def test_runresult_assertion_on_xfail(testdir):
assert result.ret == 0 assert result.ret == 0
def test_runresult_assertion_on_xpassed(testdir): def test_runresult_assertion_on_xpassed(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@ -121,7 +123,7 @@ def test_runresult_assertion_on_xpassed(testdir):
assert result.ret == 0 assert result.ret == 0
def test_xpassed_with_strict_is_considered_a_failure(testdir): def test_xpassed_with_strict_is_considered_a_failure(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@ -154,13 +156,13 @@ def make_holder():
def pytest_xyz_noarg(): def pytest_xyz_noarg():
"x" "x"
apimod.pytest_xyz = pytest_xyz apimod.pytest_xyz = pytest_xyz # type: ignore
apimod.pytest_xyz_noarg = pytest_xyz_noarg apimod.pytest_xyz_noarg = pytest_xyz_noarg # type: ignore
return apiclass, apimod return apiclass, apimod
@pytest.mark.parametrize("holder", make_holder()) @pytest.mark.parametrize("holder", make_holder())
def test_hookrecorder_basic(holder): def test_hookrecorder_basic(holder) -> None:
pm = PytestPluginManager() pm = PytestPluginManager()
pm.add_hookspecs(holder) pm.add_hookspecs(holder)
rec = HookRecorder(pm) rec = HookRecorder(pm)
@ -168,17 +170,17 @@ def test_hookrecorder_basic(holder):
call = rec.popcall("pytest_xyz") call = rec.popcall("pytest_xyz")
assert call.arg == 123 assert call.arg == 123
assert call._name == "pytest_xyz" assert call._name == "pytest_xyz"
pytest.raises(pytest.fail.Exception, rec.popcall, "abc") pytest.raises(Failed, rec.popcall, "abc")
pm.hook.pytest_xyz_noarg() pm.hook.pytest_xyz_noarg()
call = rec.popcall("pytest_xyz_noarg") call = rec.popcall("pytest_xyz_noarg")
assert call._name == "pytest_xyz_noarg" assert call._name == "pytest_xyz_noarg"
def test_makepyfile_unicode(testdir): def test_makepyfile_unicode(testdir) -> None:
testdir.makepyfile(chr(0xFFFD)) testdir.makepyfile(chr(0xFFFD))
def test_makepyfile_utf8(testdir): def test_makepyfile_utf8(testdir) -> None:
"""Ensure makepyfile accepts utf-8 bytes as input (#2738)""" """Ensure makepyfile accepts utf-8 bytes as input (#2738)"""
utf8_contents = """ utf8_contents = """
def setup_function(function): def setup_function(function):
@ -189,7 +191,7 @@ def test_makepyfile_utf8(testdir):
class TestInlineRunModulesCleanup: class TestInlineRunModulesCleanup:
def test_inline_run_test_module_not_cleaned_up(self, testdir): def test_inline_run_test_module_not_cleaned_up(self, testdir) -> None:
test_mod = testdir.makepyfile("def test_foo(): assert True") test_mod = testdir.makepyfile("def test_foo(): assert True")
result = testdir.inline_run(str(test_mod)) result = testdir.inline_run(str(test_mod))
assert result.ret == ExitCode.OK assert result.ret == ExitCode.OK
@ -200,9 +202,9 @@ class TestInlineRunModulesCleanup:
def spy_factory(self): def spy_factory(self):
class SysModulesSnapshotSpy: class SysModulesSnapshotSpy:
instances = [] instances = [] # type: List[SysModulesSnapshotSpy]
def __init__(self, preserve=None): def __init__(self, preserve=None) -> None:
SysModulesSnapshotSpy.instances.append(self) SysModulesSnapshotSpy.instances.append(self)
self._spy_restore_count = 0 self._spy_restore_count = 0
self._spy_preserve = preserve self._spy_preserve = preserve
@ -216,7 +218,7 @@ class TestInlineRunModulesCleanup:
def test_inline_run_taking_and_restoring_a_sys_modules_snapshot( def test_inline_run_taking_and_restoring_a_sys_modules_snapshot(
self, testdir, monkeypatch self, testdir, monkeypatch
): ) -> None:
spy_factory = self.spy_factory() spy_factory = self.spy_factory()
monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory)
testdir.syspathinsert() testdir.syspathinsert()
@ -237,7 +239,7 @@ class TestInlineRunModulesCleanup:
def test_inline_run_sys_modules_snapshot_restore_preserving_modules( def test_inline_run_sys_modules_snapshot_restore_preserving_modules(
self, testdir, monkeypatch self, testdir, monkeypatch
): ) -> None:
spy_factory = self.spy_factory() spy_factory = self.spy_factory()
monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory)
test_mod = testdir.makepyfile("def test_foo(): pass") test_mod = testdir.makepyfile("def test_foo(): pass")
@ -248,7 +250,7 @@ class TestInlineRunModulesCleanup:
assert spy._spy_preserve("zope.interface") assert spy._spy_preserve("zope.interface")
assert spy._spy_preserve("zopelicious") assert spy._spy_preserve("zopelicious")
def test_external_test_module_imports_not_cleaned_up(self, testdir): def test_external_test_module_imports_not_cleaned_up(self, testdir) -> None:
testdir.syspathinsert() testdir.syspathinsert()
testdir.makepyfile(imported="data = 'you son of a silly person'") testdir.makepyfile(imported="data = 'you son of a silly person'")
import imported import imported
@ -263,7 +265,7 @@ class TestInlineRunModulesCleanup:
assert imported.data == 42 assert imported.data == 42
def test_assert_outcomes_after_pytest_error(testdir): def test_assert_outcomes_after_pytest_error(testdir) -> None:
testdir.makepyfile("def test_foo(): assert True") testdir.makepyfile("def test_foo(): assert True")
result = testdir.runpytest("--unexpected-argument") result = testdir.runpytest("--unexpected-argument")
@ -271,7 +273,7 @@ def test_assert_outcomes_after_pytest_error(testdir):
result.assert_outcomes(passed=0) result.assert_outcomes(passed=0)
def test_cwd_snapshot(tmpdir): def test_cwd_snapshot(tmpdir) -> None:
foo = tmpdir.ensure("foo", dir=1) foo = tmpdir.ensure("foo", dir=1)
bar = tmpdir.ensure("bar", dir=1) bar = tmpdir.ensure("bar", dir=1)
foo.chdir() foo.chdir()
@ -285,16 +287,16 @@ def test_cwd_snapshot(tmpdir):
class TestSysModulesSnapshot: class TestSysModulesSnapshot:
key = "my-test-module" key = "my-test-module"
def test_remove_added(self): def test_remove_added(self) -> None:
original = dict(sys.modules) original = dict(sys.modules)
assert self.key not in sys.modules assert self.key not in sys.modules
snapshot = SysModulesSnapshot() snapshot = SysModulesSnapshot()
sys.modules[self.key] = "something" sys.modules[self.key] = "something" # type: ignore
assert self.key in sys.modules assert self.key in sys.modules
snapshot.restore() snapshot.restore()
assert sys.modules == original assert sys.modules == original
def test_add_removed(self, monkeypatch): def test_add_removed(self, monkeypatch) -> None:
assert self.key not in sys.modules assert self.key not in sys.modules
monkeypatch.setitem(sys.modules, self.key, "something") monkeypatch.setitem(sys.modules, self.key, "something")
assert self.key in sys.modules assert self.key in sys.modules
@ -305,17 +307,17 @@ class TestSysModulesSnapshot:
snapshot.restore() snapshot.restore()
assert sys.modules == original assert sys.modules == original
def test_restore_reloaded(self, monkeypatch): def test_restore_reloaded(self, monkeypatch) -> None:
assert self.key not in sys.modules assert self.key not in sys.modules
monkeypatch.setitem(sys.modules, self.key, "something") monkeypatch.setitem(sys.modules, self.key, "something")
assert self.key in sys.modules assert self.key in sys.modules
original = dict(sys.modules) original = dict(sys.modules)
snapshot = SysModulesSnapshot() snapshot = SysModulesSnapshot()
sys.modules[self.key] = "something else" sys.modules[self.key] = "something else" # type: ignore
snapshot.restore() snapshot.restore()
assert sys.modules == original assert sys.modules == original
def test_preserve_modules(self, monkeypatch): def test_preserve_modules(self, monkeypatch) -> None:
key = [self.key + str(i) for i in range(3)] key = [self.key + str(i) for i in range(3)]
assert not any(k in sys.modules for k in key) assert not any(k in sys.modules for k in key)
for i, k in enumerate(key): for i, k in enumerate(key):
@ -326,17 +328,17 @@ class TestSysModulesSnapshot:
return name in (key[0], key[1], "some-other-key") return name in (key[0], key[1], "some-other-key")
snapshot = SysModulesSnapshot(preserve=preserve) snapshot = SysModulesSnapshot(preserve=preserve)
sys.modules[key[0]] = original[key[0]] = "something else0" sys.modules[key[0]] = original[key[0]] = "something else0" # type: ignore
sys.modules[key[1]] = original[key[1]] = "something else1" sys.modules[key[1]] = original[key[1]] = "something else1" # type: ignore
sys.modules[key[2]] = "something else2" sys.modules[key[2]] = "something else2" # type: ignore
snapshot.restore() snapshot.restore()
assert sys.modules == original assert sys.modules == original
def test_preserve_container(self, monkeypatch): def test_preserve_container(self, monkeypatch) -> None:
original = dict(sys.modules) original = dict(sys.modules)
assert self.key not in original assert self.key not in original
replacement = dict(sys.modules) replacement = dict(sys.modules)
replacement[self.key] = "life of brian" replacement[self.key] = "life of brian" # type: ignore
snapshot = SysModulesSnapshot() snapshot = SysModulesSnapshot()
monkeypatch.setattr(sys, "modules", replacement) monkeypatch.setattr(sys, "modules", replacement)
snapshot.restore() snapshot.restore()
@ -349,10 +351,10 @@ class TestSysPathsSnapshot:
other_path = {"path": "meta_path", "meta_path": "path"} other_path = {"path": "meta_path", "meta_path": "path"}
@staticmethod @staticmethod
def path(n): def path(n: int) -> str:
return "my-dirty-little-secret-" + str(n) return "my-dirty-little-secret-" + str(n)
def test_restore(self, monkeypatch, path_type): def test_restore(self, monkeypatch, path_type) -> None:
other_path_type = self.other_path[path_type] other_path_type = self.other_path[path_type]
for i in range(10): for i in range(10):
assert self.path(i) not in getattr(sys, path_type) assert self.path(i) not in getattr(sys, path_type)
@ -375,12 +377,12 @@ class TestSysPathsSnapshot:
assert getattr(sys, path_type) == original assert getattr(sys, path_type) == original
assert getattr(sys, other_path_type) == original_other assert getattr(sys, other_path_type) == original_other
def test_preserve_container(self, monkeypatch, path_type): def test_preserve_container(self, monkeypatch, path_type) -> None:
other_path_type = self.other_path[path_type] other_path_type = self.other_path[path_type]
original_data = list(getattr(sys, path_type)) original_data = list(getattr(sys, path_type))
original_other = getattr(sys, other_path_type) original_other = getattr(sys, other_path_type)
original_other_data = list(original_other) original_other_data = list(original_other)
new = [] new = [] # type: List[object]
snapshot = SysPathsSnapshot() snapshot = SysPathsSnapshot()
monkeypatch.setattr(sys, path_type, new) monkeypatch.setattr(sys, path_type, new)
snapshot.restore() snapshot.restore()
@ -390,7 +392,7 @@ class TestSysPathsSnapshot:
assert getattr(sys, other_path_type) == original_other_data assert getattr(sys, other_path_type) == original_other_data
def test_testdir_subprocess(testdir): def test_testdir_subprocess(testdir) -> None:
testfile = testdir.makepyfile("def test_one(): pass") testfile = testdir.makepyfile("def test_one(): pass")
assert testdir.runpytest_subprocess(testfile).ret == 0 assert testdir.runpytest_subprocess(testfile).ret == 0
@ -416,17 +418,17 @@ def test_testdir_subprocess_via_runpytest_arg(testdir) -> None:
assert result.ret == 0 assert result.ret == 0
def test_unicode_args(testdir): def test_unicode_args(testdir) -> None:
result = testdir.runpytest("-k", "💩") result = testdir.runpytest("-k", "💩")
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_testdir_run_no_timeout(testdir): def test_testdir_run_no_timeout(testdir) -> None:
testfile = testdir.makepyfile("def test_no_timeout(): pass") testfile = testdir.makepyfile("def test_no_timeout(): pass")
assert testdir.runpytest_subprocess(testfile).ret == ExitCode.OK assert testdir.runpytest_subprocess(testfile).ret == ExitCode.OK
def test_testdir_run_with_timeout(testdir): def test_testdir_run_with_timeout(testdir) -> None:
testfile = testdir.makepyfile("def test_no_timeout(): pass") testfile = testdir.makepyfile("def test_no_timeout(): pass")
timeout = 120 timeout = 120
@ -440,7 +442,7 @@ def test_testdir_run_with_timeout(testdir):
assert duration < timeout assert duration < timeout
def test_testdir_run_timeout_expires(testdir): def test_testdir_run_timeout_expires(testdir) -> None:
testfile = testdir.makepyfile( testfile = testdir.makepyfile(
""" """
import time import time
@ -452,7 +454,7 @@ def test_testdir_run_timeout_expires(testdir):
testdir.runpytest_subprocess(testfile, timeout=1) testdir.runpytest_subprocess(testfile, timeout=1)
def test_linematcher_with_nonlist(): def test_linematcher_with_nonlist() -> None:
"""Test LineMatcher with regard to passing in a set (accidentally).""" """Test LineMatcher with regard to passing in a set (accidentally)."""
lm = LineMatcher([]) lm = LineMatcher([])
@ -467,10 +469,11 @@ def test_linematcher_with_nonlist():
assert lm._getlines(set()) == set() assert lm._getlines(set()) == set()
def test_linematcher_match_failure(): def test_linematcher_match_failure() -> None:
lm = LineMatcher(["foo", "foo", "bar"]) lm = LineMatcher(["foo", "foo", "bar"])
with pytest.raises(pytest.fail.Exception) as e: with pytest.raises(Failed) as e:
lm.fnmatch_lines(["foo", "f*", "baz"]) lm.fnmatch_lines(["foo", "f*", "baz"])
assert e.value.msg is not None
assert e.value.msg.splitlines() == [ assert e.value.msg.splitlines() == [
"exact match: 'foo'", "exact match: 'foo'",
"fnmatch: 'f*'", "fnmatch: 'f*'",
@ -481,8 +484,9 @@ def test_linematcher_match_failure():
] ]
lm = LineMatcher(["foo", "foo", "bar"]) lm = LineMatcher(["foo", "foo", "bar"])
with pytest.raises(pytest.fail.Exception) as e: with pytest.raises(Failed) as e:
lm.re_match_lines(["foo", "^f.*", "baz"]) lm.re_match_lines(["foo", "^f.*", "baz"])
assert e.value.msg is not None
assert e.value.msg.splitlines() == [ assert e.value.msg.splitlines() == [
"exact match: 'foo'", "exact match: 'foo'",
"re.match: '^f.*'", "re.match: '^f.*'",
@ -494,7 +498,7 @@ def test_linematcher_match_failure():
@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"]) @pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"])
def test_no_matching(function): def test_no_matching(function) -> None:
if function == "no_fnmatch_line": if function == "no_fnmatch_line":
good_pattern = "*.py OK*" good_pattern = "*.py OK*"
bad_pattern = "*X.py OK*" bad_pattern = "*X.py OK*"
@ -515,7 +519,7 @@ def test_no_matching(function):
# check the function twice to ensure we don't accumulate the internal buffer # check the function twice to ensure we don't accumulate the internal buffer
for i in range(2): for i in range(2):
with pytest.raises(pytest.fail.Exception) as e: with pytest.raises(Failed) as e:
func = getattr(lm, function) func = getattr(lm, function)
func(good_pattern) func(good_pattern)
obtained = str(e.value).splitlines() obtained = str(e.value).splitlines()
@ -542,15 +546,15 @@ def test_no_matching(function):
func(bad_pattern) # bad pattern does not match any line: passes func(bad_pattern) # bad pattern does not match any line: passes
def test_no_matching_after_match(): def test_no_matching_after_match() -> None:
lm = LineMatcher(["1", "2", "3"]) lm = LineMatcher(["1", "2", "3"])
lm.fnmatch_lines(["1", "3"]) lm.fnmatch_lines(["1", "3"])
with pytest.raises(pytest.fail.Exception) as e: with pytest.raises(Failed) as e:
lm.no_fnmatch_line("*") lm.no_fnmatch_line("*")
assert str(e.value).splitlines() == ["fnmatch: '*'", " with: '1'"] assert str(e.value).splitlines() == ["fnmatch: '*'", " with: '1'"]
def test_pytester_addopts_before_testdir(request, monkeypatch): def test_pytester_addopts_before_testdir(request, monkeypatch) -> None:
orig = os.environ.get("PYTEST_ADDOPTS", None) orig = os.environ.get("PYTEST_ADDOPTS", None)
monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused") monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused")
testdir = request.getfixturevalue("testdir") testdir = request.getfixturevalue("testdir")
@ -561,7 +565,7 @@ def test_pytester_addopts_before_testdir(request, monkeypatch):
assert os.environ.get("PYTEST_ADDOPTS") == orig assert os.environ.get("PYTEST_ADDOPTS") == orig
def test_run_stdin(testdir): def test_run_stdin(testdir) -> None:
with pytest.raises(testdir.TimeoutExpired): with pytest.raises(testdir.TimeoutExpired):
testdir.run( testdir.run(
sys.executable, sys.executable,
@ -591,7 +595,7 @@ def test_run_stdin(testdir):
assert result.ret == 0 assert result.ret == 0
def test_popen_stdin_pipe(testdir): def test_popen_stdin_pipe(testdir) -> None:
proc = testdir.popen( proc = testdir.popen(
[sys.executable, "-c", "import sys; print(sys.stdin.read())"], [sys.executable, "-c", "import sys; print(sys.stdin.read())"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -605,7 +609,7 @@ def test_popen_stdin_pipe(testdir):
assert proc.returncode == 0 assert proc.returncode == 0
def test_popen_stdin_bytes(testdir): def test_popen_stdin_bytes(testdir) -> None:
proc = testdir.popen( proc = testdir.popen(
[sys.executable, "-c", "import sys; print(sys.stdin.read())"], [sys.executable, "-c", "import sys; print(sys.stdin.read())"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -618,7 +622,7 @@ def test_popen_stdin_bytes(testdir):
assert proc.returncode == 0 assert proc.returncode == 0
def test_popen_default_stdin_stderr_and_stdin_None(testdir): def test_popen_default_stdin_stderr_and_stdin_None(testdir) -> None:
# stdout, stderr default to pipes, # stdout, stderr default to pipes,
# stdin can be None to not close the pipe, avoiding # stdin can be None to not close the pipe, avoiding
# "ValueError: flush of closed file" with `communicate()`. # "ValueError: flush of closed file" with `communicate()`.
@ -637,7 +641,7 @@ def test_popen_default_stdin_stderr_and_stdin_None(testdir):
assert proc.returncode == 0 assert proc.returncode == 0
def test_spawn_uses_tmphome(testdir): def test_spawn_uses_tmphome(testdir) -> None:
tmphome = str(testdir.tmpdir) tmphome = str(testdir.tmpdir)
assert os.environ.get("HOME") == tmphome assert os.environ.get("HOME") == tmphome
@ -659,7 +663,7 @@ def test_spawn_uses_tmphome(testdir):
assert child.wait() == 0, out.decode("utf8") assert child.wait() == 0, out.decode("utf8")
def test_run_result_repr(): def test_run_result_repr() -> None:
outlines = ["some", "normal", "output"] outlines = ["some", "normal", "output"]
errlines = ["some", "nasty", "errors", "happened"] errlines = ["some", "nasty", "errors", "happened"]

View File

@ -1,17 +1,19 @@
import re import re
import warnings import warnings
from typing import Optional
import pytest import pytest
from _pytest.outcomes import Failed
from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import WarningsRecorder
def test_recwarn_stacklevel(recwarn): def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None:
warnings.warn("hello") warnings.warn("hello")
warn = recwarn.pop() warn = recwarn.pop()
assert warn.filename == __file__ assert warn.filename == __file__
def test_recwarn_functional(testdir): def test_recwarn_functional(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
import warnings import warnings
@ -26,7 +28,7 @@ def test_recwarn_functional(testdir):
class TestWarningsRecorderChecker: class TestWarningsRecorderChecker:
def test_recording(self): def test_recording(self) -> None:
rec = WarningsRecorder() rec = WarningsRecorder()
with rec: with rec:
assert not rec.list assert not rec.list
@ -42,23 +44,23 @@ class TestWarningsRecorderChecker:
assert values is rec.list assert values is rec.list
pytest.raises(AssertionError, rec.pop) pytest.raises(AssertionError, rec.pop)
def test_warn_stacklevel(self): def test_warn_stacklevel(self) -> None:
"""#4243""" """#4243"""
rec = WarningsRecorder() rec = WarningsRecorder()
with rec: with rec:
warnings.warn("test", DeprecationWarning, 2) warnings.warn("test", DeprecationWarning, 2)
def test_typechecking(self): def test_typechecking(self) -> None:
from _pytest.recwarn import WarningsChecker from _pytest.recwarn import WarningsChecker
with pytest.raises(TypeError): with pytest.raises(TypeError):
WarningsChecker(5) WarningsChecker(5) # type: ignore
with pytest.raises(TypeError): with pytest.raises(TypeError):
WarningsChecker(("hi", RuntimeWarning)) WarningsChecker(("hi", RuntimeWarning)) # type: ignore
with pytest.raises(TypeError): with pytest.raises(TypeError):
WarningsChecker([DeprecationWarning, RuntimeWarning]) WarningsChecker([DeprecationWarning, RuntimeWarning]) # type: ignore
def test_invalid_enter_exit(self): def test_invalid_enter_exit(self) -> None:
# wrap this test in WarningsRecorder to ensure warning state gets reset # wrap this test in WarningsRecorder to ensure warning state gets reset
with WarningsRecorder(): with WarningsRecorder():
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
@ -75,50 +77,52 @@ class TestWarningsRecorderChecker:
class TestDeprecatedCall: class TestDeprecatedCall:
"""test pytest.deprecated_call()""" """test pytest.deprecated_call()"""
def dep(self, i, j=None): def dep(self, i: int, j: Optional[int] = None) -> int:
if i == 0: if i == 0:
warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) warnings.warn("is deprecated", DeprecationWarning, stacklevel=1)
return 42 return 42
def dep_explicit(self, i): def dep_explicit(self, i: int) -> None:
if i == 0: if i == 0:
warnings.warn_explicit( warnings.warn_explicit(
"dep_explicit", category=DeprecationWarning, filename="hello", lineno=3 "dep_explicit", category=DeprecationWarning, filename="hello", lineno=3
) )
def test_deprecated_call_raises(self): def test_deprecated_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception, match="No warnings of type"): with pytest.raises(Failed, match="No warnings of type"):
pytest.deprecated_call(self.dep, 3, 5) pytest.deprecated_call(self.dep, 3, 5)
def test_deprecated_call(self): def test_deprecated_call(self) -> None:
pytest.deprecated_call(self.dep, 0, 5) pytest.deprecated_call(self.dep, 0, 5)
def test_deprecated_call_ret(self): def test_deprecated_call_ret(self) -> None:
ret = pytest.deprecated_call(self.dep, 0) ret = pytest.deprecated_call(self.dep, 0)
assert ret == 42 assert ret == 42
def test_deprecated_call_preserves(self): def test_deprecated_call_preserves(self) -> None:
onceregistry = warnings.onceregistry.copy() # Type ignored because `onceregistry` and `filters` are not
filters = warnings.filters[:] # documented API.
onceregistry = warnings.onceregistry.copy() # type: ignore
filters = warnings.filters[:] # type: ignore
warn = warnings.warn warn = warnings.warn
warn_explicit = warnings.warn_explicit warn_explicit = warnings.warn_explicit
self.test_deprecated_call_raises() self.test_deprecated_call_raises()
self.test_deprecated_call() self.test_deprecated_call()
assert onceregistry == warnings.onceregistry assert onceregistry == warnings.onceregistry # type: ignore
assert filters == warnings.filters assert filters == warnings.filters # type: ignore
assert warn is warnings.warn assert warn is warnings.warn
assert warn_explicit is warnings.warn_explicit assert warn_explicit is warnings.warn_explicit
def test_deprecated_explicit_call_raises(self): def test_deprecated_explicit_call_raises(self) -> None:
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
pytest.deprecated_call(self.dep_explicit, 3) pytest.deprecated_call(self.dep_explicit, 3)
def test_deprecated_explicit_call(self): def test_deprecated_explicit_call(self) -> None:
pytest.deprecated_call(self.dep_explicit, 0) pytest.deprecated_call(self.dep_explicit, 0)
pytest.deprecated_call(self.dep_explicit, 0) pytest.deprecated_call(self.dep_explicit, 0)
@pytest.mark.parametrize("mode", ["context_manager", "call"]) @pytest.mark.parametrize("mode", ["context_manager", "call"])
def test_deprecated_call_no_warning(self, mode): def test_deprecated_call_no_warning(self, mode) -> None:
"""Ensure deprecated_call() raises the expected failure when its block/function does """Ensure deprecated_call() raises the expected failure when its block/function does
not raise a deprecation warning. not raise a deprecation warning.
""" """
@ -127,7 +131,7 @@ class TestDeprecatedCall:
pass pass
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)" msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
with pytest.raises(pytest.fail.Exception, match=msg): with pytest.raises(Failed, match=msg):
if mode == "call": if mode == "call":
pytest.deprecated_call(f) pytest.deprecated_call(f)
else: else:
@ -140,7 +144,7 @@ class TestDeprecatedCall:
@pytest.mark.parametrize("mode", ["context_manager", "call"]) @pytest.mark.parametrize("mode", ["context_manager", "call"])
@pytest.mark.parametrize("call_f_first", [True, False]) @pytest.mark.parametrize("call_f_first", [True, False])
@pytest.mark.filterwarnings("ignore") @pytest.mark.filterwarnings("ignore")
def test_deprecated_call_modes(self, warning_type, mode, call_f_first): def test_deprecated_call_modes(self, warning_type, mode, call_f_first) -> None:
"""Ensure deprecated_call() captures a deprecation warning as expected inside its """Ensure deprecated_call() captures a deprecation warning as expected inside its
block/function. block/function.
""" """
@ -159,7 +163,7 @@ class TestDeprecatedCall:
assert f() == 10 assert f() == 10
@pytest.mark.parametrize("mode", ["context_manager", "call"]) @pytest.mark.parametrize("mode", ["context_manager", "call"])
def test_deprecated_call_exception_is_raised(self, mode): def test_deprecated_call_exception_is_raised(self, mode) -> None:
"""If the block of the code being tested by deprecated_call() raises an exception, """If the block of the code being tested by deprecated_call() raises an exception,
it must raise the exception undisturbed. it must raise the exception undisturbed.
""" """
@ -174,7 +178,7 @@ class TestDeprecatedCall:
with pytest.deprecated_call(): with pytest.deprecated_call():
f() f()
def test_deprecated_call_specificity(self): def test_deprecated_call_specificity(self) -> None:
other_warnings = [ other_warnings = [
Warning, Warning,
UserWarning, UserWarning,
@ -189,40 +193,40 @@ class TestDeprecatedCall:
def f(): def f():
warnings.warn(warning("hi")) warnings.warn(warning("hi"))
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
pytest.deprecated_call(f) pytest.deprecated_call(f)
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
with pytest.deprecated_call(): with pytest.deprecated_call():
f() f()
def test_deprecated_call_supports_match(self): def test_deprecated_call_supports_match(self) -> None:
with pytest.deprecated_call(match=r"must be \d+$"): with pytest.deprecated_call(match=r"must be \d+$"):
warnings.warn("value must be 42", DeprecationWarning) warnings.warn("value must be 42", DeprecationWarning)
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
with pytest.deprecated_call(match=r"must be \d+$"): with pytest.deprecated_call(match=r"must be \d+$"):
warnings.warn("this is not here", DeprecationWarning) warnings.warn("this is not here", DeprecationWarning)
class TestWarns: class TestWarns:
def test_check_callable(self): def test_check_callable(self) -> None:
source = "warnings.warn('w1', RuntimeWarning)" source = "warnings.warn('w1', RuntimeWarning)"
with pytest.raises(TypeError, match=r".* must be callable"): with pytest.raises(TypeError, match=r".* must be callable"):
pytest.warns(RuntimeWarning, source) pytest.warns(RuntimeWarning, source) # type: ignore
def test_several_messages(self): def test_several_messages(self) -> None:
# different messages, b/c Python suppresses multiple identical warnings # different messages, b/c Python suppresses multiple identical warnings
pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning)) pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning)) pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning)) pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
def test_function(self): def test_function(self) -> None:
pytest.warns( pytest.warns(
SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax" SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax"
) )
def test_warning_tuple(self): def test_warning_tuple(self) -> None:
pytest.warns( pytest.warns(
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning) (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning)
) )
@ -230,21 +234,21 @@ class TestWarns:
(RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning) (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
) )
pytest.raises( pytest.raises(
pytest.fail.Exception, Failed,
lambda: pytest.warns( lambda: pytest.warns(
(RuntimeWarning, SyntaxWarning), (RuntimeWarning, SyntaxWarning),
lambda: warnings.warn("w3", UserWarning), lambda: warnings.warn("w3", UserWarning),
), ),
) )
def test_as_contextmanager(self): def test_as_contextmanager(self) -> None:
with pytest.warns(RuntimeWarning): with pytest.warns(RuntimeWarning):
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.raises(Failed) as excinfo:
with pytest.warns(RuntimeWarning): with pytest.warns(RuntimeWarning):
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
excinfo.match( excinfo.match(
@ -252,7 +256,7 @@ class TestWarns:
r"The list of emitted warnings is: \[UserWarning\('user',?\)\]." r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
) )
with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.raises(Failed) as excinfo:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
excinfo.match( excinfo.match(
@ -260,7 +264,7 @@ class TestWarns:
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]." r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]."
) )
with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.raises(Failed) as excinfo:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
pass pass
excinfo.match( excinfo.match(
@ -269,7 +273,7 @@ class TestWarns:
) )
warning_classes = (UserWarning, FutureWarning) warning_classes = (UserWarning, FutureWarning)
with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.raises(Failed) as excinfo:
with pytest.warns(warning_classes) as warninfo: with pytest.warns(warning_classes) as warninfo:
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
warnings.warn("import", ImportWarning) warnings.warn("import", ImportWarning)
@ -286,14 +290,14 @@ class TestWarns:
) )
) )
def test_record(self): def test_record(self) -> None:
with pytest.warns(UserWarning) as record: with pytest.warns(UserWarning) as record:
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
assert len(record) == 1 assert len(record) == 1
assert str(record[0].message) == "user" assert str(record[0].message) == "user"
def test_record_only(self): def test_record_only(self) -> None:
with pytest.warns(None) as record: with pytest.warns(None) as record:
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
@ -302,7 +306,7 @@ class TestWarns:
assert str(record[0].message) == "user" assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime" assert str(record[1].message) == "runtime"
def test_record_by_subclass(self): def test_record_by_subclass(self) -> None:
with pytest.warns(Warning) as record: with pytest.warns(Warning) as record:
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
@ -325,7 +329,7 @@ class TestWarns:
assert str(record[0].message) == "user" assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime" assert str(record[1].message) == "runtime"
def test_double_test(self, testdir): def test_double_test(self, testdir) -> None:
"""If a test is run again, the warning should still be raised""" """If a test is run again, the warning should still be raised"""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -341,32 +345,32 @@ class TestWarns:
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["*2 passed in*"]) result.stdout.fnmatch_lines(["*2 passed in*"])
def test_match_regex(self): def test_match_regex(self) -> None:
with pytest.warns(UserWarning, match=r"must be \d+$"): with pytest.warns(UserWarning, match=r"must be \d+$"):
warnings.warn("value must be 42", UserWarning) warnings.warn("value must be 42", UserWarning)
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
with pytest.warns(UserWarning, match=r"must be \d+$"): with pytest.warns(UserWarning, match=r"must be \d+$"):
warnings.warn("this is not here", UserWarning) warnings.warn("this is not here", UserWarning)
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
with pytest.warns(FutureWarning, match=r"must be \d+$"): with pytest.warns(FutureWarning, match=r"must be \d+$"):
warnings.warn("value must be 42", UserWarning) warnings.warn("value must be 42", UserWarning)
def test_one_from_multiple_warns(self): def test_one_from_multiple_warns(self) -> None:
with pytest.warns(UserWarning, match=r"aaa"): with pytest.warns(UserWarning, match=r"aaa"):
warnings.warn("cccccccccc", UserWarning) warnings.warn("cccccccccc", UserWarning)
warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("bbbbbbbbbb", UserWarning)
warnings.warn("aaaaaaaaaa", UserWarning) warnings.warn("aaaaaaaaaa", UserWarning)
def test_none_of_multiple_warns(self): def test_none_of_multiple_warns(self) -> None:
with pytest.raises(pytest.fail.Exception): with pytest.raises(Failed):
with pytest.warns(UserWarning, match=r"aaa"): with pytest.warns(UserWarning, match=r"aaa"):
warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("bbbbbbbbbb", UserWarning)
warnings.warn("cccccccccc", UserWarning) warnings.warn("cccccccccc", UserWarning)
@pytest.mark.filterwarnings("ignore") @pytest.mark.filterwarnings("ignore")
def test_can_capture_previously_warned(self): def test_can_capture_previously_warned(self) -> None:
def f(): def f():
warnings.warn(UserWarning("ohai")) warnings.warn(UserWarning("ohai"))
return 10 return 10
@ -375,8 +379,8 @@ class TestWarns:
assert pytest.warns(UserWarning, f) == 10 assert pytest.warns(UserWarning, f) == 10
assert pytest.warns(UserWarning, f) == 10 assert pytest.warns(UserWarning, f) == 10
def test_warns_context_manager_with_kwargs(self): def test_warns_context_manager_with_kwargs(self) -> None:
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
with pytest.warns(UserWarning, foo="bar"): with pytest.warns(UserWarning, foo="bar"): # type: ignore
pass pass
assert "Unexpected keyword arguments" in str(excinfo.value) assert "Unexpected keyword arguments" in str(excinfo.value)

View File

@ -2,6 +2,9 @@ import inspect
import os import os
import sys import sys
import types import types
from typing import Dict
from typing import List
from typing import Tuple
import py import py
@ -11,11 +14,17 @@ from _pytest import main
from _pytest import outcomes from _pytest import outcomes
from _pytest import reports from _pytest import reports
from _pytest import runner from _pytest import runner
from _pytest.outcomes import Exit
from _pytest.outcomes import Failed
from _pytest.outcomes import OutcomeException from _pytest.outcomes import OutcomeException
from _pytest.outcomes import Skipped
if False: # TYPE_CHECKING
from typing import Type
class TestSetupState: class TestSetupState:
def test_setup(self, testdir): def test_setup(self, testdir) -> None:
ss = runner.SetupState() ss = runner.SetupState()
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
values = [1] values = [1]
@ -25,14 +34,14 @@ class TestSetupState:
ss._pop_and_teardown() ss._pop_and_teardown()
assert not values assert not values
def test_teardown_exact_stack_empty(self, testdir): def test_teardown_exact_stack_empty(self, testdir) -> None:
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
ss = runner.SetupState() ss = runner.SetupState()
ss.teardown_exact(item, None) ss.teardown_exact(item, None)
ss.teardown_exact(item, None) ss.teardown_exact(item, None)
ss.teardown_exact(item, None) ss.teardown_exact(item, None)
def test_setup_fails_and_failure_is_cached(self, testdir): def test_setup_fails_and_failure_is_cached(self, testdir) -> None:
item = testdir.getitem( item = testdir.getitem(
""" """
def setup_module(mod): def setup_module(mod):
@ -44,7 +53,7 @@ class TestSetupState:
pytest.raises(ValueError, lambda: ss.prepare(item)) pytest.raises(ValueError, lambda: ss.prepare(item))
pytest.raises(ValueError, lambda: ss.prepare(item)) pytest.raises(ValueError, lambda: ss.prepare(item))
def test_teardown_multiple_one_fails(self, testdir): def test_teardown_multiple_one_fails(self, testdir) -> None:
r = [] r = []
def fin1(): def fin1():
@ -66,7 +75,7 @@ class TestSetupState:
assert err.value.args == ("oops",) assert err.value.args == ("oops",)
assert r == ["fin3", "fin1"] assert r == ["fin3", "fin1"]
def test_teardown_multiple_fail(self, testdir): def test_teardown_multiple_fail(self, testdir) -> None:
# Ensure the first exception is the one which is re-raised. # Ensure the first exception is the one which is re-raised.
# Ideally both would be reported however. # Ideally both would be reported however.
def fin1(): def fin1():
@ -83,7 +92,7 @@ class TestSetupState:
ss._callfinalizers(item) ss._callfinalizers(item)
assert err.value.args == ("oops2",) assert err.value.args == ("oops2",)
def test_teardown_multiple_scopes_one_fails(self, testdir): def test_teardown_multiple_scopes_one_fails(self, testdir) -> None:
module_teardown = [] module_teardown = []
def fin_func(): def fin_func():
@ -103,7 +112,7 @@ class TestSetupState:
class BaseFunctionalTests: class BaseFunctionalTests:
def test_passfunction(self, testdir): def test_passfunction(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -116,7 +125,7 @@ class BaseFunctionalTests:
assert rep.outcome == "passed" assert rep.outcome == "passed"
assert not rep.longrepr assert not rep.longrepr
def test_failfunction(self, testdir): def test_failfunction(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -131,7 +140,7 @@ class BaseFunctionalTests:
assert rep.outcome == "failed" assert rep.outcome == "failed"
# assert isinstance(rep.longrepr, ReprExceptionInfo) # assert isinstance(rep.longrepr, ReprExceptionInfo)
def test_skipfunction(self, testdir): def test_skipfunction(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
import pytest import pytest
@ -151,7 +160,7 @@ class BaseFunctionalTests:
# assert rep.skipped.location.path # assert rep.skipped.location.path
# assert not rep.skipped.failurerepr # assert not rep.skipped.failurerepr
def test_skip_in_setup_function(self, testdir): def test_skip_in_setup_function(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
import pytest import pytest
@ -172,7 +181,7 @@ class BaseFunctionalTests:
assert len(reports) == 2 assert len(reports) == 2
assert reports[1].passed # teardown assert reports[1].passed # teardown
def test_failure_in_setup_function(self, testdir): def test_failure_in_setup_function(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
import pytest import pytest
@ -189,7 +198,7 @@ class BaseFunctionalTests:
assert rep.when == "setup" assert rep.when == "setup"
assert len(reports) == 2 assert len(reports) == 2
def test_failure_in_teardown_function(self, testdir): def test_failure_in_teardown_function(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
import pytest import pytest
@ -209,7 +218,7 @@ class BaseFunctionalTests:
# assert rep.longrepr.reprcrash.lineno == 3 # assert rep.longrepr.reprcrash.lineno == 3
# assert rep.longrepr.reprtraceback.reprentries # assert rep.longrepr.reprtraceback.reprentries
def test_custom_failure_repr(self, testdir): def test_custom_failure_repr(self, testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
conftest=""" conftest="""
import pytest import pytest
@ -234,7 +243,7 @@ class BaseFunctionalTests:
# assert rep.failed.where.path.basename == "test_func.py" # assert rep.failed.where.path.basename == "test_func.py"
# assert rep.failed.failurerepr == "hello" # assert rep.failed.failurerepr == "hello"
def test_teardown_final_returncode(self, testdir): def test_teardown_final_returncode(self, testdir) -> None:
rec = testdir.inline_runsource( rec = testdir.inline_runsource(
""" """
def test_func(): def test_func():
@ -245,7 +254,7 @@ class BaseFunctionalTests:
) )
assert rec.ret == 1 assert rec.ret == 1
def test_logstart_logfinish_hooks(self, testdir): def test_logstart_logfinish_hooks(self, testdir) -> None:
rec = testdir.inline_runsource( rec = testdir.inline_runsource(
""" """
import pytest import pytest
@ -262,7 +271,7 @@ class BaseFunctionalTests:
assert rep.nodeid == "test_logstart_logfinish_hooks.py::test_func" assert rep.nodeid == "test_logstart_logfinish_hooks.py::test_func"
assert rep.location == ("test_logstart_logfinish_hooks.py", 1, "test_func") assert rep.location == ("test_logstart_logfinish_hooks.py", 1, "test_func")
def test_exact_teardown_issue90(self, testdir): def test_exact_teardown_issue90(self, testdir) -> None:
rec = testdir.inline_runsource( rec = testdir.inline_runsource(
""" """
import pytest import pytest
@ -302,7 +311,7 @@ class BaseFunctionalTests:
assert reps[5].nodeid.endswith("test_func") assert reps[5].nodeid.endswith("test_func")
assert reps[5].failed assert reps[5].failed
def test_exact_teardown_issue1206(self, testdir): def test_exact_teardown_issue1206(self, testdir) -> None:
"""issue shadowing error with wrong number of arguments on teardown_method.""" """issue shadowing error with wrong number of arguments on teardown_method."""
rec = testdir.inline_runsource( rec = testdir.inline_runsource(
""" """
@ -338,7 +347,7 @@ class BaseFunctionalTests:
"TypeError: teardown_method() takes exactly 4 arguments (2 given)", "TypeError: teardown_method() takes exactly 4 arguments (2 given)",
) )
def test_failure_in_setup_function_ignores_custom_repr(self, testdir): def test_failure_in_setup_function_ignores_custom_repr(self, testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
conftest=""" conftest="""
import pytest import pytest
@ -366,7 +375,7 @@ class BaseFunctionalTests:
# assert rep.outcome.where.path.basename == "test_func.py" # assert rep.outcome.where.path.basename == "test_func.py"
# assert instanace(rep.failed.failurerepr, PythonFailureRepr) # assert instanace(rep.failed.failurerepr, PythonFailureRepr)
def test_systemexit_does_not_bail_out(self, testdir): def test_systemexit_does_not_bail_out(self, testdir) -> None:
try: try:
reports = testdir.runitem( reports = testdir.runitem(
""" """
@ -380,7 +389,7 @@ class BaseFunctionalTests:
assert rep.failed assert rep.failed
assert rep.when == "call" assert rep.when == "call"
def test_exit_propagates(self, testdir): def test_exit_propagates(self, testdir) -> None:
try: try:
testdir.runitem( testdir.runitem(
""" """
@ -389,7 +398,7 @@ class BaseFunctionalTests:
raise pytest.exit.Exception() raise pytest.exit.Exception()
""" """
) )
except pytest.exit.Exception: except Exit:
pass pass
else: else:
pytest.fail("did not raise") pytest.fail("did not raise")
@ -402,7 +411,7 @@ class TestExecutionNonForked(BaseFunctionalTests):
return f return f
def test_keyboardinterrupt_propagates(self, testdir): def test_keyboardinterrupt_propagates(self, testdir) -> None:
try: try:
testdir.runitem( testdir.runitem(
""" """
@ -424,7 +433,7 @@ class TestExecutionForked(BaseFunctionalTests):
boxed = pytest.importorskip("xdist.boxed") boxed = pytest.importorskip("xdist.boxed")
return boxed.forked_run_report return boxed.forked_run_report
def test_suicide(self, testdir): def test_suicide(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -438,7 +447,7 @@ class TestExecutionForked(BaseFunctionalTests):
class TestSessionReports: class TestSessionReports:
def test_collect_result(self, testdir): def test_collect_result(self, testdir) -> None:
col = testdir.getmodulecol( col = testdir.getmodulecol(
""" """
def test_func1(): def test_func1():
@ -461,20 +470,24 @@ class TestSessionReports:
assert res[1].name == "TestClass" assert res[1].name == "TestClass"
reporttypes = [reports.BaseReport, reports.TestReport, reports.CollectReport] reporttypes = [
reports.BaseReport,
reports.TestReport,
reports.CollectReport,
] # type: List[Type[reports.BaseReport]]
@pytest.mark.parametrize( @pytest.mark.parametrize(
"reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes]
) )
def test_report_extra_parameters(reporttype): def test_report_extra_parameters(reporttype: "Type[reports.BaseReport]") -> None:
args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:]
basekw = dict.fromkeys(args, []) basekw = dict.fromkeys(args, []) # type: Dict[str, List[object]]
report = reporttype(newthing=1, **basekw) report = reporttype(newthing=1, **basekw)
assert report.newthing == 1 assert report.newthing == 1
def test_callinfo(): def test_callinfo() -> None:
ci = runner.CallInfo.from_call(lambda: 0, "123") ci = runner.CallInfo.from_call(lambda: 0, "123")
assert ci.when == "123" assert ci.when == "123"
assert ci.result == 0 assert ci.result == 0
@ -503,7 +516,7 @@ def test_callinfo():
@pytest.mark.xfail @pytest.mark.xfail
def test_runtest_in_module_ordering(testdir): def test_runtest_in_module_ordering(testdir) -> None:
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
import pytest import pytest
@ -534,12 +547,12 @@ def test_runtest_in_module_ordering(testdir):
result.stdout.fnmatch_lines(["*2 passed*"]) result.stdout.fnmatch_lines(["*2 passed*"])
def test_outcomeexception_exceptionattributes(): def test_outcomeexception_exceptionattributes() -> None:
outcome = outcomes.OutcomeException("test") outcome = outcomes.OutcomeException("test")
assert outcome.args[0] == outcome.msg assert outcome.args[0] == outcome.msg
def test_outcomeexception_passes_except_Exception(): def test_outcomeexception_passes_except_Exception() -> None:
with pytest.raises(outcomes.OutcomeException): with pytest.raises(outcomes.OutcomeException):
try: try:
raise outcomes.OutcomeException("test") raise outcomes.OutcomeException("test")
@ -547,20 +560,22 @@ def test_outcomeexception_passes_except_Exception():
pass pass
def test_pytest_exit(): def test_pytest_exit() -> None:
with pytest.raises(pytest.exit.Exception) as excinfo: assert Exit == pytest.exit.Exception # type: ignore
with pytest.raises(Exit) as excinfo:
pytest.exit("hello") pytest.exit("hello")
assert excinfo.errisinstance(pytest.exit.Exception) assert excinfo.errisinstance(Exit)
def test_pytest_fail(): def test_pytest_fail() -> None:
with pytest.raises(pytest.fail.Exception) as excinfo: assert Failed == pytest.fail.Exception # type: ignore
with pytest.raises(Failed) as excinfo:
pytest.fail("hello") pytest.fail("hello")
s = excinfo.exconly(tryshort=True) s = excinfo.exconly(tryshort=True)
assert s.startswith("Failed") assert s.startswith("Failed")
def test_pytest_exit_msg(testdir): def test_pytest_exit_msg(testdir) -> None:
testdir.makeconftest( testdir.makeconftest(
""" """
import pytest import pytest
@ -583,7 +598,7 @@ def _strip_resource_warnings(lines):
] ]
def test_pytest_exit_returncode(testdir): def test_pytest_exit_returncode(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
"""\ """\
import pytest import pytest
@ -614,7 +629,7 @@ def test_pytest_exit_returncode(testdir):
assert result.ret == 98 assert result.ret == 98
def test_pytest_fail_notrace_runtest(testdir): def test_pytest_fail_notrace_runtest(testdir) -> None:
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run."""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -630,7 +645,7 @@ def test_pytest_fail_notrace_runtest(testdir):
result.stdout.no_fnmatch_line("*def teardown_function*") result.stdout.no_fnmatch_line("*def teardown_function*")
def test_pytest_fail_notrace_collection(testdir): def test_pytest_fail_notrace_collection(testdir) -> None:
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during collection.""" """Test pytest.fail(..., pytrace=False) does not show tracebacks during collection."""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -645,7 +660,7 @@ def test_pytest_fail_notrace_collection(testdir):
result.stdout.no_fnmatch_line("*def some_internal_function()*") result.stdout.no_fnmatch_line("*def some_internal_function()*")
def test_pytest_fail_notrace_non_ascii(testdir): def test_pytest_fail_notrace_non_ascii(testdir) -> None:
"""Fix pytest.fail with pytrace=False with non-ascii characters (#1178). """Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
This tests with native and unicode strings containing non-ascii chars. This tests with native and unicode strings containing non-ascii chars.
@ -663,7 +678,7 @@ def test_pytest_fail_notrace_non_ascii(testdir):
result.stdout.no_fnmatch_line("*def test_hello*") result.stdout.no_fnmatch_line("*def test_hello*")
def test_pytest_no_tests_collected_exit_status(testdir): def test_pytest_no_tests_collected_exit_status(testdir) -> None:
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["*collected 0 items*"]) result.stdout.fnmatch_lines(["*collected 0 items*"])
assert result.ret == main.ExitCode.NO_TESTS_COLLECTED assert result.ret == main.ExitCode.NO_TESTS_COLLECTED
@ -685,16 +700,17 @@ def test_pytest_no_tests_collected_exit_status(testdir):
assert result.ret == main.ExitCode.NO_TESTS_COLLECTED assert result.ret == main.ExitCode.NO_TESTS_COLLECTED
def test_exception_printing_skip(): def test_exception_printing_skip() -> None:
assert Skipped == pytest.skip.Exception # type: ignore
try: try:
pytest.skip("hello") pytest.skip("hello")
except pytest.skip.Exception: except Skipped:
excinfo = _pytest._code.ExceptionInfo.from_current() excinfo = _pytest._code.ExceptionInfo.from_current()
s = excinfo.exconly(tryshort=True) s = excinfo.exconly(tryshort=True)
assert s.startswith("Skipped") assert s.startswith("Skipped")
def test_importorskip(monkeypatch): def test_importorskip(monkeypatch) -> None:
importorskip = pytest.importorskip importorskip = pytest.importorskip
def f(): def f():
@ -705,45 +721,49 @@ def test_importorskip(monkeypatch):
assert sysmod is sys assert sysmod is sys
# path = pytest.importorskip("os.path") # path = pytest.importorskip("os.path")
# assert path == os.path # assert path == os.path
excinfo = pytest.raises(pytest.skip.Exception, f) excinfo = pytest.raises(Skipped, f)
path = py.path.local(excinfo.getrepr().reprcrash.path) assert excinfo is not None
excrepr = excinfo.getrepr()
assert excrepr is not None
assert excrepr.reprcrash is not None
path = py.path.local(excrepr.reprcrash.path)
# check that importorskip reports the actual call # check that importorskip reports the actual call
# in this test the test_runner.py file # in this test the test_runner.py file
assert path.purebasename == "test_runner" assert path.purebasename == "test_runner"
pytest.raises(SyntaxError, pytest.importorskip, "x y z") pytest.raises(SyntaxError, pytest.importorskip, "x y z")
pytest.raises(SyntaxError, pytest.importorskip, "x=y") pytest.raises(SyntaxError, pytest.importorskip, "x=y")
mod = types.ModuleType("hello123") mod = types.ModuleType("hello123")
mod.__version__ = "1.3" mod.__version__ = "1.3" # type: ignore
monkeypatch.setitem(sys.modules, "hello123", mod) monkeypatch.setitem(sys.modules, "hello123", mod)
with pytest.raises(pytest.skip.Exception): with pytest.raises(Skipped):
pytest.importorskip("hello123", minversion="1.3.1") pytest.importorskip("hello123", minversion="1.3.1")
mod2 = pytest.importorskip("hello123", minversion="1.3") mod2 = pytest.importorskip("hello123", minversion="1.3")
assert mod2 == mod assert mod2 == mod
except pytest.skip.Exception: except Skipped:
print(_pytest._code.ExceptionInfo.from_current()) print(_pytest._code.ExceptionInfo.from_current())
pytest.fail("spurious skip") pytest.fail("spurious skip")
def test_importorskip_imports_last_module_part(): def test_importorskip_imports_last_module_part() -> None:
ospath = pytest.importorskip("os.path") ospath = pytest.importorskip("os.path")
assert os.path == ospath assert os.path == ospath
def test_importorskip_dev_module(monkeypatch): def test_importorskip_dev_module(monkeypatch) -> None:
try: try:
mod = types.ModuleType("mockmodule") mod = types.ModuleType("mockmodule")
mod.__version__ = "0.13.0.dev-43290" mod.__version__ = "0.13.0.dev-43290" # type: ignore
monkeypatch.setitem(sys.modules, "mockmodule", mod) monkeypatch.setitem(sys.modules, "mockmodule", mod)
mod2 = pytest.importorskip("mockmodule", minversion="0.12.0") mod2 = pytest.importorskip("mockmodule", minversion="0.12.0")
assert mod2 == mod assert mod2 == mod
with pytest.raises(pytest.skip.Exception): with pytest.raises(Skipped):
pytest.importorskip("mockmodule1", minversion="0.14.0") pytest.importorskip("mockmodule1", minversion="0.14.0")
except pytest.skip.Exception: except Skipped:
print(_pytest._code.ExceptionInfo.from_current()) print(_pytest._code.ExceptionInfo.from_current())
pytest.fail("spurious skip") pytest.fail("spurious skip")
def test_importorskip_module_level(testdir): def test_importorskip_module_level(testdir) -> None:
"""importorskip must be able to skip entire modules when used at module level""" """importorskip must be able to skip entire modules when used at module level"""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -758,7 +778,7 @@ def test_importorskip_module_level(testdir):
result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"])
def test_importorskip_custom_reason(testdir): def test_importorskip_custom_reason(testdir) -> None:
"""make sure custom reasons are used""" """make sure custom reasons are used"""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -774,7 +794,7 @@ def test_importorskip_custom_reason(testdir):
result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"])
def test_pytest_cmdline_main(testdir): def test_pytest_cmdline_main(testdir) -> None:
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
import pytest import pytest
@ -792,7 +812,7 @@ def test_pytest_cmdline_main(testdir):
assert ret == 0 assert ret == 0
def test_unicode_in_longrepr(testdir): def test_unicode_in_longrepr(testdir) -> None:
testdir.makeconftest( testdir.makeconftest(
"""\ """\
import pytest import pytest
@ -815,7 +835,7 @@ def test_unicode_in_longrepr(testdir):
assert "UnicodeEncodeError" not in result.stderr.str() assert "UnicodeEncodeError" not in result.stderr.str()
def test_failure_in_setup(testdir): def test_failure_in_setup(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
def setup_module(): def setup_module():
@ -828,7 +848,7 @@ def test_failure_in_setup(testdir):
result.stdout.no_fnmatch_line("*def setup_module*") result.stdout.no_fnmatch_line("*def setup_module*")
def test_makereport_getsource(testdir): def test_makereport_getsource(testdir) -> None:
testdir.makepyfile( testdir.makepyfile(
""" """
def test_foo(): def test_foo():
@ -841,17 +861,17 @@ def test_makereport_getsource(testdir):
result.stdout.fnmatch_lines(["*else: assert False*"]) result.stdout.fnmatch_lines(["*else: assert False*"])
def test_makereport_getsource_dynamic_code(testdir, monkeypatch): def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None:
"""Test that exception in dynamically generated code doesn't break getting the source line.""" """Test that exception in dynamically generated code doesn't break getting the source line."""
import inspect import inspect
original_findsource = inspect.findsource original_findsource = inspect.findsource
def findsource(obj, *args, **kwargs): def findsource(obj):
# Can be triggered by dynamically created functions # Can be triggered by dynamically created functions
if obj.__name__ == "foo": if obj.__name__ == "foo":
raise IndexError() raise IndexError()
return original_findsource(obj, *args, **kwargs) return original_findsource(obj)
monkeypatch.setattr(inspect, "findsource", findsource) monkeypatch.setattr(inspect, "findsource", findsource)
@ -872,7 +892,7 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch):
result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"])
def test_store_except_info_on_error(): def test_store_except_info_on_error() -> None:
""" Test that upon test failure, the exception info is stored on """ Test that upon test failure, the exception info is stored on
sys.last_traceback and friends. sys.last_traceback and friends.
""" """
@ -891,6 +911,7 @@ def test_store_except_info_on_error():
pass pass
# Check that exception info is stored on sys # Check that exception info is stored on sys
assert sys.last_type is IndexError assert sys.last_type is IndexError
assert isinstance(sys.last_value, IndexError)
assert sys.last_value.args[0] == "TEST" assert sys.last_value.args[0] == "TEST"
assert sys.last_traceback assert sys.last_traceback
@ -902,8 +923,8 @@ def test_store_except_info_on_error():
assert not hasattr(sys, "last_traceback") assert not hasattr(sys, "last_traceback")
def test_current_test_env_var(testdir, monkeypatch): def test_current_test_env_var(testdir, monkeypatch) -> None:
pytest_current_test_vars = [] pytest_current_test_vars = [] # type: List[Tuple[str, str]]
monkeypatch.setattr( monkeypatch.setattr(
sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False
) )
@ -942,7 +963,7 @@ class TestReportContents:
def getrunner(self): def getrunner(self):
return lambda item: runner.runtestprotocol(item, log=False) return lambda item: runner.runtestprotocol(item, log=False)
def test_longreprtext_pass(self, testdir): def test_longreprtext_pass(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -952,7 +973,7 @@ class TestReportContents:
rep = reports[1] rep = reports[1]
assert rep.longreprtext == "" assert rep.longreprtext == ""
def test_longreprtext_failure(self, testdir): def test_longreprtext_failure(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -963,7 +984,7 @@ class TestReportContents:
rep = reports[1] rep = reports[1]
assert "assert 1 == 4" in rep.longreprtext assert "assert 1 == 4" in rep.longreprtext
def test_captured_text(self, testdir): def test_captured_text(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
import pytest import pytest
@ -993,7 +1014,7 @@ class TestReportContents:
assert call.capstderr == "setup: stderr\ncall: stderr\n" assert call.capstderr == "setup: stderr\ncall: stderr\n"
assert teardown.capstderr == "setup: stderr\ncall: stderr\nteardown: stderr\n" assert teardown.capstderr == "setup: stderr\ncall: stderr\nteardown: stderr\n"
def test_no_captured_text(self, testdir): def test_no_captured_text(self, testdir) -> None:
reports = testdir.runitem( reports = testdir.runitem(
""" """
def test_func(): def test_func():
@ -1005,10 +1026,10 @@ class TestReportContents:
assert rep.capstderr == "" assert rep.capstderr == ""
def test_outcome_exception_bad_msg(): def test_outcome_exception_bad_msg() -> None:
"""Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)""" """Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)"""
def func(): def func() -> None:
pass pass
expected = ( expected = (
@ -1016,5 +1037,5 @@ def test_outcome_exception_bad_msg():
"Perhaps you meant to use a mark?" "Perhaps you meant to use a mark?"
) )
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
OutcomeException(func) OutcomeException(func) # type: ignore
assert str(excinfo.value) == expected assert str(excinfo.value) == expected

View File

@ -167,11 +167,13 @@ def test_stop_on_collection_errors(broken_testdir, broken_first):
result.stdout.fnmatch_lines("*error during collection*") result.stdout.fnmatch_lines("*error during collection*")
def test_xfail_handling(testdir): def test_xfail_handling(testdir, monkeypatch):
"""Ensure normal xfail is ignored, and strict xfail interrupts the session in sw mode """Ensure normal xfail is ignored, and strict xfail interrupts the session in sw mode
(#5547) (#5547)
""" """
monkeypatch.setattr("sys.dont_write_bytecode", True)
contents = """ contents = """
import pytest import pytest
def test_a(): pass def test_a(): pass
@ -205,10 +207,6 @@ def test_xfail_handling(testdir):
] ]
) )
# because we are writing to the same file, mtime might not be affected enough to
# invalidate the cache, making this next run flaky
if testdir.tmpdir.join("__pycache__").exists():
testdir.tmpdir.join("__pycache__").remove()
testdir.makepyfile(contents.format(assert_value="0", strict="True")) testdir.makepyfile(contents.format(assert_value="0", strict="True"))
result = testdir.runpytest("--sw", "-v") result = testdir.runpytest("--sw", "-v")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(

View File

@ -3,6 +3,7 @@ terminal reporting of the full testing process.
""" """
import collections import collections
import os import os
import re
import sys import sys
import textwrap import textwrap
from io import StringIO from io import StringIO
@ -21,10 +22,15 @@ from _pytest.terminal import getreportopt
from _pytest.terminal import TerminalReporter from _pytest.terminal import TerminalReporter
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
RED = r"\x1b\[31m"
GREEN = r"\x1b\[32m" COLORS = {
YELLOW = r"\x1b\[33m" "red": "\x1b[31m",
RESET = r"\x1b\[0m" "green": "\x1b[32m",
"yellow": "\x1b[33m",
"bold": "\x1b[1m",
"reset": "\x1b[0m",
}
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
class Option: class Option:
@ -623,7 +629,7 @@ class TestTerminalFunctional:
if request.config.pluginmanager.list_plugin_distinfo(): if request.config.pluginmanager.list_plugin_distinfo():
result.stdout.fnmatch_lines(["plugins: *"]) result.stdout.fnmatch_lines(["plugins: *"])
def test_header(self, testdir, request): def test_header(self, testdir):
testdir.tmpdir.join("tests").ensure_dir() testdir.tmpdir.join("tests").ensure_dir()
testdir.tmpdir.join("gui").ensure_dir() testdir.tmpdir.join("gui").ensure_dir()
@ -709,7 +715,7 @@ class TestTerminalFunctional:
""" """
) )
def test_verbose_reporting(self, verbose_testfile, testdir, pytestconfig): def test_verbose_reporting(self, verbose_testfile, testdir):
result = testdir.runpytest( result = testdir.runpytest(
verbose_testfile, "-v", "-Walways::pytest.PytestWarning" verbose_testfile, "-v", "-Walways::pytest.PytestWarning"
) )
@ -879,10 +885,70 @@ def test_pass_output_reporting(testdir):
def test_color_yes(testdir): def test_color_yes(testdir):
testdir.makepyfile("def test_this(): assert 1") p1 = testdir.makepyfile(
result = testdir.runpytest("--color=yes") """
assert "test session starts" in result.stdout.str() def fail():
assert "\x1b[1m" in result.stdout.str() assert 0
def test_this():
fail()
"""
)
result = testdir.runpytest("--color=yes", str(p1))
if sys.version_info < (3, 6):
# py36 required for ordered markup
output = result.stdout.str()
assert "test session starts" in output
assert "\x1b[1m" in output
return
result.stdout.fnmatch_lines(
[
line.format(**COLORS).replace("[", "[[]")
for line in [
"{bold}=*= test session starts =*={reset}",
"collected 1 item",
"",
"test_color_yes.py {red}F{reset}{red} * [100%]{reset}",
"",
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"",
"{bold} def test_this():{reset}",
"{bold}> fail(){reset}",
"",
"{bold}{red}test_color_yes.py{reset}:5: ",
"_ _ * _ _*",
"",
"{bold} def fail():{reset}",
"{bold}> assert 0{reset}",
"{bold}{red}E assert 0{reset}",
"",
"{bold}{red}test_color_yes.py{reset}:2: AssertionError",
"{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
]
]
)
result = testdir.runpytest("--color=yes", "--tb=short", str(p1))
result.stdout.fnmatch_lines(
[
line.format(**COLORS).replace("[", "[[]")
for line in [
"{bold}=*= test session starts =*={reset}",
"collected 1 item",
"",
"test_color_yes.py {red}F{reset}{red} * [100%]{reset}",
"",
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"{bold}{red}test_color_yes.py{reset}:5: in test_this",
"{bold} fail(){reset}",
"{bold}{red}test_color_yes.py{reset}:2: in fail",
"{bold} assert 0{reset}",
"{bold}{red}E assert 0{reset}",
"{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
]
]
)
def test_color_no(testdir): def test_color_no(testdir):
@ -994,7 +1060,7 @@ def test_tbstyle_short(testdir):
assert "assert x" in s assert "assert x" in s
def test_traceconfig(testdir, monkeypatch): def test_traceconfig(testdir):
result = testdir.runpytest("--traceconfig") result = testdir.runpytest("--traceconfig")
result.stdout.fnmatch_lines(["*active plugins*"]) result.stdout.fnmatch_lines(["*active plugins*"])
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
@ -1599,18 +1665,15 @@ class TestProgressOutputStyle:
def test_foobar(i): raise ValueError() def test_foobar(i): raise ValueError()
""", """,
) )
output = testdir.runpytest() result = testdir.runpytest()
output.stdout.re_match_lines( result.stdout.re_match_lines(
[ [
r"test_bar.py ({green}\.{reset}){{10}}{green} \s+ \[ 50%\]{reset}".format( line.format(**RE_COLORS)
green=GREEN, reset=RESET for line in [
), r"test_bar.py ({green}\.{reset}){{10}}{green} \s+ \[ 50%\]{reset}",
r"test_foo.py ({green}\.{reset}){{5}}{yellow} \s+ \[ 75%\]{reset}".format( r"test_foo.py ({green}\.{reset}){{5}}{yellow} \s+ \[ 75%\]{reset}",
green=GREEN, reset=RESET, yellow=YELLOW r"test_foobar.py ({red}F{reset}){{5}}{red} \s+ \[100%\]{reset}",
), ]
r"test_foobar.py ({red}F{reset}){{5}}{red} \s+ \[100%\]{reset}".format(
reset=RESET, red=RED
),
] ]
) )

25
tox.ini
View File

@ -20,11 +20,12 @@ envlist =
[testenv] [testenv]
commands = commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:{env:_PYTEST_TOX_DEFAULT_POSARGS:}} {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:{env:_PYTEST_TOX_DEFAULT_POSARGS:}}
doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest
coverage: coverage combine coverage: coverage combine
coverage: coverage report -m coverage: coverage report -m
passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS TERM passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS TERM
setenv = setenv =
_PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:}
# Configuration to run with coverage similar to CI, e.g. # Configuration to run with coverage similar to CI, e.g.
# "tox -e py37-coverage". # "tox -e py37-coverage".
@ -33,6 +34,8 @@ setenv =
coverage: COVERAGE_FILE={toxinidir}/.coverage coverage: COVERAGE_FILE={toxinidir}/.coverage
coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc
doctesting: _PYTEST_TOX_POSARGS_DOCTESTING=doc/en
nobyte: PYTHONDONTWRITEBYTECODE=1 nobyte: PYTHONDONTWRITEBYTECODE=1
lsof: _PYTEST_TOX_POSARGS_LSOF=--lsof lsof: _PYTEST_TOX_POSARGS_LSOF=--lsof
@ -40,6 +43,7 @@ setenv =
xdist: _PYTEST_TOX_POSARGS_XDIST=-n auto xdist: _PYTEST_TOX_POSARGS_XDIST=-n auto
extras = testing extras = testing
deps = deps =
doctesting: PyYAML
oldattrs: attrs==17.4.0 oldattrs: attrs==17.4.0
oldattrs: hypothesis<=4.38.1 oldattrs: hypothesis<=4.38.1
numpy: numpy numpy: numpy
@ -59,6 +63,15 @@ commands = pre-commit run --all-files --show-diff-on-failure {posargs:}
extras = checkqa-mypy, testing extras = checkqa-mypy, testing
commands = mypy {posargs:src testing} commands = mypy {posargs:src testing}
[testenv:mypy-diff]
extras = checkqa-mypy, testing
deps =
lxml
diff-cover
commands =
-mypy --cobertura-xml-report {envtmpdir} {posargs:src testing}
diff-cover --fail-under=100 --compare-branch={env:DIFF_BRANCH:origin/{env:GITHUB_BASE_REF:master}} {envtmpdir}/cobertura.xml
[testenv:docs] [testenv:docs]
basepython = python3 basepython = python3
usedevelop = True usedevelop = True
@ -81,16 +94,6 @@ deps = -r{toxinidir}/doc/en/requirements.txt
commands = commands =
sphinx-build -W -q --keep-going -b linkcheck . _build sphinx-build -W -q --keep-going -b linkcheck . _build
[testenv:doctesting]
basepython = python3
skipsdist = True
deps =
{[testenv]deps}
PyYAML
commands =
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest
[testenv:regen] [testenv:regen]
changedir = doc/en changedir = doc/en
skipsdist = True skipsdist = True